github octcat icon
twitter bird icon
rss icon

URL入力⇨ブラウザに表示まで

2022-02-12

network

Photo Credit: Alina Grubnyak

動機

去年基本情報&応用情報を受験して、今年4月にはネットワークスペシャリストを受験してみる予定です。

資格自体が欲しいというよりは、基礎体力をつけるために勉強したく、期限と強制力を持たせるために試験を活用しようと考えています。

そのために、「ネットワークはなぜ繋がるのか 第二版」や「マスタリングTCP/IP 入門編」などを読んでみたり、いわゆる試験対策の参考書である「徹底攻略 ネットワークスペシャリスト教科書」などを読んでみました。

しかし、勉強を進め知っていることが増えていくに従い、詳細に気を取られ全体が見えなくなってきてしまっていました。

そこで、「木を見て森を見ず」な状態を脱却するために一度全体の大きな流れを掴んでから、再度詳細を勉強した方が効率が良いのではないかと思い、擦られ倒した話題ではあるものの、「URLがアドレスバーに入力されてからWebページが表示されるまで」の流れをまとめていこうと思った次第です。

構成

ブラウザ⇨サーバー

  1. URL解析
  2. リクエストメッセージ作成
  3. DNSサーバー問い合わせ
  4. TCPコネクション確立
  5. データの送信
    1. TCPプロトコルが信頼性担保
  6. データ送信処理詳細
    1. ヘッダーの付与
    2. LANアダプタの処理
    3. ルーターに向けて信号を送信
  7. ルーター間を伝わりサーバーに届く

サーバー⇨ブラウザ

  1. サーバーが信号を受信
    1. 信号から元のリクエストメッセージまで戻す
  2. サーバーサイドアプリケーションの処理
    1. URI、HTTPメソッドなどの情報をもとに処理
  3. ブラウザが信号を受信
    1. 信号から元のレスポンスメッセージまで戻す
  4. レスポンスヘッダーを確認して、種類に応じて処理
    1. HTMLの場合:HTMLファイルを解析してOSにブラウザに表示するよう指示を出す
    2. HTMLに画像が含まれる場合:画像ファイルを再度サーバーにリクエスト
    3. レスポンスが返ってきたら、再度OSに指示を出しブラウザに表示

今回は、この順番でまとめていければと思います。

詳細を読む中で、全体のどこにいるのかがわからなくなったら、再度この構成を見ていただければと思います。

*本来各大項目毎に本が何冊も書けるレベルで情報量が多いはずですが、今回は全体の流れを掴むことが目的なので、詳細や一部項目は省略しております。

*使っているOSやブラウザなどによって異なる項目もございます。



それでは早速、URLを入力してからサーバーにデータが届くまで、往路の説明をこれからしていきます。

1. URL解析

アドレスバーにはURLではない検索文字列を打ち込むこともできるし、URLを打ち込むこともできます。

そのため、まずはアドレスバーに打ち込まれた文字列を解析して、URLであれば続く処理を、文字列であれば検索ブラウザを呼び出す処理をしていきます。

今回はテーマに沿ってURLが打ち込まれた例を考えていきます。


https://blog.shgnkn.io/from-typing-url-to-rendering-contents-in-your-browser/

という、このブログのURLがあります。このURL文字列は複数の要素で成り立っており、ブラウザ内部では以下のように分解されます。

https: プロトコルを表します。

// 後に続く文字列がサーバーの名前であることを表します。

blog.shgnkn.io サーバー名です。

/from-typing-~~/ データがあるパス名です。


これらの情報をもとに、HTTPリクエストを作成したり、必要に応じて暗号処理モジュールを呼び出して暗号化処理をしたりしていきます。

また、この際に、対象リソースのキャッシュをブラウザが保有しており、サーバーにHTTPリクエストを送信する必要がない場合には、以下の手順は飛ばして最後のブラウザへの表示を開始することができます。

2. HTTPリクエストメッセージの作成

URLの解析が完了して、サーバーへの問い合わせが必要なことがわかったら、次はHTTPリクエストメッセージを作成していきます。

