1. 程式人生 > >RESTful API設計技巧經驗總結

RESTful API設計技巧經驗總結

譯者注:本文是作者在自己的工作經驗中總結出來的RESTful API設計技巧,雖然部分技巧仍有爭議,但總體來說還是有一定的參考價值的。以下是譯文。

cover

簡單說一下程式碼重用

我們都是手藝學徒,沒有人會成為大師。

在我寫這篇文章的時候,我不禁笑了起來,因為從這件事情的背後看到了一個偉大的類比,那就是從其他人那裡引用了海明威的話。也就是說,我不需要為了得到類似的功能和結果而花費精力自己去建立一個與眾不同的東西,上面提到的海明威的話正是程式碼重用在文學上的例子。

但是,我在這裡不會寫程式碼包的好處,而是更多地提一些我的感受,這些感受會在當前以及未來的專案中積極地得到實現。我還總結了一套API規則和原語,包括了功能和實現細節。

使用API版本控制

如果你要開發一個提供客戶端服務的API,你需要為最後可能的修改而做好準備。最好的辦法就是通過為RESTful API提供“版本名稱空間”來實現。

我們只需將版本號作為字首新增到所有的URL裡即可。

GET www.myservice.com/api/v1/posts

然而,在我研究了其他的API實現之後發現,我喜歡上了這種較短的URL樣式,它把api作為是子域名的一部分,並從路由中刪除了/api,這樣更短、更簡潔。

GET api.myservice.com/v1/posts

跨域資源共享(CORS)

需要重點關注的是,如果你打算在www.myservice.com上託管你的前端站點,而將API放在另外一個不同的子域上,例如api.myservice.com

,那麼你需要在後端實現CORS,這樣才能使得AJAX呼叫不會丟擲No Access-Control-Allow-Origin header is present這樣的錯誤。

使用複數形式

當你從/posts請求多個帖子的時候,這樣的URL看起來更明瞭:

// 複數形式看起來更一致,更有意義
GET /v1/posts/:id/attachments/:id/comments

// 不能有歧義
// 這只是一個評論? 還是一個表格?
GET /v1/post/:id/attachment/:id/comment

更多有關混合型別的資訊,請看下文:“使用根級別的‘me’端點(URL)”。

避免查詢字串

查詢字串的作用是對關係資料庫返回的記錄集做進一步地過濾。

/projects/:id/collections 優於 /collections?projectId=:id
/projects/:id/collections/:id/items 優於 /items?projectId=:id&collectionId=:id

更多資訊請看下文:“避免對巢狀路由的操作”。

使用HTTP方法

我們可使用下面這些HTTP方法:
- GET 用於獲取資料。
- POST 用於新增資料。
- PUT 用於更新資料(整個物件)。
- PATCH 用於更新資料(附帶物件的部分資訊)。
- DELETE 用於刪除資料。

補充一點,對於修改物件的部分內容的請求來說,我認為PATCH是減少請求包大小的一個好的方法,並且它也能很好的跟自動提交/自動儲存欄位配合起來用。

一個很好的例子是Tumblr的“儀表盤設定”螢幕,其中,“服務的使用者體驗”的一些非關鍵性選項可以單獨地編輯和儲存,而不需要點最下面的提交按鈕。

對於POSTPUTPATCH的成功響應訊息,應該返回更新後的物件,而不是隻返回一個null

有關響應的其他內容,請閱讀下文:“JSON格式的響應和請求”。

使用封包

“我不喜歡資料封包。它只是引入了另一個鍵來瀏覽資料樹。元資訊應該包含在包頭中。”

最初,我堅持認為封包資料是不必要的,HTTP協議已經提供了足夠的“封包”來傳遞響應訊息。

然而,根據Reddit上的回覆所述,如果不封包為JSON陣列,則可能會出現各種漏洞和潛在的黑客攻擊。

現在建議使用封包,你應該把資料封包後再應答!

// 已封包,最頂級的物件既安全又簡潔
{
  data: [
    { ... },
    { ... },
    // ...
  ]
}

// 未封包,存在安全風險
[
  { ... },
  { ... },
  // ...
]

同樣要重點關注的是,不像其他語言那樣,JavaScript之類的語言將會將空物件認為是true! 因此,在下面這種情況下,不要返回空的物件來作為響應的一部分:

// 從payload中提取封包和錯誤
const { data, error } = payload

// 錯誤處理
if (error) { throw ... }

// 否則
const normalizedData = normalize(data, schema)

