HTTP メソッド

GET、POST、PUT、PATCH、DELETE、そしてどれが繰り返しても安全か。

メソッドはすべてのリクエストの最初の単語です。URL のリソースに対して何をしたいかをサーバーに伝えます。同じ URL /api/Books/1サインインして表示 でも、GET なら本を返し、PUT や PATCH なら変更し、DELETE なら削除します。ほとんどの API は 5 つのメソッドでほぼすべてをまかない、ほかのいくつかがときどき登場します。

メソッドの段階になると、ブラウザとテストツールの違いが効いてきます。アドレスバーは GET しか送れません。それ以外には curl、Postman、またはこのページにあるような「送信」ボタンが必要です。共有データを変更する例は curl を使っています。このページの「送信」ボタンで、ほかの読者が見ているレコードを変えてしまわないためです。レッスン 6 のcurl と Postman でリクエストを送るで両方のツールをきちんと準備します。まだ curl がなければ、ここでは curl の例を読むだけにして、そのレッスンの後で実行してください。

日常的に使う 5 つのメソッド

メソッドすること典型的な成功ステータス
GETリソースや一覧を読む。ボディは送らない。200
POST新しいリソースを作る、または操作を起動する。201、ときに 200 や 202
PUT送ったボディでリソースを置き換える。200 または 204
PATCHリソースの一部のフィールドを変更する。200 または 204
DELETEリソースを削除する。200 または 204

この API の Methods エンドポイントは、それぞれ 1 つの動詞を受け付け、受け取ったものを説明します。GET を送ってみましょう。

GET/api/methods/get?x=1

ボディ付きの POST も。

POST/api/methods/post
{"hello":"world"}

これらのエンドポイントは何も保存しません。メソッドが本物のデータに何をするかを見るにはリソースが必要なので、このレッスンの 残りでは Books を使います。

POST は作る

本を 1 冊作ります。

Terminal
curl -i -X POST https://api.testingapis.com/api/Books \
  -H "Content-Type: application/json" \
  -d '{"title":"The Pragmatic Programmer","author":"David Thomas",
       "isbn":"9780135957059","genre":"programming","publishedYear":2019,
       "pages":352,"language":"English","price":42.5,"rating":4.6,
       "available":true,"tags":["career"]}'
この例はサインインしたメンバー向けのエンドポイントを使います。サインイン無料アカウントを作成
レスポンス
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8

{
  "message": "Book created successfully",
  "data": {
    "id": 29,
    "title": "The Pragmatic Programmer",
    "author": "David Thomas",
    "isbn": "9780135957059",
    "genre": "programming",
    "publishedYear": 2019,
    "pages": 352,
    "language": "English",
    "price": 42.5,
    "rating": 4.6,
    "available": true,
    "tags": ["career"]
  }
}

サーバーは 201 Created で応え、ID 29 を割り当てて新しいレコードを返しました。ほかの人も本を作っているので、あなたには別の ID が返るはずです。これがどんな POST でも最初にテストすべきことです。ID を選ぶのはサーバーであり、テストは ID を推測せず、レスポンスから読み取らなければなりません。

多くの API は、201 と一緒に新しいリソースの URL を示す Location ヘッダーも送ります。たとえば Location: /api/Books/29サインインして表示 です。この API は送りません。標準で必須ではありませんが、ドキュメントで約束していれば バグです。

では同じ POST をもう一度実行してください。番号以外はまったく同じ 2 冊目の本が、新しい ID でできます。POST は繰り返しても安全なものではありません。呼ぶたびに新しいものができます。後の表のために覚えておいてください。

PUT は置き換え、PATCH は変更

どちらのメソッドもレコードを更新するので、よく混同されます。違いは、省いたフィールドに表れます。

PATCH

本 29 の価格だけを変えます。

Terminal
curl -X PATCH https://api.testingapis.com/api/Books/29 \
  -H "Content-Type: application/json" \
  -d '{"price":39.99}'
