DNS、HTTP、HTTPSは何をしているのか|Webアプリが動くまでを分解する #2
第1回では、URLを入力してから画面が表示されるまでを、DNS、通信、サーバー処理、ブラウザ描画という一本の流れで追いかけました。
今回は、その中からDNS、HTTP、HTTPSを取り出します。
よくある説明では、DNSは「インターネットの住所録」、HTTPは「通信のルール」、HTTPSは「暗号化されたHTTP」と表現されます。どれも間違いではありません。ただ、この説明だけだと、それぞれがどこまで担当し、どこから先は担当しないのかが見えてきません。
三つの役割を短く分けると、次のようになります。
DNS:どこへ接続するかを調べる
HTTP:何を要求し、何を返したかを表現する
HTTPS:HTTP通信をTLSで保護する
今回は、次のURLへアクセスする場面を使います。
https://app.example.com/articles?sort=new
ブラウザがこのURLを処理するとき、DNS、HTTP、HTTPSは同時に動くわけではありません。それぞれ異なる段階で使われます。
DNSが扱うのはURL全体ではない
DNSはDomain Name Systemの略で、ドメイン名に対応する情報を調べる仕組みです。
今回のURLからDNSが主に扱うのは、次のホスト名です。
app.example.com
一方、次の部分はDNSでは扱いません。
https
/articles
sort=new
httpsは通信方式を示すスキームです。/articlesは要求するリソースのパス、sort=newはサーバーへ渡すクエリパラメーターです。
DNSが「記事一覧画面の場所」を探しているわけではありません。DNSが探しているのは、app.example.comという名前に対応するIPアドレスなどの情報です。
この境界はかなり重要です。
たとえば、次の二つのURLがあったとします。
https://app.example.com/articles
https://app.example.com/users
ホスト名はどちらもapp.example.comなので、基本的なDNSの問い合わせ先は同じです。/articlesと/usersの振り分けは、接続後にWebサーバーやアプリケーションが行います。
ドメイン名は右側から階層になっている
app.example.comは、右側から階層構造になっています。
. ルート
└── com トップレベルドメイン
└── example example.comドメイン
└── app サブドメイン
完全なドメイン名は、厳密には末尾にルートを表すドットを付けて、次のように書けます。
app.example.com.
普段は末尾のドットが省略されています。
DNSの名前空間が木構造として管理されていることは、DNSの基本仕様であるRFC 1034で定義されています。
世界中のドメイン情報を一台のDNSサーバーが管理しているわけではありません。階層ごとに管理を分担し、必要な問い合わせを次のDNSサーバーへ委ねています。
DNS問い合わせは誰が行うのか
ブラウザが自分で世界中のDNSサーバーを順番に回るとは限りません。
一般的には、端末側のスタブリゾルバーが、設定されている再帰リゾルバーへ問い合わせます。再帰リゾルバーは、家庭用ルーター、通信事業者、組織内のDNSサーバー、パブリックDNSサービスなどから提供されます。
再帰リゾルバーに答えが保存されていない場合、概念的には次の順番で問い合わせます。
ルートDNSサーバーへapp.example.comを問い合わせる
ルートDNSサーバーから.comを担当するDNSサーバーを教えてもらう
.comのDNSサーバーからexample.comの権威DNSサーバーを教えてもらう
example.comの権威DNSサーバーへapp.example.comを問い合わせる
対応するIPアドレスなどの情報を受け取る
結果を端末側へ返す
ルートDNSサーバーがapp.example.comのIPアドレスを直接返すわけではありません。「.comについては、このDNSサーバーへ聞いてください」という委任情報を返します。
.com側も同様に、example.comを担当する権威DNSサーバーを案内します。
最後に問い合わせる権威DNSサーバーが、そのゾーンについて正式な回答を持っています。再帰リゾルバーの定義やキャッシュとの関係は、現在のDNS用語を整理したRFC 9499で確認できます。
DNSはIPアドレス以外も返している
DNSは、ドメイン名とIPアドレスだけを保存する単純な表ではありません。
DNSに保存される情報は、リソースレコードという単位で分類されています。代表的なものには次があります。
A:IPv4アドレス
AAAA:IPv6アドレス
CNAME:別の正式なホスト名を示す別名
NS:そのゾーンを担当するDNSサーバー
MX:メールの配送先
TXT:ドメイン認証などに使われる文字列
HTTPS:HTTPS接続に利用できるサービス情報
Webアプリへの接続では、主にAレコードやAAAAレコードからIPアドレスを取得します。
ただし、CNAMEが設定されている場合は、別名として指定されたホスト名をさらに解決します。また、一つのホスト名に複数のIPアドレスを対応させ、接続先を分散する構成もあります。
DNSから返ってきたIPアドレスが、アプリケーションを実行する本体サーバーとは限りません。CDNやロードバランサーなど、利用者に近い入口を指していることもあります。
毎回ルートDNSサーバーまで問い合わせるわけではない
DNSにはキャッシュがあります。
一度取得した回答は、TTLと呼ばれる時間情報に従って、再帰リゾルバー、OS、ブラウザなどに一定期間保存されます。
たとえばAレコードのTTLが300秒なら、その回答を一定の条件下で最大300秒再利用できます。キャッシュが有効な間は、同じ名前を調べるために権威DNSサーバーまで問い合わせる必要がありません。
存在しないドメイン名についても、NXDOMAINなどの否定応答が一定期間キャッシュされる場合があります。これはネガティブキャッシュと呼ばれ、RFC 2308で定義されています。
DNSの設定変更後に「反映まで時間がかかる」と言われる理由も、主にこのキャッシュです。
新しいDNS情報がインターネット全体へ少しずつ配られているというより、それぞれの場所に残っている古いキャッシュが、TTLの経過に応じて入れ替わっていきます。そのため、変更直後は利用者によって新旧どちらの接続先へ向かうかが異なる場合があります。
DNS通信自体が暗号化されているとは限らない
HTTPSのWebサイトへアクセスする場合でも、その前に行われるDNS問い合わせまで自動的に暗号化されるとは限りません。
従来のDNSは、主にUDPまたはTCPの53番ポートを使います。「DNSは必ずUDP」という説明も正確ではなく、応答サイズや通信条件などによってTCPも使用されます。
暗号化されていないDNS通信では、通信経路上から問い合わせたドメイン名を観測される可能性があります。
この問題に対して、DNS問い合わせをHTTPSで運ぶDoH、DNS over HTTPSや、TLSで保護するDoT、DNS over TLSなどがあります。DoHはRFC 8484で定義されています。
ただし、DoHを使えばDNSに関係するすべての通信が完全に見えなくなる、というわけではありません。少なくとも端末からDoHリゾルバーまでの通信は保護されますが、リゾルバー自身は問い合わせ内容を処理する必要があります。
DNSSECという仕組みもありますが、こちらの主目的はDNSデータの出所や改ざんの有無を検証することです。DNS問い合わせを秘密にする仕組みではありません。
暗号化DNSとDNSSECは、似た安全機能に見えて担当が異なります。
HTTPは通信内容の意味を決める
DNSによって接続先が分かった後、ブラウザはHTTPを使って必要なリソースを要求します。
HTTPはHypertext Transfer Protocolの略で、Web上のリソースをやり取りするためのアプリケーション層プロトコルです。
HTTPが定義しているのは、主に次のような内容です。
何をしたいのかを示すメソッド
対象を示すリクエストターゲット
追加情報を伝えるヘッダー
必要に応じて送るメッセージ本文
処理結果を示すステータスコード
返却するデータの種類やキャッシュ方法
HTTPは、通信相手のIPアドレスを探す仕組みではありません。データを暗号化する仕組みでもありません。
データベースを検索する方法も、HTMLを描画する方法も定めていません。
ブラウザとサーバーが「何を要求したのか」「どのような結果になったのか」を共通の形式で表現するための規則です。
HTTPの現在の共通セマンティクスは、RFC 9110で定義されています。
HTTPが扱うのはファイルではなくリソース
HTTPでは、URLによって識別される対象をリソースとして考えます。
次のリクエストを送ったとします。
GET /articles HTTP/1.1
Host: app.example.com
Accept: text/html
これは必ずしも、サーバー内にあるarticlesというHTMLファイルを取得しているわけではありません。
Webサーバーやアプリケーションは、GET /articlesという要求を受け取り、プログラムを実行できます。その中でデータベースを検索し、結果をHTMLへ変換して返すことも可能です。
同じ記事データでも、要求によって異なる表現を返せます。
Accept: text/html
ならHTMLを返し、
Accept: application/json
ならJSONを返す、といった設計もできます。
リソースそのものと、HTTPレスポンスで返される表現は同じものではありません。
HTTPリクエストの中身
HTTP/1.1の形式で表すと、リクエストは次のようになります。
GET /articles?sort=new HTTP/1.1
Host: app.example.com
Accept: text/html
Accept-Language: ja
Accept-Encoding: gzip, br
Cookie: session_id=abc123
最初の行には、メソッド、リクエストターゲット、HTTPバージョンがあります。
GET /articles?sort=new HTTP/1.1
その後ろにヘッダーが続きます。
Hostは対象のホスト、Acceptは受け取りたいデータ形式、Accept-Encodingは対応する圧縮方式を伝えます。ログイン済みの場合は、条件に応じてCookieが付くこともあります。
POSTやPUTなどでは、ヘッダーの後ろにJSONやフォームデータなどの本文を付けられます。
POST /api/articles HTTP/1.1
Host: app.example.com
Content-Type: application/json
{
"title": "HTTPの仕組み"
}
HTTPヘッダーは、リクエストやレスポンスに追加情報を与える仕組みです。HTTP/1.xでは名前: 値という形ですが、HTTP/2以降では:methodや:statusなどの疑似ヘッダーも使用されます。MDNのHTTPヘッダー解説にも違いが整理されています。
HTTPメソッドは単なる処理名ではない
代表的なHTTPメソッドには、次のものがあります。
GET:リソースの表現を取得する
HEAD:本文を受け取らず、GET相当のヘッダーを取得する
POST:サーバーへデータを渡し、処理を依頼する
PUT:対象リソースの状態を作成または置き換える
PATCH:対象リソースの一部を変更する
DELETE:対象リソースとの関連付けを削除する
GETは「取得」、POSTは「登録」と覚えることがありますが、実際の意味はもう少し厳密です。
GETやHEADは、要求したことでサーバー上の状態を変更することを目的としない、安全なメソッドとして定義されています。
PUTやDELETEは、同じリクエストを複数回実行しても、意図した最終状態が同じになる冪等なメソッドです。レスポンスまで毎回同じになるという意味ではありません。
POSTやPATCHには、一般的な冪等性は保証されていません。通信エラー時に無条件で再送すると、同じ登録や決済処理などが複数回実行される可能性があります。
ただし、メソッドの定義を守るかどうかは実装側にも依存します。GETリクエストでデータを削除するようなAPIも技術的には作れてしまいますが、HTTPの意味に反した危険な設計です。
ステータスコードは処理結果の分類
サーバーは、HTTPレスポンスのステータスコードによって処理結果を伝えます。
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: br
Cache-Control: private, max-age=0
<!doctype html>
<html lang="ja">
...
</html>
ステータスコードは、先頭の数字によって五つに分類されます。
1xx:処理中の情報
2xx:成功
3xx:リダイレクトやキャッシュ利用など
4xx:リクエスト側に関係するエラー
5xx:サーバー側に関係するエラー
よく使われるものには、200 OK、201 Created、301 Moved Permanently、400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、500 Internal Server Errorなどがあります。
なお、3xxがすべて別URLへのリダイレクトというわけではありません。304 Not Modifiedは、保存済みのキャッシュを再利用できることを表します。
404 Not Foundが返っているなら、少なくとも何らかのHTTPサーバーからレスポンスを受け取っています。DNSの名前解決に失敗した場合は、HTTP通信まで進まないため、404は返りません。
「HTTPはステートレス」の意味
HTTPはステートレスなプロトコルです。
これは、前のHTTPリクエストの状態が、次のリクエストへ自動的に引き継がれないという意味です。
一回目にログインしたとしても、二回目のリクエストを何の情報も付けずに送れば、HTTPだけでは同じ利用者だと判断できません。
そこで、Cookie、Session、Authorizationヘッダー、アクセストークンなどを使い、リクエストへ識別情報を付加します。
Cookie: session_id=abc123
サーバーはsession_idを手掛かりに、保存しているセッション情報を調べます。
ステートレスだからといって、サーバーがデータを保存できないわけではありません。HTTPの各リクエストが、前のリクエストを暗黙には覚えていないという話です。
また、リクエストごとにTCP接続を必ず作り直すという意味でもありません。HTTPの状態管理と、通信接続の再利用は別の仕組みです。
ログイン状態の維持については、第6回でCookie、Session、JWTを分けて扱います。
HTTP/1.1、HTTP/2、HTTP/3でも意味は共通している
HTTPには複数のバージョンがあります。
HTTP/1.1は、人が比較的読みやすいテキスト形式のメッセージ構文を持っています。現在のHTTP/1.1のメッセージ構文や接続管理は、RFC 9112で定義されています。
HTTP/2では、通信内容をバイナリフレームへ分割し、一つの接続上で複数のリクエストとレスポンスを並行して扱える多重化が導入されました。
HTTP/3は、TCPではなくQUICを使用します。QUICはUDPを基盤としながら、ストリーム単位の信頼性や輻輳制御、TLS 1.3による保護を備えています。RFC 9114でHTTP/3が定義されています。
通信方法は異なりますが、GETやPOST、ステータスコード、ヘッダーといったHTTPの意味は共通しています。
アプリケーション側から見ると同じHTTPリクエストでも、ネットワーク上での運ばれ方はバージョンによって違います。
HTTPSはHTTPをTLSで保護する
HTTPSは、HTTPとは別のリクエスト形式を持つプロトコルではありません。
HTTP通信をTLSによって保護したものです。
HTTPSでは、主に次の三つを実現します。
機密性:通信内容を第三者から読まれにくくする
完全性:通信途中の改ざんを検知する
認証:接続相手が対象のホストとして正当か確認する
一般的なHTTPS接続では、ブラウザとサーバーがTLSハンドシェイクを行います。
ブラウザは、対応するTLSバージョン、暗号方式、鍵交換に必要な情報などを送ります。サーバーは使用する方式を選び、証明書などの情報を返します。
ブラウザは、証明書について主に次のような点を確認します。
アクセス先のホスト名と証明書が一致しているか
証明書が有効期間内か
信頼する認証局まで証明書チェーンを検証できるか
証明書が要求された用途に利用できるか
TLSにおけるサービス名と証明書の照合方法は、RFC 9525で整理されています。
検証と鍵交換が完了すると、双方が共有したセッション鍵を使ってHTTP通信を暗号化します。TLS 1.3の正式な仕様はRFC 8446です。
ここで誤解されやすいのが、HTTPSでは証明書の公開鍵を使って、すべての通信データを直接暗号化しているという理解です。
実際のアプリケーションデータは、高速に処理できる共通鍵暗号を中心に保護されます。公開鍵暗号や電子署名、鍵交換の仕組みは、相手の確認や安全なセッション鍵の確立などに使われます。
HTTPSでもすべてが隠れるわけではない
HTTPSを使うと、HTTPのパス、クエリ、ヘッダー、Cookie、リクエスト本文、レスポンス本文などは、TLS接続上で暗号化されます。
たとえば、次のパスとクエリは通信経路上から簡単には読めなくなります。
/articles?sort=new
ただし、HTTPSは通信に関係するすべての情報を隠す仕組みではありません。
接続先のIPアドレス、通信量、通信した時刻などのメタデータは残ります。DNSが暗号化されていなければ、問い合わせたホスト名を観測される可能性もあります。
TLSのSNIによってホスト名が見える場合もあります。ECH、Encrypted Client Helloに対応した環境ではSNIを含むClientHelloの機密情報を保護できますが、利用状況や接続条件によって異なります。ECHはRFC 9849で定義されています。
また、URLのクエリパラメーターはHTTPSで通信中に保護されても、ブラウザ履歴、サーバーログ、アクセス解析などには残る可能性があります。
https://example.com/reset?token=秘密情報
HTTPSだからURLへ何を書いても安全、というわけではありません。
HTTPSはWebアプリそのものの安全性を保証しない
証明書が正常でHTTPS接続に成功しても、そのWebサイトの内容まで安全だと保証されたわけではありません。
HTTPSが保護するのは、主に通信経路です。
次のような問題はHTTPSだけでは防げません。
サーバー側のSQLインジェクション
ブラウザ上で発生するXSS
弱いパスワードによるアカウント侵害
サーバーやデータベースからの情報漏えい
正規の証明書を取得した偽サイト
アプリケーションの権限確認漏れ
証明書は、接続先のホスト名と暗号鍵の関係などを検証するためのものです。「このWebサービスは善良である」と認定する仕組みではありません。
CDNやリバースプロキシでTLSを終端する構成では、ブラウザからCDNまではHTTPSでも、CDNからオリジンサーバーまでの保護は別途構成する必要があります。
画面に鍵マークが出ていることと、Webアプリ全体に脆弱性がないことは別の話です。
HTTPからHTTPSへのリダイレクトには一度目の通信がある
次のURLへアクセスしたとします。
http://example.com
サーバーがHTTPSへ転送する場合、一般的にはHTTPでリクエストを受けた後、次のようなレスポンスを返します。
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
ブラウザはこのレスポンスを受け取り、改めてHTTPSで接続します。
つまり、サーバーによるリダイレクトだけでは、最初のHTTP通信が暗号化されません。
そこで使われるのがHSTS、HTTP Strict Transport Securityです。
サーバーがStrict-Transport-Securityヘッダーを返すと、ブラウザは今後そのホストへHTTPでアクセスしようとした場合、サーバーへ送信する前にHTTPSへ変換します。
Strict-Transport-Security: max-age=31536000; includeSubDomains
ただし、通常のHSTSは一度HTTPSで正常に接続し、ヘッダーを受け取る必要があります。最初のアクセスから強制するため、主要ブラウザへあらかじめ登録するHSTS Preloadという仕組みもあります。
三つの処理をもう一度つなげる
今回のURLへアクセスする流れを、DNS、HTTP、HTTPSの境界が見える形で並べると次のようになります。
https://app.example.com/articles?sort=new
ブラウザがURLからapp.example.comを取り出す
DNSでapp.example.comに対応する接続情報を調べる
取得したIPアドレスへTCPまたはQUICで接続する
HTTPSなのでTLSによる認証と鍵交換を行う
TLSで保護された接続上にHTTPリクエストを送る
HTTPレスポンスとしてステータスコード、ヘッダー、本文を受け取る
ブラウザが受け取ったHTMLなどを処理する
DNSは接続先を調べますが、記事一覧を返しません。
HTTPは記事一覧を要求しますが、接続先のIPアドレスを探しません。
HTTPSはHTTP通信を保護しますが、Webアプリの処理内容までは正しくしてくれません。
この境界が分かると、エラーが起きた場所も切り分けやすくなります。
名前解決エラー:DNSの段階で停止している
証明書エラー:TLS接続の確立に失敗している
404 Not Found:HTTP通信は成立し、対象がないと返されている
500 Internal Server Error:サーバー側の処理で問題が起きている
502 Bad Gateway:プロキシなどが上流サーバーから正常な応答を得られていない
504 Gateway Timeout:上流サーバーからの応答を待ち切れなかった
同じ「画面が開かない」でも、止まっている場所はまったく違います。
次回は、通信によって届いたHTML、CSS、JavaScriptをブラウザがどう処理するのかを扱います。
HTMLを受け取っただけでは、まだ画面にはなりません。DOM、CSSOM、レンダーツリー、レイアウト、ペイント、JavaScriptの実行まで、文字列が画面上のピクセルへ変わる流れを分解します。
