ブラウザで WEB ページを表示するまで
ブラウザの URL をアドレスバーに入力する。
- https://www.nifty.com/
Navigation
- DNS サーバからドメインに紐づく IP アドレスを引っ張る
- やってることは
nslookupやhttps://www.nslookup.io/website-to-ip-lookup/ などで確認できる
wsl の Ubuntu の nslookup。ここでは google の Public DNS を指定 - IP アドレスは覚えずらいので DNS が生まれた(https://ja.wikipedia.org/wiki/Domain_Name_Systemより)
- やってることは
- サーバが HTTP リクエストを受け取り、応じた HTTP レスポンスをブラウザに返信する
- アプリケーション層: HTTP
- 例えばこのようなメッセージ本文のやりとり
適当にpythonなどでAIで書ける
from http.server import BaseHTTPRequestHandler, HTTPServer class MyHandler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write(b"<html><body><h1>Hello World!</p></body></html>") def run(server_class=HTTPServer, handler_class=MyHandler, port=8000): server_address = ('', port) httpd = server_class(server_address, handler_class) print(f"Starting httpd server on port {port}...") httpd.serve_forever() if __name__ == "__main__": run()- HTTP のバージョンで異なる
- HTTP/1.0, HTTP/1.1, HTTP/2
- トランスポート層が TCP, つまり 3-way ハンドシェイク
- (復習?)
TCP (Transmission Control Protocol)- 3-way ハンドシェイク でコネクションを確保してから通信
マスタリングTCP/IP 入門編(第6版), p.456
- 3-way ハンドシェイク でコネクションを確保してから通信
- ちゃんとしたサイトなら TLS で HTTPS とする
- (復習?)
- トランスポート層が TCP, つまり 3-way ハンドシェイク
- HTTP/3
- トランスポート層が Google 提案の QUIC と呼ばれる UDP ベースのものなので早い
- (復習?)
UDP (User Datagram Protocol)- コネクションを作らない(コネクションレス)で、輻輳制御や再送せずただ送るトランスポートプロトコル
- ちゃんと通信するには上位層のアプリケーション層が面倒を見る必要がある
他にも色々ボトルネック消すなどあるらしい
- 認証、暗号化
- QUIC自体が認証と暗号化を提供します。これにより、TCPよりも堅牢になります。
- 低遅延のコネクション管理
- TCP上で暗号化したHTTPを使う場合、TCPのコネクション管理(6.4.4項参照)とTLSのハンドシェーク(6.4.2項参照)が別々に必要でした。QUICではこれらを同時に行い、低遅延でコネクションを確立します。
- 多重化
- TCPで扱うのは1コネクション、1つのストリームですが、QUICでは1コネクションで複数のストリームを同時に扱います。これにより、UDPのポート番号を有効に利用でき、NAT(5.6節参照)の負担を減らします。
- 再送処理
- TCPよりもきめ細かくラウンドトリップ時間(6.4.3項参照)を計測し、高精度な再送処理を行います。
- ストリームレベルの再送制御とコネクションレベルのフロー制御
- TCPは1つのコネクションで1つのストリームを扱うので、パケットの喪失が起きると通信が停止します。一方、QUICでは1つのコネクションで複数のストリームを制御します。1つのストリームでパケットの喪失が起きても他のストリームの通信は続きます。これら全体でフロー制御を行います。
- コネクションのマイグレーション
- IPアドレスが変わったときにもコネクションが維持されるようにします(携帯端末が別のNATセグメントに移ったときを含む)。
- (復習?)
HTTP/3 は 34.9% の web サイトで使われている(らしい)
- トランスポート層が Google 提案の QUIC と呼ばれる UDP ベースのものなので早い
- 社内ではあまり HTTP のバージョンは気にしていない
- 例えば AWS ECS で python でコンテナで FastAPI, Django を何も考えずに使うと Uvicorn で標準の HTTP/1.1
- 外向きは HTTP/2 、内向きは HTTP/1.1 など
Response
コネクションを貼るなどした後、 HTTP GET リクエストで対象サーバから HTML ファイルを要求。
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<title>My simple page</title>
<link rel="stylesheet" href="styles.css" />
<script src="myscript.js"></script>
</head>
<body>
<h1 class="heading">My Page</h1>
<p>A paragraph with a <a href="https://example.com/about">link</a></p>
<div>
<img src="my-image.jpg" alt="image description" />
</div>
<script src="another-script.js"></script>
</body>
</html> - TTFB (Time to First Byte)
- 最初のリクエストを送ってから最初のバイトを含むレスポンスが来るまでの時間
- https://developer.mozilla.org/en-US/docs/Glossary/Time_to_first_byte
Parsing
最初のデータのかたまりを受け取ると、ブラウザは構文解析を通して以下を行う。
- DOM ツリーの構築
- CSSOM ツリーの構築
- CSS を解釈
- JavaScript のコンパイル
- JavaScript ファイルをダウンロード、コンパイル
- 基本はブラウザのメインスレッドだが、ウェブワーカーなど別スレッドの例外もある(後述)
- アクセシビリティツリーの構築
レンダリング
DOM と CSSOM をレンダーツリーの形式へと組み合わせる。
深堀したい人はクリティカルレンダリングパスを調べてください: https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Critical_rendering_path
操作可能性
- TTI (Time to Interactive)
- DNS 検索と TCP 接続を始める最初のリクエストからページが操作可能になるまでどのくらい時間がかかったかを示す測定値
ブラウザのデータの格納手段
後続のブラウザ操作で誰の操作だったかを判別するためにセッションなどがある。
セッションは web アプリで良く使われるため、ここではセッション保持に使われる Cookie について触れておく。
- 確認方法
- ブラウザの開発者ツールで確認できる
- Domain (Path)単位でリクエスト時に自動送信される
- Cookie をレスポンスのヘッダーで設定する例
python の例
from http.server import BaseHTTPRequestHandler, HTTPServer from http.cookies import SimpleCookie class MyHandler(BaseHTTPRequestHandler): def do_GET(self): cookies = SimpleCookie() session_id_value = "test-session-value" # Set the 'session_id' cookie with a new expiration cookies["session_id"] = session_id_value cookies["session_id"]["path"] = "/" cookies["session_id"]["max-age"] = 60 * 60 # 1 hour in seconds cookies["session_id"]["httponly"] = True cookies["session_id"]["samesite"] = "Lax" self.send_response(200) for morsel in cookies.values(): self.send_header("Set-Cookie", morsel.OutputString()) # self.send_header("Content-type", "text/plain") self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write(b"<html><body><h1>Cookie Expiration Updated!</h1><p>Check your browser's developer tools.</p></body></html>") def run(server_class=HTTPServer, handler_class=MyHandler, port=8000): server_address = ('', port) httpd = server_class(server_address, handler_class) print(f"Starting httpd server on port {port}...") httpd.serve_forever() if __name__ == "__main__": run()- レスポンスの
Set-Cookieを解釈し、ブラウザに Cookie が設定される
- Expires/Max-Age で有効期限が決められる
Set-Cookie: id=a3fWa; Expires=Thu, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly; Path=/docsExpires の例 - Expires/Max-Age を未設定にするとブラウザを閉じるまで有効な Session Cookie になる
- https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Set-Cookie#httponly (JavaScriptアクセスを拒否)属性, https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Set-Cookie#httponly (HTTPSで暗号化強制)を付ける
- 忘れると脆弱性になるので、外部公開しているサービスでは基本的に付けること
- AppScan など脆弱性診断ツールを使えばわかる
- 他にも SameSite 属性などある。セキュリティ回で触れているので深堀はしない
- レスポンスの
非同期との関係
例えば chrome の場合、タブごとにプロセスを生成し、JavaScriptで動的な処理を行うが、JavaScriptはシングルスレッドで動いている。
そのため、重い処理があると他のJavaScriptが動かなくなってしまう。
そこで、 async などを用いてシングルスレッドの中で非同期処理を行うことで、操作不能になることを回避している。
ここでいうシングルスレッドとは、あくまでページに対するスレッドのこと。
基本的にブラウザはJavaScriptエンジン以上の機能を持つ 環境 であり、補助する色々な機能がある。
例えば chrome ブラウザには
- JavaScriptエンジン:
v8 - レンダリングエンジン:
Blink - https://developer.mozilla.org/ja/docs/Web/API (browser API と呼ぶ人もいる)
といったものがある。 fetch や setTimeout は ブラウザが提供する web API のスレッドに時間を計る作業や通信を任せ、処理が終わったらキューに入れることでメインスレッドで後続の処理であるコールバックを実行している。
例えば Firefoxだと、JavaScript エンジンは SpiderMonkey。
要するに、 環境 によって処理系が異なるので、使えるものや、挙動が異なる。
Google Chromeだと使える関数がNodeだと使えなかったりするのは、環境が違うから(Web API が無いなど)。
詳細は座学の後半に出てくる: 📄【Webアプリ開発2026 #12】JavaScriptの諸事情1: ランタイムとモジュールシステム
参考: https://zenn.dev/estra/books/js-async-promise-chain-event-loop/viewer/f-epasync-asynchronous-apis
python 3.13 でもシングルスレッドなのでイベントループのはず(GIL)。3.14 で変わるっぽいが、async とスレッドはここらへんがかかわる。
web worker という別スレッドの機能を使えばマルチスレッドはできる。
ということで、一般的なブラウザでのページ表示までの流れとブラウザの役割を見たところで、構成を見ていく。