レスポンスの読み方

ステータスコード、レスポンスヘッダー、そしてボディ。

どのリクエストにもレスポンスはちょうど 1 つ返ってきます(接続が切れた場合は別で、それはそれで一種の答えであり、後のレッスンで扱います)。レスポンスはステータス行、ヘッダー、ボディの 3 つからなります。テストを始めたばかりの人はデータのあるボディばかり見がちですが、見逃されるバグの多くは残りの 2 つにあります。

レスポンスの姿

リクエストと同じく、HTTP/1.1 のレスポンスもプレーンテキストです。次は GET /api/health へのレスポンスを、curl の -i オプション(ボディと一緒にヘッダーも表示する)で見たものです。インフラ関係のヘッダーをいくつか省いています。

生のレスポンス
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
x-powered-by: TestingAPIs

{"status":"healthy","uptimeSeconds":3542,"timestamp":"2026-09-30T18:28:12Z"}

1 行目はステータス行で、プロトコルのバージョン、3 桁のステータスコード、短い理由句が並びます。次にヘッダーが 1 行に 1 つずつ続き、空行のあとにボディが来ます。理由句は人間向けのものにすぎません。クライアントやテストは数字を見るべきです。 HTTP/2 では理由句そのものがなくなるので、"OK" という文字列を確かめるテストは、サーバーが更新されると壊れることがあります。

ステータスコードのクラス

ステータスコードの最初の数字がレスポンスのクラスを表します。その数字だけで結果を判断できることもよくあります。

クラス意味誰の責任か
1xx情報。リクエストはまだ処理中。誰でもない。直接目にすることはまれ。
2xx成功。サーバーは頼まれたことをした。誰でもない。
3xxリダイレクト。別の場所を見よ。誰でもない。クライアントが続きをする必要がある。
4xxクライアントエラー。リクエストが間違っていた。クライアント:入力の誤り、権限なし、URL の間違い。
5xxサーバーエラー。リクエストは正しかったかもしれない。サーバー、またはその裏にある何か。

この最後の列こそ、テストでステータスコードがこれほど大事な理由です。4xx は「リクエストを直して」、5xx は「こちら側で 何かが壊れた」という意味です。でたらめを送って 500 が返ってきたら、リクエストが悪かったとしてもそれはバグです。400 で丁寧に入力を拒むべきところで、サーバーが落ちたのですから。こうした 500 を見つけるのは、テストを始めたばかりの人ができることの中でも特に実りの多い仕事です。

よく目にするコード

コード名前典型的な使い方
200OKリクエストが成功し、ボディに結果がある。
201CreatedPOST で新しいレコードが作られた。
204No Content成功し、意図的にボディがない。DELETE の後によくある。
301 / 302Moved Permanently / Foundリソースは Location ヘッダーの URL にある。
304Not Modifiedキャッシュのコピーがまだ最新。キャッシュの回で扱う。
400Bad Requestリクエストの形式が不正か、値が無効。
401Unauthorized認証情報がないか、間違っている。名前に反して「未認証」の意味。
403Forbidden認証情報は正しいが、このユーザーにはそれをする権限がない。
404Not Foundこの URL には何もない、またはこの ID のレコードがない。
405Method Not AllowedURL は存在するが、このメソッドでは使えない。
409Conflictメールアドレスの重複など、リクエストが現在の状態とぶつかる。
422Unprocessable Content構文は正しいが、データがルールに反している。
429Too Many Requestsレート制限に達した。ペースを落とすこと。
500Internal Server Errorサーバーで処理されなかったエラー。
502Bad Gatewayプロキシが裏のサーバーから不正な応答を受け取った。
503Service Unavailableサーバーが過負荷か、メンテナンス中。

このうちいくつかは API によって扱いが分かれます。検証エラーにはすべて 400 を返すチームもあれば、422 を使うチームもあります。削除に対してボディにメッセージを入れて 200 を返すものもあれば、何も入れずに 204 を返すものもあります。どちらも間違いではありません。確かめるべきなのは、API が自分のドキュメントどおりに動き、 エンドポイントどうしで一貫していることです。

ステータスコードを試す

/api/status/{code} はパスに入れたコードで応えます。特定のステータスを受け取ったとき、ツールやクライアントライブラリがどう振る舞うかを見るのに 便利です。いくつか試してみましょう。

GET/api/status/201
GET/api/status/503

curl では、503 はこう見えます。

レスポンス
HTTP/1.1 503 Service Unavailable
Content-Type: application/json; charset=utf-8
retry-after: 5

{
  "status": 503,
  "statusText": "Service Unavailable",
  "category": "server-error",
  "message": "This is a 503 response. Use /api/status/{code} to request any status."
}

retry-after: 5 ヘッダーに注目してください。503 や 429 は、再試行の前に何秒待つべきかをクライアントに伝えることが多く、行儀のよいクライアントはそれを守ります。ステータスだけで なく、ヘッダーがあることも確かめます。

有効な範囲外のコードは拒否されます。/api/status/999 は 400 になり、メッセージは Status code must be between 200 and 599 (1xx cannot be sent as a final response). です。/api/status/abc は空のボディの 404 を返します。abc がそもそもルートに合わないからです。2 つの不正な入力に、2 種類の違う失敗。このパターンはよく見かけます。型の違う値は、 アプリケーションのコードが動く前のルーティングの層で失敗しがちなのです。

リダイレクト

3xx のレスポンスには、たいてい役に立つボディがありません。大事なのは、次にどこへ行くかを示す Location ヘッダーです。

