WebSocket は、単一の伝送制御プロトコル(TCP) 接続を介して双方向通信チャネルを提供するコンピュータ通信プロトコルです。このプロトコルは、2011 年にIETFによってRFC 6455として標準化されました。Web アプリケーションがこのプロトコルを使用できるようにする現在の仕様は、WebSocketsとして知られています。[ 1 ]これはWHATWGによって維持されているライブ スタンダードであり、 W3CのWebSocket APIの後継です。[ 2 ]
WebSocketは、ほとんどのウェブページを提供するのに使用されるHTTPとは異なります。両者は異なりますが、RFC 6455ではWebSocketは「HTTPポート443と80で動作するように設計されており、HTTPプロキシと中間サーバーもサポートする」と規定されており、WebSocketプロトコルはHTTPと互換性があります。互換性を実現するために、WebSocketハンドシェイクではHTTP Upgradeヘッダー[ 3 ]を使用してHTTPプロトコルからWebSocketプロトコルに切り替えます。
WebSocketプロトコルは、Webブラウザ(またはその他のクライアントアプリケーション)とWebサーバー間の全二重通信を可能にし、HTTPポーリングなどの半二重方式よりもオーバーヘッドを低く抑え、サーバーとの間でリアルタイムのデータ転送を容易にします。これは、サーバーがクライアントからの要求を待たずにクライアントにコンテンツを送信するための標準化された方法を提供し、接続を開いたままメッセージを交換できるようにすることで実現されます。このようにして、クライアントとサーバー間で双方向の継続的な会話が可能になります。通信は通常TCPポート番号443(またはセキュリティ保護されていない接続の場合は80)で行われ、ファイアウォールを使用してWeb以外のインターネット接続をブロックする環境に適しています。さらに、WebSocketはTCP上でメッセージストリームを可能にします。TCP単独ではバイトストリームを扱い、メッセージという概念は持ちません。同様の双方向ブラウザ・サーバー通信は、CometやAdobe Flash Playerなどの暫定的な技術を使用して非標準化された方法で実現されています。[ 4 ]
Google Chrome、Firefox、Microsoft Edge、Internet Explorer、Safari、Operaなど、ほとんどのブラウザがこのプロトコルをサポートしています。[ 5 ]
WebSocketプロトコル仕様では、暗号化されていない接続と暗号化された接続にそれぞれ使用される2つの新しい統一リソース識別子(URI)スキームとしてws、(WebSocket)とwss(WebSocket Secure)が定義されています[ 6 ]。スキーム名とフラグメント(つまりはサポートされていません)を除いて、残りのURIコンポーネントはURI汎用構文を使用するように定義されています。[ 7 ]#
WebSocket は、当初、TCP ベースのソケット API のプレースホルダーとしてHTML5仕様で TCPConnection として参照されました。 [ 8 ] 2008 年 6 月、 Michael Carterが主導した一連の議論により、WebSocket として知られるプロトコルの最初のバージョンが作成されました。[ 9 ] WebSocket 以前は、 Cometチャネル を使用してポート 80 の全二重通信が可能でしたが、Comet の実装は簡単ではなく、TCP ハンドシェイクと HTTP ヘッダーのオーバーヘッドのため、小さなメッセージには非効率的でした。WebSocket プロトコルは、Web のセキュリティの前提を損なうことなくこれらの問題を解決することを目的としています。WebSocket という名前は、その直後にIan Hicksonと Michael Carter が #whatwg IRC チャット ルームでのコラボレーションを通じて考案し、[ 10 ]その後、Ian Hickson によって HTML5 仕様に含めるために作成されました。2009 年 12 月、Google Chrome 4 は、WebSocket がデフォルトで有効になっている標準の完全なサポートを出荷した最初のブラウザとなりました。[ 11 ] WebSocketプロトコルの開発はその後、2010年2月にW3CとWHATWGグループからIETFに移管され、イアン・ヒクソンの下で2回の改訂が行われた。[ 12 ]
このプロトコルが出荷され、複数のブラウザでデフォルトで有効になった後、RFC 6455は2011年12月にイアン・フェッテによって最終決定された。
RFC 7692では、メッセージごとにDEFLATEアルゴリズムを使用したWebSocketの圧縮機能が導入されました。
ウェブアプリケーション(ウェブブラウザなど)は、WebSocketインターフェースを使用してWebSocketサーバーとの双方向通信を維持することができます。[ 13 ]
TypeScriptでは。
// サーバーに接続しますconst ws = new WebSocket ( "wss://game.example.com/scoreboard" );// Blob の代わりに ArrayBuffer を受け取るws.binaryType = " arraybuffer " ;// イベントリスナーを設定するws.onopen = () => { console.log ( "接続が開かれました" ) ; ws.send ( "サーバーさん、昨日の試合のスコアを送ってください" ) ; } ;ws.onmessage = ( event : MessageEvent ) => { console.log ( "データを受信しました" , event.data ) ; ws.close ( ) ; //スコアを取得したので、接続はもう必要ありません} ;ws.onclose = ( event : CloseEvent ) = > { console.log ( "接続が閉じられました" , event.code , event.reason , event.wasClean ) ; } ;ws.onerror = () = > { console.log ( "エラーのため接続が閉じられました" ) ; } ;
手順:
クライアントはHTTP リクエスト(メソッドGET、バージョン≥ 1.1 ) を送信し、サーバーは成功した場合にステータス コード 101 (プロトコルの切り替え)のHTTP レスポンスを返します。HTTP クライアントと WebSocket クライアントは、オープニング ハンドシェイクで HTTP を使用するため、同じポートを使用してサーバーに接続できます。追加の HTTP ヘッダー (以下の表に記載されていないもの) を送信できます。HTTP ヘッダーは任意の順序で送信できます。プロトコルの切り替えHTTP レスポンスの後、オープニング ハンドシェイクが完了し、HTTP プロトコルは使用されなくなり、通信はバイナリ フレーム ベースのプロトコルに切り替わります。[ 33 ] [ 34 ]
リクエスト例:[ 34 ]
GET /chat HTTP / 1.1 Host : server.example.com Upgrade : websocket Connection : Upgrade Sec-WebSocket-Key : dGhlIHNhbXBsZSBub25jZQ== Origin : http://example.com Sec-WebSocket-Protocol : chat, superchat Sec-WebSocket-Version : 13応答例:[ 34 ]
HTTP / 1.1 101 Switching Protocols Upgrade : websocket Connection : Upgrade Sec-WebSocket-Accept : s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol : chat以下のPythonコードはランダムな を生成しますSec-WebSocket-Key。
import base64 import osprint ( base64.b64encode ( os.urandom ( 16 ) ) )以下のPythonコードは、上記の例のリクエストSec-WebSocket-Acceptを使用して計算を行います。Sec-WebSocket-Key
import base64 import hashlibKEY : bytes = b " dGhlIHNhbXBsZSBub25jZQ==" MAGIC : bytes = b " 258EAFA5 - E914-47DA-95CA-C5AB0DC85B11" print ( base64.b64encode ( hashlib.sha1 ( KEY + MAGIC ) .digest ( )))Sec-WebSocket-Keyこれらは、キャッシュプロキシが以前の WebSocket 会話を再送信するのをSec-WebSocket-Accept防ぐことを目的としており、 [ 49 ]認証、プライバシー、または完全性を提供するものではありません。
一部のサーバーは短い Sec-WebSocket-Key ヘッダーを受け入れますがSec-WebSocket-Key、多くの最新のサーバーは「無効な Sec-WebSocket-Key ヘッダー」というエラーでリクエストを拒否します。
初回ハンドシェイク後、クライアントとサーバーはいつでも、データメッセージ(テキストまたはバイナリ)と制御メッセージ(Close、Ping、Pong)を相互に送信できます。メッセージは、断片化されていない場合は1つのフレームで構成され、断片化されている場合は少なくとも2つのフレームで構成されます。
フラグメンテーションはメッセージを2 つ以上のフレームに分割します。これにより、初期データは利用可能だが全長が不明なメッセージを送信できます。フラグメンテーションがない場合、メッセージ全体を 1 つのフレームで送信する必要があるため、最初のバイトを送信する前に全長が必要となり、バッファが必要になります。[ 50 ]この機能を拡張して、複数のストリームを同時に多重化できるようにする提案がありました (たとえば、単一の大きなペイロードのためにソケットを独占することを避けるため) が、プロトコルの拡張は受け入れられませんでした。[ 51 ]
FIN = 1を含む 1 つのフレームで構成されますopcode ≠ 0。FIN = 0とを含む 1 つのフレーム、それopcode ≠ 0に続く と を含む 0 個以上のフレーム、そしてとを含む 1 つのフレームで終わります。FIN = 0opcode = 0FIN = 1opcode = 0クライアントはサーバーに送信するすべてのフレームをマスクする必要があります。サーバーはクライアントに送信するフレームをマスクしてはなりません。 [ 68 ]フレームマスキングは、ペイロードとマスキングキーの間でXOR演算を適用します。次の擬似コードは、フレームをマスクおよびマスク解除するために使用されるアルゴリズムを示しています。[ 57 ]
iを0からpayload_length − 1まで繰り返す payload[i] := payload[i] xor masking_key[i mod 4]
この拡張機能により、データメッセージをDEFLATEpermessage-deflateアルゴリズムを使用して圧縮できます。たとえば、開始ハンドシェイク中に、クライアントとサーバーは次のヘッダーを使用してこの拡張機能を有効にすることができます。データメッセージの最初のフレームのフィールドは、ペイロードデータが圧縮されていることを示すように設定する必要があります。[ 71 ]RSV1
Sec-WebSocket-Extensions: permessage-deflatePythonで。
注:recv()要求されたバイト数まで返されます。可読性を高めるため、コードではその値を無視しているため、ネットワーク環境が理想的でない場合は失敗する可能性があります。
import base64 import hashlib import struct from typing import Optional from socket import socket as Socketdef handle_websocket_connection ( ws : Socket ) -> None : # 接続を受け入れるconn , addr = ws.accept ( )# HTTPリクエストキーを受信して解析します: Optional [ bytes ] = None for line in conn . recv ( 4096 ) . split ( b " \r\n " ): if line . startswith ( b "Sec-WebSocket-Key" ): key = line . split ()[ - 1 ]keyがNone の場合: ValueError ( "Sec-WebSocket-Key が見つかりません" )を発生させる# HTTPレスポンスを送信sec_accept = base64.b64encode ( hashlib.sha1 ( key + b " 258EAFA5- E914-47DA -95CA-C5AB0DC85B11" ) . digest ()) conn.sendall ( b " \r\n ".join ( [ b " HTTP / 1.1 101 Switching Protocols" , b " Connection: Upgrade" , b "Upgrade: websocket" , b "Sec-WebSocket-Accept: " + sec_accept , b "" , b "" , ]) )# フレームをデコードして出力するwhile True : byte0 , byte1 = conn . recv ( 2 ) fin : int = byte0 >> 7 opcode : int = byte0 & 0b1111 masked : int = byte1 >> 7 assert masked , "クライアントはすべてのフレームをマスクする必要があります" if opcode >= 8 : assert fin , "制御フレームは断片化できません"# ペイロードサイズpayload_size : int = byte1 & 0b111_1111 if payload_size == 126 : payload_size , = struct . unpack ( ">H" , conn . recv ( 2 )) assert payload_size > 125 , "最小ビット数を使用する必要があります" elif payload_size == 127 : payload_size , = struct . unpack ( ">Q" , conn . recv ( 8 )) assert payload_size > 2 ** 16 - 1 , "最小ビット数を使用する必要があります" assert payload_size <= 2 ** 63 - 1 , "最上位ビットはゼロである必要があります" if opcode >= 8 : assert payload_size <= 125 , "制御フレームは最大 125 バイトである必要があります"# マスク解除masking_key : bytes = conn . recv ( 4 ) payload : bytearray = bytearray ( conn . recv ( payload_size )) for i in range ( payload_size ): payload [ i ] = payload [ i ] ^ masking_key [ i % 4 ]print ( "受信フレーム" , FIN , opcode , payload )if __name__ == "__main__" : # ポート 80 の任意のインターフェースで TCP 接続を受け入れるws : Socket = Socket () ws . bind (( "" , 80 )) ws . listen ()handle_websocket_connection ( ws )WebSocketプロトコルのセキュアバージョンは、Firefox 6 [ 72 ]、Safari 6、Google Chrome 14 [ 73 ] 、 Opera 12.10、Internet Explorer 10 [ 74 ]に実装されています。 詳細なプロトコルテストスイートレポート[ 75 ]には、これらのブラウザが特定のプロトコル側面に準拠していることが記載されています。
Opera 11 およびSafari 5、ならびにiOS 4.2の Safari のモバイル版では、より古く、セキュリティの低いバージョンのプロトコルが実装されていました。[ 76 ] OS7 の BlackBerry Browser は WebSocket を実装しています。[ 77 ]脆弱性のため、Firefox 4 および 5、[ 78 ]および Opera 11では無効化されました。 [ 79 ] ブラウザ開発者ツールを使用すると、開発者は WebSocket ハンドシェイクと WebSocket フレームを検査できます。[ 80 ]
ASP.NET Core はapp.UseWebSockets();ミドルウェアを使用して WebSocket をサポートしています。[ 96 ]
通常のクロスドメイン HTTP リクエストとは異なり、WebSocket リクエストは同一オリジン ポリシーによって制限されません。そのため、WebSocket サーバーは、接続確立中に「Origin」ヘッダーを期待されるオリジンと照合して検証し、接続がCookieまたは HTTP 認証で認証されている場合に発生する可能性のあるクロスサイト WebSocket ハイジャック攻撃 (クロスサイト リクエスト フォージェリに類似) を回避する必要があります。WebSocket を介して機密 (プライベート) データが転送される場合は、トークンまたは同様の保護メカニズムを使用して WebSocket 接続を認証する方が望ましいです。[ 97 ]脆弱性の実際の例は、2020 年にCable Hauntという形で確認されました。
WebSocketプロトコルのクライアント実装は、宛先ホストとポートに接続する際に、ユーザーエージェントがプロキシを使用するように設定されているかどうかを検出し、設定されている場合はHTTP CONNECTメソッドを使用して永続的なトンネルを確立します。
WebSocketプロトコル自体はプロキシサーバーやファイアウォールを認識しませんが、HTTP互換のハンドシェイクを備えているため、HTTPサーバーはデフォルトのHTTPポートとHTTPSポート(それぞれ80番と443番)をWebSocketゲートウェイまたはサーバーと共有できます。WebSocketプロトコルは、WebSocket接続とWebSocket Secure接続を示すために、それぞれws://とwss://というプレフィックスを定義します。どちらの方式もHTTPアップグレードメカニズムを使用してWebSocketプロトコルにアップグレードします。一部のプロキシサーバーは透過的でWebSocketと問題なく動作しますが、他のプロキシサーバーはWebSocketが正しく動作しないようにし、接続を失敗させます。場合によっては、追加のプロキシサーバー設定が必要になる場合があり、特定のプロキシサーバーをWebSocketをサポートするようにアップグレードする必要がある場合もあります。
暗号化されていない WebSocket トラフィックが、WebSocket をサポートしていない明示的または透過的なプロキシ サーバーを通過すると、接続が失敗する可能性が高くなります。[ 98 ]
暗号化された WebSocket 接続を使用する場合、WebSocket Secure 接続でトランスポート層セキュリティHTTP CONNECT(TLS) を使用することで、ブラウザが明示的なプロキシ サーバーを使用するように設定されている場合にコマンドが発行されることが保証されます。これにより、WebSocket Secure クライアントと WebSocket サーバー間で、HTTP プロキシを介して低レベルのエンドツーエンド TCP 通信を提供するトンネルが確立されます。透過型プロキシ サーバーの場合、ブラウザはプロキシ サーバーを認識しないため、コマンドはHTTP CONNECT送信されません。ただし、通信トラフィックは暗号化されているため、中間の透過型プロキシ サーバーは暗号化されたトラフィックをそのまま通過させる可能性があります。そのため、WebSocket Secure を使用すると、WebSocket 接続が成功する可能性がはるかに高くなります。暗号化の使用にはリソース コストがかかりますが、安全なトンネルを通過するため、多くの場合、最も高い成功率が得られます。
2010 年半ばのドラフト (バージョン hixie-76)では、ヘッダーの後に 8 バイトのキー データが含まれていましたが、そのデータをヘッダーで通知していなかったため、リバース プロキシContent-Length: 8やゲートウェイとの互換性が損なわれていました。[ 99 ]このデータはすべての中間者によって転送されなかったため、プロトコルの障害につながる可能性がありました。より新しいドラフト (例: hybi-09 [ 100 ] ) では、キー データをSec-WebSocket-Keyヘッダーに含めて、この問題を解決しました。
接続には「クライアント」と「サーバー」が必要です。Flash Playerはクライアントソケットを作成できます。
計算[...]は、キャッシュ仲介者がWSサーバーとの実際のやり取りなしにキャッシュされたWSサーバー応答をWSクライアントに提供することを防ぐことを目的としています。