closing handshake

接続の切断 (WebSocket)

[122] WebSocketクライアントは close メソッドによって接続を閉じることができます。 WebSocket接続を閉じる際は Closeフレームによる closing handshake が行われます。

[123] その他に、 Closeフレームを使わない異常終了処理も規定されています。

仕様書

接続の終了

[44] WebSocket接続の終了は、次のような動作により開始されます。

[128] 接続を閉じる動作の大まかな流れは次のようになります。

handshake
接続確立
->
failed
->
abort
->
normal
v
>>
3
disconnected
下位層切断
v
>>
1
->
failed
normal
送受信
->
failed
->
Close received
>>
5
make disappear
disappear
v
->
failed
->
abort
>>
1
close method
close
->
start closing
>>
3
gc
ごみ収集
->
start closing
>>
4
abort
Abort
->
close
v
failed
接続失敗
->
send Close 1
>>
2
start closing
Closing
->
send Close 1
>>
3
send Close 1
Close 送信
->
close
->
Close received
>>
2
v
Close received
Close 受信
->
send Close 2
->
close
v
>>
5
send Close 2
Close 送信
v
>>
5
->
close
close
閉じる
v
>>
1

[37] クライアントは、トランスポート層の接続が失われたら、 WebSocket接続失敗を実行しなければなりません >>12。

[57] なぜかサーバーに関する規定はありません。

[36] クライアントは、応用から指示された場合やプロトコルで規定されている場合を除き、 接続を閉じるべきではありません >>12。 閉じる場合は、 Start the WebSocket Closing Handshake を実行します >>12。

[40] サーバーは、適宜接続を閉じて構いません >>12。 閉じる場合は、 Start the WebSocket Closing Handshake を実行します >>12。

[131] Chrome は異常終了時 (で可能なとき) に、 状態符号 1001 で理由空文字列のCloseフレームを送信するようです。

WebSocket インターフェイス close メソッド

[59] WebSocket インターフェイスの close メソッド >>58 は、 WebSocket の接続を閉じることを指示するものです。

[159] 第1引数として、WebSocket状態符号 (数値) を指定できます。 この引数は省略可能です。指定された場合は、 Closeフレームでサーバーに送られます。 いくつかの状態符号の値はプロトコルにより定められていますが、 サーバーとクライアントの裁量で決められる値の範囲もあります。

[160] 第2引数として、WebSocket接続閉じ理由 (文字列) を指定できます。 この引数は省略可能です。指定された場合は、Closeフレームでサーバーに送られます。 サーバーとクライアントの裁量で任意の文字列を使うことができますが、 UTF-8 で符号化したときに123バイト以下に収まらなければなりません。

[161] このメソッドは接続を閉じることを指示するものですが、 送受信の状態によっては、すぐには接続が閉じられない場合もあります。

[158] 本メソッドは、次のようにしなければなりません >>58。

  1. [140] 符号を、省略可能な第1引数を unsigned short として解釈した結果に設定します。 Clamp 拡張属性を適用します。
  2. [141] 理由を、省略可能な第2引数を USVString として解釈した結果に設定します。
  3. [142] 符号が 1000 か [ 3000, 4999 ] の範囲になければ、
    1. [143] InvalidAccessError 例外を投げ、ここで停止します。
  4. [144] 理由が null でなければ、
    1. [145] 理由を、理由にUTF-8符号化を適用した結果に設定します。
    2. [146] 理由の長さが123バイトより大きければ、
      1. [147] SyntaxError 例外を投げ、ここで停止します。
  5. [148] 文脈オブジェクトの readyState が CLOSING (2) か CLOSED (3) なら、
    1. [149] ここで停止します。
  6. [150] それ以外で、文脈オブジェクトがまだWebSocket接続確立に達していなければ (= 接続の状態が CONNECTING なら)、
    1. [151] 文脈オブジェクトについてWebSocket接続失敗を実行します。
    2. [152] 文脈オブジェクトの readyState を、 CLOSING (2) に設定します。
  7. [153] それ以外で、文脈オブジェクトがまだ The WebSocket Closing Handshake is Started に達していなければ (= 接続の状態が OPEN なら)、
    1. [154] 文脈オブジェクトについて Start the WebSocket closing handshake を実行します。 状態符号は符号、理由は理由とします。
    2. [155] 文脈オブジェクトの readyState を、 CLOSING (2) に設定します。
  8. [156] それ以外なら、
    1. [157] 文脈オブジェクトの readyState を、 CLOSING (2) に設定します。
