RESTful API 編寫指南

Gevin發表於2016-06-06

restful-api-get-started-to-write

基於一些不錯的RESTful開發元件,可以快速的開發出不錯的RESTful API,但如果不瞭解開發規範的、健壯的RESTful API的基本面,即便優秀的RESTful開發元件擺在面前,也無法很好的理解和使用。下文Gevin結合自己的實踐經驗,整理了從零開始開發RESTful API的核心要點,完善的RESTful開發元件基本都會包含全部或大部分要點,對於支援不夠到位的要點,我們也可以自己寫程式碼實現。

Outline

1. Request 和 Response

RESTful API的開發和使用,無非是客戶端向伺服器發請求(request),以及伺服器對客戶端請求的響應(response)。本真RESTful架構風格具有統一介面的特點,即,使用不同的http方法表達不同的行為:

  • GET(SELECT):從伺服器取出資源(一項或多項)
  • POST(CREATE):在伺服器新建一個資源
  • PUT(UPDATE):在伺服器更新資源(客戶端提供完整資源資料)
  • PATCH(UPDATE):在伺服器更新資源(客戶端提供需要修改的資源資料)
  • DELETE(DELETE):從伺服器刪除資源

客戶端會基於GET方法向伺服器傳送獲取資料的請求,基於PUTPATCH方法向伺服器傳送更新資料的請求等,服務端在設計API時,也要按照相應規範來處理對應的請求,這點現在應該已經成為所有RESTful API的開發者的共識了,而且各web框架的request類和response類都很強大,具有合理的預設設定和靈活的定製性,Gevin在這裡僅準備強調一下響應這些request時,常用的Response要包含的資料和狀態碼(status code),不完善的內容,歡迎大家補充:

  • GET, PUTPATCH請求成功時,要返回對應的資料,及狀態碼200,即SUCCESS
  • POST建立資料成功時,要返回建立的資料,及狀態碼201,即CREATED
  • DELETE刪除資料成功時,不返回資料,狀態碼要返回204,即NO CONTENT
  • GET 不到資料時,狀態碼要返回404,即NOT FOUND
  • 任何時候,如果請求有問題,如校驗請求資料時發現錯誤,要返回狀態碼 400,即BAD REQUEST
  • 當API 請求需要使用者認證時,如果request中的認證資訊不正確,要返回狀態碼 401,即NOT AUTHORIZED
  • 當API 請求需要驗證使用者許可權時,如果當前使用者無相應許可權,要返回狀態碼 403,即FORBIDDEN

最後,關於Request 和 Response,不要忽略了http header中的Content-Type。以json為例,如果API要求客戶端傳送request時要傳入json資料,則伺服器端僅做好json資料的獲取和解析即可,但如果服務端支援多種型別資料的傳入,如同時支援json和form-data,則要根據客戶端傳送請求時header中的Content-Type,對不同型別是資料分別實現獲取和解析;如果API響應客戶端請求後,需要返回json資料,需要在header中新增Content-Type=application/json

2. Serialization 和 Deserialization

Serialization 和 Deserialization即序列化和反序列化。RESTful API以規範統一的格式作為資料的載體,常用的格式為jsonxml,以json格式為例,當客戶端向伺服器發請求時,或者伺服器相應客戶端的請求,向客戶端返回資料時,都是傳輸json格式的文字,而在伺服器內部,資料處理時基本不用json格式的字串,而是native型別的資料,最典型的如類的例項,即物件(object),json僅為伺服器和客戶端通訊時,在網路上傳輸的資料的格式,伺服器和客戶端內部,均存在將json轉為native型別資料和將native型別資料轉為json的需求,其中,將native型別資料轉為json即為序列化,將json轉為native型別資料即為反序列化。雖然某些開發語言,如Python,其原生資料型別listdict能輕易實現序列化和反序列化,但對於複雜的API,內部實現時總會以物件作為資料的載體,因此,確保序列化和反序列化方法的實現,是開發RESTful API最重要的一步準備工作

題外話,序列化和反序列化的便捷,造就了RESTful API跨平臺的特點,使得REST取代RPC成為Web Service的主流

序列化和反序列化是RESTful API開發中的一項硬需求,所以幾乎每一種常用的開發語言都會有一個或多個優秀的開源庫,來實現序列化和反序列化,因此,我們在開發RESTful API時,沒必要製造重複的輪子,選一個好用的庫即可,如python中的marshmallow,如果基於Django開發,Django REST Framework中的serializer即可。

3. Validation