JSON格式的響應和請求

所有東西都應該被序列化成JSON。如果你期待從伺服器上獲取JSON格式的資料,那麼請客氣一點,請傳送JSON格式的內容給伺服器。請兩邊保持一致!

某些情況下,如果動作執行成功(例如DELETE),那我並沒有什麼需要返回的。但是,在某些語言(如Python)中返回一個空物件可能被認為是false,並且在開發人員除錯程式的時候,這種情況並不容易發現。因此,我喜歡返回“OK”,儘管這是一個字串,但是在返回的時候會被包裝成一個簡單的響應物件。

DELETE /v1/posts/:id
// response - HTTP 200
{
  "message": "OK"
}

使用HTTP狀態碼和錯誤響應

因為我們使用了HTTP方法,所以我們應當使用HTTP狀態碼。
我喜歡使用這些狀態碼:

對於資料錯誤

400:請求資訊不完整或無法解析。
422:請求資訊完整,但無效。
404:資源不存在。
409:資源衝突。

對於鑑權錯誤

401:訪問令牌沒有提供,或者無效。
403:訪問令牌有效,但沒有許可權。

對於標準狀態

200: 所有的都正確。
500: 伺服器內部丟擲錯誤。

假設要建立一個新帳戶,我們提供了emailpassword兩個值。我們希望讓客戶端應用程式能夠阻止任何無效的電子郵件或密碼太短的請求,但外部人員可以像我們的客戶端應用程式一樣在需要的時候直接訪問API。

如果email欄位丟失,則返回400
如果password欄位太短,則返回422
如果email欄位不是有效的電子郵件,則返回422
如果email已經被使用,返回一個409

從上面這些情況來看,有兩個錯誤會返回422,不過他們的原因是不同的。這就是為什麼我們需要一個錯誤碼,甚至是一個錯誤描述。要區分程式碼和描述,我打算將error(程式碼)作為機器可識別的常量,將description作為可更改的用於人類識別的字串。

欄位校驗錯誤

對於欄位的錯誤,可以這樣返回:

POST /v1/register
// 請求
{
  "email": "[email protected]@user.comx"
  "password": "abc"
}

// 響應 - 422
{
  "error": {
    "status": 422,
    "error": "FIELDS_VALIDATION_ERROR",
    "description": "One or more fields raised validation errors."
    "fields": {
      "email": "Invalid email address.",
      "password": "Password too short."
    }
  }
}

操作校驗錯誤

對於返回操作校驗錯誤:

POST /v1/register
// 請求
{
  "email": "[email protected]",
  "password": "password"
}

// 響應 - 409
{
  "error": {
    "status": 409,
    "error": "EMAIL_ALREADY_EXISTS",
    "description": "An account already exists with this email."
  }
}

這樣,你的程式的錯誤提取邏輯要當心非200的錯誤了,你可以直接從響應中檢查error欄位,然後將其與客戶端中相應的邏輯進行比較。

status這個欄位似乎也很有用,如果你不想檢查響應裡的元資料,那你可以在需要的時候有條件地新增這個欄位。

description可作為備用的使用者可讀的錯誤訊息。

密碼規則

在做了很多密碼規則的研究之後,我比較贊同 密碼規則是廢話NIST禁止做的事情 這兩篇帖子的觀點。

