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

2022-02-12

infra

network

web

動機

去年基本情報&応用情報を受験して、今年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ライブラリのメソッドを呼び出した際には、そのメソッドがプロトコル・スタックに実際の処理を依頼して、プロトコル・スタックが処理は行っていると解釈してください。

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

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

5. データの送信

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

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

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

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

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

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

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

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

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

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


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


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

6. データ送信処理詳細

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

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


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

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

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

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

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

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

【IPヘッダーの中身】

【MACヘッダーの中身】

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

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

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

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

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

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

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

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

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

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

ルータ内の処理

ルーター間について

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


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

サーバーが信号を受信

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



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

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

サーバーが信号を受信

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

LANアダプタの処理

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

IPでの処理

TCPでの処理

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

ブラウザがデータを受信

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

まとめ

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