GET /api/status/302 へのレスポンス
HTTP/1.1 302 Found
Content-Type: application/json; charset=utf-8
location: /api/echo

ブラウザやほとんどの HTTP ライブラリはリダイレクトを自動でたどり、最後のレスポンスだけを見せます。便利ですが、リダイレクト そのものをテストしたいときには困ります。curl は -L を付けない限りリダイレクトをたどらないので、こういうときに向いています。

レスポンスヘッダー

レスポンスヘッダーはボディを説明し、クライアントに指示を与えます。商品一覧の 3 ページ目を、1 ページ 2 件で求めてみましょう。

GET/api/Products?limit=2&page=3
レスポンスヘッダー
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
x-page: 3
x-per-page: 2
x-total-count: 20
x-total-pages: 10

Content-Type はボディが UTF-8 の JSON であることを示します。x-page と x-total の 4 つのヘッダーはページネーションの詳細で、商品は 20 件、ページは 10 あり、これは 3 ページ目だとわかります。ボディは商品 2 件の配列にすぎないので、これらのヘッダーを無視するクライアントには続きがあることを知るすべがありません。X- で始まるヘッダーは API が独自に作ったもので、ドキュメントを書くのは HTTP の標準ではなく API 自身です。

時刻を Unix タイムスタンプ(UTC の 1970 年 1 月 1 日からの秒数)で持つヘッダーもあります。レート制限のある API は、 利用枠がいつリセットされるかを x-ratelimit-reset で知らせることがよくあります。信じる前に変換してみてください。リセット時刻が過去だったり何年も先だったりするのは、 ヘッダーを読み飛ばさずに読んだときにだけ見つかるバグです。(タイムスタンプ変換ツールで変換できます。)

Content-Length ヘッダーがあると思うかもしれません。この API はたいてい代わりに Transfer-Encoding: chunked を送ります。これはボディが分割して送られ、全体のサイズが事前に知らされないという意味です。どちらも正しく、レスポンスには どちらか一方だけが付きます。

ボディ

ボディはデータそのものです。商品、一覧、エラーメッセージ、画像などです。Content-Type がその読み方を示します。確認のほとんどはボディに対して行うことになり、詳しくは JSON の読み方 で扱います。その前に、特別なケースを 2 つ見ておきましょう。

ボディがまったくない

204 は、意図的にボディのない成功を意味します。説明するものがないので、Content-Type もありません。

GET/api/formats/no-content
このリクエストはサインインしたメンバー向けのエンドポイントを使います。サインイン無料アカウントを作成
レスポンス
HTTP/1.1 204 No Content
x-powered-by: TestingAPIs

常に response.json() を呼ぶクライアントのコードはここで落ちます。空の文字列は有効な JSON ではないからです。すべてのレスポンスを解析するテスト用のヘルパーを書くなら、空のボディを扱えるようにしておく必要があります。

ヘッダーが嘘をつくとき

ステータス、ヘッダー、ボディはサーバーの別々の部分が作るもので、一致させる仕組みは何もありません。次は HTML だと名乗るレスポンスです。

GET/api/formats/wrong-content-type
このリクエストはサインインしたメンバー向けのエンドポイントを使います。サインイン無料アカウントを作成
レスポンス
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

{"id":1,"name":"Alice Johnson","email":"alice@example.com","roles":["admin","editor"],"active":true,"score":98.5,"manager":null}

ボディはまったく問題のない JSON で、人が読んでも何の問題にも気づかないでしょう。しかしブラウザはそれを Web ページとして表示しようとし、HTTP ライブラリの中には JSON としての解析を拒んだり、文字列のまま返したりするものもあります。このエンドポイントはわざと壊してあります。実際のシステム では、同じバグはたいていプロキシが作ったエラーページや、フレームワークの既定の設定から生まれます。テストでは Content-Type を明示的に確かめましょう。

その逆もあります。ステータスは 200 なのに、ボディに {"success": false} と書いてあるものです。すべてのエラーをこの形で返す API もあります。5xx のレスポンスを数える監視ツールにはこうした失敗が見えず、ステータスコードしか見ないテストにも見えません。API がこうしているなら、テストではすべてのレスポンスのボディを読む必要があります。

どのレスポンスでも確かめること

データのフィールドを 1 つでも見る前に、基本を確認します。数秒で終わり、驚くほど多くのバグが見つかります。

  • ステータスコードが、その状況についてドキュメントが約束しているものと一致している。
  • Content-Type が実際に受け取ったボディと一致している。
  • ドキュメントにあるヘッダー(ページネーション、Location、Retry-After)があり、筋が通っている。
  • ボディが解析でき、期待どおりの形をしている。
  • エラーのレスポンスが、この API のほかのエラーと同じ形式に従っている。

演習

それぞれのリクエストについて、ステータスコード、Content-Type、ボディについての一文を書き留めてください。

  • /api/status/418。このコードがなぜあるのかを調べてみてください。
  • /api/formats/emptyサインインして表示。/api/formats/no-contentサインインして表示 とどう違いますか。一方は 200、もう一方は 204 を返します。クライアントにとって扱いやすいのはどちらだと思いますか。
  • /api/Products?limit=2&page=99。99 ページ目はありません。どんなステータスとボディが返り、ページネーションの ヘッダーは何と言っていますか。自分ならこう設計しますか。
  • /api/status/list。一覧の中から "supported": false と書かれたコードを見つけ、リクエストしてみてください。

練習とその先へ