RESTful設計
關於REST的URI設計
1、URI中不應該包含動詞
因為"資源"表示一種實體,所以應該是名詞,URI不應該有動詞, 動詞應該放在HTTP協議中。
舉例來說,某個URI是/posts/show/1,其中show是動詞,這個URI就設計錯了,正確的寫法應該是/posts/1,然後用GET方法表示show。
如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上匯款,從賬戶1向賬戶2匯款500元,錯誤的URI是:
POST /accounts/1/transfer/500/to/2
正確的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務:
POST /transaction HTTP/1.1 Host: 127.0.0.1 from=1&to=2&amount=500.00
2、 URI中不應該加入版本號碼
http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
http://www.example.com/app/2.0/foo
因為不同的版本,可以理解成同一種資源的不同表現形式,所以應該採用同一個URI。版本號可以在HTTP請求頭資訊的Accept欄位中進行區分(參見Versioning REST Services):
Accept: vnd.example-com.foo+json; version=1.0 Accept: vnd.example-com.foo+json; version=1.1 Accept: vnd.example-com.foo+json; version=2.0
URI設計總結
動詞 + 賓語
RESTful 的核心思想就是,客戶端發出的資料操作指令都是"動詞 + 賓語"的結構。比如,GET /articles
這個命令,GET是動詞,/articles
是賓語。
動詞通常就是五種 HTTP 方法,對應 CRUD 操作。
GET:讀取(Read)
POST:新建(Create)
PUT:更新(Update)
PATCH:更新(Update),通常是部分更新
DELETE:刪除(Delete)
根據 HTTP 規範,動詞一律大寫。
動詞的覆蓋
有些客戶端只能使用GET
和POST
這兩種方法。伺服器必須接受POST模擬其他三個方法(PUT、PATCH、DELETE
X-HTTP-Method-Override
屬性,告訴伺服器應該使用哪一個動詞,覆蓋POST
方法。
POST /api/Person/4 HTTP/1.1
X-HTTP-Method-Override: PUT
上面程式碼中,X-HTTP-Method-Override指定本次請求的方法是PUT,而不是POST。
賓語必須是名詞
賓語就是 API 的 URL,是 HTTP 動詞作用的物件。它應該是名詞,不能是動詞。比如,/articles
這個 URL 就是正確的,而下面的 URL 不是名詞,所以都是錯誤的
/getAllCars
/createNewCar
/deleteAllRedCars
複數 URL
既然 URL 是名詞,那麼應該使用複數,還是單數?
這沒有統一的規定,但是常見的操作是讀取一個集合,比如GET /articles
(讀取所有文章),這裡明顯應該是複數。
為了統一起見,建議都使用複數 URL,比如GET /articles/2要好於GET /article/2。
避免多級 URL
常見的情況是,資源需要多級分類,因此很容易寫出多級的 URL,比如獲取某個作者的某一類文章。
GET /authors/12/categories/2
這種 URL 不利於擴充套件,語義也不明確,往往要想一會,才能明白含義。
更好的做法是,除了第一級,其他級別都用查詢字串表達。
GET /authors/12?categories=2
下面是另一個例子,查詢已釋出的文章。你可能會設計成下面的 URL。
GET /articles/published
查詢字串的寫法明顯更好。
GET /articles?published=true
關於伺服器響應
不要返回純文字
API 返回的資料格式,不應該是純文字,而應該是一個 JSON 物件,因為這樣才能返回標準的結構化資料。所以,伺服器迴應的 HTTP 頭的Content-Type
屬性要設為application/json
。
客戶端請求時,也要明確告訴伺服器,可以接受 JSON 格式,即請求的 HTTP 頭的ACCEPT屬性也要設成application/json
。下面是一個例子。
GET /orders/2 HTTP/1.1
Accept: application/json
發生錯誤時,不要返回200狀態碼
有一種不恰當的做法是,即使發生錯誤,也返回200狀態碼,把錯誤資訊放在資料體裡面,就像下面這樣。
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "failure",
"data": {
"error": "Expected at least two items in list."
}
}
上面程式碼中,解析資料體以後,才能得知操作失敗。
這張做法實際上取消了狀態碼,這是完全不可取的。正確的做法是,狀態碼反映發生的錯誤,具體的錯誤資訊放在資料體裡面返回。下面是一個例子。
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Invalid payoad.",
"detail": {
"surname": "This field is required."
}
}
提供連結
API 的使用者未必知道,URL 是怎麼設計的。一個解決方法就是,在迴應中,給出相關連結,便於下一步操作。這樣的話,使用者只要記住一個 URL,就可以發現其他的 URL。這種方法叫做 HATEOAS。 舉例來說,GitHub 的 API 都在 api.github.com 這個域名。訪問它,就可以得到其他 URL。
{
...
"feeds_url": "https://api.github.com/feeds",
"followers_url": "https://api.github.com/user/followers",
"following_url": "https://api.github.com/user/following{/target}",
"gists_url": "https://api.github.com/gists{/gist_id}",
"hub_url": "https://api.github.com/hub",
...
}
上面的迴應中,挑一個 URL 訪問,又可以得到別的 URL。對於使用者來說,不需要記住 URL 設計,只要從 api.github.com 一步步查詢就可以了。
HATEOAS 的格式沒有統一規定,上面例子中,GitHub 將它們與其他屬性放在一起。更好的做法應該是,將相關連結與其他屬性分開。
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "In progress",
"links": {[
{ "rel":"cancel", "method": "delete", "href":"/api/status/12345" } ,
{ "rel":"edit", "method": "put", "href":"/api/status/12345" }
]}
}