本文へ移動

ホーム / SRE / 第3部 コンピュータとネットワークの基礎

第3部 コンピュータとネットワークの基礎・第6章

Web技術(HTTP・URI・REST)

★★☆ 重要19トピック・面接 15問

『Webを支える技術』の構成(Web概論・URI・HTTP・ハイパーメディアフォーマット・Webサービスの設計)に沿って、SREとして押さえるべきWebの仕組みをまとめます。本の出版後に広まったHTTP/2・3、OAuth、セキュリティヘッダ、GraphQL・gRPCなども合わせて扱います。

ひとことで

Webは「URIで名前をつけたリソースを、HTTPでやりとりし、ハイパーリンクで結ぶ」巨大な分散システム。この3つの約束事がWebを大規模でも動かしている。

Webを支える中心の技術は3つです。URIはリソース(情報やモノ)に世界中で一意な名前をつけ、HTTPはそのリソースを取得・操作するための決まった手順を定め、HTMLなどのハイパーメディアはリソース同士をリンクで結びます。どのサーバーもどのクライアントも同じ約束事に従うので、間にプロキシやCDNを挟んだり、サーバーを増やしたりしても全体が動き続けます。

山本陽平『Webを支える技術』は、この3つとRESTを、仕様と設計の両面から解説した定番の本です。おおまかな構成と、SREとして特に押さえたい点を対応させると次のとおりです。

本の構成(おおまかな流れ)主な内容SREとして押さえたい点このノートの該当箇所
第1部 Web概論Webとは何か、歴史、RESTステートレスとキャッシュが、スケールアウトとCDNの土台になっているREST
第2部 URIURIの仕様、良いURIの設計URIの部品とオリジン、変わらないURI、URIを変えるときのリダイレクトURI
第3部 HTTPHTTPの基本、メソッド、ステータスコード、ヘッダ冪等性とリトライ、5xxの見分け方、キャッシュ・認証・中継のヘッダHTTPの基本〜条件付きリクエスト
第4部 ハイパーメディアフォーマットHTML、microformats、Atom、JSONAPIの多くはJSON。Content-Typeとエラー本文の形をそろえるWeb APIの設計
第5部 Webサービスの設計読み取り専用・書き込み可能なサービスの設計、リソースの設計リソースとURIの切り方、エラーの設計、一覧の取得方法Web APIの設計

本が書かれた後に広く使われるようになったもの(HTTP/2・HTTP/3、OAuth 2.0とOpenID Connect、JWT、GraphQLとgRPC、WebSocket、CORS、セキュリティヘッダなど)も、SREの実務では欠かせないので、この章で合わせて扱います。

面接ではこう答える

Webは、URIでリソースに名前をつけ、HTTPで取得・操作し、ハイパーリンクで結ぶ分散システムです。全員が同じ約束事に従うので、プロキシやCDNを間に挟んだり、サーバーを増やしたりしても動き続けます。その設計思想をまとめたのがRESTです。

ひとことで

RESTは、Webがうまく動いている理由を「制約」の組み合わせとして整理した設計の考え方。リソースをURIで指し、決まったメソッドで操作する。

RESTは、HTTPの仕様策定に関わったRoy Fieldingが博士論文で示したアーキテクチャスタイル(設計の型)です。中心にあるのはリソースという考え方で、「ユーザー42」「今日の天気」のような、名前をつけて指し示す価値のある情報をすべてリソースとみなし、URIで名前をつけます。クライアントがやりとりするのはリソースそのものではなく、その時点の状態をJSONやHTMLで表した表現です。

RESTは複数の制約の組み合わせです。中でもSREにとって重要なのはステートレスとキャッシュです。ステートレスとは、サーバーがクライアントの状態(何ページ目を見ているかなど)を覚えず、リクエストに必要な情報を毎回すべて含めることです。どのサーバーでも同じように処理できるので、ロードバランサの後ろに台数を増やすだけでスケールできます。その代わり、毎回送る情報が増える、という欠点があります。

統一インターフェースの4つの要素意味
リソースの識別すべてのリソースをURIで指す
表現による操作JSONなどの表現を送り合ってリソースを操作する
自己記述的メッセージメソッド・ヘッダー・Content-Typeなど、メッセージだけで意味が分かる
ハイパーメディア(HATEOAS)レスポンスに次に行けるリンクを含め、アプリの状態遷移をリンクでたどる
成熟度(リチャードソンの成熟度モデル)状態例
レベル01つのURIにPOSTで何でも送るPOST /api に「操作名」を入れる
レベル1リソースごとにURIを分ける/users/42、/orders/7
レベル2HTTPメソッドとステータスコードを正しく使うGETで取得、DELETEで削除、404・409を返す
レベル3レスポンスにリンクを含める次のページや関連リソースのURIを返す

実務の「REST API」の多くはレベル2です。SREの観点では、レベル2まで守られていると、ステータスコードで監視でき、GETはキャッシュやリトライが安全にでき、LBやWAFがメソッドを見て制御できる、という恩恵があります。

RESTは、Webを大規模でも動かすための「制約」の組み合わせクライアント/サーバUIとデータの処理を分け、別々に進化できるSREにとっての効き目画面とAPIを別々に開発・デプロイできるステートレス必要な情報を毎回すべて送るSREにとっての効き目どのサーバーでも処理でき、水平に増やせるキャッシュキャッシュしてよいかをレスポンスに書くSREにとっての効き目CDN・ブラウザで負荷と遅延を減らす統一インターフェースURIと決まったメソッドだけで操作するSREにとっての効き目プロキシやLBが中身を理解して中継できる階層化システム間にプロキシやLBを挟めるSREにとっての効き目CDN・WAF・LBを後から追加できるコードオンデマンド(任意)サーバーからJSなどを送って実行するSREにとっての効き目ブラウザの機能を拡張できる特に「ステートレス」と「キャッシュ」は、スケールアウトとCDNが効く理由そのもの
RESTを構成する制約

セッションをサーバーのメモリに持つと、ステートレスが崩れてスケールしにくくなる。

面接ではこう答える

RESTは、リソースをURIで識別し、決まったHTTPメソッドで表現をやりとりする設計の考え方で、クライアント/サーバ、ステートレス、キャッシュ、統一インターフェース、階層化システムといった制約からなります。特にステートレスだからサーバーを水平に増やせ、キャッシュ可能だからCDNで負荷を減らせます。

6.3URIの仕組みと設計

用語★★☆ 重要
ひとことで

URIはリソースの名前。部品ごとに役割があり、良いURIは「短く、意味が分かり、変わらない」。

