1. 程式人生 > 程式設計 >關於遠端呼叫RestTemplate的使用避坑指南

關於遠端呼叫RestTemplate的使用避坑指南

目錄
  • 一、前言介紹
  • 二、 問題記錄
    • 1. 慎!【url引數中有on字串】
    • 2. 慎!【url引數中有經過URLEncode的字串】
    • 3. 慎!【url引數中存在特殊字元】 --- 針對HttpClient
  • 總結

    一、前言介紹

    RestTemplate是Spring中用於遠端介面呼叫的工具類,它是Apache的HttpClient的模板封裝,使用起來非常方便,本文將講述這兩天自己在使用RestTemplate過程中遇到的問題,當然這些問題也是由於自己對RestTemplate工具類瞭解不夠全面不夠透徹造成的,希望自己遇到的這些問題能為大家提前避雷或是遇到類似問題時的一個解決參考。

    二、 問題記錄

    1. 慎!【url引數中有json字串】

    在使用RestTemplate進行遠端介面呼叫時,如果url拼接引數中json字串時一定要小心,使用場景如下:利用restTemplate呼叫user的查詢資訊介面,url中的一個引數user為json字串格式{\"user\":\"xiaoming,\"age\":"12"}

      // JSON引數
            Map<String,String> paramMap = new HashMap<>(8);
            paramMap.put("name","xiaoming");
            paramMap.put("age","12");
            String paramJsonStr = JSONObject.toJSONString(paramMap);
     
            // 實際引數 url = "http://localhost:8080/api/user?user={\"name\":\"xiaoming\",\"age\":\"12\"}&country=china";
    
    String url = "http://localhost:8080/api/user?user=" + paramJsonStr + "&country=china"; RestTemplate restTemplate = new RestTemplate(); // 調用出錯 Object execute = restTemplate.execute(url,HttpMethod.GET,null,null);

    此時當我們執行程式時會丟擲以下錯誤:

    關於遠端呼叫RestTemplate的使用避坑指南

    錯誤意思大概是沒有足夠可用的變數值來填充擴充套件 'name',這是什麼鬼意思,彆著急讓我們跟跟程式碼看看異常丟擲的位置,最終定位如下,在建立URI過程中呼叫了 UriComponents.expandUriComponent()方法丟擲異常:

    關於遠端呼叫RestTemplate的使用避坑指南

    這段程式碼的作用其實就是通過NAMES_PATERN規則匹配到相應字串然後利用 uriVariables.getValue(varibaleName)進行替換,再看看NAMES_PATERN的值就是用來匹配{}中的字串客棧內容的

    private static final Pattern NAMES_PATTERN = Pattern.compile(\\{([^/]+?)\\});

    問題分析到這兒相信大家應該也明白了RestTemplate在建立URI時會進行{param}佔位替換,這個規則在文字輸出時應用比較多,如日誌列印和控制檯列印中常有使用:

       String value = "test";
       logger.info("佔位引數{}",value);

    解決辦法:

    找到原因了,那麼我們應當如何解決呢,既然RestTemplate在處理url時會進行{}變數替換,那它理應提供相應的介面呼叫,檢視RestTemplate原始碼它提供了多個exchange過載方法,其中多個方法都有uriVariables引數,如下所示:

        public <T> T execute(String url,HttpMethod method,RequestCallback requestCallback,ResponseExtractor<T> responseExtractor,Object... uriVariables) throws RestClientException {
            URI expanded = this.getUriTemplateHandler().expand(url,uriVariables);
            return this.doExecute(expanded,method,requestCallback,responseExtractor);
        }

    那麼我們將url稍微修改即可解決問題:

    關於遠端呼叫RestTemplate的使用避坑指南

    2. 慎!【url引數中有經過URLEncode的字串】

    其實在遇到第一個坑時,我並沒有採用上面給出的解決方式,而是想著將Json字串經過URLEncode編碼後在拼接到url後面,不就沒有{}符號了,不就可以完美解決問題了,心裡想著就美滋滋,那讓我們來試一把吧:

          // JSON引數
            Map<String,"12");
            String paramJsonStr = JSONObject.toJSONString(paramMap);
     
            // 實際引數 url = "http://localhost:8080/api/user?user=%7B%22name%22%3A%22xiaoming%22%2C%22age%22%3A%2212%22%7D&country=china";
            String encode = URLEncoder.encode(paramJsonStr,"utf-8");
            String url = "http://localhost:8080/api/user?user="+encode+"&country=china";
            System.out.println(url);
            RestTemplate restTemplate = new RestTemplate();
            Object execute = restTemplate.execute(url,paramJsonStr);

    json字串經過編碼後已經沒有{}符號了,也能夠成功呼叫介面,但是介面提供方無情的返回了錯誤:引數無法反序列化。聽這口氣肯定也是這json串的原因,趕緊用Postman試一試:

    關於遠端呼叫RestTemplate的使用避坑指南

    神奇的一幕出現了,居然成功了! Postman方式呼叫和RestTemplate呼叫有什麼不一樣,為什麼postman行,restTemplate不行?趕緊檢視伺服器日誌看看兩種方式接收到的引數有和不一樣:

    1. Postman方式伺服器接收到的url:

    "http://localhost:8080/api/user?user=%7B%22name%22%3A%22xiaoming%22%2C%22age%22%3A%2212%22%7D&country=china"

    2. RestTemplate方式伺服器接收到的url

    "http://localhost:8080/api/user?user=%257B%2522name%2522%3A%2522xiaoming%2522%2C%2522age%2522%253A%252212%2522%257D&country=china"

    restTemplate居然在每個百分號%後面都擅自加了25這個數字,難怪服務端沒法解析,它為什麼要這麼做?難道是restTemplate的url處理bug?讓我們跟一跟程式碼看個究竟:

    關於遠端呼叫RestTemplate的使用避坑指南

    詳細呼叫層次就不貼了,簡單來說就是RestTemplate在處理url時會對url引數進行再編碼,也就是會對url中的特殊字元進行轉義,如%號會被轉義為%25,所以傳給服務端的url就被改變了,具體url特殊字元轉義知識請檢視這篇文章

    既然知道了原因,那麼我們應該如何解決呢?

    解決方案:

    關於遠端呼叫RestTemplate的使用避坑指南

    RestTemplate中的URI物件是通過UriTemplateHandler生成的,所以我們只需要利用.net包中的URI自己構建URI物件傳給RestTemplate即可,這樣url中的特殊字元就不會被轉義了:

    關於遠端呼叫RestTemplate的使用避坑指南

    3. 慎!【url引數中存在特殊字元】 --- 針對HttpClient

    前面兩個坑然我對RestTemplate有點望而生畏了,既然RestTemplate這麼多坑,那好咋們換回老傢伙apache家族的HttpClient,本以為可以一切順利,沒成想一坑接著一坑啊!!!

    先看看呼叫場景:按照出生時間去查詢使用者資訊

    關於遠端呼叫RestTemplate的使用避坑指南

    呼叫請求都還沒發出就無情報錯了:

    關於遠端呼叫RestTemplate的使用避坑指南

    通過異常資訊可以容易知道在建立URL物件時url引數索引位置50處有非法字元,而這個字元剛好就是時間引數中的空格!,跟蹤程式碼異常發生位置發現下面一段註釋,大意就是不允許url中有特殊字元存在,看了本文第二個坑的應該已經明白url中特殊字元為什麼需要進行轉義了,這裡就不詳細敘述,至此解決方案就呼之欲出了。

    關於遠端呼叫RestTemplate的使用避坑指南

    解決方案:

    需要對引數中的特殊字元進行轉義:

    1. 直接特殊字元替換

    關於遠端呼叫RestTemplate的使用避坑指南

    2. 利用google的工具包UrlEscapers(可以處理url、xml、html中的特殊字元)

    maven依賴

     <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
        <dependency>
          <groupId>com.google.guava</groupId>
              <artifactId>guava</artifactId>
              <version>28.1-jre</version>
        </dependency>

    使用方式:UrlEscapers可以對路徑、引數、片段進行處理,提供了 path,parameter,fragment三個部分的Escape例項

    分別呼叫UrlEscapers類的以下方法獲取:

    • urlFormParameterEscaper()
    • urlPathSegmentEscaper()
    • urlFragmentEscaper()

    關於遠端呼叫RestTemplate的使用避坑指南

    總結

    本次三個案例本人覺得還是具有典型性,由於平時發起請求大多通過瀏覽器或者是postman這類的http模擬工具進行,而瀏覽器和模擬工具在內部會對請求url和引數進行一定處理,例如編碼和轉義,所以平時對這塊關注不多,而當我們在server端自我構建http請求進行遠端呼叫時這類問題就需要我們特別注意,稍有不慎就會掉入坑中,還有一點感悟在使用一個工具類時應當先大致閱讀一下工具類提供了哪些方法,做到心中有數使用時就會少走很OkIveQQ多彎路。

    以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。