第1話: HTTP/2 の雑多なメモ 〜 REAL WORLD HTTP第7章より 〜

yagisuke.hatenadiary.com の第1話です。

HTTP/2って

ワーキンググループにおいて HTTP/2.0 で制定作業をしていたが、 後に HTTP/2 に名称を置き換えることになりました。
参考: https://ja.wikipedia.org/wiki/HTTP/2#.E5.90.8D.E7.A7.B0

マイナーアップデートなんてしてられないぜってことかな。

大型アップデートだがこれまで通り

HTTP/2はHTTP1.1から16年ぶりの大きなアップデートでした。 HTTP/2においても「メソッド」、「ヘッダー」、「ステータスコード」、「ボディ」という HTTPの提供する4つの基本要素は変わらない。
基本要素を忘れちゃった方はこちら: http://www.tohoho-web.com/ex/http.htm

目的は高速化

HTTP/2の目的は高速化に尽きます。
HTTP/1.0からHTTP/1.1にかけて、TCPソケットレベルで見ると次のような改善が行われてきました。

機能 効果
キャッシュ(max-age) 通信そのものをキャンセル
キャッシュ(ETag, Date) 変更がなければボディ送信をキャンセル
Keep-Alive アクセスごとに接続にかかる時間(1.5TTL)を削減
圧縮 レスポンスのボディサイズの削減
チャンク レスポンス送信開始を早める
パイプライニング 通信の多重化

1回の通信では、接続の確立リクエスト送信、受信と何往復もパケットが飛び交います。 また、データサイズを通信速度で割った時間だけ通信時間がかかります。 通信待ちがあれば、その分通信完了までの時間は長くなります。

これまで行われた高速化はこの通信のあらゆる箇所での高速化に寄与してきました。 HTTP/2ではこれまで手がつけられてこなかった「ヘッダー部の圧縮」や、 規格化されたもののいまひとつ活用されていなかった「パイプライニングの代替実装」が追加されました。
端的にまとめてくれている記事: http://blog.redbox.ne.jp/http2-cdn.html#HTTP11

ストリームによる通信の高速化

バイナリフレームの採用

HTTP/2の一番の大きな変化は、テキストベースのプロトコルから バイナリベースへのプロトコルに変化したという点です。

コンピューターは、バイナリしか理解できないので、 テキストで送られてきたらデータを解析する必要があり、 データを解析するオーバーヘッドが解消されたという訳です。

f:id:yagi_suke:20180121232213p:plain

画像: http://webcommu.net/http2/

各データは「フレーム」と呼ばれる単位で送受信が行われます。

TYPE FRAME TYPE ROLE
0 DATA リクエスト/レスポンスのボディ部分に相当
1 HEADERS リクエスト/レスポンスのヘッダー部分に相当
2 PRIORITY ストリームの優先順位を指定(クライアントのみ送信可能)
3 RST_STREAM エラーなどの理由でストリームを終了するために用いる
4 SETTINGS 接続設定を変更する
5 PUSH_PROMISE サーバプッシュを予告します(サーバのみ送信可能)
6 PING 接続の生存状態を調べる
7 GOAWAY エラーなどの理由で接続を終了するために用いる
8 WINDOW_UPDATE ウインドウサイズを変更する
9 CONTINUATION サイズの大きなHEADERS/PUSH_PROMISEフレームの断片

ストリームの多重化

これまでの問題
HTTPの問題

従来のHTTPでは、リクエストとレスポンスの組を1つずつしか同時に送受信できないことが、 パフォーマンス上のボトルネックになっています。

パイプライニングの欠点

このような制約のなか、HTTP/1.1では前回のリクエストの完了を待たなくてもいい「パイプライニング」 という仕組みがありました。パイプライニングを使うとクライアント側は前回のリクエストのレスポンスが 返ってくる前に余裕があれば、次のリクエストを送ることができます。