URI(Uniform Resource Identifier)は、リソースを一意に識別する名前です。よく聞くURLは、URIのうち「どこにあるか(場所)」を表すもので、Webで使うURIはほぼURLと考えて差し支えありません。部品は、スキーム(https)、ホスト(api.example.com)、ポート、パス(/v1/users/42)、クエリ(?fields=name)、フラグメント(#top)です。フラグメントはブラウザの中だけで使われ、サーバーには送られません。URIに使えない文字(日本語、空白、一部の記号)はパーセントエンコード(例:空白は%20)して送ります。

良いURIの指針良い例避けたい例
リソースを名詞で表す(動詞を入れない)/users/42/getUser?id=42
実装の都合を見せない/articles/123/cgi-bin/article.php?id=123
小文字・ハイフンで統一/order-items/Order_Items
階層は素直に/users/42/orders/orders-of-user/42
絞り込み・並び替えはクエリで/orders?status=paid&sort=-created_at/orders/paid/sorted-by-date
変わらない一度公開したURIは使い続けるリニューアルのたびにURIが変わる

「クールなURIは変わらない」という有名な言葉のとおり、URIは外部からリンクされ、ブックマークされ、検索エンジンに登録されるので、一度公開したら変えないのが原則です。どうしても変えるなら、古いURIから新しいURIへ301(恒久的な移動)でリダイレクトします。APIのバージョンをURIに入れる(/v1/)か、ヘッダーで指定するかは流派がありますが、URIに入れる方式はルーティングやログの集計がしやすく、運用面で扱いやすいです。

SREとしての注意点:クエリ文字列にトークンや個人情報を入れると、アクセスログ、CDNのログ、ブラウザの履歴、Refererヘッダーに残ります。秘密の値はヘッダーか本文で送ります。また、クエリ文字列の順番や不要なパラメータがCDNのキャッシュキーに入ると、ヒット率が下がります。

URIは部品ごとに役割が決まっているhttpsスキーム://user@ユーザー情報api.example.comホスト:443ポート/v1/users/42パス?fields=nameクエリ#topフラグメント・スキーム〜ポートまでがオリジン。パスは「どのリソースか」、クエリは「絞り込みや並び替えなどの条件」・フラグメント(#以降)はブラウザの中だけで使われ、サーバーには送られない・ユーザー情報を書く形式は、パスワードが漏れるので使わない・日本語や記号はパーセントエンコード(例:スペースは %20)して送る
URIの構造

スキーム・ホスト・ポートまでがオリジン。CORSやCookieの範囲を考えるときの基本単位。

面接ではこう答える

URIはリソースの名前で、スキーム、ホスト、ポート、パス、クエリ、フラグメントからなります。良いURIはリソースを名詞で表し、実装の都合を見せず、一度公開したら変えません。変える場合は301でリダイレクトします。運用面では、クエリにトークンなどの秘密を入れるとログに残ること、不要なクエリがCDNのキャッシュキーを分散させることに注意します。

ひとことで

HTTPは、クライアントがリクエストを送り、サーバーがレスポンスを返すだけのシンプルなプロトコル。シンプルだからこそ、間に色々な仕組みを挟める。

HTTPのメッセージは、1行目(リクエストならメソッド・パス・バージョン、レスポンスならステータス)、ヘッダー、空行、本文(ボディ)でできています。HTTPはTCP(HTTP/3ではQUIC)の上で動き、HTTPSはその間にTLSを挟んだものです。curl -v を使うと、実際に送られたリクエストとレスポンスのヘッダーを見られるので、障害調査の基本ツールになります。

$ curl -v https://api.example.com/v1/users/42
> GET /v1/users/42 HTTP/2
> Host: api.example.com
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/2 200
< content-type: application/json
< cache-control: private, max-age=0
< x-request-id: 9f1c...
<
{"id":42,"name":"..."}
バージョン特徴SREの観点
HTTP/1.01リクエストごとに接続を張って閉じる接続のコストが大きい
HTTP/1.1持続的接続(Keep-Alive)、Hostヘッダーで1つのIPに複数サイトKeep-Aliveのタイムアウトの不一致が502の原因に
HTTP/2バイナリ化、1本の接続で多重化、ヘッダー圧縮gRPCの土台。LBがHTTP/2に対応しているか確認
HTTP/3UDP上のQUICで動く。パケットロスに強く、接続が速いUDPを通すFWやLBの設定が必要

HTTPはステートレスです。サーバーは前のリクエストを覚えておらず、ログイン状態などはCookieやトークンで毎回送ります。これによりどのサーバーでも処理でき、障害時に別のサーバーへ振り替えやすくなります。

HTTPは「1行目・ヘッダー・空行・本文」というテキストのやりとりリクエストPOST /v1/orders HTTP/1.1Host: api.example.comContent-Type: application/jsonAuthorization: Bearer eyJ...Idempotency-Key: 7f3a...{"item": "book", "qty": 1}レスポンスHTTP/1.1 201 CreatedContent-Type: application/jsonLocation: /v1/orders/123Cache-Control: no-store{"id": 123, "status": "paid"}■ 1行目:メソッド・パス・バージョン/ステータス■ ヘッダー:付加情報■ 本文(ボディ)HTTP/2・3では中身がバイナリになり圧縮されるが、メソッド・ヘッダー・ステータスの意味は同じ
HTTPメッセージの構造

ヘッダーを見れば、キャッシュの扱い、認証の有無、どのプロキシを通ったかが分かる。

HTTP/2は1本の接続で多重化、HTTP/3はTCPの詰まりからも解放HTTP/1.1接続ごとに1つずつ順番に接続を何本も張る(ブラウザは1オリジンあたり6本程度)HTTP/21本の接続で並行して流すTCPのパケットが1つ落ちると全部待つHTTP/3QUIC(UDP)で流れごとに独立落ちた流れだけ待つ・接続確立も速い
HTTPのバージョンと接続の使い方

HTTP/2以降は接続の本数が減るので、LBの接続数の見え方も変わる。

面接ではこう答える

HTTPはリクエストとレスポンスを1往復ずつやりとりするステートレスなプロトコルで、メッセージは1行目、ヘッダー、空行、本文からなります。HTTP/1.1で持続的接続、HTTP/2で1本の接続の多重化、HTTP/3でQUICによるパケットロスへの強さが加わりました。調査ではcurl -vで実際のヘッダーを確認します。

項目HTTP/1.1HTTP/2HTTP/3
トランスポートTCPTCPQUIC(UDP)
多重化なし(同時接続を複数張る)1接続で複数ストリーム1接続で複数ストリーム
Head-of-Line Blockingアプリ層で発生TCP層で発生解消
ヘッダ圧縮なしHPACKQPACK
接続確立TCP+TLS で2〜3往復同左1往復(再接続は0往復も可)

6.6HTTPメソッド

用語★★☆ 重要
ひとことで

メソッドは「リソースに対して何をしたいか」。安全か、冪等か、という性質の違いが、キャッシュとリトライの安全性を決める。

メソッド意味CRUD使いどころ・注意点
GETリソースを取得Read状態を変えてはいけない。キャッシュできる
HEADヘッダーだけ取得Read存在確認やサイズ確認。ヘルスチェックにも
POST作成、またはその他の処理Create作成先のURIはサーバーが決める。201とLocationを返す
PUT指定したURIに置き換え(なければ作成)Update / CreateURIをクライアントが決める。全体を送る
PATCH一部を更新Update変更したい項目だけ送る
DELETE削除Delete2回目は404でもよい(結果は同じ「無い」)
OPTIONS使えるメソッドなどの確認-CORSのプリフライトで使われる

安全(safe)とは、呼んでもリソースの状態を変えないことです。冪等(idempotent)とは、1回呼んでも何回呼んでも、結果として同じ状態になることです。SREにとってこの性質が重要なのは、ネットワークの瞬断やタイムアウトで「成功したか分からない」ときに、冪等なメソッドなら安心してリトライできるからです。LBやプロキシ、クライアントライブラリの中には、冪等なメソッドだけを自動でリトライするものもあります。

POSTは冪等ではないので、タイムアウト後にリトライすると二重注文や二重決済になります。決済や注文のAPIでは、クライアントが一意なIdempotency-Keyヘッダーを付け、サーバーは同じキーの2回目以降に最初の結果を返すようにします。また、GETで状態を変える作り(GET /delete?id=1 など)は、クローラーやプリフェッチ、キャッシュによって意図せず実行されるので避けます。

「安全」なら何回呼んでも変化なし、「冪等」なら何回呼んでも結果が同じメソッド意味安全冪等本文GET取得○○まれHEADヘッダーだけ取得○○なしPOST作成・その他の処理××ありPUT置き換え(なければ作成)×○ありPATCH一部を更新××ありDELETE削除×○任意OPTIONS使える機能の確認(CORS)○○なし冪等なメソッドはタイムアウト時も安全にリトライできる。POSTとPATCHはIdempotency-Keyなどで重複を防ぐ「冪等」は結果の状態が同じという意味。DELETEは2回目に404を返してもよい(状態は「無い」のまま)
HTTPメソッドの安全性と冪等性

冪等かどうかで、リトライしてよいかが決まる。

面接ではこう答える

HTTPメソッドは、GETで取得、POSTで作成、PUTで置き換え、PATCHで部分更新、DELETEで削除のように、リソースに対する操作を表します。GETやHEADは安全で、GET・PUT・DELETEは冪等なので、タイムアウト時にリトライしても結果が変わりません。POSTやPATCHは冪等でないため、決済などではIdempotency-Keyで重複実行を防ぎます。

6.7ステータスコード

用語★★☆ 重要
ひとことで

ステータスコードは「結果の種類」。最初の1桁で成功・リダイレクト・クライアントの誤り・サーバーの誤りが分かり、監視とSLOの土台になる。

コード意味SREの観点
200 OK成功本文にエラーを入れて200を返す作りは、監視で失敗が見えなくなるので避ける
201 Created作成したLocationヘッダーで作成したリソースのURIを返す
202 Accepted受け付けた(処理はこれから)非同期処理。状態を確認するURIを返す
204 No Content成功・本文なし削除や更新の成功
301 / 308恒久的に移動URI変更、HTTPからHTTPSへの移行。308はメソッドを変えない
302 / 307一時的に移動307はメソッドと本文を保つ
303 See Other別のURIをGETで見てPOSTの完了後に結果ページへ
304 Not Modified変わっていない条件付きGETの結果。キャッシュをそのまま使う
400 Bad Requestリクエストが不正クライアントのバグ。急増したら直前のリリースを疑う
401 Unauthorized認証されていないトークン切れ。急増は認証基盤の障害のサイン
403 Forbidden認証済みだが権限がないWAFのブロックでも返る
404 Not Found存在しないデプロイ漏れ、ルーティングの誤り、壊れたリンク
405 Method Not Allowedそのメソッドは使えないOPTIONSが未対応でCORSが失敗するなど
409 Conflict状態が衝突重複登録、同時更新
412 Precondition Failed前提条件(If-Match)が成り立たない楽観ロックの競合
413 Content Too Large本文が大きすぎるLBやプロキシのサイズ上限
422 Unprocessable Content形式は正しいが内容が不正入力チェックのエラー
429 Too Many Requests回数制限を超えたRetry-Afterで待つ時間を返す
500 Internal Server Errorサーバー内部のエラーアプリの例外。ログとトレースで原因を追う
502 / 503 / 504上流の不正応答・利用不可・タイムアウト多くはLBやプロキシが返す。502・503・504の違いの図も参照
見分けたい組み合わせ違い
401 と 403401は「あなたが誰か分からない」、403は「分かったが許可されていない」
404 と 410404は「無い(理由は言わない)」、410は「以前はあったが恒久的に消した」
409 と 412 と 422409は状態の衝突、412は条件付きリクエストの前提が崩れた、422は内容の検証エラー
301 と 302、308 と 307恒久か一時か。301・302はPOSTがGETに変わる場合があり、307・308は変わらない
500 と 503500は予期しない失敗、503は「今は受け付けられない」(メンテナンス・過負荷)。503ならRetry-Afterを付けられる

エラーの本文は、形式をそろえると調査と監視が楽になります。標準としてRFC 9457(Problem Details)があり、type、title、status、detailなどの項目を持つJSONで返します。どのコードを返すかに加えて、「誰が返したか」(アプリか、LBか、CDNか、WAFか)をログで区別できるようにしておくと、障害の切り分けが速くなります。

最初の1桁で「誰の問題か」が分かる。SREが最も見るのは5xx1xx処理中100 Continue101 Switching Protocols2xx成功200 OK201 Created204 No Content206 Partial Content3xxリダイレクト301302303304 Not Modified3073084xxクライアントの誤り4004014034044094224295xxサーバーの誤り5005025035044xxの急増も見る:401は認証基盤の障害、429はレート制限、404はデプロイ漏れのサインのことがある
ステータスコードの分類

SLOでは5xxを失敗として数えるのが基本。4xxは利用者の誤りなので通常は除くが、急増には注意。

面接ではこう答える

ステータスコードは1桁目で、2xxは成功、3xxはリダイレクト、4xxはクライアントの誤り、5xxはサーバーの誤りを表します。SLOでは主に5xxを失敗として数えますが、401や429の急増は認証基盤の障害やレート制限のサインなので監視します。502・503・504は多くの場合LBが返すので、アプリのログに出ないこともあります。エラーの本文はRFC 9457の形式にそろえると扱いやすくなります。

コード意味調べる場所
400リクエスト不正クライアント側、API仕様
401 / 403未認証 / 権限なし認証設定、IAM、WAF
404存在しないパス、ルーティング設定
429レート制限呼び出し元の流量、制限値
500アプリ内部エラーアプリログ、例外
502上流からの不正な応答バックエンドが落ちている、接続が切られた、Keep-Alive のタイムアウト不一致
503サービス利用不可健全なターゲットがない、過負荷、メンテナンス
504上流のタイムアウトバックエンドの処理が遅い、LBのタイムアウト設定

502 の定番原因として、ALB のアイドルタイムアウトよりバックエンドの Keep-Alive タイムアウトが短いと、ALB が閉じられた接続を使って 502 になる、というものがある。

どれもLBが返すが、詰まっている場所が違う502クライアントALBバックエンド不正な応答・接続が切れたバックエンド停止、Keep-Aliveのタイムアウト不一致、プロセスのクラッシュ503クライアントALBバックエンド送り先の健全なターゲットがないターゲットが0台、過負荷、メンテナンス中504クライアントALBバックエンド応答が返ってこない(遅い)処理やDBが遅い、LBのタイムアウトより処理時間が長い
502・503・504の違い

コードを見れば、どこを調べに行くべきかが分かる。

6.9HTTPヘッダ

用語★★☆ 重要
ひとことで

ヘッダーはメッセージの付加情報。中身の形式、キャッシュ、認証、中継、セキュリティなど、運用に必要な情報の多くがここに入る。

ヘッダー方向役割
Hostリクエストどのサイトへのリクエストか。1つのIPで複数サイトを振り分ける
Content-Type両方本文の形式(application/json; charset=utf-8 など)
Content-Length / Transfer-Encoding: chunked両方本文の長さ。長さが事前に分からないときは分割して送る
Accept / Accept-Encoding / Accept-Languageリクエスト受け取れる形式・圧縮方式・言語
Content-Encodingレスポンス圧縮方式(gzip、br)
Authorizationリクエスト認証情報(Bearer トークンなど)
Cookie / Set-Cookie両方状態を保持する小さなデータ
Cache-Control / ETag / Last-Modified / Varyレスポンスキャッシュの可否と期間、変更の確認、キャッシュを分ける条件
Locationレスポンスリダイレクト先、作成したリソースのURI
Retry-Afterレスポンス429や503で、何秒後に再試行してよいか
X-Forwarded-For / X-Forwarded-Protoリクエストプロキシを通る前の送信元IPとプロトコル
traceparentリクエスト分散トレースのIDを次のサービスへ引き継ぐ
X-Request-ID(独自)両方リクエストごとのIDをログに出し、問い合わせ時に追跡する
Cookieの属性意味
SecureHTTPSのときだけ送る
HttpOnlyJavaScriptから読めない(XSSで盗まれにくい)
SameSite=Lax / Strict / None他サイトからのリクエストで送るかどうか(CSRF対策)。Noneにする場合はSecureが必須
Domain / Path送る範囲。Domainを広く取ると全サブドメインに送られる
Max-Age / Expires有効期限
セキュリティヘッダ効果
Strict-Transport-Security(HSTS)以後このサイトには必ずHTTPSで接続させる
Content-Security-Policy(CSP)読み込んでよいスクリプトなどの出どころを限定し、XSSの被害を抑える
X-Content-Type-Options: nosniffContent-Typeを無視した推測をさせない
X-Frame-Options / CSPのframe-ancestors他サイトのiframeに埋め込ませない(クリックジャッキング対策)
Referrer-Policy遷移先に送るReferer(元のURL)を制限する

CORSに関係するヘッダーはCORSの解説、キャッシュの詳しい使い方はHTTPキャッシュの解説で解説しています。独自ヘッダーに「X-」を付ける慣習は、標準化の際に名前を変えにくくなるため、新しく作るものには推奨されていません。

ヘッダーは役割でまとめて覚えると、障害調査で「どこを見るか」が分かる中身の情報Content-TypeContent-LengthContent-EncodingAccept / Accept-EncodingAccept-LanguageTransfer-Encodingキャッシュ・条件Cache-ControlETag / If-None-MatchLast-ModifiedIf-Match(楽観ロック)VaryAge認証・状態AuthorizationWWW-AuthenticateCookie / Set-CookieOriginAccess-Control-Allow-*中継・接続HostX-Forwarded-For / -ProtoViaConnection / Keep-Alivetraceparent制御・案内LocationRetry-AfterRateLimit-*(形式は様々)Idempotency-KeyUser-AgentセキュリティStrict-Transport-SecurityContent-Security-PolicyX-Content-Type-OptionsX-Frame-OptionsReferrer-Policy
HTTPヘッダの分類

curl -v やブラウザの開発者ツールでヘッダーを読む習慣をつけると、調査が速くなる。

面接ではこう答える

ヘッダーには、中身の形式(Content-Type)、キャッシュ(Cache-Control、ETag)、認証(Authorization、Cookie)、中継(Host、X-Forwarded-For、traceparent)、制御(Location、Retry-After)、セキュリティ(HSTS、CSP)などの情報が入ります。Cookieには Secure、HttpOnly、SameSite を付け、HTTPSのサイトにはHSTSを設定します。調査ではcurl -vで実際のヘッダーを確認します。

ひとことで

「変わっていなければ」「この版のままなら」という条件をつけて、転送量を減らしたり、同時更新の衝突を防いだりする。

ヘッダー使い方結果
If-None-Match(ETagを指定)キャッシュの再検証変わっていなければ304(本文なし)
If-Modified-Since更新日時で再検証変わっていなければ304
If-Match(ETagを指定)更新時の楽観ロック他の人が先に更新していたら412
Rangeファイルの一部だけ取得206 Partial Content。大きなファイルの再開ダウンロード

If-Matchを使うと、DBの楽観ロックと同じことをHTTPのレベルで実現できます。GETで受け取ったETagをPUTやPATCHのときにIf-Matchで送り、サーバーは今のETagと違えば412を返します。複数の人が同じ文書や設定を編集する画面で、気づかないうちに他人の変更を上書きする事故を防げます。

コンテンツネゴシエーションは、同じURIのリソースを、クライアントの希望(Accept、Accept-Language、Accept-Encoding)に応じて別の表現で返す仕組みです。たとえば圧縮(gzip、br)や言語の切り替えです。このときレスポンスにVaryヘッダー(例:Vary: Accept-Encoding)を付けないと、CDNが別の表現を取り違えてキャッシュし、文字化けや別言語のページが表示される事故になります。

If-Matchで「読んだときから変わっていなければ更新」を実現する利用者AAPIサーバー利用者BGET /docs/7200・ETag: "v1"GET /docs/7200・ETag: "v1"PUT・If-Match: "v1"200(ETagは "v2" に)PUT・If-Match: "v1"412 Precondition FailedAの古い内容でBの更新を上書きせずに済む(ロストアップデートの防止)
If-Matchによる同時更新の検知

412を受け取ったクライアントは、最新を取り直して利用者に確認させる。

面接ではこう答える

条件付きリクエストでは、If-None-Matchで「変わっていなければ304」を返して転送量を減らし、If-Matchで「読んだときの版のままなら更新」を実現して同時更新の上書きを防ぎます。コンテンツネゴシエーションで同じURIから別の表現を返す場合は、Varyヘッダーを付けてキャッシュが取り違えないようにします。

ひとことで

一度取ったデータを手元に置いて使い回し、速くして奥の負荷を減らす。ただし「古いデータを返す」「他人のデータを返す」危険と隣り合わせ。

Webのリクエストは、ブラウザ、CDN、リバースプロキシ、アプリのキャッシュ(Redisなど)、DBのバッファプールと、何層ものキャッシュを通ります。手前で返せるほど速く、奥の負荷が減ります。HTTPのキャッシュは、主にレスポンスのCache-Controlヘッダーで制御します。

Cache-Control意味
max-age=秒この秒数の間は確認なしでキャッシュを使ってよい
s-maxage=秒CDNなど共有キャッシュ向けの有効期間(max-ageより優先)
no-cache保存してよいが、使う前に毎回オリジンに確認する(「キャッシュしない」ではない)
no-store一切保存しない。個人情報を含むレスポンス向け
privateブラウザだけに保存してよい(CDNは保存しない)
publicCDNなど共有キャッシュにも保存してよい
stale-while-revalidate=秒期限切れでも裏で更新しながら古いものを返してよい

期限が切れたら、ETag(内容の識別子)やLast-Modifiedを使って「前回から変わったか」だけを確認します(If-None-Match)。変わっていなければ304 Not Modifiedが本文なしで返り、転送量を節約できます。

CDNは、URLなどから作るキャッシュキーで「同じリクエストか」を判断します。キャッシュキーに含めない要素(Cookie、Authorizationヘッダー、言語など)でレスポンスが変わるページをキャッシュすると、他人のページが表示される重大事故になります。ログイン後のページや個人情報を含むAPIは、no-storeやprivateにするか、CDNでキャッシュしない設定にします。

キャッシュの事故防ぎ方
他人のマイページが表示された認証が必要なレスポンスにno-store/private。CDNのキャッシュ対象を明示的に限定する
デプロイしたのに古いJSが配信されるファイル名にハッシュを入れて別URLにする(app.3f9a.js)。HTMLは短いキャッシュに
キャッシュ削除後にオリジンが落ちた全削除で一斉にオリジンへ。段階的な削除、事前のウォームアップ、Origin Shield
ヒット率が低くて効果がないクエリ文字列や不要なヘッダーがキャッシュキーに入っている。キーを見直す
リクエストは手前のキャッシュから順に当たり、外れたものだけ奥へ進む手前で返せるほど速く、奥のDBの負荷も減るブラウザCache-Controlで自分の端末に保存CDN世界中のエッジで静的ファイルなどリバースプロキシNginx・Varnishでレスポンスを保存アプリのキャッシュRedisなどに計算結果を保存DBバッファプールによく使うページ
多層キャッシュ

どの層でキャッシュしているかを把握していないと、「どこで古いデータが返っているか」を追えない。

期限内はキャッシュから返し、期限切れ後は「変わったか」だけ確認するブラウザCDNオリジンGET /app.jsGET(キャッシュなし)200・Cache-Control: max-age=3600・ETag: "v1"2001時間以内:ブラウザやCDNのキャッシュから返る(オリジンに届かない)期限切れ後:If-None-Match: "v1"304 Not Modified(本文なし)
HTTPキャッシュとETagによる再検証

max-ageの間はオリジンに届かず、期限切れ後は304で本文を省略できる。

面接ではこう答える

Webには、ブラウザ、CDN、リバースプロキシ、アプリ、DBと多層のキャッシュがあり、HTTPではCache-Controlで期間や保存先を制御します。期限切れ後はETagで変更を確認し、変わっていなければ304で本文を省きます。最も危険なのは、キャッシュキーに含まれない要素で内容が変わるページをキャッシュして他人のデータを返すことなので、認証が必要なレスポンスはno-storeやprivateにします。

別のページで開く →

ネットワーク図を読み込んでいます…

6.12CORSとオリジン

用語★★☆ 重要
ひとことで

ブラウザは別のオリジンからのデータを、JavaScriptに勝手に読ませない。CORSは、サーバーが「このオリジンなら読んでよい」と許可するための仕組み。

オリジンは「スキーム+ホスト+ポート」の組み合わせです。ブラウザには同一オリジンポリシーがあり、あるオリジンのページのJavaScriptが、別のオリジンに送ったリクエストのレスポンスを読むことを禁止しています。これがないと、悪意のあるサイトが、あなたのログイン中のCookieを使って銀行のAPIを呼び、その結果を盗めてしまうからです。

しかし画面(app.example.com)とAPI(api.example.com)を分けるのは普通の構成です。そこでCORS(Cross-Origin Resource Sharing)を使い、APIサーバーがレスポンスヘッダーで「app.example.com からなら読んでよい」と宣言します。ブラウザはそのヘッダーを確認して、許可されていればJavaScriptにレスポンスを渡します。

大事なポイントは、CORSはブラウザが守るルールだということです。curlやサーバー同士の通信には関係ありません。だから「curlでは成功するのにブラウザだけでエラーになる」が典型的な症状です。また、単純なリクエストの場合、サーバーは処理を実行してレスポンスも返していて、ブラウザがJavaScriptに渡さないだけ、ということもあります。CORSはサーバーを守る仕組みではないので、CSRF対策などは別に必要です。

PUTやDELETE、JSONの送信、独自ヘッダーの付与など「単純でない」リクエストの前には、ブラウザが自動でOPTIONSメソッドのプリフライトを送り、許可を確認します。単純なリクエストは、GET・HEAD・POSTで、ヘッダーとContent-Typeが限られたもの(フォーム送信やtext/plain)だけです。

レスポンスヘッダー意味
Access-Control-Allow-Origin読んでよいオリジン。認証情報つきなら「*」は使えず、具体的なオリジンを返す
Access-Control-Allow-Methodsプリフライトで許可するメソッド
Access-Control-Allow-Headersプリフライトで許可するリクエストヘッダー(Authorizationなど)
Access-Control-Allow-CredentialsCookieなどの認証情報つきリクエストを許可するか
Access-Control-Max-Ageプリフライトの結果をキャッシュする秒数
Access-Control-Expose-HeadersJavaScriptから読めるようにするレスポンスヘッダー
よくあるCORSエラー原因と対処
プリフライトが404・401になるOPTIONSメソッドのルーティングや認証をしていない。OPTIONSは認証なしで許可ヘッダーを返す
エラー時だけCORSエラーになる500などのエラーレスポンスにCORSヘッダーが付いていない。エラー時も付ける
Cookieを送ると失敗するAllow-Originが「*」、またはAllow-Credentialsがない。具体的なオリジンを返す
CDN経由だと別オリジンで失敗するCDNが最初のオリジン向けのレスポンスをキャッシュしている。Vary: Origin を付けるか、キャッシュキーにOriginを含める
ローカルでは動くが本番で失敗許可リストに本番のオリジン(https、ポート)が入っていない
オリジン=スキーム+ホスト+ポート。1つでも違えば別オリジンhttps://app.example.com:443/path?q=1スキームホストポートパスはオリジンに含まないhttps://app.example.com から見るとhttps://app.example.com/users同じhttp://app.example.com違う(スキーム)https://api.example.com違う(ホスト)https://app.example.com:8443違う(ポート)
オリジンの比較

httpとhttps、サブドメイン違い、ポート違いはすべて別オリジン。

ブラウザは、別オリジンへ送ってよいかを先に確認する(プリフライト)画面は app.example.com、APIは api.example.com(別オリジン)ブラウザAPIサーバーOPTIONS(Origin・使いたいメソッドとヘッダー)Access-Control-Allow-Origin などで許可を返すブラウザが許可内容を確認。合わなければここでCORSエラー本番のリクエスト(PUT・JSONなど)レスポンス+Access-Control-Allow-Originヘッダーが無い・合わないと、届いていてもJavaScriptに渡さない
プリフライトとCORSの流れ

ブラウザの開発者ツールのネットワークタブで、OPTIONSとレスポンスヘッダーを見ると原因が分かる。

面接ではこう答える

CORSは、ブラウザの同一オリジンポリシーで禁止されている別オリジンからのレスポンスの読み取りを、サーバーがヘッダーで許可する仕組みです。ブラウザが守るルールなのでcurlでは起きません。単純でないリクエストの前にはOPTIONSのプリフライトが飛ぶので、調査ではまずプリフライトのレスポンスとAccess-Control-Allow-Originを確認します。

ひとことで

HTTPはステートレスなので、「誰か」「ログイン中か」は毎回のリクエストで証明する。サーバーが覚えるか(セッション)、トークン自身が証明するか(JWTなど)の2つの型がある。

方式仕組み向き・注意点
Basic認証ユーザー名とパスワードをBase64で毎回送る暗号化ではないのでHTTPS必須。社内ツールの簡易保護くらい
セッション+Cookieサーバーがセッションを保存し、IDだけをCookieで送るすぐ無効化できる。保存先(Redisなど)が必要
Bearerトークン(JWTなど)署名つきのトークンを毎回送り、サーバーは署名を検証保存先が不要でスケールしやすい。期限前の無効化が難しい
APIキー固定の鍵をヘッダーで送るサーバー間の連携向け。漏えい時のローテーション手順を用意
OAuth 2.0利用者が許可した範囲のアクセストークンをアプリに渡す「他のサービスのAPIを使わせる」ための仕組み
OpenID ConnectOAuth 2.0の上で「誰がログインしたか」を伝えるソーシャルログイン、社内SSO
mTLSクライアントも証明書で身元を示すサービス間通信、ゼロトラスト

OAuth 2.0は「認可」(何をしてよいか)の仕組みで、アプリに利用者のパスワードを渡さずに、許可した範囲のトークンだけを渡します。ブラウザやスマホのアプリでは、認可コードフローとPKCEを使うのが現在の推奨です。ログイン(認証)にはOAuth 2.0の上に作られたOpenID Connectを使います。

関連する攻撃対策
XSS(スクリプトの埋め込み)出力のエスケープ、CSP、HttpOnlyのCookie。トークンをlocalStorageに置くと盗まれやすい
CSRF(他サイトから勝手に送らせる)SameSite属性、CSRFトークン、重要な操作はPOST以上に
トークンの漏えい短い有効期限とリフレッシュ、ログやURLにトークンを出さない
セッション固定ログイン時にセッションIDを作り直す
セッションは「サーバーが覚える」、トークンは「トークン自身が証明する」セッション(Cookie)ブラウザsession_id=abcサーバーセッション保存Redisなど・すぐ無効化できる(ログアウト・強制ログアウト)・保存先が必要。ステートレスにするには外部に置くトークン(JWTなど)クライアントBearer eyJ…API署名を検証公開鍵で確認・保存先が不要で、どのサーバーでも検証できる・期限まで有効なので、無効化が難しい(短い期限+更新)Cookieには Secure(HTTPSのみ)・HttpOnly(JSから読めない)・SameSite(他サイトから送らない)を付けるトークンをブラウザのlocalStorageに置くと、XSSで盗まれやすい点に注意
セッションとトークンの比較

すぐ無効化したいならセッション、スケールのしやすさならトークン。組み合わせることも多い。

OAuth 2.0(認可コード+PKCE):パスワードをアプリに渡さずに権限だけ渡す利用者アプリ認可サーバーAPIログイン認可サーバーへ転送ログインして許可認可コードコードを渡すコード+検証値で交換アクセストークンBearer トークンOpenID Connectなら、ログインした人の情報(IDトークン)も受け取れる
OAuth 2.0の認可コードフロー

アプリが受け取るのはパスワードではなく、範囲と期限の決まったトークン。

第11章「セキュリティ」:認証の種類と強化(MFA・SSO・パスキー)

面接ではこう答える

HTTPはステートレスなので、毎回のリクエストで身元を証明します。サーバーにセッションを保存してIDをCookieで送る方式は即時に無効化でき、JWTなどの署名つきトークンは保存先が不要でスケールしやすい反面、期限前の無効化が難しいです。外部サービスの権限を渡すにはOAuth 2.0(認可コード+PKCE)、ログインにはOpenID Connectを使います。CookieにはSecure、HttpOnly、SameSiteを付けます。

6.14Web APIの設計

用語★★☆ 重要
ひとことで

リソースとURIを切り出し、メソッドとステータスコードを正しく使い、一覧・エラー・回数制限・非同期処理の形をそろえる。運用しやすいAPIは、監視しやすく、壊れにくい。

設計の手順は、『Webを支える技術』で紹介されている流れが参考になります。提供するデータを洗い出し、URIで指せるリソースに分け、名前(URI)をつけ、表現の形を決め、リンクで結び、典型的な使われ方とエラーを検討します。

設計項目よく使われる形SREの観点
一覧の取得カーソル方式(?cursor=…&limit=50)、上限つき深いOFFSETは遅い。limitの上限で重いリクエストを防ぐ
絞り込み・並び替え?status=paid&sort=-created_atインデックスで支えられる条件だけを許す
バージョン管理/v1/ をURIに入れる、または専用ヘッダー古いバージョンの利用状況を計測してから廃止する
エラーの形RFC 9457(Problem Details)のJSONエラーの種類を集計・監視しやすい
回数制限429とRetry-After、残り回数のヘッダークライアントに待ち方を伝え、リトライの集中を防ぐ
冪等性作成・決済はIdempotency-Keyタイムアウト後のリトライによる二重実行を防ぐ
時間のかかる処理202 Acceptedと状態確認用のURIリクエストを長時間つなぎっぱなしにしない
Webhook(こちらから通知)署名つき、失敗時はリトライ、受信側は冪等に相手が落ちていても再送できるようキューで送る
データ形式JSON(application/json)大きな一覧はページングで分け、不要な項目は返さない

『Webを支える技術』の第4部では、HTML、microformats、Atom、JSONといったハイパーメディアのフォーマットが紹介されています。今日のAPIはほとんどがJSONですが、「レスポンスに関連リソースや次のページへのリンクを含める」という考え方は、カーソル方式のページングなどに生きています。

リソース指向の設計手順(『Webを支える技術』の流れを参考に)1 データを特定する提供する情報を全部書き出す2 データを分割するURIで指せる単位にまとめる3 URIで名前をつける名詞で、階層を素直に表す4 表現を設計するJSON・HTMLなど返す形を決める5 リンクで結ぶ関連リソースへのリンクやフォーム6 典型的な流れを検討よくある使い方を順に確かめる7 エラーを検討するステータスとエラー本文の形
リソース指向の設計手順

最後の「エラーを検討する」を飛ばすと、障害時に原因の分からないAPIになる。

面接ではこう答える

Web APIは、データをリソースに分けてURIで名前をつけ、メソッドとステータスコードを正しく使うことが基本です。運用の観点では、一覧はカーソル方式と上限で重いリクエストを防ぎ、エラーはRFC 9457の形にそろえ、回数制限は429とRetry-Afterで伝え、作成や決済はIdempotency-Keyで冪等にし、時間のかかる処理は202で受け付けて非同期にします。

ひとことで

静的に配信できるならS3+CDNが最も安く速く壊れにくい。画面をサーバー側で作る必要があるときだけSSRのサーバーを足す。

フロントエンドの置き方は、画面の作り方で決まります。ReactやVueで作ったSPAのように、ビルドするとHTMLとJSのファイルになるなら、S3に置いてCloudFrontで配信するのが基本です。サーバーを持たないので、スケールや障害の心配がほとんどありません。SEOのために最初からHTMLを返したい、利用者ごとに画面を作りたい、という場合はNext.jsなどのSSR(サーバーサイドレンダリング)が必要で、ECS・Lambda・専用のホスティングサービスで動かします。

方式置き場所の例向くもの注意点
静的配信(SPA・SSG)S3+CloudFront、Amplify Hosting管理画面、ドキュメント、多くのWebアプリSPAのルーティング(直接アクセス時の404)をCloudFrontで処理
SSRECS・Lambda・VercelなどSEOが重要な公開ページ、個人ごとの画面サーバーの運用とスケール、キャッシュ設計が必要
ハイブリッド静的ページはCDN、一部だけSSRECサイトなどページごとにキャッシュ方針を決める

構成のポイントは、画面とAPIを同じドメインの下に置いて、CloudFrontでパスごとに振り分けることです(/ はS3、/api/* はALB)。オリジンが同じになるのでCORSの設定が不要になり、Cookieの扱いも単純になります。S3は公開せず、OAC(Origin Access Control)でCloudFrontからだけ読めるようにします。デプロイでは、ファイル名にハッシュを入れたJSや画像は長くキャッシュし、index.htmlは短くするか毎回確認させます。

同じドメインの下でパスごとに振り分けると、CORSも不要でシンプルブラウザexample.comCloudFrontパスで振り分け・キャッシュS3(静的ファイル)/ と /assets/*:HTML・JS・画像ALB → API(ECS など)/api/*:キャッシュしないSSRサーバー(必要なら)Next.jsなどの画面生成静的に出せるならS3+CDNが最も安く速く壊れにくい。SEOや個人ごとの画面生成が必要ならSSRを足すS3は非公開のままOAC(Origin Access Control)でCloudFrontからだけ読ませる
フロントエンドとAPIの配置

同じドメインでパスを分けると、CORS・Cookie・証明書の管理がまとめて楽になる。

面接ではこう答える

画面がビルドで静的なファイルになるなら、S3に置いてCloudFrontで配信するのが最も安く速く、運用もほぼ不要です。SEOや利用者ごとの画面生成が必要な場合だけ、SSRのサーバーをECSやLambdaで動かします。画面とAPIを同じドメインの下に置き、CloudFrontでパスごとに振り分けると、CORSが不要になり構成が単純になります。S3はOACでCloudFrontからだけ読めるようにします。

ひとことで

バンドラーは、たくさんのソースファイルとライブラリを、ブラウザが効率よく読める少数のファイルにまとめる道具。出力の性質を知っていると、配信とキャッシュの設計を間違えない。

ReactやVueのアプリは、数百〜数千のファイルとnpmのライブラリでできています。そのまま配ると読み込みが遅いので、バンドラー(Vite、webpack、esbuild、Rollup、Turbopackなど)がビルド時にまとめます。主な処理は、ファイルを少数にまとめるバンドル、空白や長い名前を削って小さくするminify、使われていないコードを捨てるtree shaking、画面ごとに必要になってから読むコード分割(遅延読み込み)です。

出力は大きく2種類です。名前が毎回同じindex.html(入口)と、中身のハッシュが名前に入ったJS・CSS・画像(assets/index.3f9a1c.js など)です。中身が変われば名前も変わるので、ハッシュつきのファイルは1年などの長いキャッシュにしても、新しい版と混ざりません。逆に、index.htmlを長くキャッシュすると、利用者はいつまでも古い版を読み続けます。

ファイルCache-Control の例理由
index.htmlno-cache(毎回確認)、または max-age=60 程度どの版のJSを読むかを決める入口。古いと新しい版に切り替わらない
assets/*.[hash].js・csspublic, max-age=31536000, immutable名前が変われば別ファイル。長くキャッシュしても安全
ハッシュのない画像やfavicon数時間〜1日名前が同じまま中身が変わる可能性がある
config.json など実行時の設定no-cache環境ごとの設定。ビルドし直さずに変えられるようにする
バンドルで気にすること内容
バンドルの大きさ大きいと初回の表示が遅い。可視化ツール(rollup-plugin-visualizer など)で大きなライブラリを見つける
ソースマップminify前の行に対応づける情報。公開するとコードが丸見えなので、エラー監視ツールにだけアップロードする
圧縮CDNでgzip・Brotliを有効にする。ビルド時に事前圧縮しておく方法もある
ブラウザ対応どのブラウザまで対応するか(browserslist)で、出力の大きさと互換性が変わる
バンドラーは「名前の変わらない入口」と「名前にハッシュが入った中身」を作るソースコード数百のファイルバンドラーVite・webpack・esbuildindex.html名前が毎回同じ。どのJSを読むかが書いてあるCache-Control: no-cache(毎回確認)assets/index.3f9a1c.jsassets/chart.8b21de.js(遅延読み込み)assets/index.77ac02.css中身が変わると名前も変わるmax-age=31536000, immutable(1年)まとめる(バンドル)・小さくする(minify)・使わないコードを捨てる(tree shaking)・必要になってから読む(コード分割)名前にハッシュが入っているので、長くキャッシュしても新しい版と混ざらない
バンドラーの出力とキャッシュ方針

index.htmlは短く、ハッシュつきファイルは長く。これを逆にすると事故になる。

面接ではこう答える

バンドラーは、多数のソースとライブラリを、バンドル・minify・tree shaking・コード分割で少数のファイルにまとめます。出力は名前が毎回同じindex.htmlと、中身のハッシュが名前に入ったJSやCSSで、前者はno-cacheなど短く、後者はimmutableで1年など長くキャッシュします。ソースマップは公開せずエラー監視ツールにだけ送ります。

ひとことで

フロントエンドの障害は「画面が真っ白」「一部の画面だけ動かない」として現れ、サーバーのメトリクスには出ないことが多い。デプロイの順番とキャッシュ、古いファイルの残し方で防ぐ。

フロントエンドのデプロイは、中身(assets)を先に、入口(index.html)を最後に差し替えるのが原則です。逆の順番だと、新しいindex.htmlが、まだアップロードされていないJSを読みに行って画面が壊れます。また、デプロイ前にページを開いたままの利用者は、画面遷移のタイミングで古いJSの分割ファイルを取りに来るので、古いassetsはすぐに消さず、数世代残してから後で掃除します。

症状主な原因対策
ChunkLoadError・画面遷移で動かない古いページが、削除済みの古いJSの分割ファイルを読みに行った古いassetsを残す。読み込み失敗時に再読み込みを促す処理を入れる
デプロイしたのに古い画面のままindex.htmlがCDNやブラウザに長くキャッシュされているindex.htmlをno-cacheに、デプロイ時にCDNのキャッシュを削除
真っ白・「Unexpected token '<'」JSのURLが404になり、SPAの設定でindex.html(HTML)が返ってきた存在しないassetsは404のままにし、SPAのフォールバックはページのパスだけにする
MIMEタイプのエラーでJSが実行されないS3に正しいContent-Typeで置かれていないアップロード時にContent-Typeを設定する(デプロイツールに任せる)
一部の利用者だけ壊れる新しいJSと古いAPI(または逆)の組み合わせAPIの後方互換を保ち、フロントとAPIのデプロイ順を決める
CSPの違反で動かない新しく使い始めた外部スクリプトやドメインがCSPで許可されていないCSPの変更もデプロイの確認項目に入れる
監視するもの方法
JavaScriptのエラーSentry、Datadog RUMなどでエラー率とChunkLoadErrorの件数
実際の表示速度RUMでCore Web Vitals(LCP・INP・CLS)
外形監視合成監視で主要な画面が表示できるかを定期確認
CDNの4xx・5xxCloudFrontのメトリクスとログで、assetsの404の急増

ビルドはCIで1回だけ行い、同じ成果物をステージングから本番へそのまま昇格させます。APIのURLなど環境ごとの値をビルド時に埋め込むと環境ごとに作り直すことになるので、実行時にconfig.jsonなどから読む形にしておくと安全です。切り戻しは、前の版のindex.htmlに戻すだけで済むようにしておきます。

デプロイは「中身を先に、入口を最後に」。古い中身はすぐ消さない1 CIで1回だけビルド成果物をコミットIDで保存(環境ごとに作らない)2 assets/ をアップロード新しいハッシュのファイルを追加(上書きではない)3 index.html を差し替えこれで新しい版に切り替わる4 CDNのキャッシュ削除index.html だけ(/index.html と /)5 監視JSエラー率・ChunkLoadErrorの件数6 古い assets は後で削除数世代は残し、ライフサイクルで掃除
フロントエンドのデプロイ手順

index.htmlを最後に差し替え、古いassetsは残す。この2つでほとんどの事故は防げる。

古いページを開いたままの人が、消された古いJSを取りに行って壊れる利用者のブラウザCDNS310:00 に v1 のページを開いた(index.html は app.v1.js を読む)10:30 に v2 をデプロイし、v1 の assets を削除してしまった画面遷移で chart.v1.js を遅延読み込みchart.v1.js をください404(もう無い)404ChunkLoadError で画面が動かない・真っ白に。対策:古い assets を残す/失敗したら再読み込みを促す
古いJSを消して起きるChunkLoadError

サーバー側のメトリクスは正常なので、RUMやエラー監視がないと気づけない。

面接ではこう答える

フロントエンドは、ハッシュつきのassetsを先にアップロードし、最後にindex.htmlを差し替え、CDNのキャッシュはindex.htmlだけ削除します。古いページを開いたままの利用者が古い分割ファイルを読みに来るので、古いassetsは数世代残し、読み込み失敗時は再読み込みを促します。こうした障害はサーバーのメトリクスに出ないので、RUMやエラー監視でJSのエラー率を見ます。

ひとことで

RESTが基本形。用途に応じてGraphQL、gRPC、WebSocket、SSEを使い分ける。運用面ではLB・キャッシュ・監視との相性が違う。

方式特徴向く用途運用上の注意
REST(HTTP+JSON)リソースとメソッドで操作。広く理解されている一般的な公開API・Webアプリエンドポイントごとに監視でき、GETはCDNでキャッシュしやすい
GraphQL1つのエンドポイントに、欲しい項目をクエリで指定画面ごとに必要なデータが違うフロントすべて200・POSTになりがちで、ステータスやURLで監視しにくい。重いクエリの制限が必要
gRPCHTTP/2上でProtocol Buffersのバイナリを送る。型が厳密マイクロサービス間の通信LBがHTTP/2に対応している必要。接続が長く続くので負荷が偏りやすい
WebSocket1本の接続で双方向にやりとりチャット、共同編集、ゲーム接続が長時間続く。LBのアイドルタイムアウト、デプロイ時の切断と再接続
SSE(Server-Sent Events)HTTPのまま、サーバーから一方向に送り続ける通知、進捗表示、AIの逐次応答プロキシのバッファリングで届かないことがある。タイムアウトの設定
ポーリングクライアントが定期的に問い合わせる更新頻度が低いもの台数×頻度で無駄な負荷が増える
「サーバーから知らせたい」ときの選択肢リクエスト/レスポンスクライアントサーバー基本形。REST・GraphQLサーバーから送れないポーリングクライアントサーバー定期的に聞きに行く簡単だが無駄が多いSSEクライアントサーバーサーバーから一方向に送り続ける(HTTPのまま)WebSocketクライアントサーバー双方向に常時接続LBのタイムアウトに注意
サーバーから知らせる方式の比較

常時接続の方式は、LBのタイムアウトとデプロイ時の再接続を必ず設計に含める。

面接ではこう答える

基本はRESTで、エンドポイントごとの監視やキャッシュがしやすいです。画面ごとに必要なデータが違うならGraphQL、サービス間で型の厳密さと効率を求めるならgRPC、双方向のリアルタイム通信ならWebSocket、サーバーから一方向に流すならSSEを使います。運用面では、GraphQLの監視のしにくさ、gRPCの負荷の偏り、WebSocketやSSEのLBタイムアウトと再接続に注意します。

ひとことで

AIアプリが、外部のツールやデータに共通の手順でつながるためのプロトコル。リモートのMCPサーバーは「AIが使う公開API」として守る。

MCPは、Anthropicが2024年に公開したオープンな仕様で、Claudeなどを使うAIアプリ(ホスト)が、社内のAPIやデータベース、SaaSなどにつながるための共通の差し込み口です。ホストの中のMCPクライアントが、各MCPサーバーとJSON-RPCでやりとりします。サーバーは、AIが呼び出せるツール(操作)、読み取れるリソース(データ)、定型のプロンプトを提供します。

通信方式使いどころ
stdio同じPC上でMCPサーバーを子プロセスとして起動。開発者のローカル用
Streamable HTTPネットワーク越しのリモートMCPサーバー。社内外に提供する場合
リモートMCPサーバーを提供するときの観点内容
認証・認可OAuthで利用者ごとに認証し、その人の権限の範囲でだけ操作させる
最小権限ツールごとに読み取り専用か書き込みかを分け、危険な操作は提供しないか承認を挟む
プロンプトインジェクションツールが返すデータに紛れた指示でAIが誤作動する。外部データは信用せず、重要な操作は人が確認する
回数制限とコストAIは短時間に大量に呼ぶことがある。利用者ごとのレート制限と上限
監査ログ誰が、どのツールを、どんな引数で呼んだかを記録
入力の検証AIが作る引数は想定外の値になりうる。サーバー側で必ず検証
可用性・監視通常のAPIと同じくSLO、レイテンシ、エラー率を監視
MCPは、AIアプリと外部のツール・データをつなぐ共通の差し込み口ホスト(AIアプリ)LLMMCPクライアントサーバーごとに1つMCPサーバー(社内API)Streamable HTTP+OAuthMCPサーバー(ローカル)stdioで同じPC上JSON-RPC提供するもの:ツール(操作)・リソース(データ)・プロンプトリモートのMCPサーバーは「公開API」と同じ扱い:認証・最小権限・回数制限・監査ログ・入力の検証ツールの結果に紛れた指示でAIが誤作動する「プロンプトインジェクション」にも備える(危険な操作は人の承認を挟む)
MCPの構成

MCPサーバーの後ろにある社内システムの権限が、そのままAIの権限になる。

面接ではこう答える

MCPは、AIアプリが外部のツールやデータに共通の手順でつながるためのプロトコルで、ホストの中のMCPクライアントがMCPサーバーとJSON-RPCでやりとりし、サーバーはツール・リソース・プロンプトを提供します。ローカルはstdio、リモートはStreamable HTTPを使います。リモートで提供する場合は公開APIと同じく、OAuthによる認証、ツールごとの最小権限、レート制限、監査ログに加え、プロンプトインジェクションへの対策を設計します。

この章のまとめ

🖨 まとめシート(印刷・PDF)
  • URI・HTTP・ハイパーメディアとRESTの関係を、Webが大規模に動く理由として説明できるようにする
  • メソッドの安全性・冪等性、ステータスコード、主要なヘッダーを、監視やリトライと結びつけて覚える
  • CORS・キャッシュ・プロキシなど、Webの「玄関」で起きる障害の原因と調べ方をつかむ
  • 認証の方式とWeb APIの設計(一覧・エラー・回数制限・冪等性)を、運用しやすさの観点で語れるようにする
  • Webのしくみと『Webを支える技術』の全体像:Webは「URIで名前をつけたリソースを、HTTPでやりとりし、ハイパーリンクで結ぶ」巨大な分散システム。この3つの約束事がWebを大規模でも動かしている。
  • REST(Webのアーキテクチャスタイル):RESTは、Webがうまく動いている理由を「制約」の組み合わせとして整理した設計の考え方。リソースをURIで指し、決まったメソッドで操作する。
  • URIの仕組みと設計:URIはリソースの名前。部品ごとに役割があり、良いURIは「短く、意味が分かり、変わらない」。
  • HTTPの基本(メッセージ・バージョン・ステートレス):HTTPは、クライアントがリクエストを送り、サーバーがレスポンスを返すだけのシンプルなプロトコル。シンプルだからこそ、間に色々な仕組みを挟める。
  • HTTPメソッド:メソッドは「リソースに対して何をしたいか」。安全か、冪等か、という性質の違いが、キャッシュとリトライの安全性を決める。
  • ステータスコード:ステータスコードは「結果の種類」。最初の1桁で成功・リダイレクト・クライアントの誤り・サーバーの誤りが分かり、監視とSLOの土台になる。
  • HTTPヘッダ:ヘッダーはメッセージの付加情報。中身の形式、キャッシュ、認証、中継、セキュリティなど、運用に必要な情報の多くがここに入る。
  • 条件付きリクエストとコンテンツネゴシエーション:「変わっていなければ」「この版のままなら」という条件をつけて、転送量を減らしたり、同時更新の衝突を防いだりする。
  • キャッシュ(HTTPキャッシュと多層キャッシュ):一度取ったデータを手元に置いて使い回し、速くして奥の負荷を減らす。ただし「古いデータを返す」「他人のデータを返す」危険と隣り合わせ。
  • CORSとオリジン:ブラウザは別のオリジンからのデータを、JavaScriptに勝手に読ませない。CORSは、サーバーが「このオリジンなら読んでよい」と許可するための仕組み。
  • 認証と状態管理(Cookie・セッション・トークン・OAuth):HTTPはステートレスなので、「誰か」「ログイン中か」は毎回のリクエストで証明する。サーバーが覚えるか(セッション)、トークン自身が証明するか(JWTなど)の2つの型がある。
  • Web APIの設計:リソースとURIを切り出し、メソッドとステータスコードを正しく使い、一覧・エラー・回数制限・非同期処理の形をそろえる。運用しやすいAPIは、監視しやすく、壊れにくい。
  • フロントエンドをどこに置くか:静的に配信できるならS3+CDNが最も安く速く壊れにくい。画面をサーバー側で作る必要があるときだけSSRのサーバーを足す。
  • JSバンドルとバンドラー:バンドラーは、たくさんのソースファイルとライブラリを、ブラウザが効率よく読める少数のファイルにまとめる道具。出力の性質を知っていると、配信とキャッシュの設計を間違えない。
  • フロントエンドのデプロイとよくある障害:フロントエンドの障害は「画面が真っ白」「一部の画面だけ動かない」として現れ、サーバーのメトリクスには出ないことが多い。デプロイの順番とキャッシュ、古いファイルの残し方で防ぐ。
  • 通信方式の選び方(REST・GraphQL・gRPC・WebSocket・SSE):RESTが基本形。用途に応じてGraphQL、gRPC、WebSocket、SSEを使い分ける。運用面ではLB・キャッシュ・監視との相性が違う。
  • MCP(Model Context Protocol):AIアプリが、外部のツールやデータに共通の手順でつながるためのプロトコル。リモートのMCPサーバーは「AIが使う公開API」として守る。

この章のクイズ

面接ではこう聞かれる

まず答えを見ずに声に出して答え、そのあと開いて比べる。

Q1RESTとは何ですか?

模範回答

Webのアーキテクチャを整理した設計の考え方で、リソースをURIで識別し、GETやPOSTなど決まったHTTPメソッドで、その表現をやりとりします。クライアント/サーバ、ステートレス、キャッシュ、統一インターフェース、階層化システムといった制約からなり、特にステートレスだからサーバーを水平に増やせ、キャッシュ可能だからCDNが効きます。

深掘りされたら

  • ステートレスの欠点は?
  • HATEOASまで実装していますか?

見られているポイント制約とスケールのつながり

Q2GETとPOSTの違いは?PUTとPOSTの違いは?

模範回答

GETは取得で、状態を変えない安全なメソッドなのでキャッシュでき、何度呼んでも問題ありません。POSTは作成などの処理で、冪等ではないのでリトライすると重複する可能性があります。PUTは指定したURIのリソースを丸ごと置き換えるもので冪等です。新しく作るとき、作成先のURIをサーバーが決めるならPOST、クライアントが決めるならPUTを使います。

深掘りされたら

  • PATCHは冪等ですか?

見られているポイント安全性・冪等性の理解

Q3冪等性とは何ですか?SREにとってなぜ重要ですか?

模範回答

同じリクエストを何回送っても、結果として同じ状態になる性質です。タイムアウトやネットワークの瞬断で成功したか分からないとき、冪等ならそのままリトライできます。GET・PUT・DELETEは冪等ですが、POSTは違うので、決済などではIdempotency-Keyを使い、同じキーの2回目以降は最初の結果を返すようにします。

深掘りされたら

  • Idempotency-Keyはどこに、どれくらいの期間保存しますか?

見られているポイントリトライと二重実行の関係

Q4401と403の違いは?

模範回答

401は認証されていない、つまり「あなたが誰か分からない」状態で、トークンがない、期限切れなどです。403は認証はできているが、その操作の権限がない状態です。運用では、401が急増したら認証基盤やトークンの更新処理の障害を、403が急増したらWAFのルール変更や権限設定の変更を疑います。

深掘りされたら

  • リソースの存在自体を隠したい場合は?

見られているポイント意味の違いと監視への応用

Q5SLOの計算で、どのステータスコードを失敗として数えますか?

模範回答

基本は5xxを失敗として数えます。4xxは利用者側の誤りなので通常は除きますが、429(こちらの制限で断った)や、こちらの不具合で起きる400・404などは、内容によって失敗に含めるか検討します。また、エラーを200で返す作りだと失敗が見えなくなるので、ステータスコードを正しく返すことが前提です。LBが返す502・503・504はアプリのログに出ないことがあるので、LBのメトリクスも合わせて数えます。

深掘りされたら

  • タイムアウトでクライアントが諦めたリクエストはどう数えますか?

見られているポイントSLIの定義の具体性

Q6セッションとJWTの違いと、使い分けは?

模範回答

セッションはサーバーがログイン状態を保存し、CookieでIDだけを送る方式で、ログアウトや強制ログアウトですぐに無効化できますが、保存先が必要です。JWTは署名つきのトークン自体に情報が入っていて、サーバーは署名を検証するだけなので保存先が不要でスケールしやすい一方、期限前に無効化するのが難しいです。Webのログインはセッション、サービス間やモバイルのAPIは短い期限のトークンとリフレッシュ、のように使い分けることが多いです。

深掘りされたら

  • JWTを即時に無効化したい場合はどうしますか?

見られているポイントトレードオフの説明

Q7URIを変更するとき、何に気をつけますか?

模範回答

URIは外部からのリンクやブックマーク、検索エンジンに残っているので、原則として変えません。変える場合は古いURIから新しいURIへ301で恒久的にリダイレクトし、POSTを受けるAPIなら308でメソッドを保ちます。アクセスログで古いURIへの流入を監視し、なくなってから整理します。APIなら旧バージョンを並行稼働させ、利用状況を計測して利用者に告知してから廃止します。

深掘りされたら

  • 301と302を間違えるとどうなりますか?

見られているポイント利用者を壊さない移行

Q8WebSocketを導入するとき、インフラ側で気をつけることは?

模範回答

接続が長時間続くので、LBやプロキシのアイドルタイムアウトを延ばすか、定期的にpingを送って切れないようにします。デプロイやスケールインのときに接続が切れるので、クライアント側の自動再接続と、切断を少しずつ行う仕組みが必要です。接続が特定のサーバーに偏りやすく、接続数がCPUよりも先に限界になることがあるので、接続数を監視し、スケールの指標にします。

深掘りされたら

  • SSEとの使い分けは?

見られているポイント長時間接続の運用

Q9502・503・504の違いは?

模範回答

いずれもロードバランサなどの中継役が返すエラーです。502は上流から不正な応答が返った、または接続が切れた場合で、バックエンドの停止やKeep-Aliveのタイムアウト不一致が典型です。503は送り先の健全なサーバーがない、または過負荷の場合です。504は上流からの応答が時間内に返らない場合で、処理やDBの遅さを疑います。

深掘りされたら

  • ALBで断続的に502が出る原因として何を疑いますか?

見られているポイントどこを調べに行くかまで言えるか

Q10ブラウザでだけCORSエラーが出ます。どう調べますか?

模範回答

まずブラウザの開発者ツールで、失敗したリクエストの前にOPTIONSのプリフライトが出ているかと、そのステータスコード、レスポンスのAccess-Control-Allow-Origin・Allow-Methods・Allow-Headersを確認します。プリフライトが404や401ならOPTIONSのルーティングや認証の問題、ヘッダーが無いなら許可設定の漏れです。Cookieを使うならAllow-Originが「*」でないか、エラー時のレスポンスにもヘッダーが付いているか、CDNが別オリジン向けのレスポンスをキャッシュしていないか(Vary: Origin)も確認します。

深掘りされたら

  • curlでは成功するのはなぜですか?
  • CORSを全部「*」で許可してはいけない理由は?

見られているポイントブラウザが守るルールだと理解しているか

Q11CDNのキャッシュで「他人のページが表示された」という報告が来ました。どう対応しますか?

模範回答

個人情報の漏えいなので、まず該当パスのキャッシュを止めて全削除し、影響を止めます。次に、どのレスポンスがキャッシュされたかを調べます。典型的な原因は、Cookieや認証ヘッダーで内容が変わるページなのに、それらをキャッシュキーに含めずにキャッシュしたことです。再発防止として、認証が必要なレスポンスにno-storeやprivateを付け、CDNでキャッシュするパスを明示的な許可リストにします。影響を受けた利用者の範囲を特定し、セキュリティ・法務と報告の要否を判断します。

深掘りされたら

  • 影響範囲はどうやって特定しますか?

見られているポイント影響の封じ込めと原因の理解

Q12フロントエンドとAPIはどう配置しますか?

模範回答

画面がビルドで静的なファイルになるならS3に置き、CloudFrontで配信します。APIはALBの後ろのECSなどで動かし、CloudFrontでパスごとに振り分けて同じドメインの下に置きます。同じオリジンになるのでCORSが不要になり、Cookieや証明書の管理も単純になります。S3はOACでCloudFrontからだけ読めるようにし、ハッシュつきのファイルは長く、index.htmlは短くキャッシュします。SEOや個人ごとの画面生成が必要な場合だけSSRのサーバーを足します。

深掘りされたら

  • SPAで直接URLにアクセスすると404になるのはなぜですか?

見られているポイント同一オリジン化とキャッシュの設計

Q13社内のシステムをMCPサーバーとしてAIから使えるようにするとき、何に気をつけますか?

模範回答

公開APIと同じ扱いで設計します。OAuthで利用者ごとに認証し、その人の権限の範囲でだけ操作させます。ツールは読み取りと書き込みを分け、削除などの危険な操作は提供しないか人の承認を挟みます。AIは短時間に大量に呼ぶことがあるのでレート制限を設け、誰がどのツールをどんな引数で呼んだかを監査ログに残します。ツールが返すデータに紛れた指示でAIが誤作動するプロンプトインジェクションも想定し、AIが作った引数は必ずサーバー側で検証します。

深掘りされたら

  • 社内向けと社外向けで何を変えますか?

見られているポイントAI特有のリスクと従来のAPIの守りの両方

Q14デプロイ直後に「画面が真っ白になる」と報告がありました。何を疑いますか?

模範回答

まずブラウザの開発者ツールのコンソールとネットワークで、JSの読み込みが404になっていないか、HTMLが返ってきて構文エラーになっていないか、ChunkLoadErrorやCSPの違反が出ていないかを確認します。よくある原因は、index.htmlを先に差し替えて新しいJSがまだない、古いassetsを消してしまった、index.htmlが長くキャッシュされて古い版と新しい版が混ざった、S3のContent-Typeが正しくない、などです。サーバーのメトリクスには出ないので、RUMやエラー監視で影響範囲を確認し、前の版のindex.htmlに戻して緩和します。

深掘りされたら

  • 再発防止としてデプロイ手順をどう変えますか?

見られているポイントフロントエンド特有の障害の切り分け

Q15フロントエンドのJS・CSSと index.html のキャッシュ設定はどうしますか?

模範回答

ハッシュがファイル名に入ったJSやCSSは、中身が変われば名前も変わるので、max-age=31536000とimmutableで1年キャッシュします。index.htmlは、どの版のJSを読むかを決める入口なので、no-cacheか短い時間にします。デプロイ時はassetsを先にアップロードし、最後にindex.htmlを差し替え、CDNのキャッシュはindex.htmlだけ削除します。古いassetsはすぐには消しません。

深掘りされたら

  • ハッシュの入っていない画像はどうしますか?

見られているポイントファイルの性質ごとにキャッシュを分けられるか