HTTP/1.1 400 Bad Request

サーバーに「何言ってるかわからない」と言われないために。

400 Bad Request は、サーバーがリクエストを理解できなかったときに返すステータスコードです。このサイトでは、HTTPを「正しいリクエストを書けるようになる」ところまで、順番に学びます。

各章の最後に確認問題があります。正解すると左の目次にチェックが付き、進み具合はこのブラウザに保存されます。最終章は、本物っぽいリクエストを組み立てて 200 OK をもらう実技です。

CHAPTER 01HTTPってなに

HTTP(HyperText Transfer Protocol)は、クライアントとサーバーがやり取りするための約束ごとです。

ブラウザでページを開くとき、裏ではブラウザ(クライアント)がサーバーに「このページをください」というリクエストを送り、サーバーがレスポンスを返しています。HTTPはこの1往復を基本単位にした、とてもシンプルなプロトコルです。

クライアント
サーバー
GET /index.html →
← 200 OK + HTML

3つの大事な性質

  • リクエストとレスポンス: 必ずクライアントから話しかけ、サーバーが答えます。サーバーから勝手に話しかけることは(基本的に)ありません。
  • ステートレス: サーバーは前のリクエストを覚えていません。1回1回が独立しています。ログイン状態などは Cookie などで別に持ち運びます(第7章)。
  • テキストで読める(HTTP/1.1): HTTP/1.1 のメッセージは人間が読める文字列です。HTTP/2 以降はバイナリになりますが、意味(セマンティクス)は同じです。
仕様書の場所今のHTTPの意味は RFC 9110(HTTP Semantics)にまとまっています。キャッシュは RFC 9111、HTTP/1.1 の書式は RFC 9112、HTTP/2 は RFC 9113、HTTP/3 は RFC 9114 です。
HTTPが「ステートレス」とはどういう意味?
ステートレス=状態を持たない。だから Cookie やトークンで毎回「自分は誰か」を伝えます。

CHAPTER 02URLの読み方

リクエストは「どこの、何を」から始まります。それを表すのがURLです(仕様は RFC 3986)。

https://400.pages.dev:443/learn/http?ch=2&lang=ja#quiz
スキームホストポートパスクエリフラグメント
部品役割
スキームhttp か https。どの方式で話すか。
ホスト相手のサーバー名。DNSでIPアドレスに変換されます。
ポート省略すると http は 80、https は 443。
パスサーバーの中の「どれ」。
クエリ? 以降の キー=値。& でつなぎます。
フラグメント# 以降。サーバーには送られません。ページ内の位置などにブラウザが使います。

パーセントエンコーディング

URLに使えない文字(日本語やスペースなど)は % と16進数で表します。たとえば「あ」は UTF-8 で3バイトなので %E3%81%82、スペースは %20 です。変な文字をそのまま入れると、サーバーによっては 400 を返します。

URLのうち、サーバーに送られない部分は?
フラグメントはブラウザの中だけで使われます。サーバー側のログにも残りません。

CHAPTER 03リクエストの中身

HTTP/1.1 のリクエストは、リクエスト行・ヘッダー・空行・ボディの4つでできています。

GET /learn/http?ch=3 HTTP/1.1     ← リクエスト行(メソッド・パス・バージョン)
Host: 400.pages.dev               ← ヘッダー(ここから)
User-Agent: Mozilla/5.0 ...
Accept: text/html
                                  ← 空行(ヘッダーの終わり)
                                  ← ボディ(GETでは普通なし)

改行は本来 CR LF(\r\n)です。HTTP/1.1 では Host ヘッダーが必須で、無いとサーバーは 400 を返すことになっています。1つのIPアドレスで複数のサイトを動かせるのは、このHostのおかげです。

メソッド

メソッド意味安全冪等
GET取得する○○
HEADGETのヘッダーだけ○○
POSTデータを送って処理してもらう××
PUT指定の場所を丸ごと置き換える×○
PATCH一部だけ変更する(RFC 5789)××
DELETE削除する×○
OPTIONS使える機能を聞く(CORSで使う)○○
安全と冪等安全=サーバーの状態を変えない。冪等(べきとう)=何回送っても結果が同じ。PUTで同じ内容を2回送っても結果は同じなので冪等、POSTで2回注文したら2個届くかもしれないので冪等ではありません。通信が切れたとき、冪等なリクエストなら安心して再送できます。
HTTP/1.1 のリクエストで、無いと 400 になる必須ヘッダーは?
RFC 9112 で、Host が無い(または2つ以上ある)HTTP/1.1 リクエストには 400 を返すよう定められています。