しかし、サーバー側はクライアントからのリクエストの順番通りに返さなくてはいけなく、 どこかで重いリクエストが来たらその処理以降のレスポンスがブロックされてしまいます。 これを「ヘッドオブライン・ブロッキング」問題と言います。

そして残念なことに、現在ほとんどのブラウザはパイプライニング機能を全く実装していないか、 デフォルトでオフにしています。 これには、実装の困難さから、 パイプライニングを正しく実装したサーバが少ないという事情があるようです。

f:id:yagi_suke:20180121232214p:plain

画像: https://www.slideshare.net/techblogyahoo/http2-35029629 P.20

更に、ブラウザから同時接続できるTCP接続数は最大6本であるため、 Webサイトに画像が100枚あっても、同時にリクエストとレスポンスできる画像は、たった6枚。 非常にリソースの無駄と考えたGOOGLE先生はHTTP/1.1を進化させるために頑張ることになります。

ストリームの多重化をすることで

リクエストとレスポンスの組を1つずつしか同時に送受信できないという問題に対して、 HTTP/2では1本のTCP接続の内部に、ストリームという仮想のTCPソケットを作って通信を行うことで対応しています。

ストリームはフレームに付随するフラグで簡単に作ったり閉じたりできるルールになっており、 通常のTCPソケットのようなハンドシェイクは必要ありません。 そのため、IDの数値とTCPの通信容量が許す限り、簡単に数万接続でも並列化できます。

HTTP/1.1
f:id:yagi_suke:20180121232221p:plain

画像: http://webcommu.net/http2/

HTTP/2
f:id:yagi_suke:20180121232252p:plain

画像: http://webcommu.net/http2/

要するに、あるストリーム上ではリクエストにあたるフレームが送信中だとしても、 別のストリームではレスポンスにあたるフレームを受信するといったことが可能になります。 これにより、全体的なパフォーマンスが向上し以下のようなリクエストとレスポンスが可能となりました。

f:id:yagi_suke:20180121232229p:plain

画像: https://www.slideshare.net/techblogyahoo/http2-35029629 P.20

ストリームの優先度

フロントエンドとしてはファイルの読み込む順番はきになるもの。

ストリームの多重化によりヘッドオブライン・ブロッキングの問題は解決しましたが、 あまり重要でないストリームが先にリソースを占有してしまい、 重要なストリームが返却を待たされてしまう可能性があります。

これを解決するために、HTTP/2ではクライアントがPRIORITYフレームを用いて、 ストリーム間に優先順位を付けることが可能となりました。 これはあくまでもリソースに余裕がないときの動作で、 後続のストリームをブロックするものではありません。

(ちなみに、PRIORITYフレームの送信が許されているのはクライアント側のみ)

参考: https://qiita.com/Jxck_/items/16a5a9e9983e9ea1129f

HTTP/2のアプリケーション層

ヘッダーとボディだけ

HTTP/2においても「メソッド」、「ヘッダー」、「ステータスコード」、「ボディ」という基本要素が 存在するのは変わりありませんが、メソッドとパス、ステータスコードプロトコルバージョンは すべて擬似ヘッダーフィールド化され、ヘッダーの中に組み込まれました。 上位のアプリケーションから見た機能性には変更ありませんが、 実装上は「ヘッダー」「ボディ」しかありません。

リクエストメッセージ
f:id:yagi_suke:20180121232205p:plain

画像: https://www.slideshare.net/techblogyahoo/http2-35029629 P.16

レスポンスメッセージ
f:id:yagi_suke:20180121232204p:plain

画像: https://www.slideshare.net/techblogyahoo/http2-35029629 P.17

高度な並列化もどんとこい

HTTP/1.1はテキストプロトコルでした。 ヘッダーの終端を探すには空行を見つけるまで1バイトずつ先読みして発見する必要がありました。 エラー処理などもあるため、サーバーとしてはパースまで込みで逐次処理をせざるを得ず、 高度な並列化は難しかったようです。

