RESTful Best Practices

RESTful Best Practices

tags: RESTful Specification Apis Designjavascript

Author: Andy Ai
Weibo: NinetyHhtml

聲明

此文爲實踐總結,是本身在實踐過程當中積累的經驗和"哲學"。部份內容參考相關資料,參考內容請看尾頁。建議對RESTful有必定了解者閱讀!java

哲學

  • 不要爲了RESTful而RESTfulgit

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

接口路徑設計

接口設計原則

URI指向的是惟一的資源對象

示例: 指向ID爲yanbo.aiAccount對象json

GET http://~/$version/accounts/yanbo.ai

URI能夠隱式指向惟一的集合列表

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

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": "yanbo.ai",
    "state": "Shanghai",
    "title": "Senior software engineer"
}

組合資源要避免資源路徑嵌套

示例: 一個系統裏面包含多個 applications,一個 application 又包含多個 users。那獲取 user 資源的路徑應該是怎樣的?安全

看一個路徑嵌套的例子:服務器

GET http://~/$version/systems/:systemId/applications/:applicationId/users/:userId

這樣作是不合理的,它會讓你的接口變得愈來愈混亂和缺乏靈活性。正確的作法是:

GET http://~/$version/systems/:systemId
GET http://~/$version/applications/:applicationId 
GET http://~/$version/users/:userId/

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/yanbo.ai
Response Body:
{
    "user_name": "yanbo.ai",
    "e_mail": "yanbo.ai@gmail.com",
    "state": "Shanghai",
    "title": "Senior software engineer"
}

v2版本

GET http://~/v2/accounts/yanbo.ai
Response Body:
{
    "user_name": "yanbo.ai",
    "e_mail": "yanbo.ai@gmail.com",
    "profile": {
        "state": "Shanghai",
        "title": "Senior software engineer"
    }
}

示例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

服務型接口

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

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

系統設置

使用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`是一個全新的概念,它主要的觀點是將一個大型的服務系統分解成多個微型系統。每一個微型系統都能獨立工做,而且提供各類不一樣的服務。獨立運行的特色使微型系統之間不會產生相互影響,其中的一個微型系統宕機並不會牽連到其餘的微型系統。這種架構使[分佈式系統的節點數量][6]大大提高。由於RESTful服務是無狀態的,因此這種分解並不會帶來狀態共享的問題。
  2. 路由規則(邏輯)

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

緩存

網絡接口相對於堆棧接口來講數據傳輸極其不穩定,儘量地減小數據傳輸不只能控制這種風險還能減小流量。使用緩存還能有效地提升後臺的吞吐量。
後臺在響應請求時使用響應頭E-TagLast-Modified來標記數據的版本,前臺在發送請求時將數據版本經過請求頭If-None-Match幫助後臺判斷緩存的使用。

Request Header

If-None-Match: 2390239059405940

Response Header

E-Tag: 2390239059405940
Last-Modified: 2014-04-05T14:30Z

Bookmarker

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

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

普通方式

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/accounts
Headers:
    Accept: application/json
    Content-Type: application/json;charset=utf-8
Body:
    {
        "username": "yanbo.ai@gmail.com",
        "e_mail": "yanbo.ai@gmail.com",
        "password": "balabala"
    }

Register Response

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

Profile Request

POST http://~/$version/accounts/yanbo.ai/profiles
Headers:
    Accept: application/json
    Content-Type: application/json;charset=utf-8
Body:
    {
        "full_name": "yanbo.ai",
        "state": "Shanghai",
        "title": "Senior software engineer"
    }

Profile Response

Headers:
    Content-Type: application/json;charset=utf-8
Status: 201 Created
Body:
    {
        "uri": "http://~/$version/accounts/yanbo.ai/profiles",
        "identity": "yanbo.ai",
        "created_at": "2014-04-05T14:30Z"
    }

示例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串可能被進行重定向傳遞。經過這兩種方式入侵者能夠不經過受權而使用泄漏的受權碼訪問那些受保護的數據,會形成數據泄漏的風險。

以Tomcat爲例,訪問日誌爲:

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

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


數據設計

交互原則

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

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

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

  4. 數據要作到拿來就可用的原則,不須要「拆箱」的過程。

  5. 使用ISO-8601格式表達時間字段,例如: 2014-04-05T14:30Z

結構

使用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": "Andy Ai"
        }
        
Response
    status: 201 Created
    headers:
        Content-Type: application/json;charset=UTF-8
    body:
        {
            "uri": "http://~/$version/users/1234",
            "identity": 1234,
            "created_at": "2014-04-05T14:30Z",
            "links": [
                {
                    "rel": "next",
                    "href": "http://~/gui/users/1234"
                }
            ]
        }

爲何是JSON?

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

屬性定義限制

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

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

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

提取部分字段

無狀態服務器應該容許客戶端對數據按需提取。在請求頭使用X-Result-Fields指定數據返回的字段集合。
例如:trade 有trade_id, trade_name, created_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
102 Processing GET, POST, PUT, DELETE, PATCH 處理狀態的信息 當前請求正在處理
200 Ok GET, PUT 資源 操做成功
201 Created POST, PUT 資源, 元數據 對象建立成功
202 Accepted POST, PUT, DELETE, PATCH 處理信息 請求已經被接受
204 No Content DELETE, PUT, PATCH N/A 操做已經執行成功,可是沒有返回數據
301 Moved Permanently GET link 資源已被移除
303 See Other GET link 重定向
304 Not Modified GET N/A 資源沒有被修改
400 Bad Request GET, POST, PUT, DELETE, PATCH 錯誤提示 參數列表錯誤(缺乏,格式不匹配)
401 Unauthorized GET, POST, PUT, DELETE, PATCH 錯誤提示 未受權
403 Forbidden GET, POST, PUT, DELETE, PATCH 錯誤提示 訪問受限,受權過時
404 Not Found GET, POST, PUT, DELETE, PATCH 錯誤提示 資源,服務未找到
405 Method Not Allowed GET, POST, PUT, DELETE, PATCH 錯誤提示 不容許的http方法
406 Not Acceptable GET, POST, PUT, DELETE, PATCH 錯誤提示 媒體內容不符合要求
408 Request Timeout GET, POST, PUT, DELETE, PATCH 錯誤提示 請求超時
409 Conflict GET, POST, PUT 錯誤提示 資源衝突,重複的資源
415 Unsupported Media Type GET, POST, PUT, DELETE, PATCH 錯誤提示 不支持的數據(媒體)類型
422 Unprocessable Entity GET, POST, PUT, PATCH 錯誤提示 請求格式正確,可是因爲含有語義錯誤,沒法響應。
423 Locked GET, POST, PUT, DELETE, PATCH 錯誤提示 當前資源被鎖定
429 Too Many Requests GET, POST, PUT, DELETE, PATCH 錯誤提示 請求過多被限制
500 Internal Server Error GET, POST, PUT, DELETE, PATCH 錯誤提示 系統內部錯誤
501 Not Implemented GET, POST, PUT, DELETE, PATCH 錯誤提示 接口未實現

容器狀態碼

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

Code HTTP Operation Body Contents Description
303 GET link 靜態資源被移除,應用限制使用
503 GET, POST, 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-Reset: 1403162176516
    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: ods@shuyun.com
X-Powered-By: yanbo.ai;email=yanbo.ai@gmail.com

附頁

框架&工具

參考資料

未經贊成不可轉載, 轉載需保留原文連接與做者署名。

相關文章
相關標籤/搜索