CHAPTER 04レスポンスとステータスコード

サーバーの返事は、ステータス行・ヘッダー・空行・ボディ。最初の3桁の数字が結果を表します。

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234

<!doctype html>...
範囲意味ひとこと
1xx情報まだ途中です
2xx成功できました
3xxリダイレクト別の場所へどうぞ
4xxクライアントエラーあなたのリクエストが悪い
5xxサーバーエラーこっちが悪い

よく見るコード(タップで説明)

コードを選んでください。
400番台の使い分け形式がおかしい → 400。誰かわからない → 401。誰かはわかるけどダメ → 403。無い → 404。形式は正しいけど中身の意味が通らない → 422。送りすぎ → 429。
ログインしていない人が会員ページを開いた。返すべきなのは?
名前は Unauthorized ですが、意味は「認証されていない」。ログイン済みで権限が足りないなら 403 です。

CHAPTER 05ヘッダー

ヘッダーはメッセージに付ける付せんです。名前: 値 の形で、名前の大文字小文字は区別しません。

ヘッダー向き役割
Hostリクエスト宛先のホスト名
User-Agentリクエストクライアントのソフト名
Acceptリクエスト欲しい形式(text/html など)
Accept-Languageリクエスト欲しい言語(ja,en;q=0.8)
Authorizationリクエスト認証情報(Bearer ...)
Content-Type両方ボディの形式
Content-Length両方ボディのバイト数
Locationレスポンスリダイレクト先(3xx)
Set-CookieレスポンスCookieを保存させる
Cache-Control両方キャッシュの方針

コンテンツネゴシエーション

同じURLでも、Accept や Accept-Language によってサーバーが返す中身を変えられます。q=0.8 は「優先度0.8」という意味で、1が最優先です。

ヘッダー名 content-type と Content-Type の関係は?
ヘッダー名は大文字小文字を区別しません。HTTP/2 と HTTP/3 では、逆に必ず小文字で送ることになっています。

CHAPTER 06ボディとContent-Type

ボディは本体。何が入っているかを Content-Type(MIMEタイプ)で伝えます。

Content-Type中身
text/html; charset=utf-8HTMLページ
application/jsonJSON(APIでよく使う)
application/x-www-form-urlencodedHTMLフォームの標準。a=1&b=2
multipart/form-dataファイルアップロード付きフォーム
image/png など画像
POST /api/notes HTTP/1.1
Host: 400.pages.dev
Content-Type: application/json
Content-Length: 18

{"text":"hello"}
よくある400の原因JSONを送るのに Content-Type を付け忘れる。JSONの最後に余計なカンマがある。Content-Length が実際の長さと合わない。どれもサーバーから見ると「読めないリクエスト」です。
JSONをPOSTするとき付けるべき Content-Type は?
JSONのMIMEタイプは application/json(RFC 8259)です。

CHAPTER 07Cookieと状態

ステートレスなHTTPで「ログインしたまま」を実現する仕組みが Cookie です(RFC 6265)。

ブラウザ
サーバー
POST /login →
← Set-Cookie: sid=abc123
GET /mypage + Cookie: sid=abc123 →
← 200 OK(あなた専用ページ)

Set-Cookie の属性

属性意味
Expires / Max-Age有効期限。無ければブラウザを閉じると消える
Domain / PathどのURLに送るか
SecureHTTPSのときだけ送る
HttpOnlyJavaScriptから読めなくする(盗まれにくくなる)
SameSite他サイトからのリクエストに付けるか(Strict / Lax / None)
小ネタpages.dev は Public Suffix List に載っているので、400.pages.dev が Domain=pages.dev のCookieを作ることはできません。他人の 705.pages.dev にCookieを押し付けられないようにするためです。
CookieをJavaScriptから読めなくする属性は?
HttpOnly を付けると document.cookie から見えなくなり、XSSで盗まれるリスクが下がります。

CHAPTER 08キャッシュ

同じものを何度も取りに行かないための仕組みです(RFC 9111)。

新鮮さ:Cache-Control

指定意味
max-age=36003600秒は新鮮。サーバーに聞かずに使ってよい
no-cache保存はいいけど、使う前に毎回サーバーに確認して
no-storeそもそも保存しないで
private / publicブラウザだけ / CDNなど共有キャッシュもOK
immutable期限内は絶対に変わらない

