如何让RestTemplate使用UriComponents和EncodingMode.VALUES_ONLY对所有字符进行编码?

时间:2018-07-09 08:42:45

标签: java spring rest

我希望我的REST客户端使用Spring Web的RestTemplate对URL参数中的所有特殊字符(不仅是非法字符)进行%编码。 Spring Web's documentation指出可以通过将DefaultUriBuilderFactory使用的RestTemplatesetEncodingMode(EncodingMode.VALUES_ONLY)配置来更改编码方法:

String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

should“应用于UriUtils.encode(String, Charset)到每个URI变量值”,这将“对RFC中定义的URI中任何地方非法或具有保留含义的所有字符进行编码” 3986”。

我编写了以下测试用例,以尝试证明更改为EncodingMode.VALUES_ONLY并没有达到预期的效果。 (使用依赖项org.springframework.boot:spring-boot-starter:2.0.3.RELEASEorg.springframework:spring-web:5.0.7.RELEASEorg.springframework.boot:spring-boot-starter-test:2.0.3.RELEASE执行)

package com.example.demo.encoding;

import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

import java.nio.charset.StandardCharsets;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;

@RunWith(SpringRunner.class)
@RestClientTest(DemoClient.class)
public class EncodingTest {
  @Autowired private MockRestServiceServer mockServer;
  @Autowired private DemoClient client;

  @Test
  public void encodeAllCharactersInParameter() {
    mockServer.expect(requestTo(encodedQueryUrl("https://host", "+:/")))
      .andExpect(method(HttpMethod.GET))
      .andRespond(withSuccess());
    client.request("https://host", "+:/");
    mockServer.verify();
  }

  private String encodedQueryUrl(final String baseUrl, final String parameter) {
    return String.format("%s?parameter=%s", baseUrl,
      UriUtils.encode(parameter, StandardCharsets.UTF_8));
  }
}

@Component
class DemoClient {
  private final RestTemplate restTemplate;

  public DemoClient(RestTemplateBuilder restTemplateBuilder) {
    DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
    factory.setEncodingMode(EncodingMode.VALUES_ONLY);
    restTemplateBuilder.uriTemplateHandler(factory);
    this.restTemplate = restTemplateBuilder.build();
  }

  public Object request(final String url, final String parameter) {
    UriComponents queryUrl = UriComponentsBuilder.fromHttpUrl(url)
      .queryParam("parameter", parameter).build().encode();
    return restTemplate.getForObject(queryUrl.toUri(), Object.class);
  }
}

该测试失败,并显示java.lang.AssertionError: Request URI expected:<https://host?parameter=%2B%3A%2F> but was:<https://host?parameter=+:/>。那我在做什么错?是Spring Framework中的错误,还是MockRestServiceServer在验证期望之前解码URL?

2 个答案:

答案 0 :(得分:0)

示例中的两个问题:

一个,请求方法在外部准备和编码java.net.URI,因此RestTemplate并不是准备它的人。您需要传递一个带有URI变量的URI模板,以便RestTemplate有机会准备URI并进行编码。例如:

public Object request(final String url, final String parameter) {
    String urlString = UriComponentsBuilder.fromHttpUrl(url)
            .queryParam("parameter", "{param}")
            .build()
            .toUriString();
    return restTemplate.getForObject(urlString, Object.class, parameter);
}

或者只是让请求使用URI模板字符串:

public Object request(final String url) {
    return restTemplate.getForObject(url, Object.class, parameter);
}

// then invoke like this...
request("https://host?parameter={param}");

二,RestTemplateBuilder#uriTemplateHandler返回一个新实例,因此您需要使用该实例才能使配置更改生效:

DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
restTemplateBuilder = restTemplateBuilder.uriTemplateHandler(factory); // <<<< see here
this.restTemplate = restTemplateBuilder.build();

通过上述更改,它可以正常工作。

请注意,https://jira.spring.io/browse/SPR-17039将使使用UriComponentsBuilder达到相同的效果更加容易,因此请在此处检查更新。

答案 1 :(得分:0)

更正的示例:

package com.example.demo.encoding;

import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

import java.nio.charset.StandardCharsets;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;

@RunWith(SpringRunner.class)
@RestClientTest(DemoClient.class)
public class EncodingTest {
  @Autowired private MockRestServiceServer mockServer;
  @Autowired private DemoClient client;

  @Test
  public void encodeAllCharactersInParameter() {
    mockServer.expect(requestTo(encodedQueryUrl("https://host", "+:/")))
      .andExpect(method(HttpMethod.GET))
      .andRespond(withSuccess());
    client.request("https://host", "+:/");
    mockServer.verify();
  }

  private String encodedQueryUrl(final String baseUrl, final String parameter) {
    return String.format("%s?parameter=%s", baseUrl,
      UriUtils.encode(parameter, StandardCharsets.UTF_8));
  }
}

@Component
class DemoClient {
  private final RestTemplate restTemplate;

  public DemoClient(RestTemplateBuilder restTemplateBuilder) {
    DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
    factory.setEncodingMode(EncodingMode.VALUES_ONLY);
    this.restTemplate = restTemplateBuilder.uriTemplateHandler(factory).build();
  }

  public Object request(final String url, final String parameter) {
    String urlString = UriComponentsBuilder.fromHttpUrl(url)
      .queryParam("parameter", "{param}").build().toUriString();
    return restTemplate.getForObject(urlString, Object.class, parameter);
  }
}