HTTP/2はバイナリ化され、最初にフレームサイズがはいっています。 TCPソケットのレイヤーでは、データをフレーム単位へと簡単に切り分けられるため、 受信側のTCPソケットのバッファを素早く空にでき、通信相手に対して、 次のデータを高速にリクエストできるようになっています。

フローコントロール

フローコントロールとは、ひとつのストリームがリソースを占有してしまうことで、 他のストリームがブロックしてしまうことを防ぐ仕組みです。

具体的な状況はいくつか考えられます。 - 大きなファイルの通信が帯域を食いつぶし、他の通信を妨害する。 - あるリクエストの処理にサーバが占有され、他のリクエストをサーバが処理できなくなる。 - 高速なアップロードを行うクライアントと、低速な書き込みをしているサーバとの間に挟まった プロキシが、調整のためにデータを貯めているバッファが溢れる。

こうした状況を防ぐために、HTTP/2ではTCPと、その上に多重化されたストリームの両者について、 フローコントロールする仕組みを備えています。

実際にはウィンドウサイズという閾値を設定し、この値の範囲内であればデータを送ることができ、 その値を使い切った場合、送信者はデータ送信を停止します。 受信者は、リソースが回復したことをWINDOW_UPDATEというフレームで通知し、 そこに設定された分の値だけウィンドウサイズを回復されることで、 送信者はデータの送信を再開するというものです。

詳細はこちらがオススメ: https://qiita.com/Jxck_/items/622162ad8bcb69fa043d

サーバープッシュ

HTTP/1.1では、ブラウザーがページを要求するとサーバーはHTMLを応答として送信しますが、 サーバーがHTMLに埋め込まれているJavaScript、画像、CSSのアセットを送信するには、 ブラウザーがHTMLを解析し、それらのリクエストを送信するまで待たなければなりません。

HTTP/2では、クライアントからのリクエスト内容をもとに、 サーバー側で必要ファイルを判別して事前にクライアントに送信できます。 例えば、リクエストしたHTMLにJavaScriptファイルの読み込み記載があった場合、 そのJavaScriptファイルを最初のHTMLリクエストの段階で送信しておくといった具合になります。

f:id:yagi_suke:20180121232242p:plain

画像: https://www.digicert.ne.jp/ssl/http2.html

HPACKによるヘッダーの圧縮

gzip圧縮の予定だった

HTTP1.xでは、CookieやUserAgentといったHTTPヘッダーがなんども同じ内容で送信されており、 オーバーヘッドが生じています。

そこでHTTP/2の前進であるSPDYでは当初、ヘッダーの圧縮にgzipを用いていましたが、 後に「CRIME」と呼ばれる攻撃手法が発見され、 圧縮率をさげざるをえなくなってしまいました。

CRIMEについて: https://www.scutum.jp/information/waf_tech_blog/2012/09/waf-blog-014.html

HPACKの誕生

そこでHTTP/2では、ヘッダーの差分だけを送るアルゴリズムを使ったHPACKという 圧縮機構を採用しました。

ただ肝心のCRIMEの脆弱性対策ですが、gzipよりも困難になりましたが、 完全に対策できているものにはなっていないそうです。 結局フレームにパディングを入れてサイズをごまかしたり、 機密度の高いヘッダー情報は圧縮用にインデックスしないなどHTTP/2時代でも CRIME攻撃を意識してHPACKを使わないといけない状況なんだそうです。

参照: http://d.hatena.ne.jp/jovi0608/20150527/1432723310

HPACKで使われる用語

Reference Set

前回送信したヘッダー名/値ペアのセット。クライアントやサーバーは、 ヘッダーの差分を計算するために接続毎にリファレンスセットを保持します。

Static Table

よく送信されるヘッダー名/値ペアにIDを設定したものを管理するテーブルです。 クライアントとサーバーの両方で保持されます。 送信するヘッダーがこのテーブルに含まれていた場合は、 設定されたIDを使用してヘッダーを送信することができます。

