プログラマのための IT 教科書
第3部 コンピュータとネットワークの基礎

Web と HTTP

読了目安 45

この章を読むとできるようになること
  • 生の HTTP リクエストを読み書きできる
  • 状態を持たない HTTP でログインが維持される仕組みを説明できる
  • CORS エラーの原因を特定して直せる

Web アプリを作るということは、HTTP でやり取りするものを作るということです。

フロントエンドもバックエンドも、間にあるのは HTTP です。 「API が動かない」「なぜか 401 が返る」「CORS エラーが出た」—— 新人が最初にぶつかる問題のほとんどは、HTTP を知らないと解けません。

この章では、ブラウザの開発者ツールを開いて中身を読めるところまでを目指します。

URL は住所ではなく命令書

まず URL を分解します。何気なく見ているこの文字列には、意味のある部品が詰まっています。

https://api.example.com:443/items/42?sort=price&limit=20#reviews
└─┬─┘  └──────┬───────┘└┬┘└──┬───┘└────────┬─────────┘└──┬──┘
scheme      host       port  path         query        fragment
部品意味
schemeプロトコル。https は暗号化あり、http はなし
hostどのサーバーか。DNS で IP アドレスに変換される
portサーバー上のどのプロセスか。省略時は http:80 / https:443
pathサーバー内のどのリソースか
query絞り込みや並び順などの指定。? から始まり & で繋ぐ
fragmentサーバーには送られない。ブラウザ内でページ内位置を示すだけ
fragment はサーバーに届かない

# 以降はブラウザだけが解釈します。サーバーのログには残りません。

だから「URL にトークンを入れて共有する」設計を fragment でやると、 サーバー側では検証できません。逆に「サーバーに知られたくない情報」を置く用途には使えます。

クエリに秘密情報を入れない

クエリ文字列はアクセスログに残り、ブラウザの履歴にも残り、Referer で他サイトにも漏れます

パスワードや API キーを ?token=xxx のように渡す設計は避けてください。 そういうものはヘッダかリクエストボディで送ります。

リクエストとレスポンス

HTTP は、テキストのやり取りです。ブラウザが送っているのは、実際にはこういう文字列です。

GET /items/42?sort=price HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOi...
User-Agent: Mozilla/5.0 ...
 

サーバーが返すのはこれです。

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-store
Content-Length: 87
 
{"id":42,"name":"ワイヤレスイヤホン","price":8900}

構造は3つです。

  1. 1行目 — リクエストなら「メソッド + パス」、レスポンスなら「ステータスコード」
  2. ヘッダ名前: 値 の並び。付帯情報
  3. 空行のあとにボディ — 実際のデータ

これがすべてです。 GraphQL も gRPC も REST も、この上に載っているだけです。

メソッド

メソッド用途冪等か
GET取得する。副作用があってはいけないはい
POST作成する。何度も送ると増えるいいえ
PUT置き換える。同じ内容を何度送っても結果は同じはい
PATCH一部だけ更新する実装次第
DELETE削除する。2回消しても「無い」状態は同じはい

冪等(べきとう) とは「何度実行しても結果が同じ」という性質です。

なぜこれが重要かというと、通信は失敗するからです。 レスポンスが返ってこなかった時、リトライしていいかどうかは冪等かどうかで決まります。

GET で状態を変えてはいけない

「削除は簡単だから GET でいいや」と GET /items/42/delete のような API を作ると、 ブラウザやクローラが勝手にアクセスして削除します

実際、これで全データが消えた事故は何度も起きています。 状態を変える操作は必ず POST / PUT / DELETE を使ってください。

ステータスコード

全部覚える必要はありません。先頭の数字が意味を持ちます

範囲意味誰の問題か
2xx成功
3xxリダイレクト(別の場所を見て)
4xxクライアントの問題送った側
5xxサーバーの問題受けた側

4xx と 5xx の境界が最も重要です。障害対応で最初に見るのがここで、 「4xx が増えた」ならクライアントの使い方かバリデーション、 「5xx が増えた」ならサーバーが壊れている、と切り分けられます。

覚えておくべきものだけ挙げます。

コード意味よくある原因
200OK
201CreatedPOST でリソースを作成した
204No Content成功したが返す中身がない(DELETE など)
301 / 302移動した301 は恒久、302 は一時
400Bad Requestリクエストの形式がおかしい
401Unauthorized認証されていない(ログインしていない)
403Forbidden認証はされたが権限がない
404Not Foundリソースが無い
409Conflict競合(重複登録など)
429Too Many Requestsレート制限に引っかかった
500Internal Server Errorサーバー側の例外。ログを見る
502 / 503Bad Gateway / Unavailable後ろのサーバーが落ちている・過負荷
504Gateway Timeout後ろのサーバーが時間内に応答しなかった
401 と 403 の違いは頻出
  • 401 = あなたが誰か分からない → ログインしてください
  • 403 = あなたが誰かは分かった。でも権限がない → 管理者に頼んでください