整理了一些處理密碼的規則:

  1. 執行unicode密碼的最小長度策略(最小8-10位)。
  2. 檢查常見的密碼(例如“password12345”)
  3. 檢查密碼熵(不允許使用“aaaaaaaaaaaaa”)。
  4. 不要使用密碼編寫規則(至少包含其中一個字元“!@#$%&”)。
  5. 不要使用密碼提示(“assword”這樣的)。
  6. 不要使用基於知識的認證。
  7. 不要超期不修改密碼。
  8. 不要使用簡訊進行雙認證。
  9. 使用32位以上的密碼鹽(salt)。

在某種程度上,所有這些規則能使密碼驗證更容易!

使用訪問和重新整理令牌

現代的無狀態、RESTful API一般會使用令牌來實現身份認證。這消除了在無狀態伺服器上處理會話和Cookie的需要,並且可以很容易地使用Authorization頭(或access_token查詢引數)來除錯網路請求。

訪問令牌用於認證所有未來的API請求,生命期短,不會被取消。

重新整理令牌在初始登入的響應中返回,然後跟過期時間戳和與使用者的關係一起進行雜湊計算後儲存到資料庫中。這個長生命期的像密碼一樣的金鑰,可以被用來請求新的短生命期的JWT訪問令牌。重新整理令牌也可以用於續訂並延長其使用壽命,這意味著如果使用者持續使用該服務,則無需再次登入。

但是,如果API希望簽訂一個不同的“金鑰”,JWT就會被取消,但是這將使所有當前發出的令牌全部無效,但因為這些令牌是短生命期的,所以這並沒有關係。

登入

在我的程式實現中,正常的登入過程如下所示:

  1. 通過/login接收郵件和密碼。
  2. 檢查資料庫的電子郵件和密碼雜湊。
  3. 建立一個新的重新整理令牌和JWT訪問令牌。
  4. 返回以上兩個資料。

續訂令牌

正常的續訂驗證流程如下所示:

  1. 嘗試從客戶端建立請求時,JWT已經過期。
  2. 將重新整理令牌提交到/renew
  3. 通過將重新整理令牌進行雜湊與資料庫中儲存的進行匹配。
  4. 成功後,建立新的JWT訪問令牌並延長到期時間。
  5. 返回訪問令牌。

驗證令牌

通過檢查到期日期和簽名雜湊可以校驗JWT訪問令牌的有效性。如果校驗失敗,則認為是一個無效的令牌。

如果驗證通過,則JWT的有效載荷中包含了一個uid,它用於在API響應的上下文中傳遞一個對應的user物件來檢查許可權/角色,並相應地建立/讀取/更新/刪除資料。

終止會話

由於重新整理令牌儲存在資料庫中,因此可以將其刪除來“終止會話”。這為使用者提供了一個控制方法,即他們可以通過主動的重新整理令牌“會話”來保護自己的帳戶,並且通過這種方法來進行多次重複認證(通過調整超時時間戳來實現)。

讓JWT保持小巧

在把資訊序列化到JWT訪問令牌中時,請儘可能地讓這個資訊小巧,身份驗證令牌的生命期不需要很長,因此沒必要。如果可以的話,只序列化使用者的uid(id)就可以了,其餘的可以通過“GET /me”來傳遞。

還值得注意的是,儲存在JWT有效載荷中的任何敏感資訊並不安全,因為它只是一個經過base64編碼的字串。

使用根級別的“Me”端點(URL)

一般人會使用/profile這個URL來提供自身的基本屬性。但是,我也看到過比較混論的實現,例如對於/users/:id這種接受整數的URL,它竟然允許傳入字串me來指向自身的屬性。

通過/me訪問自身資訊的更深層次的URL,例如/me/settings或者/billing資訊,而通過users/:id/billing訪問其他使用者的資訊。

// 不推薦
GET /v1/users/me

// 推薦,因為更短,沒有把整數和字串混在一起
GET /v1/me

避免對巢狀路由的操作

有一個採用了以上一些設計理念的重構的專案,最後卻設計出了一個難用的URL系統:

// 一個長長的URL
PATCH /v1/projects/:id/collections/:id/items/:id/attachments

如果要POST上傳一個附件,這個URL可能看起來還行,但是如果在開發客戶端應用程式時想要實現像對附件標星號這麼一個簡單操作的功能的話,那你就需要重寫相關的程式碼。相關程式碼如下:

const apiRoot = 'https://api.myservice.com/v1'
const starAttachment = (projectId, collectionId, itemId, attachmentId, starred) => {
  fetch(
    `${apiRoot}/projects/${projectId}/collections/${collectionId}/items/${itemId}/attachments/${attachmentId}`,
    {
      method: 'PATCH',
      body: JSON.stringify({ starred }),
      // ...
    }
}

助手函式的程式碼如下:

import { starAttachment } from './actions/attachments.js'
class MyComponent extends React.Component {
  doStarAttachment = (id, starred) => {
    // now all the "boilerplate" for starring the attachment
    const {
      projectId,
      collectionsId,
      itemId
    } = this.props.entities.attachments[id]
    // now actually plugging in all that information
    starAttachment(projectId, collectionId, itemId, id, starred)
  }
  // ...
}

如果你把獲取附件屬性這個功能委派給伺服器來實現,並且只使用根級別的URL,這樣不是更好嗎?

const apiRoot = 'https://api.myservice.com/v1'
const starAttachment = (id, starred) => {
  fetch(
    `${apiRoot}/attachments/${id}`,
    {
      method: 'PATCH',
      body: JSON.stringify({ starred }),
      // ...
    }
}
import { starAttachment } from './actions/attachments.js'
class MyComponent extends React.Component {
  doStarAttachment = (id, starred) => {
    // simple as, and you could even easily call it from a gallery-like list
    starAttachment(id, starred)
  }
  // ...
}

總的來說,我認為這兩種方法各有各的優勢,而我傾向於用一個長的路徑來建立/提取資源,用一個短的路徑來更新/刪除資源。

提供分頁功能

分頁很重要,因為你不會想讓一個簡單的請求就獲得數千行的記錄。這個問題似乎很明顯,但是還是會有許多人忽略這個功能。

有多種方法來實現分頁:

“From”引數

可以說這是最容易實現的,API接受一個from查詢字串引數,然後從這個偏移量開始返回有限數量的結果(通常返回20個結果)。

另外最好提供一個limit引數來限制最大記錄數,例如Twitter,最大限制為1000,而預設限制為200。

“下一頁”令牌

如果每頁20個結果之外還有其他的結果,谷歌的Places API就會在響應中返回next_page_token。然後,伺服器在新的請求中接收到這個令牌後,就會返回更多的結果,並附帶新的next_page_token,直到所有的結果全部都返回給客戶端。

Twitter使用引數next_cursor實現了類似的功能。

實現“健康檢查”URL

很有必要提供一種方法來輸出一個簡單的響應,以此來表明API例項是活著的,不需要重新啟動。這個功能也很有用,通過它可以很方便地檢查某個時間點的某臺伺服器上的API是什麼版本,而這無需通過認證。

GET /v1
// response - HTTP 200
{
  "status": "running", 
  "version": "fdb1d5e"
}

我提供了statusversion這兩個值。另外值得一提的是,這個值是從version.txt檔案讀取到的,如果讀取錯誤或者檔案不存在,則預設值為__UNKNOWN__

SDCC 2017·深圳站之架構&大資料技術實戰峰會將於2017年6月10-11日於深圳南山區中南海濱大酒店舉行,集阿里、騰訊、百度、滴滴出行、Intel、微博、唯品會的資深架構師和一線實踐者,納知名研發案例,遇見蘇寧雲商大資料中心總監陳敏敏、Apache RocketMQ聯合創始人馮嘉、餓了麼大資料平臺部總監畢洪宇等大牛。
票務火熱,預購從速,團購立減1000元,更多嘉賓詳細議題敬請關注大會官網和票務點選註冊參會

相關推薦

RESTful API設計技巧經驗總結

譯者注:本文是作者在自己的工作經驗中總結出來的RESTful API設計技巧,雖然部分技巧仍有爭議,但總體來說還是有一定的參考價值的。以下是譯文。簡單說一下程式碼重用 我們都是手藝學徒,沒有人會成為大師。 在我寫這篇文章的時候,我不禁笑了起來,因為從這件事情

RESTful API 設計指南

head 簡單 option eat set 取出 tro 其他 first   網絡應用程序,分為前端和後端兩個部分。當前的發展趨勢,就是前端設備層出不窮(手機、平板、桌面電腦、其他專用設備……)。   因此,必須有一種統一的機制,方便不同的前端設備與後端進行通信。這

Restful API設計

rfc mage erro art 狀態 存在 asc tar 區分 理解RESTful架構 越來越多的人開始意識到,網站即軟件,而且是一種新型的軟件。 這種"互聯網軟件"采用客戶端/服務器模式,建立在分布式體系上,通過互聯網通信,具有高延時(high latency

Restful API 設計參考原則

width 包裝 修改 api開發 司機 word 屬性 add 數據返回 在項目中,需要為後臺服務撰寫API。剛開始接觸的時候,並沒有考慮太多,就想提供URL,服務端通過該URL進行查詢、創建、更新等操作即可。但再對相關規範進行了解後,才發現,API的設計並沒有那麽簡單,

API設計RESTful API 設計指南

sys i/o ani sta 所有 com 訪問 指定 名詞 RESTful API URL定位資源,用HTTP動詞(GET,POST,DELETE,DETC)描述操作。 例如 1. REST描述的是在網絡中client和server的一種交互形式;REST本身不實

Python Restful API設計規範

探討 資源 表現層 gin htm 異步任務 sci 在服務器 type Python 之路,Restful API設計規範 理解RESTful架構 Restful API設計指南 理解RESTful架構 越來越多的人開始意識到,網站即軟件

RESTful API設計規範收集

版本控制 執行 tap cep 冪等性 解耦 sdn hyperlink radi 說明:其實沒有絕對的規範,達到90%即可。 理解RESTful架構:http://www.ruanyifeng.com/blog/2011/09/restful.html RESTful

理解RESTful架構——Restful API設計指南

eval 高並發 服務器 ani eric 互聯網通信 info ati 排序 理解RESTful架構 Restful API設計指南 理解RESTful架構 越來越多的人開始意識到,網站即軟件,而且是一種新型的軟件。 這種"互聯網軟件"采用客戶端/服務器模式,建立

RESTful API設計方法

伸縮性 提高 php 加網 結構 機制 事情 統架構 網頁 1.如果已經開始逐步的接觸到了RESTful API設計方法的朋友,首先要對HTTP/HTTPS有一個大致的了解,雖然本身和RESTful API沒有什麽關系。但是對於增加網站的安全性還是十分重要的,這裏就涉及到了

RESTful API 設計最佳實踐

並不是 要求 關於 bin 是我 最好 實用 link keep 數據模型已經穩定,接下來你可能需要為web(網站)應用創建一個公開的API(應用程序編程接口)。需要認識到這樣一個問題:一旦API發布後,就很難對它做很大的改動並且保持像先前一樣的正確性。現在,網絡上有很

Restful API設計規範

理解RESTful架構 Restful API設計指南     理解RESTful架構 越來越多的人開始意識到,網站即軟體,而且是一種新型的軟體。 這種"網際網路軟體"採用客戶端/伺服器模式,建立在分散式體系上,通過網際網路通訊,具有高延時(high latency)、高併發等

11. RESTful API 設計最佳實踐

API 設計規範 API 設計規範 URI 的設計 過濾、排序和搜尋等資訊 響應和錯誤處理 版本控制(delete) 認證 快取 未完待續… 其他規範可參考

RESTful架構與RESTful API設計

一、REST的由來   REST這個詞是Roy Thomas Fielding博士在他2000年的博士論文中提出的,Fielding將他對網際網路軟體的架構原則定名為REST,即Representational State Transfer的縮寫,翻譯為“表現層狀態轉化”。如果一個架

RESTful API 設計指南——最佳實踐

RESTful API 設計指南——最佳實踐 Facebook、谷歌、Github、Netflix 和幾個其他的科技巨頭已經允許開發者和其產品通過 API 呼叫他們的資料,併為他們提供平臺。即使你不是寫 API 的專業人士,擁有精美的 API 也對你的應用程式有好處。 關於設計 API 的最

Restful API設計思路

Restful API是目前比較成熟的一套網際網路應用程式的API設計理念,Rest是一組架構約束條件和原則,如何Rest約束條件和原則的架構,我們就稱為Restful架構,Restful架構具有結構清晰、符合標準、易於理解以及擴充套件方便等特點,受到越來越多網站的採用! Restful API

RESTful API設計簡單例項

請求型別                              請求路徑          &n

基於RESTful API 設計使用者許可權控制

RESTful簡述   本文是基於RESTful描述的,需要你對這個有初步的瞭解。 RESTful是什麼? Representational State Transfer,簡稱REST,是Roy Fielding博士在2000年他的博士論文中提出來的一種軟體架構風格。 REST比較

關於以太坊智慧合約在專案實戰過程中的設計經驗總結(1)

此文已由作者蘇州授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗 1.智慧合約的概述 近幾年,區塊鏈概念的大風吹遍了全球各地,有的人覺得這是一個大風口,有的人覺得他是個泡沫。眾所周知,比特幣是區塊鏈1.0,而以太坊被稱為了區塊鏈2.0,而區塊鏈1.0和2.0最主要的差別就在於以太坊擁有

關於以太坊智慧合約在專案實戰過程中的設計經驗總結(2)

此文已由作者蘇州授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗 7.智慧合約經驗分享 1)智慧合約開發的工具的問題 古人云“工欲善其事必先利其器”,同意良好的智慧合約的開發工具對智慧合約的開發效率有極大的提升。以下是一些比較好的智慧合約的開發組合: &nb

RESTfulRESTful API 設計規範

概念 本質:一種軟體架構風格 核心:面向資源設計的API 解決問題: 降低開發的複雜性 提高系統的可伸縮性 例如:設計一套API,為多個終端服務。 設計概念和準則 網路上的所有事物都可以被抽象為資源 每一個資源都有唯一的資源標識,對資源的操作不會改變這些標