天天看點

總結常見的違背Rest原則的接口設計做法

此文已由作者鄭華斌授權網易雲社群釋出。

REST這詞我們常常挂在嘴邊,比如“開發一個rest接口”,又比如Spring項目的代碼:

@RestControllerpublic class CommonController {    @RequestMapping("/")    public String index() {        return "Welcome to Yanxuan DMS!";
    }      

CommonController使用了@RestController注解,顧名思義,告訴讀者這是一個Rest接口的實作。然而以@RestController注解的接口卻不一定符合Rest原則。結合最近的項目,總結下常見的違背Rest設計的一些做法。

一、一律使用POST或者GET方法

典型的錯誤做法:無論什麼請求,一律用POST,或者‘增删改’用POST,‘查’用GET。

其實REST有個原則叫統一接口(uniform interface),統一接口原則建議了各http方法的使用場合,

  1. GET:擷取資源,傳回消息頭和消息表示,即header和body。
  2. HEAD:擷取資源中繼資料,傳回消息頭
  3. DELETE:删除資源
  4. POST:REST設計中,POST通常用來為一個已有資源建立一個從屬資源(subordinate resource),如AWS S3的POST Object(或者稱web post)接口。
  5. PUT:建立或修改一個資源

PUT和POST的差別比較微妙,這裡拿AWS S3(或者參考網易對象存儲NOS)的接口設計來舉例。其中AWS S3的詳細API文檔參見:http://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html。 S3有兩種資源,桶(bucket)和對象(object),對象從屬于某個桶。

建立一個桶的接口為:

PUT /BucketName  HTTP/1.1
Host: s3.amazonaws.com      

建立/修改一個對象的PUT Object接口為:

PUT /BucketName/ObjectName  HTTP/1.1
Host: s3.amazonaws.com[對象資料]      

AWS S3同時提供了POST Object接口,同樣可以建立/修改一個對象,如下

POST /BucketName HTTP/1.1Host: s3.amazonaws.comContent-Type: multipart/form-data; boundary=9431149156168[包含對象資料的body]      

擷取對象的GET Object接口為:

GET /BucketName/ObjectName  HTTP/1.1
Host: s3.amazonaws.com      

同樣的建立/修改一個對象,一個用PUT方法,另一個用POST方法,為什麼?關鍵在于URL,PUT請求的目标URL(這裡為/BucketName/ObjectName),就是将來用于擷取該對象的URL,即PUT Object和GET Object的URL是一緻的。但是POST Object的URL與GET ObjectURL不一樣,POST 請求隻知道父資源的URL(即/BucketName),表示在該父資源下建立新資源,至于新資源的确切URL,是由伺服器決定的,一般來說是POST請求的響應應該包含一個Location消息頭,其包含建立從屬資源的URL。

安全性safe和幂等性idempotent

REST設計還應該遵循安全性和幂等性限制,如下:

  1. GET和HEAD應當是安全的:GET和HEAD請求不應該導緻伺服器狀态發生改變
  2. GET、HEAD、PUT和DELETE應當是幂等的:向一個URL發送多次PUT和DELETE請求,跟隻做過一次請求一樣。比如PUT不能是append語義,否則不幂等。GET和HEAD也是幂等。

統一接口原則的好處:

  1. 給一個資源URI,不用看文檔就知道可以有GET、DELETE等操作及其意義,世界通用。
  2. 安全性和幂等性增加了http的可靠性:如果請求沒成功(但也許已成功了),隻需重新發一次即可,不用擔心副作用。

二、HTTP Code一律傳回200

典型的錯誤做法:無論成功失敗,HTTP Code一律傳回200,具體錯誤資訊交由json body裡的内容來判斷,舉例如下,

某甲服務xxx接口的響應如下

HTTP/1.1 200 OK{    "status":1,  //1: 成功  0: 參數異常 -1: 失敗
    "message":"" //傳回的消息
    成功時傳回的資料
}      

某乙服務xxx接口的響應如下

HTTP/1.1 200 OK{    "code":200,  //1: 成功  0: 參數異常 -1: 失敗
    "msg":"" //code非200時傳回的錯誤資訊
    "data":{成功時傳回的資料内容} 
}      

其實RESTful的設計的一個标志特征是充分并正确利用HTTP響應碼,典型的如:

  • 200 -- OK,成功
  • 301 -- Moved Permanently,重定向
  • 400 -- Bad Request,錯誤的請求,比如缺少參數或者參數值不對
  • 403 -- Forbidden,無權限通路
  • 404 -- Not Found,url不存在
  • 500 -- Internal Server Error,系統錯誤,如資料庫通路失敗或者bug導緻的錯誤

設計REST接口應該遵循上面的響應碼,語義明确并通用。如果像上面例子那樣,任何情況都一律傳回200,而具體成功與否需要到http響應消息體裡去解析,而且不同的服務或開發者自定義消息體的格式,那麼服務調用方就需要針對不同的服務寫不同的判斷邏輯,增加系統互動複雜性。

有些通用的用戶端,會針對301自動處理重定向,針對500以上的響應自動重試,而一律傳回200的設計是沒法使用這些特性的,隻能調用方一一自個處理。

三、 面向操作而不是面向資源的url設計

典型的錯誤做法:設計的URI是面向操作而不是面向資源的,舉例如下,

某系統 設計的管道相關的URI是這樣的:

  1. 新增管道
    POST /xhr/thirdparty/admin/channel/add.json?{管道資訊參數}      
  2. 編輯管道
POST /xhr/thirdparty/admin/channel/update.json?{管道資訊參數}      
  1. 删除管道
POST /xhr/thirdparty/admin/channel/delete.json?channelId=id      

這裡的接口設計有三個特點:

  1. http方法都是POST;
  2. URI裡攜帶操作資訊,如URI裡出現“add”,“update”,“delete”等字眼;
  3. 同一個資源由于操作不一樣而URI不一樣。

其實REST式的設計中,URI即是資源的名稱,也是資源的位址,因為不同的操作而資源位址不一樣是不合适的。資源的操作(方法資訊)應該由統一接口來表示,即http 方法PUT、POST、GET、DELETE等,而不應該放到URI中。

對照統一接口和面向資源這兩個特征來設計,上面的接口RESTful化可以是這樣的:

POST /xhr/thirdparty/admin/channel

[管道具體資訊]      
  1. 修改管道
PUT /xhr/thirdparty/admin/channel?channelId=id 或者PUT /xhr/thirdparty/admin/channel/${id}

[管道具體資訊]      
DELETE /xhr/thirdparty/admin/channel?channelId=id或者DELETE /xhr/thirdparty/admin/channel/${id}      

管道的位址為/xhr/thirdparty/admin/channel?channelId=id或者/xhr/thirdparty/admin/channel/${id},重在url唯一。

參考文獻

《RESTful Web Services》

相關文章:

【推薦】 Bug是一種财富-------研發同學的錯題集、測試同學的遺漏用例集

【推薦】 類檔案結構與javap的使用

繼續閱讀