我希望我的REST客户端使用Spring Web的RestTemplate
对URL参数中的所有特殊字符(不仅是非法字符)进行%编码。 Spring Web's documentation指出可以通过将DefaultUriBuilderFactory
使用的RestTemplate
与setEncodingMode(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.RELEASE
,org.springframework:spring-web:5.0.7.RELEASE
,org.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?
答案 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);
}
}