REST API設計優秀實踐之參數與查詢的使用
譯文【51CTO.com快譯】眾所周知,我們設計API的目標往往是要通過我們的服務,為用戶提供一定的功能。雖然HTTP和URL資源都允許數據流進行一定程度的基本交互,但是它們在面對其他特定需求時,往往會讓您的API顯得力不從心。在此,我們以分頁為例,即:如果某個數據庫中存放著上百萬篇的文章,那么我們很可能無法在單次響應中,將每一篇文章都發送給客戶。針對此類需求,業界提出了參數化(parametrization)的解決方法。
什么是參數化?
通常而言,參數化是一種請求配置。在編程語言中,我們可以通過某個函數來請求對應的返回值。如果一個函數不帶任何參數,那么我們將無法直接去影響它的返回值。
API也是如此,尤其是那些無狀態的REST API。畢竟,所有REST的交互都是無狀態的。也就是說,每個請求都會包含那些方便連接器理解該請求的所有信息,而且它們與之前的任何請求都無關。
在實際應用中,我們有許多種方法可以向HTTP請求中添加參數,其中包括:查詢字符串,POST、PUT報文,各種PATCH請求、以及標頭(header)。它們各自都有自己的用例和規則。
那么,添加所有參數數據的最簡單方法,就是將所有內容放入報文內。在具體應用中,每個端點都會用到POST方法,而許多API也會把所有的參數放置在報文中。長此以往,傳統的API里積累了越來越多的參數,以致于它們不再適合查詢字符串等操作。顯然,我們應當避免在設計API時此類極端情況的發生。
添加何種參數?
就REST而言,作為API查詢語言的GraphQL,為用戶提供了滿足數據查詢的運行時。那么,我們是否應當添加那些在HTTP規范中已經標準化的標頭字段類來作為參數(請參見:http://www.rfcreader.com/#rfc2616_line4589)呢?
- 如果使用Accept標頭,我們可以定義響應所采用的格式或媒體類型。我們既可以使用它來告知API所需要的JSON或XML,又可以使用它來獲取API的版本(請參見:https://www.moesif.com/blog/technical/api-design/Best-Practices-for-Versioning-REST-and-GraphQL-APIs/?utm_source=dzone&utm_medium=paid&utm_campaign=placed%20article&utm_term=rest%20api%20design%20best%20practices%20for%20parameters%20and%20query%20string%20usage)。
- 而如果使用Cache-Control標頭,我們則可以防止API發送帶有no-cache的緩存響應,且無需使用查詢字符串作為cache buster(?cb=)。
- 當然,授權也可以被視為一種參數。根據API在實際授權過程中的詳細信息,是否通過授權,來產生不同種類的響應。因此,HTTP也為此定義了一個授權類型的標頭(請參見:http://www.rfcreader.com/#rfc2616_line4922)。
在我們了解了各種默認標頭字段后,下面我們來討論是否應該為自己的參數創建一個自定義的標頭字段,或者將其放入URL的查詢字符串中。
何時該使用查詢字符串?
如果我們已獲悉待添加的參數不屬于默認的標頭字段,且并不敏感,那么我們就應當通過查看查詢字符串,以確認是否合適。在查詢字符串的時候,有個<isindex>的HTML元素,可被用于向服務器發送一些關鍵字。而服務器則會據此做出響應,并列出與關鍵字相匹配的頁面列表。接著,查詢字符串會被重新用于Web表單,以通過GET的請求方式,將數據發送到服務器處。
因此,查詢字符串的主要用例便是過濾,它會著重過濾搜索和分頁這兩種特殊的情況。有關此方面的詳細討論,請參見:https://www.moesif.com/blog/technical/api-design/REST-API-Design-Filtering-Sorting-and-Pagination/?utm_source=dzone&utm_medium=paid&utm_campaign=placed%20article&utm_term=rest%20api%20design%20best%20practices%20for%20parameters%20and%20query%20string%20usage。不過,正如針對Web表單的目的所示,它可以被用于不同類型的參數。而RESTful API可以將POST或PUT請求與報文一起使用,以實現將表單數據發送給服務器。
我們來看一個有關嵌套表示(nested representation)的參數示例。默認情況下,我們需要返回文章的普通表示形式(plain representation)。當我們將?withComments查詢字符串添加到端點時,?withComments會以普通表示形式返回某篇文章的評論,而且僅需要一個請求即可。至于此類參數是應該被放在自定義的標頭中,還是查詢字符串里,則主要取決于開發人員的個人經驗與偏好。
根據HTTP的規范陳述(請參見:http://www.rfcreader.com/#rfc2616_line1761):標頭字段可以被視為函數的參數。不過,將查詢字符串添加到URL中會明顯比創建客戶端標頭,要更快、更顯著。實際上,這些字段充當了請求修飾符,其語義等同于編程語言在方法調用中的參數。
在實際應用中,您會發現:在所有端點上保持相同的參數更適合于標頭。例如:身份驗證令牌可以由每個請求所發送。而那些高度動態的參數(尤其是僅對少數幾個端點有效的參數)則應該被放在查詢字符串中。例如:每個端點的過濾器參數都會有所不同。
數組和映射參數
對于開發人員而言,他們可能會經常碰到的一個問題是:如何處理查詢字符串中的數組參數?例如,我們可能碰到需要搜索多個名稱的需求場景。而通常的解決方案是:使用方括號。如下列代碼所示:
不過,HTTP的規范指出:由IPv6 [RFC3513]或更高版本標識的主機通過將IP方括號(“[”和“]”)來區分。這是URI語法中唯一允許使用方括號字符的地方。
我們在許多HTTP服務器和客戶端的實現場景中,都應當牢記上述規范。當然,為了簡便起見,我們可以采用“多次使用一個參數名稱”的另一種解決方案。如下列代碼所示:
該方法雖然有效,但是它會導致開發人員體驗上的下降。通常情況下,客戶端只會使用類似于地圖(map-like)的數據結構。而該結構在被添加到URL中之前,會進行簡單的字符串轉換。而這恰恰會導致后續的數值被覆蓋。可見,在發送請求之前,我們需要進行更加復雜的轉換。
與此同時,也有人會采用另一種方法:使用“,”字符來分隔數值,直接在URL中出現未經編碼的字符。如下列代碼所示:
而對于那些類似地圖的數據結構,我們也可以使用無需編碼的“.”字符。如下列代碼所示:
另外,您還可以對整個查詢字符串進行URL編碼(請參見:https://en.wikipedia.org/wiki/Percent-encoding),以便直接使用任何想要的字符或格式。不過值得一提的是,這同樣也會大幅降低開發人員的使用體驗。
何時不該使用查詢字符串?
在實際應用中,由于查詢字符串往往是URL的一部分。而那些隱藏在客戶端和API之間的攻擊者(如:中間人,MIM)則可以輕而易舉地讀取到我們的URL,因此我們不應該簡單地將諸如密碼之類的敏感數據,直接放入查詢字符串之中。
雖然大多數HTTP客戶端在URL中都會允許使用五位數(five-figure)長度的字符,但是如果我們未能全面設計和考慮URL的整體長度,那么開發人員在調試此類字符串時,往往也會經歷各種繁瑣和不便。
當然,由于我們能夠將任何內容都定義為資源,因此我們有時候完全可以使用POST端點,來進行大量參數的調用與傳遞,進而將報文中的所有數據都發送給API。
可見,為了避免向查詢字符串中那些具有多個參數的資源發送GET請求,進而導致冗長且不可識別的URL產生,我們可以設計出諸如搜索類型的資源,根據API的需求,來緩存各種計算的結果。同時,我們還可以向/searches端點發布新的請求。而該請求會將我們的搜索相關配置與參數保存在報文之中。通過返回一個搜索ID,以便我們使用它來獲取搜索的結果。
總結
作為API設計人員或架構師,我們所追求的優秀實踐,實際上就是要找出API的最理想使用方式,以及最簡單的實現用例。綜上所述,我們建議您注意如下兩個方面:
- 從一開始就能夠借助Moesif(人工智能API服務平臺)之類的服務,著手分析自身API的使用模式。
- 嵌套式資源雖然可以提高URL的可讀性,但是如果我們嵌套得太多的話,則會導致URL參數的冗長。因此,如果您發現自己創建了一個具有超大的查詢字符串端點,那么我建議您最好從其中提取一部分子資源,放置到報文中作為參數發送出去。
原文標題:REST API Design Best Practices for Parameters and Query String Usage,作者:Kay Ploesser
【51CTO譯稿,合作站點轉載請注明原文譯者和出處為51CTO.com】