リクエストメッセージにはルールが決められており、それに従ってブラウザは以下のような形式のリクエストメッセージを作成していきます。

POST / /sample/samle.php HTTP / 1.1
・
・
・
<空白行>
<メッセージボディ>

最初の行がリクエストヘッダで、どのメソッドをどのリソースに対してどんなプロトコルでリクエストを出したいのか指定します。 その後複数行のメッセージヘッダーで、コンテンツの種類や圧縮の形式など、必要な情報を付与していきます。 メソッドがPOSTの場合には、メッセージヘッダーの後に1行空白行を開けてからメッセージボディが続きます。ここには、/sample/samle.phpに渡したいデータなどを記載します。

リクエストメッセージのルールについて、詳しくはMDNのHTTP メッセージをご確認ください。


ブラウザはURLを解析して、HTTPリクエストメッセージを作成することまでできますが、メッセージをサーバーに向けて送信する機能は持ち合わせていません。

そのため、作成したメッセージをサーバーに向けて送信する作業はOSに依頼していきます。

OSが依頼を受け取ってからは、TCP/IPなどさまざまなプロトコルが共同で作業をしていくことになります。

全体の流れとしてはOSI参照モデルに沿って順番にデータが流れていくのですが、一部階層の異なるプロトコルが互いにやりとりをして進む処理もあるため、今回のまとめでは厳密に切り分けずに進めていきたいと思います。

3. DNSサーバーへの問い合わせ

URLを解析して、宛先ドメインはわかりましたが、宛先IPアドレスはまだわかっていません。

ただし、ブラウザがドメインとIPアドレスのペアをキャッシュとして保有している場合には、DNSサーバーに問い合わせする必要がなくなるので、以下のプロセスは飛ばして、「4.TCPコネクション確立」に進むことができます。

ブラウザがキャッシュを保有していない場合には、DNSサーバーと通信してドメインからIPアドレスを取得する必要があります。

その際に、SocketライブラリのDNSリゾルバを用いて通信を進めていきます。

イメージとしては、今回まとめているブラウザとサーバーの関係がDNSリゾルバとDNSとで成り立っていると考えていただけると分かりやすいかと思います。

Socketライブラリについて少し補足すると、SocketライブラリはOSに組み込まれているネットワークと通信するためのライブラリです。

これからの説明でも、Socketライブラリの他のメソッドが登場していきます。

DNSリゾルバがDNSに問い合わせを行い、ドメインからIPアドレスを取得するのですが、実際の通信はDNSリゾルバではなく、OS組み込みのプロトコル・スタックが行います。DNSリゾルバがリクエストメッセージを作成しプロトコル・スタックを呼び出して、DNSサーバーへの問い合わせをプロトコル・スタックに依頼するイメージです。

また、この際に「DNSサーバーのIPアドレスはどうやって調べるんだ?」という疑問が出るかと思うのですが、DNSサーバーのIPアドレスは、PCのTCP/IPの設定であらかじめ設定してあるので、特にどこかに問い合わせたりする必要がないのです。

ちなみに、DNSサーバーへの問い合わせはTCPではなくUDPで行われます。なぜなら、問い合わせに使うパケットは1つしかないので、万が一通信に失敗してもDNSサーバーからのレスポンスが返って来なかったら失敗と判断することが可能で、わざわざ面倒な接続手続きをしてTCPを活用するよりも効率が良いからです。

DNSサーバーは階層構造で世界中にたくさんあるので、自分が問い合わせしたドメインのIPアドレスが格納されているDNSサーバーに問い合わせるまでにはもう少し道のりがあるのですが、今回は簡略化のために詳細は割愛します。

4. TCPコネクション確立

宛先IPアドレスがわかったら、次はサーバーに向けてHTTPリクエストメッセージを送信するための準備をしていきます。

ここでも、DNSサーバーに問い合わせを行った時のように、Socketライブラリを活用しOSのプロトコル・スタックを呼び出して実際の処理を行っていきます。

イメージとしては、データを流すためのパイプラインを通信の最初に確立して、そこにデータを流していくイメージです。

