0w0

Web 페이지를 브라우저에 띄우기까지 일어나는 일

이 글은 브라우저 주소창에 url를 입력하고 엔터를 누를 때 무엇이 일어나는가?를 이해하는 것을 목표로 합니다.

글 예시로 사용하면 url(https://client.sharefull.com/)은 자사에서 개발한 셰어풀 서비스입니다.

유저가 url을 입력하고 엔터를 누르면 브라우저는 우선 입력된 url 해독을 시작합니다. Url를 분해해서 프로토콜과 도메인명 경로명이 무엇인가 조사합니다.

https://client.sharefull.com/

상기한 url이라면 https 프로토콜, client.sharefull.com 도메인명 /가 경로명입니다.

만약 프로토콜이 기술되지 않았거나 유효한 도메인이 아니라면 브라누저는 입력된 텍스트를 브라우저 기본 검색 엔진으로 넘깁니다.

HSTS(HTTP Strict Transport Security)란 초간단하게 설명하면 유저가 http로 접근할 때 브라우저가 자동으로 https로 바꿔 접근해주는 기능입니다.

http로 접근한 유저를 중간자 공격으로부터 지켜주는 구조입니다.

HSTS 리스트를 조사하는 흐름에 대해서는 다른 을 읽어주세요.

DNS는 IP 주소와 도메인명에 다루는 시스템입니다.

인터넷으로 접속하는 기기에는 반드시 하나하나 고유의 번호가 있으며 이 번호를 IP 주소라 합니다.

전화걸 때 상대 번호를 지정해 전화하는 것과 같이 인터넷 통신을 할 때 IP 주소로 상대를 지정해 통신합니다. IP주소는 10.11.12.13 같이 점으로 나눠진 숫자입니다.

사람은 10.11.12.13 같이 숫자 나열을 외우기 어려우므로 도메인명을 입력하면 해당하는 IP 주소가 무엇인가 알려주는 구조입니다.

우선 브라우저는 자신이 IP 주소를 이미 알고 있는가 알기 위해 자신의 캐시를 봅니다. 최근에 접속했라면 IP 주소 캐시를 남겨뒀을 것입니다.

만약 사용하고 있는 브라우저가 크롬이라면 이하와 같이 캐시된 DNS 정보를 확인합니다.

chrome://net-internals/#dns

만일 캐시가 있다면 이름 해결 프로세스는 종료됩니다.

브라우저에 캐시가 없다면 이번에는 hosts 파일을 조사합니다.

이 파일에는 OS 설정의 하나로 TCP/IP 네트워크 상 IP 주소와 호스트 명을 기술한 텍스트 파일입니다.

초기 인터넷은 hosts 파일 원형인 hosts.txt 파일을 서용해 도메인명을 IP 주소 변환하는 처리를 했습니다.

무슨 소리냐면 인터넷 상에 있는 모든 호스트와 ip주소를 단 하나의 파일에 담겨있어서 다른 호스트와 통신할 때 자신의 pc안에 있는 이 파일을 참조해서 통신합니다.

Hosts파일은 이를 잇는 파일입니다.

이하의 명령어로 속을 볼 수 있습니다.

Hosts 파일 들여다보기

$ sudo vi /private/etc/hosts

이런 식입니다

파일의 속

### Host Database## localhost is used to configure the loopback interface# when the system is booting.  Do not change this entry.##127.0.0.1       localhost

상기한 127.0.0.1 같이 IP 주소 호스트명이라는 포맷 구성입니다.

hosts.txt를 사용할 당시 (1972년대)에는 굉장히 적은 수백대의 호스트만 존재했기에 인터넷 상의 모든 호스트 정보를 담을 수 있었습니다.

그러나 인터넷이 보급이 넓어지면서

しかし、インターネットが普及していくにつれて HOSTS.TXT は肥大化していき、1983 年には、ホスト数はおよそ数万台になりました。もはや HOSTS.TXT による名前解決は不可能となったので、現在のような DNS サーバを設置して名前解決する仕組みが生み出された、という流れです。

名残の hosts ファイルは現在も使われてます。 名前解決で DNS サーバに問い合わせる前に、ブラウザは hosts ファイルを見に行きます。もしファイル内に対象のホスト名があれば、そこで名前解決のプロセスは終了です。

スタブリゾルバを呼び出す

もし hosts ファイルに対象の client.sharefull.com の IP アドレスがなかったら、DNS サーバに問い合わせます。

ブラウザはまず スタブリゾルバ というものを呼び出します。 スタブリゾルバとは、クライアントの PC の中に OS として備わっている機能のことです。呼び出されたスタブリゾルバは、キャッシュ DNS サーバに対して「client.sharefull.com の IP アドレス知ってる?」と問い合わせを行います。

キャッシュ DNS サーバにキャッシュがあったら

キャッシュ DNS サーバ は名前の通り、各問い合わせの結果を一定期間キャッシュに保存しておいて、後で同じ問い合わせがきた場合は、その結果を再利用して返します。

そのため、以前にスタブリゾルバから「client.sharefull.com の IP アドレス知ってる?」という問い合わせを受けていたら、キャッシュに保存されているはずなので、その問い合わせ結果をスタブリゾルバに返します。

スタブリゾルバは、受け取った結果から IP アドレスを取り出して、ブラウザから指定されているメモリー領域に書き込みます。これで、名前解決のプロセスは終了です。

キャッシュ DNS サーバにキャッシュがなかったら

もしキャッシュ DNS サーバに client.sharefull.com の問い合わせ結果のキャッシュがなかったら、キャッシュ DNS サーバは、スタブリゾルバの代わりに 「ルートネームサーバ」 → 「.com のネームサーバ」 → 「sharefull.com のネームサーバ」 へと順に問い合わせを行います。

そして、IP アドレスが分かったら、スタブリゾルバに「IP アドレスわかったよ〜」という問い合わせ結果を返します。スタブリゾルバは、問い合わせ結果から IP アドレスを取り出して、ブラウザから指定されているメモリー領域に書き込みます。

これでブラウザは client.sharefull.com の IP アドレスをゲットできたので、名前解決のプロセスは終了です。IP アドレスを利用するときは、このメモリー領域から IP アドレスを抜き出して Web サーバにリクエストを送るという流れになります。

Amazon Route 53

Amazon Route 53 というのは、AWS が提供するクラウド上の DNS サービスのことです。 シェアフルでは、この Amazon Route 53 というのを使って、ネームサーバの運用をしています。

なので、先ほど出した図では、⑤ で「sharefull.com のネームサーバにきいて」と書きましたが、実際には「Route 53 のネームサーバにきいて」となります。

問い合わせを受けた Route 53 は client.sharefull.com のドメイン名に対応する IP アドレスを検索して、結果をキャッシュ DNS サーバに返します。そのあとは、先程書いた流れと同様です。

もし見つからなかったら

IP アドレスを探し回って、それでも見つからなかったらエラーが返されます。  ユーザーはこのような画面を見ることになると思います(´∵ `)

ポート番号

ポート番号 とは、TCP/IP において、同じコンピュータ内で動作する複数のソフトウェアのうち、どれと通信するのか指定するための番号のことです。

IP アドレスを「電話番号」と例えるならば、ポート番号番号は電話をかけたときに「○○ さんをお願いしたいのですが」と言って、話す相手を呼び出すような仕組みです。

この「○○ さんをお願いしたいのですが」というのは、URL のドメイン名の 末尾 にコロン (:) と決められたポート番号を指定すれば、相手の指定ができます。

たとえば http://example.com/ の場合は、http://example.com:80/というような感じです。80というのは、HTTPだからです。

client.sharefull.com の場合は、HTTPS なのでhttps://client.sharefull.com:443/と指定します。

私たちがブラウザで URL にポート番号を入力しなくても Web サイトにアクセスできるのは、スキームを見て「http:」だったら 80 番、「https」だったら 443 番というように、自動的にポートを割り当てているからです。

HTTP リクエストの送信

次は Web サーバに送信する HTTP リクエストについてです。

HTTP リクエスト とは、ブラウザから Web サーバへ送信される要求のことです。 ブラウザがどのように HTTP リクエストを送信するのか見ていきたいと思います。

「url を解読する」でドメイン名とパス名が判明したので、ブラウザはそれを元に HTTP リクエストを作成します。

HTTP リクエストは「リクエストライン」と「ヘッダー」と「メッセージボディ」というフォーマットで作成されるらしいですが、そんなことを言われてもよく分からないので、実際にどんなリクエストが送信されているのかコマンドを叩いて確認してみたいと思います。

使用したのは以下のコマンドです。

出力された HTTP リクエストの中身。

HTTP リクエスト

GET / HTTP/2 Host: client.sharefull.com User-Agent: curl/7.64.1 Accept: /

実際にはこのリクエストのあとに、レスポンス内容も続けて出力されているのですが、それは次の「HTTP レスポンスの送信」で触れることにします。

リクエストの構成をわかりやすいように図で表すと、以下のようになります。  この中で、一番大事なのは 1 行目の リクエストライン です。

GET / HTTP/2 は、左端からそれぞれ「HTTP メソッド」「リクエスト先の URI」「HTTP のバージョン」のことを指します。

GET は最も一般的に使用されるメソッドで、ブラウザが Web サーバに対してページの取得を要求することを表しています。どのページを要求しているかというのは、左から 2 番目に書かれた / のことです。これは URL の中に埋め込まれているパス名からそのまま書き写されます。

また、フォーマットが「リクエストライン」と「ヘッダー」と「メッセージボディ」であるのに対して、実際のリクエストではメッセージボディが存在しないのは、HTTP メソッドが GET だからです。GET の場合は、メソッドと URL だけで Web サーバは何をすべきか判断できるので、メッセージボディに何かを書く必要は無いからです。

ロードバランサー

ロードバランサー とは、Web サーバにかかる負荷を複数に分散させるための装置です。「負荷分散装置」とも呼ばれます。

Web サービスにおいて、1 台のサーバのみで運用するとアクセス集中でサーバがダウンしたときなど、サービス停止に追い込まれてしまうので、複数サーバを用意するのが一般的です。

ロードバランサーは、これら複数台の Web サーバを束ねて、Web サーバに来たリクエストをバランス良く振り分ける装置のことを言います。

ロードバランサーの有能な機能として、サーバの状態を把握する ヘルスチェック と、同じクライアントのリクエストを継続的に同一のサーバに振り当てる セッション維持 というのがあります。

ヘルスチェック

配下にある Web サーバが正常に稼働しているかどうかを常にチェックする機能です。 もし正常に応答を返さなかったら「異常」だと判断して、そのサーバに対してはリクエストを割り振らず、別の正常なサーバに割り振ります。

セッション維持

同一ユーザーからのアクセスは同じサーバに振り分ける機能です。

これがないと、ログインをしても、その次の通信でロードバランサーが別のサーバにリクエストを飛ばしてしまったときに、サーバは前の通信状態を知らないので、「あなた誰ですか?」となります。

なので、送信元の IP アドレスをチェックするなどの方法で、同じユーザーからのアクセスは、同じサーバに振り分けます。

また、同じクッキーを持つ通信は必ず同じ Web サーバに振り分けるクッキーによるセッション維持の方法もあります。

HTTP レスポンスの送信

リクエストを送ると、Web サーバからはレスポンスが返ってきます。 「HTTP リクエストの送信」で省略していた HTTP レスポンスの中身は、以下のとおりです。

リクエストと違って、レスポンスの 1 行目は ステータスライン と言います。HTTP/2 200 というのは、それぞれ「HTTP のバージョン」と「ステータスコード」を表しています。

ステータスコード というのは、リクエストが成功したのかエラーが起きたのかを表すコードです。この場合は 200 なので、Web サーバの処理が無事成功したことを表しています。

また、ヘッダーの中には content-type: text/html; charset=utf-8 というものが記載されています。メッセージボディに入っているデータが、どのような形式なのかを示すものです。この場合「コンテンツは HTML ファイルであり,その文字コードは UTF-8 である」ことを表しています。ブラウザはこれを見て、データをどのように処理するかを判断します。

メッセージボディには、content-type で記載されたとおり、リクエストしたリソースである HTML が格納されます。 レスポンスが返ってきたら、メッセージボディからデータを取り出して、ブラウザに表示させます。

HTML のレンダリング

ブラウザレンダリングには、大きく分けて 4 つのフェーズがあります。

最初に行う処理は Loading です。描画に必要な HTML、CSS、JavaScript、画像などのリソースを読み込みます。 このとき一番最初に取得されるリソースは、HTML ファイル です。

ブラウザはこの HTML ファイルを上から順に読み込んでいって、途中で CSS や JavaScript や画像などの外部のリソースを発見したら、Web サーバにその都度問い合わせてリソースを取得します。

読み込んだリソースは、レンダリングエンジンの内部リソースに変換されます。 HTML は DOM ツリーに、CSS は CSSOM ツリーにそれぞれ変換されます。これらは後続のフェーズである Rendering や Painting で利用されます。

それが終わったら、今度は Scripting という処理に入ります。 Scripting では、字句解析 → 構文解析 → コンパイルという処理が終わって、はじめて JavaScript のコードが実行されます。

シェアフルでは JavaScript のフレームワークとして Vue を使っているので、JavaScript のコードが実行されると、Vue が呼び出されます。

Vue の中に API 呼び出しの処理があった場合、API サーバにリクエストをしてデータの取得を行います。

JSON データの取得

例えばシェアフルの Web ページでは、画面の右上にログインしているユーザーの名前、所属部署名を表示します。これらのデータは、API サーバを利用して取得します。

API とは、Application Programming Interface の略で、「プログラム同士をインターフェースを通じで繋げる機能」のことです。API サーバ は、API の仕組みを利用してデータを提供するサーバのことを言います。

ブラウザは、エンドポイントに対して、HTTP メソッドやパラメータを指定してリクエストを送ることで、API サーバにデータを要求します。

シェアフルでは、GraphQL を使っているので、エンドポイントは単一になります。全て/graphql で、POST メソッドです。欲しいリソースは、メッセージボディに記載することで指定します。ヘッダーに content-type:application/json と指定して、JSON 形式でリクエストを送ります。

API サーバは、受け取ったリクエスト内容を元に SQL 文を発行して、DB サーバに問い合わせ、データを取得します。これを JSON 形式に整形してブラウザに返します。基本的に以下のような構造になります。

レスポンス

{ "data": {...}, "errors": [...] }

data にはクエリ結果、errors にはエラー内容が格納されます。 ブラウザはこの結果を受け取って、描画していきます。

おわり

ブラウザのアドレスバーに URL を打ちこんで、Enter を押したとき何が起こるのか? という説明はこれで以上になります( ‘-‘ )ง

SPA

ページが表示された後ですが、SPA ( Single Page Application ) についても触れておきたいと思います。 SPA とは、最初に与えられた 1 枚の HTML ファイルをもとに、差分がある箇所のみ更新していって、複数ページあるように表現するアプリケーションのことです。

従来の Web アプリと SPA では、どのような違いがあるのか見ていきます。

従来の Web アプリ

リクエストの送信

サーバ側で HTML を生成し、ブラウザに返す

ブラウザで HTML を描画

従来の Web アプリでは、上記の流れの繰り返しになります。 ユーザーの操作の度に、ブラウザはページ全体の再読み込みを行います。

SPA

SPA では、変更があった部分だけ書き換えられます。

リクエストの送信

サーバ側で HTML を生成し、ブラウザに返す

ブラウザで HTML を描画

差分の更新に必要なデータをリクエストする

サーバ側で JSON データを用意して、ブラウザに返す

JavaScript の DOM 操作で差分を更新する

上記の 1〜3 までは、従来の Web アプリと同じです。初回アクセス時のみ HTML を読み込みます。 HTML の描画後は、ユーザーからの操作に応じて 4〜6 の部分を繰り返します。

もし、ヘッダーやフッターが全ページ共通のものであったら、書き換える必要はないので変更せずに置いておき、ページ遷移の際はそれ以外のパーツを変更していきます。

そうすることで、Web サーバへの通信量削減やリロードによる再レンダリングを減らせるため、より高速なページ遷移の実現ができるようになります。