RFC 6455 の仕様に記載されている WebSocket
プロトコルは、持続的な接続を介してブラウザとサーバー間でデータを交換する方法を提供します。
WebSocket は、オンライン ゲームやリアルタイム トレーディング システムなど、継続的なデータ交換を必要とするサービスに特に適しています。
簡単な例
WebSocket 接続を開くには、URL の特別なプロトコル ws
new WebSocket
を作成する必要があります。
let socket = new WebSocket("ws://javascript.info");
また、暗号化されたwss://
プロトコルもあります。
wss://
wss://
プロトコルは、暗号化されているだけでなく、より信頼性があります。
それは、ws://
wss://
はTLS上のWebSocketであり(HTTPSがTLS上のHTTPであるのと同じ)、トランスポート セキュリティ レイヤが送信者でデータを暗号化し、受信者で復号化します。 そのため、データパケットは暗号化されてプロキシを通過します。
ソケットが作成されたら、そのイベントをリッスンする必要があります。 イベントは全部で4つあります。
-
open
– connection established, -
message
– data received, -
error
– Websocketエラー、 -
close
– 接続の終了。
…そして、何かを送信したい場合は、socket.send(data)
がそれを行います。
以下に例を示します。
let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello");socket.onopen = function(e) { alert(" Connection established"); alert("Sending to server"); socket.send("My name is John");};socket.onmessage = function(event) { alert(` Data received from server: ${event.data}`);};socket.onclose = function(event) { if (event.wasClean) { alert(` Connection closed cleanly, code=${event.code} reason=${event.reason}`); } else { // e.g. server process killed or network down // event.code is usually 1006 in this case alert(' Connection died'); }};socket.onerror = function(error) { alert(` ${error.message}`);};
デモのために、上の例のNode.jsで書かれた小さなサーバーserver.jsが動いています。
つまり、イベント open
message
close
が表示されているわけです。
さて、もう少し踏み込んだ話をしてみましょう。
Websocketを開く
new WebSocket(url)
が作成されると、すぐに接続を開始します。
接続中、ブラウザは(ヘッダーを使用して)サーバーに尋ねます。 “Do you support Websocket?” サーバーが「はい」と答えると、HTTPではないWebSocketプロトコルで会話が続きます。
new WebSocket("wss://javascript.info/chat")
が行ったリクエストのブラウザヘッダーの例です。
GET /chatHost: javascript.infoOrigin: https://javascript.infoConnection: UpgradeUpgrade: websocketSec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==Sec-WebSocket-Version: 13
-
Origin
https://javascript.info
。 WebSocket オブジェクトは、もともとクロスオリジンです。 特別なヘッダやその他の制限はありません。 古いサーバーはどのみちWebSocketを扱えないので、互換性の問題はありません。 -
Connection: Upgrade
– クライアントがプロトコルの変更を希望していることを示すシグナルです。 -
Upgrade: websocket
– 要求されたプロトコルは「websocket」です。 -
Sec-WebSocket-Key
– セキュリティのためにブラウザが生成したランダムなキーです。 -
Sec-WebSocket-Version
– WebSocket プロトコルのバージョンで、13 が現在のものです。
この種のHTTPリクエストを行うために、XMLHttpRequest
fetch
を使用することはできません。
サーバーがWebSocketへの切り替えに同意した場合、コード101のレスポンスを送信する必要があります。
101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
ここでSec-WebSocket-Accept
Sec-WebSocket-Key
です。
その後、データは WebSocket プロトコルを使用して転送されますが、その構造 (フレーム) については後ほど説明します。
拡張機能とサブプロトコル
拡張機能やサブプロトコルを記述する追加のヘッダー Sec-WebSocket-Extensions
Sec-WebSocket-Protocol
があるかもしれません。
例えば、
-
Sec-WebSocket-Extensions: deflate-frame
は、ブラウザがデータ圧縮をサポートしていることを意味しています。 拡張機能とは、データの転送に関連するもので、WebSocketプロトコルを拡張する機能のことです。 -
Sec-WebSocket-Protocol: soap, wamp
は、単なるデータではなく、SOAP または WAMP (「The WebSocket Application Messaging Protocol」) プロトコルでデータを転送したいことを意味します。 WebSocketのサブプロトコルは、IANAカタログに登録されています。このオプションのヘッダーは、
new WebSocket
の第2パラメータを使って設定します。let socket = new WebSocket("wss://javascript.info/chat", );
サーバーは、使用することに同意したプロトコルと拡張機能のリストを応答する必要があります。
例えば、リクエスト:
GET /chatHost: javascript.infoUpgrade: websocketConnection: UpgradeOrigin: https://javascript.infoSec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==Sec-WebSocket-Version: 13Sec-WebSocket-Extensions: deflate-frameSec-WebSocket-Protocol: soap, wamp
レスポンス。
101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=Sec-WebSocket-Extensions: deflate-frameSec-WebSocket-Protocol: soap
ここでサーバーは、拡張子「deflate-frame」をサポートしており、要求されたサブプロトコルのうちSOAPのみをサポートしていることを応答しています。
データ転送
WebSocket の通信は、「フレーム」と呼ばれるデータの断片で構成されており、どちらの側からでも送信することができます。
ブラウザでは、テキストまたはバイナリのフレームのみを直接操作します。
WebSocket .send()
メソッドは、テキストまたはバイナリのデータを送信できます。
socket.send(body)
body
Blob
ArrayBuffer
などのバイナリ形式で送信できます。
データを受信すると、テキストは常に文字列として送られてきます。 また、バイナリデータの場合は、Blob
ArrayBuffer
の2つのフォーマットを選択できます。
これはsocket.binaryType
"blob"
Blob
オブジェクトとして提供されます。
Blob は高レベルのバイナリ オブジェクトで、<a>
<img>
などのタグと直接統合されているので、これがまともなデフォルトです。
socket.binaryType = "arraybuffer";socket.onmessage = (event) => { // event.data is either a string (if text) or arraybuffer (if binary)};
速度制限
想像してみてください、アプリが大量のデータを送信しているとします。
私たちは、socket.send(data)
を何度も呼び出すことができます。
socket.bufferedAmount
プロパティには、現時点でバッファリングされている残りのバイト数が格納されており、ネットワーク経由で送信されるのを待っています。
このプロパティを調べることで、ソケットが実際に送信可能かどうかを確認できます。
// every 100ms examine the socket and send more data// only if all the existing data was sent outsetInterval(() => { if (socket.bufferedAmount == 0) { socket.send(moreData()); }}, 100);
接続の終了
通常、当事者が接続を終了したい場合(ブラウザとサーバーの両方に同等の権利があります)、数字のコードとテキストの理由を含む「接続終了フレーム」を送信します。
その方法としては
socket.close(, );
-
code
は特別なWebSocketの終了コード(オプション) -
reason
は閉じる理由を表す文字列(任意)
これで、close
イベントハンドラの相手はコードと理由を取得します。 e.g.
// closing party:socket.close(1000, "Work complete");// the other partysocket.onclose = event => { // event.code === 1000 // event.reason === "Work complete" // event.wasClean === true (clean close)};
最も一般的なコードの値。
-
1000
code
が供給されていない場合に使用)、 -
1006
– このようなコードを手動で設定する方法はなく、接続が失われたことを示します(クローズフレームがない)。
他にも次のようなコードがあります:
-
1001
– パーティーが終了すること。 -
1009
– メッセージが大きすぎて処理できません -
1011
– サーバーで予期せぬエラーが発生しました - ……など。
完全なリストはRFC6455の§7.4.1にあります。
WebSocketのコードはHTTPのコードに似ていますが、違います。 特に、1000
以下のコードは予約されており、このようなコードを設定しようとするとエラーになります。
// in case connection is brokensocket.onclose = event => { // event.code === 1006 // event.reason === "" // event.wasClean === false (no closing frame)};
接続状態
接続状態を取得するために、値を持つsocket.readyState
プロパティが追加されました。
-
0
– “CONNECTING”: 接続はまだ確立されていません、 -
1
– “OPEN”:通信中です、 -
2
– “CLOSING”:接続が終了しています、 -
3
– “CLOSED”:接続が終了しています。
チャットの例
ブラウザのWebSocket APIとNode.jsのWebSocketモジュールhttps://github.com/websockets/wsを使ったチャットの例を見てみましょう。 ここでは、主にクライアント側に注目しますが、サーバー側も簡単です。
HTML: メッセージを送信するための <form>
<div>
が必要です。
<!-- message form --><form name="publish"> <input type="text" name="message"> <input type="submit" value="Send"></form><!-- div with messages --><div></div>
JavaScriptからは次の3つのことを行います:
- 接続を開く。
- フォーム送信時 –
socket.send(message)
にメッセージを追加。 - メッセージ受信時 –
div#messages
にメッセージを追加。
以下にコードを示します。
let socket = new WebSocket("wss://javascript.info/article/websocket/chat/ws");// send message from the formdocument.forms.publish.onsubmit = function() { let outgoingMessage = this.message.value; socket.send(outgoingMessage); return false;};// message received - show the message in div#messagessocket.onmessage = function(event) { let message = event.data; let messageElem = document.createElement('div'); messageElem.textContent = message; document.getElementById('messages').prepend(messageElem);}
サーバー側のコードは、今回の範囲を少し超えています。 ここでは、Node.jsを使用しますが、そうする必要はありません。
サーバー側のアルゴリズムは次のとおりです。
- Create
clients = new Set()
– a set of sockets. - 受け入れられた各ウェブソケットについて、それをセット
clients.add(socket)
message
イベントリスナーをセットアップします。 - メッセージを受信したら、クライアントを反復して全員に送信します。
clients.delete(socket)
。
const ws = new require('ws');const wss = new ws.Server({noServer: true});const clients = new Set();http.createServer((req, res) => { // here we only handle websocket connections // in real project we'd have some other code here to handle non-websocket requests wss.handleUpgrade(req, req.socket, Buffer.alloc(0), onSocketConnect);});function onSocketConnect(ws) { clients.add(ws); ws.on('message', function(message) { message = message.slice(0, 50); // max message length will be 50 for(let client of clients) { client.send(message); } }); ws.on('close', function() { clients.delete(ws); });}
ここに動作例があります:
ダウンロードして(iframeの右上のボタン)、ローカルで実行することもできます。
Summary
WebSocketは、ブラウザとサーバーの持続的な接続を行うための最新の方法です。
- WebSocketにはクロスオリジンの制限がありません。
- ブラウザでよくサポートされています。
- 文字列やバイナリデータを送受信できます。
APIはシンプルです。
Methods:
-
socket.send(data)
, -
socket.close(, )
.
Events:
-
open
, -
message
, -
error
, -
close
.
WebSocket単体では、再接続や認証、その他多くのハイレベルなメカニズムを含みません。
既存のプロジェクトに WebSocket を統合するために、メインの HTTP サーバーと並行して WebSocket サーバーを実行し、1 つのデータベースを共有することがあります。 WebSocket へのリクエストは、WebSocket サーバーにつながるサブドメインである wss://ws.site.com
https://site.com
はメインの HTTP サーバーに向かいます。
もちろん、他の統合方法も可能です。