7點關于RESTful規范的API接口設計的建議

weedw 8年前發布 | 55K 次閱讀 RESTful WEB服務/RPC/SOA


在項目中,需要為APP撰寫API。剛開始接觸的時候,并沒有考慮太多,就想提供URL,APP端通過該URL進行查詢、創建、更新等操作即可。但再對相關規范進行了解后,才發現,API的設計并沒有那么簡單,遠遠不是URL的問題,而是一個通信協議的整體架構。因此,我寫這篇文章,用來記錄自己的一些心得,并不斷完善。并提供關于RESTful API的一些參考文獻。

1. 使用SSL(https)來提供URL

首先,使用https可以在數據包被抓取時多一層加密。我們現在的APP使用環境大部分都是在路由器WIFI環境下,一旦路由器被入侵,那么黑客可以非常容易的抓取到用戶通過路由器傳輸的數據,如果使用http未經加密,那么黑客可以很輕松的獲取用戶的信息,甚至是賬戶信息。

其次,即使使用https,也要在API數據傳輸設計時,正確的采用加密。例如直接將token信息放在URL中的做法,即使你使用了https,黑客抓不到你具體傳輸的數據,但是可以抓到你請求的URL啊!因此,使用https進行請求時,要采用POST、PUT或者HEAD的方式傳輸必要的數據。

2. 使用GET、POST、PUT、DELETE這幾種請求模式

請求模式也可以說是動作、數據傳輸方式,通常我們在web中的form有GET、POST兩種,而在HTTP中,存在下發這幾種。

GET (選擇):從服務器上獲取一個具體的資源或者一個資源列表。

POST (創建): 在服務器上創建一個新的資源。

PUT(更新):以整體的方式更新服務器上的一個資源。

PATCH (更新):只更新服務器上一個資源的一個屬性。

DELETE(刪除):刪除服務器上的一個資源。

HEAD : 獲取一個資源的元數據,如數據的哈希值或最后的更新時間。

OPTIONS:獲取客戶端能對資源做什么操作的信息。

3. 在URI中體現資源,而非動作

閱讀RESTful架構的參考文獻之后,你會了解什么是資源的概念,以及REST的確切含義。再構建API的URL的時候,URI中應該僅包含資源(對象),而不要加入動作。比如 /user/1/update ,其中update就是一個動作,雖然我們希望通過這個URI來實現用戶ID為1的用戶進行信息更新,但是按照RESTful的規范,作為動作,應該用上面的PUT來表示,所以請求更新用戶信息,應該使用 PUT /user/1 來表示更新用戶ID為1的用戶信息。

如果去對應上面的請求模式,GET表示顯示、列出、展示,POST表示提交、創建,PUT表示更新,DELETE表示刪除。

<?php
    $ch = curl_init();
    $url = 'http://api.xxx.com/user';
    $data = "name=姓名&email=xxx@xxx.com";
    // 添加參數
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    // 執行HTTP請求
    curl_setopt($ch , CURLOPT_URL , $url);
    $res = curl_exec($ch);
    var_dump(json_decode($res));
?>

上面這段代碼中$url僅僅是提供到了user,而并沒有提供add,服務端通過識別POST請求來確定,這是一個創建用戶的操作。但是還有一些數據并沒有用以處理數據,而是用以驗證的,比如下文的鑒權,可以將這些信息通過header進行傳輸,下方詳細展示。

4. 版本

API的開發直接關系了APP是否可以正常使用,如果原本運行正常的API,突然改動,那么之前使用這個API的APP可能無法正常運行。APP是不可能強迫用戶主動升級的,因此,通過API版本來解決這個問題。也就是說,API的多個版本是同時運行的,而且都要保證可以正常使用。

按照RESTful的規范,不同的版本也應該用相同的API URL,通過header信息來判斷版本,再調用不同版本的程序進行處理。但是這明顯會給開發帶來巨大的成本。解決辦法有兩種:1.新版本兼容舊版本,所有舊版本的動作、字段、操作,都在新版本中可以被實現,但明顯這樣的維護成本很大;2.不同的版本,用不同的URL來提供服務,比如再URL中通過v1、v2來區分版本號,我則更喜歡采用子域名的方式,比如v2.api.xxx.com/user的方式。

5. HTTP響應碼

在用戶發出請求,服務端對請求進行響應時,給予正確的HTTP響應狀態碼,有利于讓客戶端正確區分遇到的情況。

200 OK - [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。

201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。

202 Accepted - [*]:表示一個請求已經進入后臺排隊(異步任務)

204 NO CONTENT - [DELETE]:用戶刪除數據成功。

400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作,該操作是冪等的。

401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)。