[60] 接続の状態と readyState は異なることがあります。 WebSocket接続を参照。

[134] Chrome は、 接続を開いて handshake を開始した後で確立する前に close したとき、接続確立直後に Closeフレームを送信するようです。 handshake 失敗時は送信しません。

文書の破棄

[90] WebSocket オブジェクトの make disappear は、次のようにしなければなりません >>84。

  1. 接続の状態が CONNECTING なら、
    1. WebSocket接続失敗を実行します。
  2. 接続の状態が OPEN なら、
    1. Start the WebSocket Closing Handshake を実行します。 状態符号は 1001 とします。
[91] これは unloading document cleanup steps から呼び出されます。

ごみ収集

[87] WebSocket オブジェクトのごみ収集は、 次のように規定されています。

[85] event loop step 1 時点での readyState が、

[116] 言い換えると、まだ実行される可能性のあるイベントリスナーが存在している間は、 強い参照を保持し続ける必要があります。 event loop step 1 時点でそのようなイベントリスナーが存在しなくなっていれば、 強い参照を破棄できます。

[86] 送信バッファーが空でないなら、ごみ収集してはなりません >>84。

[88] このバッファーは WebSocket オブジェクトに関連付けられたアプリケーション層のものであり、OS (ソケット) 側のバッファーとは異なります。

[89] ごみ収集により破棄する直前に、次の処理を実行しなければなりません。

  1. [52] 接続の状態が OPEN なら、
    1. [53] Start the WebSocket Closing Handshake を状態符号なしで実行します >>84。

閉じ handshake

[20] Start the WebSocket Closing Handshake は、 状態符号符号 (整数または null) と理由理由 (バイト列または null) について、 次のようにしなければなりません。

  1. [21] フレームを送信します >>12, >>58。

    フレーム

    opcode
    Close
    状態符号
    符号が null 以外なら、符号
    close reason
    符号と理由が null 以外なら、理由
  2. [10] Closeフレーム送信済みフラグを設定します。
  3. [24] WebSocket接続の状態を CLOSING とします >>12。
  4. [11] The WebSocket Closing Handshake is Started を実行します >>12。
  5. [26] Closeフレーム受信済みフラグが設定されていれば、
    1. [23] WebSocket接続を閉じるを実行します >>1, >>12。 cleanly は真に設定します >>12。 状態符号と close reason を引き渡します。

[25] Closeフレームを受信したら、次のようにします。

  1. [100] Closeフレーム送信済みフラグが設定されていれば、
    1. [101] WebSocket接続を閉じるを実行します >>1, >>12。 cleanly は真に設定します >>12。 状態符号と close reason を引き渡します。
  2. [108] そうでなければ、
    1. [98] WebSocket接続の状態を CLOSING とします >>12。
    2. [99] The WebSocket Closing Handshake is Started を実行します >>12。
    3. [8] WebSocket接続失敗フラグが設定されていなければ、>>12
      1. [7] フレームを送信します >>1。 これはできるだけすぐに行うべきですが、 現在のメッセージをすべて送り終えるまで遅延させても構いません >>1。

        フレーム

        opcode
        Close
        状態符号
        あれば、受信した値とするのが普通です >>1。
      2. [126] Closeフレーム送信済みフラグを設定します。
      3. [127] 下位層の接続を閉じます >>1。WebSocket接続を閉じる処理 (>>13) を実行するので良いと思われます。

