RESTful API 設計最佳實踐

RESTful API 設計最佳實踐

來源:http://www.oschina.net/translate/best-practices-for-a-pragmatic-restful-api

背景

目前互聯網上充斥着大量的關於RESTful API(爲方便,下文中“RESTful API ”簡寫爲“API”)如何設計的文章,然而卻沒有一個”萬能“的設計標準:如何鑑權?API 格式如何?你的API是否應該加入版本信息?當你開始寫一個app的時候,特別是後端模型部分已經寫完的時候,你不得不殫精竭慮的設計和實現自己app的public API部分。因爲一旦發佈,對外發布的API將會很難改變。

在給SupportedFu設計API的時候,我試圖以實用的角度來解決上面提到的問題。我希望可以設計出容易使用,容易部署,並且足夠靈活的API,本文因此而生。

API設計的基本要求

網上的很多關於API設計的觀點都十分”學院派“,它們也許更有理論基礎,但是有時卻和現實世界脫軌(因此我是自由派)。所以我這篇文章的目標是從實踐的角度出發,給出當前網絡應用的API設計最佳實踐(當然,是我認爲的最佳了~),如果覺得不合適,我不會遵從標準。當然作爲設計的基礎,幾個必須的原則還是要遵守的:

  1. 當標準合理的時候遵守標準。
  2. API應該對程序員友好,並且在瀏覽器地址欄容易輸入。
  3. API應該簡單,直觀,容易使用的同時優雅。
  4. API應該具有足夠的靈活性來支持上層ui。
  5. API設計權衡上述幾個原則。

需要強調的是:API的就是程序員的UI,和其他UI一樣,你必須仔細考慮它的用戶體驗!

使用Restful URLs和action.

雖然前面我說沒有一個萬能的API設計標準。但確實有一個被普遍承認和遵守:Restful設計原則。它被Roy Felding提出(在他的”基於網絡的軟件架構“論文中第五章)。而REST的核心原則是將你的API拆分爲邏輯上的資源。這些資源通過http被操作(GET ,POST,PUT,DELETE)。

那麼我應該如何拆分出這些資源呢?

顯然從API用戶的角度來看,”資源“應該是個名詞。即使你的內部數據模型和資源已經有了很好的對應,API設計的時候你仍然不需要把它們一對一的都暴露出來。這裏的關鍵是隱藏內部資源,暴露必需的外部資源。

在SupportFu裏,資源是 ticket、user、group。

一旦定義好了要暴露的資源,你可以定義資源上允許的操作,以及這些操作和你的API的對應關係:

GET /tickets # 獲取ticket列表
GET /tickets/12 # 查看某個具體的ticket
POST /tickets # 新建一個ticket
PUT /tickets/12 # 更新ticket 12
DELETE /tickets/12 #刪除ticekt 12

可以看出使用REST的好處在於可以充分利用http的強大實現對資源的CURD功能。而這裏你只需要一個endpoint:/tickets,再沒有其他什麼命名規則和url規則了,cool!

接入點的名稱應該選擇單數還是複數呢?

keep-it-simple原則可以在此應用。雖然你內在的語法知識會告訴你用複數形式描述單一資源實例是錯誤的,但實用主義的答案是保持URL格式一致並且始終使用複數形式。不用處理各種奇形怪狀的複數形式(比如person/people,goose/geese)可以讓API消費者的生活更加美好,也讓API提供者更容易實現API(因爲大多數現代框架天然地將/tickets和/tickets/12放在同一個控制器下處理)。

如何處理關聯?

關於如何處理資源之間的管理REST原則也有相關的描述:

  • GET /tickets/12/messagesRetrieves list of messages for ticket #12
  • GET /tickets/12/messages/5Retrieves message #5 for ticket #12
  • POST /tickets/12/messagesCreates a new message in ticket #12
  • PUT /tickets/12/messages/5Updates message #5 for ticket #12
  • PATCH /tickets/12/messages/5Partially updates message #5 for ticket #12
  • DELETE /tickets/12/messages/5Deletes message #5 for ticket #12