フロー

f:id:yagi_suke:20180121232233p:plain

画像: https://www.slideshare.net/techblogyahoo/http2-35029629 P.24

f:id:yagi_suke:20180121232237p:plain

画像: https://www.slideshare.net/techblogyahoo/http2-35029629 P.25

SPDYとQUIC

SPDY

SPDYはGOOGLE先生が開発していたHTTPの代替プロトコルでこれがそのままHTTP/2となりました。 2010年からCHROMEにSPDYを搭載し、2014年にHTTP/2にバトンタッチしました。

GOOGLE先生がSPDYを開発したのは、これまでのHTTPが改善してきた転送速度を一段と向上させるためです。 ウェブサイトの構成によって効果が大きく変わるため、導入の効果としては30%から、3倍以上まで、 色々な数値があげられましたが、並列アクセスでのブロッキングが減るため、 小さなファイルをたくさん転送するほど高速化します。

HTTP/1.x時代ではJavaScriptCSS、画像などなるべく少ないファイル数にまとめることで 高速化させるというのが常でした。

SPDYとHTTP/2時代では、ファイルをまとめる効果は小さくなります。 圧縮されているとはいえ、サイズは0ではないので、結合した方が通信量は減りますが、 細かい方が変更があったときもキャッシュが生き残る確率は上がります。 なので、結合してもしなくてもどちらも一長一短で差はあまりないかもしれません。

QUIC

SPDYなりQUICなり、早くして〜って感じ。

QUICが登場した背景

HTTP/2はTCP上で転送されるプロトコルであることに起因した問題が残されています。

このTCP接続を開始するためには、3wayハンドシェイクが行われています。 つまりこれは、接続を開始するたびにラウンドトリップ(ネットワークパケットの往復)が 追加されるということであり、新たな接続先に対し大幅な遅延が発生します。

それに加えて、暗号化された安全なHTTP接続を行うために、 TLSとのネゴシエーションも必要ということになると、 さらに多くのパケットをネットワーク間でやり取りしなければなりません。

一方、UDPはFire and Forget(撃ちっ放しの)プロトコルだと言えます。 メッセージがUDPで送信されると、後は宛先に到達するものだと想定されています。 なので、ネットワークでパケットを検証するために費やされる時間が少なくなります。 ただし欠点もあり、信頼性を確保するには、パケットのデリバリ確認ができるよう、 UDPの上に何かを構築しなければいけません。

そこで、登場するのがQUICプロトコルです。 QUICプロトコルは、接続を開始すると、全てのTLSHTTPS)パラメータを1つか2つのパケットで ネゴシエートすることができます。 QUICにおけるGoogleの狙いは、UDPTCPの良いところを取り、 最新のセキュリティー技術と組み合わせることということです。

f:id:yagi_suke:20180121232257p:plain

画像: http://jp.techcrunch.com/2015/04/20/20150418google-wants-to-speed-up-the-web-with-its-quic-protocol/

以下はHTTP/2 over TLS/TCPと、HTTP/2 over QUICの構成です。

f:id:yagi_suke:20180121232301p:plain

画像: http://internet.watch.impress.co.jp/docs/column/ietf2017/1060156.html

左側がHTTP/2 over TLS/TCP、右側がHTTP/2 over QUICです。 QUICは、TCPの機能に相当する輻輳(ふくそう)制御、ロスリカバリー、 およびHTTP/2の機能の一部であるストリーム制御、フローコントロールなどを提供します。 暗号方式のネゴシエーションや鍵交換にはTLS1.3の仕組みを使うそうです。

QUICの進捗はこちら

QUIC WGでは、2018年中にQUICベースプロトコルおよびHTTP/2へのマッピングRFCにするというマイルストーンで動いているので現在絶賛稼働中。 - git: https://github.com/quicwg/base-drafts - IETF: https://datatracker.ietf.org/wg/quic/about/

