9月15日(土) DNSについて追記しました。バックエンド?今はクラウドがきっと上手くやってくれるので深く考える必要は無いんですよ(知らないので書けません😔)
問題のツイート
面接の質問で「ブラウザを立ち上げてページが表示されるまでの仕組みを全て知ってる限り説明してください」ってのをやると結構Web系の知識どれだけあるか分かると思ってる
— 🍛🍺 (@tan_go238) September 10, 2018
解釈
今回は「ChromeのURL欄に入力してからページが表示されるまで」をやります。ブラウザの起動云々はWeb系の話じゃないと信じてます。
1. HTTPリクエストが飛ぶ
**HTTP2のヘッダ圧縮技術**に全て書いてありました。
(追記)下のリクエストヘッダはテキストで書かれていますが、実際にはこれをバイナリにしたものが飛んでいるとのことです。segfoさんありがとうございます!
更にいうと、TLSセッションの確立についても抜けてますし、ALPNの話も、http/2上でのネゴシエーション(サーバプッシュどうするかとか)の話がないのでhttp/2前提であれば少々どころではなく相当抜けがあるかと思います。
らしいです。この辺りは僕の知識がなくて色々抜けているのでこの記事だけを読んで分かったつもりにならないよう気をつけましょう。
:authority: www.google.co.jp
:method: GET
:path: /
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
accept-encoding: gzip, deflate, br
accept-language: ja,en-US;q=0.9,en;q=0.8
cache-control: max-age=0
cookie: 🤫
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36
x-client-data: CJW2yQEIpbbJAQjBtskBCKmdygEI2J3KAQjancoBCKijygEIh6fKAQ==
今回はリクエストメソッドがGETなのでヘッダのみですが、POSTやPUTの場合はデータがリクエストボディで送られます。詳しくは**HTTPメソッド(CRUD)についてまとめた**などを参照してください。
余談ですが、何年か前に**【パズル1】ほとんどのエンジニアには解けるが、下位10%のダメなエンジニアにだけ解けないパズル?**で遊んだのを思い出しました。楽しくHTTPメソッドについて学べるパズルなので解いていない人はぜひ解いてみてください。
HTTP/1.1 200
status: 200
date: Tue, 11 Sep 2018 09:36:30 GMT
expires: -1
cache-control: private, max-age=0
content-type: text/html; charset=UTF-8
strict-transport-security: max-age=3600
content-encoding: br
server: gws
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
set-cookie: 1P_JAR=2018-09-11-09; expires=Thu, 11-Oct-2018 09:36:30 GMT; path=/; domain=.google.co.jp
alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"
レスポンスヘッダは上みたいな感じで、このヘッダと一緒に届くレスポンスボディが<!doctype html>〜〜
というhtmlドキュメントです。
もっと低レイヤーについては**OSI参照モデルまとめ**が分かりやすいです。
(追記)HTTPリクエストからレスポンスまでを飛ばし過ぎという声が多かったのでもうちょっと書きます。
DNS
DNS?何ですかそれは。
- ドメイン名のしくみ | 何も知らない人用
- DNSパケットフォーマットと、DNSパケットの作り方 (1/2) | じゃあ実際DNSプロトコルってどんなデータを送ってるの?という人用
- Cloudflareが1.1.1.1で超高性能DNS始めたし、いっちょ俺のパソコンもDNS over HTTPSしてみる | 俺は検閲には屈しねぇ!という人用
DNSプロトコルってよく分かりませんしHTTPSで名前解決できたら楽でいいですよね。
**(追記)**なんかはてブとかtwitterとか見てるとDNSに興味津々の方が多いようだったので詳しく調べてみました。
###DNSプロトコル
0020 xx xx xx xx xx xx xx xx xx xx c4 db 01 00 00 01
0030 00 00 00 00 00 00 03 77 77 77 06 67 6f 6f 67 6c
0040 65 02 63 6f 02 6a 70 00 00 01 00 01
c4 db
がトランザクションIDです。どのクエリとどの応答が一致しているかを確かめるために使用します。
次の01 00
は2bitで表すと0000 0001 0000 0000
となります。まず前半の8bitを見ていきます。
-
0... ....
先頭の1bitはクエリなら0、レスポンスなら1となるフラグです。 -
.000 0...
次の4bitはオペレーションコードです。0は通常のクエリです。 -
.... .0..
管理権限フラグだそうです。DNSが権威サーバーかどうかについてを表しており、リクエストでは意味のないbitです。 -
.... ..0.
古のDNSは1パケット512byteまでしか扱えなかったらしく、メッセージが512byteを超えた場合には途中で切り捨てられていることを示す必要があったようです。切り捨てられた場合1になります。 -
.... ...1
フルリゾルブを要求するbitです。
後半は全て0ですが、ほとんどがレスポンスでしか意味のないbitのようです。クエリで意味のあるのは.Z.C ....
の2箇所で、Zは予備bit、CはDNSSECを禁止する要求らしいです。
その後00 01 00 00 00 00 00 00
と0が続きますが、前から2byteずつ「Questionセクション」、「Answerセクション」、「Authorityセクション」、「Additionalセクション」の数を表しています。今回はQuestionセクションが1つ(www.google.co.jp)なので最初だけ1であとは全部0です。
残りの部分がQuestionセクションです。
03 77 77 77 06 67 6f 6f 67 6c 65 02 63 6f 02 6a 70 00 00 01 00 01
-
03
文字数 -
77 77 77
www -
06
文字数 -
67 6f 6f 67 6c 65
google -
02
文字数 -
63 6f
co -
02
文字数 -
6a 70
jp -
00
終端のnull -
00 01
どのレコードを問い合わせるか?について。1はAレコード -
00 01
どのクラスを問い合わせるのか?について。クラスって何やねん。1はインターネットを表すらしい...。
0020 xx xx xx xx xx xx xx xx xx xx c4 db 81 80 00 01
0030 00 01 00 00 00 00 03 77 77 77 06 67 6f 6f 67 6c
0040 65 02 63 6f 02 6a 70 00 00 01 00 01 c0 0c 00 01
0050 00 01 00 00 00 0d 00 04 ac d9 a1 43
-
c4 db
トランザクションIDです。リクエストと同じ。 -
81 80
1000 0001 1000 0000
です。前半8bitは上を見てください。-
1... ....
サーバーがフルリゾルブ出来たかどうか。 -
..0. ....
DNSSECに成功していたら1となるフラグ。 -
.... 0000
応答コード。0はNo Error
-
次の8byteは4つのセクション(Question、Answer、Authority、Additional)の数を返すのですが、リクエストと比べてみるとAnswerの数が1増えていることが分かりますね。
-
03 77 ~ 00 01
Questionセクションの中身。リクエストと同じものが返って来ています。 -
c0 0c
1100 0000 0000 1100
です。- 最初の
11..
はポインタであることを示しているらしいです。 - 残りの
..00 0000 0000 1100
は10進数だと12に相当するのですが、これはDNSレスポンスの先頭から12byte後を示していて、つまり03 77~
でありwww.google.co.jpです。
- 最初の
-
00 01
タイプがAレコードである事を示しています。 -
00 01
インターネット・クラス(謎) -
00 00 00 0d
TTL=13 -
00 04
データの長さ -
ac d9 a1 43
172.217.161.67
DNS over HTTPS (DoH)
DNSプロトコルは暗号化されてないので中身が丸見えで危険だし、そもそもDNSプロトコルって覚えるの面倒なんだけど。。。httpsでクエリ飛ばせないの?という状況を解決できるのがDoHです。
curl -H 'accept: application/dns-json' 'https://1.1.1.1/dns-query?name=www.google.co.jp&type=A'
{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "www.google.co.jp.", "type": 1}],"Answer":[{"name": "www.google.co.jp.", "type": 1, "TTL": 18, "data": "216.58.197.131"}]}
使いやすすぎワロタ
(追記)1.1.1.1はCloudflareの提供するPublic DNSサービスです。
WikipediaのPublic DNSサーバーの一覧ページなどでどのPublic DNSサーバーがDoHに対応しているかが分かります。
2. レンダリングエンジンが描画を行う
レスポンスボディで返ってきたhtmlドキュメントを元にブラウザに搭載されているレンダリングエンジンが画面を描画します。
Chromeの場合はBlinkというエンジンが搭載されています。
(image from How Blink works by Kentaro Hara)
で、このBlinkで行われているレンダリングプロセスが下図です。
(image from How Blink works by Kentaro Hara)
htmlドキュメントを解釈してDOMを構築し、styleを当てはめてレイアウトしてから描画です。あとは**Life of a Pixel 2018**を見てください。マジで分かりやすいのでオススメです。これを見た後じゃあ自分で書き直そうという気にはなれませんね。
でも「どうしてもお前が書いた解説が読みたいんだ!!!」という人の為に書きます。正直これ以降の部分は質が微妙です。プロのあなたの助けを求めています。コメント・編集リクエストお待ちしています。
DOMとは
Document Object Modelの略です。chromiumのDOMのページに実装などの情報がまとまっています。
「いやDocument Object Modelって何やねん」となると思うのですが、端的にいうと木です。tree。
- データ構造の選択次第で天国と地獄の差 (3/3) | tree構造とは
DOMはChromeで「⌘+⌥+i」を押すと出現する検証モードの「element」の項目に並んでいるアイツです。キーボードに⌘キーや⌥キーが無い場合はこちらへどうぞ。
DOCUMENT (always the root)
└─HTML
├─HEAD
│ └─...
└─BODY
├─DIV
│ └─...
├─SCRIPT
└─SCRIPT
それぞれのnodeの実装
(image from core/dom/README.md by きらきら☆はやとたん)
親はfirstChildとlastChildの情報しか持ってないそうです。連結リストってヤツですね。
レンダリングエンジンはHTML文章を解析し、上記のようなDOMツリーを構築します。
Style
レンダリングエンジンはCSSを解析してCSS Object Modelツリーを構築します。
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
⬇️CSSOM
(image from オブジェクト モデルの構築 by Ilya Grigorik)
DOMとCSSOMを組み合わせるとRender Treeが完成します。
(image from レンダリング ツリーの構築、レイアウト、ペイント by Ilya Grigorik)
ちなみにこのCSSOMはJavascriptにAPIが公開されているので**window.getComputedStyle() で要素のスタイルを取得する**事ができます。
Layout
レンダリングツリーを元に、個々の要素がウィンドウ内のどこに配置されるかを決めていきます。
(image from CSS Box Model Module Level 3)
上の画像みたいなCSS Box Modelを使って、親から子へと再帰的に、つまり子は親のcontent内にレイアウトされていきます。CSSでは親要素との相対的な位置関係で記述されていますが、レイアウト処理の出力段階では画面上の絶対的な位置になります。
(image from Life of a Pixel 2018)
(image from Life of a Pixel 2018)
中のコンテンツがスペースをオーバーしてもOKで、その時の選択肢は全て表示する「visible」、オーバーした部分を表示しない「hidden」、そしてスクロールできるようにする「scroll」の3つです。
- SCROLLING IN BLINK | スクロールの仕組みについて。position: stickyって初めて知ったけどめっちゃ便利じゃないですか?もっと早く知りたかった……
後LayoutNGなる新しいレイアウトシステムが開発中らしい。
Paint
表示されるべき要素とそのスタイルが計算できたので、最後にそれぞれの要素を実際のピクセルに落とし込んでいきます。
(image from Life of a Pixel 2018)
DOMツリーとは関係なく、描画リストの順番で描画されていきます。CSSのz-indexを設定する事で順番を明示的に指定できます。
(image from Life of a Pixel 2018)
また、backgroundやfloatなどの要素によってどれが上に来るのかが変わるので、背後の要素が部分的に前に出て来ることもあるらしいです。
この描画工程が終わると画面にページが表示されます。
Compositor thread
ブラウザのレンダリング処理は全てmainスレッドという一つのスレッドで行われます。スレッドが一つしか無いというのは、javascriptなどで何か重たい処理を実行してしまいスレッドを埋めてしまった場合にスクロールなど基本的な操作すらおぼつかなくなってしまう危険があるということです。
そこで、スクロールやアニメーション、ズームなど「レイヤーの移動・スケールの変更」で対処できるものに関しては、レンダリングが終わった後に別スレッドへと移動させ、スムーズな操作を可能にしようというのが「Compositor Thread Architecture」です。
(image from Life of a Pixel 2018)
終
プロの方々のコメントや編集リクエストをお待ちしています。よろしくお願いします。
この記事よりも**Life of a Pixel 2018**を読みましょう。ブラウザのレンダリングについて学ぶなら最高にオススメのスライドです。