restful 風格API 實踐

  • 不要爲了RESTful而RESTful

  • 在能表達清楚的情況下,簡單就是美

接口路徑設計

接口設計原則

URI指向的是唯一的資源對象
示例: 指向ID爲cloud.mario的Account對象

GET http://~/$version/accounts/cloud.mario

URI可以隱式指向唯一的集合列表

示例: 隱式地指向trades list 集合

GET http://~/$version/trades/(list)

等同於

GET http://~/$version/trades

聚合資源必須通過父級資源操作

示例ProfileUser的聚合資源,User有一個唯一且私有的Profile資源,只能通過User操作Profile

更新user_id爲123456的Profile資源
PUT http://~/$version/users/123456/profiles
Request Body:{    
  "full_name""cloud.mario",    
  "state""Beijing",    
  "title""一個開發者"
  }

Http Methods

HTTP Operation Description
GET 獲取,查找
POST 新增創建
PUT 更新
PATCH 部分更新
DELETE 刪除

URL組成

  1. 網絡協議(HTTP, HTTPS)

  2. 服務器地址

  3. 版本

  4. 接口名稱

  5. ?參數列表

GET https://github.com/v1/trades

爲什麼需要版本?

當服務被更多其他系統使用的時候,服務的可用性和上下兼容變得至關重要。被外部系統依賴的服務在升級時是一個非常麻煩的事情,既要發佈新的接口,又要保留舊的接口留出時間讓調用者去升級。在URL中加入Version標示能很好地解決上下兼容(新老版本共存)問題。

示例1: URL中新增了Path parameter

v1版本

GET http://~/v1/trades?user_id=123456

v2版本

GET http://~/v2/:user_id/trades

示例1中的user_id參數在v2版本被加入到path parameter中,使用$version保證了v1v2接口的共存。

示例2: 數據接口發生變化

v1版本

GET http://~/v1/accounts/cloud.mario
Response Body:
{    
  "user_name""cloud.mario",    
  "e_mail""[email protected]",    
  "state""Beijign",    
  "title""一個開發者"
}

v2版本

GET http://~/v2/accounts/cloud.mario
Response Body:
{        
  "user_name""cloud.mario",        
  "e_mail""[email protected]",        
  "profile": 
    {            
      "state""Beijign",            
      "title""一個開發者"
        
    }
}

示例2中的接口返回數據結構已經發生了變化。使用$version保證了v1v2接口的共存。

URL定義限制

  1. 不使用大寫字母

  2. 使用中線-代替下劃線_

  3. 參數列表應該被encode過

接口分類

資源對象的CURD操作

GET http://~/$version/trades                 #獲取trades列表
GET http://~/$version/trades/:id            #根據id獲取單個trade
POST http://~/$version/trades               #創建trade
PUT http://~/$version/trades/:id             #根據id更新trade
PATCH http://~/$version/trades/:id        #根據id部分更新trade
DELETE http://~/$version/trades/:id      #根據id刪除trade

系統設置

使用settings標識,根據服務的屬性選擇http方法。

http://~/settings/$version/server-name

示例1: 搜索

GET http://~/services/$version/search?q=filter&category=file

示例2: 任務隊列操作

PUT http://~/services/$version/queued/jobs          往任務隊列裏面添加一個新的任務
DELETE http://~/services/$version/queued/jobs/:id   根據id刪除任務

示例3: 更改界面語言環境

PUT http://~/settings/$version/gui/lang
{    "lang""zh-CN"}

爲什麼需要區分?

  1. Microservices

Microservices是一個全新的概念,它主要的觀點是將一個大型的服務系統分解成多個微型系統。每個微型系統都能獨立工作,並且提供各種不同的服務。獨立運行的特點使微型系統之間不會產生相互影響,其中的一個微型系統宕機並不會牽連到其他的微型系統。這種架構使分佈式系統的節點數量大大提升。因爲RESTful服務是無狀態的,所以這種分解並不會帶來狀態共享的問題。

  1. 路由規則(邏輯)

當我們需要對不同屬性的接口做路由規則的時候,按功能劃分接口是一個很好的方案。例如:我們要對系統設置接口設置增加更嚴格的調用限制。

緩存

網絡接口相對於堆棧接口來說數據傳輸極其不穩定,儘可能地減少數據傳輸不僅能控制這種風險還能減少流量。使用緩存還能有效地提高後臺的吞吐量。