其中,如果這種關聯和資源獨立,那麼我們可以在資源的輸出表示中保存相應資源的endpoint。然後API的使用者就可以通過點擊鏈接找到相關的資源。如果關聯和資源聯繫緊密。資源的輸出表示就應該直接保存相應資源信息。(例如這裏如果message資源是獨立存在的,那麼上面 GET /tickets/12/messages就會返回相應message的鏈接;相反的如果message不獨立存在,他和ticket依附存在,則上面的API調用返回直接返回message信息)

不符合CURD的操作

  1. 重新構造這個Action,使得它像一個資源的field(我理解爲部分域或者部分字段)。這種方法在Action不包含參數的情況下可以奏效。例如一個有效的action可以映射成布爾類型field,並且可以通過PATCH更新資源。
  2. 利用RESTful原則像處理子資源一樣處理它。例如,Github的API讓你通過PUT /gists/:id/star 來 star a gist ,而通過DELETE /gists/:id/star來進行 unstar
  3. 有時候你實在是沒有辦法將Action映射到任何有意義的RESTful結構。例如,多資源搜索沒辦法真正地映射到任何一個資源接入點。這種情況,/search 將非常有意義,雖然它不是一個名詞。這樣做沒有問題 - 你只需要從API消費者的角度做正確的事,並確保所做的一切都用文檔清晰記錄下來了以避免(API消費者的)困惑。

總是使用SSL,沒有例外

今天,您的web api可以從任何地方訪問互聯網(如圖書館、咖啡店、機場等)。不是所有這些都是安全的,許多不加密通信,便於竊聽或僞造,如果身份驗證憑證被劫持。

另一個優點是,保證總是使用SSL加密通信簡化了認證效果——你可以擺脫簡單的訪問令牌,而不是讓每個API請求籤署。

要注意的一點是非SSL訪問API URLs。不要重定向這些到對應的SSL。相反,拋出一個系統錯誤!最後一件你想要的是配置不佳的客戶發送請求到一個未加密的端點,只是默默地重定向到實際加密的端點

文檔

API的好壞關鍵看其文檔的好壞. 好的API的說明文檔應該很容易就被找到,並能公開訪問。在嘗試任何整合工作前大部分開發者會先查看其文檔。當文檔被藏於一個PDF之中或要求必須登記信息時,將很難被找到也很難搜索到。

好的文檔須提供從請求到響應整個循環的示例。最好的是,請求應該是可粘貼的例子,要麼是可以貼到瀏覽器的鏈接,要麼是可以貼到終端裏的curl示例 。 GitHubStripe 在這方面做的非常出色。

一旦你發佈一個公開的API,你必須承諾”在沒有通告的前提下,不會更改APIDe功能” .對於外部可見API的更新,文檔必須包含任何將廢棄的API的時間表和詳情。應該通過博客(更新日誌)或者郵件列表送達更新說明(最好兩者都通知)。

版本控制

必須對API進行版本控制。版本控制可以快速迭代並避免無效的請求訪問已更新的接入點。它也有助於幫助平滑過渡任何大範圍的API版本變遷,這樣就可以繼續支持舊版本API。

關於API的版本是否應該包含在URL或者請求頭中 莫衷一是。從學術派的角度來講,它應該出現在請求頭中。然而版本信息出現在URL中必須保證不同版本資源的瀏覽器可瀏覽性(browser explorability),還記得文章開始提到的API要求嗎?

