[122] [[WebSocketクライアント]]は [DFN[[CODE(DOMm)@en[[[close]]]]]] 
[[メソッド]]によって接続を閉じることができます。 [[WebSocket接続]]を閉じる際は
[DFN[[[[CODE[Close]]フレーム]]]]による [DFN[[[closing handshake]]]] が行われます。

[123] その他に、 [[[CODE[Close]]フレーム]]を使わない異常終了処理も規定されています。

* 仕様書

[REFS[
- [1] [CITE@en[RFC 6455 - The WebSocket Protocol]] ([TIME[2015-03-11 20:42:50 +09:00]] 版) <http://tools.ietf.org/html/rfc6455#section-5.5.1>
- [12] [CITE@en[RFC 6455 - The WebSocket Protocol]] ([TIME[2015-03-11 20:42:50 +09:00]] 版) <http://tools.ietf.org/html/rfc6455#section-7>
- [49] [CITE@en[RFC 6455 - The WebSocket Protocol]] ([TIME[2015-03-11 20:42:50 +09:00]] 版) <http://tools.ietf.org/html/rfc6455#section-8>
- [42] [CITE@en[RFC 6455 - The WebSocket Protocol]] ([TIME[2015-03-11 20:42:50 +09:00]] 版) <http://tools.ietf.org/html/rfc6455#section-10.7>
- [54] [CITE[RFC Errata Report]] ([TIME[2015-05-16 22:26:12 +09:00]] 版) <http://www.rfc-editor.org/errata_search.php?rfc=6455>
- [58] [CITE@en-GB-x-hixie[HTML Standard]] ([TIME[2015-05-06 10:42:35 +09:00]] 版) <https://html.spec.whatwg.org/#dom-websocket-close>
- [62] [CITE@en-GB-x-hixie[HTML Standard]] ([TIME[2015-05-06 10:42:35 +09:00]] 版) <https://html.spec.whatwg.org/#feedback-from-the-protocol>
- [77] [CITE@en-GB-x-hixie[HTML Standard]] ([TIME[2015-05-06 10:42:35 +09:00]] 版) <https://html.spec.whatwg.org/#closeevent>
- [84] [CITE@en-GB-x-hixie[HTML Standard]] ([TIME[2015-05-06 10:42:35 +09:00]] 版) <https://html.spec.whatwg.org/#garbage-collection-3>
]REFS]

* 接続の終了

[44] [[WebSocket接続]]の終了は、次のような動作により開始されます。
[FIG(list)[
- [45] [CODE(DOMi)@en[[[WebSocket]]]] [[オブジェクト]]の [CODE(DOMm)@en[[[close]]]] 
メソッド (>>59) や、それに相当する操作
- [105] 相手からの [[[CODE[Close]]フレーム]]の受信 (>>25)
- [47] [CODE(DOMi)@en[[[WebSocket]]]] [[オブジェクト]]の [[make disappear]] (>>90)
- [48] [CODE(DOMi)@en[[[WebSocket]]]] [[オブジェクト]]の[[ごみ収集]] (>>89)
- [46] [[WebSocket handshake]] や [[WebSocketメッセージ受信]]による[[WebSocket接続失敗]]
- [55] [[WebSocket handshake]] による [[Abort the WebSocket Connection]] (>>38)
- [56] 下位層の接続が切断されることによる[[WebSocket接続失敗]] (>>37)
- [104] その他の理由での [[Start the WebSocket Closing Handshake]] (>>36, >>40)
]FIG]

[128] 接続を閉じる動作の大まかな流れは次のようになります。

[FIG(flow)[
:handshake: 接続確立
:->:failed
:->:abort
:->:normal
:v:
:>>:3

:disconnected:下位層切断
:v:
:>>:1
:->:failed

:normal:送受信
:->:failed
:->:Close received
:>>:5

:make disappear:disappear
:v:
:->:failed
:->:abort
:>>:1

:close method:[CODE(DOMm)@en[[[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:[CODE[[[Close]]]] 送信
:->:close
:->:Close received
:>>:2
:v:

:Close received:[CODE[[[Close]]]] 受信
:->:send Close 2
:->:close
:v:
:>>:5

:send Close 2:[CODE[[[Close]]]] 送信
:v:
:>>:5
:->:close

:close:閉じる
:v:
:>>:1

]FIG]

[37] [[クライアント]]は、[[トランスポート層]]の[[接続]]が失われたら、
[[WebSocket接続失敗]]を実行しなければ[['''なりません''']] [SRC[>>12]]。

;; [57] なぜか[[サーバー]]に関する規定はありません。

[36] [[クライアント]]は、[[応用]]から指示された場合や[[プロトコル]]で規定されている場合を除き、
[[接続]]を閉じる[['''べきではありません''']] [SRC[>>12]]。
閉じる場合は、 [[Start the WebSocket Closing Handshake]] を実行します [SRC[>>12]]。

[40] [[サーバー]]は、適宜接続を閉じて構いません [SRC[>>12]]。
閉じる場合は、 [[Start the WebSocket Closing Handshake]] を実行します [SRC[>>12]]。

[131] [[Chrome]] は異常終了時 (で可能なとき) に、
[[状態符号]] [CODE[[[1001]]]] で理由[[空文字列]]の[[Closeフレーム]]を送信するようです。
[TIME[2015-08-22T06:18:57.300Z]]

* [CODE(DOMi)@en[WebSocket]] インターフェイス [CODE(DOMm)@en[close]] メソッド

[59] [CODE(DOMi)@en[[[WebSocket]]]] [[インターフェイス]]の
[DFN[[CODE(DOMm)@en[[[close]]]]]] [[メソッド]] [SRC[>>58]] は、
[[WebSocket]] の[[接続]]を閉じることを指示するものです。

[159] 第1引数として、[[WebSocket状態符号]] (数値) を指定できます。
この引数は省略可能です。指定された場合は、 [[Closeフレーム]]で[[サーバー]]に送られます。
いくつかの[[状態符号]]の値は[[プロトコル]]により定められていますが、
[[サーバー]]と[[クライアント]]の裁量で決められる値の範囲もあります。

;; [[WebSocket状態符号]]参照。

[160] 第2引数として、[[WebSocket接続閉じ理由]] (文字列) を指定できます。
この引数は省略可能です。指定された場合は、[[Closeフレーム]]で[[サーバー]]に送られます。
[[サーバー]]と[[クライアント]]の裁量で任意の文字列を使うことができますが、
[[UTF-8]] で[[符号化]]したときに123[[バイト]][[以下]]に収まらなければなりません。

[161] この[[メソッド]]は接続を閉じることを指示するものですが、
送受信の状態によっては、すぐには接続が閉じられない場合もあります。

[158] 本[[メソッド]]は、次のようにしなければ[['''なりません''']] [SRC[>>58]]。

[FIG(steps)[
= [140] [VAR[符号]]を、省略可能な第1引数を [CODE(IDL)@en[unsigned short]] として解釈した結果に設定します。
[CODE(IDL xattr)@en[Clamp]] [[拡張属性]]を適用します。
= [141] [VAR[理由]]を、省略可能な第2引数を [CODE(IDL)@en[USVString]] として解釈した結果に設定します。
= [142] [VAR[符号]]が [CODE[1000]] か [ [CODE[3000]], [CODE[4999]] ] の範囲になければ、
== [143] [CODE(DOMe)@en[InvalidAccessError]] [[例外]]を[[投げ]]、ここで停止します。
= [144] [VAR[理由]]が [[null]] でなければ、
== [145] [VAR[理由]]を、[VAR[理由]]に[[UTF-8符号化]]を適用した結果に設定します。
== [146] [VAR[理由]]の長さが123[[バイト]]より大きければ、
=== [147] [CODE(DOMe)@en[SyntaxError]] [[例外]]を[[投げ]]、ここで停止します。
= [148] [[文脈オブジェクト]]の [CODE(DOMa)@en[readyState]] が [CODE(DOMc)@en[[[CLOSING]]]] ([CODE[[[2]]]]) か
[CODE(DOMc)@en[[[CLOSED]]]] ([CODE[[[3]]]]) なら、
== [149] ここで停止します。
= [150] それ以外で、[[文脈オブジェクト]]がまだ[[WebSocket接続確立]]に達していなければ 
(= [F[接続の状態]]が [CODE(DOMc)@en[[[CONNECTING]]]] なら)、
== [151] [[文脈オブジェクト]]について[[WebSocket接続失敗]]を実行します。
== [152] [[文脈オブジェクト]]の [CODE(DOMa)@en[readyState]] を、
[CODE(DOMc)@en[[[CLOSING]]]] ([CODE[[[2]]]]) に設定します。
= [153] それ以外で、[[文脈オブジェクト]]がまだ
[[The WebSocket Closing Handshake is Started]] に達していなければ
(= [F[接続の状態]]が [CODE(DOMc)@en[[[OPEN]]]] なら)、
== [154] [[文脈オブジェクト]]について [[Start the WebSocket closing handshake]] を実行します。
[[状態符号]]は[VAR[符号]]、理由は[VAR[理由]]とします。
== [155] [[文脈オブジェクト]]の [CODE(DOMa)@en[readyState]] を、 
[CODE(DOMc)@en[[[CLOSING]]]] ([CODE[[[2]]]]) に設定します。
= [156] それ以外なら、
== [157] [[文脈オブジェクト]]の [CODE(DOMa)@en[readyState]] を、 
[CODE(DOMc)@en[[[CLOSING]]]] ([CODE[[[2]]]]) に設定します。
]FIG]

;; [60] 接続の状態と [CODE(DOMa)@en[[[readyState]]]] は異なることがあります。
[[WebSocket接続]]を参照。

[134] [[Chrome]] は、 [[接続]]を開いて [[handshake]] を開始した後で確立する前に
[CODE[[[close]]]] したとき、接続確立直後に [[Closeフレーム]]を送信するようです。
[[handshake]] 失敗時は送信しません。 [TIME[2015-08-22T07:47:25.300Z]]

* 文書の破棄

[90] [CODE(DOMi)@en[[[WebSocket]]]] [[オブジェクト]]の [DFN[[[make disappear]]]]
は、次のようにしなければ[['''なりません''']] [SRC[>>84]]。

[FIG(steps)[
= 接続の状態が [CODE[[[CONNECTING]]]] なら、
== [[WebSocket接続失敗]]を実行します。
= 接続の状態が [CODE[[[OPEN]]]] なら、
== [[Start the WebSocket Closing Handshake]] を実行します。
[[状態符号]]は [CODE[[[1001]]]] とします。
]FIG]

;; [91] これは [[unloading document cleanup steps]] から呼び出されます。

* ごみ収集

[87] [CODE(DOMi)@en[[[WebSocket]]]] [[オブジェクト]]の[[ごみ収集]]は、
次のように規定されています。

[85] [[event loop step 1]] 時点での [CODE(DOMa)@en[[[readyState]]]] が、
[FIG(list)[
- [CODE(DOM)[[[CONNECTING]]]] なら、
[CODE(DOMe)@en[[[open]]]], [CODE(DOMe)@en[[[message]]]], [CODE(DOMe)@en[[[error]]]],
[CODE(DOMe)@en[[[close]]]] のいずれかの[[イベントリスナー]]が登録されているなら、
[[ごみ収集]]しては[['''なりません''']]。 [SRC[>>84]]
- [CODE(DOM)[[[OPEN]]]] なら、
[CODE(DOMe)@en[[[message]]]], [CODE(DOMe)@en[[[error]]]],
[CODE(DOMe)@en[[[close]]]] のいずれかの[[イベントリスナー]]が登録されているなら、
[[ごみ収集]]しては[['''なりません''']]。 [SRC[>>84]]
- [CODE(DOM)[[[CLOSING]]]] なら、
[CODE(DOMe)@en[[[error]]]], [CODE(DOMe)@en[[[close]]]] 
のいずれかの[[イベントリスナー]]が登録されているなら、
[[ごみ収集]]しては[['''なりません''']]。 [SRC[>>84]]
]FIG]

;; [116] 言い換えると、まだ実行される可能性のある[[イベントリスナー]]が存在している間は、
[[強い参照]]を保持し続ける必要があります。 [[event loop step 1]]
時点でそのような[[イベントリスナー]]が存在しなくなっていれば、
[[強い参照]]を破棄できます。

[86] 送信[[バッファー]]が空でないなら、[[ごみ収集]]しては[['''なりません''']] [SRC[>>84]]。

;; [88] この[[バッファー]]は [CODE(DOMi)@en[[[WebSocket]]]] [[オブジェクト]]に関連付けられた[[アプリケーション]]層のものであり、[[OS]] ([[ソケット]]) 側の[[バッファー]]とは異なります。

[89] [[ごみ収集]]により破棄する直前に、次の処理を実行しなければ[['''なりません''']]。
[FIG(steps)[
= [52] 接続の状態が [CODE[[[OPEN]]]] なら、
== [53] [[Start the WebSocket Closing Handshake]] を[[状態符号]]なしで実行します [SRC[>>84]]。
]FIG]

* 閉じ handshake

[20] [DFN[[[Start the WebSocket Closing Handshake]]]] は、
[[状態符号]][VAR[符号]] ([[整数]]または [[null]]) と[[理由]][VAR[理由]]
([[バイト列]]または [[null]]) について、
次のようにしなければ[MUST[なりません]]。
[FIG(steps)[
= [21] [[フレーム]]を送信します [SRC[>>12, >>58]]。
[FIG(list members)[
[FIGCAPTION[
[[フレーム]]
]FIGCAPTION]
:[CODE[[[opcode]]]]:[CODE[[[Close]]]]
:[[状態符号]]: [VAR[符号]]が [[null]] 以外なら、[VAR[符号]]
:[[close reason]]: [VAR[符号]]と[VAR[理由]]が [[null]] 以外なら、[VAR[理由]]
]FIG]
= [10] [[[CODE[Close]]フレーム]]送信済みフラグを設定します。
= [24] [[WebSocket接続]]の状態を [CODE[[[CLOSING]]]] とします [SRC[>>12]]。
= [11] [[The WebSocket Closing Handshake is Started]] を実行します [SRC[>>12]]。
= [26] [[[CODE[Close]]フレーム]]受信済みフラグが設定されていれば、
== [23] [[WebSocket接続を閉じる]]を実行します [SRC[>>1, >>12]]。
[VAR[cleanly]] は[[真]]に設定します [SRC[>>12]]。
[[状態符号]]と [[close reason]] を引き渡します。
]FIG]

[25] [[[CODE[Close]]フレーム]]を受信したら、次のようにします。
[FIG(steps)[
= [100] [[[CODE[Close]]フレーム]]送信済みフラグが設定されていれば、
== [101] [[WebSocket接続を閉じる]]を実行します [SRC[>>1, >>12]]。
[VAR[cleanly]] は[[真]]に設定します [SRC[>>12]]。
[[状態符号]]と [[close reason]] を引き渡します。
= [108] そうでなければ、
== [98] [[WebSocket接続]]の状態を [CODE[[[CLOSING]]]] とします [SRC[>>12]]。
== [99] [[The WebSocket Closing Handshake is Started]] を実行します [SRC[>>12]]。
== [8] [[WebSocket接続失敗]]フラグが設定されていなければ、[SRC[>>12]]
=== [7] [[フレーム]]を送信します [SRC[>>1]]。
これはできるだけすぐに行う[['''べきです''']]が、
現在のメッセージをすべて送り終えるまで遅延させても構いません [SRC[>>1]]。
[FIG(list members short)[
[FIGCAPTION[
[[フレーム]]
]FIGCAPTION]
:[CODE[[[opcode]]]]:[CODE[[[Close]]]]
:[[状態符号]]:あれば、受信した値とするのが普通です [SRC[>>1]]。
]FIG]
=== [126] [[[CODE[Close]]フレーム]]送信済みフラグを設定します。
=== [127] 下位層の接続を閉じます [SRC[>>1]]。[[WebSocket接続を閉じる]]処理 (>>13)
を実行するので良いと思われます。
]FIG]

[61] [[利用者エージェント]]は、[DFN[[[the WebSocket closing handshake is started]]]] の際、
次のようにしなければ[['''なりません''']]。
[FIG(steps)[
= [102] 次の[[タスク]]を[[タスクキュー]]に追加します [SRC[>>62]]。
[FIG(list members)[
[FIGCAPTION[
[[タスク]]
]FIGCAPTION]
:[[タスク源]]:[[WebSocketタスク源]]
:処理:
[FIG(steps)[
= [103] [CODE(DOMa)@en[[[readyState]]]] を、 [DFN[[CODE(DOM)@en[[[CLOSING]]]]]] ([CODE[[[2]]]])
に設定します。
;; [63] 既に [CODE(DOM)@en[[[CLOSING]]]] になっている場合もあります。
]FIG]
]FIG]
]FIG]

* 接続失敗

[7] [[WebSocket接続の確立]]や[[WebSocketメッセージ受信]]により、
[DFN[[RUBYB[WebSocket接続失敗]@en[Fail the WebSocket Connection]]]]となることがあります。

[33] 次のようにしなければ[['''なりません''']]。
[FIG(steps)[
= [34] [[WebSocket接続失敗]]フラグを[[真]]に設定します。
= [80] 接続の状態が [CODE[[[OPEN]]]] なら、
== [81] 適切な[[状態符号]]の [[[CODE[Close]]フレーム]]を送信する[['''べきです''']]。
ただしエラーの性質上相手が受信して処理しないだろうと思われるなら、省略して構いません。 [SRC[>>12]]
= [93] [[WebSocket接続を閉じる]]処理を実行します。 [SRC[>>12]]
= [94] [[クライアント]]なら[[利用者]]に問題を報告しても構いません。 [SRC[>>12]]
= [95] [[サーバー]]なら問題を記録する[['''べきです''']]。 [SRC[>>12]]
]FIG]

;; [76] [[RFC]] は「[[WebSocket接続確立]]を過ぎていれば」 [SRC[>>12]] との条件で
[[[CODE[Close]]フレーム]]を送ることを求めていますが、
[[サーバー]]には[[WebSocket接続確立]]が無く、また [CODE[[[CLOSING]]]]
になった後で再度 [[[CODE[Close]]フレーム]]を送るのは不適切と思われます。

;; [109] 接続の状態を [CODE[[[OPEN]]]] から [CODE[[[CLOSING]]]]
に変更するべきかもしれません。

[35] [[WebSocket接続失敗]]以後、受信したデータを処理し続けようとしては[['''なりません''']]。
これには、 [[[CODE[Close]]フレーム]]への返信も含まれます。 [SRC[>>12]]

[38] [[サーバー]]が [DFN[[[Abort the WebSocket Connection]]]] したら、
[[WebSocket接続を閉じる]]処理を実行しなければ[['''なりません''']] [SRC[>>12]]。

* 接続を閉じる

[13] [DFN[[RUBYB[WebSocket接続を閉じる]@en[Close the WebSocket Connection]]]]時には、
下位の[[TCP接続]]を閉じます [SRC[>>12]]。

[14] [[TCP接続]]と[[TLSセッション]]を綺麗に閉じる方法を使い、
受信したかもしれない[[バイト]]は捨てる[['''べきです''']] [SRC[>>12]]。

[16] 最も通常な場合には、まず ([[クライアント]]からではなく)
[[サーバー]]から [[TCP接続]]を閉じる[['''べきです''']] [SRC[>>12]]。
[[サーバー]]は、直ちに [[TCP]] を閉じる[['''べきです''']] [SRC[>>12, >>1]]。
[[クライアント]]は、[[サーバー]]から [[TCP]] が閉じられるのを待つ[['''べきです''']] [SRC[>>12, >>1]]。

;; [17] そうすることで、[[クライアント]]ではなく[[サーバー]]の [[TCP]]
が [CODE[[[TIME_WAIT]]]] 状態となります。 [CODE[[[TIME_WAIT]]]] 状態である
[[最大セグメント寿命]]の2倍 (2[[MSL]]) の間は改めて[[接続]]を開くことができませんが、
[[サーバー]]側ならより大きな[[シーケンス番号]]の [CODE[[[SYN]]]] を送信すれば直ちに[[接続]]を開けるので問題となりません。 [SRC[>>12]]

[EG[
[19] 例えば [[socket]] API では、
まず [CODE[[[SHUT_WR]]]] を指定して [CODE[[[shutdown]]]] を呼び出し、
[CODE[[[0]]]] が返されるまで [CODE[[[recv]]]] を呼び出し、
最後に [CODE[[[close]]]] を呼ぶことでこれを実装できます [SRC[>>12]]。
]EG]

[18] 十分な時間[[サーバー]]から [[TCP]] Close を受信しない場合などには[[クライアント]]から
[[TCP]] を閉じて構いません [SRC[>>12, >>1]]。

[132] [[Chrome]] は、 [[Closeフレーム]]の送信から 60s で [[Closeフレーム]]を受信しないとき、
[[TCP]] [[接続]]を閉じるようです。 [[Firefox]] は、 20s で閉じるようです。
[TIME[2015-08-22T07:44:50.500Z]]

[133] [[Chrome]] も [[Firefox]] も、 [[Closeフレーム]]を送信した後
[[Closeフレーム]]を受信したら、 [[TCP接続]]を閉じるようです。
切断まで [[Chrome]] は 2s 強、 [[Firefox]] は 1s 強待つようです。 [TIME[2015-08-22T07:44:49.500Z]]

[135] [[Closeフレーム]]を受信した後 [[Closeフレーム]]を送信してから、
[[Chrome]] は 2s、[[Firefox]] は 1s 待機した後、
[[TCP接続]]を閉じるようです。 [TIME[2015-08-22T08:07:34.000Z]]

[136] [[Chrome]] は [[Closeフレーム]]受信後にも[[フレーム]]の解釈を続け、
エラーがあったら[[コンソール]]に報告します。 [[Firefox]] はそのような報告はしないので、
解釈しているのかどうかは不明です。 [TIME[2015-08-22T08:09:26.000Z]]

[15] 攻撃されている時など、必要に応じて他の方法で閉じても構いません [SRC[>>12]]。

[137] [[Chrome]] も [[Firefox]] も、[[WebSocket接続失敗]]時にすぐに
[[TCP接続]]を閉じるようです。 [TIME[2015-08-22T09:34:03.000Z]]

[27] [[TCP接続]]が閉じられた時、次のようにしなければなりません。
[FIG(steps)[
= [29] [[WebSocket接続]]の状態を、 [CODE[[[CLOSED]]]] に設定します [SRC[>>12]]。
= [110] [[The WebSocket Connection is Closed]] を実行します [SRC[>>12]]。
[VAR[[[cleanly]]]] フラグを引き継ぎます。
]FIG]

[64] [[利用者エージェント]]は [DFN[[[The WebSocket Connection is Closed]]]] の際、
次のようにしなければ[['''なりません''']]。
[FIG(steps)[
= [118] [[WebSocket接続失敗]]フラグが設定されているか、
[[flagged as full]] フラグが設定されているなら、
== [119] [VAR[失敗]]を、[[真]]に設定します。
= [120] それ以外なら、
== [121] [VAR[失敗]]を、[[偽]]に設定します。
= [111] 次の[[タスク]]を[[タスクキュー]]に追加します [SRC[>>62]]。
[FIG(list members)[
[FIGCAPTION[
[[タスク]]
]FIGCAPTION]
:[[タスク源]]:[[WebSocketタスク源]]
:処理:
[FIG(steps)[
= [112] [CODE(DOMa)@en[[[readyState]]]] を、 [DFN[[CODE(DOM)@en[[[CLOSED]]]]]] ([CODE[[[3]]]])
に設定します。
= [113] [VAR[失敗]]が[[真]]なら、
[[単純イベントを発火]]します。
[FIG(list members short)[
[FIGCAPTION[
[[単純イベント]]
]FIGCAPTION]
:[[イベント型]]:[CODE(DOMe)@en[[[error]]]]
:[[対象オブジェクト]]:[CODE(DOMi)@en[[[WebSocket]]]] [[オブジェクト]]
]FIG]
= [114] [[イベント]]を[[発火]]します。
[FIG(list members short)[
[FIGCAPTION[
[[イベント]]
]FIGCAPTION]
:[[インターフェイス]]:[CODE(DOMi)@en[[[CloseEvent]]]]
:[[イベント型]]:[CODE(DOMe)@en[[[close]]]]
:[[対象オブジェクト]]:[CODE(DOMi)@en[[[WebSocket]]]] [[オブジェクト]]
:[[bubbles]]:[[偽]]
:[[取り消し可能]]:[[偽]]
:[[既定動作]]:なし
:[CODE(DOMa)@en[[[wasClean]]]]:[VAR[cleanly]]
:[CODE(DOMa)@en[[[code]]]]:[[WebSocket接続閉じ符号]]
:[CODE(DOMa)@en[[[reason]]]]:[[WebSocket接続閉じ理由]]
]FIG]
;; [115] [[WebSocket接続閉じ符号]]と [[WebSocket接続閉じ理由]]は、
(あれば) 指定された[[状態符号]]と [[close reason]] から決まります。
]FIG]
]FIG]
]FIG]

[28] [DFN[[VAR[[[cleanly]]]]]] フラグは、
[[WebSocket接続]]の閉じ handshake が完了してから [[TCP接続]]が閉じられた [SRC[>>12]]
かどうかを表します。

[78] [CODE(DOMi)@en[[[CloseEvent]]]] [[インターフェイス]]の
[DFN[[CODE(DOMa)@en[[[wasClean]]]]]] [[属性]]は、 [VAR[cleanly]]
に閉じられたかどうかを表します。

[79] この[[属性]]は、その[[属性値]]として設定された値を返さなければ[['''なりません''']] [SRC[>>77]]。[[データ型]]は [CODE(IDL)@en[[[boolean]]]] で、初期値は[[偽]]です [SRC[>>77]]。

[130] [DFN[[[flagged as full]]]] は、[[バッファー]]が満杯でデータを送信できないことを表します。
[CODE(DOMm)@en[[[send]]]] [[メソッド]]からデータを送信する時に設定されることがあります。

[66] [[利用者エージェント]]は、[[スクリプト]]に次の状況を区別できる情報を提供しては[['''なりません''']] [SRC[>>62]]。
[FIG(list)[
- [67] [[ホスト名]]が[[名前解決]]できなかった場合
- [68] [[サーバー]]に[[パケット]]を配送する[[経路]]を見つけられなかった場合
- [69] [[ポート]]への[[接続]]が拒まれた場合
- [70] [[TLS handshake]] に失敗した場合 (例えば[[証明書]]を検証できなかった場合)
- [71] [[WebSocket handshake]] を完了できなかった場合
- [72] [[WebSocket handshake]] は完了したものの、[[クライアント]]が[[接続]]を閉じる状況となった場合 (例えば[[クライアント]]が想定しない[[部分プロトコル]]を[[サーバー]]が指定した場合)
- [73] [[WebSocket handshake]] は成功したものの[[接続]]が予期せず閉じられた場合
]FIG]

[74] このような場合の[[WebSocket接続閉じ符号]]はいずれも [CODE[[[1006]]]] となります [SRC[>>62]]。

;; [75] こうした状況を区別できるとすると、[[スクリプト]]が[[利用者]]の属するネットワークの状態を調べるために使えてしまいます [SRC[>>62]]。

* 再接続待ち

[39] [[クライアント]]が異常終了の後再接続を試みる際は、
何らかの [[backoff]] を行う[['''べきです''']]。
最初の再接続は、[[無作為]]に決めた時間だけ遅延させる[['''べきです''']]。
0s-5s の間から[[無作為]]に決めるのが妥当でしょうが、
実装経験や[[応用]]に基づき任意の方法で決めて構いません。
それ以後の再接続は、[RUBYB[[[冪乗打ち切り待機法]]]@en[truncated binary exponential backoff]]などにより徐々に時間を長くしていく[['''べきです''']]。 [SRC[>>12]]

;; [65] これを誰が実装するべきなのかは不明です。 [CODE(DOMi)@en[[[WebSocket]]]]
[[API]] の内部で実装するべきなのでしょうか? 
それとも[[スクリプト]]が個別に実装するべきなのでしょうか?

* [CODE[Close]] フレーム

[96] [DFN[[CODE[Close]]フレーム]]は、 [[WebSocket接続]]を閉じる際に用いられます。

;; [97] 正常終了にも異常終了にも用いられます。もっとも、異常終了時は
[[[CODE[Close]]フレーム]]なしで切断される場合もあります。

** 構文

[2] [[opcode]] は、 [CODE[0x8]] です [SRC[>>1]]。
[CODE[[[Close]]]] [[フレーム]]は、[[制御フレーム]]です。

[3] 閉じる理由を示す[RUBYB[[[本体]]]@en[body]] ([[応用データ]]) を含んで構いません [SRC[>>1]]。

[FIG(packet)[
:width:16

= 16 [[状態符号]]
= 16... [[close reason]]
]FIG]

[4] 本体がある場合、最初の2バイトは[[ネットワークバイト順]]の2バイト[[符号無し整数]]でなければ[['''なりません''']]。
この[[整数]]は、[[状態符号]]でなければ[['''なりません''']]。 [SRC[>>1]]

[5] 本体は更に [[UTF-8]] 符号化されたデータを含めることができます。
その解釈は [[WebSocket]] 仕様書では規定されておらず、[[人間可読]]である必要もありませんが、
[[デバッグ]]などに有用かもしれません。[[クライアント]]はこれを[[末端利用者]]に示しては[['''なりません''']]。
[SRC[>>1]]

;; [22] この部分は「[DFN[[[close reason]]]]」[SRC[>>12]] と呼ばれています。

[FIG(railroad)[
= ?
== [[状態符号]]
== *
=== [[Unicode文字]]
]FIG]

[125] 利用する場面によっては、[[状態符号]]と[[close reason]]は指定されていません。
そのような場合にどうするべきか、 [[RFC]] 上は明確ではありません。
実装は状況に応じた適当な値を指定できると思われます。これは[[デバッグ]]には有用かもしれませんが、
一方で [[fingerprinting vector]] となったり、不必要な内部情報を送ってしまったりするかもしれませんから、
[[クライアント]]は特に注意が必要です。

** 文脈

[106] >>20 参照。

** 処理

[107] >>25 参照。

[6] [[応用]]は、 [CODE[[[Close]]]] [[フレーム]]より後に[[データフレーム]]を送っては[['''なりません''']]
[SRC[>>1]]。

[31] [[応用データ]]が1バイトのみしかない場合にどう解釈するべきかは不明です。
非妥当なデータを受信した場合は[[WebSocket接続を閉じる]]ことが認められており [SRC[>>42]]、
それに従う (というより問題を無視して閉じる処理を続ける) のが適切かもしれません。

;; [43] 規定が曖昧だと[[相互運用性]]に問題は無いのでしょうかね?

[50] 理由が [[UTF-8]] として[[妥当]]でなければ、[[WebSocket接続失敗]]しなければ[['''なりません''']] [SRC[>>49]]。

;; [51] が、無視して閉じる処理を続ける方が適切かもしれません。

[129] [[Chrome]] は、 payload が1バイトのみの時、[[状態符号]] [CODE[[[1002]]]]、
理由[[空文字列]]の[[Closeフレーム]]を送信します。
理由が [[UTF-8]] として正しくない時、[[状態符号]] [CODE[[[1002]]]]、
理由 [CODE[Invalid UTF-8 in Close frame]] を送信します。
[[状態符号]]が [CODE[[[1005]]]]、[CODE[[[1006]]]] の時、
[[状態符号]] [CODE[[[1002]]]] 理由[[空文字列]]を送信します。
[[Firefox]] はこれらの時、データなしの [[Closeフレーム]]を送信します。
[[Firefox]] は[[状態符号]]が [CODE[[[0]]]] の時もデータなしの
[[Closeフレーム]]を送信します。 [TIME[2015-08-16T15:31:14.200Z]]

* 状態符号と理由

[41] [[[CODE[Close]]フレーム]]には、閉じる理由を記述できます。
数値の[[状態符号]]と、任意の文字列を記述できます。
受信者による理由の解釈や処理方法は、 [[WebSocket Protocol]]
としては規定されていません [SRC[>>12]]。

;; [30] [[WebSocket状態符号]]も参照。

[124] [[close reason]] は、[[制御フレーム]]の制約と[[状態符号]]が2バイトであることから、
123バイト[[以下]]である必要があります。 ([[UTF-8]] [[符号化]]された[[バイト列]]の制限で、
[[文字列]]の制限ではありません。) [CODE(DOMi)@en[[[WebSocket]]]] [[インターフェイス]]の
[CODE(DOMm)@en[[[close]]]] [[メソッド]]では、この制限を超えると[[例外]]が投げられます。

[32] [DFN[[RUBYB[WebSocket接続閉じ理由]@en[The WebSocket Connection Close Reason]]]]は、
[[応用]]が最初に受信した [[[CODE[Close]]フレーム]]に含まれていた閉じる理由
([[UTF-8 decode without BOM]] を適用 [SRC[>>62]] した[[文字列]]) 
です。そのようなものが無ければ、
[[空文字列]]です。 [SRC[>>12]]

[82] [CODE(DOMi)@en[[[CloseEvent]]]] [[インターフェイス]]の
[DFN[[CODE(DOMa)@en[[[reason]]]]]] [[属性]]は、[[WebSocket接続閉じ理由]]を表します。

[83] この[[属性]]は、その[[属性値]]として設定された値を返さなければ[['''なりません''']] [SRC[>>77]]。[[データ型]]は [CODE(IDL)@en[[[DOMString]]]] で、初期値は[[空文字列]]です [SRC[>>77]]。

* [CODE(DOMe)@en[close]] イベント

[117] [CODE(DOMe)@en[[[close]]]], [CODE(DOMa)@en[[[onclose]]]],
[CODE(DOMi)@en[[[CloseEvent]]]] を参照。

* 歴史

[REFS[
- [92] [CITE@en[Web Applications 1.0 r2870 Drop <eventsource>. Replace the API with an EventSource object. Rename onclosed to onclose on WebSocket. Remove the mostly worthless event definitions. I'll add more useful intro text to replace them later. Defined garbage collection specialness for WebSocket and EventSource.]] ([TIME[2009-02-26 17:33:00 +09:00]] 版) <http://html5.org/tools/web-apps-tracker?from=2869&to=2870>
]REFS]

* メモ

[9] [[RFC]] には同じようにみえる規定が複数あり、[[助動詞]]が [[MUST]] だったり [[SHOULD]]
だり無かったりしていたり、しかし微妙に条件が違っていて矛盾しているのかどうか判断が難しかったりします。
[[IETF]] の仕様書ではよくあることですが、[[RFC]] の細部は信頼せず、
[[相互運用性]]が高いと思われる方法を推測して実装する必要がありそうです。

[138] [CITE@en[Use utf-8 decode without BOM rather than UTF-8 decoder · whatwg/html@39a2e6c]]
([TIME[2016-02-10 21:50:21 +09:00]] 版)
<https://github.com/whatwg/html/commit/39a2e6cde3b4820db56fabe1859de0dc0e6ed8d9>

[139] [CITE@en[Editorial: xref UTF-8 encode in WebSocket's close() · whatwg/html@e2bd61d]]
([TIME[2016-03-29 00:35:19 +09:00]] 版)
<https://github.com/whatwg/html/commit/e2bd61d1e4bd356b747e15c4e724a75eadbb25ac>