この例はサインインしたメンバー向けのエンドポイントを使います。サインイン無料アカウントを作成
レスポンス
{
  "message": "Book patched successfully",
  "changed": ["price"],
  "ignored": [],
  "data": {
    "id": 29,
    "title": "The Pragmatic Programmer",
    "author": "David Thomas",
    "isbn": "9780135957059",
    "genre": "programming",
    "publishedYear": 2019,
    "pages": 352,
    "language": "English",
    "price": 39.99,
    "rating": 4.6,
    "available": true,
    "tags": ["career"]
  }
}

変わったのは価格だけです。触れなかったものはすべて元の値のままです。この API は変更したフィールドも教えてくれますが、 これは珍しく、テストにはとても便利です。

PUT

今度はフィールドを 3 つだけにして PUT を送ります。

Terminal
curl -X PUT https://api.testingapis.com/api/Books/29 \
  -H "Content-Type: application/json" \
  -d '{"title":"The Pragmatic Programmer","author":"David Thomas","price":39.99}'
この例はサインインしたメンバー向けのエンドポイントを使います。サインイン無料アカウントを作成
レスポンス
{
  "message": "Book updated successfully",
  "data": {
    "id": 29,
    "title": "The Pragmatic Programmer",
    "author": "David Thomas",
    "isbn": "",
    "genre": "",
    "publishedYear": 0,
    "pages": 0,
    "language": "",
    "price": 39.99,
    "rating": 0,
    "available": false,
    "tags": []
  }
}

ISBN、ジャンル、年、ページ数、言語、評価、タグが消えてしまいました。PUT は「これがこのリソースの新しい完全版です」という 意味です。送らなかったフィールドは、空またはゼロの値に置き換えられました。API は PUT の定義どおりに動いただけなのに、クライアントが一部だけのボディを送ったせいで、レコードのほとんどが消えたのです。

これはアプリで実際によく起きるバグの 1 つです。開発者が 1 つのフィールドを更新するために PUT を使い、そのフィールドだけを送ると、レコードのほかのフィールドが静かにすべて消されてしまいます。更新機能をテストするときは、 一部だけのボディを送ってからレコードを GET し、何が残ったかを確かめてください。

API によっては扱いが違います。欠けたフィールドのある PUT を PATCH のように扱うものや、必須フィールドがないとして 400 で拒否するものもあります。最後のものがおそらくいちばん安全です。何をテストするかはドキュメント次第ですが、テストすること 自体は必ずしてください。

DELETE は削除

本 29 を削除し、もう一度削除してみます。

ターミナル
$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"message":"Book deleted successfully"}          (200 OK)

$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"status":404,"error":"Not Found","message":"Book with ID 29 does not exist."}   (404 Not Found)
この例はサインインしたメンバー向けのエンドポイントを使います。サインイン無料アカウントを作成

1 回目はメッセージ付きの 200 を返します。2 回目は、もう削除するものがないので 404 です。削除の後は、同じ ID への GET が 404 を返すことも確かめてください。レコードに削除済みの印を付けるだけで返し続けるシステムもあり、ドキュメントと違えばそれは バグです。

安全なメソッドと冪等なメソッド

HTTP の標準にある 2 つの性質が、クライアントやテストが各メソッドをどう扱ってよいかを決めます。

安全なメソッドはサーバー上の何も変えません。何度でも呼べます。冪等なメソッドは何かを変えることがありますが、2 回呼んでも 1 回呼んだのと同じ効果しかサーバーに与えません。

メソッド安全冪等
GETはいはい
HEADはいはい
OPTIONSはいはい
PUTいいえはい
DELETEいいえはい
POSTいいえいいえ
PATCHいいえ保証されない