引用元や参考情報

Index: Real World HTTPの輪読会をした

Real World HTTPの輪読会をした

昨年の秋頃にReal World HTTPの輪読会をおこなった。 ぼくは2章と7章を担当。 この本はけっこうヘビーな内容だったけど、しっかり読んだらだんだん楽しくなっていった。 けど、また読もうとは思わない。笑 www.oreilly.co.jp

ということで、当時のメモ書きが出てきたので7章の 「HTTP/2のシンタックス: プロトコルの再定義」の部分を6回に分けて掲載します。文面はほとんど人のものを引用していて、自分の文章ではなく引用元のままなことが多めです。 (ご指摘あれば削除いたします)

HTTP/2のシンタックス: プロトコルの再定義

内容は以下のとおりです。

  • 1話. HTTP/2
  • 2話. Fetch API
  • 3話. Server-Sent Events
  • 4話. WebSocket
  • 5話. WebRTC
  • 6話. HTTPウェブプッシュ

SourceTreeが自動でコミットメッセージのスペルミスを修正しちゃう問題

問題と背景

GitのGUIクライアントにSourceTreeを使っており、 英語でコミットメッセージを書くとスペルチェックをしてくれるのですが、 コミット時に自動でスペルを修正してしまうことがありました。

例えば、「add axios」ってコミットメッセージ欄に入力してコミットしたら、、 「add axis」と自動的に修正されてコミットされてしまった。 f:id:yagi_suke:20170902162925p:plain

これまで別に気にしてなかったんですが、、 同僚に突っ込まれてしまったので、 今回この問題を解決することにしました。

解決手順

  1. コミットメッセージ欄で右クリック
  2. スペルと文法
  3. スペルを自動的に修正のチェックを外す f:id:yagi_suke:20170902162008p:plain

ためしに、再度「add axios」って入力してコミットしてみました。 勝手に修正されずに無事「add axios」でコミットできました。 f:id:yagi_suke:20170902163931p:plain

備考

まったく同じ記事がありました。。 SourceTreeの自動スペル修正機能を無効にする - たけぞう瀕死ブログ

brewで気軽にVagrant環境を準備したよ for Mac

オフィスでYoutube

GW中ですがちょっと勉強でもするかと、休日に会社のオフィスへ。
ちょっと古いですが、O'Reilly Japan - インタラクティブ・データビジュアライゼーションでD3.jsでも触ろうと。意識高い!w

やる気は満々!!
だったんですが。。肝心の本を間違えた。
バンに入っていたのは昔読んだことのある魚さんのオライリー本。

呆然..
せっかく横浜から渋谷にでてきたのに。。
ん。。。。なにやってんだろ。。

とりあえずYoutubeNARUTOの中忍試験でもみよっ。
やる気スイッチOFF…

出会い

NARUTOにも飽きて会社の本棚を物色。
くじらさんの本があった。これ。 www.socym.co.jp この人には実践力を身につける Pythonの教科書 | マイナビブックスの本で出会い、 分かりやすい構成・文章でファンになったのです。
んで、中身をみるとD3.jsを少し触るそう!おおっ。
運命✨
おまけにクローリングについても勉強できる! ← 本はこっちが本題だけど
ということで、この本を読み始めようかと。

Vagrantインストールしてログインしますー

やっと本題。ただし、本題は短いです。
nodeを動かすための環境として本ではVagrantを使うみたいです。
Vagrantを使うためにはvirtual boxも必要になるみたいです。
そう、わたくしVagrant初めてです。

っで、Macbrew入れている人はサクッと以下の手順でVagrant環境作れます。
とりあえず、terminalを開きます。

