今天在对UC接口的时候,被UC的同事狠狠的鄙视了一翻。先说下场景啊:
我们需要把一个url类型参数传递给UC这边。之前的时候用的是我个人封装的Httpclient的工具类,一直没什么问题,现在做项目改造微服务化的时候,使用是我重新封装的ResetTemplate。关于ResetTemplate的封装可以参考
因为UC这边对接口设定是GET请求,或者POST请求,请求类型为:application/x-www-form-urlencoded。如果正常的POST请求是:application/json的形式的话不会出现问题的。
当然我们传递URL类型的参数的时候一般都需要编码的
url = URLEncoder.encode(url,"UTF-8");
理论上编码完成之后这个url应该就是一个普通的字符串参数,但是我在使用restTemplate发起一个请求的的时候出现了诡异的情况:
url = URLEncoder.encode(url,"UTF-8");
String pushUrl = MessageFormat.format(UcConstant.PUSH_URL+"?link={0}&event_type={1}&event_time={2}",url,eventType,String.valueOf(createDate.getTime()));
String result = resetTemplateService.getWithNoParams(url,String.class);
简单的请求测试,会发现一直报错,说解析link参数出错。当时整的我一脸懵逼,然后我去问UC的同事,给我的反馈
这种时候,当然是先打断点跟下确认下,追踪之后就一脸懵逼了,框架在构造URI对象的时候居然,对url参数进行了二次编码,还针对的是%这个字符。好吧,这个时候我得承认我是真的被打脸了呗。
分析下:
@Override
public URI build(Map<String, ?> uriVars) {
if (!defaultUriVariables.isEmpty()) {
Map<String, Object> map = new HashMap<>();
map.putAll(defaultUriVariables);
map.putAll(uriVars);
uriVars = map;
}
if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
uriVars = UriUtils.encodeUriVariables(uriVars);
}
UriComponents uriComponents = this.uriComponentsBuilder.build().expand(uriVars);
if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
uriComponents = uriComponents.encode();
}
return URI.create(uriComponents.toString());
}
@Override
public URI build(Object... uriVars) {
if (ObjectUtils.isEmpty(uriVars) && !defaultUriVariables.isEmpty()) {
return build(Collections.emptyMap());
}
if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
uriVars = UriUtils.encodeUriVariables(uriVars);
}
UriComponents uriComponents = this.uriComponentsBuilder.build().expand(uriVars);
if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
uriComponents = uriComponents.encode();
}
return URI.create(uriComponents.toString());
}
两个builder方法提供关键URI生成逻辑,根据最后的返回可以知道,生成URI依然是使用URI.create
,所以出问题的地方就应该是 uriComponents.encode()
实现url编码的地方了,对应的代码如下
@Override
public HierarchicalUriComponents encode(Charset charset) {
if (this.encoded) {
return this;
}
String scheme = getScheme();
String fragment = getFragment();
String schemeTo = (scheme != null ? encodeUriComponent(scheme, charset, Type.SCHEME) : null);
String fragmentTo = (fragment != null ? encodeUriComponent(fragment, charset, Type.FRAGMENT) : null);
String userInfoTo = (this.userInfo != null ? encodeUriComponent(this.userInfo, charset, Type.USER_INFO) : null);
String hostTo = (this.host != null ? encodeUriComponent(this.host, charset, getHostType()) : null);
PathComponent pathTo = this.path.encode(charset);
MultiValueMap<String, String> paramsTo = encodeQueryParams(charset);
return new HierarchicalUriComponents(
schemeTo, fragmentTo, userInfoTo, hostTo, this.port, pathTo, paramsTo, true, false);
}
// org.springframework.web.util.HierarchicalUriComponents#encodeQueryParams
private MultiValueMap<String, String> encodeQueryParams(Charset charset) {
int size = this.queryParams.size();
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(size);
this.queryParams.forEach((key, values) -> {
String name = encodeUriComponent(key, charset, Type.QUERY_PARAM);
List<String> encodedValues = new ArrayList<>(values.size());
for (String value : values) {
encodedValues.add(encodeUriComponent(value, charset, Type.QUERY_PARAM));
}
result.put(name, encodedValues);
});
return result;
}
结果,构建前后输出的结果集确实不一样。对比下上面的区别,发现这个参数编码,会将请求参数中的 %
编码为 %25
, 所以问题就清楚了,我传进来本来就已经是编码之后的了,结果再编码一次,相当于修改了请求参数了。
那么怎么解决呢?
1、和UC的同事说的一样,改成Httpclient作为请求发起工具。
但是这个不是又让我回到原点了嘛。
2、遇到问题解决问题嘛,居然是在构造URI对象的时候出问题的,那么我就自己构造URI对象就OK了嘛
URI uri = null;
try {
url = URLEncoder.encode(url,"UTF-8");
String pushUrl = MessageFormat.format(UcConstant.PUSH_URL+"?link={0}&event_type={1}&event_time={2}",url,eventType,String.valueOf(createDate.getTime()));
uri = new URI(pushUrl);
}catch (Exception e){
log.info("连接编码失败");
}
String result = resetTemplateService.getWithNoParams(uri,String.class);
OK,开始测试,没问题。
学无止境,遇到问题能转化成自己就是最大的收获!加油!!!
不要为自己的无知找借口!!!!!!!