大多數現代的Web應用程序都公開了客戶端能夠用來與應用程序交互的API。精心設計的Web API應該旨在支持:
數據庫
平臺獨立性。不管內部如何實現API,任何客戶端都應該可以調用API。這須要使用標準協議,並具備一種機制,使客戶端和Web服務能夠就要交換的數據格式達成一致。json
服務演進。Web API應該可以獨立於客戶端應用程序進行演化和添加功能。隨着API的發展,現有的客戶端應用程序應繼續運行而無需修改。全部功能都應該是可發現的,以便客戶端應用程序能夠充分使用它。後端
本文介紹了設計Web API時應考慮的問題。api
在2000年,羅伊·菲爾丁(Roy Fielding)提出了表明性狀態轉移(REST)做爲設計Web服務的體系結構方法。REST是一種用於構建基於超媒體的分佈式系統的體系結構樣式。REST獨立於任何底層協議,不必定與HTTP綁定。可是,大多數常見的REST實現使用HTTP做爲應用程序協議,而且本指南重點介紹爲HTTP設計REST API。緩存
REST相對於HTTP的主要優點在於它使用開放標準,而且不會將API或客戶端應用程序的實現綁定到任何特定的實現。例如,REST Web服務能夠用ASP.NET編寫,而且客戶端應用程序可使用能夠生成HTTP請求並解析HTTP響應的任何語言或工具集。bash
如下是使用HTTP的RESTful API的一些主要設計原則:服務器
REST API是圍繞網絡
資源具備數據結構
客戶端經過交換資源架構
REST API使用統一的接口,這有助於使客戶端和服務實現脫鉤。對於基於HTTP構建的REST API,統一接口包括使用標準HTTP動詞對資源執行操做。最多見的操做是GET,POST,PUT,PATCH和DELETE。
REST API使用無狀態請求模型。HTTP請求應該是獨立的,而且能夠以任何順序發生,所以在請求之間保留瞬態信息是不可行的。信息存儲的惟一位置是資源自己,每一個請求都應該是原子操做。此約束使Web服務具備高度可伸縮性,由於不須要在客戶端和特定服務器之間保留任何親緣關係。任何服務器均可以處理來自任何客戶端的任何請求。也就是說,其餘因素可能會限制可伸縮性。例如,許多Web服務寫入後端數據存儲,這可能很難擴展。
REST API由表示形式中包含的超媒體連接驅動。例如,如下顯示了訂單的JSON表示形式。它包含獲取或更新與訂單關聯的客戶的連接。
2008年,倫納德·理查森(Leonard Richardson)爲Web API 提出瞭如下成熟度模型:
級別0:定義一個URI,而且全部操做都是對此URI的POST請求。
級別1:爲單個資源建立單獨的URI。
級別2:使用HTTP方法定義對資源的操做。
級別3:使用超媒體(HATEOAS,以下所述)。
根據Fielding的定義,級別3對應於真正的RESTful API。實際上,許多已發佈的Web API都屬於2級左右。
着重於Web API公開的業務實體。例如,在電子商務系統中,主要實體多是客戶和訂單。建立訂單能夠經過發送包含訂單信息的HTTP POST請求來實現。HTTP響應指示訂單是否成功下達。若是可能,資源URI應基於名詞(資源)而不是動詞(對資源的操做)。
https://www.abc.com/orders // Goodhttps://www.abc.com/create-order // Avoid複製代碼
資源沒必要基於單個物理數據項。例如,訂單資源可能在內部實現爲關係數據庫中的多個表,但做爲單個實體呈現給客戶。避免建立僅反映數據庫內部結構的API。REST的目的是爲實體以及應用程序能夠在這些實體上執行的操做建模。客戶不該接觸內部實現。
實體一般被組合到集合中(訂單,客戶)。集合是與集合中項目無關的資源,而且應具備本身的URI。例如,如下URI可能表明訂單的集合:
https://www.abc.com/orders
向集合URI發送HTTP GET請求可檢索集合中的項目列表。集合中的每一個項目都有本身的惟一URI。對商品URI的HTTP GET請求返回該商品的詳細信息。
在URI中採用一致的命名約定。一般,對引用集合的URI使用複數名詞會有所幫助。將集合和項目的URI組織到層次結構中是一個好習慣。例如,/customers
是通往客戶集合/customers/5
的路徑,是通往ID等於5的顧客的路徑。這種方法有助於保持Web API的直觀性。另外,許多Web API框架均可以基於參數化URI路徑來路由請求,所以您能夠爲path定義路由/customers/{id}
。
還請考慮不一樣類型的資源之間的關係以及如何暴露這些關聯。例如,/customers/5/orders
可能表明客戶5的全部訂單。您也能夠朝另外一個方向前進,並使用URI之類的URI表示從訂單到客戶的關聯/orders/99/customer
。可是,將模型擴展得太遠可能難以實施。更好的解決方案是在HTTP響應消息的正文中提供指向關聯資源的可導航連接。
在更復雜的系統中,提供URI使客戶端可以瀏覽多個級別的關係(例如)可能很誘人/customers/1/orders/99/products
。可是,若是未來資源之間的關係發生變化,則這種複雜性級別可能難以維護,而且很難保持靈活性。相反,請嘗試使URI保持相對簡單。一旦應用程序引用了資源,就應該可使用該引用來查找與該資源有關的項目。能夠將前面的查詢替換爲URI,/customers/1/orders
以查找客戶1的全部訂單,而後/orders/99/products
查找此訂單中的產品。
另外一個因素是,全部Web請求都會對Web服務器施加負載。請求越多,負載越大。所以,請嘗試避免使用暴露大量小資源的「聊天」 Web API。這樣的API可能要求客戶端應用程序發送多個請求以查找其所需的全部數據。取而代之的是,您可能想對數據進行非規範化並將相關信息合併爲更大的資源,這些資源能夠經過單個請求檢索。可是,您須要在此方法與獲取客戶端不須要的數據的開銷之間取得平衡。檢索大對象可能會增長請求的延遲,並致使額外的帶寬成本。
避免在Web API和基礎數據源之間引入依賴關係。例如,若是您的數據存儲在關係數據庫中,則Web API不須要將每一個表都顯示爲資源的集合。實際上,這多是一個糟糕的設計。相反,能夠將Web API視爲數據庫的抽象。若有必要,請在數據庫和Web API之間引入一個映射層。這樣,客戶端應用程序就不會與基礎數據庫方案的更改保持隔離。
最後,可能沒法將Web API實施的每一個操做映射到特定資源。您能夠經過調用功能的HTTP請求來處理此類
HTTP協議定義了許多將語義分配給請求的方法。大多數RESTful Web API使用的常見HTTP方法是:
GET以指定的URI檢索資源的表示形式。響應消息的正文包含所請求資源的詳細信息。
POST在指定的URI處建立一個新資源。請求消息的正文提供了新資源的詳細信息。請注意,POST也能夠用於觸發實際上並不建立資源的操做。
PUT能夠建立或替換指定URI處的資源。請求消息的正文指定要建立或更新的資源。
PATCH執行資源的部分更新。請求主體指定要應用於資源的一組更改。
DELETE刪除指定URI處的資源。
特定請求的效果應取決於資源是集合仍是單個項目。下表使用電子商務示例總結了大多數RESTful實現所採用的通用約定。並不是全部這些請求均可以實現-這取決於特定的方案。
資源資源 | 開機自檢 | 獲得 | 放 | 刪除 |
---|---|---|---|---|
/顧客 | 創建新客戶 | 檢索全部客戶 | 批量更新客戶 | 刪除全部 客戶 |
/客戶/ 1 | 錯誤 | 檢索客戶1的詳細信息 | 更新客戶1的詳細信息(若是存在) | 刪除客 戶1 |
/ customers / 1 /訂單 | 爲客戶1建立新訂單 | 檢索客戶1的全部訂單 | 批量更新客戶1的訂單 | 刪除客戶1的全部訂單 |
POST,PUT和PATCH之間的差別可能使人困惑。
POST請求建立資源。服務器爲新資源分配一個URI,並將該URI返回給客戶端。在REST模型中,您常常將POST請求應用於集合。新資源將添加到集合中。POST請求也能夠用於提交數據以處理現有資源,而無需建立任何新資源。
PUT請求建立資源
PATCH請求對現有資源執行
PUT請求必須是冪等的。若是客戶端屢次提交相同的PUT請求,則結果應始終相同(將使用相同的值修改相同的資源)。POST和PATCH請求不保證是冪等的。
本節描述了設計符合HTTP規範的API的一些典型注意事項。可是,它沒有涵蓋全部可能的細節或場景。若有疑問,請查閱HTTP規範。
如前所述,客戶端和服務器交換資源的表示形式。例如,在POST請求中,請求主體包含要建立的資源的表示形式。在GET請求中,響應主體包含獲取的資源的表示形式。
在HTTP協議中,經過使用
請求或響應中的Content-Type標頭指定表示的格式。這是一個包含JSON數據的POST請求示例:
POSThttps://adventure-works.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
若是服務器不支持媒體類型,則應返回HTTP狀態碼415(不支持的媒體類型)。
客戶端請求能夠包含一個Accept標頭,該標頭包含客戶端將在響應消息中從服務器接受的媒體類型列表。例如:
GEThttps://adventure-works.com/orders/2 HTTP/1.1 Accept: application/json
若是服務器沒法匹配列出的任何媒體類型,則服務器應返回HTTP狀態代碼406(不可接受)。
成功的GET方法一般返回HTTP狀態代碼200(肯定)。若是找不到資源,則該方法應返回404(未找到)。
若是POST方法建立新資源,它將返回HTTP狀態代碼201(已建立)。新資源的URI包含在響應的Location標頭中。響應主體包含資源的表示形式。
若是該方法進行了一些處理但未建立新資源,則該方法能夠返回HTTP狀態代碼200,並將操做結果包括在響應主體中。或者,若是沒有要返回的結果,則該方法能夠返回沒有響應正文的HTTP狀態代碼204(無內容)。
若是客戶端將無效數據放入請求中,則服務器應返回HTTP狀態代碼400(錯誤請求)。響應主體能夠包含有關錯誤的其餘信息,也能夠包含提供更多詳細信息的URI連接。
PUT方法
若是PUT方法建立新資源,則與POST方法同樣,它返回HTTP狀態代碼201(已建立)。若是該方法更新了現有資源,則返回200(肯定)或204(無內容)。在某些狀況下,可能沒法更新現有資源。在這種狀況下,請考慮返回HTTP狀態代碼409(衝突)。
考慮實現批量HTTP PUT操做,該操做能夠批量更新集合中的多個資源。PUT請求應指定集合的URI,請求主體應指定要修改的資源的詳細信息。這種方法能夠幫助減小聊天狀況並提升性能。
若是刪除操做成功,則Web服務器應使用HTTP狀態代碼204進行響應,指示該過程已成功處理,可是響應主體不包含其餘信息。若是資源不存在,則Web服務器能夠返回HTTP 404(未找到)。
有時POST,PUT,PATCH或DELETE操做可能須要一些處理才能完成。若是在發送響應到客戶端以前等待完成,則可能會致使沒法接受的延遲。若是是這樣,請考慮使操做異步。返回HTTP狀態碼202(已接受)以指示請求已接受處理,但未完成。
您應該公開一個返回異步請求狀態的端點,以便客戶端能夠經過輪詢狀態端點來監視狀態。在202響應的Location標頭中包含狀態終結點的URI。例如:
HTTP/1.1 202 Accepted
Location: /api/status/12345
若是客戶端將GET請求發送到此端點,則響應應包含請求的當前狀態。可選地,它還能夠包括估計的完成時間或取消操做的連接。
HTTP/1.1 200 OK
Content-Type: application/json
{
"status":"In progress", "link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" } }
若是異步操做建立了新資源,則該操做完成後,狀態端點應返回狀態代碼303。在303響
應中,包括一個Location標頭,該標頭提供了新資源的URI:
HTTP/1.1 303 See Other
Location: /api/orders/12345
當僅須要一部分信息時,經過單個URI公開資源集合可能致使應用程序獲取大量數據。例如,假設客戶端應用程序須要查找成本超過特定值的全部訂單。它可能會從
相反,API能夠容許在URI的查詢字符串中傳遞過濾器,例如
minCost
查詢字符串中的參數,並在服務器端返回過濾後的結果。
收集資源上的GET請求可能會返回大量項目。您應該設計一個Web API來限制任何單個請求返回的數據量。考慮支持查詢字符串,這些字符串指定要檢索的最大項目數以及集合中的起始偏移量。例如:
/orders?limit=25&offset=50
還應考慮對返回的項目數施加上限,以幫助防止拒絕服務攻擊。爲了幫助客戶端應用程序,返回分頁數據的GET請求還應該包括某種形式的元數據,以指示集合中可用資源的總數。
經過提供將字段名做爲值的排序參數,例如
若是每一個項目包含大量數據,則能夠擴展此方法以限制爲每一個項目返回的字段。例如,您可使用查詢字符串參數,該參數接受以逗號分隔的字段列表,例如
在查詢字符串中爲全部可選參數提供有意義的默認值。例如,若是實現分頁,則將參數設置limit
爲10,將offset
參數設置爲0,若是實現排序,則將sort參數設置爲資源的鍵,fields
若是支持投影,則將參數設置爲資源中的全部字段。
資源可能包含較大的二進制字段,例如文件或圖像。爲了克服由不可靠和間歇性鏈接引發的問題並改善響應時間,請考慮使此類資源可以分塊檢索。爲此,Web API應該支持用於大資源的GET請求的Accept-Ranges標頭。此標頭表示GET操做支持部分請求。客戶端應用程序能夠提交GET請求,該請求返回指定爲字節範圍的資源子集。
另外,請考慮爲這些資源實現HTTP HEAD請求。HEAD請求與GET請求類似,不一樣之處在於,它僅返回描述資源的HTTP標頭,且消息正文爲空。客戶端應用程序能夠發出HEAD請求,以肯定是否經過使用部分GET請求來獲取資源。例如:
HEADhttps://adventure-works.com/products/10?fields=productImage HTTP/1.1
這是示例響應消息:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580
Content-Length標頭提供資源的總大小,Accept-Ranges標頭指示相應的GET操做支持部分結果。客戶端應用程序可使用此信息以較小的塊來檢索圖像。第一個請求經過使用Range標頭獲取前2500個字節:
HTTP/1.1 303 See Other
Location: /api/orders/12345
當僅須要一部分信息時,經過單個URI公開資源集合可能致使應用程序獲取大量數據。例如,假設客戶端應用程序須要查找成本超過特定值的全部訂單。它可能會從
相反,API能夠容許在URI的查詢字符串中傳遞過濾器,例如
minCost
查詢字符串中的參數,並在服務器端返回過濾後的結果。
收集資源上的GET請求可能會返回大量項目。您應該設計一個Web API來限制任何單個請求返回的數據量。考慮支持查詢字符串,這些字符串指定要檢索的最大項目數以及集合中的起始偏移量。例如:
/orders?limit=25&offset=50
還應考慮對返回的項目數施加上限,以幫助防止拒絕服務攻擊。爲了幫助客戶端應用程序,返回分頁數據的GET請求還應該包括某種形式的元數據,以指示集合中可用資源的總數。
經過提供將字段名做爲值的排序參數,例如
若是每一個項目包含大量數據,則能夠擴展此方法以限制爲每一個項目返回的字段。例如,您可使用查詢字符串參數,該參數接受以逗號分隔的字段列表,例如
在查詢字符串中爲全部可選參數提供有意義的默認值。例如,若是實現分頁,則將參數設置limit
爲10,將offset
參數設置爲0,若是實現排序,則將sort參數設置爲資源的鍵,fields
若是支持投影,則將參數設置爲資源中的全部字段。
資源可能包含較大的二進制字段,例如文件或圖像。爲了克服由不可靠和間歇性鏈接引發的問題並改善響應時間,請考慮使此類資源可以分塊檢索。爲此,Web API應該支持用於大資源的GET請求的Accept-Ranges標頭。此標頭表示GET操做支持部分請求。客戶端應用程序能夠提交GET請求,該請求返回指定爲字節範圍的資源子集。
另外,請考慮爲這些資源實現HTTP HEAD請求。HEAD請求與GET請求類似,不一樣之處在於,它僅返回描述資源的HTTP標頭,且消息正文爲空。客戶端應用程序能夠發出HEAD請求,以肯定是否經過使用部分GET請求來獲取資源。例如:
HEADhttps://adventure-works.com/products/10?fields=productImage HTTP/1.1
這是示例響應消息:
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580
Content-Length標頭提供資源的總大小,Accept-Ranges標頭指示相應的GET操做支持部分結果。客戶端應用程序可使用此信息以較小的塊來檢索圖像。第一個請求經過使用Range標頭獲取前2500個字節:
GEThttps://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499
響應消息經過返回HTTP狀態代碼206指示這是部分響應。Content-Length標頭指定了消息正文中返回的實際字節數(不是資源的大小),而Content-Range標頭指示了哪一個響應。這是資源的一部分(4580中的字節0-2499):
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580
來自客戶端應用程序的後續請求能夠檢索資源的其他部分。
REST背後的主要動機之一是,無需事先了解URI方案,就應該能夠瀏覽整個資源集。每一個HTTP GET請求應經過響應中包含的超連接返回查找與請求的對象直接相關的資源所必需的信息,而且還應向其提供描述這些資源中的每個可用操做的信息。此原理稱爲HATEOAS,或稱爲應用程序狀態引擎的超文本。該系統其實是一個有限狀態機,對每一個請求的響應都包含從一種狀態轉移到另外一種狀態所需的信息。不須要其餘信息。
Web API保持靜態的可能性很小。隨着業務需求的變化,可能會添加新的資源集合,資源之間的關係可能會更改,而且資源中的數據結構可能會被修改。儘管更新Web API以處理新的或不一樣的要求是一個相對簡單的過程,可是您必須考慮此類更改將對使用Web API的客戶端應用程序產生的影響。問題在於,儘管設計和實現Web API的開發人員能夠徹底控制該API,可是開發人員對客戶端應用程序的控制程度不一樣,該客戶端應用程序能夠由遠程運行的第三方組織構建。
經過版本控制,Web API能夠指示其公開的功能和資源,而且客戶端應用程序能夠提交針對功能或資源的特定版本的請求。如下各節描述了幾種不一樣的方法,每種方法都有其自身的優勢和取捨。
這是最簡單的方法,對於某些內部API多是可接受的。重大更改能夠表示爲新資源或新連接。向現有資源添加內容可能不會帶來重大變化,由於不但願看到此內容的客戶端應用程序將忽略它。
例如,到URI的請求https://www.abc.com/customers/3
應該返回單個客戶的含有細節id
,name
以及address
由所述客戶端應用程序預期字段:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8 {"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
若是將該DateCreated
字段添加到客戶資源的架構,則響應將以下所示:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}
若是現有的客戶端應用程序可以忽略沒法識別的字段,則它們可能會繼續正常運行,而新的客戶端應用程序能夠設計爲處理此新字段。可是,若是對資源的架構進行了更根本的更改(例如,刪除或重命名字段),或者資源之間的關係發生了更改,則這些更改可能構成重大更改,從而阻止現有客戶端應用程序正常運行。在這些狀況下,您應該考慮使用如下方法之一。
每次您修改Web API或更改資源架構時,您都會爲每一個資源的URI添加一個版本號。先前存在的URI應該繼續像之前同樣操做,返回符合其原始架構的資源。
延伸的前面的例子,若是該address
字段被重組爲包含地址的每一個組成部分(如子場streetAddress
,city
,state
,和zipCode
),這個版本的資源的可經過URI暴露包含一個版本號,如 https://adventure-works.com/v2/customers/3
:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}
這種版本控制機制很是簡單,可是取決於服務器將請求路由到適當的端點。可是,隨着Web API經過屢次迭代成熟,而且服務器必須支持許多不一樣的版本,它可能變得笨拙。一樣,從純粹主義者的角度來看,在全部狀況下,客戶端應用程序都在提取相同的數據(客戶3),所以URI不該真正取決於版本。此方案還使HATEOAS的實現複雜化,由於全部連接都須要在其URI中包括版本號。
您可使用附加到HTTP請求的查詢字符串中的參數(例如)來指定資源的版本,而不是提供多個URI https://www.abc.com/customers/3?version=2
。若是較舊的客戶端應用程序省略了版本參數,則該版本參數應默認爲有意義的值,例如1。
這種方法具備語義優點,即始終從相同的URI中檢索相同的資源,可是它取決於處理請求的代碼以解析查詢字符串併發送回適當的HTTP響應。這種方法還遭受與URI版本控制機制相同的實現HATEOAS的複雜性。
您能夠實現一個指示資源版本的自定義標頭,而不是將版本號附加爲查詢字符串參數。這種方法要求客戶端應用程序將適當的標頭添加到任何請求,儘管若是省略了版本標頭,則處理客戶端請求的代碼可使用默認值(版本1)。如下示例使用名爲
版本1:
GEThttps://www.abc.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
版本2:
GEThttps://www.abc.com/customers/3 HTTP/1.1
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}
與前兩種方法同樣,實現HATEOAS要求在任何連接中包括適當的自定義標頭。
當客戶端應用程序將HTTP GET請求發送到Web服務器時,應按照本指南前面所述,指定它可使用Accept標頭處理的內容格式。一般,
GEThttps://adventure-works.com/customers/3 HTTP/1.1
Accept: application/vnd.adventure-works.v1+json
處理請求的代碼負責處理
的格式)。Web服務器經過使用Content-Type標頭確認響應正文中的數據格式:
HTTP/1.1 200 OK
Content-Type: application/vnd.adventure-works.v1+json; charset=utf-8
{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}
若是Accept標頭未指定任何已知的媒體類型,則Web服務器能夠生成HTTP 406(不可接受)響應消息或返回具備默認媒體類型的消息。
這種方法能夠說是最純粹的版本控制機制,而且很天然地適合HATEOAS,後者能夠在資源連接中包括相關數據的MIME類型。
注意: 選擇版本控制策略時,還應考慮對性能的影響,尤爲是Web服務器上的緩存。URI版本控制和查詢字
符串版本控制方案是緩存友好的,由於相同的URI /查詢字符串組合每次都引用相同的數據。標頭版本控制
和媒體類型版本控制機制一般須要其餘邏輯來檢查自定義標頭或「接 受」標頭中的值。在大規模環境中,
許多使用不一樣版本的Web API的客戶端可能會 在服務器端緩存中致使大量重複數據。若是客戶端應用程序
經過實現緩存的代理與Web服務器通訊,而且僅在當前不將請求數據的副本保存在其緩存中的狀況下將請求
轉發到Web服務器,則此問題可能變得很嚴重。複製代碼
感謝閱讀!
喜歡本文的朋友,歡迎關注「isevena」