no-cache は「キャッシュしない」ではない、というのが一番の落とし穴です。

検証:ETag と 304

// 1回目
← 200 OK
  ETag: "v42"
// 2回目(期限切れ後)
→ GET /style.css
  If-None-Match: "v42"
← 304 Not Modified   (ボディなし。手元のを使って)
「絶対に保存しないで」と伝える指定は?
no-cache と max-age=0 は「保存してもいいけど使う前に確認」。保存自体を禁止するのは no-store です。

CHAPTER 09HTTPSとTLS

HTTPS は、HTTP を TLS という暗号化の層の上で動かしたものです。HTTPそのものの書き方は変わりません。

  • 盗み見防止: 通信内容が暗号化されます。パスやヘッダー、Cookieも見えません。
  • 改ざん防止: 途中で書き換えられたら気付けます。
  • なりすまし防止: サーバー証明書で「本当に 400.pages.dev か」を確認します。
何が見えて、何が見えないか経路上の人には、接続先のIPアドレスと、たいていの場合はホスト名(TLSの SNI)が見えます。パス・クエリ・ヘッダー・ボディは見えません。SNIまで隠す仕組みとして ECH(Encrypted Client Hello)があります。

HSTS

Strict-Transport-Security: max-age=31536000 を返すと、ブラウザは以後そのサイトに http で接続しなくなります。ちなみに .dev ドメインはまるごとHSTSプリロードされているので、400.pages.dev は最初から HTTPS 専用です。

HTTPSでも経路上から見えてしまうことが多いのは?
ホスト名は TLS の握手の最初(ClientHello)に平文で入ることが多いです。ECHが普及すれば隠せます。

CHAPTER 10HTTP/1.1・HTTP/2・HTTP/3

意味は同じまま、運び方が進化してきました。

HTTP/1.1HTTP/2HTTP/3
形式テキストバイナリ(フレーム)バイナリ(フレーム)
下の層TCPTCP + TLSQUIC(UDP、TLS 1.3内蔵)
同時に複数基本1本ずつ1接続で多重化1接続で多重化
ヘッダー圧縮なしHPACKQPACK
仕様RFC 9112RFC 9113RFC 9114

ヘッドオブラインブロッキング

HTTP/1.1 では前のレスポンスが終わるまで次が詰まります。HTTP/2 は1本の接続に複数のストリームを混ぜて解決しましたが、下のTCPでパケットが1つ落ちると全ストリームが止まる問題が残りました。HTTP/3 はTCPをやめてQUICにすることで、落ちたストリームだけが待つようにしました。

HTTP/2 では Host の代わりに :authority、リクエスト行の代わりに :method :path :scheme という疑似ヘッダーを使います。

HTTP/3 が使うトランスポートは?
QUIC(RFC 9000)はUDPの上で動き、暗号化(TLS 1.3)と再送制御を自分で持っています。

CHAPTER 11CORSとオリジン

ブラウザの中のJavaScriptが、別のサイトのAPIを勝手に読めないようにする仕組みです。

オリジン=スキーム+ホスト+ポートの組。https://400.pages.dev と https://705.pages.dev は別オリジンです。ブラウザは、別オリジンへの fetch の結果を、相手サーバーが許可したときだけJavaScriptに渡します(同一オリジンポリシー)。

→ GET /api/data
  Origin: https://400.pages.dev
← 200 OK
  Access-Control-Allow-Origin: https://400.pages.dev

プリフライト

PUT や Content-Type: application/json のような「単純でない」リクエストの前には、ブラウザが自動で OPTIONS を送り「このメソッドとヘッダーで送っていい?」と確認します。

よくある誤解CORSはサーバーを守る仕組みではなく、ユーザーを守る仕組みです。curl やサーバー同士の通信にはCORSは関係ありません。止めているのはブラウザです。
CORSの制限を実際にかけているのは誰?
サーバーは許可のヘッダーを返すだけ。読ませるかどうか決めているのはブラウザです。

CHAPTER 12実技:200 OKをもらえ

このサーバーはとても厳しい。ここまでの知識で、正しいリクエストを組み立ててください。

ここにあるのはトップページだけ。見るだけのつもりで、HTTP/1.1 の決まりは守って、宛先は正しく。それと、このサーバーは礼儀にうるさいです。