我非常贊成 approach that Stripe has taken to API versioning - URL包含一個主版本號(比如http://shonzilla/api/v1/customers/1234),但是API還包含基於日期的子版本(比如http://shonzilla/api/v1.2/customers/1234),可以通過配置HTTP請求頭來進行選擇。這種情況下,主版本確保API結構總體穩定性,而子版本會考慮細微的變化(field deprecation、接入點變化等)。

API不可能完全穩定。變更不可避免,重要的是變更是如何被控制的。維護良好的文檔、公佈未來數月的deprecation計劃,這些對於很多API來說都是一些可行的舉措。它歸根結底是看對於業界和API的潛在消費者是否合理。

結果過濾,排序和搜索

最好是儘量保持基本資源URL的簡潔性。 複雜結果過濾器、排序需求和高級搜索 (當限定在單一類型的資源時) ,都能夠作爲在基本URL之上的查詢參數來輕鬆實現。下面讓我們更詳細的看一下:

過濾
對每一個字段使用一個唯一查詢參數,就可以實現過濾。 例如,當通過“/tickets”終端來請求一個票據列表時,你可能想要限定只要那些在售的票。這可以通過一個像 GET /tickets?state=open 這樣的請求來實現。這裏“state”是一個實現了過濾功能的查詢參數。

排序
跟過濾類似, 一個泛型參數排序可以被用來描述排序的規則. 爲適應複雜排序需求,讓排序參數採取逗號分隔的字段列表的形式,每一個字段前都可能有一個負號來表示按降序排序。
我們看幾個例子:
GET /tickets?sort=-priority - 獲取票據列表,按優先級字段降序排序
GET /tickets?sort=-priority,created_at - 獲取票據列表,按“priority”字段降序排序。在一個特定的優先級內,較早的票排在前面。

搜索
有時基本的過濾不能滿足需求,這時你就需要全文檢索的力量。或許你已經在使用 ElasticSearch 或者其它基於 Lucene 的搜索技術。當全文檢索被用作獲取某種特定資源的資源實例的機制時, 它可以被暴露在API中,作爲資源終端的查詢參數,我們叫它“q”。搜索類查詢應當被直接交給搜索引擎,並且API的產出物應當具有同樣的格式,以一個普通列表作爲結果。

組合
把這些組合在一起,我們可以創建以下一些查詢:

GET /tickets?sort=-updated_at - 獲取最近更新的票
GET /tickets?state=closed&sort=-updated_at - 獲取最近更新並且狀態爲關閉的票。
GET /tickets?q=return&state=open&sort=-priority,created_at - 獲取優先級最高、最先創建的、狀態爲開放的票,並且票上有 ‘return’ 字樣。

對於經常使用的搜索查詢,我們可以爲他們設立別名,這樣會讓API更加優雅。例如:
get /tickets?q=recently_closed -> get /tickets/recently_closed.

限制哪些字段由API返回

API的使用者並不總是需要一個資源的完整表示。選擇返回字段的功能由來已久,它使得API使用者能夠最小化網絡阻塞,並加速他們對API的調用。

使用一個字段查詢參數,它包含一個用逗號隔開的字段列表。例如,下列請求獲得的信息將剛剛足夠展示一個在售票的有序列表:

GET /tickets?fields=id,subject,customer_name,updated_at&state=open&sort=-updated_at

更新和創建應該返回一個資源描述

一個 PUT, POST 或者 PATCH 調用可能會對指定資源的某些字段造成更改,而這些字段本不在提供的參數之列 (例如: created_at 或 updated_at 這兩個時間戳)。 爲了防止API使用者爲了獲取更新後的資源而再次調用該API,應當使API把更新(或創建)後的資源作爲response的一部分來返回。

以一個產生創建活動的 POST 操作爲例, 使用一個 HTTP 201 狀態代碼 然後包含一個 Location header 來指向新生資源的URL。

你是否應該HATEOAS?

(譯註:Hypermedia as the Engine of Application State (HATEOAS)超媒體作爲應用程序狀態引擎)

對於API消費方是否應該創建鏈接,或者是否應該將鏈接提供給API,有許多混雜的觀點。RESTful的設計原則指定了HATEOAS ,大致說明了與某個端點的交互應該定義在元數據(metadata)之中,這個元數據與輸出結果一同到達,並不基於其他地方的信息。

雖然web逐漸依照HATEOAS類型的原則運作(我們打開一個網站首頁並隨着我們看到的頁面中的鏈接瀏覽),我不認爲我們已經準備好API的HATEOAS了。當瀏覽一個網站的時候,決定點擊哪個鏈接是運行時做出的。然而,對於API,決定哪個請求被髮送是在寫API集成代碼時做出的,並不是運行時。這個決定可以移交到運行時嗎?當然可以,不過順着這條路沒有太多好處,因爲代碼仍然不能不中斷的處理重大的API變化。也就是說,我認爲HATEOAS做出了承諾,但是還沒有準備好迎接它的黃金時間。爲了完全實現它的潛能,需要付出更多的努力去定義圍繞着這些原則的標準和工具。

目前而言,最好假定用戶已經訪問過輸出結果中的文檔&包含資源標識符,而這些API消費方會在製作鏈接的時候用到。關注標識符有幾個優勢——網絡中的數據流減少了,API消費方存儲的數據也減少了(因爲它們存儲的是小的標識符而不是包含標識符的URLs)。

同樣的,在URL中提供本文倡導的版本號,對於在一個很長時間內API消費方存儲資源標識符(而不是URLs),它更有意義。總之,標識符相對版本是穩定的,但是表示這一點的URL卻不是的!

只返回JSON

是時候在API中丟棄XML了。XML冗長,難以解析,很難讀,他的數據模型和大部分編程語言的數據模型 不兼容,而他的可擴展性優勢在你的主要需求是必須序列化一個內部數據進行輸出展示時變得不相干。

我不打算對上述進行解釋了,貌似諸如 (YouTube, Twitter 和 Box)之類的已經開始了去XML化.

但是,如果你的客戶羣包括大量的企業客戶,你會發現自己不得不支持XML的方式。如果你必須這樣,一個新問題出現了:

媒體類型是應該基於Accept頭還是基於URL呢 ? 爲確保瀏覽器的瀏覽性,應該基於URL。這裏最明智的選擇是在端點URL後面附加 .json 或 .xml 的擴展.

字段名稱書寫格式的 snake_case vs camelCase

如果你在使用JSON (JavaScript Object Notation) 作爲你的主要表示格式,正確的方法就是遵守JavaScript命名約定——對字段名稱使用camelCase!如果你要走用各種語言建設客戶端庫的路線,最好使用它們慣用的命名約定—— C# & Java 使用camelCase, python & ruby 使用snake_case。

深思:我一直認爲snake_case比JavaScript的camelCase約定更容易閱讀。我沒有任何證據來支持我的直覺,直到現在,基於從2010年的camelCase 和 snake_case的眼動追蹤研究 (PDF),snake_case比駝峯更容易閱讀20%!這種閱讀上的影響會影響API的可勘探性和文檔中的示例。

許多流行的JSON API使用snake_case。我懷疑這是由於序列化庫遵從它們所使用的底層語言的命名約定。也許我們需要有JSON序列庫來處理命名約定轉換。

缺省情況下確保漂亮的打印和支持gzip

一個提供空白符壓縮輸出的API,從瀏覽器中查看結果並不美觀。雖然一些有序的查詢參數(如 ?pretty=true )可以提供來使漂亮打印生效,一個默認情況下能進行漂亮打印的API更爲平易近人。額外數據傳輸的成本是微不足道的,尤其是當你比較不執行gzip壓縮的成本。

考慮一些用例:假設分析一個API消費者正在調試並且有自己的代碼來打印出從API收到的數據——默認情況下這應是可讀的。或者,如果消費者抓住他們的代碼生成的URL,並直接從瀏覽器訪問它——默認情況下這應是可讀的。這些都是小事情。做好小事情會使一個API能被更愉快地使用!

那麼該如何處理額外傳輸的數據呢?

讓我們看一個實際例子。我從GitHub API上拉取了一些數據,默認這些數據使用了漂亮打印(pretty print)。我也將做一些GZIP壓縮後的對比。


    $ curl https://api.github.com/users/veesahni > with-whitespace.txt
    $ ruby -r json -e 'puts JSON JSON.parse(STDIN.read)' < with-whitespace.txt > without-whitespace.txt
    $ gzip -c with-whitespace.txt > with-whitespace.txt.gz
    $ gzip -c without-whitespace.txt ? without-whitespace.txt.gz

輸出文件的大小如下:
without-whitespace.txt - 1252 bytes
with-whitespace.txt - 1369 bytes
without-whitespace.txt.gz - 496 bytes
with-whitespace.txt.gz - 509 bytes

在這個例子中,當未啓用GZIP壓縮時空格增加了8.5%的額外輸出大小,而當啓用GZIP壓縮時這個比例是2.6%。另一方面,GZIP壓縮節省了60%的帶寬。由於漂亮打印的代價相對比較小,最好默認使用漂亮打印,並確保GZIP壓縮被支持。

關於這點想了解更多的話,Twitter發現當對他們的 Streaming API 開啓GZIP支持後可以在某些情況獲得 80%的帶寬節省 。Stack Exchange甚至強制要求必須對API請求結果使用GZIP壓縮(never return a response that’s not compressed)。

不要默認使用大括號封裝,但要在需要的時候支持

許多API會像下面這樣包裹他們的響應信息:


    {
      "data" : {
        "id" : 123,
        "name" : "John"
      }
    }

有不少這樣做的理由 - 更容易附加元數據或者分頁信息,一些REST客戶端不允許輕易的訪問HTTP頭信息,並且JSONP請求不能訪問HTTP頭信息。無論怎樣,隨着迅速被採用的標準,比如CORS和Link header from RFC 5988, 大括號封裝開始變得不必要。

我們應當默認不使用大括號封裝,而僅在特殊情況下使用它,從而使我們的API面向未來。

特殊情況下該如何使用大括號封裝?

有兩種情況確實需要大括號封裝 - 當API需要通過JSONP來支持跨域的請求時,或者當客戶端沒有能力處理HTTP頭信息時。

JSONP 請求附帶有一個額外的查詢參數(通常稱爲callback或jsonp) 表示了回調函數的名稱。如果提供了這個參數,API應當切換至完整封裝模式,這時它總是用200HTTP狀態碼作爲響應,然後把真實的狀態碼放入JSON有效載荷中。任何被一併添加進響應中的額外的HTTP頭信息都應當被映射到JSON字段中, 像這樣:


    callback_function({
      status_code: 200,
      next_page: "https://..",
      response: {
        ... actual JSON response body ... 
      }
})

類似的,爲了支持HTTP受限的客戶端,可以允許一個特殊的查詢參數“?envelope=true”來觸發完整封裝(沒有JSONP回調函數)。

使用JSON 編碼的 POST, PUT & PATCH 請求體

如果你正在跟隨本文中講述的開發過程,那麼你肯定已經接受JSON作爲API的輸出。下面讓我們考慮使用JSON作爲API的輸入。

許多API在他們的API請求體中使用URL編碼。URL編碼正如它們聽起來那樣 - 將使用和編碼URL查詢參數時一樣的約定,對請求體中的鍵值對進行編碼。這很簡單,被廣泛支持而且實用。

然而,有幾個問題使得URL編碼不太好用。首先,它沒有數據類型的概念。這迫使API從字符串中轉換整數和布爾值。而且,它並沒有真正的層次結構的概念。儘管有一些約定,可以用鍵值對構造出一些結構(比如給一個鍵增加“[]”來表示一個數組),但還是不能跟JSON原生的層次結構相比。

如果API很簡單,URL編碼可以滿足需要。然而,複雜API應當嚴格對待他們的JSON格式的輸入。不論哪種方式,選定一個並且整套API要保持一致。

一個能接受JSON編碼的POST, PUT 和 PATCH請求的API,應當也需要把Content-Type頭信息設置爲application/json,或者拋出一個415不支持的媒體類型(Unsupported Media Type)的HTTP狀態碼。

分頁

信封喜歡將分頁信息包含在信封自身的API。我不能指責這點——直到最近,我們才找到更好的方法。正確的方法是使用 RFC 5988 中介紹的鏈接標頭。

使用鏈接標頭的API可以返回一系列線程的鏈接,API使用者無需自行生成鏈接。這在分頁時指針導向 非常重要。下面是抓取自 Github的正確使用鏈接標頭的文件:


    Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next", <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

不過這個並非完成版本,因爲很多 API 喜歡返回額外信息,例如可用結果的總數。需要發送數量的 API 可用類似 X-Total-Count 的普通 HTTP 標頭。

自動裝載相關的資源描述

在很多種情況下,API的使用者需要加載和被請求資源相關的數據(或被請求資源引用的數據)。與要求使用者反覆訪問API來獲取這些信息相比,允許在請求原始資源的同時一併返回和裝載相關資源,將會帶來明顯的效率提升。

然而, 由於這樣確實 有悖於一些RESTful原則, 所以我們可以只使用一個內置的(或擴展)的查詢參數來實現這一功能,來最小化與原則的背離。

這種情況下,“embed”將是一個逗號隔開的需要被內置的字段列表。點號可以用來表示子字段。例如:

GET /ticket/12?embed=customer.name,assigned_user

這將返回一個附帶有詳細內置信息的票據,如下:


    {
      "id" : 12,
      "subject" : "I have a question!",
      "summary" : "Hi, ....",
      "customer" : {
        "name" : "Bob"
      },
      assigned_user: {
       "id" : 42,
       "name" : "Jim",
      }
    }

當然,實現類似於這種功能的能力完全依賴於內在的複雜度。這種內置的做法很容易產生 N+1 select 問題。

爲什麼對X-Rate-Limit-Reset不使用時間戳而使用秒數?

一個時間戳包含了各種各樣的信息,比如日期和時區,但它們卻不是必需的。一個API使用者其實只是想知道什麼時候能再次發起請求,對他們來說一個秒數用最小的額外處理回答了這個問題。同時規避了時鐘偏差的問題。

有些API給X-Rate-Limit-Reset使用UNIX時間戳(紀元以來的秒數)。不要這樣做!

爲什麼對X-Rate-Limit-Reset使用UNIX時間戳是不好的做法?

HTTP 規範已經指定使用RFC 1123 的日期格式 (目前被使用在日期, If-Modified-Since & Last-Modified HTTP頭信息中)。如果我們打算指定一種使用某種形式時間戳的、新的HTTP頭信息,我們應當遵循RFC 1123規定,而不是使用UNIX時間戳。

認證

一個 RESTful API 應當是無狀態的。這意味着認證請求應當不依賴於cookie或session。相反,每一個請求都應當攜帶某種類型的認證憑證。

由於總是使用SSL,認證憑證能夠被簡化爲一個隨機產生的訪問令牌,裏面傳入一個使用HTTP Basic Auth的用戶名字段。這樣做的極大的好處是,它是完全的瀏覽器可探測的 - 如果瀏覽器從服務器收到一個401未授權狀態碼,它僅需要一個彈出框來索要憑證即可。

然而,這種基於基本認證的令牌的認證方法,僅在滿足下列情形時纔可用,即用戶可以把令牌從一個管理接口複製到API使用者環境。當這種情形不能成立時,應當使用OAuth 2來產生安全令牌並傳遞給第三方。OAuth 2使用了承載令牌(Bearer tokens) 並且依賴於SSL的底層傳輸加密。

一個需要支持JSONP的API將需要第三種認證方法,因爲JSONP請求不能發送HTTP基本認證憑據(HTTP Basic Auth)或承載令牌(Bearer tokens) 。這種情況下,可以使用一個特殊的查詢參數access_token。注意,使用查詢參數token存在着一個固有的安全問題,即大多數的web服務器都會把查詢參數記錄到服務日誌中。

這是值得的,所有上面三種方法都只是跨API邊界兩端的傳遞令牌的方式。實際的底層令牌本身可能都是相同的。

緩存

HTTP 提供了一套內置的緩存框架! 所有你必須做的是,包含一些額外的出站響應頭信息,並且在收到一些入站請求頭信息時做一點兒校驗工作。

有兩種方式: ETag和Last-Modified

ETag: 當產生一個請求時, 包含一個HTTP 頭,ETag會在裏面置入一個和表達內容對應的哈希值或校驗值。這個值應當跟隨表達內容的變化而變化。現在,如果一個入站HTTP請求包含了一個If-None-Match頭和一個匹配的ETag值,API應當返回一個304未修改狀態碼,而不是返回請求的資源。

Last-Modified: 基本上像ETag那樣工作,不同的是它使用時間戳。在響應頭中,Last-Modified包含了一個RFC 1123格式的時間戳,它使用If-Modified-Since來進行驗證。注意,HTTP規範已經有了 3 種不同的可接受的日期格式 ,服務器應當準備好接收其中的任何一種。

錯誤

就像一個HTML錯誤頁面給訪問者展示了有用的錯誤信息一樣,一個API應當以一種已知的可使用的格式來提供有用的錯誤信息。 錯誤的表示形式應當和其它任何資源沒有區別,只是有一套自己的字段。

API應當總是返回有意義的HTTP狀態代碼。API錯誤通常被分成兩種類型: 代表客戶端問題的400系列狀態碼和代表服務器問題的500系列狀態碼。最簡情況下,API應當把便於使用的JSON格式作爲400系列錯誤的標準化表示。如果可能(意思是,如果負載均衡和反向代理能創建自定義的錯誤實體), 這也適用於500系列錯誤代碼。

一個JSON格式的錯誤信息體應當爲開發者提供幾樣東西 - 一個有用的錯誤信息,一個唯一的錯誤代碼 (能夠用來在文檔中查詢詳細的錯誤信息) 和可能的詳細描述。這樣一個JSON格式的輸出可能會像下面這樣:


    {
      "code" : 1234,
      "message" : "Something bad happened :(",
      "description" : "More details about the error here"
    }

對PUT, PATCH和POST請求進行錯誤驗證將需要一個字段分解。下面可能是最好的模式:使用一個固定的頂層錯誤代碼來驗證錯誤,並在額外的字段中提供詳細錯誤信息,就像這樣:


    {
      "code" : 1024,
      "message" : "Validation Failed",
      "errors" : [
        {
          "code" : 5432,
          "field" : "first_name",
          "message" : "First name cannot have fancy characters"
        },
        {
           "code" : 5622,
           "field" : "password",
           "message" : "Password cannot be blank"
        }
      ]
    }

HTTP 狀態代碼

HTTP定義了一套可以從API返回的有意義的狀態代碼。 這些代碼能夠用來幫助API使用者對不同的響應做出相應處理。我已經把你必然會用到的那些列成了一個簡短的清單:

200 OK (成功) - 對一次成功的GET, PUT, PATCH 或 DELETE的響應。也能夠用於一次未產生創建活動的POST
201 Created (已創建) - 對一次導致創建活動的POST的響應。 同時結合使用一個位置頭信息指向新資源的位置- Response to a POST that results in a creation. Should be combined with a Location header pointing to the location of the new resource
204 No Content (沒有內容) - 對一次沒有返回主體信息(像一次DELETE請求)的請求的響應
304 Not Modified (未修改) - 當使用HTTP緩存頭信息時使用304
400 Bad Request (錯誤的請求) - 請求是畸形的, 比如無法解析請求體
401 Unauthorized (未授權) - 當沒有提供或提供了無效認證細節時。如果從瀏覽器使用API,也可以用來觸發彈出一次認證請求
403 Forbidden (禁止訪問) - 當認證成功但是認證用戶無權訪問該資源時
404 Not Found (未找到) - 當一個不存在的資源被請求時
405 Method Not Allowed (方法被禁止) - 當一個對認證用戶禁止的HTTP方法被請求時
410 Gone (已刪除) - 表示資源在終端不再可用。當訪問老版本API時,作爲一個通用響應很有用
415 Unsupported Media Type (不支持的媒體類型) - 如果請求中包含了不正確的內容類型
422 Unprocessable Entity (無法處理的實體) - 出現驗證錯誤時使用
429 Too Many Requests (請求過多) - 當請求由於訪問速率限制而被拒絕時

總結

一個API是一個給開發者使用的用戶接口。要努力確保它不僅功能上可用,更要用起來愉快。

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