以下の手順で処理をしていきます。

  • サーバーとクライアント双方でそれぞれソケットを作成する
  • クライアント側のソケットからからサーバー側のソケットにパイプを伸ばす(ソケットを接続する)
  • 実際のデータ送受信を行う
  • 最後にパイプを外す

このパートでは、データの送受信の手前までそれぞれ詳細を見ていきます。

ソケット作成

ソケットは実態を持っているわけではなく、宛先IPアドレス、送信元IPアドレス、ポート番号などを保持しているメモリ領域にすぎません。 プロトコル・スタックはこれらの情報を確認して、どの宛先に対してデータを送信するのかなどを把握しています。

クライアント側、サーバー側ではそれぞれソケットが作成されるタイミングが異なります。


【クライアント側】

Socketライブラリからsocketを呼び出して、socketがプロトコル・スタックを呼び出して、プロトコル・スタックがソケットを作成します。 ソケットを作成すると、ディスクリプタというソケットの識別子が生成されるので、ソケット内部に保存しておきます。

また、この際にデータの送受信で必要となるバッファメモリの確保も行っておきます。


【サーバー側】

サーバーを立ち上げた際にクライアント側と同じ手順でソケットが作成されるようになっています。

サーバー側では、ソケットを作成したら、接続待ち状態でクライアントからの接続依頼が来るのを待ちます。

ソケット接続

ブラウザはサーバーのIPアドレスなどの情報を持っていますが、プロトコル・スタックはそれらの情報を持っていません。

そのため、ソケット同士をSocketライブラリのconnectを呼び出すことで接続して、プロトコル・スタックに通信で必要になる情報を知らせてやる必要があります。

サーバーも然りで、ソケットがあるだけではどのクライアントと接続したらいいのかわからないので、接続動作をする際に、クライアントからクライアントのIPアドレス&ポート番号をサーバー側に知らせてやる必要があります。

本物のパイプのように物理的な何かを生成するわけではなく、IPアドレスなど制御情報のやりとりを行うことをソケットの接続とよんでいます。

流れは以下の通りです。3回にわたって、SYN / ACK / SYNとやり取りがなされて接続が完了するので一連の処理は「3 way handshake」と呼ばれています。 *以降説明を省略しますが、Socketライブラリのメソッドを呼び出した際には、そのメソッドがプロトコル・スタックに実際の処理を依頼して、プロトコル・スタックが処理は行っていると解釈してください。

  • アプリケーションがSocketライブラリのconnectを呼び出す
    • connectの呼び出し時にはディスクリプタ、サーバーIP、サーバーポート番号を渡す
  • データ送受信動作の開始を示すヘッダーである、TCPヘッダーを作成
    • 送信元、宛先のポート番号を入れる。これでどことどこが通信するのかわかる
    • コントロールビットのSYNを1にする
    • その他もろもろ設定して、TCPヘッダーをIP担当部分に渡す。実際の通信はIP担当部分の仕事
  • IP担当がネットワークにパケットを流して、サーバーのIP担当が受け取る
  • サーバーのIP担当はTCP担当に渡して、TCPはTCPヘッダーの中身を見る。ポートを調べて、該当するソケットが見つかったら通信進行中状態にする
    • この際に、接続待ち状態のソケットにそのまま接続するのではなく、接続待ち状態のソケットをコピーしてコピーした新しいソケットとクライントのソケットを繋ぐ
      • サーバー側のソケットは、コピーして使い続けるので、常に80番ポート(変えている場合もあるが)を使い続けることになる
      • そのため、送信元&宛先IPアドレス、送信元&宛先ポート番号の4つで判断するようにしている
      • クライアント側は接続の都度異なるポート番号を割り当ててくるし、異なるクライアントならIPアドレスが異なるので正しく見極めることが可能
  • サーバーのTCP担当はクライアントと同じように、必要な情報をtcpヘッダーに記載
    • 送信元、送信先のポート番号
    • コントロールビットのSYNは1
    • ACK番号は初期シーケンス番号+1
    • 但しコントロールビットのACKは1
      • ここがややこしいので注意。確認番号とコントロールビットは同じACKでも別もの。
  • TCPヘッダーをIP担当に渡してネットワークに流す
  • クライアントは受け取ったら、SYNが1かどうか確認。1なら接続成功なので、ソケットにサーバーのIP、ポート、などを記載して、接続完了を示す制御情報もソケットに書き込んでおく。
  • サーバーにACKを1にしたTCPヘッダーを送り返して、接続完了を伝える。
  • これでコネクションが確立された状態になり、データを流すことが可能。Socketライブラリのcloseを実行するまでコネクションは確立された状態になる。

