差異處

這裏顯示兩個版本的差異處。

連向這個比對檢視

java:web:restapi:http_method_cant_make_sense [2017/09/25 00:04]
tony [Why don't you use the query string or formdata to pass the action?]
java:web:restapi:http_method_cant_make_sense [2023/06/25 09:48]
行 1: 行 1:
-{{tag>​rest}} 
-====== Http Method無法表達出某些動作(施工中) ====== 
-===== Introduction ===== 
-在設計RestAPI時,並非所有domain操作都符合CRUD;例如,model中資料的同步(sync)、有transaction的行如轉換(transfer)、搬移(move)、複製(copy)等;因此做些案例研究看是否能找尋到自己能滿意的做法。本篇文章是根據研究結果,分享針對操作做resource modeling的心得。 
-===== Model the action as a service resource ===== 
-action本身就是一種service,如果能把它model成一個service resource,是再好不過的。最常見的例子是login/​logout,可以model為sessions,以[[https://​pubs.vmware.com/​vcd-55/​index.jsp|vCloud]]為例:​ 
-<​code>​ 
-POST https://​vcloud.example.com/​api/​sessions ​ 
-DELETE https://​vcloud.example.com/​api/​sessions 
-</​code>​ 
-在伺服器管理的domain中,可能有像修改firmware設定、更新firmware或掃描硬體等操作,這些該如何model為resource呢?​ 首先是修改firmware設定,以[[http://​h20566.www2.hpe.com/​hpsc/​doc/​public/​display?​docId=c04423967|HPE Server Management API]]為例,它將firmware種類model為一種resource,而設定是它的sub-resource:​ 
-<​code>​ 
-GET /​Systems/​1/​BIOS/​Settings 
-PATCH /​Systems/​1/​BIOS/​Settings 
-</​code>​ 
-接著是更新firmware,以[[http://​www.dell.com/​support/​manuals/​tw/​en/​twdhs1/​dell-active-system-mngr-v8.1/​asm%20rest%20api-v1/​manageddevicefirmware-put?​guid=guid-ad63b2c1-00c4-4ce3-ad7a-a498e15f6636&​lang=en-us|Dell ASM REST API]]為例,使用store resource的方式去操作:​ 
-<​code>​ 
-PUT /​ManagedDevice/​firmware 
-</​code>​ 
-最後是掃描硬體,以[[https://​access.redhat.com/​documentation/​en-US/​Red_Hat_Enterprise_Virtualization/​3.0/​html-single/​REST_API_Guide/​index.html|RHEL Virtualization]]為例,透過post去新增一個discover的sub-resource(task):​ 
-<​code>​ 
-POST /​api/​hosts/​2ab5e1da-b726-4274-bbf7-0a42b16a0fc3/​iscsidiscover HTTP/1.1 
-Accept: application/​xml 
-Content-Type:​ application/​xml 
  
-<​action>​ 
-    <​iscsi>​ 
-        <​address>​mysan.example.com</​address>​ 
-    </​iscsi>​ 
-</​action>​ 
- 
-HTTP/1.1 202 Accept 
-Content-Type:​ application/​xml 
- 
-<action id="​e9126d04-0f74-4e1a-9139-13f11fcbb4ab"​ 
-  href="/​api/​hosts/​2ab5e1da-b726-4274-bbf7-0a42b16a0fc3/​iscsidiscover/​ 
-  e9126d04-0f74-4e1a-9139-13f11fcbb4ab">​ 
-    <​iscsi_target>​iqn.2009-08.com.example:​mysan.foobar</​iscsi_target>​ 
-    ... 
-<​action>​ 
-</​code>​ 
-看起來好像很容易,但要做到這些需要經驗與想像力,否則model出來的resource可能會讓user覺得困惑。除此之外,還要考量是否有非同步需求;以更新firmware來說,雖然PUT不是不能回傳202,但比較少看到有人使用。由於以上原因,我們需要一個比較簡單的方式。 
-===== A simple way: store resource/​PATCH/​controller resource ===== 
-這個方法是根據[[http://​www.vinaysahni.com/​best-practices-for-a-pragmatic-restful-api|這篇best practice]]、 
-[[http://​shop.oreilly.com/​product/​0636920021575.do|REST API Design Rulebook]]與case studies整理出來的,給大家當一個參考方法。\\ 
-我們以下面的動作為例:​ 
-<​code>​ 
-lock file 
-unlock file 
-power on 
-power off system 
-activate user 
-deactivate user 
-login  
-logout 
-search 
-import 
-update firmware 
-config bios 
-</​code>​ 
-==== Classify the actions ==== 
-首先我們必須先分類;目前我分成兩類,\\ 
-1. The positive and negative action,動作有正向與反向:​ 
-<​code>​ 
-lock/unlock file 
-power on/off system 
-activate/​deactivate 
-login/​logout 
-</​code>​ 
-2. The procedural action,動作是一個執行過程:​ 
-<​code>​ 
-search 
-import 
-update firmware 
-config bios 
-</​code>​ 
-==== The positive and negative action ==== 
-假如你的動作是屬於這個種類,你有兩個選擇, 
-  * Treat it like a sub-resource. 
-  * Patch for the partial update. 
-如果可以是resource中的屬性,可以考慮用PATCH的方式去更新屬性值:​ 
-<​code>​ 
-Patch /​Users/​12345 
-{“active”:​ false} 
-</​code>​ 
-另外一個選擇就是把它當sub-resource,也就是store resource的做法;positive的action使用PUT,negative的action使用DELETE。以[[https://​developer.github.com/​v3/​issues/​|github]]與[[https://​developer.box.com/​reference|box]]為例:​ 
-<​code>​ 
-lock/unlock (github) 
-    PUT /​repos/:​owner/:​repo/​issues/:​number/​lock 
-    DELETE /​repos/:​owner/:​repo/​issues/:​number/​lock 
-apply/​remove the watermark (box) 
-    PUT https://​api.box.com/​2.0/​files/​file_id/​watermark 
-    DELETE https://​api.box.com/​2.0/​files/​file_id/​watermark 
-</​code>​ 
- 
-==== The procedural action ==== 
-這個能列舉的範例很多,只要不是CRUD的操作,你可以選擇把它model唯一個controller resource: 
-<​code>​ 
-Search 
-    POST /​v1/​users/​search (instagram) 
-    POST /​1/​indexes/​{indexName}/​query (algolia) 
-    POST /​indexes/​hotels/​docs/​search (azure) 
-    POST facebook, twitter, box, github, etc.. 
-POST /​videos/​reportAbuse (youtube) 
-</​code>​ 
-如果不想濫用,你可以把動作是非同步做為前置條件。 
-===== Case Study: Power Management ===== 
-以上述的方法,我們來討論電源管理該怎麼做。首先我們把電源當成一個resource,而狀態為其屬性;所以我們可以透過GET去取得狀態,而透過PUT或PATCH去修改狀態:​ 
-<​code>​ 
-GET /​hosts/​123/​power 
-{'​state':'​on'​} 
-PUT /​hosts/​123/​power 
-{'​state':'​on'​} 
-PUT /​hosts/​123/​power 
-{'​state':'​off'​} 
-</​code>​ 
-這樣的做法,有哪些問題我們需要考量?​ 
-  - hypermedia: 我們有辦法表達出取得狀態、關機、開機等的link嗎?​ 
-  - synchronized:​ 在我們做開機與關機動作後,是可以立即反應的嗎? ​ 
-  - implementation:​ 這部分稍後再做說明。 
-或許把它做成store resource會比較好:​ 
-<​code>​ 
-GET /​hosts/​123/​power_status 
-{'​state':'​on'​} 
-PUT /​hosts/​123/​power_on 
-PUT /​hosts/​123/​power_off 
-</​code>​ 
-這裡我並沒有使用DELETE,因為在語義上使用DELETE power_on不會比PUT power_off來得清楚。此外,在使用這種方式後,hypermedia可以很容易的使用URI去表達出不同的意義。剩下的問題就是synchronized。\\ 
-\\ 
-PUT回傳202的方式,我自己本身還沒看過範例,但HTTP規格書也沒說這樣是不對的。我覺得值得討論的部分是:​ Idempotent。假設PUT回傳202,這代表著server將產生一個asynchronized的task,每次PUT power_on所產生的task是否會相同呢?​ 假如不同,是不是代表違反了Idempotent?​ 假如相同,實作會不會蠻奇怪的呢?​\\ 
-\\ 
-另外一個選擇,就是把它當controller resource,使用POST去操作:​ 
-<​code>​ 
-POST /​hosts/​123/​power_on 
-POST /​hosts/​123/​power_off 
-</​code>​ 
-有個實際案例就是[[https://​pubs.vmware.com/​vcd-51/​index.jsp?​topic=/​com.vmware.vcloud.api.reference.doc_51/​doc/​operations/​POST-PowerOffVApp.html|vCloud]]的Power On/Off API。順便提一下,會使用這個範例是由於在Roy Fielding在2008年[[http://​roy.gbiv.com/​untangled/​2009/​it-is-okay-to-use-post|It is okay to use POST]]文章中,留言區有人提及;而Roy Fielding也是傾向於將狀態與操作model為不同狀態,這讓我很好奇為什麼他會這樣想。 
- 
-===== Some questions ===== 
-針對以上提到的方法,我思考著幾個問題:​ 
-==== Why don't you use the query string or formdata to pass the action? ==== 
-(這裡先撇開query string、formdata或request body等方式的差異) 對我而言,我目前認為有幾點需要考量:​ 
-  - hypermedia: 假如要表達出query string等方式,會需要使用template的方式,實作上不會比單純透過URI Path的方式容易。 
-  - http method convention: 如果在collection上使用POST,是否會讓新增與其它action讓使用者混淆。 
-  - implementation: ​ 
-我們看看如果使用URI的Path來實做lock與unlock可能會長怎樣:​ 
-<code java> 
- @RequestMapping(value = "/​files/​{fid}/​lock",​ method = RequestMethod.PUT,​ produces = {"​application/​json"​}) 
- public OutputData<​HostRestBean>​ lockFile(@PathVariable("​fid"​) String aFid, HttpServletResponse ​ aResponse){ 
- // ... 
- } 
- 
- @RequestMapping(value = "/​files/​{fid}/​unlock",​ method = RequestMethod.DELETE,​ produces = {"​application/​json"​}) 
- public OutputData<​HostRestBean>​ unlockFile(@PathVariable("​fid"​) String aFid, HttpServletResponse ​ aResponse){ 
- // ... 
- } 
-</​code>​ 
-接著使用POST+RequestParam:​ 
-<code java> 
- @RequestMapping(value = "/​files/​{fid}",​ method = RequestMethod.POST,​ produces = {"​application/​json"​}) 
- public OutputData<​HostRestBean>​ opFile(@PathVariable("​fid"​) String aFid,  
- @RequestParam(value="​action",​ required=true) String action, 
- HttpServletResponse ​ aResponse) { 
- if("​lock"​.equalsIgnoreCase(action)) { 
- // ... 
- } else if("​unlock"​.equalsIgnoreCase(action)){ 
- // ... 
- } else 
- // ... 
- } 
-</​code>​ 
-你喜歡哪個?​ 假如File的action只有lock與unlock,那真的是天下太平;但事實上,action還有move、copy、rename等。考慮一下測試、維護、擴充的話,哪一個會比較好?​ 以擴充與維護來說,RequestParam的方式讓所有的action都必須接受同一組參數甚至輸出,增加了修改的麻煩;而URI Path則由各別的實做去決定。測試則是因為實做已分開,根據個別操作的目的去測試即可。 
-==== Threat the P/N action as a store resource or use PATCH? ==== 
- 
-===== Summary ===== 
-[[.:​http_method_cant_make_sense:​other_case_studies|其它的案例]] 
-===== Reference ===== 
-  * [[https://​stackoverflow.com/​questions/​2173721/​why-does-including-an-action-verb-in-the-uri-in-a-rest-implementation-violate-th|Why does including an action verb in the URI in a REST implementation violate the protocol?]] 
-  * [[http://​shop.oreilly.com/​product/​0636920021575.do|REST API Design RuleBook]] Rule: A verb or verb phrase should be used for controller names 
-  * [[https://​stackoverflow.com/​questions/​27121749/​confusion-between-noun-vs-verb-in-rest-urls|Confusion Between Noun vs. Verb in Rest URLs]] 
-  * [[https://​apihandyman.io/​do-you-really-know-why-you-prefer-rest-over-rpc/​|Do you really know why you prefer REST over RPC?]] 
-  * [[https://​developer.mozilla.org/​en-US/​docs/​Web/​HTTP/​Methods/​OPTIONS|HTTP OPTIONS method]] 
-  * [[https://​www.smashingmagazine.com/​2016/​09/​understanding-rest-and-rpc-for-http-apis/​|Understanding rest and rpc for http apis]] 
-  * [[https://​stackoverflow.com/​questions/​24241893/​rest-api-patch-or-put|Patch VS PUT Store Resource]] 
-  * [[https://​www.instagram.com/​developer/​endpoints/​|Instangram REST API]] 
-  * [[http://​roy.gbiv.com/​untangled/​2009/​it-is-okay-to-use-post|it is okay to use post]] 
-  * [[http://​www.vinaysahni.com/​best-practices-for-a-pragmatic-restful-api|Best Practices for Designing a Pragmatic RESTful API]] 
- 
-=====    ===== 
----- 
-\\ 
-~~DISQUS~~