HTTPメソッドと冪等性
商品penの名前・数量・色を取得し、置換と部分更新を行います。同じ要求を繰り返した後の値、注文の新規作成、削除後の応答を順に確認し、安全なメソッドと冪等なメソッドの意味を具体的な状態変化から学びます。
説明
HTTPメソッドは、対象の資源に何を求めるかを表します。GETは取得、PUTは指定した表現による作成・置換、PATCHは変更内容の適用、DELETEは対象の削除を要求します。メソッドの違いは、同じ要求を再送するときにも関わります。
商品penの名前・数量・色を取得し、置換と部分更新を行います。同じ要求を繰り返した後の値、注文の新規作成、削除後の応答を順に確認し、安全なメソッドと冪等なメソッドの意味を具体的な状態変化から学びます。
1商品の現在値を取得
対象URLは/items/penです。まずGETで名前・数量・色を取得します。
curl -sS -iGETの応答ヘッダーと本文を表示 http://127.0.0.1:18080/items/pen商品penの資源200 OKの本文にname、qty、colorが返ります。GETは取得を求める安全なメソッドです。ここでの「安全」は利用者が資源の変更を要求しないという意味で、アクセスログの記録など一切の副作用がないという意味ではありません。
2HEADで本文を受け取らずに確認
HEADでは、GETで返す表現の情報を本文なしで取得します。curlにはHEAD用の-Iを指定します。
curl -sS -IHEADを送り本文なしで処理 http://127.0.0.1:18080/items/pen状態とContent-Type、Content-Lengthが表示されますが、JSON本文はありません。ここでのContent-LengthはGETなら返る本文の長さです。-X HEADだけではcurl側に本文を待たない動作まで指示できないため、-Iを使います。
3受け付けるメソッドを問い合わせる
OPTIONSで対象資源が受け付ける操作とPATCH形式を確認します。
curl -sS -i -X OPTIONS対象の通信上の選択肢を問い合わせ http://127.0.0.1:18080/items/penAllowはこの資源が許すメソッド一覧です。Accept-PatchはPATCH本文の対応形式です。204 No Contentには応答本文がありません。OPTIONSだけで認証や個別の更新条件がすべて分かるわけではありません。
4PUTで表現全体を置換
名前と数量2だけを含むJSONで置換します。colorは送信しません。
curl -sS -i -X PUT表現全体を作成・置換 -H "Content-Type: application/json" --data-binary置換するJSON本文 '{"name":"pen","qty":2}' http://127.0.0.1:18080/items/pen返る表現はnameとqtyになり、以前のcolorは残りません。このAPIのPUTは送った表現全体で置き換えるためです。PUTを「書いた項目だけの更新」として使うと、送らなかった項目を失う設計になり得ます。
5同じPUTを再送
同一の置換要求をもう一度送り、GETでも現在値を取得します。
curl -sS -i -X PUT同じ置換を再送 -H "Content-Type: application/json" --data-binary '{"name":"pen","qty":2}' http://127.0.0.1:18080/items/pen
curl -sS http://127.0.0.1:18080/items/pen最終状態を取得数量は2のままです。同じ要求を複数回適用しても、要求した効果が一回と同じになる性質を冪等性といいます。PUTは冪等なメソッドとして定義されています。応答や記録件数まで毎回同じになることは要件ではありません。
6PATCHで数量だけを変更
JSON Merge Patch形式でqtyを5にします。同じ内容を二回送り、nameが残るかも確認します。
curl -sS -i -X PATCH -H "Content-Type: application/merge-patch+json項目単位の変更形式" --data-binary '{"qty":5}数量を5に設定' http://127.0.0.1:18080/items/pen
curl -sS -i -X PATCH -H "Content-Type: application/merge-patch+json" --data-binary '{"qty":5}' http://127.0.0.1:18080/items/pennameは残り、qtyは二回とも5です。JSON Merge Patchは指定項目を変更し、指定しない項目を維持します。nullには項目を削除する意味があります。この定数への代入は冪等ですが、PATCH全般が冪等という意味ではありません。
7加算を表すPATCHを再送
このAPIにはaddの値を数量へ足す変更形式もあります。1を足す要求を二回送ります。
curl -sS -i -X PATCH -H "Content-Type: application/vnd.lbox.delta+jsonこのAPIの加算形式" --data-binary '{"add":1}数量に1を加算' http://127.0.0.1:18080/items/pen
curl -sS -i -X PATCH -H "Content-Type: application/vnd.lbox.delta+json" --data-binary '{"add":1}' http://127.0.0.1:18080/items/pen数量は5から6、さらに7になります。このAPI固有の形式では、本文が最終値ではなく加算操作を表すためです。PATCHの再送可否は変更形式と処理内容にも依存します。応答を受け損ねた場合、未処理と決めつけて再送すると二重更新になります。
8POSTで注文を新規作成
POSTは対象資源固有の処理を求めます。ここでは/ordersへ商品と数量を送ると、新しい注文が作られます。
curl -sS -i -H "Content-Type: application/json" --data-binaryPOSTする注文内容 '{"item":"pen","qty":1}' http://127.0.0.1:18080/orders新規注文を受け付ける資源201 Createdと注文id=1が返ります。Locationは作成された資源の場所を示します。POSTは常に新規作成だけに使うメソッドではありませんが、このAPIでは一回のPOSTで一件の注文を作成します。
9同じPOSTで作られる件数
同じ注文内容をもう一度送り、注文一覧を取得します。
curl -sS -i -H "Content-Type: application/json" --data-binary同じ内容を再送 '{"item":"pen","qty":1}' http://127.0.0.1:18080/orders
curl -sS http://127.0.0.1:18080/orders作成済みの注文一覧id=2の別注文が増え、一覧には二件あります。POSTはメソッドとして冪等性を保証しません。重複を防いで再送するAPIには、注文IDの指定や同じ処理キーの重複排除など、追加の契約が必要です。
10商品を削除して再送
DELETEを二回送り、最後にGETで資源の有無を確かめます。
curl -sS -i -X DELETE対象資源を削除 http://127.0.0.1:18080/items/pen
curl -sS -i -X DELETE http://127.0.0.1:18080/items/pen
curl -sS -i http://127.0.0.1:18080/items/pen削除後の取得最初は204、二回目は404ですが、最終状態はどちらも「対象がない」です。応答コードが異なってもDELETEの冪等性とは矛盾しません。冪等性はサーバーに求めた効果についての性質です。
11不存在のURLへPUT
削除したURLへPUTで表現を指定し直します。
curl -sS -i -X PUT指定URLに表現を作成 -H "Content-Type: application/json" --data-binary '{"name":"pen","qty":2}' http://127.0.0.1:18080/items/penこのAPIは新規作成を許すため201 Createdになります。PUTは既存資源の置換だけでなく、指定URLへの作成にも使われます。GET・HEAD・OPTIONSは安全かつ冪等、PUT・DELETEは安全ではないが冪等、POST・PATCHはメソッドだけでは冪等性を保証しません。
まとめ
安全性は利用者が変更を要求するか、冪等性は同じ要求を再送した効果が一回と同じかという性質です。
PUTの置換、PATCHの変更、POSTの資源固有処理では、送った本文の意味と再送後の状態を確認します。
同じ応答が返ることと冪等性は別です。DELETEで204から404へ変わっても、削除された状態は同じです。
操作履歴と解説を振り返れます。
シェル / curl・HTTP/1.1
curl -sS -i http://127.0.0.1:18080/items/penHTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 38
{"name":"pen","qty":1,"color":"blue"}curl -sS -I http://127.0.0.1:18080/items/penHTTP/1.1 200 OK Content-Type: application/json Content-Length: 38
curl -sS -i -X OPTIONS http://127.0.0.1:18080/items/penHTTP/1.1 204 No Content Allow: GET, HEAD, PUT, PATCH, DELETE, OPTIONS Accept-Patch: application/merge-patch+json, application/vnd.lbox.delta+json
curl -sS -i -X PUT -H "Content-Type: application/json" --data-binary '{"name":"pen","qty":2}' http://127.0.0.1:18080/items/penHTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
{"name":"pen","qty":2}curl -sS -i -X PUT -H "Content-Type: application/json" --data-binary '{"name":"pen","qty":2}' http://127.0.0.1:18080/items/pen
curl -sS http://127.0.0.1:18080/items/penHTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
{"name":"pen","qty":2}
{"name":"pen","qty":2}curl -sS -i -X PATCH -H "Content-Type: application/merge-patch+json" --data-binary '{"qty":5}' http://127.0.0.1:18080/items/pen
curl -sS -i -X PATCH -H "Content-Type: application/merge-patch+json" --data-binary '{"qty":5}' http://127.0.0.1:18080/items/penHTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
{"name":"pen","qty":5}
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
{"name":"pen","qty":5}curl -sS -i -X PATCH -H "Content-Type: application/vnd.lbox.delta+json" --data-binary '{"add":1}' http://127.0.0.1:18080/items/pen
curl -sS -i -X PATCH -H "Content-Type: application/vnd.lbox.delta+json" --data-binary '{"add":1}' http://127.0.0.1:18080/items/penHTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
{"name":"pen","qty":6}
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 23
{"name":"pen","qty":7}curl -sS -i -H "Content-Type: application/json" --data-binary '{"item":"pen","qty":1}' http://127.0.0.1:18080/ordersHTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 30
Location: /orders/1
{"id":1,"item":"pen","qty":1}curl -sS -i -H "Content-Type: application/json" --data-binary '{"item":"pen","qty":1}' http://127.0.0.1:18080/orders
curl -sS http://127.0.0.1:18080/ordersHTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 30
Location: /orders/2
{"id":2,"item":"pen","qty":1}
[{"id":1,"item":"pen","qty":1},{"id":2,"item":"pen","qty":1}]curl -sS -i -X DELETE http://127.0.0.1:18080/items/pen
curl -sS -i -X DELETE http://127.0.0.1:18080/items/pen
curl -sS -i http://127.0.0.1:18080/items/penHTTP/1.1 204 No Content
HTTP/1.1 404 Not Found
Content-Type: application/json
Content-Length: 22
{"error":"not found"}
HTTP/1.1 404 Not Found
Content-Type: application/json
Content-Length: 22
{"error":"not found"}curl -sS -i -X PUT -H "Content-Type: application/json" --data-binary '{"name":"pen","qty":2}' http://127.0.0.1:18080/items/penHTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 23
{"name":"pen","qty":2}演習完了