今回の説明では、「IP担当」という言葉が複数回にわたって登場しましたが、この詳細は後ほど説明していきます。

ここでは、TCP/IPプロトコルを用いて制御情報のやりとりを行い、クライアント側、サーバー側それぞれのソケットに対して制御情報をセットしていく作業がソケット接続に当たるのだな。と理解していただければOKです。

5. データの送信

ソケットの接続が完了したら、アプリケーションに処理が戻ります。

次は、アプリケーションがSocketライブラリのwriteを呼び出し、送信データをプロトコル・スタックに渡してデータの送信処理が始まります。

プロトコル・スタックは、データの中身をこの時点では知らず、ただサイズがわかっているだけです。

一定程度、送信用バッファにデータを貯めてから流すことで、効率の良い通信を行っています。

逆に、データサイズが大きすぎてMSS(マックス送信できるデータ量、MTUからヘッダー分を引いたもの)を超えるものに関しては送信用バッファから分割して送信するようにします。

送信する際に、制御情報から送信元ポート番号や宛先ポート番号などを書き込んだTCPヘッダーを付与して、IP担当部分に渡し、IP担当部分がIPヘッダーやイーサネットのMACヘッダーなどを付与して送信を行います。

IP担当の処理は、ソケット接続を行った時と同じでやっていることは共通しているので、後ほどまとめて説明していきます。

TCPプロトコルが信頼性担保

TCPプロトコルは通信の信頼性を担保して、他のプロトコルが信頼性のことは気にしなくても済むようにしています。

具体的な方法としては、以下の手順で信頼性を担保しています。


  • データを送信する際に、シーケンス番号をTCPヘッダーに入れておく
    • シーケンス番号には、通信開始から数えて何バイト目のデータを送るのかを記載した項目
    • 受信側はシーケンス番号とパケットのサイズから何バイト目から始まるデータが何バイト届くのかがわかる
      • 受信側でパケットに抜けがないか確認することが可能になる。
  • 受信側は、何バイト目まで受信完了したのかをTCPヘッダーのACKに書き込んで送信側に知らせる
    • 送信側もどこまで正確に送ることができているのかわかる。
  • ACKが正しく返ってこなかったら再送するが、何回か再送してダメだったら諦めてアプリケーションにエラーを返す

*シーケンス番号は1から始まるのではなく、乱数で設定されます。ソケット接続時に、シーケンス番号の初期値をクライアントで設定してTCPヘッダーに付与することでサーバー側に渡しています。


*例では1パケット送ったらACKが帰ってくるまで待って送信しましたが、実際にはウィンドウサイズ(サーバー側で受け取ったデータを貯めておける量)をサーバーから受け取ってそのサイズまではACKが帰ってこなくても連続してデータを送信できるようにしています。 こうすることでACKの返答待ちをしなくてもパケットを送信することができる&ACKパケットが減ることで通信効率を上げることができます。

6. データ送信処理詳細

これまでの説明の中で「IP担当に依頼して〜」のような形でお茶を濁してきた部分の詳細を説明していきます。

ここまでお茶を濁してきた理由としては、IPプロトコル、正確にはIP以下のプロトコルで行っている通信はデータの中身や目的によらず共通で、基本的にはあるデータを目的地まで届けるということを行っているので、後からまとめて説明した方が全体の流れを追いやすいと考えたためでした。


ここから、IP担当が行っていた処理の詳細を説明していきます。色々と出てきますが、あくまでも、「データを目的地まで届ける」ということをしている点を忘れないでください。

