1. 程式人生 > >RESTful設計

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 規範,動詞一律大寫。

動詞的覆蓋

有些客戶端只能使用GETPOST這兩種方法。伺服器必須接受POST模擬其他三個方法(PUT、PATCH、DELETE

)。這時,客戶端發出的 HTTP 請求,要加上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" }
  ]}
}