[61] 利用者エージェントは、the WebSocket closing handshake is started の際、 次のようにしなければなりません。

  1. [102] 次のタスクをタスクキューに追加します >>62。

    タスク

    タスク源
    WebSocketタスク源
    処理
    1. [103] readyState を、 CLOSING (2) に設定します。
      [63] 既に CLOSING になっている場合もあります。

接続失敗

[7] WebSocket接続の確立やWebSocketメッセージ受信により、 WebSocket接続失敗 (Fail the WebSocket Connection) となることがあります。

[33] 次のようにしなければなりません。

  1. [34] WebSocket接続失敗フラグを真に設定します。
  2. [80] 接続の状態が OPEN なら、
    1. [81] 適切な状態符号の Closeフレームを送信するべきです。 ただしエラーの性質上相手が受信して処理しないだろうと思われるなら、省略して構いません。 >>12
  3. [93] WebSocket接続を閉じる処理を実行します。 >>12
  4. [94] クライアントなら利用者に問題を報告しても構いません。 >>12
  5. [95] サーバーなら問題を記録するべきです。 >>12

[76] RFC は「WebSocket接続確立を過ぎていれば」 >>12 との条件で Closeフレームを送ることを求めていますが、 サーバーにはWebSocket接続確立が無く、また CLOSING になった後で再度 Closeフレームを送るのは不適切と思われます。
[109] 接続の状態を OPEN から CLOSING に変更するべきかもしれません。

[35] WebSocket接続失敗以後、受信したデータを処理し続けようとしてはなりません。 これには、 Closeフレームへの返信も含まれます。 >>12

[38] サーバーが Abort the WebSocket Connection したら、 WebSocket接続を閉じる処理を実行しなければなりません >>12。

接続を閉じる

[13] WebSocket接続を閉じる (Close the WebSocket Connection) 時には、 下位のTCP接続を閉じます >>12。

[14] TCP接続とTLSセッションを綺麗に閉じる方法を使い、 受信したかもしれないバイトは捨てるべきです >>12。

[16] 最も通常な場合には、まず (クライアントからではなく) サーバーから TCP接続を閉じるべきです >>12。 サーバーは、直ちに TCP を閉じるべきです >>12, >>1。 クライアントは、サーバーから TCP が閉じられるのを待つべきです >>12, >>1。

[17] そうすることで、クライアントではなくサーバーの TCP が TIME_WAIT 状態となります。 TIME_WAIT 状態である 最大セグメント寿命の2倍 (2MSL) の間は改めて接続を開くことができませんが、 サーバー側ならより大きなシーケンス番号の SYN を送信すれば直ちに接続を開けるので問題となりません。 >>12

[19] 例えば socket API では、 まず SHUT_WR を指定して shutdown を呼び出し、 0 が返されるまで recv を呼び出し、 最後に close を呼ぶことでこれを実装できます >>12。

[18] 十分な時間サーバーから TCP Close を受信しない場合などにはクライアントから TCP を閉じて構いません >>12, >>1。

[132] Chrome は、 Closeフレームの送信から 60s で Closeフレームを受信しないとき、 TCP 接続を閉じるようです。 Firefox は、 20s で閉じるようです。

[133] Chrome も Firefox も、 Closeフレームを送信した後 Closeフレームを受信したら、 TCP接続を閉じるようです。 切断まで Chrome は 2s 強、 Firefox は 1s 強待つようです。

[135] Closeフレームを受信した後 Closeフレームを送信してから、 Chrome は 2s、Firefox は 1s 待機した後、 TCP接続を閉じるようです。

[136] Chrome は Closeフレーム受信後にもフレームの解釈を続け、 エラーがあったらコンソールに報告します。 Firefox はそのような報告はしないので、 解釈しているのかどうかは不明です。

[15] 攻撃されている時など、必要に応じて他の方法で閉じても構いません >>12。

[137] Chrome も Firefox も、WebSocket接続失敗時にすぐに TCP接続を閉じるようです。

[27] TCP接続が閉じられた時、次のようにしなければなりません。

  1. [29] WebSocket接続の状態を、 CLOSED に設定します >>12。
  2. [110] The WebSocket Connection is Closed を実行します >>12。 cleanly フラグを引き継ぎます。

[64] 利用者エージェントは The WebSocket Connection is Closed の際、 次のようにしなければなりません。

  1. [118] WebSocket接続失敗フラグが設定されているか、 flagged as full フラグが設定されているなら、
    1. [119] 失敗を、真に設定します。
  2. [120] それ以外なら、
    1. [121] 失敗を、偽に設定します。
  3. [111] 次のタスクをタスクキューに追加します >>62。

    タスク

    タスク源
    WebSocketタスク源
    処理
    1. [112] readyState を、 CLOSED (3) に設定します。
    2. [113] 失敗が真なら、 単純イベントを発火します。

      単純イベント

      イベント型
      error
      対象オブジェクト
      WebSocket オブジェクト
    3. [114] イベントを発火します。

      イベント

      インターフェイス
      CloseEvent
      イベント型
      close
      対象オブジェクト
      WebSocket オブジェクト
      bubbles
      偽
      取り消し可能
      偽
      既定動作
      なし
      wasClean
      cleanly
      code
      WebSocket接続閉じ符号
      reason
      WebSocket接続閉じ理由
      [115] WebSocket接続閉じ符号と WebSocket接続閉じ理由は、 (あれば) 指定された状態符号と close reason から決まります。

[28] cleanly フラグは、 WebSocket接続の閉じ handshake が完了してから TCP接続が閉じられた >>12 かどうかを表します。

[78] CloseEvent インターフェイスの wasClean 属性は、 cleanly に閉じられたかどうかを表します。

[79] この属性は、その属性値として設定された値を返さなければなりません >>77。データ型は boolean で、初期値は偽です >>77。

[130] flagged as full は、バッファーが満杯でデータを送信できないことを表します。 send メソッドからデータを送信する時に設定されることがあります。

[66] 利用者エージェントは、スクリプトに次の状況を区別できる情報を提供してはなりません >>62。

[74] このような場合のWebSocket接続閉じ符号はいずれも 1006 となります >>62。

[75] こうした状況を区別できるとすると、スクリプトが利用者の属するネットワークの状態を調べるために使えてしまいます >>62。

再接続待ち

[39] クライアントが異常終了の後再接続を試みる際は、 何らかの backoff を行うべきです。 最初の再接続は、無作為に決めた時間だけ遅延させるべきです。 0s-5s の間から無作為に決めるのが妥当でしょうが、 実装経験や応用に基づき任意の方法で決めて構いません。 それ以後の再接続は、冪乗打ち切り待機法 (truncated binary exponential backoff) などにより徐々に時間を長くしていくべきです。 >>12

[65] これを誰が実装するべきなのかは不明です。 WebSocket API の内部で実装するべきなのでしょうか? それともスクリプトが個別に実装するべきなのでしょうか?

Close フレーム

[96] Closeフレームは、 WebSocket接続を閉じる際に用いられます。

[97] 正常終了にも異常終了にも用いられます。もっとも、異常終了時は Closeフレームなしで切断される場合もあります。

構文

[2] opcode は、 0x8 です >>1。 Close フレームは、制御フレームです。

[3] 閉じる理由を示す本体 (body) (応用データ) を含んで構いません >>1。

width
16
  1. 16 状態符号
  2. 16... close reason

[4] 本体がある場合、最初の2バイトはネットワークバイト順の2バイト符号無し整数でなければなりません。 この整数は、状態符号でなければなりません。 >>1

[5] 本体は更に UTF-8 符号化されたデータを含めることができます。 その解釈は WebSocket 仕様書では規定されておらず、人間可読である必要もありませんが、 デバッグなどに有用かもしれません。クライアントはこれを末端利用者に示してはなりません。 >>1

[22] この部分は「close reason」>>12 と呼ばれています。
  1. ?
    1. 状態符号
    2. *
      1. Unicode文字

[125] 利用する場面によっては、状態符号とclose reasonは指定されていません。 そのような場合にどうするべきか、 RFC 上は明確ではありません。 実装は状況に応じた適当な値を指定できると思われます。これはデバッグには有用かもしれませんが、 一方で fingerprinting vector となったり、不必要な内部情報を送ってしまったりするかもしれませんから、 クライアントは特に注意が必要です。

文脈

[106] >>20 参照。

処理

[107] >>25 参照。

[6] 応用は、 Close フレームより後にデータフレームを送ってはなりません >>1。

[31] 応用データが1バイトのみしかない場合にどう解釈するべきかは不明です。 非妥当なデータを受信した場合はWebSocket接続を閉じることが認められており >>42、 それに従う (というより問題を無視して閉じる処理を続ける) のが適切かもしれません。

[43] 規定が曖昧だと相互運用性に問題は無いのでしょうかね?

[50] 理由が UTF-8 として妥当でなければ、WebSocket接続失敗しなければなりません >>49。

[51] が、無視して閉じる処理を続ける方が適切かもしれません。

[129] Chrome は、 payload が1バイトのみの時、状態符号 1002、 理由空文字列のCloseフレームを送信します。 理由が UTF-8 として正しくない時、状態符号 1002、 理由 Invalid UTF-8 in Close frame を送信します。 状態符号が 1005、1006 の時、 状態符号 1002 理由空文字列を送信します。 Firefox はこれらの時、データなしの Closeフレームを送信します。 Firefox は状態符号が 0 の時もデータなしの Closeフレームを送信します。

状態符号と理由

[41] Closeフレームには、閉じる理由を記述できます。 数値の状態符号と、任意の文字列を記述できます。 受信者による理由の解釈や処理方法は、 WebSocket Protocol としては規定されていません >>12。

[124] close reason は、制御フレームの制約と状態符号が2バイトであることから、 123バイト以下である必要があります。 (UTF-8 符号化されたバイト列の制限で、 文字列の制限ではありません。) WebSocket インターフェイスの close メソッドでは、この制限を超えると例外が投げられます。

[32] WebSocket接続閉じ理由 (The WebSocket Connection Close Reason) は、 応用が最初に受信した Closeフレームに含まれていた閉じる理由 (UTF-8 decode without BOM を適用 >>62 した文字列) です。そのようなものが無ければ、 空文字列です。 >>12

[82] CloseEvent インターフェイスの reason 属性は、WebSocket接続閉じ理由を表します。

[83] この属性は、その属性値として設定された値を返さなければなりません >>77。データ型は DOMString で、初期値は空文字列です >>77。

close イベント

[117] close, onclose, CloseEvent を参照。

歴史

メモ

[9] RFC には同じようにみえる規定が複数あり、助動詞が MUST だったり SHOULD だり無かったりしていたり、しかし微妙に条件が違っていて矛盾しているのかどうか判断が難しかったりします。 IETF の仕様書ではよくあることですが、RFC の細部は信頼せず、 相互運用性が高いと思われる方法を推測して実装する必要がありそうです。

[138] Use utf-8 decode without BOM rather than UTF-8 decoder · whatwg/html@39a2e6c ( 版) <https://github.com/whatwg/html/commit/39a2e6cde3b4820db56fabe1859de0dc0e6ed8d9>

[139] Editorial: xref UTF-8 encode in WebSocket's close() · whatwg/html@e2bd61d ( 版) <https://github.com/whatwg/html/commit/e2bd61d1e4bd356b747e15c4e724a75eadbb25ac>