tags: RESTful Specification Apis Designjavascript
Author: Andy Ai
Weibo: NinetyHhtml
此文爲實踐總結,是本身在實踐過程當中積累的經驗和"哲學"。部份內容參考相關資料,參考內容請看尾頁。建議對RESTful有必定了解者閱讀!java
不要爲了RESTful而RESTfulgit
在能表達清楚的狀況下,簡單就是美github
示例: 指向ID爲yanbo.ai
的Account
對象json
GET http://~/$version/accounts/yanbo.ai
示例: 隱式地指向trades list 集合api
GET http://~/$version/trades/(list) 等同於 GET http://~/$version/trades
示例: Profile
是User
的聚合資源,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 Operation | Description |
---|---|
GET | 獲取,查找 |
POST | 新增建立 |
PUT | 更新 |
PATCH | 部分更新 |
DELETE | 刪除 |
網絡協議(HTTP, HTTPS)
服務器地址
版本
接口名稱
?參數列表
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
保證了v1
和v2
接口的共存。
示例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
保證了v1
和v2
接口的共存。
不使用大寫字母
使用中線-
代替下劃線_
參數列表應該被encode過
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" }
爲何須要區分?
`Microservices`是一個全新的概念,它主要的觀點是將一個大型的服務系統分解成多個微型系統。每一個微型系統都能獨立工做,而且提供各類不一樣的服務。獨立運行的特色使微型系統之間不會產生相互影響,其中的一個微型系統宕機並不會牽連到其餘的微型系統。這種架構使[分佈式系統的節點數量][6]大大提高。由於RESTful服務是無狀態的,因此這種分解並不會帶來狀態共享的問題。
路由規則(邏輯)
當咱們須要對不一樣屬性的接口作路由規則的時候,按功能劃分接口是一個很好的方案。例如:咱們要對系統設置接口設置增長更嚴格的調用限制。
網絡接口相對於堆棧接口來講數據傳輸極其不穩定,儘量地減小數據傳輸不只能控制這種風險還能減小流量。使用緩存還能有效地提升後臺的吞吐量。
後臺在響應請求時使用響應頭E-Tag
或Last-Modified
來標記數據的版本,前臺在發送請求時將數據版本經過請求頭If-None-Match
幫助後臺判斷緩存的使用。
Request Header
If-None-Match: 2390239059405940
Response Header
E-Tag: 2390239059405940 Last-Modified: 2014-04-05T14:30Z
在實際的環境中,有大量的查詢需求是相同的。將這些搜索需求標籤化能下降使用難度也能夠達到重用的目的。
示例1: 查找狀態爲關閉的訂單
普通方式
GET http://~/$version/trades?status=closed&sorting=-created_at
Bookmarker
GET http://~/$version/trades#recently_closed
或
GET http://~/$version/trades/recently_closed
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: 用戶註冊業務
用戶填寫E-Mail與密碼
完善用戶資料
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
調用資源服務器獲取通過資源全部者受權使用的資源。這種受權方式的特色有:
資源全部者能夠隨時撤銷受權許可
能夠經過撤銷token
拒絕客戶端的調用
資源服務器能夠拒絕客戶端的調用
經過這三種方式能夠作到對資源的嚴格保護。資源的訪問權限也把握在資源全部者的手中,而不是資源服務器。
固然,Oauth2受權框架也容許受信任的客戶端直接使用token
調用資源服務器獲取資源。這種靈活性徹底取決於客戶端類型和對資源的保護程度。
爲何受權碼要放在Http Header中?
WEB服務器對訪問作記錄已經成爲了行業的一個標準,訪問記錄不只能夠用來作訪問量統計還能用來作訪問特徵分析。互聯網廣告平臺就是利用訪問記錄來作精準營銷的。若是token
(受權碼)包含在URL中就有很大的安全風險。
包含在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
信息。
查詢,過濾條件使用query string。
用來描述數據或者請求的元數據放Header中,例如 X-Result-Fields
。
Content body 僅僅用來傳輸數據。
數據要作到拿來就可用的原則,不須要「拆箱」的過程。
使用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
是一種能夠跨平臺高擴展的輕量級的數據交換格式。易於人閱讀和編寫,同時也易於機器解析和生成。
不能使用大寫(大小寫友好)
使用下劃線_命名(鏈接兩個單詞)
屬性和字符串值必須使用雙引號""
無狀態服務器應該容許客戶端對數據按需提取。在請求頭使用X-Result-Fields
指定數據返回的字段集合。
例如:trade 有trade_id
, trade_name
, created_at
三個屬性,客戶端只需其中的trade_id
與trade_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錯誤。
錯誤信息應該包含下列內容:
錯誤標題 message
, 必須
錯誤代碼 error code
, 必須
錯誤信息 error message
, 必須
資源 resource
, 可選
屬性 field
, 可選
文檔地址 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/" } ] }
格式化(Pettyprint)JSON數據(返回結果)而且使用gzip壓縮,Pettyprint易於閱讀,多餘的空格在通過gzip壓縮以後佔用空間比壓縮以前更小。
重寫Server
頭
返回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
http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
https://developer.yahoo.com/social/rest_api_guide/http-response-codes.html
http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
未經贊成不可轉載, 轉載需保留原文連接與做者署名。