[HTTP2] youtube.comでHTTP2のフロー制御のテスト

コメントを受けて一部修正しました。
他、データの加算が間違っていたので修正。

#フロー制御

HTTP2にはストリームで受信できるデータ量をコントロールするフロー制御という仕組みがあります。

フロー制御では二つのことを決めています。

⚫️コネクション全体で受信できるデータ量　(INITIAL_WINDOW_SIZE)
　　(デフォルト65535)
⚫️一つのストリームで受信できるデータ量
 　　(デフォルト65535)

これは最初に交換するSETTINGフレームでクライアントとサーバーが取り決めます。その後に一つのストリームでデータを受信するたびに減っていきます。
0になったらデータを受信することはできないので、WINDOW_UPDATEフレームで任意のバイト数を加算させます。

#例

▪️step0.

初期値
 INITIAL_WINDOW_SIZE 100
 コネクション全体 100

▪️step1.
　ストリームID1で50K受信

ウィンドウサイズ
 ストリームID1 50K
 コネクション全体 50K

▪️step2.
　ストリームID1で50K受信

ウィンドウサイズ
 ストリームID1 0K
 コネクション全体 0K

▪️step3.
　WINDOW_UPDATEフレームでストリームID1に200K指定

ウィンドウサイズ
 ストリームID1 200K
 コネクション全体 0K

このままではコネクション全体が0なのでストリームID1に余裕があっても受信できない。

▪️step4.
　WINDOW_UPDATEフレームでストリームID0(コネクション全体)に200K指定

ウィンドウサイズ
 ストリームID1 200K
 コネクション全体 200K

これで再び受信可能になる。

▪️step5.
　WINDOW_UPDATEフレームでストリームID0(コネクション全体)を70000K指定

ウィンドウサイズ
 ストリームID1 200K
 コネクション全体 70200K

コネクション全体を初期値より増やすことが可能

つまり、受信時は両方減り、加算時は指定したものだけ加算します。


#youtubeでテスト

youtbeでテストしてみたところ、データ受信量が多くて良いサンプルが採れました。
nghttp2を使用してフロー制御の簡単な例を見てみました。


```
vagrant@nghttp2:~$ nghttp -nv https://www.youtube.com/
[  5.016] Connected
[  5.022][NPN] server offers:
          * h2-15
          * h2-14
          * spdy/3.1
          * spdy/3
          * http/1.1
The negotiated protocol: h2-14
```

NPNによりHTTP2ドラフト14が採用されました。
続いてSETTINGフレームの交換が行われます。

```
[  5.030] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
          [SETTINGS_MAX_FRAME_SIZE(0x05):16384]
[  5.032] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=983041)
[  5.033] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
```

最初のやりとりの概要

youtubeサーバーは自身が受け取るストリームの初期ウィンドウサイズが65535。
自身が受け取る1回のフレームの最大サイズ(SETTINGS_MAX_FRAME_SIZE)が16384。
そのすぐ後でWINDOW_UPDATEでコネクション全体のウィンドウサイズに983041を加算してアップデートしている。

クライアントはストリームの初期ウィンドウサイズを65535に設定。

クライアント
　初期ストリームウィンドウサイズ　65535
　初期コネクションウィンドウサイズ　65535

```
[  5.034] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  5.034] send HEADERS frame <length=39, flags=0x05, stream_id=1>
          ; END_STREAM | END_HEADERS
          (padlen=0)
          ; Open new stream
          :method: GET
          :path: /
          :scheme: https
          :authority: www.youtube.com
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/0.7.10-DEV
[  5.039] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  5.089] recv (stream_id=1) :status: 200
[  5.089] recv (stream_id=1) accept-ranges: none
[  5.090] recv (stream_id=1) alternate-protocol: 443:quic,p=0.5
[  5.090] recv (stream_id=1) cache-control: no-cache
[  5.090] recv (stream_id=1) content-type: text/html; charset=utf-8
[  5.090] recv (stream_id=1) date: Thu, 02 Apr 2015 12:00:48 GMT
[  5.090] recv (stream_id=1) expires: Tue, 27 Apr 1971 19:44:06 EST
[  5.090] recv (stream_id=1) p3p: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl=ja for more info."
[  5.090] recv (stream_id=1) server: gwiseguy/2.0
[  5.090] recv (stream_id=1) set-cookie: YSC=BR-dzjKtehY; path=/; domain=.youtube.com; HttpOnly
[  5.090] recv (stream_id=1) set-cookie: VISITOR_INFO1_LIVE=6ZUYN3dlY40; expires=Tue, 01-Dec-2015 23:53:48 GMT; path=/; domain=.youtube.com; HttpOnly
[  5.090] recv (stream_id=1) set-cookie: PREF=f1=50000000; expires=Tue, 01-Dec-2015 23:53:48 GMT; path=/; domain=.youtube.com
[  5.090] recv (stream_id=1) vary: Accept-Encoding
[  5.090] recv (stream_id=1) x-content-type-options: nosniff
[  5.090] recv (stream_id=1) x-frame-options: SAMEORIGIN
[  5.090] recv (stream_id=1) x-xss-protection: 1; mode=block; report=https://www.google.com/appserve/security-bugs/log/youtube
[  5.090] recv HEADERS frame <length=528, flags=0x04, stream_id=1>
          ; END_HEADERS
          (padlen=0)
          ; First response header
```

ここまでがACKの送受信、リクエストヘッダの送信とレスポンスヘッダの受信です。