Validation即資料校驗,是開發健壯RESTful API中另一個重要的一環。仍以json為例,當客戶端向伺服器發出post, putpatch請求時,通常會同時給伺服器傳送json格式的相關資料,伺服器在做資料處理之前,先做資料校驗,是最合理和安全的前後端互動。如果客戶端傳送的資料不正確或不合理,伺服器端經過校驗後直接向客戶端返回400錯誤及相應的資料錯誤資訊即可。常見的資料校驗包括:

  • 資料型別校驗,如欄位型別如果是int,那麼給欄位賦字串的值則報錯
  • 資料格式校驗,如郵箱或密碼,其賦值必須滿足相應的正規表示式,才是正確的輸入資料
  • 資料邏輯校驗,如資料包含出生日期和年齡兩個欄位,如果這兩個欄位的資料不一致,則資料校驗失敗

以上三種型別的校驗,資料邏輯校驗最為複雜,通常涉及到多個欄位的配合,或者要結合使用者和許可權做相應的校驗。Validation雖然是RESTful API 編寫中的一個可選項,但它對API的安全、伺服器的開銷和互動的友好性而言,都具有重要意義,因此,Gevin建議,開發一套完善的RESTful API時,Validation的實現必不可少。

4. Authentication 和 Permission

Authentication指使用者認證,Permission指許可權機制,這兩點是使RESTful API 強大、靈活和安全的基本保障。

常用的認證機制是Basic AuthOAuth,RESTful API 開發中,除非API非常簡單,且沒有潛在的安全性問題,否則,認證機制是必須實現的,並應用到API中去。Basic Auth非常簡單,很多框架都整合了Basic Auth的實現,自己寫一個也能很快搞定,OAuth目前已經成為企業級服務的標配,其相關的開源實現方案非常豐富更多)。

我在《RESTful 架構風格概述》中,對認證機制做了更加詳細的描述,有興趣的同學不妨閱讀相關章節。

許可權機制是對API請求更近一步的限制,只有通過認證的使用者符合許可權要求,才能訪問API。許可權機制的具體實現通常依賴於系統的業務邏輯和應用場景,generally speaking,常用的許可權機制主要包含全域性型的和物件型的,全域性型的許可權機制,主要指通過為使用者賦予許可權,或者為使用者賦予角色或劃分到使用者組,然後為角色或使用者組賦予許可權的方式來實現許可權控制,物件型的許可權機制,主要指許可權控制的顆粒度在object上,使用者對某個具體物件的訪問、修改、刪除或其行為,要單獨在該物件上為使用者賦予相關許可權來實現許可權控制。

全域性型的許可權機制容易理解,實現也簡單,有很多開源庫可做備選方案,不少完善的web開發框架,也會整合相關的許可權邏輯,object permission 相對難複雜一點,但也有很多典型的應用場景,如多人部落格系統中,作者對自己文章的編輯許可權即為object permission,其對應的開源庫也有很多。

注: 我寫過一篇《Django許可權機制的實現》,Django 開發者可做延伸閱讀。

開發一套完整的RESTful API,許可權機制必須納入考慮範圍,雖然許可權機制的具體實現依賴於業務,許可權機制本身,是有典型的模式存在的,需要開發者掌握基本的許可權機制實現方案,以便隨時應用到API中去。

5. CORS

CORS即Cross-origin resource sharing,在RESTful API開發中,主要是為js服務的,解決javascript 呼叫 RESTful API時的跨域問題。

由於固有的安全機制,js的跨域請求時是無法被伺服器成功響應的。現在前後端分離日益成為web開發主流方式的大趨勢下,後臺逐漸趨向指提供API服務,為各客戶端提供資料及相關操作,而網站的開發全部交給前端搞定,網站和API服務很少部署在同一臺伺服器上並使用相同的埠,js的跨域請求時普遍存在的,開發RESTful API時,通常都要考慮到CORS功能的實現,以便js能正常使用API。

目前各主流web開發語言都有很多優秀的實現CORS的開源庫,我們在開發RESTful API時,要注意CORS功能的實現,直接拿現有的輪子來用即可。

更多關於CORS的介紹,有興趣的同學可以檢視阮一峰老師的跨域資源共享 CORS 詳解

6. URL Rules

RESTful API 是寫給開發者來消費的,其命名和結構需要有意義。因此,在設計和編寫URL時,要符合一些規範。Url rules 可以單獨寫一篇部落格來詳細闡述,本文只列出一些關鍵點。

6.1 Version your API

規範的API應該包含版本資訊,在RESTful API中,最簡單的包含版本的方法是將版本資訊放到url中,如:

/api/v1/posts/
/api/v1/drafts/

/api/v2/posts/
/api/v2/drafts/