IPヘッダーとMACヘッダーの付与

TCP担当からTCPヘッダーとデータを渡されたら、そこにIPヘッダーとMACヘッダーを付与していきます。

パケットを宛先に届けるにあたって、まだどこに届けたらいいのかがわからない状態です。

そのため、IPヘッダーとMACヘッダーそれぞれの中に、最終的な宛先IPアドレスと、次にパケットを渡す先のMACアドレスを設定してどこにパケットを渡せばいいのか他の機器が判断することができる状態にする必要があるのです。

その後、ヘッダーが付与されたパケットはLANアダプタに渡されていきます。

【IPヘッダーの中身】

  • 宛先IPアドレス
    • ソケット接続時(TCPコネクション確立時)にアプリケーションからソケットに渡していたものを付与
  • 送信元IPアドレス
    • プロトコルスタックに設定されているモノを付与
  • その他情報
    • パケット長、生存時間、プロトコル(上位プロトコルで、TCPやUDPなどを表す番号)、フラグ(分割の可否)、フラグメントオフセット(分割されたパケットが元のデータのどこに位置しているか)など

【MACヘッダーの中身】

  • 宛先MACアドレス
    • 宛先をもとに経路表からどのルータ(IPアドレス)にパケットを渡すべきか調べる
    • 宛先ルータのIPアドレスからARPを用いてMACアドレスを取得する
      • DNSサーバに問い合わせる時同様、キャッシュにあるかまず確認して、なかったら問い合わせる
  • 送信元MACアドレス
    • LANアダプタに製造時に付与されている。
    • OS起動時に読み出してメモリに保存しておいて、その値をMACヘッダー作成時に付与する
  • イーサタイプ
    • IP(0800)、ARP(0806)など上位プロトコルを表すための番号

LANアダプタでデジタルデータを信号に変換

IPヘッダーとMACヘッダーを付与したことで、パケットを最終的にどこに届けたらいいのか、次の宛先はどこに送信したらいいのかは分かるようになりました。

次は、ネットワークの中にデータを流して、宛先まで届けることができるように、デジタルデータを信号に変換して送信していきます。

この作業は、LANアダプタが担います。

LANアダプタはそれ単体では活用することができず、LANドライバと一緒になって初めて活用することができます。

LANドライバが動き出してから、デジタルデータを信号に変換して送信するまでの流れを追っていきます。

  • OS起動時にLANアダプタを初期化する
    • イーサネットの送受信動作をコントロールする、LANアダプタ内のMAC回路にMACアドレスをセットする
      • LANアダプタのROMにMACアドレスが製造時に書き込まれているのでそれをセット
      • 宛先MACアドレスはこのMACアドレスを指している
  • IPからパケットを受け取る
  • LANドライバがIPから受け取ったパケットをLANアダプタ内のバッファメモリにコピーする
  • MAC回路に送信コマンドを送って、そこから先はMAC回路が処理をする
  • MAC回路はバッファメモリからデータを読み出して、イーサネットフレームの先頭にプリアンブルとスタート・フレーム・デリミタ、末尾にはFCSも付与
    • プリアンブルはデータ受信側がタイミングを図るため
    • スタート・フレーム・デリミタはどこから実際のデータが始まるのか受信側が判断するため
    • FCSはフレームのエラーを検知するため
  • MAC回路がプリアンブルの頭から1ビットずつデジタルデータを信号に変換して、PHYもしくはMAUという信号送受信部分に送る。
  • 信号をPHYもしくはMAU回路がケーブルに送り出す形式に変換して送信
    • イーサネットはケーブルの種類や伝送速度によって形式が異なるのだが、MAC回路は共通のところまで変換して残りはPHYもしくはMAU回路に任している
    • 送信したら、あとはルーター間を辿ってネットワーク内を流れ、送信先まで届く
  • 送信時には受信信号が流れこんできていないかも確認している
    • ただ、エラーはほぼない&あっても処理はTCPに任せているので、イーサネットは何もしない
  • 万が一流れ込んできていたらパケットが衝突するので送信を止める
    • 送信を止めたら、ジャミング信号を他の機器に流して、しばらく送信動作をストップ
      • 待ち時間は、MACアドレスから乱数を生成して、他の機器と被らないようにする