virtual boxをインストール
$ brew cask install virtualbox
vagrantをインストール
$ brew cask install vagrant
vagrant managerをインストール
$ brew cask install vagrant-manager
仮想マシンを動かしたいディレクトリの作成・移動
$ mkdir ~/Documents/sample
$ cd ~/Documents/sample
vagrantの設定ファイルを作成
$ vagrant init
vagrantの設定ファイルを編集

上記で作成されたVagrantfileのconfig.vm.box = "base"を以下のように変更。
Discover Vagrant Boxes | Atlas by HashiCorpからosのイメージを選ぶ。
他にもosの選び方はあるみたいだけど。

config.vm.box = "puphpet/centos7-x64"
vagrantの起動
$ vagrant up

初回は結構時間かかります。

ssh接続
$ vagrant ssh

これで接続できれば環境準備終わりです。

Wiresharkでパケットキャプチャしてみた

Wiresharkとは

TCP/IPで流れるデータを確認できる「パケットキャプチャ」ソフトの代表格。
Windows版, Mac版, Linux版それぞれあります。
ここではMac版を試します。
Wiresharkの公式サイト: https://www.wireshark.org/

Wiresharkをインストール

Wiresharkはhomebrewでインストールできるらしい。
参考: homebrewでwiresharkのQt版をインストール - Qiita

それではTerminalを開いて、下記コマンドをえいやっ。

$ brew install wireshark --with-qt

なんかwaringがでて--with-qtは非推奨だぜって怒られてます。
が、勝手に推奨されているものをinstallしてくれているので問題無し。 f:id:yagi_suke:20170311194901p:plain

古かったみたいです。最新は以下のようです。

$ brew install wireshark --with-qt5

それではWiresharkを起動してみます。

$ wireshark

….ありまっ。。No interface foundとでてきました。。 f:id:yagi_suke:20170311194924p:plain

先ほどのinstall時のログを見てみるとIf your list of available capture interfaces is empty と書いてあり、brew cask install wireshark-chmodbpfを実行してって。 f:id:yagi_suke:20170311203502p:plain

ということで、言われるがままに、えいやっ。

$ brew cask install wireshark-chmodbpf

そして再度Wiresharkを起動すると、
今度はインターフェイスリストが表示されました。 f:id:yagi_suke:20170311203435p:plain

上記を踏まえると、以下の手順でやるとスムーズですね。

$ brew install wireshark --with-qt5
$ brew cask install wireshark-chmodbpf



補足:

homebrewを入れていない人は
下記記事を参考にしてみて使えるようにしてみては。
参考: パッケージ管理システム Homebrew - Qiita

Wiresharkの使い方

Wiresharkを起動したら、「キャプチャ」メニューから「オプション」を選択。
f:id:yagi_suke:20170311211258p:plain インターネットとの通信をパケットキャプチャしたいので、
インターネットと繋がっているネットワークを選択して、開始! f:id:yagi_suke:20170311211313p:plain

UDPのパケットを見る

画面上部のFilterにudp.port==53といれると、
DNSのパケットに絞り込んで見れます。 f:id:yagi_suke:20170311211328p:plain

TCPのパケットを見る

TCPUDPと同様にFilterにtcp.port==80といれると、
TCPのパケットに絞り込んで見れます。

ただし、上記だけでは他のアプリの通信も拾ってしまうので
パブリックIPアドレスを絞り込み条件に加えます。

パブリックIPアドレスはTerminalからnslookupコマンドで確認できます。
こんな感じです。

$ nslookup leihauoli.com

すると、こんな感じで表示され、54.249.34.46がパブリックIPアドレスです。 f:id:yagi_suke:20170311211346p:plain

Filterにtcp.port==80 and ip.dst_host==54.249.34.46のように入力すれば
通信を見たいサイトのパケットを確認できます。 f:id:yagi_suke:20170311211409p:plain

上記imgのNo206 - 209は「SYN」「SYN+ACK」「ACK」の3ウェイハンドシェーク。
また、中央部にはGETコマンドが送信されていることもわかります。


ひっさしぶりの投稿でした。