【Webアプリ開発2026 #6】ブラウザで WEB ページを表示するまで

ブラウザで WEB ページを表示するまで

ブラウザの URL をアドレスバーに入力する。

  • https://www.nifty.com/

Navigation

  1. DNS サーバからドメインに紐づく IP アドレスを引っ張る
  2. サーバが HTTP リクエストを受け取り、応じた HTTP レスポンスをブラウザに返信する
    • アプリケーション層: HTTP
    • 例えばこのようなメッセージ本文のやりとり
    Image in a image block
    適当に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 ハンドシェイク でコネクションを確保してから通信
            Image in a image block
            マスタリングTCP/IP 入門編(第6版), p.456
        • ちゃんとしたサイトなら TLS で HTTPS とする
    • 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 サイトで使われている(らしい)
    • 社内ではあまり HTTP のバージョンは気にしていない

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>

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()

非同期との関係

例えば chrome の場合、タブごとにプロセスを生成し、JavaScriptで動的な処理を行うが、JavaScriptはシングルスレッドで動いている。

そのため、重い処理があると他のJavaScriptが動かなくなってしまう。

そこで、 async などを用いてシングルスレッドの中で非同期処理を行うことで、操作不能になることを回避している。

💡
ちょっと補足
ここでいうシングルスレッドとは、あくまでページに対するスレッドのこと。
基本的にブラウザはJavaScriptエンジン以上の機能を持つ 環境 であり、補助する色々な機能がある。


例えば chrome ブラウザには


といったものがある。 fetchsetTimeout は ブラウザが提供する web API のスレッドに時間を計る作業や通信を任せ、処理が終わったらキューに入れることでメインスレッドで後続の処理であるコールバックを実行している。


例えば Firefoxだと、JavaScript エンジンは SpiderMonkey
要するに、 環境 によって処理系が異なるので、使えるものや、挙動が異なる。
Google Chromeだと使える関数がNodeだと使えなかったりするのは、環境が違うから(Web API が無いなど)。

詳細は座学の後半に出てくる: 📄Arrow icon of a page link【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 という別スレッドの機能を使えばマルチスレッドはできる。

ということで、一般的なブラウザでのページ表示までの流れとブラウザの役割を見たところで、構成を見ていく。