ブラウザで WEB ページを表示するまで
ブラウザの URL をアドレスバーに入力する
- https://www.nifty.com/
Navigation
-
DNS サーバからドメインに紐づく IP アドレスを引っ張る
-
やってることは
nslookup
や https://www.nslookup.io/website-to-ip-lookup/ などで確認できる - IP アドレスは覚えずらいので DNS が生まれた( wiki より)
-
やってることは
-
サーバが 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 のバージョンで本文も異なる
- ちゃんとしたサイトなら TLS で HTTPS とする
-
HTTP/1.0, HTTP/1.1, HTTP/2
-
トランスポート層が TCP, つまり 3-way ハンドシェイク
-
(復習?)
TCP (Transmission Control Protocol)
-
3-way ハンドシェイク でコネクションを確保してから通信
- マスタリングTCP/IP 入門編(第6版), p.456 など参照のこと
-
3-way ハンドシェイク でコネクションを確保してから通信
-
(復習?)
-
トランスポート層が TCP, つまり 3-way ハンドシェイク
-
HTTP/3
-
トランスポート層が Google 提案の QUIC と呼ばれる UDP ベースのものなので早い
-
(復習?)
UDP (User Datagram Protocol)
- コネクションを作らない(コネクションレス)で、輻輳制御や再送せずただ送るトランスポートプロトコル
- ちゃんと通信するには上位層のアプリケーション層が面倒を見る必要がある
- 他にも色々ボトルネック消すなどある
-
(復習?)
HTTP/3 は 35.2% の 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=/docs
Expires の例 - Expires/Max-Age を未設定にするとブラウザを閉じるまで有効な Session Cookie になる
-
HTTPOnly
(JavaScriptアクセスを拒否)属性, Secure 属性 (HTTPSで暗号化強制)を付ける- 忘れると脆弱性になるので、外部公開しているサービスでは基本的に付けること
- 脆弱性診断ツールを使えばわかる
-
他にも
SameSite
属性などある。セキュリティ回で触れているので深堀はしない
-
レスポンスの
非同期との関係
例えば Chrome の場合、タブごとにプロセスを生成し、 JavaScript で動的な処理を行うが、 JavaScript はシングルスレッドで動いている。
そのため、重い処理があると他の JavaScript が動かなくなってしまう
そこで、 async などを用いてシングルスレッドの中で非同期処理を行うことで、操作不能になることを回避している
ということで、一般的なブラウザでのページ表示までの流れとブラウザの役割を見たところで、構成を見ていく。