403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的。

404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作,該操作是冪等的。

406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)。

410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。

422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤。

500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷發出的請求是否成功。

6. 返回值結構

在完成了上面的URL部署之后,接下來我們來看看返回結果應該怎么樣來確定。我看到大部分文獻中指出,最好使用JSON進行返回,而非xml。我認為原因可能有兩點:1. JSON可以很好的被很多程序支持,javascript的ajax可以直接將JSON轉換為對象。2. JSON的格式在容量上比xml小很多,可以減低寬帶占用,提高傳輸效率。

那么,返回值應該怎么去部署呢?

首先,字段的合理返回,數據的包裹。因為返回值中,我們常常要對數據進行區分分組,或者按照從屬關系打包,所以,我們再返回時,最好有包裹的思想,把數據存放在不同的包裹中進行返回。

{
  'error_code' : 0,
  'data' : {
      'user_id' : 1,
      'username' : 'xiaomin'
              },
  'server_time': 14939939
}

上面返回的JSON中,使用data來作為數據包,將所有數據統一以這個字段進行包裹。除了data,也可以用list等其他形式的包裹,命名都是自己來根據自己的需要確定的。

{
  'error_code' : 0,
  'list' : [
      {'user_id':1,'username':'xiaoming'},
      {'user_id':2,'username':'goudan'}
    ]
  'server_time': 14939939
}

總之,不要不分包,直接把所有數據和一些你想返回的全局數據混在一起進行返回。

其次,錯誤碼。錯誤碼的作用是方便查找錯誤原因,通常情況下,我喜歡用error_code來表示,當error_code=0時,表示沒有發生錯誤,當error_code>0時,發生了錯誤,并且提供較為詳細的文檔,告訴客戶端對應的error_code值所產生的錯誤的原因和位置。

最后,空白壓縮和字符轉換。也就是返回的JSON結果不要換行和空格,用一行返回結果,使整個結果文本容量最小。同時,中文等字符或結果中有引號,都進行字符轉換,防止結果無法被正確識別。

7. 鑒權

其實也就是客戶端的權限控制。一般而言,我會采用給客戶端分發一個token來確定該客戶端的唯一身份。客戶端在請求時,通過這個token,判斷發出請求的客戶端所對應的用戶,及其相關信息和權限。

前文已經提到了,token信息不是用來進行處理的數據,雖然可以通過POST、PUT等進行數據提交或傳輸,但是從RESTful規范來講,它不屬于操作數據,在服務端進行處理時,僅是利用token進行鑒權處理,所以,我的建議是通過header來發送token。

<?php
    $ch = curl_init();
    $url = 'http://api.xx.com/user';
    $header = array(
       'token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
       'X-HTTP-Method-Override: PUT'       
    );
    $data = array(
        'user_name' => 'xiaoming',
        'user_email' => 'xx@sfa.com'
    );
    // 添加apikey到header
    curl_setopt($ch, CURLOPT_HTTPHEADER  , $header);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    // 執行HTTP請求
    curl_setopt($ch , CURLOPT_URL , $url);
    $res = curl_exec($ch);
    var_dump(json_decode($res));
?>

上面的代碼中,通過將CURLOPT_CUSTOMREQUEST設置為PUT,就可以發出PUT請求,發出的PUT請求,仍然需要通過CURLOPT_POSTFIELDS來傳輸數據。服務端接受PUT請求時,首先要對發出請求的客戶端進行token驗證,通過對token的處理,查找到擁有該token的實際用戶,從而確定了將對哪一個用戶進行信息更新操作。

國內大部分API對PUT、DELETE請求進行了閹割,更不用提HEAD、PACTH、OPTIONS請求。實際上,國內大部分開放API僅支持GET和POST兩種,部分API支持將app key信息通過header進行發送。在面對這種情況下,我們不得不拋棄標準的RESTful規范,在url中加入get、add、update、delete等動作詞匯,以補充由于請求支持不完善帶來的動作區分問題。如果僅支持GET和POST,那么所有需要保密的數據,絕對不可以用GET來進行請求,而必須用POST。

參考文獻

《理解RESTful架構》《RESTful API 設計指南》

《好RESTful API的設計原則》

《我所理解的RESTful Web API [設計篇]》

《https工作原理》

我的個人博客 www.tangshuang.net 偶爾寫一些學習中的感想和經驗,希望有相同興趣的朋友到博客來交流。

 本文由用戶 weedw 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!