ここまでで、IP担当が行っていた送信処理をまとめてきました。

受信処理に関しては、サーバー側に関する説明の際にまとめますが、クライアント側が受信処理をする際でも流れは同じで、基本的には送信処理の逆手順を辿っていきます。

7. ルーター間を伝わりサーバーに届く

詳細を記載していくと膨大になるので、今回は大幅に簡略化してまとめていきたいと思います。

ルータ内の処理

  • クライアント側LANアダプタから送信された信号を受け取る
  • 信号をデジタルデータに変換
  • FCSをチェックしてデータにエラーがないか確認
  • IPアドレスのネットワーク部を元にルーティングテーブルを参照して、次のルーターの宛先IPアドレスをアドレスを取得
  • 必要であればパケットを分割
  • 宛先IPアドレスからMACアドレスを取得するために、ARPで問い合わせてMACアドレスを取得する
    • ARPキャッシュが残っていれば、キャッシュからMACアドレスを取得
  • ルーターのポート部分がLANアダプタと同様の働きをしてデジタルデータを信号に変換し、次のルーターに送信

ルーター間について

ルーター間は、回線事業者が引いたADSL回線などを通ってデータが流れています。 この部分でも本来はさまざまな処理が行われていますが、普段我々が直接操作したり意識したりする部分は少ないので、今回は割愛します。


また、ルーター間を通ってサーバーに届くまでの間には、ファイヤウォールやロードバランサ、プロキシサーバー(キャッシュサーバー)、CDSなど、様々な技術が活用されており、それぞれセキュリティを高めたり、サーバーの負荷を分散したり、コンテンツ配信の速度を上げたりといった役割を持っています。 これらも今回は詳細を割愛します。

サーバーが信号を受信

ここまでの長い道のりを経て、やっとサーバーが信号を受信します。



ここからは復路の説明です。

この先、サーバーが信号を受信してから、レスポンスを返して、ブラウザが画面にコンテンツを表示するまでを見ていきます。受信する際の処理はクライアントが送信処理をした際の逆になるものがほとんどなので、新しいことは多くありません。

サーバーが信号を受信

受信時はクライアントからのデータ送信時とは逆の手順が取られます。 クライアント側がデータを受信する場合でも、サーバー側がデータを受信する場合でも処理内容は同じです。

LANアダプタの処理

  • PHYもしくはMAU回路が、プリアンプルをみてタイミングを取って、データを読み込む
  • PHYもしくはMAU回路がMAC回路が処理できる形式に変換してMAC回路に渡す
  • MAC回路がデジタルデータに変換して、バッファメモリに貯める
  • データを全て受け取ったら、FCSの計算をする
    • イーサネットフレームの末尾についているFCSと付き合わせてズレていたらパケットを捨てる
  • MACアドレスが自分宛かどうかチェックし、異なる場合捨てる。自分ならバッファメモリに貯める
    • MAC回路の仕事は終了で、CPUにデータの受信を通知する
      • 通知方法は、LANアダプタが拡張バススロットにある割り込み用の信号線に信号を送る
  • CPUは今の仕事を止めて、割り込み処理用プログラムに切り替える
  • CPUがLANドライバを呼び出しLANアダプタのバッファメモリに格納されたデータを取り出す
  • LANドライバがMACヘッダーのタイプ・フィールドを確認して適切なプロトコルスタックにパケットを渡す

ここからは、LANアダプタの仕事ではなく、プロトコルスタックが処理する範囲です。