「ログインしているのに 401 が返る」なら、トークンが期限切れか、 そもそも送られていない可能性が高いです。開発者ツールで Authorization ヘッダが付いているか確認してください。

API を呼んだら 500 が返りました。最初に見るべきものは?

ヘッダ

よく見るものだけ挙げます。

ヘッダ意味
Content-Typeボディの形式。application/json など
Authorization認証情報。Bearer トークン の形が多い
Acceptクライアントが受け取れる形式
User-Agentクライアントの種類
Set-Cookieサーバーがブラウザに Cookie を保存させる
Cookieブラウザがサーバーに Cookie を送り返す
Cache-Controlキャッシュの方針
Content-Type の付け忘れ

JSON を送っているのに Content-Type: application/json を付けないと、 サーバーはボディをパースできず 400 を返します。

「curl では動くのにアプリからだと動かない」という時、 まずこのヘッダの有無を疑ってください。

状態を持たない HTTP で、なぜログインが維持されるのか

HTTP はステートレスです。1回のリクエストが終わったら、サーバーはあなたを忘れます。

ではなぜ、ページを移動してもログインしたままなのでしょうか。 毎回「自分が誰か」を証明する情報を送っているからです。

1. ログイン          POST /login  { email, password }
2. サーバーが発行     Set-Cookie: session_id=abc123; HttpOnly; Secure
3. ブラウザが保存
4. 以降のリクエスト   Cookie: session_id=abc123      ← 自動で付く
5. サーバーが照合     abc123 → ユーザーID 42 だな

サーバー側は「セッション ID → ユーザー」の対応表を持っています。 ログアウトはこの対応表から消すだけで、確実に無効化できます。

Cookie の属性意味
HttpOnlyJavaScript から読めない。XSS で盗まれるのを防ぐ
SecureHTTPS の時だけ送る
SameSite別サイトからのリクエストで送るかどうか。CSRF 対策

トークン(JWT)

セッションの代わりに、署名付きのトークンを渡す方式もあります。

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0Mn0.xxxxx

サーバーは対応表を持たず、署名を検証するだけでユーザーを特定できます。 サーバーを増やしやすいので、マイクロサービスでよく使われます。

JWT の中身は誰でも読める

JWT は暗号化ではなく署名です。Base64 をデコードすれば中身は誰でも読めます。 秘密情報を入れてはいけません。

もう1つの弱点は、発行後に無効化しにくいことです。 サーバー側に対応表がないので「このトークンを失効させる」が難しく、 有効期限が切れるまで使えてしまいます。短い期限にするのが基本です。

キャッシュ

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

ヘッダ意味
Cache-Control: no-store保存しない(個人情報など)
Cache-Control: no-cache保存はするが、毎回サーバーに聞く
Cache-Control: max-age=36001時間はそのまま使ってよい
ETag内容のバージョン。変わっていなければ 304 を返せる
「直したのに反映されない」

新人が高確率で踏みます。原因はキャッシュです。

  1. ブラウザのキャッシュ → スーパーリロード(Cmd+Shift+R)で確認
  2. CDN のキャッシュ → パージが必要
  3. アプリ側のキャッシュ → TTL が切れるまで残る

まず「本当に新しいコードが動いているか」を確認してから原因を探してください。 デプロイしたのに古い JS が読まれている、というのは本当によくあります。

CORS — 実務で最も時間が溶けるエラー

独立した節にする価値があります。それくらい遭遇します。

何が起きているか

ブラウザには同一オリジンポリシーというルールがあります。 https://app.example.com で開いているページから https://api.example.com へリクエストすると、ブラウザが結果を渡してくれません

オリジンとは スキーム + ホスト + ポート の3点セットです。1つでも違えば別オリジンです。

https://app.example.com        (基準)
https://api.example.com   → ホストが違う   別オリジン
http://app.example.com    → スキームが違う 別オリジン
https://app.example.com:8080 → ポートが違う 別オリジン

なぜそんな制限があるのか

もし制限がなければ、悪意あるサイトを開いただけで、 そのサイトの JavaScript があなたのログイン中の銀行サイトに勝手にリクエストを送り、 結果を読み取れてしまいます。それを防ぐための仕組みです。

どう解決するか

サーバー側が「このオリジンからは許可する」と応答ヘッダで宣言します。

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
CORS はサーバー側で直すもの

