• RestTemplate经典问题:%被转码为%25导致url错误


    一、遇到问题

    今天要写一个接口,收到请求后,给第三方接口发送请求,第三方接口会创建一个聊天室,然后返回报文。

    碰到一个问题:

    使用restTemplate.getForObject()发送请求时,获取的响应报文显示:{"message":"url请求非法!"},无法获取正确的响应报文。

    代码如下:

    //样例url
    String url = "http://10.111.222.333/live";
    String cid = "USER_NAME_EXAMPLE";
    String signCode = "LIKEPASS123ABC456";
    
    String roomId = "@ABC#1A2B3C";
    
    String tail = "/chatRoom/create_room?cid="+cid+"&timestamp="+System.currentTimeMillis()+"&roomId="+URLEncoder.encode(roomId);
    String sign = "&sign="+signCode;
    String s = MD5Util.computeMD5(tail+sign);
    
    //获得最终的url
    url = url + tail + "&md5=" + s;
    
    RestTemplate restTemplate = new RestTemplate();
    //发送请求并用String格式获取响应报文
    String backStr = restTemplate.getForObject(url, String.class);
    //将响应报文转为JSONObject格式
    JSONObject backJson = JSONObject.fromObject(backStr);
    
    //打印下请求地址,url
    System.out.println(url);
    //打印下响应报文,backStr
    System.out.println(backStr);
    

    调用接口后,收到的错误的响应报文:

    {"message":"url请求非法!","code":"-1"}

    二、分析问题

    1.首先,第三方的接口没有问题。(虽然不知道什么情况下会返回这种信息)

    2.将控制台打印出来的url复制,用Chrome打开是没有问题的,页面也可以看到"创建聊天室成功"的json报文。(本质上就是一个普通的get请求,也不是请求头设置错误)

    3.同样的url,在程序中就无法获取正确的响应报文。

    4.准备抓包分析。

    (1)首先打开抓包工具Fiddler,用chrome访问url,获得正确的包;

    (2)在代码中增加配置,使用代理:

    System.setProperty("http.proxyHost", "127.0.0.1");
    System.setProperty("https.proxyHost", "127.0.0.1");
    System.setProperty("http.proxyPort", "8888");
    System.setProperty("https.proxyPort", "8888");

    (3)打开Fiddler,Tools->Fiddler Options...->Connections,确认代理端口是否为8888(与代码中的要匹配)

    (4)调试代码,抓包,与正确的包比较,发现了不同点:

    //正确的url
    http://10.111.222.333/live/chatRoom/create_room?cid=USER_NAME_EXAMPLE&timestamp=1602737280156&roomId=%40ABC%231A2B3C&md5=b0f35all1k3j241l
    //错误的url
    http://10.111.222.333/live/chatRoom/create_room?cid=USER_NAME_EXAMPLE&timestamp=1602737280156&roomId=%2540ABC%25231A2B3C&md5=b0f35all1k3j241l

    (5)不同点是roomId的值。

    三、明确问题

    1.代码中,为了获取md5码,需要先使用URLEncoder.encode(roomId)对roomId转码,然后拼接成url,这本来是没有问题的。

    2.然而在使用restTemplate.getForObject(url, String.class)发送请求时,它对url中的【%】又进行了一次encode转码,导致【%】被转为了【%25】,进一步导致实际请求的url错误,第三方接口也就返回了“url请求非法!”的错误信息。

    四、解决方法

    1.首先想到的解决方法是,既然restTemplate在发送请求时会对url进行encode转码,那么在拼接url时自己先不转码,就不会有问题了;然而这是一个坑:

    //代码同上,省略
    ......
    
    //使用tail2,不encode,进行url拼接
    String tail2 = "/chatRoom/create_room?cid="+cid+"&timestamp="+System.currentTimeMillis()+"&roomId="+roomId;
    
    //获得最终的url
    url = url + tail2 + "&md5=" + s;

    通过抓包发现,实际请求的url还是有问题:

    //控制台打印的url,roomId没有encode
    http://10.111.222.333/live/chatRoom/create_room?cid=USER_NAME_EXAMPLE&timestamp=1602737280156&roomId=@ABC#1A2B3C&md5=b0f35all1k3j241l
    
    //我们认为的正确url,restTemplate发送请求时会对url进行encode,将@和#转码了
    http://10.111.222.333/live/chatRoom/create_room?cid=USER_NAME_EXAMPLE&timestamp=1602737280156&roomId=%40ABC%231A2B3C&md5=b0f35all1k3j241l
    
    //实际抓包得到的url,还是请求了一个错误的路径
    http://10.111.222.333/live/chatRoom/create_room?cid=USER_NAME_EXAMPLE&timestamp=1602737280156&roomId=@ABC

    可以看到,如果我们自己不对url进行encode的话,restTemplate进行处理时会有问题,丢失了【#】之后的字段,并且没有对【@】进行encode。

    经过多次测试,restTemplate会将url中的【%】转为【%25】,其它特殊字符如【!@#】等,则不会转码。

    如果我们自己对url进行了encode,此时特殊符号被转为了【%+数字或字母】的形式;然而,restTemplate进行处理时还会将【%】转为【%25】,实际请求的url还是错误的。

    2.踩过坑之后,下面是正确的解决方式:

    //上方代码同上,可以执行自己的encode
    ......
    
    URI uriObj = URI.create(url);
    RestTemplate restTemplate = new RestTemplate();
    //之前是传入String类型的url
    //现在改为URI类型的uriObj,这样restTemplate就不会进行encode了。
    String backStr = restTemplate.getForObject(uriObj, String.class);

    五、总结

    使用restTemplate发送请求时,如果传入String类型的url,则这个url中的【%】会被转为【%25】;

    如果url中不存在特殊符号,则没有问题;

    如果url存在特殊符号且自己没有encode,那么restTemplate执行后,实际请求的url可能会有问题(会丢失#后面的内容等,url机制);

    如果url中存在特殊符号且自己执行过encode,那么restTemplate执行后,url会被再次encode,导致实际请求的url出错(%被转码)。

    因此,如果url中存在特殊符号,可以在自己执行encode后,转为URI对象,再使用restTemplate发送请求,就避免了【%】被转码的问题。

    <补充:URLEncoder.encode()是将字符转为URL可以使用的编码的方法,一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。>

  • 相关阅读:
    描述商品信息
    新版本Mariadb安装后相关问题的解决
    配置docker阿里云加速器
    Portainer实战
    搭建Portainer可视化界面
    如何在 Debian 9 上安装和使用 Docker
    debian10使用国内源安装docker以及一些使用方法
    帝国CMS 7.5编辑器从WORD中粘贴过来无法保留格式和图片的解决办法
    解决UEditor将div标签换成p标签的问题
    ueditor div style被过滤 解决办法
  • 原文地址:https://www.cnblogs.com/codeToSuccess/p/13906192.html
Copyright © 2020-2023  润新知