IPでの処理

  • (タイプフィールドが0800でIPと仮定) IP担当がIPヘッダーを調べる
    • フォーマットに誤りがないか確認
    • 宛先IPアドレスが自分のものか確認
      • エラーだった場合には、ICMPで通信相手にエラーを返す
      • サーバーの場合にはルーターと同じ中継機能を持つ場合があるので、その時には自分宛以外でのも中継する
  • IPヘッダーのフラグを見ると、分割されているかどうか判定
    • 分割されていた場合、IP担当の内部メモリに一時保管して他のデータと繋ぎ合わせる
      • ID情報に同じ値を持つパケットが分割されている同一パケット
      • フラグメント・オフセット:該当パケットが同一ID値内でどの位置にくるパケットかを表しているので、この情報をもとにデータを結合する
  • データを結合したら、次のプロトコルをチェックして然るべき担当にデータを渡す
    • この際、IPヘッダーの宛先&送信元IPアドレス、TCPヘッダーの宛先&送信元ポート番号を調べて、該当するソケットを探してTCP担当を見つけている

TCPでの処理

  • TCPヘッダーを調べる
    • SYNが1の接続依頼の場合には、接続処理をするが、以下はデータが送られてきている前提で話を進める
    • 該当するソケットにデータを渡し、TCPヘッダーの中身を調べる
      • シーケンス番号が正しいかどうかなど
  • 前回受け取ったデータに繋げ、受信バッファに保存
  • TCPヘッダにACKを1にして作成し、IP担当に依頼し受信動作成功をクライアントに伝える
  • アプリケーションがreadを呼び出して待っているはずなので、受信バッファのデータがアプリケーションに取られたらTCP担当の処理は完了

サーバーサイドアプリケーションの処理

  • サーバーアプリがreadでソケットの受信バッファからデータを読む
  • URIとHTTPメソッドを元に後続の処理を決定する
  • サーバーサイドで処理したらwriteでクライアントにデータを送り返す
    • HTMLをそのまま返すこともあれば、URIに付与されたパラメタやリクエストボディの内容をCGIプログラムに渡して動かして、処理結果を返すこともある
    • IPアドレスやドメイン、ユーザ名&パスワードなどでアクセス制御をかける場合もある
  • 処理されたデータをまとめ、クライアントがリクエストメッセージを作成したのと同じ方法で、レスポンスメッセージを作成する
  • Socketライブラリのwriteメソッドでプロトコルスタックを呼び出しクライアントに送り返す
    • 送信先はソケットのディスクリプタで指定する。ソケットはIPアドレスなど、データを届けるのに必要な情報持っているのでディスクリプタを指定するだけでOK
    • この先の流れは、クライアントからサーバーまでデータが送信された際の流れと同じです。

ブラウザがデータを受信

ブラウザがデータを受信してから、画面にコンテンツを表示するまでの流れを説明していきます。

  • LANアダプタが信号からデータに変換して受信バッファに貯める
  • プロトコルスタックが分割されたデータをまとめて元のレスポンスメッセージに戻してブラウザに渡す
    • この辺りはサーバーの受信動作と同じ
  • レスポンスヘッダーフィールドにあるContent-Typeでコンテンツの種類が何か確認
    • text/htmlのように、主タイプ/サブタイプの組になっている
      • textの場合、charsetで文字コードも調べる
    • Content-typeはサーバー管理者が設定を間違えていたりすることがある。そのため、データの中身などから総合的に判断する機能をブラウザが持っていることも多い。これはブラウザによって異なる
  • Content-Encodingも確認して、どんな方法で圧縮されているのか確認。必要に応じて元に戻す
  • コンテンツの種類がHTMLの場合には、中身を解析
  • OSが画面上にコンテンツを表示する機能を持っているので、OSに対してどこに何を表示するのかブラウザから指示を出す
  • 画像データを示すファイルがHTMLに入っている場合、サーバーに問い合わせて画像を取得する
    • HTMLを取得したリクエストと同じ方法を画像に対しても1枚ずつ行う
      • そのため、大量に画像が設置されているwebサイトなどは全ての画像を表示するために何回も通信する必要があり時間がかかる
  • JPEGなどは圧縮されているので、受け取ったら復元して、文章と同じくOSに依頼して表示する

まとめ

かなり絞ってまとめたつもりでも、一部詳細に書き過ぎている部分があるので後ほどより絞る編集をし直すことができたらと思います。 いったん目的であった、全体感を把握できるようにまとめる。という目的は達成できたので、デリバリー優先でリリースとします。