後臺在響應請求時使用響應頭E-TagLast-Modified來標記數據的版本,前臺在發送請求時將數據版本通過請求頭If-Match幫助後臺判斷緩存的使用。

Request Header

 If-Match: 2390239059405940

Response Header

E-Tag: 2390239059405940Last-Modified: 1403183502701

Bookmarker

在實際的環境中,有大量的查詢需求是相同的。將這些搜索需求標籤化能降低使用難度也可以達到重用的目的。

示例: 查找狀態爲關閉的訂單

普通方式
GET http://~/$version/trades?status=closed&sorting=-created_at

Bookmarker
GET http://~/$version/trades#recently_closed

GET http://~/$version/trades/recently_closed

HATEOAS

HATEOAS通過Web Linking的方式來描述程序的狀態信息

Link 主要包含以下屬性:

Property Description
rel 關聯內容
href URL
type 媒體類型
method Http Method
title 標題
arguments 參數列表
value 返回值

Rel 可能爲以下值:

Value Description
next 下一步
prev 上一步
first 第一步,最前
last 最後一步,最後
source 來源
self 資源自身,相對於this

Web Linking 可以通過兩種方式傳遞至客戶端:

Http Header

Link: <http://~/$version/trades?page_no=10>; rel="next", <http://~/$version/trades?page_no=19>; rel="last"

Http JSON Body

{
    "links": [
        {
            "rel""next",
            "href""http://~/$version/trades?page_no=1"
        },
        {
            "rel""last",
            "href""http://~/$version/trades?page_no=19"
        }
    ]
}

示例1: 用戶註冊業務

  1. 用戶填寫E-Mail與密碼

  2. 完善用戶資料

Register Request

POST http://~/$version/accountsHeaders:
    Accept: application/json    
    Content-Type: application/json;charset=utf-8Body:
    {        
    "username""[email protected]",        
    "e_mail""[email protected]",        
    "password""balabala"
    }

Register Response

Headers:
    Content-Type: application/json;charset=utf-8
Status: 201 Created
Body:
    {         
    "uri""http://~/$version/accounts/cloud.mario",        
    "identity""cloud.mario",        
    "created_at": 1403535668653,        
    "links": [
            {                
            "rel""next",                
            "href""http://~/$version/accounts/cloud.mario/profiles",                
            "method""POST",                
            "title""Editing Profiles",                
            "arguments""status=editing"
            }
        ]
    }

Profile Request

POST http://~/$version/accounts/cloud.mario/profilesHeaders:
    Accept: application/json    
    Content-Type: application/json;charset=utf-8Body:
    {        
    "full_name""cloud.mario",        
    "state""Beijing",        
    "title""一個開發者"
    }

Profile Response

Headers:
    Content-Type: application/json;charset=utf-8Status: 201 Created
Body:
    {        
    "uri": "http://~/$version/accounts/cloud.mario/profiles",        
    "identity": "cloud.mario",       
    "created_at": 1403535668653
    }

示例2: 請看下節<分頁>

HATEOAS在解決什麼問題?

HATEOAS是Hypermedia as the Engine of Application State的縮寫形式,中文意思爲:超媒體應用狀態引擎。它的核心思想是使用超媒體表達應用狀態,與hypertext-driven思想是一致的。在此之前,我們大多數的程序業務控制在前臺完成。例如:我們會在前臺做註冊流程,我們在前臺判定下一步應該做什麼,可以做什麼。當使用HATEOAS時,這些狀態流程控制都在應用程序的後臺完成。我們使用超媒體來表達前臺做完某一步驟之後可以做哪些? 這樣一來,前臺的任務就變得相當簡單了,前臺需要處理的是理解狀態表述,數據收集和結果顯示。

思考

HATEOAS會帶來怎樣的改變? 使用它的意義在哪?

分頁

Request

GET http://~/$version/trades?page=10&pre_page=100

Response

Link Header

Link: <http://~/$version/trades?page=11&pre_page=100>; rel="next", <http://~/$version/trades?page=19&pre_page=100>; rel="last"

JSON Body

{
    "links": [
        {
            "rel""next",
            "href""http://~/$version/trades?page=11&pre_page=100"
        },
        {
            "rel""last",
            "href""http://~/$version/trades?page=19&pre_page=100"
        }
    ]
}