```
[  5.091] recv DATA frame <length=5057, flags=0x00, stream_id=1>
[  5.496] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.504] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.505] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.505] recv DATA frame <length=11326, flags=0x00, stream_id=1>
```

「GET /」に対するbodyデータを受信しています。

5つのDATAフレームの受信で
5057 + 16384 + 16384 + 16384 + 11326 = 65535

65535消費したので、クライアントのコネクション全体とストリームID1の初期ストリームウィンドウサイズが0になりました。

```
[  5.505] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.506] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
[  5.512] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33738)
[  5.513] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=33738)
```

クライアント側からWINDOW_UPDATEフレームを送信して、ストリームID1とストリームID0(コネクション全体)のウィンドウサイズを加算させています。

それぞれ
コネクション全体とストリームID1に対して「33738 + 32951 = 66689」なので、今のウィンドウサイズは66689になります。

この時に送信できるサイズは「2^31」(2147483648)なので、nghttp2がなぜ2回に小分けにして送信しているのかは今のところ謎です。

この時の状態

コネクション全体 66689
ストリームID1　66689

```
[  5.514] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.525] recv DATA frame <length=15298, flags=0x00, stream_id=1>
[  5.525] recv DATA frame <length=1269, flags=0x00, stream_id=1>
[  5.526] recv DATA frame <length=16384, flags=0x00, stream_id=1>
```

今回受信したデータ量 
16384 + 15298  + 1269 + 16384 = 49335

コネクション全体 66689 - 49335 = 17354
ストリームID1　66689 - 49335 = 17354

```
[  5.526] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.527] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
```

今回、加算したデータ量 
コネクション全体 32951
ストリームID1　32951

コネクション全体 17354 + 32951 = 50305
ストリームID1　17354 + 32951 = 50305

```
[  5.529] recv DATA frame <length=15115, flags=0x00, stream_id=1>
[  5.530] recv DATA frame <length=2239, flags=0x00, stream_id=1>
```

今回受信したデータ量 
15115 + 2239 = 17354

コネクション全体 50305 - 17354 = 32951
ストリームID1　50305 - 17354 = 32951

以下同じ流れ。

```
[  5.532] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33738)
[  5.533] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=33738)
[  5.534] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.536] recv DATA frame <length=16193, flags=0x00, stream_id=1>
[  5.537] recv DATA frame <length=374, flags=0x00, stream_id=1>
[  5.537] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.538] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
[  5.539] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.541] recv DATA frame <length=16010, flags=0x00, stream_id=1>
[  5.542] recv DATA frame <length=1344, flags=0x00, stream_id=1>
[  5.542] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33738)
[  5.542] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=33738)
[  5.544] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.547] recv DATA frame <length=15040, flags=0x00, stream_id=1>
[  5.548] recv DATA frame <length=1527, flags=0x00, stream_id=1>
[  5.549] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.550] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
[  5.551] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.552] recv DATA frame <length=14857, flags=0x00, stream_id=1>
[  5.553] recv DATA frame <length=2497, flags=0x00, stream_id=1>
[  5.556] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33738)
[  5.557] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=33738)
[  5.557] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.560] recv DATA frame <length=15935, flags=0x00, stream_id=1>
[  5.560] recv DATA frame <length=632, flags=0x00, stream_id=1>
[  5.563] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=32951)
[  5.564] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
          (window_size_increment=32951)
[  5.565] recv DATA frame <length=16384, flags=0x00, stream_id=1>
[  5.569] recv DATA frame <length=15752, flags=0x00, stream_id=1>
[  5.570] recv DATA frame <length=1602, flags=0x00, stream_id=1>
[  5.570] recv DATA frame <length=965, flags=0x01, stream_id=1>
          ; END_STREAM
[  5.570] recv PING frame <length=8, flags=0x00, stream_id=0>
          (opaque_data=0000000000000000)
[  5.570] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=33549)
[  5.570] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
```

最終的にウィンドウサイズは65000程度を保つようになっているようです。


受信ウィンドウの最大サイズは「2^31」(2147483648)で、WINDOW_UPDATEで加算させることができる最大サイズも「2^31」(2147483648)です。
しかしこのような巨大な数字は今のところは見たことがないです。

⚫️Twitter.comの場合

```
[  5.423] recv SETTINGS frame <length=6, flags=0x00, stream_id=0>
          (niv=1)
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65536]
```

⚫️nghttp2.orgの場合

```
[  5.135] recv SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
```

⚫️http2.akamai.comの場合

```
[  5.040] recv SETTINGS frame <length=18, flags=0x00, stream_id=0>
          (niv=3)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
          [SETTINGS_MAX_HEADER_LIST_SIZE(0x06):16384]
[  5.041] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
          (window_size_increment=1)
```



# ウィンドウサイズを減らされた場合

負の値を保持して待機する必要がある。

http://summerwind.jp/docs/draft-ietf-httpbis-http2-16/#section6-9-2

>6.9.2. 初期フロー制御ウインドウサイズ
>
>例えば、クライアントがコネクション確立と同時に60KBを送信し、サーバーが初期ウインドウサイズを16KBに設定した場合、クライアントは SETTINGS フレームの受信時に、使用可能なフロー制御ウインドウを-44KBに再計算します。クライアントは、WINDOW_UPDATE フレームが>ウインドウを正の値に戻すまで、負の値のフロー制御ウインドウを保持します。ウインドウが正の値に戻った後に、クライアントは送信を再開します。