PUT が冪等なのは、同じ完全なレコードを 2 回送っても同じレコードが残るからです。DELETE は、2 回目で 404 が返っても冪等です。レスポンスは違っても、サーバーの状態(本 29 は存在しない)はどちらでも同じだからです。冪等性が問題に するのは効果であって、レスポンスではありません。2 回目の POST で 2 冊目の本ができたように、POST は冪等ではありません。PATCH はどちらにもなりえます。価格を 39.99 にするのは冪等ですが、「在庫数に 1 を足す」という patch は冪等ではありません。

これが大事なのは、ネットワークは失敗するものだからです。リクエストがタイムアウトすると、クライアントにはサーバーがそれを 受け取ったかどうかがわかりません。GET や PUT の再試行は無害ですが、POST の再試行ではカードに二重請求が起きかねません。 ブラウザ、プロキシ、HTTP ライブラリは、自分で再試行するかどうかをこのルールで決めています。タイムアウト、リトライ、レート制限では、API が冪等キーで POST を安全に再試行できるようにする方法を紹介します。

テストする側から見ると、これらのルールはテストケースをただでくれます。GET を呼んで、何も変わっていないことを確かめる。PUT を 2 回呼んで、2 回目で何も作られたり重複したりしていないことを確かめる。データを変える GET(実際にあるコードベースもある /api/deleteUser?id=5サインインして表示 のようなもの)は設計上のバグです。クローラー、リンクのプレビュー、ブラウザの先読みは、 どれも断りなく GET リクエストを送るからです。

HEAD と OPTIONS

HEAD はボディのない GET です。サーバーは GET と同じステータスとヘッダーを送り、そこで止まります。クライアントは、 ダウンロードせずに何かが存在するかやファイルの大きさを確かめるのに使います。

curl -I https://api.testingapis.com/api/methods/head
HTTP/1.1 200 OK
x-message: HEAD request received. Responses to HEAD carry headers only.
x-method: HEAD

OPTIONS は、URL で何が許されているかを尋ねます。ブラウザは一部のクロスオリジンリクエストの前にこれを自動で送るので (「プリフライト」)、どのコードも呼んでいなくてもネットワークのログに現れます。CORS の設定に関わる仕事でない限り、直接テストすることはまれでしょう。

OPTIONS/api/methods/options

間違ったメソッドを使う

/api/methods/get は GET しか受け付けません。curl -i -X POST https://api.testingapis.com/api/methods/get で POST を送ってみます。

レスポンス
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: GET

405 Method Not Allowed は、URL は存在するがそのメソッドには対応していないという意味です。allow ヘッダーに対応しているものが並びます。本は /api/Booksサインインして表示 で作るので、1 冊の本への POST は意味がなく、同じように扱われます。

curl -i -X POST https://api.testingapis.com/api/Books/1
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: DELETE, GET, HEAD, PATCH, PUT
この例はサインインしたメンバー向けのエンドポイントを使います。サインイン無料アカウントを作成

すべてのエンドポイントにすべてのメソッドを送るのは、手軽なのに本物の問題が見つかるテストです。探すのは、拒否されるべき なのに拒否されないメソッド(読み取り専用のはずのエンドポイントで動いてしまう DELETE など)と、405 であるべきところの 500 や 404 です。この API は 405 を空のボディで返しますが、ほかのエラーは JSON であることに注意してください。どの 4xx にも JSON のエラーボディを期待するクライアントは、ここでつまずきます。

演習

curl を使って順にやってみてください(または curl のレッスンの後で戻ってきてください)。

  • POST で本を作り、その ID を書き留める。
  • タイトルを PATCH で変える。GET して、タイトルだけが変わったことを確かめる。
  • tags 以外のすべてのフィールドを含むボディで PUT する。タグはどうなりましたか。
  • 同じ PUT をもう一度送る。2 回目で何か変わりましたか。
  • DELETE し、GET し、もう一度 DELETE する。3 つのステータスコードをすべて書き留める。
  • /api/Booksサインインして表示(ID なし)に DELETE を送る。実行する前にステータスコードを予想し、その後 allow ヘッダーを確かめる。

練習とその先へ