安全

調用限制

爲保證服務的可用性應對服務進行調用過載保護

Response Headers

X-RateLimit-Limit: 3000             調用量的最大限制
X-RateLimit-Reset: 1403162176516    調用限制重置時間
X-RateLimit-Remaining: 299          剩餘的調用量

安全驗證

RESTful服務使用Oauth2的方式進行調用授權,使用http請求頭Authorization設置授權碼; 必須使用User-Agent設置客戶端信息, 無User-Agent請求頭的請求應該被拒絕訪問。

Request Header

User-Agent: Data-Server-Client
Authorzation: Bearer 383w9JKJLJFw4ewpie2wefmjdlJLDJF

爲什麼建議使用Oauth2授權?

Oauth2的參與者爲:客戶端,資源所有者,授權服務器,資源服務器。客戶端先從資源所有者得到授權碼之後使用授權碼從授權服務器得到token,再使用token調用資源服務器獲取經過資源所有者授權使用的資源。這種授權方式的特點有:

  1. 資源所有者可以隨時撤銷授權許可

  2. 可以通過撤銷token拒絕客戶端的調用

  3. 資源服務器可以拒絕客戶端的調用

通過這三種方式可以做到對資源的嚴格保護。資源的訪問權限也把握在資源所有者的手中,而不是資源服務器。
當然,Oauth2授權框架也允許受信任的客戶端直接使用token調用資源服務器獲取資源。這種靈活性完全取決於客戶端類型和對資源的保護程度。

爲什麼授權碼要放在Http Header中?

  1. WEB服務器對訪問做記錄已經成爲了行業的一個標準,訪問記錄不僅可以用來做訪問量統計還能用來做訪問特徵分析。互聯網廣告平臺就是利用訪問記錄來做精準營銷的。如果token(授權碼)包含在URL中就有很大的安全風險。

  2. 包含在URL中的token串可能被進行重定向傳遞。通過這兩種方式入侵者可以不通過授權而使用泄漏的授權碼訪問那些受保護的數據,會造成數據泄漏的風險。

以Apache爲例,訪問日誌爲:

