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只能使用英文字母、阿拉伯數字和某些標點符號,不能使用其他文字和符號。>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM