WEBRick で HTTP streaming (Chunked transfer encoding)


# HTTP Streamingとは

[Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding)とも呼ばれます。
HTTP/1.1の[4.1.  Chunked Transfer Coding](https://tools.ietf.org/html/rfc7230#section-4.1)で定義されています。
HTTPレスポンスを分割して返却します。

[HTTP/1.1 の Transfer-Encoding: chunked をビジュアライズするツール書いてみた - blog.nomadscafe.jp](http://blog.nomadscafe.jp/2013/05/http11-transfer-encoding-chunked.html)

> 一般にHTTP KeepAliveを利用するには、レスポンスのボディがどこで終わり、次のレスポンスがどこから始まるかをクライアントが知る必要があります、そのためHTTP/1.0ではKeepAliveを行う為にボディの長さをContent-Lengthをヘッダに入れなければなりませんでしたが、サイズを測るためにデータをすべてメモリに読み込むなどの処理が必要になり、レスポンス開始までの時間もかかります。（一般的なアプリケーションにはあまり影響がありませんが）
>
> そこでHTTP/1.1ではChunked Transferという仕組みが入っていて、事前に全体のレスポンスの長さが分からなくても、chunk=固まり毎にサイズを記してレスポンスを返していき、最後に0byteと送信することで、コンテンツの切れ目がわかるようになっています。


## 主な用途

### レスポンスヘッダとレスポンスボディを分けて返す

ブラウザはヘッダで指定されたasset（css, javascript, 画像...）とボディを並列にダウンロードできます。
ボディのダウンロード完了を待たない分、ページの表示が早くなります。

[Why HTTP Streaming? | Riding Rails](http://weblog.rubyonrails.org/2011/4/18/why-http-streaming/)

### ボディを分割して返す


複数のバックエンドサーバから検索してレスポンスを返す場合など
ボディの取得に時間がかかるときに、サーバは取得した部分からボディを返却できます。
ブラウザは取得できたボディからページに表示することができます。
レスポンスが向上します。

ボディが巨大な場合に、サーバはボディを分割して返却できます。
サーバはメモリに、ボディの返却する部分のみを読み込みます。
ボディすべてをメモリに読み込む必要がなくなり、必要なメモリ容量が減ります。

# WEBRickで試す

Rubyに標準添付のHTTPサーバフレームワーク[webrick](https://docs.ruby-lang.org/ja/latest/library/webrick.html)で試してみましょう。

## WEBRickを使った簡易サーバ
単に文字列を返却するサーバを用意します。

```hello_webrick.rb
require 'webrick'

srv = WEBrick::HTTPServer.new({ Port: 8080 })

srv.mount_proc '/' do |req, res|
  res.body = 'hello'
end

srv.start
```

`ruby hello_webrick.rb`で実行します。

```
curl localhost:8080 -i
HTTP/1.1 200 OK
Server: WEBrick/1.3.1 (Ruby/2.4.1/2017-03-22)
Date: Thu, 22 Jun 2017 00:45:40 GMT
Content-Length: 5
Connection: Keep-Alive

hello
```

## HTTP streamingを有効に

[WEBrick::HTTPResponse#chunked=](
https://docs.ruby-lang.org/ja/latest/class/WEBrick=3a=3aHTTPResponse.html#I_CHUNKED--3D)を
> 真に設定するとクライアントに返す内容(エンティティボディ)を chunk に分けるようになります。

```hello_webrick.rb
require 'webrick'

srv = WEBrick::HTTPServer.new({ Port: 8080 })

srv.mount_proc '/' do |req, res|
  res.chunked = true
  res.body = 'hello'
end

srv.start
```

`ruby hello_webrick.rb`で実行します。

```
curl localhost:8080 -i
HTTP/1.1 200 OK
Server: WEBrick/1.3.1 (Ruby/2.4.1/2017-03-22)
Date: Thu, 22 Jun 2017 00:50:44 GMT
Transfer-Encoding: chunked
Connection: Keep-Alive

hello
```

HTTPヘッダーが`Content-Length: 5`から`Transfer-Encoding: chunked`に変わりました。

## 送受信パケットの変化
MacOSではtcpdumpコマンドで実際に送受信しているパケットを見ることができます。
`sudo tcpdump -i lo0 port 8080 -X -t` の実行結果を抜粋します。

### リクエスト
HTTPリクエスト `HTTP: GET / HTTP/1.1` が飛んでいます。

```
IP6 localhost.55855 > localhost.http-alt: Flags [P.], seq 1:79, ack 1, win 12743, options [nop,nop,TS val 341903535 ecr 341903535], length 78: HTTP: GET / HTTP/1.1
	0x0000:  6003 672d 006e 0640 0000 0000 0000 0000  `.g-.n.@........
	0x0010:  0000 0000 0000 0001 0000 0000 0000 0000  ................
	0x0020:  0000 0000 0000 0001 da2f 1f90 1ab5 823c  ........./.....<
	0x0030:  2389 6a6e 8018 31c7 0076 0000 0101 080a  #.jn..1..v......
	0x0040:  1461 08af 1461 08af 4745 5420 2f20 4854  .a...a..GET./.HT
	0x0050:  5450 2f31 2e31 0d0a 486f 7374 3a20 6c6f  TP/1.1..Host:.lo
	0x0060:  6361 6c68 6f73 743a 3830 3830 0d0a 5573  calhost:8080..Us
	0x0070:  6572 2d41 6765 6e74 3a20 6375 726c 2f37  er-Agent:.curl/7
	0x0080:  2e34 332e 300d 0a41 6363 6570 743a 202a  .43.0..Accept:.*
	0x0090:  2f2a 0d0a 0d0a                           /*....
```

リクエストには変化はありません。

### HTTP streamingなしレスポンス
HTTPヘッダーが帰ってきます。

```
IP6 localhost.http-alt > localhost.55855: Flags [P.], seq 1:148, ack 79, win 12741, options [nop,nop,TS val 341903535 ecr 341903535], length 147: HTTP: HTTP/1.1 200 OK
	0x0000:  6002 29cb 00b3 0640 0000 0000 0000 0000  `.)....@........
	0x0010:  0000 0000 0000 0001 0000 0000 0000 0000  ................
	0x0020:  0000 0000 0000 0001 1f90 da2f 2389 6a6e  .........../#.jn
	0x0030:  1ab5 828a 8018 31c5 00bb 0000 0101 080a  ......1.........
	0x0040:  1461 08af 1461 08af 4854 5450 2f31 2e31  .a...a..HTTP/1.1
	0x0050:  2032 3030 204f 4b20 0d0a 5365 7276 6572  .200.OK...Server
	0x0060:  3a20 5745 4272 6963 6b2f 312e 332e 3120  :.WEBrick/1.3.1.
	0x0070:  2852 7562 792f 322e 342e 312f 3230 3137  (Ruby/2.4.1/2017
	0x0080:  2d30 332d 3232 290d 0a44 6174 653a 2054  -03-22)..Date:.T
	0x0090:  6875 2c20 3232 204a 756e 2032 3031 3720  hu,.22.Jun.2017.
	0x00a0:  3031 3a34 313a 3137 2047 4d54 0d0a 436f  01:41:17.GMT..Co
	0x00b0:  6e74 656e 742d 4c65 6e67 7468 3a20 350d  ntent-Length:.5.
	0x00c0:  0a43 6f6e 6e65 6374 696f 6e3a 204b 6565  .Connection:.Kee
	0x00d0:  702d 416c 6976 650d 0a0d 0a              p-Alive....
```

その後ボディが帰ってきます。

```
IP6 localhost.http-alt > localhost.55855: Flags [P.], seq 148:153, ack 79, win 12741, options [nop,nop,TS val 341903536 ecr 341903535], length 5: HTTP
	0x0000:  6002 29cb 0025 0640 0000 0000 0000 0000  `.)..%.@........
	0x0010:  0000 0000 0000 0001 0000 0000 0000 0000  ................
	0x0020:  0000 0000 0000 0001 1f90 da2f 2389 6b01  .........../#.k.
	0x0030:  1ab5 828a 8018 31c5 002d 0000 0101 080a  ......1..-......
	0x0040:  1461 08b0 1461 08af 6865 6c6c 6f         .a...a..hello
```

### HTTP streamingありレスポンス
ヘッダーが帰ってきます。

```
IP6 localhost.http-alt > localhost.55880: Flags [P.], seq 1:157, ack 79, win 12741, options [nop,nop,TS val 342094266 ecr 342094262], length 156: HTTP: HTTP/1.1 200 OK
	0x0000:  600f 56d5 00bc 0640 0000 0000 0000 0000  `.V....@........
	0x0010:  0000 0000 0000 0001 0000 0000 0000 0000  ................
	0x0020:  0000 0000 0000 0001 1f90 da48 7621 f890  ...........Hv!..
	0x0030:  5105 966f 8018 31c5 00c4 0000 0101 080a  Q..o..1.........
	0x0040:  1463 f1ba 1463 f1b6 4854 5450 2f31 2e31  .c...c..HTTP/1.1
	0x0050:  2032 3030 204f 4b20 0d0a 5365 7276 6572  .200.OK...Server
	0x0060:  3a20 5745 4272 6963 6b2f 312e 332e 3120  :.WEBrick/1.3.1.
	0x0070:  2852 7562 792f 322e 342e 312f 3230 3137  (Ruby/2.4.1/2017
	0x0080:  2d30 332d 3232 290d 0a44 6174 653a 2054  -03-22)..Date:.T
	0x0090:  6875 2c20 3232 204a 756e 2032 3031 3720  hu,.22.Jun.2017.
	0x00a0:  3031 3a34 343a 3238 2047 4d54 0d0a 5472  01:44:28.GMT..Tr
	0x00b0:  616e 7366 6572 2d45 6e63 6f64 696e 673a  ansfer-Encoding:
	0x00c0:  2063 6875 6e6b 6564 0d0a 436f 6e6e 6563  .chunked..Connec
	0x00d0:  7469 6f6e 3a20 4b65 6570 2d41 6c69 7665  tion:.Keep-Alive
	0x00e0:  0d0a 0d0a                                ....
```

続いてボディが１つ帰ってきます。

```
IP6 localhost.http-alt > localhost.55880: Flags [P.], seq 157:167, ack 79, win 12741, options [nop,nop,TS val 342094266 ecr 342094266], length 10: HTTP
	0x0000:  600f 56d5 002a 0640 0000 0000 0000 0000  `.V..*.@........
	0x0010:  0000 0000 0000 0001 0000 0000 0000 0000  ................
	0x0020:  0000 0000 0000 0001 1f90 da48 7621 f92c  ...........Hv!.,
	0x0030:  5105 966f 8018 31c5 0032 0000 0101 080a  Q..o..1..2......
	0x0040:  1463 f1ba 1463 f1ba 350d 0a68 656c 6c6f  .c...c..5..hello
	0x0050:  0d0a
```

`50d 0a68 656c 6c6f 0d0a`がHTTPボディです。
これは

```
5

hello

```

です。HTTP Streamingでは

```
送信サイズ
改行
送信データ
改行
```

の形式でチャンクを送信します。
つづいて、もう１つボディが帰ってきます。

```
IP6 localhost.http-alt > localhost.55880: Flags [P.], seq 167:172, ack 79, win 12741, options [nop,nop,TS val 342094266 ecr 342094266], length 5: HTTP
	0x0000:  600f 56d5 0025 0640 0000 0000 0000 0000  `.V..%.@........
	0x0010:  0000 0000 0000 0001 0000 0000 0000 0000  ................
	0x0020:  0000 0000 0000 0001 1f90 da48 7621 f936  ...........Hv!.6
	0x0030:  5105 966f 8018 31c5 002d 0000 0101 080a  Q..o..1..-......
	0x0040:  1463 f1ba 1463 f1ba 300d 0a0d 0a         .c...c..0....
```

HTTPボディは`0a0d 0a`です。

これは

```
0

```

です。HTTP Streamingでは送信終了の印に、0と改行を送ります。

https://tools.ietf.org/html/rfc7230#section-4.1 に
>     chunked-body   = *chunk
>                      last-chunk
>                      trailer-part
>                      CRLF
>
>     chunk          = chunk-size [ chunk-ext ] CRLF
>                      chunk-data CRLF
>     chunk-size     = 1*HEXDIG
>     last-chunk     = 1*("0") [ chunk-ext ] CRLF
>
>     chunk-data     = 1*OCTET ; a sequence of chunk-size octets

定義されている通りです。

# 参考
- [Transfer-Encoding: chunked について - 理系学生日記](http://kiririmode.hatenablog.jp/entry/20100606/p1)
- [HTTP1.1のTransfer-Encoding: chunkedを試してみる - ぬけラボ](http://nuke.hateblo.jp/entry/2013/07/08/144327)