Spring RestTemplate的URLEncode分析
一、请求的url(字符串类型)会自动URLEncode
测试代码:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForEntity("https://www.baidu.com?r=编码", String.class);
控制台日志:
源码分析:
通过以下这段代码,将url字符串进行URLEncode
URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
具体流程:
- 通过url生成DefaultUriBuilderFactory.DefaultUriBuilder对象
new DefaultUriBuilderFactory.DefaultUriBuilder(uriTemplate)
-
调用DefaultUriBuilderFactory.DefaultUriBuilder的build方法URI
2.1 通过UriComponents的实现类HierarchicalUriComponents已分层的形式解析生成HierarchicalUriComponents对象
this.expandInternal(new UriComponents.VarArgsTemplateVariables(uriVariableValues));
protected HierarchicalUriComponents expandInternal(UriTemplateVariables uriVariables) { Assert.state(!this.encodeState.equals(HierarchicalUriComponents.EncodeState.FULLY_ENCODED), "URI components already encoded, and could not possibly contain '{' or '}'."); String schemeTo = expandUriComponent(this.getScheme(), uriVariables, this.variableEncoder); String userInfoTo = expandUriComponent(this.userInfo, uriVariables, this.variableEncoder); String hostTo = expandUriComponent(this.host, uriVariables, this.variableEncoder); String portTo = expandUriComponent(this.port, uriVariables, this.variableEncoder); HierarchicalUriComponents.PathComponent pathTo = this.path.expand(uriVariables, this.variableEncoder); MultiValueMap<String, String> queryParamsTo = this.expandQueryParams(uriVariables); String fragmentTo = expandUriComponent(this.getFragment(), uriVariables, this.variableEncoder); return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo, hostTo, portTo, pathTo, queryParamsTo, this.encodeState, this.variableEncoder); }
2.2 对已封装好的Uri组件进行编码生成URI对象
this.createUri(uric);
private URI createUri(UriComponents uric) { if (DefaultUriBuilderFactory.this.encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT)) { uric = uric.encode(); } return URI.create(uric.toString()); }
2.3 对Uri各部分进行编码
public HierarchicalUriComponents encode(Charset charset) { if (this.encodeState.isEncoded()) { return this; } else { String scheme = this.getScheme(); String fragment = this.getFragment(); String schemeTo = scheme != null ? encodeUriComponent(scheme, charset, HierarchicalUriComponents.Type.SCHEME) : null; String fragmentTo = fragment != null ? encodeUriComponent(fragment, charset, HierarchicalUriComponents.Type.FRAGMENT) : null; String userInfoTo = this.userInfo != null ? encodeUriComponent(this.userInfo, charset, HierarchicalUriComponents.Type.USER_INFO) : null; String hostTo = this.host != null ? encodeUriComponent(this.host, charset, this.getHostType()) : null; BiFunction<String, HierarchicalUriComponents.Type, String> encoder = (s, type) -> { return encodeUriComponent(s, charset, type); }; HierarchicalUriComponents.PathComponent pathTo = this.path.encode(encoder); MultiValueMap<String, String> queryParamsTo = this.encodeQueryParams(encoder); return new HierarchicalUriComponents(schemeTo, fragmentTo, userInfoTo, hostTo, this.port, pathTo, queryParamsTo, HierarchicalUriComponents.EncodeState.FULLY_ENCODED, (UnaryOperator)null); } }
具体编码(核心逻辑:判断字符是否需要编码,如果需要就进行URLEncode):
static String encodeUriComponent(String source, Charset charset, HierarchicalUriComponents.Type type) { if (!StringUtils.hasLength(source)) { return source; } else { Assert.notNull(charset, "Charset must not be null"); Assert.notNull(type, "Type must not be null"); byte[] bytes = source.getBytes(charset); boolean original = true; byte[] var5 = bytes; int var6 = bytes.length; int var7; for(var7 = 0; var7 < var6; ++var7) { byte b = var5[var7]; if (!type.isAllowed(b)) { original = false; break; } } if (original) { return source; } else { ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length); byte[] var13 = bytes; var7 = bytes.length; for(int var14 = 0; var14 < var7; ++var14) { byte b = var13[var14]; if (type.isAllowed(b)) { baos.write(b); } else { baos.write(37); char hex1 = Character.toUpperCase(Character.forDigit(b >> 4 & 15, 16)); char hex2 = Character.toUpperCase(Character.forDigit(b & 15, 16)); baos.write(hex1); baos.write(hex2); } } return StreamUtils.copyToString(baos, charset); } } }
二、RestTemplate的Encode与java.net.URLEncode不完全一致
Spring的RestTemplate会对url进行encode,但它的encode与jdk自带的java.net.URLEncode.encode方法并不完全一致,区别在于:判断字符是否需要encode的逻辑不一样。
示例:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForEntity("https://www.baidu.com?r=编,码", String.class);
System.out.println(URLEncoder.encode("编,码", "utf-8"));
控制台日志:
16:12:09.548 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET https://www.baidu.com?r=%E7%BC%96,%E7%A0%81
16:12:09.559 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
16:12:09.745 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
16:12:09.746 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/html"
%E7%BC%96%2C%E7%A0%81
对于"编,码"
字符串
Spring的encode编码结果:%E7%BC%96,%E7%A0%81
java.net.URLEncode.encode:%E7%BC%96%2C%E7%A0%81
区别:Spring不会对,
encode;URLEncode会对,
编码(编码结果%2C
)
Spring不编码的字符(对应ASCII码):
// 参考:org.springframework.web.util.HierarchicalUriComponents.Type#QUERY_PARAM
c >= 97 && c <= 122 || c >= 65 && c <= 90 || 33 == c || 36 == c || 39 == c || 40 == c || 41 == c || 42 == c || 43 == c || 44 == c || 59 == c || 58 == c || 64 == c
URLEncode不编码的字符(对应ASCII码):
// 参考:/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/src.zip!/java/net/URLEncoder.java:87
for (i = 'a'; i <= 'z'; i++) {
dontNeedEncoding.set(i);
}
for (i = 'A'; i <= 'Z'; i++) {
dontNeedEncoding.set(i);
}
for (i = '0'; i <= '9'; i++) {
dontNeedEncoding.set(i);
}
dontNeedEncoding.set(' '); /* encoding a space to a + is done
* in the encode() method */
dontNeedEncoding.set('-');
dontNeedEncoding.set('_');
dontNeedEncoding.set('.');
dontNeedEncoding.set('*');
Spring的Encode与java.net.URLEncode不完全一致,大部分使用场景没有影响,因为即使未转码,URLDecode也可以解码。但涉及需要签名计算时,就要特别注意了!最近在项目中就踩到这样的坑:
百度开放平台,验签需要对参数进行URLEncode编码再加密生成签名。如果使用RestTemplate发起请求时,参数带有,
字符时,就会验签失败。因为url上的参数URLEncode与生成签名时的URLEncode结果不一致
三、execute方法中的String url
和URI url
的区别
源码:
@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Map<String, ?> uriVariables) throws RestClientException {
URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
return this.doExecute(expanded, method, requestCallback, responseExtractor);
}
@Nullable
public <T> T execute(URI url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
return this.doExecute(url, method, requestCallback, responseExtractor);
}
从源码很容易得知,如果参数为String url
,其为对url处理,比如URLEncode等。
转载自:https://juejin.cn/post/7014028784382787591