六步實現Rest風格的API
Rest的作者認為計算機發展到現在,最大的成就不是企業應用,而是web,是漫漫無邊的網際網路web世界。Web能有這麼大的成就,它值得我們研究。所以Rest的作者仔細研究了Web,按照Web的世界一些關鍵特性,提出了我們在實現企業應用的時候應該遵循的一種風格,就是Restful。
Rest風格的API可以給我們很多好處,比如:簡潔,統一,效能,可擴充套件性等等。可惜的是,在實現Rest的時候,總有一些Rest的關鍵特性沒有實現,比如,無狀態性,這在我做過的兩個專案和我知道的另外一個專案都存在。事實上要實現無狀態性在java裡不是那麼容易,因為那意味著要把servlet的session拋棄了。除此之外,Rest的一些其他特性在各個專案中實現的也是各有不同。
接下來,我會列出一些我認為的,要實現Rest風格API的關鍵步驟:
1. 所有東西都是資源(Resource)
所有要給API操作的物件都只能是資源。不管實際上存在的,還是抽象上的。所有資源都會有一個不變的標識(ID),對資源的任何API操作都不應該改變資源的標識。資源和其他資源會有關係,資源與資源的關係通過資源的標識來引用。對資源的操作都應該是完整的,比如獲取資源拿到的應該是一個完整的資源物件(根據企業引用特點有些例外,後面會提到)。
事實上,上面的這些完完全全是按照網際網路的特性提出來的。網際網路中,一個URL就是一個資源;資源的內容就是HTML頁面;不管怎麼改HTML內容,URL都不會改變;資源之間通過HTML裡的連線聯絡起來;每次獲取的時候,獲取到的都是完整的HTML內容。
假設有一個部落格系統,那麼其中的資源可以包括:博主,每個博主都是一個資源;部落格,每篇部落格都是一個資源,部落格和博主之間有聯絡,通過ID聯絡起來;每篇部落格都會有回覆,回覆也算是資源,但是它是隸屬於部落格的,可以認為回覆是部落格的子資源(你也可以認為部落格是博主的子資源,怎麼抽象取決於具體的應用,這裡不討論)。
這步通常不難實現,因為這和ORM中的物件概念是類似的,實現上,如果用了Hibernate之類的框架,改動也應該很小。
2. 規範對資源的操作,最好只包括CRUD
CRUD指建立(Create),讀取(Read), 更新(Update),刪除(Delete)
通常對資源的操作只包含CRUD是不可能的,CRUD裡甚至連查詢的操作的都沒有。但這不妨礙我們對Rest的理解,Rest提出的要求是,對資源的操作都應該是統一的,不管是操作哪種型別的資源,API都應該是一致的。這樣當呼叫API的客戶端知道某種資源的時候,它不需要去學習對這個資源該怎麼操作,因為對所有資源的操作都是一致的,它們都應該支援CRUD操作,以及一些其他操作,比如list(用來查詢,或者列出所有資源), merge(部分更新資源,這應該是唯一的不操作資源所有內容的API)。
要實現簡單的幾個操作不難,難在這幾個簡單操作沒法支撐整個系統的需求。但是想想吧,網際網路也夠複雜了吧,還是不是成功了,而且魚和熊掌不可兼得。有時候伺服器端也不一定要實現所有東西,可以把一些邏輯交給客戶端去做。比如顯示,Rest裡顯示是完全交給客戶端去處理的,伺服器只管資料的儲存,不管客戶端是網頁,還是一個手機應用程式,還是另外一個企業應用。各種各樣的客戶端進來,他們會有各種各樣的需求,伺服器端不可能一一滿足,只能客戶端使用統一的API去組合,實現自己的需求。
3. 規範URL的使用
好了,對資源的操作統一了,但是客戶端還是要知道怎麼觸發對資源的某個具體的操作。Rest用URL的規範來保證這種統一性。
建立並儲存一個部落格:
POST /blog/save
這個請求需要返回部落格的儲存後的結果,其中包括部落格的標識(ID)。 獲取一個已經儲存的部落格,並更新它:
GET /blog/get/345
//更新它
POST /blog/update/345
這個部落格的標識是345。獲取部落格的某個回覆:
GET /blog/get/345/reply/456
對待子資源,通常的做法就是和這個例子一樣,是一級一級的往下找。我們還可以有其他方法,比如remove用來刪除,merge用來部分更新,list用來查詢。
有三種方式可以將引數傳給API操作:
第一種是通過URL的地址傳遞,如前面的例子中把標識放在URL裡;
第二種是通過URL的引數,比如,對於一個查詢請求,可以把查詢的過濾條件放在引數裡:
GET /blog/list?name=Azure:用InstanceInputEndpoint直接和指定instance通訊
第三種是PUT或者POST請求的時候,把內容放在HTTP body裡面。這裡通常就是部落格的內容。
前面我們的例子中有些請求是GET,有些是POST,其實這是有原則的。通常對資源內容沒有改變的操作都實用GET,比如獲取資源,查詢資源;對資源有改變的操作都用POST,比如儲存資源。
如果想做的更好,我們應該近一步的使用HTTP的請求方法,直接把HTTP方法和要做的操作對映起來。比如我喜歡認為GET請求就是獲取資源(get),PUT方法就是更新整個內容(save,update,我覺得這兩個沒必要區分),POST方法就是更新部分內容(merge),DELETE方法就是刪除資源(remove)。如果這樣的話,請求的URL又能簡化:
PUT /blog //建立儲存一個新的部落格
GET /blog/345 //獲取部落格345內容
PUT /blog/345 //更新部落格345
GET /blog/345/reply/456 //獲取部落格345的回覆456
POST /blog/345 //更新部落格345的部分內容
DELETE /blog/345 //刪除部落格345
當然對於list操作,這裡就沒法滿足了,還是需要在URL層面上做些區別。
對於merge操作,有很多人認為是不必要的,Rest不應該提供這個API,但是我覺得在某些情況下很有用。比如某個資源物件,它的內容在不斷的擴充,怎麼讓老的客戶端在內容擴充後還能繼續使用呢? 如果我們要求所有更新請求都必須把所有內容都放在請求的body中,對於客戶端來說就不是那麼好做了,但是如果我們允許merge請求,客戶端可以可以完全忽略新增加的欄位,而只把自己知道的欄位放在請求內容中即可。
4. 資源的多重表述
這一步我覺得不是必須的。
Rest裡,資源的內容通常直接作為一段JSON或者XML返回給客戶端。資源多重表述指的是,一個資源應該能夠支援根據客戶端的請求,返回相應的格式給客戶端。伺服器應該按照請求HTTP頭中的Accept屬性決定返回格式。比如對於Ajax請求,Accept頭是application/json,服務端返回JSON格式;對於android請求,Accept頭是application/xhtml+xml,伺服器返回XML格式。
我覺得這一步不是必須的因為至少從專案前期來說,我們應該都只會支援一種格式。資源的多重表述給我們一種處理多重請求格式的方式,但是我們不需要一開始就支援它。
5. 進一步合理利用HTTP
前面我們已經應用了HTTP的一些東西,比如請求方法,Accept頭。事實上我們可以利用更多。
HTTP支援客戶端快取,在HTTP響應裡利用Cache-Control,Expires,Last-Modified三個頭欄位,我們可以讓瀏覽器快取資源一段時間。Rest也可以利用這些頭,告訴客戶端在一定時間內不需要再次請求資源。這對提高效能有很大好處。更多HTTP頭資訊,可以參考http://en.wikipedia.org/wiki/List_of_HTTP_header_fields。
Rest的請求會出錯,HTTP的請求也會出錯。我們可以直接利用HTTP的response code來告訴客戶端Rest請求出了什麼錯誤。比如500,告訴客戶端,伺服器出錯了;401告訴客戶端需要把安全驗證資訊附上,需要登入系統;404告訴請求的資源不存在,等等。更多HTTP響應碼,可以參考http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html。在實際的業務中,HTTP的那些response code肯定是不能滿足所有需求的,適當的在response body中加上更詳細的錯誤資訊也是必須的。
還有其他很多,總之能利用上的就利用上,不比再次發明輪子。
6. 實現請求的無狀態
Rest是無狀態的。Rest的請求之間不應該有依賴,在呼叫一個請求前,不需要一定要去提前呼叫另外一個請求。Rest裡面不應該有session,特別是Rest請求不應該儲存資訊在sesssion裡,以便在後面的呼叫中使用。甚至包括安全驗證,客戶端不應該需要提前登入,然後把許可權資訊儲存在session裡,後面的請求用同一個session來呼叫。
實現無狀態的方法就是,把所有資訊都包含在當前的請求中,包括驗證資訊。HTTP是無狀態的,HTTP裡有一個Authorization頭,HTTP的要求是在每次請求的時候都把驗證資訊放在裡面,伺服器每次處理請求前都去驗證這個資訊。為了安全,我們可以提供一個生成token的Rest API,客戶端呼叫這個API生成token(可以附上使用者名稱/密碼來生成token)。在後面的所有請求中都把這個token放在Authentication頭中。
實現無狀態最大的好處是能夠方便的擴充套件伺服器,也即scalability。否則的話,我們要麼把Session繫結到具體伺服器上,要麼用一個共享的空間儲存Session。而實現無狀態後,我們可以隨意增加,減少伺服器數量,都不會對當前使用者造成影響。
關鍵字: Rest, Resful, 實現Rest版權所有,轉載請註明來源