原文:虛擬研討會:如何設計好的RESTful API?html
REST架構風格最初由Roy T. Fielding(HTTP/1.1協議專家組負責人)在其2000年的博士學位論文中提出。HTTP就是該架構風格的一個典型應用。從其誕生之日開始,它就因其可擴展性和簡單性受到愈來愈多的架構師和開發者們的青睞。它必將獲得更大的發展。一方面,隨着雲計算和移動計算的興起,許多企業願意在互聯網上共享本身的數據、功能;另外一方面,在企業中,RESTful API(也稱RESTful Web服務)也逐漸超越SOAP成爲實現SOA的重要手段之一。爲此,InfoQ將經過一系列深度文章和虛擬研討的形式,深刻剖析REST,以饗讀者。前端
設計好RESTful API對於軟件架構的可擴展性、可伸縮性和消費者的體驗都具備相當重要的做用。本次虛擬研討會的主題是,如何設計好的RESTful API。咱們邀請了幾位國內對REST風格架構及RESTful API設計很有心得的專家,請他們就本身的實戰經驗談談對這一問題的見解。他們分別是李錕、李建業、丁雪豐、馬鈞(注:文末有他們的我的簡介)。java
咱們爲本次虛擬研討會設計的問題有:程序員
InfoQ:什麼是好的RESTful API?相信每一個人都有本身的評判標準。那麼,您認爲一個好的RESTful API應該具備哪些特徵呢?算法
李錕:一個好的RESTful API,應該具有如下特徵:編程
這個API應該是對瀏覽器友好的,可以很好地融入Web,而不是與Web格格不入。json
瀏覽器是最多見和最通用的REST客戶端。好的RESTful API應該可以使用瀏覽器+HTML完成全部的測試(不須要使用編程語言)。這樣的API還能夠很方便地使用各類自動化的Web功能測試、性能測試工具來作測試。Web前端應用(基於瀏覽器的RIA應用、移動App等等)也能夠很方便地將多個RESTful API的功能組合起來,建造Mashup類的應用。api
這個API中所包含的資源和對於資源的操做,應該是直觀和容易理解的,而且符合HTTP協議的要求。瀏覽器
REST開發又被稱做「面向資源的開發」,這說明對於資源的抽象,是設計RESTful API的核心內容。RESTful API建模的過程與面向對象建模相似,是以名詞爲核心的。這些名詞就是資源,任何可命名的抽象概念均可以定義爲一個資源。而HTTP協議並非一種傳輸協議,它實際提供了一個操做資源的統一接口。對於資源的任何操做,都應該映射到HTTP的幾個有限的方法(經常使用的有GET/POST/PUT/DELETE四個方法,還有不經常使用的PATCH/HEAD/OPTIONS方法)上面。因此RESTful API建模的過程,能夠看做是具備統一接口約束的面向對象建模過程。七牛雲存儲
按照HTTP協議的規定,GET方法是安全且冪等的,POST方法是既不安全也不冪等的(能夠用來做爲全部寫操做的通配方法),PUT、DELETE方法都是不安全但冪等的。將對資源的操做合理映射到這四個方法上面,既不過分使用某個方法(例如過分使用GET方法或POST方法),也不添加過多的操做以致於HTTP的四個方法不夠用。
若是發現資源上的操做過多,以致於HTTP的方法不夠用,應該考慮設計出更多的資源。設計出更多資源(以及相應的URI)對於RESTful API來講並無什麼害處。
這個API應該是鬆耦合的。
RESTful API的設計包括了三個按部就班、由低到高的層次:資源抽象、統一接口、超文本驅動。正是這三個層次確保了RESTful API的鬆耦合性。
當設計面向互聯網的API時,鬆耦合變成了一種「必須有」的強需求。緊耦合的API很是脆弱,一旦公佈出去,服務器端和客戶端都沒法持續進化。尤爲是服務器端,公佈出去的接口根本不敢改,改了以後,幾乎全部客戶端應用當即沒法正常工做。REST這種架構風格就是緊耦合API的解毒劑,這個話題能夠談的很深,這裏就不展開了。感興趣的讀者能夠參考《REST實戰》。
這個API中所使用的表述格式應該是常見的通用格式
在RESTful API中,對於資源的操做,是經過在服務器端-客戶端之間傳遞資源的表述來間接完成的。資源的表述能夠有不少種格式,而且在響應和請求中的資源表述格式也會有所不一樣。GET/POST響應中的資源表述格式,常見的有HTML、XML、JSON;POST/PUT請求中的資源表述格式,常見的有標準的HTML表單參數、XML、JSON。
這些常見表述格式,處理起來很是容易,有大量的框架和庫提供支持。因此除非有很合理的要求,一般不須要使用自定義的私有格式。
使用HTTP響應狀態代碼來表達各類出錯狀況
HTTP響應狀態代碼,是HTTP協議這個統一接口中用來表達出錯狀況的標準機制。響應狀態代碼分紅兩部分:status code和reason phase。兩部分都是可定製的,也可使用標準的status code,只定制reason phase。
若是一個所謂的「RESTful API」對於任何請求都返回200 OK響應,在響應的消息體中返回出錯狀況信息,這種作法顯然不符合「確保操做語義的可見性」這個REST架構風格的基本要求。
這個API應該對於HTTP緩存是友好的
充分利用好HTTP緩存是RESTful API可伸縮性的根本。HTTP協議是一個分層的架構,從兩端的user agent到origin server之間,能夠插入不少中間組件。而在整個HTTP通訊鏈條的不少位置,均可以設置緩存。HTTP協議內建有很好的緩存機制,能夠分紅過時模型和驗證模型兩套緩存機制。若是API設計者徹底沒有考慮過如何利用HTTP緩存,那麼這個API的可伸縮性會有不少問題。
李建業:首先說明一下,對REST這個概念,我通常把它理解爲REST風格的架構,可是如今實踐中最爲普遍認知的是HTTP,而它是REST的一個實現,因此RESTful API也能夠不太嚴格的指基於HTTP的API——固然,即便是不嚴格的時候,API自己也應該力求遵循REST架構風格。
我認爲,一個RESTful API最重要的一點應該是——「儘量少的先驗信息」,這一條也同時是我判斷一個好的RESTful API的標準。
好比HTTP動詞,在實踐中,你們可能會經常糾結於有效利用 HTTP 動詞,但這卻並非特別重要的事情——除非你理解這麼作的價值。HTTP 動詞最重要的地方在於它是標準闡明瞭的行爲,也就是說,若是咱們的「客戶端」遵循約定,那麼就沒必要要發明新的動詞,也就沒必要增長「先驗信息」;可是,所謂「先驗信息」,針對的是客戶端——對API來講就是調用者,對於一些企業內部系統,或者一些傳統系統,因爲「資源」很穩定,對資源的操做也很穩定,這些系統的「調用客戶端」不是瀏覽器而是另外一個系統,此時若是強制對應到HTTP動詞,反而會變成額外的「先驗信息」,這時我就不會太拘泥HTTP動詞,本身制定一套動詞放在參數中也能夠接受——只要動詞不變化,這個系統依然是REST風格的。
再好比Response裏面的Content-Type,這個有時會被新手忽略,但這其實很重要,由於通常涉及到系統間協同的API,每每不會使用普通的文本,比較常見的是使用json表達複雜結構,而這與一般的缺省理解不一樣(缺省通常會認爲是text/plain和text/html),因此若是在API中忘記用Content-Type進行區分的話,後續對多種類型的客戶端接入的支持就會變成陷阱(咱們屢次遇到過這個問題)。而若是一開始就檢查是否增長先驗知識(缺省Content-Type爲plain或者容許指定Content-Type),那這一困難就能夠避免了。
丁雪豐:首先,應該正確地使用HTTP的統一接口,好比HTTP的動詞,若是不分青紅皁白清一色POST那顯然還有改進的餘地;
其次,資源有合適的粒度,能夠從三個方面來評判資源的粒度是否合理——網絡的效率、表述的大小以及客戶端使用時的易用程度;
最後,是表述的設計,除了表述的正文內容,還有其中的URI和連接,這些都是評判一個RESTful API好壞的標準。
馬鈞:在我看來,一個好的API標準,就是能儘可能利用到HTTP協議的特性,將HTTP當成一種轉移協議,而不是傳輸協議。包括但不限於:利用HTTP的各類動詞來明確操做;包含有內容協商,能夠根據請求頭提供的參數選擇一個資源最合適的媒體類型、語言、字符集和編碼的表現;使用不一樣的返回代碼來描述各類狀態。但實際上見到過的不少聲稱RESTful API,包括國內的和國外的,能符合這些條件的並很少。parse.com提供的API是我見到過的較爲不錯的RESTful API,能夠做爲範例參考。
InfoQ:安全是恆久的話題,對於基於WSDL和SOAP的Web Service,咱們有WS-Security這樣的安全規範來指導實現認證、受權、身份管理等安全需求。那麼,RESTful API有無成熟可用規範或實現框架呢?如何保證RESTful API的安全性呢?
李錕:保證RESTful API的安全性,主要包括三大方面:
a) 對客戶端作身份認證
b) 對敏感的數據作加密,而且防止篡改
c) 身份認證以後的受權
對客戶端作身份認證,有幾種常見的作法:
在請求中加簽名參數
爲每一個接入方分配一個密鑰,而且規定一種簽名的計算方法。要求接入方的請求中必須加上簽名參數。這個作法是最簡單的,可是須要確保接入方密鑰的安全保存,另外還要注意防範replay攻擊。其優勢是容易理解與實現,缺點是須要承擔安全保存密鑰和按期更新密鑰的負擔,並且不夠靈活,更新密鑰和升級簽名算法很困難。
使用標準的HTTP身份認證機制
HTTP Basic身份認證安全性較低,必須與HTTPS配合使用。HTTP Digest身份認證能夠單獨使用,具有中等程度的安全性。
HTTP Digest身份認證機制還支持插入用戶自定義的加密算法,這樣能夠進一步提升API的安全性。不過插入自定義加密算法在面向互聯網的API中用的不是不少。
這個作法須要確保接入方「安全域-用戶名-密碼」三元組信息的安全保存,另外還要注意防範replay攻擊。
優勢:基於標準,獲得了普遍的支持(大量HTTP服務器端、客戶端庫)。在服務器端作HTTP身份認證的職責能夠由Web Server(例如Nginx)、App Server(例如Tomcat)、安全框架(例如Spring Security)來承擔,對應用開發者來講是透明的。HTTP身份認證機制(RFC 2617)很是好地體現了「分離關注點」的設計原則,並且保持了操做語義的可見性。
缺點:這類基於簡單用戶名+密碼機制的安全性不可能高於基於非對稱密鑰的機制(例如數字證書)。
使用OAuth協議作身份認證
OAuth協議適用於爲外部應用受權訪問本站資源的狀況。其中的加密機制與HTTP Digest身份認證相比,安全性更高。須要注意,OAuth身份認證與HTTP Digest身份認證之間並非相互取代的關係,它們的適用場景是不一樣的。OAuth協議更適合於爲面向最終用戶維度的API提供受權,例如獲取隸屬於用戶的微博信息等等。若是API並非面向最終用戶維度的,例如像七牛雲存儲這樣的存儲服務,這並不是是OAuth協議的典型適用場景。
對敏感的數據作加密,而且防止篡改,常見的作法有:
- 部署SSL基礎設施(即HTTPS),敏感數據的傳輸所有基於SSL。
- 僅對部分敏感數據作加密(例如預付費卡的卡號+密碼),並加入某種隨機數做爲加密鹽,以防範數據被篡改。
身份認證以後的受權,主要是由應用來控制。一般應該實現某種基於角色+用戶組的受權機制,這方面的框架有很多(例如Spring Security),不過大多數開發團隊仍是喜歡本身來實現相關功能。
李建業:我不認爲安全是RESTful API須要考慮的問題,事實上我以爲這是兩個正交的問題。固然,若是使用RESTful API來提供認證、受權和身份管理,那也算是雙方有關係,可是這和其它風格的API設計所要考慮的問題彷佛沒什麼區別,不值得特別注意。
可是在具體設計層面,這二者的「正交點」上彷佛確實有些問題,由於REST是一個推崇狀態無關原則的架構風格,而認證和受權一般基於第三方解決方案,因此每每會出現違背有狀態約束的問題,這個地方我也沒有特別的想法,固然這個困難和原問題關係不大。
至於WS-族的協議,我不太瞭解,不太能參與討論。
丁雪豐:對於RESTful API,常見的安全措施都是能夠繼續使用的。例如,爲了防篡改,能夠對所有參數進行簽名;爲了防範重放攻擊能夠在請求中增長一次性的Token,或者短期內有效的Token;對內容加密能夠實現數據防泄露……;對於DDoS攻擊,各類HTTP流量清洗策略,均可以繼續發揮做用,由於這就是基本的HTTP請求。
在受權和認證方面,OAuth 2.0已經基本成熟了,而且獲得了普遍地應用。若是能夠,接入第三方帳戶體系是個不錯的選擇,好比Google和Facebook的,國內的固然也有幾個候選。
馬鈞:我的認爲RESTful的安全性分爲幾個層次,在安全要求較高的場合,能夠經過HTTPs這樣的加密協議來保證網絡層的安全,應用層的安全能夠經過OAuth實現認證,而對於資源的訪問受權,則只能依靠應用程序來實現了。
InfoQ:如何對RESTful API進行版本控制,請分享您認爲實用的作法?
李錕:一個比較簡單實用的作法是直接在URI中插入版本號,這樣作容許多個版本的API並行運行。
另外一個作法是在HTTP請求中加入自定義頭信息,標明使用的版本號。不過這個作法其實對瀏覽器不夠友好,簡單地使用瀏覽器+HTML沒法測試。
李建業:目前比較好的方式仍是在uri設計中添加版本信息,其它方法都不如這個實用。
丁雪豐:我的認爲最好的版本化,就是沒有明顯的版本。在對已發佈的服務進行變動時,要儘可能作到兼容,其中包括URI、連接和各類不一樣的表述的兼容,最關鍵的就是在擴展時不能破壞現有的客戶端。例如,要變動一個參數,能夠選擇同時兼容新舊兩種輸入,或者保持老參數不動,提供一個新的參數,在文檔中必須作出說明,不推薦新用戶再繼續使用以前的參數。
若是必需要進行不兼容的變動,那麼能夠選擇標記不一樣的版本號,這時能夠選擇在路徑或參數中增長版本信息。也有作法是增長HTTP標頭,只是在調用時會稍有不便,推薦前兩種方法。
馬鈞:RESTfulAPI的版本升級,儘可能兼容以前的版本,保證原有的API都能正常工做,能夠經過HTTP 301轉跳到新的資源。另一種實用的作法就是在url中保留版本號,同時提供多個版本供客戶端使用,如 v1.rest.com 或者 rest.com/v1/ 這樣。
InfoQ:HTTP1.1規範中給出的動詞對於設計RESTful API夠用嗎?您在實際項目中會擴展本身的動詞嗎?在什麼狀況下須要擴展?
李錕:這個問題取決於設計者如何看待和設計資源。若是資源抽象作的很好,對於某個資源的任何操做,一般都可以映射到CRUD四個類別中。CRUD四個類別對於操做資源來講,絕大多數狀況下是完備的。HTTP的GET/POST/PUT/DELETE四個方法,對於CRUD四個類別的操做來講是足夠的,映射關係是Create-POST/Retrieve-GET/Update-PUT/Delete-DELETE。
咱們一般不會選擇建立本身的動詞,這樣作對於客戶端開發者來講,須要更多的學習成本。若是在資源上定義的操做過多,咱們會選擇拆分出更多的資源。
李建業:通常是夠用的,有時一些「不夠用」的場景是因爲咱們沒有設計出合理的資源,好比批量操做。可是,正如以前所說的那樣,對於某些內部的、傳統的(所以模型穩定且已知)系統,API提供者和調用者會有自已的固定動詞表,此時不必拘泥。另外,我不建議擴展動詞,一旦擴展了動詞,其實已經破壞了我以前說的*「儘量少的先驗信息」*,那麼,擴展動詞和從新設計動詞的成本差異不大。基於這個考慮,我建議儘量保持動詞不變,除非你想從新設計動詞表。
丁雪豐:通常狀況下,經常使用的HTTP動詞是夠用的,並無出現必定要本身擴展動詞的狀況。其實,最經常使用的也就是GET、POST、DELETE和PUT,而HEAD、OPTIONS、TRACE則基本用不太到。若是出現一時找不到合適的動詞,安全冪等的操做用GET,其餘均可以用POST,在設計資源時稍加考慮便可。
馬鈞:在個人實際項目中,只用到了POST,PUT,DELETE,GET這四個動詞。
InfoQ:今年5月份發佈的JAX-RS 2.0規範對於RSTfulAPI的設計最有價值的特性是哪一個(些)? 它(們)用於解決什麼問題?
李錕:REST開發框架RESTEasy項目負責人Bill Burke,去年寫了一篇文章介紹JAX-RS 2.0。
我贊成Bill在文章中的觀點,在JAX-RS 2.0增長的內容中,最重要的三部分爲:
a) Client API——用來規範化JAX-RS客戶端的開發方式。
b) Server-side Asynchronous HTTP——用來實現服務器端推送功能,而不須要依靠低效的輪詢方式。
c) Filters and Interceptors——用來分離關注點,將鑑權、日誌等邏輯與業務邏輯分離開,更好地實現代碼重用。
這三部分的內容對於開發者來講都頗有用。遵循JAX-RS規範作開發,能夠確保服務器端以及客戶端代碼的可移植性。
李建業:我我的關注異步API這部分,主要是由於流式服務將會愈來愈多,那將大量須要這類支持。
InfoQ:可否爲InfoQ的讀者推薦一款實用的RESTful API開發框架,並說明您的推介理由。
李錕:這個問題我就不詳細回答了。不一樣的編程語言有不一樣的REST開發框架,對於REST的支持程度也不一樣。開發RESTful API的需求範圍很廣,可選擇的開發框架的範圍也很廣。保持多樣性是繁榮生態環境的基礎。像Java就有支持JAX-RS規範的Jersey、RESTEasy、Restlet、Apache CXF,和不支持JAX-RS規範的Spring MVC等等不少框架。這些框架目前都作的不錯。我對框架的選擇沒有傾向性。RESTful API設計的最佳實踐應該是通用的,而不是必須依賴某種特定的開發框架。
李建業:很差意思,這個我不過重視,無法推薦,不過我能夠解釋一下爲何對RESTful API框架不感冒的緣由。
REST做爲一個架構風格,對咱們的系統開發有很大影響,可是這些影響通常是針對架構(例如狀態無關)或者設計(例如資源識別)上的,因此一旦涉及到具體實現,主要工做就基本結束了,此時開發框架能作的事也就只有簡化編程了(相較而言,有的框架還能起到引導設計的做用),而因爲RESTful會抽象動詞,因此實現層面中和API規範相關的工做原本就很少,那麼框架的價值就更小了。
固然,咱們也不可能直接基於servlet/rakc/wsgi來開發,不過通常的編程語言都會提供一些簡單的url route/match策略,咱們使用這些就足夠了。另外,有些框架能幫咱們生成所有的動詞支持,但這也未必是好事,我通常傾向於按需實現——用到了再支持,這就更不須要太關注開發框架對RESTful的支持了。
丁雪豐:因爲本人是Spring的擁護者,工做中也一直在使用Spring,因此在選擇框架時會更多地傾向Spring MVC(並非說別的框架很差,這裏有些我的主觀的成份)。若是必定要選擇其餘框架,也要選擇可以方便與Spring集成的框架。若是在項目中已經使用了Spring,那麼沒有什麼理由不選擇Spring MVC,鑑於目前Spring在各類項目中的高出鏡率,相信通常狀況下都會選擇Spring MVC。
REST的成熟度模型中,第三層就是HATEOAS,Spring目前還提供了Spring Hateoas子項目,對連接、資源等方面的支持都作了必定的加強。
馬鈞:我目前在實際項目中使用的是Spray,這是一個開源的 REST/HTTP 工具包和底層網絡 IO 包,基於 Scala 和 Akka 構建。輕量級、異步、非堵塞、基於 actor 模式、模塊化和可測試是Spray的特色。
InfoQ:HTTP2.0規範正在制定當中,您對它的期待是什麼?
李錕:個人期待包括兩個方面:應該作的和不該該作的。
HTTP/2.0規範應該作的:
- 與HTTP/1.1協議保持兼容。兼容的含義是說二者能夠並存,客戶端應用能夠根據服務器端的能力,自由地選擇使用HTTP/2.0仍是HTTP/1.1,並且選擇過程對應用來講是透明的。
- 改進HTTP協議(做爲資源的統一接口)之中操做語義表達方式的語法,提升網絡傳輸效率。
- 更好地模塊化,這樣HTTP/2.0協議的實現可以更好地模塊化。應用程序可根據須要選擇適當的模塊,而不是要麼全有、要麼全無。
- 廢棄掉HTTP/1.1協議中一些不多有人用到的部分,例如採用管道(pipelining)方式發送請求。
- 增長更多的動詞,以適應除CRUD以外的其餘場景。
HTTP/2.0規範不該該作的:
- HTTP/2.0協議不該該把底層的數據加密機制(即SSL)做爲必選項。
- HTTP/2.0協議不該該背離REST架構風格的約束,尤爲是要確保操做語義對於中間組件的可見性。
在上面這兩個方面,Roy Fileidng曾經與SPDY協議設計者Mike Belshe發生過激烈爭論,詳情請看:Roy Fielding談Google SPDY協議
李建業:對此規範關注很少,不知道會不會有對於流的支持,目前我所知道的只有chunk方式進行簡單的支持,可是真正的流須要區分數據通道和控制通道——哪怕是邏輯上的區分,這樣就直接對REST風格產生了很大沖擊,考慮到流式服務在將來的發展潛力,我特別期待業界在這方面有所進展。
丁雪豐:HTTP 2.0很大程度上是借鑑了Google的SPDY,就我而言,首先,但願這個規範能作到與HTTP 1.1的兼容,使用者若是隻認識1.1,那麼2.0能優雅「降級」;其次,但願2.0能帶來更好的性能,SPDY在這方面仍是有所改進的,但願HTTP 2.0能再接再礪;最後,但願這個規範能在最終定稿時附帶一個最佳實踐,正確引導人們合理地使用HTTP 2.0。
馬鈞:沒研究過,估計即便出來,1.1還有很長的生命週期,不會很快被取代。
李錕:從2003年開始嘗試Ajax開發,意識到這種開發方式的優勢後,次年(2004年)在知名技術論壇JavaEye上與技術愛好者深刻討論過相關問題。他也是輕量級Java開發框架的擁護者,曾參與JavaEye論壇組織的《J2EE Development without EJB》一書的翻譯。在2007年Ruby on Rails 1.2版支持REST開發以後,他對搞清楚REST的前因後果產生了濃厚興趣,同年組織翻譯了Roy T. Fielding的博士論文《Architectural Styles and the Design of Network-based Software Architectures》(中文版名爲《架構風格與基於網絡的軟件架構設計》)。在2011年又翻譯了《REST實戰》一書,目前仍然致力於在國內普及對於REST架構風格的深刻理解,以及相關實戰經驗的交流。目前主要感興趣的技術領域包括DDD、DSL、SOA、REST。
李建業:2002年畢業,以後一直從事軟件開發,涉及辦公自動化、電信網管/增值業務系統以及互聯網;2009年12月加入淘寶的廣告應用開發團隊;從 2011年末開始,關注軟件研發自己,涉及運維和測試相關工做,目前關注持續集成。李建業還常常受邀出席一些技術大會並發表主題演講,好比InfoQ,Ruby大會等。
丁雪豐:一線攻城師一枚,InfoQ中文站小編,常年混跡於各類社區,業餘時間寫做翻譯、漢化軟件,《RESTfulWebServices Cookbook中文版》、《Spring攻略》等多部圖書的譯者。
馬鈞:全端程序員,15年以上多種程序編程經驗,愛好開源軟件,積極參加各種社區活動,《REST實戰》等技術圖書的譯者。