rest api參數與content-type

来源:http://www.cnblogs.com/ASPNET2008/archive/2016/08/06/5744815.html
-Advertisement-
Play Games

rest api的參數想即能支持application/x-www-form-urlencoded也能支持application/json方式傳參。 ...


最近為項目組提供rest api 時遇到了關於介面參數的傳遞問題,主要是沒有充分考慮到第三方調用者的使用方式,應該儘量的去相容公司之前提供出去的介面調用方式,這樣可以降低第三方調用者的學習成本,儘管之前的方式並不是那麼的推薦,好的做法是即相容老的做法也支持推薦的做法。

對於基於http post介面,Content-type我會優先選擇application/json,但公司之前提供的介面恰恰採用了application/x-www-form-urlencoded,它是表單預設的提交類型,基於key/value形式提交到服務端的。spring mvc是如何接收下麵兩種經典數據的? (至於form-data,它即可以傳鍵值對也可以上傳文件,這裡不涉及到文件所以只討論下麵兩種):

  • Content-type=application/json:需要在參數上增加@RequestBody這個註解,說明參數是從http的requestbody中獲取。

    

        下圖中的參數,是標準的json格式,對前端js非常友好。

       

  • Content-type=application/x-www-form-urlencoded,參數上不能增加@RequestBody的註解

         下圖的可以看出參數形式與get請求時,URL後面的參數格式

       


為什麼不推薦採用application/x-www-form-urlencoded這種類型,它有如下問題:

  • 測試困難,就別想通過postman這類工具測試:提交到服務端實際上是一個MultiValueMap,如果value中的對象也是一個對象,那麼在構建這個參數時就非常困難,看下它的過程
    •  採用key1=value1&key2=value2這種形式將所有參數拼接起來,從一長串字元中想瞭解每個參數的含義沒有個好眼力怕是不行。
    •  value要進行編碼,編碼之後的對調試者不友好。
    •  value是複雜對象的情況更加糟糕,一般只能通過程式來序列化得到參數,想手寫基本不可能。
  • 客戶端調用複雜

        需要去構建List<NameValuePair>,一般頁面傳遞的參數都是一個實體對象Model,需要額外的將這個Model轉換成List<NameValuePair>,如果這個對象複雜,那麼構建這個Key/Value就夠人煩的了。這裡給一個java通過apache httpclient調用的對比,看看哪一個簡單。

    • application/x-www-form-urlencoded

                需要手工將model轉換成NameValuePair。

          

    • application/json

                 這裡只需要Model即可,不需要二次轉換,結構也非常清楚。

            

  • key/value的語言表達形式沒有json強,下麵兩種你更加喜歡哪一個呢?
    • 字元串

             

              post man這類模似http請求的工具中,如果key對應的value是個對象,那麼你需要通過工具得到它的序列化之後的字元串然後填寫到欄位中,想想都煩。如果你說我不需要通過這些模似工具測試,那就另當別論

            

    • json

              

  • 數據結構更加複雜

   如果需要提交的對象非常複雜,屬性非常多,如果將所有的屬性都構建到MultiValueMap中,那個Map的構建會非常複雜,試想如果對象有多級嵌套對象呢。所有為了避免這個問題,我們將需要提交的業務對象做為一個key來存儲,value就是對象序列化之後的字元串。再加了一些非業務參數,比如安全方面的token等參數,有效的降低了MultiValueMap構建的複雜度。但這種方式相對於json的傳遞方式來講層次更深。如下圖,我們的參數多了一層,jsonParam。

    



如果解決呢?
不能不相容現有的模式,但又想支持json,焦點就是在參數的接收上,讓其能夠完美的相容上述兩種參數傳遞,這裡可以從HttpMessageConverter著手,這個就是用來將請求的參數映射到spring mvc方法中的實體參數的。我們可以編寫一個自定義的類,內部借用FormHttpMessageConverter來接收MultiValueMap,即使方法參數上增加了@RequestBody的註解,也會走我們自定義的converter,就有機會去重新給參數賦值。

這個方法中需要解決一個問題,就是客戶端傳遞時每個參數都是當成字元串來處理的,這種導致我們通過FormHtppMessageConverter轉換成Map時,原本是對象的屬性被識別成字元串,而不是object,結果就是在反序列化時會出錯。好在,上面我們將需要提交的對象包裝了一次,產生一個公共的object參數jsonParam,只需要處理這一個特殊對象。做法就是從Map取出jsonParam,然後對其內容進行反序列化,更新Map值,再次進行反序列化就正常了。
  
     上圖中的做法目前有如下問題

  • 序列化的欄位是約定好的,也是基於我們的post model基本上來處理的,是針對性的converter
  • 代碼最後面調用的jackon的convertValue,對需要反序列化的對象類型有要求,好像不支持泛型類型,比如這種類型的就不行: CommonParamInfoDto<SearchParamInfo<ProductSearchInfo>>

     完整的conveter代碼如下,其實主要代碼就是上圖貼圖中的那麼對特定欄位的序列化處理,其它的方法都是預設即可。

