🚀 ニフティ’s Notion

【Webアプリ2025 #15】ブラウザで WEB ページを表示するまで

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

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

  • https://www.nifty.com/

Navigation
  1. DNS サーバからドメインに紐づく IP アドレスを引っ張る
  2. サーバが 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 など参照のこと
    • HTTP/3
      • トランスポート層が Google 提案の QUIC と呼ばれる UDP ベースのものなので早い
        • (復習?) UDP (User Datagram Protocol)
          • コネクションを作らない(コネクションレス)で、輻輳制御や再送せずただ送るトランスポートプロトコル
          • ちゃんと通信するには上位層のアプリケーション層が面倒を見る必要がある
        • 他にも色々ボトルネック消すなどある
      HTTP/3 は 35.2% の 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()
    • レスポンスの 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 などを用いてシングルスレッドの中で非同期処理を行うことで、操作不能になることを回避している

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