新人が誤解しやすい点です。フロントエンドのコードをいくら直しても CORS は解決しません。 ブラウザがサーバーの応答ヘッダを見て判断しているからです。

「CORS エラーが出ました」と報告する時は、バックエンド側の設定の話だと理解しておくと、 話が早く進みます。

プリフライト

単純ではないリクエストの場合、ブラウザは本番のリクエストの前に OPTIONS を送って 許可を確認します。これがプリフライトです。

プリフライトが飛ぶ条件(代表的なもの):

  • GET HEAD POST 以外のメソッド(PUT DELETE PATCH
  • Content-Type: application/json を使う
  • Authorization などのカスタムヘッダを付ける

つまり普通の JSON API はほぼ全部プリフライトが飛びます

開発者ツールの Network タブで、同じ URL に対して OPTIONSPOST の 2行が出ていれば、それがプリフライトです。

OPTIONS が 401 で落ちる

よくある罠です。プリフライトの OPTIONS リクエストには認証情報が付きません

サーバー側で「全リクエストに認証が必要」という設定をしていると、 OPTIONS が 401 を返し、本番のリクエストが送られる前に失敗します。

OPTIONS は認証の対象外にする必要があります。

フロントエンドから API を呼ぶと CORS エラーが出ました。どう対処しますか。

開発者ツールの Network タブ

実務で毎日使います。読み方を覚えてください。

Cmd+Option+I(Mac)で開き、Network タブを選んで、ページを再読み込みします。

見るところ分かること
Status200 か、4xx か、5xx か
Headers → Request Headers自分が何を送ったか(Authorization は付いているか)
Headers → Response Headersサーバーが何を返したか(CORS ヘッダ、Set-Cookie)
Payload / Request送ったボディの中身
Response返ってきた中身
Timingどこで時間がかかっているか
「APIが動きません」の前に

先輩に聞く前に、この3つを見てください。答えが書いてあることが多いです。

  1. リクエストは飛んでいるか(Network に行が出ているか)
  2. ステータスは何か(4xx なら自分側、5xx ならサーバー側)
  3. 何を送っているか(Request Headers と Payload)

そして聞く時は、この3つを添えてください。第6章の質問テンプレートそのものです。

REST の考え方

「リソース」を URL で表し、「操作」をメソッドで表す設計スタイルです。

悪い例(操作を URL に書いている)
  POST /getItem?id=42
  POST /updateItemName
  POST /deleteItem

良い例(リソース + メソッド)
  GET    /items/42
  PATCH  /items/42
  DELETE /items/42
  • URL は名詞(リソース)、メソッドが動詞(操作)
  • 複数形を使う(/items
  • 階層で関係を表す(/items/42/reviews
厳密さより一貫性

REST の原理主義的な議論は現場ではあまり意味がありません。 大事なのはチーム内で一貫していることです。

既存の API がどういう形をしているかを先に見て、それに合わせてください。 新しい流儀を1人だけ持ち込むと、使う側が混乱します。

実務の落とし穴まとめ

  1. CORS はサーバー側で直す — フロントをいくら直しても解決しない
  2. OPTIONS を認証対象にしない — プリフライトが 401 で落ちる
  3. Content-Type の付け忘れ — JSON なのに 400 が返る
  4. GET で状態を変える — クローラに勝手に実行される
  5. クエリに秘密情報 — ログ・履歴・Referer に残る
  6. キャッシュを疑わない — 「直したのに反映されない」の主犯
  7. 401 と 403 を混同 — 認証と認可は別のもの

まとめ

  • HTTP は「1行目 + ヘッダ + 空行 + ボディ」のテキスト。これがすべて
  • 4xx はクライアント、5xx はサーバー。障害対応の切り分けはここから
  • 401 = 誰か分からない、403 = 権限がない
  • ステートレスな HTTP でログインが続くのは、毎回証明を送っているから
  • Cookie は HttpOnly Secure SameSite を付ける。JWT の中身は誰でも読める
  • CORS はサーバー側の設定。プリフライト(OPTIONS)に認証を要求しない
  • 困ったら開発者ツールの Network タブ。ステータス・リクエストヘッダ・ボディを見る

章末問題

ログイン済みなのに API が 401 を返します。まず確認すべきことは?

決済APIを呼んだが、レスポンスが返る前にタイムアウトしました。そのままリトライしてよいですか。

https://app.example.com のページから https://app.example.com:8443 の API を呼びました。CORS は発生しますか。

次の章では、このリクエストを実際に投げているブラウザの中で 何が起きているのかを見ていきます。

読み終わったら記録しておくと、目次で進み具合が分かります。