public class ObjectHttpMessageConverter implements HttpMessageConverter<Object> {

    private final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
    private final ObjectMapper objectMapper = new ObjectMapper();

    private static final LinkedMultiValueMap<String, ?> LINKED_MULTI_VALUE_MAP = new LinkedMultiValueMap<>();
    private static final Class<? extends MultiValueMap<String, ?>> LINKED_MULTI_VALUE_MAP_CLASS
            = (Class<? extends MultiValueMap<String, ?>>) LINKED_MULTI_VALUE_MAP.getClass();

    @Override
    public boolean canRead(Class clazz, MediaType mediaType) {
        return objectMapper.canSerialize(clazz) && formHttpMessageConverter.canRead(MultiValueMap.class, mediaType);
    }

    @Override
    public boolean canWrite(Class clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return formHttpMessageConverter.getSupportedMediaTypes();
    }


    @Override
    public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        Map input = formHttpMessageConverter.read(LINKED_MULTI_VALUE_MAP_CLASS, inputMessage).toSingleValueMap();
        String jsonParamKey="jsonParam";
        if(input.containsKey(jsonParamKey)) {
            String jsonParam = input.get(jsonParamKey).toString();
            SearchParamInfo<Object> searchParamInfo = new SearchParamInfo<Object>();
            Object jsonParamObj = JsonHelper.json2Object(jsonParam, searchParamInfo.getClass());
            input.put("jsonParam", jsonParamObj);
        }
        Object objResult= objectMapper.convertValue(input, clazz);
        return objResult;
    }

    @Override
    public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("");
    }
}

 

     配置,寫好了conveter之後,需要在配置文件中配置上才能生效。

    

 

     最後,我們的方法就可以這樣寫,即可以支持 key/value對,也支持json

    

我的目的在於api的參數即能支持application/x-www-form-urlencoded也能支持application/json,上面是我目前能想到的辦法,如果大家有其它更好的辦法多多指點。

 

     我又可以愉快的使用post man測試了。而且可以推薦第三方調用者優先使用json,我相信這種即能簡化編程又方便調試的優點應該能夠吸引它們。

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MVVM學習筆記 1、MVVM的簡介 MVVM模式是Model-View-ViewModel模式的簡稱,也就是由模型(Model)、視圖(View)、視圖模型(ViewModel),其目的是為了實現將業務和界面分開,降低耦合度。 2、示例(綁定TextBox和Combox控制項) 總體結構: View ...
  • quartznet 上篇說到quartznet這個東東,topshelf+quartznet有很多不錯的文章,可以查看七七同學的文章(http://www.cnblogs.com/jys509/p/4628926.html)。這裡我主要說說cron表達式,如果玩過linux下定時任務的肯定不陌生。 ...
  • 最近在項目中碰到一個很頭疼的問題,在前端連接事件中寫了一個廣播線程,該廣播線程寫在while迴圈中,但是前臺會有很多個客戶端,沒連接一次就會有一個廣播線程開啟,很吃資源,剛開始我解決這個問題的方法是每次觸發連接事件是檢測一下當前連接數,如果是count_client<=1,就開線程,否則跳過廣播,但 ...
  • 在開發過程中經常需要發佈到開發環境、測試環境或者預發佈環境上給其他同事進行測試驗證效果等等,每次發佈都要備份,拷貝,修改配置文件等等重覆操作非常的麻煩,效率大打折扣,而web部署提供了這樣的解決方案:在服務端安裝Web Deploy服務,由Web Deploy服務完成備份發佈等操作,今天小編就以圖文 ...
  • 一、前言 Treeview控制項常用於遍歷本地文件信息,通常與Datagridview與ImageList搭配。ImageList控制項用於提供小圖片給TreeView控制項,DatagridView通常顯示TreeNode節點下文件及文件夾的信息。 效果圖: 二、代碼 初始化窗體: 初始化DataGri ...
  • 項目架構採用:Asp.Net MVC4.0 + EntityFramework6.0 code first + AutoMapper + Unity(IOC) + SqlServer2012 項目地址:www.xiaoboke.net 這個博客會不斷開發完善哦 歡迎大家去吐槽和提建議,一起學習,一起 ...
  • 使用自動載入和解析url的參數,實現調用到不同的控制器,實現了pathinfo模式和普通的url模式 文件結構: |--Controller |--Index |--Index.php |--Application.php Application.php \Controller\Index\Inde ...
  • 前言:在javaweb開發中自定義標簽的用處還是挺多的。今天和大家一起看自定義標簽是如何實現的。 1:什麼是標簽 標簽是一種XML元素,通過標簽可以使JSP頁面變得簡介易用,而且標簽具有很好的復用性。 2:自定義標簽的標簽庫主要的介面以及類的繼承實現關係圖 3:一步步實現自定義標簽 3.1:Tag接 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...