RestTemplate的Get请求参数urlencode编码加密后请求异常的解决方案

项目场景:

项目中对外请求的一个接口,里面的参数需要进行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干了什么
在这里插入图片描述这里就很有意思了,URL居然不是使用常见的URI.create(url)创建,而是使用了URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);这里我是从主观上怀疑这里有问题,但是还是需要进一步看看究竟是不是这里出了问题。
继续跟,会发现来到这里。
在这里插入图片描述RestTemplate通过type.isAllowed(b)来判断是不是要进行urlencode编码加密,然后type.isAllowed(b)我们一层层跟进去
在这里插入图片描述在这里插入图片描述在这里插入图片描述到这里,基本就明朗了,43在ASCII码中代表的就是+,37在ASCII码中代表的就是%,也就是RestTemplate里面对“+”不进行urlencode,而对“%”进行urlencode,这就导致了,url编码前传进去不对,编码后传进去也不对。

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

去一趟百度,百度百科查了下。
在这里插入图片描述
+确实是需要转码的,然而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

在这里插入图片描述
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的方法:
在这里插入图片描述那换个思路,自己构建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请求正常返回了如期数据。

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

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值