问题:
在实际项目中,对传入的图片url进行下载,使用的是RestTemplate的exchange方法,具体如下:
使用以下RestTemplate的方法:
Sring url = "http://10.64.203.183:6120/pic?=d6ei2a4i9c84*33c-793=11i5m*ep5t9d5=*2pdi=*1s5i2=94b8i5d2e*14b863328-aa2e959-1b246b-43s=10d3z83";
ResponseEntity responseEntity = restTemplate
.exchange(url, HttpMethod.GET, null, byte[].class);
用这种方式调用请求,抓拍可看出url中的特殊字符被转义:
http://10.64.203.183:6120/pic?d6ei2a4i9c84*33c-793=11i5m*ep5t9d5%3D*2pdi%3D*1s5i2%3D94b8i5d2e*14b863328-aa2e959-1b246b-43s%3D10d3z83
由于第三方的图片服务器,没有对请求的url进行解码,因此不识别转义后的url导致下载失败。
解决方法:
如果不希望被转码,则可使用
public
该方法,直接传入URI对象。
该对象可如下进行组装:
Sring url = "http://10.64.203.183:6120/pic?=d6ei2a4i9c84*33c-793=11i5m*ep5t9d5=*2pdi=*1s5i2=94b8i5d2e*14b863328-aa2e959-1b246b-43s=10d3z83";
URI uri = new URI(url);
restTemplate.exchange(url, HttpMethod.GET, null, byte[].class);
这样,url就不会被encode成utf-8,不会将特殊字符转义,解决了下载失败的问题。
源码分析
以下是url被转码的源码分析。
对应的包为
Sring url = "http://10.64.203.183:6120/pic?=d6ei2a4i9c84*33c-793=11i5m*ep5t9d5=*2pdi=*1s5i2=94b8i5d2e*14b863328-aa2e959-1b246b-43s=10d3z83";
ResponseEntity responseEntity = restTemplate
.exchange(url, HttpMethod.GET, null, byte[].class);
对应源码的调用关系:
RestTemplate.class:
public ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity> requestEntity, Class responseType, Map uriVariables) throws RestClientException {
RequestCallback requestCallback = this.httpEntityCallback(requestEntity, responseType);
ResponseExtractor> responseExtractor = this.responseEntityExtractor(responseType);
return (ResponseEntity)nonNull(this.execute(url, method, requestCallback, responseExtractor, uriVariables));
}
RestTemplate.class:
@Nullable
public T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor, Map uriVariables) throws RestClientException {
URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
return this.doExecute(expanded, method, requestCallback, responseExtractor);
}
从execute函数可看出,源码中会将url String对象转成URI对象。
转换的源码如下:
DefaultUriBuilderFactory.class:
public URI expand(String uriTemplate, Map uriVars) {
return this.uriString(uriTemplate).build(uriVars);
}
我们来看下this.uriString(uriTemplate),返回的是UriBuilder 对象,
public UriBuilder uriString(String uriTemplate) {
return new DefaultUriBuilderFactory.DefaultUriBuilder(uriTemplate);
}
private class DefaultUriBuilder implements UriBuilder:
public DefaultUriBuilder(String uriTemplate) {
this.uriComponentsBuilder = this.initUriComponentsBuilder(uriTemplate);
}
private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
UriComponentsBuilder result;
if (!StringUtils.hasLength(uriTemplate)) {
result = DefaultUriBuilderFactory.this.baseUri != null ? DefaultUriBuilderFactory.this.baseUri.cloneBuilder() : UriComponentsBuilder.newInstance();
} else if (DefaultUriBuilderFactory.this.baseUri != null) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate);
UriComponents uri = builder.build();
result = uri.getHost() == null ? DefaultUriBuilderFactory.this.baseUri.cloneBuilder().uriComponents(uri) : builder;
} else {
result = UriComponentsBuilder.fromUriString(uriTemplate);
}
if (DefaultUriBuilderFactory.this.encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES)) {
result.encode();
}
this.parsePathIfNecessary(result);
return result;
}
result.encode();该语句设置了该对象编码格式:
UriComponentsBuilder.class
public final UriComponentsBuilder encode() {
return this.encode(StandardCharsets.UTF_8);
}
public UriComponentsBuilder encode(Charset charset) {
this.encodeTemplate = true;
this.charset = charset;
return this;
}
至此,获取到一个UriBuilder对象,且该对象中的charset设置成utf-8。
以上代码,我们解释到了this.uriString(uriTemplate),返回的是UriBuilder 对象,接下来我们看this.uriString(uriTemplate).build(uriVars)中build(uriVars)。
UriComponentsBuilder.class
public URI build(Map uriVariables) {
return this.buildInternal(UriComponentsBuilder.EncodingHint.ENCODE_TEMPLATE).expand(uriVariables).toUri();
}
/**
被上以函数调用,传入的hint是UriComponentsBuilder.EncodingHint.ENCODE_TEMPLATE
*/
private UriComponents buildInternal(UriComponentsBuilder.EncodingHint hint) {
Object result;
if (this.ssp != null) {
result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
} else {
//走到该逻辑
HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, hint == UriComponentsBuilder.EncodingHint.FULLY_ENCODED);
//下面的判断为真,因此result赋值为uric.encodeTemplate(this.charset),其中this.charset在上面已被设置成utf-8
result = hint == UriComponentsBuilder.EncodingHint.ENCODE_TEMPLATE ? uric.encodeTemplate(this.charset) : uric;
}
if (!this.uriVariables.isEmpty()) {
result = ((UriComponents)result).expand((name) -> {
return this.uriVariables.getOrDefault(name, UriTemplateVariables.SKIP_VALUE);
});
}
return (UriComponents)result;
}
HierarchicalUriComponents.class
/**
该对象将对象中的内容均进行utf-8编码
*/
HierarchicalUriComponents encodeTemplate(Charset charset) {
if (this.encodeState.isEncoded()) {
return this;
} else {
this.variableEncoder = (value) -> {
return encodeUriComponent(value, charset, HierarchicalUriComponents.Type.URI);
};
HierarchicalUriComponents.UriTemplateEncoder encoder = new HierarchicalUriComponents.UriTemplateEncoder(charset);
String schemeTo = this.getScheme() != null ? encoder.apply(this.getScheme(), HierarchicalUriComponents.Type.SCHEME) : null;
String fragmentTo = this.getFragment() != null ? encoder.apply(this.getFragment(), HierarchicalUriComponents.Type.FRAGMENT) : null;
String userInfoTo = this.getUserInfo() != null ? encoder.apply(this.getUserInfo(), HierarchicalUriComponents.Type.USER_INFO) : null;
String hostTo = this.getHost() != null ? encoder.apply(this.getHost(), this.getHostType()) : null;
HierarchicalUriComponents.PathComponent pathTo = this.path.encode(encoder);
MultiValueMap queryParamsTo = this.encodeQueryParams(encoder);
return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo, hostTo, this.port, pathTo, queryParamsTo, HierarchicalUriComponents.EncodeState.TEMPLATE_ENCODED, this.variableEncoder);
}
}
如上,因此调用RestTemplate类的public
这样,传输过程中特殊字符就不会被转义