当前位置: 首页>前端>正文

RestTemplate链接池 resttemplate urlencoded

项目场景:

项目中对外请求的一个接口,里面的参数需要进行urlencode编码加密后进行发送,这时候出现一个莫名其妙的问题,同样的url和同样的参数,使用postman请求返回正常,使用RestTemplate却一直报错,无法得到正确结果。
如果接口是我们自己的,进去服务端看下日志,问题就能直接定位,但由于接口是外部的,对于我们来说是黑盒子,只能摸石头过河,从而找到解决方案。


问题描述:

出现问题的关键点在于,get参数的里包含了需要urlencode编码的+、%等特殊符号,导致RestTemplate没有返回预期数据。
产生问题实际代码如下:

String requestUrl = "https://www.xxx.com/getData.do?param=abcd+efg%";
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Cookie", cookies);
httpHeaders.set("Content-Type", "application/json; charset=utf-8");
ResponseEntity<String> getData = RestTemplateUtil.get(requestUrl, httpHeaders, 
String.class, params);
String result = getData.getBody();

使用原生RestTemplate实现代码如下:

String requestUrl = "https://www.xxx.com/getData.do?param=abcd+efg%";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(requestUrl, String.class);

此时的result并没有返回预期数据,而是返回null,目测是服务端出现空指针异常了。

但是奇怪的事情来了,postman能返回正确数据,使用okHttp请求,也能返回正常数据。
okHttp代码如下:

OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
Request request = new Request.Builder()
  .url(requestUrl)
  .method("GET", null)
  .build();
Response response = client.newCall(request).execute();

解决思路分析:

初步判断,肯定是服务端那边接收到的信息不一致,导致服务端报错了,尽管服务端不属于我们,但是我们知道出现问题的触发方式,是Get请求的参数里带有+、%等特殊符号,这些需要urlencode编码符号。那我们自己写一个简单的服务端,理论上是可以重现问题关键点所在。

重现问题

@GetMapping("/test}")
    public Map<String, Object> testRequest(@RequestParam String param) {
        return StringUtils.equals(param,"abcd+efg%");
    }

浏览器直接访问:http://127.0.0.1:8080/myProject/test?param=abcd+efg%,返回true
通过HTTPclient请求,返回true
通过okHttp请求,返回true
通过Restemplate请求,返回false。
到这里就有点可疑了,那么对url进行urlencode编码试试,
通过RestTemplate请求http://127.0.0.1:8080/myProject/test?param=abcd%2befg%25,返回false。
而如果进行对传参param进行打印查看,会发现RestTemplate的两次请求的param和其他不一样。
这里就有点骚了,RestTemplate莫非有存在什么限制,编码前不行,编码后也不行,搞事情。

查看RestTemplate源码

我们跟进去看下创建url的过程中,RestTemplate干了什么

RestTemplate链接池 resttemplate urlencoded,RestTemplate链接池 resttemplate urlencoded_spring boot,第1张

这里就很有意思了,URL居然不是使用常见的URI.create(url)创建,而是使用了URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);这里我是从主观上怀疑这里有问题,但是还是需要进一步看看究竟是不是这里出了问题。

继续跟,会发现来到这里。

RestTemplate链接池 resttemplate urlencoded,RestTemplate链接池 resttemplate urlencoded_spring_02,第2张

RestTemplate通过type.isAllowed(b)来判断是不是要进行urlencode编码加密,然后type.isAllowed(b)我们一层层跟进去

RestTemplate链接池 resttemplate urlencoded,RestTemplate链接池 resttemplate urlencoded_spring_03,第3张

RestTemplate链接池 resttemplate urlencoded,RestTemplate链接池 resttemplate urlencoded_RestTemplate链接池_04,第4张

RestTemplate链接池 resttemplate urlencoded,RestTemplate链接池 resttemplate urlencoded_spring boot_05,第5张

到这里,基本就明朗了,43在ASCII码中代表的就是+,37在ASCII码中代表的就是%,也就是RestTemplate里面对“+”不进行urlencode,而对“%”进行urlencode,这就导致了,url编码前传进去不对,编码后传进去也不对。

对于ASCII码不了解的同学,可以参考ASCII码 http://c.biancheng.net/c/ascii/,这里面讲的还是挺清楚的。

去一趟百度,百度百科查了下。

RestTemplate链接池 resttemplate urlencoded,RestTemplate链接池 resttemplate urlencoded_服务端_06,第6张

+确实是需要转码的,然而Spring的RestTemplate却不支持,那就是RestTemplate不完善咯,那就可以提bug咯…

然而,去一趟Spring的官网和Github一看,发现老早就有人提过了,对应issues:22153
https://github.com/spring-projects/spring-framework/issues/22153 https://github.com/spring-projects/spring-framework/issues/21399 https://github.com/spring-projects/spring-framework/issues/19394 https://jira.spring.io/projects/SPR/issues/SPR-17621?filter=allissues

RestTemplate链接池 resttemplate urlencoded,RestTemplate链接池 resttemplate urlencoded_spring boot_07,第7张

Spring的issues/21399说它不包含“+”是合理的,你Get请求出现问题,那肯定是你自己的url不符合规范。也就是Spring认为这个bug不是bug,意思是服务端的解析不符合规范才会导致出错,但是服务端是第三方的,我们也没得权限改第三方,也怼不动第三方。

RFC 3986的规范:https://www.ietf.org/rfc/rfc3986.txt

RFC3986文档规定,Url中只允许包含以下四种:

1、英文字母(a-zA-Z)
2、数字(0-9)
3、-_.~ 4个特殊字符
4、所有保留字符,RFC3986中指定了以下字符为保留字符(英文字符): ! * ’ ( ) ; : @ & = + $ , / ? # [ ]

第4点的“+”,就是触发我们这次bug的直接原因。


解决方案:

URL使用了URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);对url遵循了RFC 3986的规范进行urlencode编码加密,我们也没办法。

不过还好,RestTemplate还提供了直接传入URL的方法:

RestTemplate链接池 resttemplate urlencoded,RestTemplate链接池 resttemplate urlencoded_spring boot_08,第8张

那换个思路,自己构建URL传入:

String requestUrl = "https://www.xxx.com/getData.do?param=abcd+efg%";
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Cookie", cookies);
httpHeaders.set("Content-Type", "application/json; charset=utf-8");
ResponseEntity<String> getData = RestTemplateUtil.exchange(URI.create(requestUrl ), 
HttpMethod.GET,new HttpEntity<>(httpHeaders),String.class);

到止为止,问题得以解决,使用RestTemplate带+、%的Get请求正常返回了如期数据。

如果该文章中有哪些不足或者改进,希望您能留下评论告知一声。



https://www.xamrdz.com/web/2yt1942395.html

相关文章: