HTTP/1.0, HTTP/1.1, HTTP/2, Server-Sent Events, WebSocket におけるデータの送信単位

# はじめに

この記事では簡易的にプロトコルの説明も行いますが，目的としては**データの送信単位**および**データ終端の表現方法**に焦点をあてて解説を行います．またTCPやHTTP/2については深く説明しません．

# 用語の定義

<table>
<tr><th>クライアント</th><td>通信要求を行う側</td></tr>
<tr><th>サーバ</th><td>通信応答を行う側</td></tr>
<tr><th>プロトコル</th><td>クライアントとサーバが通信を行うための取り決め</td></tr>
</table>

<table>
<tr><th>リクエストヘッダ</th><td>これからクライアントが送信するデータに対する説明</td></tr>
<tr><th>リクエストボディ</th><td>クライアントが送信するデータ</td></tr>
<tr><th>レスポンスヘッダ</th><td>これからサーバが送信するデータに対する説明</td></tr>
<tr><th>レスポンスボディ</th><td>サーバが送信するデータ</td></tr>
</table>
<table>
<tr><th>TCP</th><td>バイト列を双方向に伝送し合うためのプロトコル</td></tr>
<tr><th>HTTP</th><td>バイト列にファイル単位での意味を与えるための<br>TCPを利用したプロトコル</td></tr>
<tr><th>WebSocket</th><td>バイト列にイベント単位での意味を与えるための<br>TCPを利用したプロトコル</td></tr>
</table>

「コネクション」や「セッション」に関しては定義が曖昧ですが，この記事では以下のようにします．

- 通信路と呼べるものは「コネクション」とします．
- 単なる二者間のやりとりは「セッション」とします．

<table>
<tr><th>TCPコネクション</th><td>TCPの通信路</td></tr>
<tr><th>HTTPコネクション</th><td>TCPコネクションの上に成り立つ<br><strong>HTTP/2</strong>の通信路<br></td></tr>
<tr><th>WebSocketコネクション</th><td>TCPコネクションの上に成り立つ<br>WebSocketの通信路</td></tr>
<tr><th>HTTPセッション</th><td><strong>HTTP/1.x</strong>のやりとり<br></td></tr>
<tr><th>セグメント</th><td>TCPの最小データ単位</td></tr>
<tr><th>チャンク</th><td>HTTPセッションのうちレスポンスボディが分割された単位<br><strong>(HTTP/1.1専用)</strong></td></tr>
<tr><th>ストリーム</th><td>HTTPコネクションの上に成り立つ<br>HTTP1.xのセッションに相当するもの<br><strong>(HTTP/2専用)</strong></td></tr>
<tr><th>メッセージ</th><td>WebSocketコネクションの上に成り立つ<br>HTTP1.xのセッションに相当するもの</td></tr>
<tr><th>フレーム</th><td>HTTP/2またはWebSocketの最小データ単位</td></tr>
</table>

1つのTCPコネクションの上に連なるプロトコル群を図に示すとこんな感じです． (`SG = Segment`)

```
HTTP/1.x: | HTTP Session |
          |--------------| 
TCP:      | SG | SG | SG |
```

```
          | HTTP Session |
HTTP/1.1: | Chunk | Chunk|
          |--------------| 
TCP:      | SG | SG | SG |
```

```
          |     Stream    |     Stream    |
HTTP/2:   | Frame | Frame | Frame | Frame |
          |-------------------------------| 
TCP:      |  SG | SG | SG |  SG | SG | SG |
```


```
          |    Message    |
WebSocket:| Frame | Frame |
          |---------------| 
TCP:      |  SG | SG | SG |
```


# 昔からある方式 (~2010)

## HTTP/1.0

```
HTTP/1.0: | HTTP Session |
          |--------------| 
TCP:      | SG | SG | SG |
```

最もシンプルな方式です．

<table>
<tr><th>ヘッダフォーマット</th><td>テキスト</td></tr>
<tr><th>1つのTCPコネクションに<br>何個のHTTPセッションが載るか？</th><td>1つ</td></tr>
<tr><th>サーバ側から能動的にデータを送信できるか？</th><td>できない</td></tr>
<tr><th>レスポンスボディ終端の表現方法</th><td>TCPセグメントのFINオプション</td></tr>
</table>

- HTTP/1.1で`Connection: close`としてヘッダで明示した場合もこちらの動作になります．
- 詳細は割愛しますが，TCPコネクション切断時には**FIN**(Finish)オプションを持ったTCPセグメントが相互に送られることになっています．

### 例

`TCPコネクションを生成`

`HTTPセッションを開始`

```http:→
GET /main.html HTTP/1.0

```

```http:←
HTTP/1.0 200 OK
Content-Type: text/html

<iframe src="hello.html"></iframe>
```

`HTTPセッションを終了`

`TCPコネクションを切断`

`TCPコネクションを生成`

`HTTPセッションを開始`

```http:→
GET /hello.html HTTP/1.0

```

```http:←
HTTP/1.0 200 OK
Content-Type: text/html

<h1>Hello</h1>
```

`HTTPセッションを終了`

`TCPコネクションを切断`

## HTTP/1.1

```
HTTP/1.1: | HTTP Session |
          |--------------| 
TCP:      | SG | SG | SG |
```

```
          | HTTP Session |
HTTP/1.1: | Chunk | Chunk|
          |--------------| 
TCP:      | SG | SG | SG |
```

HTTP/1.0では毎回TCPコネクションを生成しては切断を繰り返していましたが，効率が悪いため，TCPコネクションの再利用を行うようにしたプロトコルです．その代わり送信されるデータの長さを明確に表現する必要があります．また`Host: xxx`というリクエストヘッダが必須になっています．

<table>
<tr><th>ヘッダフォーマット</th><td>テキスト</td></tr>
<tr><th>1つのTCPコネクションに<br>何個のHTTPセッションが載るか？</th><td>多数<br>(並列送信はできなくはないが困難)</td></tr>
<tr><th>サーバ側から能動的にデータを送信できるか？</th><td>できない</td></tr>
<tr><th>レスポンスボディ終端の表現方法</th><td>
<ul>
<li><code>Content-Length: xxx</code>
<li><code>Transfer-Encoding: chunked</code>
</ul>
</td></tr>
</table>

- HTTP/1.0で`Connection: keep-alive`としてヘッダで明示した場合もこちらの動作になります．
- 複数のHTTPセッションの並列処理を可能にする 「HTTP/1.1 Pipelining」という規格もありますが，Webブラウザ向けには問題点が多いためあまり利用されていません．このニーズはHTTP/2で満たすほうが主流です．

<ins>Transfer-Encoding: chunked について</ins>

- 任意のチャンクにデータを分割でき，それぞれのサイズは16進数で表現されます．
- 最後は0バイトのチャンクを送信してデータの終わりを通知します．
- **これは<ins>送信時点でデータサイズが不明</ins>であり，かつ<ins>TCPコネクションを切断せずに再利用したい</ins>という要件を満たすために利用されます．分割される単位に意味があるわけではありません．**

```
4
[4バイトのデータ]
f
[15バイトのデータ]
0

```

### 例： `Content-Length: xxx`を利用する場合

`TCPコネクションを生成`

`HTTPセッションを開始`

```http:→
GET /main.html HTTP/1.1
Host: example.com

```

```http:←
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 34

<iframe src="hello.html"></iframe>
```

`HTTPセッションを終了`

`HTTPセッションを開始`

```http:→
GET /hello.html HTTP/1.1
Host: example.com

```

```http:←
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 14

<h1>Hello</h1>
```

`HTTPセッションを終了`

### 例： `Transfer-Encoding: chunked`を利用する場合

`TCPコネクションを生成`

`HTTPセッションを開始`

```http:→
GET /main.html HTTP/1.1
Host: example.com

```

```http:←
HTTP/1.1 200 OK
Content-Type: text/html
Transfer-Encoding: chunked

19
<iframe src="hello.html">
11
</iframe>
0

```

`HTTPセッションを終了`

`HTTPセッションを開始`

```http:→
GET /hello.html HTTP/1.1
Host: example.com

```

```http:←
HTTP/1.1 200 OK
Content-Type: text/html
Transfer-Encoding: chunked

e
<h1>Hello</h1>
0

```

`HTTPセッションを終了`

# 比較的新しい方式 (2010~)

## HTTP/2

```
          |     Stream    |     Stream    |
HTTP/2:   | Frame | Frame | Frame | Frame |
          |-------------------------------| 
TCP:      |  SG | SG | SG |  SG | SG | SG |
```

HTTP/1.xであった問題点を，HTTP/2は以下のように解決しています．

- 並列に通信を行うためには，TCPコネクションを多数使用する必要がある<br>→ HTTP自身がコネクションとなり，HTTPセッションをその中にストリームとして確保する
- 毎回似たようなヘッダを送信しなければならない<br>→ 最初に送信したヘッダは変更がない限り省略
- テキスト形式であるためヘッダサイズが増えやすく，解釈も曖昧になる<br>→ 厳格なバイナリ形式を定義

見た目は大きく変化しているものの，意味的にはHTTP/1.xと互換性があり，相互に変換出来るようになっています．

<table>
<tr><th>ヘッダフォーマット</th><td>バイナリ</td></tr>
<tr><th>1つのTCPコネクションに<br>何個のHTTPコネクションが載るか？</th><td>1つ</td></tr>
<tr><th>1つのHTTPコネクションに<br>何個のストリームが載るか？</th><td>多数<br>(並列送信可能)</td></tr>
<tr><th>サーバ側から能動的にデータを送信できるか？</th><td>できる<br>(但し用途が高速化のみに限られる)</td></tr>
<tr><th>フレーム終端の表現方法</th><td>フレームのLengthフィールド</td>
<tr><th>ストリーム終端の表現方法</th><td>フレームのEND_STREAMオプション</td></tr>
</table>

- TCPコネクションの切断にはTCPセグメントのFINオプションが用いられると述べましたが，ストリームの終了にはHTTP/2フレームの`END_STREAM`というオプションが用いられます． 
- HTTP/2ではヘッダ終端を示す`END_HEADERS`というオプションも用いられます．

HTTP/2について完全に説明するとそれだけで記述が膨大になるので，他のスライドや記事をリンクしておきます．

- [HTTP/2, QUIC入門](http://www.slideshare.net/shigeki_ohtsu/http2-quic)
- [HTTP2のストリーム状態遷移](http://qiita.com/0xfffffff7/items/c8cabb51ced5efcaec6f)

### 例: main.htmlとhoge.htmlへの同時リクエストを行う場合

実際にはHTTPコネクションを生成するための手順が別にありますが，ここでは省略します．また，HTTP/2のバイナリヘッダに関してはHTTP/1.x風に書くことにします．

`TCPコネクションを生成`

`HTTPコネクションを生成`

`ストリーム#01を開始`

```yaml:ヘッダフレーム&#32;(Stream&#32;ID&#58;&#32;01;&#32;END_STREAM&#32;|&#32;END_HEADERS)→
method: GET
path: /main.html
scheme: https
authority: example.com
```

`ストリーム#02を開始`

```yaml:ヘッダフレーム&#32;(Stream&#32;ID&#58;&#32;02;&#32;END_STREAM&#32;|&#32;END_HEADERS)→
path: /hoge.html
```

```yaml:←ヘッダフレーム&#32;(Stream&#32;ID&#58;&#32;01;&#32;&#32;END_HEADERS)
status: 200
content-type: text/html
```

```html:←データフレーム&#32;(Stream&#32;ID&#58;&#32;01;&#32;END_STREAM)
<iframe src="hello.html"></iframe>
```

`ストリーム#01を終了`

```yaml:←ヘッダフレーム&#32;(Stream&#32;ID&#58;&#32;02;&#32;&#32;END_HEADERS)
status: 404
```

```html:←データフレーム&#32;(Stream&#32;ID&#58;&#32;02;&#32;END_STREAM)
<h1>Not Found</h1>
```

`ストリーム#02を終了`

`ストリーム#03を開始`

```yaml:ヘッダフレーム&#32;(Stream&#32;ID&#58;&#32;03;&#32;END_STREAM&#32;|&#32;END_HEADERS)→
path: /hello.html
```

```html:←ヘッダフレーム&#32;(Stream&#32;ID&#58;&#32;03;&#32;&#32;END_HEADERS)
status: 200
```

```html:←データフレーム&#32;(Stream&#32;ID&#58;&#32;03;&#32;END_STREAM)
<h1>Hello</h1>
```

`ストリーム#03を終了`

## Server-Sent Events

HTTP/1.xでは「クライアントからリクエストがあったらサーバが応答する」という動作方式に則っており，「サーバからクライアントに対して主体的にデータを送信する」ということが出来ませんでした．<br>**(HTTP/2ではできるようになってはいますが用途が限定的です)**

このままではチャットなどのリアルタイムなアプリケーションに対応するのが難しいです．そこで，HTTPを使用して実現される「Server-Sent Events」という方式が作られました．

- HTTP/1.xにおいては，1つのHTTPセッションにおけるレスポンスボディを継続的に送信し続けます．
- HTTP/2においては，1つのストリームを通じてデータフレームを継続的に送信し続けます．

一応これもプロトコルといえますが，TCPではなくHTTPの上に載っているので，ただのHTTP通信であるとも言えます．なお，この通信を行うための専用のオブジェクトがJavaScriptには用意されており，Webブラウザ側の実装はこれを使って簡単に行うことが出来ます．

- [Server-Sent Events の利用 - Server-sent events | MDN](https://developer.mozilla.org/ja/docs/Server-sent_events/Using_server-sent_events)

以下にTwitterを模したストリーミングAPIの例を示します．

### 例： HTTP/1.0から利用する場合

`TCPコネクションを生成`

`HTTPセッションを開始`

```http:→
GET /streaming HTTP/1.0

```

```http:←
HTTP/1.0 200 OK
Content-Type: text/event-stream

event: tweet
data: {"id":123,"text":"あいうえお","user":{"id":893,"screen_name":"mpyw"}}

event: tweet
data: {"id":456,"text":"今からこれふぁぼります","user":{"id":893,"screen_name":"mpyw"}}

event: favorite
data: {"id":456,"user_id":893}

```

`HTTPセッションを終了`

`TCPコネクションを切断`

### 例： HTTP/1.1から利用する場合

長さがあらかじめ分かっているわけではないので，`Transfer-Encoding: chunked`を使わなければなりません．但し先ほども書いたように，**分割される単位に意味があるわけではない**ことに注意してください．どこで分割されるかわかりません．

`TCPコネクションを生成`

`HTTPセッションを開始`

```http:→
GET /streaming HTTP/1.1
Host: example.com

```

```http:←
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/event-stream

5f
event: tweet
data: {"id":123,"text":"あいうえお","user":{"id":893,"screen_name":"mpyw"}}

28
event: tweet
data: {"id":456,"text":"今
48
からこれふぁぼります","user":{"id":893,"screen_name":"mpyw"}}

25
event: favorite
data: {"id":456,"user_id":893}

0

```

`HTTPセッションを終了`

### 例： HTTP/2から利用する場合

`TCPコネクションを生成`

`HTTPコネクションを生成`

`ストリーム#01を開始`

```yaml:ヘッダフレーム&#32;(Stream&#32;ID&#58;&#32;01;&#32;END_STREAM&#32;|&#32;END_HEADERS)→
method: GET
path: /streaming
scheme: https
authority: example.com
```

```yaml:←ヘッダフレーム&#32;(Stream&#32;ID&#58;&#32;01;&#32;&#32;END_HEADERS)
status: 200
content-type: text/event-stream
```

```text:←データフレーム&#32;(Stream&#32;ID&#58;&#32;01)
event: tweet
data: {"id":123,"text":"あいうえお","user":{"id":893,"screen_name":"mpyw"}}

```

```text:←データフレーム&#32;(Stream&#32;ID&#58;&#32;01)
event: tweet
data: {"id":456,"text":"今
```

```text:←データフレーム&#32;(Stream&#32;ID&#58;&#32;01)
からこれふぁぼります","user":{"id":893,"screen_name":"mpyw"}}

```

```text:←データフレーム&#32;(Stream&#32;ID&#58;&#32;01;&#32;END_STREAM)
event: favorite
data: {"id":456,"user_id":893}

```

`ストリーム#01を終了`

### 備考: 実際のTwitterはどうなっているか？

TwitterのストリーミングAPIは， Server-Sent Events の規格ができる前から存在するものなので，独自のフォーマットに基づいています．イベントタイプをデータに内包する形式です．但し，ツイートの場合は外を包むものが無く，あまり一貫性のない実装となっています．

また，`Connection: close`なのに`Transfer-Encoding: chunked`を使うという無駄なことをやっているようにも思えますが，**TCPレイヤーでいきなり切断を行うよりも，上位のHTTPレイヤーから切断を行ったほうが緩やかでお行儀が良い**と考えられるのかもしれません．

`TCPコネクションを生成`

`HTTPセッションを開始`

```http:→
GET /1.1/statuses/sample.json HTTP/1.1
Host: stream.twitter.com
Authorization: OAuth ...

```

```http:←
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Connection: close
Content-Type: application/​‌json

4b
{"id":123,"text":"あいうえお","user":{"id":893,"screen_name":"mpyw"}}

15
{"id":456,"text":"今
48
からこれふぁぼります","user":{"id":893,"screen_name":"mpyw"}}

2c
{"event":"favorite","target_id":456,"user_id":893}

1c
{"disconnect":{"code":810}}

0

```

`HTTPセッションを終了`

`TCPコネクションを切断`

## WebSocket

```
          |    Message    |
WebSocket:| Frame | Frame |
          |---------------| 
TCP:      |  SG | SG | SG |
```

Server-Sent Events の際はHTTPを利用していましたが，こちらはTCPを直接利用するプロトコルとなっています．但し，HTTPとの互換性を考慮するため，**まずHTTPで接続し，`101 Switching Protocols`というHTTPステータスコードを用いて後からWebSocketに切り替える**方式を採用しています．

<table>
<tr><th>ヘッダフォーマット</th><td>バイナリ</td></tr>
<tr><th>1つのTCPコネクションに<br>何個のWebSocketコネクションが載るか？</th><td>1つ</td></tr>
<tr><th>1つのWebSocketコネクションに<br>何個のメッセージが載るか？</th><td>多数<br>(並列送信可能)</td></tr>
<tr><th>サーバ側から能動的にデータを送信できるか？</th><td>できる</td></tr>
<tr><th>フレーム終端の表現方法</th><td>フレームのPayload-Lengthフィールド</td>
<tr><th>メッセージ終端の表現方法</th><td>フレームのFINオプション</td>
</table>

WebSocketのヘッダフォーマットはバイナリですが，それでもHTTPに比べて極めてシンプルです．

- [WebSocket サーバの実装とプロトコル解説 - Block Rockin’ Codes](http://jxck.hatenablog.com/entry/20120725/1343174392)

### 例

もしTwitterがWebSocketでのストリーミングAPIに対応したとすればどうなるかのイメージを示してみます．

`TCPコネクションを生成`

`HTTPセッションを開始`

```http:→
GET /1.1/statuses/sample.json HTTP/1.1
Host: stream.twitter.com
Authorization: OAuth ...
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: ...
Sec-WebSocket-Version: 13

```

```http:←
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ...

```

`HTTPセッションを終了`

`WebSocketコネクションを生成`

```text:←テキストフレーム&#32;(FIN&#58;&#32;1)
{"id":123,"text":"あいうえお","user":{"id":893,"screen_name":"mpyw"}}
```

```text:←テキストフレーム&#32;(FIN&#58;&#32;0)
{"id":456,"text":"今
```

```text:←テキストフレーム&#32;(FIN&#58;&#32;1)
からこれふぁぼります","user":{"id":893,"screen_name":"mpyw"}}
```

```text:←テキストフレーム&#32;(FIN&#58;&#32;1)
{"event":"favorite","target_id":456,"user_id":893}
```

```text:←コネクション切断通知フレーム&#32;(FIN&#58;&#32;1)
```

`WebSocketコネクションを切断`

`TCPコネクションを切断`

この実装ではクライアントはただサーバからのメッセージを読み取っているだけですが，この間に**クライアントからサーバに別のメッセージを送ることもできる**のがWebSocketの最大の長所です．TCPをほぼそのままの形でWeb用途に特化させた感じです．TwitterがSiteStreamsとして一部ユーザに限定的に提供していた，動的にカスタマイズ可能なストリーミングAPIは，WebSocketを使えばもっとシンプルに実現できることでしょう．