另一種優雅的做法是,使用HTTP header中的accept來傳遞版本資訊,這也是GitHub API 採取的策略

6.2 Use nouns, not verbs

RESTful API 中的url是指向資源的,而不是描述行為的,因此設計API時,應使用名詞而非動詞來描述語義,否則會引起混淆和語義不清。即:

# Bad APIs
/api/getArticle/1/
/api/updateArticle/1/
/api/deleteArticle/1/

上面四個url都是指向同一個資源的,雖然一個資源允許多個url指向它,但不同的url應該表達不同的語義,上面的API可以優化為:

# Good APIs
/api/Article/1/

article 資源的獲取、更新和刪除分別通過 GET, PUTDELETE方法請求API即可。試想,如果url以動詞來描述,用PUT方法請求 /api/deleteArticle/1/ 會感覺多麼不舒服。

6.3 GET and HEAD should always be safe

RFC2616已經明確指出,GETHEAD方法必須始終是安全的。例如,有這樣一個不規範的API:


# The following api is used to delete articles
# [GET]
/api/deleteArticle?id=1

試想,如果搜尋引擎訪問了上面url會如何?

6.4 Nested resources routing

如果要獲取一個資源子集,採用 nested routing 是一個優雅的方式,如,列出所有文章中屬於Gevin編寫的文章:

# List Gevin's articles
/api/authors/gevin/articles/

獲取資源子集的另一種方式是基於filter(見下面章節),這兩種方式都符合規範,但語義不同:如果語義上將資源子集看作一個獨立的資源集合,則使用 nested routing 感覺更恰當,如果資源子集的獲取是出於過濾的目的,則使用filter更恰當。

至於編寫RESTful API時到底應採用哪種方式,則仁者見仁,智者見智,語義上說的通即可。

6.5 Filter

對於資源集合,可以通過url引數對資源進行過濾,如:

# List Gevin's articles
/api/articles?author=gevin

分頁就是一種最典型的資源過濾。

6.6 Pagination

對於資源集合,分頁獲取是一種比較合理的方式。如果基於開發框架(如Django REST Framework),直接使用開發框架中的分頁機制即可,如果是自己實現分頁機制,Gevin的策略是:

返回資源集合是,包含與分頁有關的資料如下:

{
  "page": 1,            # 當前是第幾頁
  "pages": 3,           # 總共多少頁
  "per_page": 10,       # 每頁多少資料
  "has_next": true,     # 是否有下一頁資料
  "has_prev": false,    # 是否有前一頁資料
  "total": 27           # 總共多少資料
}

當想API請求資源集合時,可選的分頁引數為:

引數 含義
page 當前是第幾頁,預設為1
per_page 每頁多少條記錄,預設為系統預設值

另外,系統內還設定一個per_page_max欄位,用於標記系統允許的每頁最大記錄數,當per_page值大於 per_page_max 值時,每頁記錄條數為 per_page_max

6.7 Url design tricks

(1)Url是區分大小寫的,這點經常被忽略,即:

  • /Posts
  • /posts

上面這兩個url是不同的兩個url,可以指向不同的資源

(2)Back forward Slash (/)

目前比較流行的API設計方案,通常建議url以/作為結尾,如果API GET請求中,url不以/結尾,則重定向到以/結尾的API上去(這點現在的web框架基本都支援),因為有沒有 /,也是兩個url,即:

  • /posts/
  • /posts

這也是兩個不同的url,可以對應不同的行為和資源

(3)連線符 - 和 下劃線 _

RESTful API 應具備良好的可讀性,當url中某一個片段(segment)由多個單片語成時,建議使用 - 來隔斷單詞,而不是使用 _,即:

# Good
/api/featured-post/

# Bad
/api/featured_post/

這主要是因為,瀏覽器中超連結顯示的預設效果是,文字並附帶下劃線,如果API以_隔斷單詞,二者會重疊,影響可讀性。

總結

編寫本文的初衷,是為了整理一套從零開始編寫規範、安全的RESTful API的基本思路。本文介紹了開發RESTful API時,要考慮的基本內容,對於類似Flask這種天生支援RESTful風格的web框架,不依賴其他RESTful第三方庫開發RESTful 服務時,可以從本文內容入手;不少強大的RESTful 庫,雖然其相關介面基本涵蓋了本文的全部或大部分內容,但本文的總結,相信對這些庫的理解和使用也是有幫助的。

最後,關於如何開發RESTful API,歡迎大家與我交流~


注:轉載本文,請與Gevin聯絡




如果您覺得Gevin的文章有價值,就請Gevin喝杯茶吧!

|

歡迎關注我的微信公眾賬號

RESTful API 編寫指南

相關文章