7點關於RESTful規範的API接口設計的想法

在項目中,須要爲APP撰寫API。剛開始接觸的時候,並無考慮太多,就想提供URL,APP端經過該URL進行查詢、建立、更新等操做便可。但再對相關規範進行了解後,才發現,API的設計並無那麼簡單,遠遠不是URL的問題,而是一個通訊協議的總體架構。所以,我寫這篇文章,用來記錄本身的一些心得,並不斷完善。並提供關於RESTful API的一些參考文獻。javascript

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

首先,使用https能夠在數據包被抓取時多一層加密。咱們如今的APP使用環境大部分都是在路由器WIFI環境下,一旦路由器被入侵,那麼黑客能夠很是容易的抓取到用戶經過路由器傳輸的數據,若是使用http未經加密,那麼黑客能夠很輕鬆的獲取用戶的信息,甚至是帳戶信息。php

其次,即便使用https,也要在API數據傳輸設計時,正確的採用加密。例如直接將token信息放在URL中的作法,即便你使用了https,黑客抓不到你具體傳輸的數據,可是能夠抓到你請求的URL啊!(查了資料了,https用GET方式請求,也僅能抓到域名字符部分,不能抓到請求的數據,可是URL能夠在瀏覽器或特殊客戶端工具中直接看到。多謝下面的朋友指正錯誤)所以,使用https進行請求時,要採用POST、PUT或者HEAD的方式傳輸必要的數據。java

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

請求模式也能夠說是動做、數據傳輸方式,一般咱們在web中的form有GET、POST兩種,而在HTTP中,存在下發這幾種。web

GET (選擇):從服務器上獲取一個具體的資源或者一個資源列表。
POST (建立): 在服務器上建立一個新的資源。
PUT(更新):以總體的方式更新服務器上的一個資源。
PATCH (更新):只更新服務器上一個資源的一個屬性。
DELETE(刪除):刪除服務器上的一個資源。
HEAD : 獲取一個資源的元數據,如數據的哈希值或最後的更新時間。
OPTIONS:獲取客戶端能對資源作什麼操做的信息。ajax

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

閱讀RESTful架構的參考文獻以後,你會了解什麼是資源的概念,以及REST的確切含義。再構建API的URL的時候,URI中應該僅包含資源(對象),而不要加入動做。好比 /user/1/update ,其中update就是一個動做,雖然咱們但願經過這個URI來實現用戶ID爲1的用戶進行信息更新,可是按照RESTful的規範,做爲動做,應該用上面的PUT來表示,因此請求更新用戶信息,應該使用 PUT /user/1 來表示更新用戶ID爲1的用戶信息。json

若是去對應上面的請求模式,GET表示顯示、列出、展現,POST表示提交、建立,PUT表示更新,DELETE表示刪除。api

<?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中經過v一、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 偶爾寫一些學習中的感想和經驗,但願有相同興趣的朋友到博客來交流。

相關文章
相關標籤/搜索