127.0.0.1 - - [24/Jun/2014:14:38:04 +0800"GET /v1/accounts/cloud.mario?token=dgdreLJLJLER798989erJKJK HTTPS/1.1" 200 343

通過對訪問日誌的提取,很容易得到token信息。

數據設計

交互原則

  1. 查詢,過濾條件使用query string。

  2. 用來描述數據或者請求的元數據放Header中,例如 X-Result-Fields

  3. Content body 僅僅用來傳輸數據。

  4. 數據要做到拿來就可用的原則,不需要“拆箱”的過程。

結構

使用JSON格式傳輸數據,在http請求頭和響應頭申明Content-Type。返回的數據結構應該做到儘可能簡單,不要過於包裝。響應狀態應該包含在響應頭中!

Request

Accept: application/json
Content-Type: application/json;charset=UTF-8

Response

Content-Type: application/json;charset=UTF-8

錯誤的做法

{
    "status"200,
    "data": {
        "trade_id"1234,
        "trade_name""Bala bala"
    }
}

正確的做法

Response Headers:
    Status: 200
    Response Body:
    {        
    "trade_id"1234,        
    "trade_name""Bala bala"
    }

示例1: 創建User對象

POST http://~/$version/users
Request
    headers:
        Accept: application/json       
        Content-Type: application/json;charset=UTF-8
    body:
        {            
        "user_name""Cloud Mario"
        }
Response
    status: 201 Created
    headers:
        Content-Type: application/json;charset=UTF-8
    body:
        {            
        "uri""http://~/$version/users/1234",            
        "identity"1234,            
        "created_on""Date()",            
        "links": [
                {                    
                "rel""next",                    
                "href""http://~/gui/users/1234"
                }
            ]
        }

爲什麼是JSON?

JSON 是一種可以跨平臺高擴展的輕量級的數據交換格式。易於人閱讀和編寫,同時也易於機器解析和生成。

屬性定義限制

  1. 不能使用大寫(大小寫友好)

  2. 使用下劃線_命名(連接兩個單詞)

  3. 屬性和字符串值必須使用雙引號""

提取部分字段

無狀態服務器應該允許客戶端對數據按需提取。在請求頭使用X-Result-Fields指定數據返回的字段集合。

例如:trade 有trade_id, trade_namecreated_at 三個屬性,客戶端只需其中的trade_idtrade_name屬性。

Request Header

X-Result-Fields: trade_id,trade_name

子對象描述

數據裏面的子對象使用URI描述不應該被提取,除非用戶指定需要提取子對象

示例trade裏面的order對象

錯誤的做法

{
    "trade_id""123456789",
    "full_path"null,
    "order": {
        "order_id""987654321"
    }
}

正確的做法

{
    "trade_id""123456789",
    "order""http://~/$version/orders/987654321"
}

應用指定提取子對象,需要在請求頭聲明X-Expansion-Fields

Request

X-Expansion-Fields: true

爲什麼要客戶端指定提取子對象時才提取?

懶模式服務能夠最大程度地節省運算資源。雖然與客戶端交互的次數有所增加,但是能做到按需提取,按需響應,這也是響應式設計的一大特點。客戶端的用戶行爲模式無法真實地模擬,也就無法確定哪些資源需要做到一次性推送,讓客戶端按需使用是一個不錯的方式。

關於空字段

應該在返回結果裏面剔除空字段,因爲null值傳輸到客戶端並沒有實際的含義,反而增加了佔用空間。

Tips

使用HTTP Header時,優先使用合適的標準頭屬性。用X-作爲前綴自定義一個頭屬性,例如: X-Result-Fields

狀態碼&錯誤處理

應用狀態碼

Code HTTP Operation Body Contents Description
200 GET,PUT 資源 操作成功
201 POST 資源,元數據 對象創建成功
202 POST,PUT,DELETE,PATCH N/A 請求已經被接受
204 DELETE,PUT,PATCH N/A 操作已經執行成功,但是沒有返回數據
301 GET link 資源已被移除
303 GET link 重定向
304 GET N/A 資源沒有被修改
400 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 參數列表錯誤(缺少,格式不匹配)
401 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 未授權
403 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 訪問受限,授權過期
404 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 資源,服務未找到
405 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 不允許的http方法
409 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 資源衝突,或者資源被鎖定
415 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 不支持的數據(媒體)類型
429 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 請求過多被限制
500 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 系統內部錯誤
501 GET,PSOT,PUT,DELETE,PATCH 錯誤提示(消息) 接口未實現

容器狀態碼

容器狀態碼是指http容器的狀態碼,應用不應該使用或限制使用

Code HTTP Operation Body Contents Description
303 GET link 靜態資源被移除,應用限制使用
503 GET,PSOT,PUT,DELETE,PATCH text body 服務器宕機

Tips

4開頭的錯誤用來表達來自於客戶端的錯誤,例如: 未授權,參數缺失。5開頭的錯誤用來表達服務端的錯誤,例如: 在連接外部系統(DB)發生的IO錯誤。

錯誤信息格式

錯誤信息應該包含下列內容:

  1. 錯誤標題 message, 必須

  2. 錯誤代碼 error code, 必須

  3. 錯誤信息 error message, 必須

  4. 資源 resource, 可選

  5. 屬性 field, 可選

  6. 文檔地址 document, 可選

Tips

Error Code 儘可能做到簡潔明瞭,提取異常的關鍵字並且使用下劃線_把它們連接起來。

示例: 調用頻率超過限制,Response:

    Headers:
        Content-Type: application/json;charset=UTF-8
        X-RateLimit-Limit: 3000
        X-RateLimit-Reset1403162176516
        X-RateLimit-Remaining: 0
    {       
     "message""Message title",        
     "errors": [
            {                
             "code""rate_limit_exceeded",               
             "message""Too Many Requests. API rate limit exceeded",               
             "document""https://developer.github.com/v3/gists/"
            }
        ]
    }

錦上添花

  1. 格式化(Pettyprint)JSON數據(返回結果)並且使用gzip壓縮,Pettyprint易於閱讀,多餘的空格在經過gzip壓縮之後佔用空間比壓縮之前更小。

  2. 重寫Server

  3. 返回X-Powered-By

Response Headers

X-Pretty-Print: true
Content-Encoding: gzip
Server: [email protected]
X-Powered-By: cloud.mario;[email protected]

資源福利

框架&工具

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章