From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- files/ja/web/api/websockets_api/index.html | 113 +++++++++ .../websockets_api/websockets_reference/index.html | 22 ++ .../writing_a_websocket_server_in_java/index.html | 221 +++++++++++++++++ .../index.html | 183 ++++++++++++++ .../writing_websocket_server/index.html | 273 +++++++++++++++++++++ .../writing_websocket_servers/index.html | 242 ++++++++++++++++++ 6 files changed, 1054 insertions(+) create mode 100644 files/ja/web/api/websockets_api/index.html create mode 100644 files/ja/web/api/websockets_api/websockets_reference/index.html create mode 100644 files/ja/web/api/websockets_api/writing_a_websocket_server_in_java/index.html create mode 100644 files/ja/web/api/websockets_api/writing_websocket_client_applications/index.html create mode 100644 files/ja/web/api/websockets_api/writing_websocket_server/index.html create mode 100644 files/ja/web/api/websockets_api/writing_websocket_servers/index.html (limited to 'files/ja/web/api/websockets_api') diff --git a/files/ja/web/api/websockets_api/index.html b/files/ja/web/api/websockets_api/index.html new file mode 100644 index 0000000000..9e118ad574 --- /dev/null +++ b/files/ja/web/api/websockets_api/index.html @@ -0,0 +1,113 @@ +--- +title: WebSocket API (WebSockets) +slug: Web/API/WebSockets_API +tags: + - API + - Client + - Communication + - Overview + - Server + - Two-Way + - WebSocket + - WebSocket API + - WebSockets + - data + - interactive +translation_of: Web/API/WebSockets_API +--- +

{{DefaultAPISidebar("Websockets API")}}

+ +

WebSocket API は、ユーザーのブラウザーとサーバー間で対話的な通信セッションを開くことができる先進技術です。この API によって、サーバーにメッセージを送信したり、応答をサーバーにポーリングすることなく、イベント駆動型のレスポンスを受信したりすることができます。

+ +
+

メモ: WebSocket のコネクションは機能的にどこか標準 Unix スタイルのソケットに似ていますが、関連はありません。

+
+ +

インターフェイス

+ +
+
WebSocket
+
WebSocket サーバに接続し、その接続を通じてデータを送受信するための主要インターフェイス
+
CloseEvent
+
接続が閉じた時に WebSocket オブジェクトによって送信されるイベントです。
+
MessageEvent
+
サーバーからメッセージを受信した時に WebSocket オブジェクトによって送信されるイベント
+
+ +

ガイド

+ + + +

ツール

+ + + + + + + +

仕様書

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
仕様書状態備考
{{SpecName("HTML WHATWG", "web-sockets.html", "WebSocket API")}}{{Spec2("HTML WHATWG")}}
WebSocketsCandidate Recommendation
{{RFC(6455, "The WebSocket Protocol")}}IETF RFC
+ +

ブラウザーの互換性

+ + + +

{{Compat("api.WebSocket")}}

+ +

関連情報

+ + diff --git a/files/ja/web/api/websockets_api/websockets_reference/index.html b/files/ja/web/api/websockets_api/websockets_reference/index.html new file mode 100644 index 0000000000..85b5acc880 --- /dev/null +++ b/files/ja/web/api/websockets_api/websockets_reference/index.html @@ -0,0 +1,22 @@ +--- +title: WebSockets リファレンス +slug: Web/API/WebSockets_API/WebSockets_reference +tags: + - WebSocket + - WebSockets +translation_of: Web/API/WebSockets_API +--- +
{{draft}}
+

以下のページは、WebSocket API のインタフェースに関する文書です。

+ + +
+
WebSocket
+
WebSocket のサーバに接続し、その接続上でデータを送受信するためのプライマリインターフェイス
+ +
CloseEvent
+
接続を閉じる際に WebSocket オブジェクトによって送信されるイベント
+ +
MessageEvent
+
サーバからのメッセージの受信時に Websocket オブジェクトによって送出されるイベント
+
diff --git a/files/ja/web/api/websockets_api/writing_a_websocket_server_in_java/index.html b/files/ja/web/api/websockets_api/writing_a_websocket_server_in_java/index.html new file mode 100644 index 0000000000..a1d1fc553a --- /dev/null +++ b/files/ja/web/api/websockets_api/writing_a_websocket_server_in_java/index.html @@ -0,0 +1,221 @@ +--- +title: Java で WebSocket サーバを記述する +slug: Web/API/WebSockets_API/Writing_a_WebSocket_server_in_Java +tags: + - HTML5 + - NeedsMarkupWork + - WebSockets + - チュートリアル + - ハンドシェイキング +translation_of: Web/API/WebSockets_API/Writing_a_WebSocket_server_in_Java +--- +

イントロダクション

+ +

この例では、Oracle Java を使用して WebSocket API サーバーを作成する方法を示します。
+
+ 他のサーバーサイドの言語を使用して WebSocket サーバーを作成することもできますが、この例では Oracle Java を使用してサンプルコードを簡略化しています。

+ +

このサーバーは RFC 6455 に準拠しているため、Chrome バージョン 16、Firefox 11、IE 10 以降の接続のみを処理します。

+ +

ファーストステップ

+ +

WebSocket は TCP (伝送制御プロトコル) 接続を介して通信します。 Java の ServerSocket クラスは java.net パッケージにあります。

+ +

ServerSocket

+ +

コンストラクタ:

+ +
ServerSocket(int port)
+ +

ServerSocket クラスをインスタンス化すると、port 引数で指定したポート番号にバインドされます。

+ +

私たちが学んだことを実装する方法は次のとおりです。

+ +
import java.net.ServerSocket;
+import java.net.Socket;
+
+public class Server{
+    public static void main(String[] args){
+        ServerSocket server = new ServerSocket(80);
+
+        System.out.println("Server has started on 127.0.0.1:80.\r\nWaiting for a connection...");
+
+        Socket client = server.accept();
+
+        System.out.println("A client connected.");
+    }
+}
+ +

Socket

+ +

メソッド:

+ + + +

OutputStream

+ +

メソッド:

+ +
write(byte[] b, int off, int len)
+ +

指定された byte 配列からオフセット off で始まる len バイトをこの出力ストリームに書き込みます。

+ +

InputStream

+ +

メソッド:

+ +
read(byte[] b, int off, int len)
+ +

最大 len バイトのデータを入力ストリームからバイト配列に読み込みます。

+ +

私たちの例を拡張してみましょう。

+ +
Socket client = server.accept();
+
+System.out.println("A client connected.");
+
+InputStream in = client.getInputStream();
+
+OutputStream out = client.getOutputStream();
+
+new Scanner(in, "UTF-8").useDelimiter("\\r\\n\\r\\n").next();
+ +

ハンドシェイキング

+ +

クライアントがサーバーに接続すると、単純な HTTP リクエストから WebSocket への接続をアップグレードするための GET リクエストが送信されます。 これはハンドシェイキングと呼ばれます。

+ +
import java.util.Scanner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+//translate bytes of request to string
+String data = new Scanner(in,"UTF-8").useDelimiter("\\r\\n\\r\\n").next();
+
+Matcher get = Pattern.compile("^GET").matcher(data);
+
+if (get.find()) {
+
+} else {
+
+}
+ +

このようにしなければならない理由を理解するよりも、レスポンスを作成する方が簡単です。

+ +

次のことをやらなければなりません

+ +
    +
  1. 先頭と末尾の空白なしで Sec-WebSocket-Key リクエストヘッダーの値を取得します
  2. +
  3. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" とリンクします
  4. +
  5. SHA-1 と Base64 のコードを計算します
  6. +
  7. Sec-WebSocket-Accept レスポンスヘッダーの値を HTTP レスポンスの一部として書き戻します。
  8. +
+ +
if (get.find()) {
+    Matcher match = Pattern.compile("Sec-WebSocket-Key: (.*)").matcher(data);
+    match.find();
+    byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
+            + "Connection: Upgrade\r\n"
+            + "Upgrade: websocket\r\n"
+            + "Sec-WebSocket-Accept: "
+            + DatatypeConverter
+            .printBase64Binary(
+                    MessageDigest
+                    .getInstance("SHA-1")
+                    .digest((match.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
+                            .getBytes("UTF-8")))
+            + "\r\n\r\n")
+            .getBytes("UTF-8");
+
+    out.write(response, 0, response.length);
+}
+
+ +

メッセージのデコード

+ +

ハンドシェイクが成功した後、クライアントはサーバーにメッセージを送信できますが、これはエンコードされています。

+ +

"abcdef" を送信すると、次のバイトが得られます。

+ + + + + + + + + + + + + + + + + + +
129134167225225210198131130182194135
+ +

- 129:

+ + + + + + + + + + + + + + + + + + + + +
FIN (これは全体のメッセージですか?)RSV1RSV2RSV3Opcode
10000x1=0001
+ +

FIN: あなたはメッセージをフレームで送ることができますが、今は物事を単純に保ちます。
+ Opcode 0x1 は、これがテキストであることを意味します。オペコードの完全なリストです

+ +

- 134:

+ +

2番目のバイトから 128 を引いた値が 0〜125 の場合、これはメッセージの長さです。126 の場合は、次の 2 バイト (16 ビット符号なし整数)、127 の場合、次の 8 バイト (64 ビット符号なし整数、最上位ビットは 0 でなければならない) が長さです。

+ +
+

最初のビットは常に1なので、128を取ることができます。

+
+ +

- 167、225、225、および 210 はデコードするキーのバイトです。それは毎回変わります。

+ +

- 残りの符号化されたバイトはメッセージです。

+ +

アルゴリズムのデコード

+ +

デコードされたバイト = エンコードされたバイト XOR (エンコードされたバイトの位置 BITWISE および 0x3) バイト目のキー

+ +

Java の例です

+ +
byte[] decoded = new byte[6];
+byte[] encoded = new byte[] {198, 131, 130, 182, 194, 135};
+byte[] key = byte[4] {167, 225, 225, 210};
+
+for (int i = 0; i < encoded.length; i++) {
+    decoded[i] = (byte)(encoded[i] ^ key[i & 0x3]);
+}
+ +

関連

+ + + +
 
diff --git a/files/ja/web/api/websockets_api/writing_websocket_client_applications/index.html b/files/ja/web/api/websockets_api/writing_websocket_client_applications/index.html new file mode 100644 index 0000000000..b9111abfe8 --- /dev/null +++ b/files/ja/web/api/websockets_api/writing_websocket_client_applications/index.html @@ -0,0 +1,183 @@ +--- +title: WebSocket クライアントアプリケーションの記述 +slug: Web/API/WebSockets_API/Writing_WebSocket_client_applications +tags: + - Client + - Example + - Guide + - Networking + - Web Sockets API + - WebSocket + - WebSocket API + - WebSockets + - ガイド + - ネットワーク + - 例 +translation_of: Web/API/WebSockets_API/Writing_WebSocket_client_applications +--- +
{{APIRef("Websockets API")}}
+ +

WebSocket クライアントアプリケーションは WebSocket API を使用して、 WebSocket プロトコルを経由して WebSocket サーバーと通信します。

+ +

{{AvailableInWorkers}}

+ +
+

メモ: この記事のサンプルスニペットは WebSocket チャットクライアント/サーバーサンプルから取得したものです。 コードを見てから、自分で例を試してみてください

+
+ +

WebSocket オブジェクトの作成

+ +

WebSocket プロトコルを使用して通信するには、 {{domxref("WebSocket")}} オブジェクトを作成する必要があります。これにより自動的にサーバーへの接続が開かれます。

+ +

WebSocket のコンストラクターは、必須1つ、任意1つの引数を受け取ります。

+ +
webSocket = new WebSocket(url, protocols);
+
+ +
+
url
+
接続先 URL。これは、 WebSocket サーバーが応答する URL である必要があります。これは URL スキームに wss:// を使用するべきですが、ソフトウェアによってはローカル接続用に安全ではない ws:// を使用することを許可していることがあります。
+
protocols {{ optional_inline() }}
+
単一のプロトコル文字列または一連のプロトコル文字列。これらの文字列はサブプロトコルを示すのに使用されるため、単一のサーバーで複数の WebSocket サブプロトコルを実装できます (たとえば、特定の protocol に応じて1つのサーバーで異なるタイプのインタラクションを処理できるようにする)。プロトコル文字列を指定しない場合、空文字列であると仮定されます。
+
+ +

コンストラクターは、接続先がアクセスを許可していない場合に SecurityError 例外を発生させます。これは安全ではない接続を利用しようとしたときに発生することがあります (ほとんどの{{Glossary("user agent", "ユーザーエージェント")}}は、同じ機器か、可能であれば同じネットワークでない限り、すべての WebSocket 接続に安全なリンクを要求します。

+ +

接続エラー

+ +

接続中にエラーが発生した場合、最初に error という名前の単純なイベントが {{domxref("WebSocket")}} オブジェクトに送信され (その結果、その {{domxref("WebSocket.onerror", "onerror")}} ハンドラーが呼び出されます)、次に {{domxref("CloseEvent")}} が WebSocket オブジェクトに送信され ({{domxref("WebSocket.onclose", "onclose")}} ハンドラーが呼び出されます) 接続の終了の理由を示します。

+ +

ブラウザーはコンソールにも、 RFC 6455 の 7.4 節 で定義されている {{domxref("CloseEvent")}} 経由の終了コードと同時に、もっと説明的なエラーメッセージを出力することもあります。

+ +

+ +

この簡単な例では新しい WebSocket を作成し、 wss://www.example.com/socketserver のサーバーに接続します。この例では、ソケットの要求で "protocolOne" のカスタムプロトコルが指定されていますが、省略することもできます。

+ +
var exampleSocket = new WebSocket("wss://www.example.com/socketserver", "protocolOne");
+
+ +

返されると、 {{domxref("WebSocket.readyState", "exampleSocket.readyState")}} は CONNECTING です。 readyState は接続がデータを転送する準備ができたら OPENになります。

+ +

接続を開き、サポートしているプロトコルについて柔軟に対応したい場合は、プロトコルの配列を指定できます。

+ +
var exampleSocket = new WebSocket("wss://www.example.com/socketserver", ["protocolOne", "protocolTwo"]);
+
+ +

接続が確立されると (つまり readyStateOPEN)、 {{domxref("WebSocket.protocol", "exampleSocket.protocol")}} は、サーバーが選択したプロトコルを通知します。

+ +

WebSocket を確立するには、HTTP アップグレードメカニズムが必要です。したがって、 HTTP サーバーを ws://www.example.com または wss://www.example.com としてアドレス指定すると、プロトコルのアップグレード要求が暗黙的に行われます。

+ +

サーバーにデータを送信する

+ +

接続を開くと、サーバーにデータを送信することができます。 これを行うには、送信するメッセージごとに WebSocket オブジェクトの {{domxref("WebSocket.send", "send()")}} メソッドを呼び出します。

+ +
exampleSocket.send("Here's some text that the server is urgently awaiting!");
+
+ +

データを文字列、 {{ domxref("Blob") }}、 {{jsxref("ArrayBuffer")}} のいずれかとして送信することができます。

+ +

接続の確立は非同期であり、失敗しやすいため、 send() メソッドの呼び出しが WebSocket オブジェクトの作成直後に成功するという保証はありません。データの送信を試みるのは、少なくともいったん接続が確立してからでなければならないので、作業を行うための {{domxref("WebSocket.onopen", "onopen")}} イベントハンドラーを定義してその中で行います。

+ +
exampleSocket.onopen = function (event) {
+  exampleSocket.send("Here's some text that the server is urgently awaiting!");
+};
+
+ +

JSON を使用したオブジェクトの送信

+ +

サーバーに複雑なデータを合理的に送信するのに手軽な方法の一つとして、 {{glossary("JSON")}} を使用する方法があります。たとえば、チャットプログラムがサーバーとやり取りするのに、 JSON でカプセル化されたデータのパケットを使用して実装されたプロトコルを使用することができます。

+ +
// Send text to all users through the server
+function sendText() {
+  // Construct a msg object containing the data the server needs to process the message from the chat client.
+  var msg = {
+    type: "message",
+    text: document.getElementById("text").value,
+    id:   clientID,
+    date: Date.now()
+  };
+
+  // Send the msg object as a JSON-formatted string.
+  exampleSocket.send(JSON.stringify(msg));
+
+  // Blank the text input element, ready to receive the next line of text from the user.
+  document.getElementById("text").value = "";
+}
+
+ +

サーバーからのメッセージの受信

+ +

WebSockets はイベント駆動型 API です。メッセージを受信すると、 message イベント WebSocket オブジェクトに送信されます。これを処理するには、 message イベントのイベントリスナーを追加するか、 {{domxref("WebSocket.onmessage", "onmessage")}} イベントハンドラーを使用するかします。受信データの待ち受けを開始するには、次のようにします。

+ +
exampleSocket.onmessage = function (event) {
+  console.log(event.data);
+}
+
+ +

JSON オブジェクトの受信と解釈

+ +

まず{{ anch("Using JSON to transmit objects", "JSON を使用したオブジェクトの送信") }}で述べられているチャットクライアントアプリケーションを考えてみましょう。クライアントが受信するデータパケットの種類は次のとおりです。

+ + + +

これらの受信メッセージを解釈するコードは、次のようになります。

+ +
exampleSocket.onmessage = function(event) {
+  var f = document.getElementById("chatbox").contentDocument;
+  var text = "";
+  var msg = JSON.parse(event.data);
+  var time = new Date(msg.date);
+  var timeStr = time.toLocaleTimeString();
+
+  switch(msg.type) {
+    case "id":
+      clientID = msg.id;
+      setUsername();
+      break;
+    case "username":
+      text = "<b>User <em>" + msg.name + "</em> signed in at " + timeStr + "</b><br>";
+      break;
+    case "message":
+      text = "(" + timeStr + ") <b>" + msg.name + "</b>: " + msg.text + "<br>";
+      break;
+    case "rejectusername":
+      text = "<b>Your username has been set to <em>" + msg.name + "</em> because the name you chose is in use.</b><br>"
+      break;
+    case "userlist":
+      var ul = "";
+      for (i=0; i < msg.users.length; i++) {
+        ul += msg.users[i] + "<br>";
+      }
+      document.getElementById("userlistbox").innerHTML = ul;
+      break;
+  }
+
+  if (text.length) {
+    f.write(text);
+    document.getElementById("chatbox").contentWindow.scrollByPages(1);
+  }
+};
+
+ +

ここで {{jsxref("JSON.parse()")}} を使用して JSON オブジェクトを元のオブジェクトに変換し、その内容を調べて処理します。

+ +

テキストデータ形式

+ +

WebSocket 接続を介して受信されるテキストは、 UTF-8 形式です。

+ +

接続を閉じる

+ +

WebSocket 接続の使用を終了したら、 WebSocket のメソッド {{domxref("WebSocket.close", "close()")}} を呼び出します。

+ +
exampleSocket.close();
+
+ +

接続を閉じようとする前に、ソケットの {{domxref("WebSocket.bufferedAmount", "bufferedAmount")}} 属性を確認して、データがネットワーク上でまだ送信されていないかどうかを判断すると有用かもしれません。この値が 0 ではない場合、まだ待ち状態のデータがあるので、接続を閉じる前に待ったほうが良いかもしれません。

+ +

セキュリティの考慮事項

+ +

混合コンテンツ環境では WebSocket を使用しないでください。つまり、 HTTPS を使用もしくはそうでない方法でもロードされたページから、セキュアでない WebSocket 接続を開くべきではありません。最近のブラウザーは安全な WebSocket 接続のみを許可し、また安全ではないコンテキストでの使用には対応しなくなってきています。

diff --git a/files/ja/web/api/websockets_api/writing_websocket_server/index.html b/files/ja/web/api/websockets_api/writing_websocket_server/index.html new file mode 100644 index 0000000000..4f0df00628 --- /dev/null +++ b/files/ja/web/api/websockets_api/writing_websocket_server/index.html @@ -0,0 +1,273 @@ +--- +title: C# で WebSocket サーバーを記述する +slug: Web/API/WebSockets_API/Writing_WebSocket_server +tags: + - HTML5 + - NeedsMarkupWork + - WebSockets + - チュートリアル +translation_of: Web/API/WebSockets_API/Writing_WebSocket_server +--- +

イントロダクション

+ +

WebSocket API を使用したい場合は、サーバーを持っている場合に便利です。この記事では、C# で記述する方法を説明します。どんなサーバーサイドの言語でも行うことができますが、わかりやすく理解しやすいように、Microsoft の言語を選択しました。

+ +

このサーバーは RFC 6455 に準拠しているため、Chrome バージョン16、Firefox 11、IE 10 以上の接続のみを処理します。

+ +

ファーストステップ

+ +

WebSocket は TCP (伝送制御プロトコル) 接続を介して通信します。幸いにも、C# には TcpListener クラスがあり、その名前が示すようにします。これは System.Net.Sockets 名前空間にあります。

+ +
+

少なく書くためには名前空間を using キーワードに含めることをお勧めします。毎回完全な名前空間を入力することなく、名前空間のクラスを使用できます。

+
+ +

TcpListener

+ +

コンストラクタ:

+ +
TcpListener(System.Net.IPAddress localaddr, int port)
+ +

localaddr はリスナーの IP を指定し、port はポートを指定します。

+ +
+

string からIPAddress オブジェクトを作成するには、IPAddress の静的 Parse メソッドを使用します。

+
+ +

メソッド:

+ + + +

ベアボーンサーバーの実装は次のとおりです。

+ +
​using System.Net.Sockets;
+using System.Net;
+using System;
+
+class Server {
+    public static void Main() {
+        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);
+
+        server.Start();
+        Console.WriteLine("Server has started on 127.0.0.1:80.{0}Waiting for a connection...", Environment.NewLine);
+
+        TcpClient client = server.AcceptTcpClient();
+
+        Console.WriteLine("A client connected.");
+    }
+}
+
+ +

TcpClient

+ +

メソッド:

+ + + +

プロパティ:

+ + + +

NetworkStream

+ +

メソッド:

+ + + +

私たちの例を拡張してみましょう。

+ +
TcpClient client = server.AcceptTcpClient();
+
+Console.WriteLine("A client connected.");
+
+NetworkStream stream = client.GetStream();
+
+//enter to an infinite cycle to be able to handle every change in stream
+while (true) {
+    while (!stream.DataAvailable);
+
+    Byte[] bytes = new Byte[client.Available];
+
+    stream.Read(bytes, 0, bytes.Length);
+}
+ +

ハンドシェイキング

+ +

クライアントがサーバーに接続すると、単純な HTTP リクエストから WebSocket への接続をアップグレードするための GET リクエストが送信されます。これはハンドシェイキングと呼ばれます。

+ +

このサンプルコードはクライアントから GET を検出できます。これは、メッセージの最初の 3 バイトが利用可能になるまでブロックされることに注意してください。運用環境では、代替ソリューションを検討する必要があります。

+ +
using System.Text;
+using System.Text.RegularExpressions;
+
+while(client.Available < 3)
+{
+   // wait for enough bytes to be available
+}
+
+Byte[] bytes = new Byte[client.Available];
+
+stream.Read(bytes, 0, bytes.Length);
+
+//translate bytes of request to string
+String data = Encoding.UTF8.GetString(bytes);
+
+if (Regex.IsMatch(data, "^GET")) {
+
+} else {
+
+}
+ +

リクエストは簡単に作成できますが、理解するのは少し難しいかもしれません。サーバーのハンドシェイクの完全な説明は RFC 6455、セクション4.2.2 にあります。私たちの目的のために、簡単なレスポンスを作成します。

+ +

やらなければならないのは:

+ +
    +
  1. 先行または後続空白なしで "Sec-WebSocket-Key" リクエストヘッダーの値を取得します
  2. +
  3. それを "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455 で指定された特別な GUID)
  4. +
  5. 新しい値の SHA-1 および Base64 ハッシュを計算します
  6. +
  7. HTTP レスポンスの "Sec-WebSocket-Accept" レスポンスヘッダの値としてハッシュを書き戻します
  8. +
+ +

+if (new System.Text.RegularExpressions.Regex("^GET").IsMatch(data))
+{
+    const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker
+
+    Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol
+        + "Connection: Upgrade" + eol
+        + "Upgrade: websocket" + eol
+        + "Sec-WebSocket-Accept: " + Convert.ToBase64String(
+            System.Security.Cryptography.SHA1.Create().ComputeHash(
+                Encoding.UTF8.GetBytes(
+                    new System.Text.RegularExpressions.Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+                )
+            )
+        ) + eol
+        + eol);
+
+    stream.Write(response, 0, response.Length);
+}
+
+ +

メッセージのデコード

+ +

ハンドシェイクが成功すると、クライアントはエンコードされたメッセージをサーバーに送信します。

+ +

"MDN" を送信すると、次のバイトが得られます。

+ + + + + + + + + + + + + + + +
129131618435611216109
+ +

これらのバイトの意味を見てみましょう。

+ +

現在 129 の値を持つ最初のバイトは、次のように分解するビットフィールドです。

+ + + + + + + + + + + + + + + + + + + + +
FIN (Bit 0)RSV1 (Bit 1)RSV2 (Bit 2)RSV3 (Bit 3)Opcode (Bit 4:7)
10000x1=0001
+ + + +

現在 131 の値を持つ 2 番目のバイトは、次のように分解する別のビットフィールドです。

+ + + + + + + + + + + + + + +
MASK (Bit 0)ペイロードの長さ (Bit 1:7)
10x83=0000011
+ + + +
+

最初のビットはクライアントからサーバーへのメッセージでは常に 1 なので、このバイトから 128 を引いて MASK ビットを取り除くことができます。

+
+ +

メッセージに MASK ビットが設定されていることに注意してください。これは次の4バイト (61、84、35、および6) がメッセージのデコードに使用されるマスクバイトであることを意味します。これらのバイトはすべてのメッセージとともに変化します。

+ +

残りのバイトはエンコードされたメッセージペイロードです。

+ +

アルゴリズムのデコード

+ +

Di = Ei XOR M(i mod 4)

+ +

D は復号されたメッセージ配列、E は符号化されたメッセージ配列、M はマスクバイト配列、i はデコードするメッセージバイトのインデックスです。

+ +

C# の例です:

+ +
Byte[] decoded = new Byte[3];
+Byte[] encoded = new Byte[3] {112, 16, 109};
+Byte[] mask = new Byte[4] {61, 84, 35, 6};
+
+for (int i = 0; i < encoded.Length; i++) {
+    decoded[i] = (Byte)(encoded[i] ^ mask[i % 4]);
+}
+ +

関連

+ + + +
 
diff --git a/files/ja/web/api/websockets_api/writing_websocket_servers/index.html b/files/ja/web/api/websockets_api/writing_websocket_servers/index.html new file mode 100644 index 0000000000..2dbf4fe7da --- /dev/null +++ b/files/ja/web/api/websockets_api/writing_websocket_servers/index.html @@ -0,0 +1,242 @@ +--- +title: WebSocket サーバーの記述 +slug: Web/API/WebSockets_API/Writing_WebSocket_servers +tags: + - HTML5 + - NeedsContent + - NeedsExample + - WebSocket + - WebSockets + - ガイド + - チュートリアル +translation_of: Web/API/WebSockets_API/Writing_WebSocket_servers +--- +

WebSocket サーバは、特定のプロトコルに従うサーバの任意のポートを待機する TCP アプリケーションです。カスタムサーバーを作成する作業は人々を悩ませる傾向があります。ただし、選択したプラットフォームに簡単な WebSocket サーバーを実装するのは簡単です。

+ +

WebSocket サーバは、C(++) や Python、PHP やサーバサイド JavaScript などの Berkeley sockets が利用可能なサーバサイドプログラミング言語で記述できます。これは特定の言語のチュートリアルではありませんが、独自のサーバーの作成を容易にするガイドとして役立ちます。

+ +

あなたはまだ HTTP がどのように動くのかを知り、中級プログラミング経験を得ている必要があるでしょう。言語サポートによっては、TCP ソケットに関する知識が必要な場合があります。このガイドの範囲は、WebSocket サーバーを作成するために必要な最小限の知識を提示することです。

+ +
+

最新の公式 WebSockets 仕様である RFC 6455 を参照してください。セクション 1 と 4-7 はサーバー実装者にとって特に興味深いものです。第 10 章ではセキュリティについて説明しています。サーバーを公開する前にセキュリティを正しく理解する必要があります。

+
+ +

ここでは WebSocket サーバについて非常に低いレベルで説明しています。WebSocket サーバは多くの場合、リバースプロキシ (通常の HTTP サーバなど) を使用して WebSocket ハンドシェイクを検出、事前処理し、それらのクライアントを実際の WebSocket サーバに送信します。つまり、(例えば) クッキーと認証ハンドラーを使用してサーバ側のコードを膨らませる必要はありません。

+ +

WebSocket ハンドシェイク

+ +

まず、サーバーは標準の TCP ソケットを使用して着信ソケット接続を待ち受ける必要があります。プラットフォームによっては、すでに処理されている可能性があります。たとえば、サーバーが example.com、8000番ポートで待ち受けているとし、ソケットサーバーが /chat で GET リクエストにレスポンスしたとします。

+ +
+

警告: サーバーは選択したポートで待機しますが、80 または 443 以外のポートを選択すると、ファイアウォールやプロキシに問題が発生する可能性があります。443番ポートの接続はより頻繁に成功する傾向がありますが、もちろんその接続には安全な接続 (TLS/SSL) が必要です。また、ほとんどのブラウザ (特に Firefox 8 以降) ではセキュリティで保護されていない WebSocket サーバーへの接続を許可していないことに注意してください。

+
+ +

ハンドシェイクは WebSockets の "Web" です。それは HTTP から WS への橋渡しです。ハンドシェイクでは、接続の詳細がネゴシエートされ、いずれの当事者も条件が悪い場合には完了前に取り消すことができます。 サーバーはクライアントがリクエストするすべてをのものを理解するように注意する必要があります。そうしないとセキュリティの問題が発生します。

+ +
+

Tip: request-uri (ここでは/chat) は仕様に定義された意味を持ちません。多くの人がうまくそれを使用して、あるサーバーが複数の WebSocket アプリケーションを処理できるようにします。たとえば、example.com/chat はマルチユーザチャットアプリを呼び出すことができ、同じサーバの /game はマルチプレイヤーゲームを呼び出すことができます。

+
+ +

クライアントハンドシェイクリクエスト

+ +

サーバーを構築しているにもかかわらず、依然としてクライアントは WebSocket ハンドシェイクプロセスを開始する必要があります。したがってクライアントのリクエストをどのように解釈するかを知っておく必要があります。クライアントは次のようなかなり標準的な HTTP リクエスト (HTTP バージョンは 1.1 以上でなければならず、メソッドはGET でなければなりません) を送信します。

+ +
GET /chat HTTP/1.1
+Host: example.com:8000
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Sec-WebSocket-Version: 13
+ +

クライアントはここで拡張子 および/または サブプロトコルを求めることができます。詳細は「その他」を参照してください。また User-AgentRefererCookie、認証ヘッダーなどの一般的なヘッダーも存在する可能性があります。あなたはそれらで何でもしてください。WebSocket には直接関係しません。それらを無視することも安全です。多くの一般的な設定では、リバースプロキシは既にそれらを処理しています。

+ +
+

Tip: すべてのブラウザOrigin ヘッダーを送信します。 このヘッダをセキュリティ (同じ起点のチェック、ホワイトリスト/ブラックリストなど) に使用し、あなたが見ているものが気に入らなければ 403 Forbidden を送ることができます。ただし、ブラウザ以外のエージェントは、偽の Origin を送信するだけであることに注意してください。ほとんどのアプリケーションは、このヘッダーのない要求を拒否します。

+
+ +

ヘッダーが解釈されていないか値が正しくない場合、サーバーは "400 Bad Request" を送信し、すぐにソケットを閉じる必要があります。通常は、HTTP レスポンス本体でハンドシェークが失敗した理由を示すかもしれませんが、メッセージは表示されないかもしれません (ブラウザはそれを表示しません)。 サーバーが WebSocket のバージョンを認識しない場合、サーバーは解釈可能なバージョンを含む Sec-WebSocket-Version ヘッダーを返す必要があります。(このガイドでは最新のv13について説明しています)。 ここで、最も興味深いヘッダーである Sec-WebSocket-Key に移動しましょう。

+ +
+

Note: 通常の HTTP ステータスコードは、ハンドシェイクの前にのみ使用できます。ハンドシェイクが成功したら、別のコードセット (仕様の 7.4 節で定義されている) を使用する必要があります。

+
+ +

サーバーハンドシェイクレスポンス

+ +

このリクエストを受け取ったら、server はこれ (各ヘッダーは \r\n で終わり、最後の \r\n は最後に付く) と似たかなり奇妙な (ただしまだ HTTP の) レスポンスを送るべきです。

+ +
HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+
+ +

さらに、サーバーはここでの 拡張/サブプロトコル リクエストを決定できます。詳細はその他を参照してください。Sec-WebSocket-Accept の部分は面白いです。サーバーは、クライアントが送信した Sec-WebSocket-Accept から派生しなければなりません。
+ これを取得するには、クライアントの Sec-WebSocket-Key"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" を連結して (これは "マジック文字列" です)、結果の SHA-1 ハッシュを取り、ハッシュのエンコーディングを base64 にして返します。

+ +
+

FYI: このように見た目は複雑すぎるプロセスが存在するため、サーバーが WebSocket をサポートしているかどうかはクライアントには明らかです。これはサーバーが WebSockets 接続を受け入れ、HTTP リクエストとしてデータを解釈する場合にセキュリティ上の問題が発生する可能性があるため重要です。

+
+ +

したがって、Key が "dGhlIHNhbXBsZSBub25jZQ==" だった場合、Accept は "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" になります。サーバーがこれらのヘッダーを送信すると、ハンドシェイクは完了し、データのスワップを開始できます。

+ +
+

サーバーは、Set-Cookie のような他のヘッダーを送信したり、レスポンスハンドシェイクを送信する前に他のステータスコードで認証またはリダイレクトを要求したりすることができます。

+
+ +

クライアントを追跡する

+ +

これは WebSocket プロトコルには直接関係しませんが、ここで言及する価値はあります。サーバーはクライアントのソケットを追跡して、ハンドシェイクをすでに完了しているクライアントとハンドシェイクを再開しないようにする必要があります。同じクライアント IP アドレスが複数回接続しようとする可能性があります (サービス拒否攻撃から自分自身を守るためにサーバーが接続を多すぎると拒否することがあります)。

+ +

データフレームの交換

+ +

クライアントまたはサーバーのいずれかがいつでもメッセージを送信することができます。これが WebSocket の魔法です。しかし、これらのいわゆる「フレーム」のデータから情報を抽出することはあまり魔法のような経験ではありません。すべてのフレームは同じ特定のフォーマットに従いますが、クライアントからサーバーに向かうデータは XOR 暗号化 (32ビットキー) を使用してマスクされます。本明細書の第5節でこれについて詳細に説明する。

+ +

フォーマット

+ +

各データフレーム (クライアントからサーバーへ、またはその逆) は、次の同じ形式に従います。

+ +
Frame format:
+​​
+      0                   1                   2                   3
+      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+     +-+-+-+-+-------+-+-------------+-------------------------------+
+     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
+     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
+     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
+     | |1|2|3|       |K|             |                               |
+     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+     |     Extended payload length continued, if payload len == 127  |
+     + - - - - - - - - - - - - - - - +-------------------------------+
+     |                               |Masking-key, if MASK set to 1  |
+     +-------------------------------+-------------------------------+
+     | Masking-key (continued)       |          Payload Data         |
+     +-------------------------------- - - - - - - - - - - - - - - - +
+     :                     Payload Data continued ...                :
+     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+     |                     Payload Data continued ...                |
+     +---------------------------------------------------------------+
+ +

MASK ビットはメッセージがエンコードされているかどうかを示します。クライアントからのメッセージはマスクされている必要がありますので、サーバはこのビットが 1 であることを確認する必要があります (実際、セクション5.1ではクライアントがマスクされていないメッセージを送信する場合、サーバはクライアントから切断する必要があります)。フレームをクライアントに戻すときは、マスクしたりマスクビットを設定しないでください。後でマスキングについて説明します。注意:セキュアソケットを使用している場合でも、メッセージをマスクする必要があります。RSV1-3 は無視することができますが、それは拡張のためのものです。

+ +

opcode フィールドは、ペイロードデータをどのように解釈するかを定義します。継続の場合 0x0 、テキスト (UTF-8 で常にエンコードされる) の場合は 0x1、バイナリの場合は 0x2、およびその他のいわゆる「制御コード」については後で説明します。この WebSocket バージョンでは、0x3 〜  0x7 および 0xB 0xF は意味を持ちません。

+ +

FIN ビットは、これがシリーズ内の最後のメッセージであるかどうかを示します。0 の場合、サーバーはメッセージのより多くの部分をリスニングし続けます。それ以外の場合、サーバーは配信されたメッセージを考慮する必要があります。これについては後で詳しく説明します。

+ +

ペイロード長のデコード

+ +

ペイロードデータを読み取るには、いつ読み終えるべきかを知っておく必要があります。そのためペイロードの長さを知ることが重要です。残念ながら、これはやや複雑です。それを読むには、次の手順を実行します。

+ +
    +
  1. ビット9〜15 (インクルーシブ) を読み取り、それを符号なし整数として解釈します。それが 125 以下であれば、それが長さです。あなたはそれを読み終えました。126 の場合は手順2に、127 の場合は手順3に進みます。
  2. +
  3. 次の16ビットを読み取り、それらを符号なし整数として解釈します。 それであなたは読み終えました。
  4. +
  5. 次の 64 ビットを読んで、それらを符号なし整数として解釈する(最上位ビットは 0 でなければならない)。それであなたは読み終えました。
  6. +
+ +

データの読み込みとマスク解除

+ +

MASK ビットがセットされていれば (クライアントからサーバへのメッセージではそうあるべきです)、次の4オクテット (32ビット) を読み込みます。これがマスキングキーです。ペイロード長とマスキングキーがデコードされたら、ソケットからそのバイト数を読み取ることができます。データを ENCODED、キーを MASK としましょう。DECODED を取得するには、ENCODED のオクテット (テキストデータの文字のバイト) をループし、オクテットを MASK の (iモジュロ4) オクテットを使用して XOR します。擬似コードの場合(JavaScript が有効な場合)

+ +
var DECODED = "";
+for (var i = 0; i < ENCODED.length; i++) {
+    DECODED[i] = ENCODED[i] ^ MASK[i % 4];
+}
+ +

これで、アプリケーションに応じて DECODED が何を意味するのかを理解することができます。

+ +

メッセージフラグメンテーション

+ +

FIN フィールドとオペコードフィールドは連携して、別々のフレームに分割されたメッセージを送信します。これはメッセージフラグメンテーションと呼ばれます。フラグメンテーションは、オペコード 0x00x2 でのみ使用できます。

+ +

オペコードはフレームの意味を示しています。0x1 の場合、ペイロードはテキストです。0x2 の場合、ペイロードはバイナリデータです。ただし、0x0 の場合、フレームは継続フレームです。つまりサーバーはフレームのペイロードをそのクライアントから受信した最後のフレームに連結する必要があります。ここでは、サーバーがテキストメッセージを送信するクライアントに反応する概略を示します。第1のメッセージは単一のフレームで送信され、第2のメッセージは3つのフレームにわたって送信されます。FIN とオペコードの詳細は、クライアントに対してのみ表示されます。

+ +
Client: FIN=1, opcode=0x1, msg="hello"
+Server: (process complete message immediately) Hi.
+Client: FIN=0, opcode=0x1, msg="and a"
+Server: (listening, new message containing text started)
+Client: FIN=0, opcode=0x0, msg="happy new"
+Server: (listening, payload concatenated to previous message)
+Client: FIN=1, opcode=0x0, msg="year!"
+Server: (process complete message) Happy new year to you too!
+ +

最初のフレームにメッセージ全体が含まれていることに注意してください(FIN=1 および opcode!=0x0)、それによりサーバは適切に処理またはレスポンスできます。クライアントが送信した2番目のフレームにはテキストペイロード (opcode=0x1) がありますが、メッセージ全体がまだ到着していません (FIN=0)。そのメッセージの残りの部分はすべて継続フレーム(opcode=0x0) と共に送信され、メッセージの最終フレームは FIN=1 でマークされます。仕様の 5.4 節では、メッセージフラグメンテーションについて説明があります。

+ +

Ping と Pong: WebSockets のハートビート

+ +

ハンドシェイク後の任意の時点で、クライアントまたはサーバのどちらかが、相手にpingを送信することを選択できます。 pingが受信されると、受信者はできるだけ早くpongを返さなければなりません。 これを使用して、たとえばクライアントがまだ接続されていることを確認できます。

+ +

Ping や Pong は単なる通常のフレームですが、コントロールフレームです。ping のオペコードは 0x9、pong のオペコードは 0xA です。ping を取得したら、ping と同じペイロードデータを持つ pong を送ります (ping と pong の場合、最大ペイロード長は125です)。ping を送信することなく pong を取得することもできます。その場合はこれを無視してください。

+ +
+

あなたが pong を送信する機会を得る前に複数の ping を取得した場合でも、1つの pong しか送信しません。

+
+ +

接続を閉じる

+ +

クライアントまたはサーバの接続を閉じるには指定した制御シーケンスを含むデータの制御フレームを送信して、終了ハンドシェイクを開始します (5.5.1 項を参照)。このようなフレームを受信すると、もう1つの peer はレスポンスとしてクローズフレームを送信します。最初の peer は接続を閉じます。接続の終了後に受信されたそれ以上のデータは、その後破棄されます。

+ +

その他

+ +
+

WebSocket コード、エクステンション、サブプロトコルなどは、IANA WebSocket プロトコルレジストリに登録されています。

+
+ +

WebSocket のエクステンションとサブプロトコルは、ハンドシェイク中にヘッダーを介してネゴシエートされます。エクステンションとサブプロトコルは異なるものというにはあまりにも似ていることがありますが、明確な区別があります。エクステンションは WebSocket フレームを制御し、ペイロードを変更しますが、サブプロトコルは WebSocket ペイロードを構造化しますが、何も変更しません。エクステンションは任意のもので一般化されています (圧縮など)。サブプロトコルは必須のもので、ローカライズされています (チャットや MMORPG ゲームなど)。

+ +

エクステンション

+ +
+

このセクションは拡張が必要です。あなたがそうする準備ができている場合は編集してください。

+
+ +

エクステンションはファイルを誰かに電子メールで送る前に圧縮していると考えてください。あなたが何をしても、同じデータをさまざまな形で送信しています。受信者は最終的にローカルコピーと同じデータを得ることができますが、別の方法で送信されます。それがエクステンションの機能です。WebSockets はプロトコルとデータを送信する簡単な方法を定義しますが、圧縮などのエクステンションでは同じデータを短い形式で送信することができます。

+ +
+

エクステンションについては、仕様の 5.8, 9, 11.3.2, 11.4 節で説明しています。

+
+ +

TODO

+ +

サブプロトコル

+ +

サブプロトコルをカスタム XML スキーマまたは doctype 宣言と考えてください。あなたはまだ XML とその構文を使用していますが、あなたが合意した構造によってさらに制限されます。WebSocket のサブプロトコルはまさにそのようなものです。それらは空想的な何かを導入しておらず、構造を確立するだけです。doctype やスキーマと同様に、両者はサブプロトコルに同意しなければなりません。doctype やスキーマとは異なり、サブプロトコルはサーバー上に実装されており、クライアントから外部参照することはできません。

+ +
+

サブプロトコルは、仕様のセクション 1.9, 4.2, 11.3.4、および 11.5 で説明されています。

+
+ +

クライアントは特定のサブプロトコルを要求する必要があります。 これを行うには、元のハンドシェイクの一部として次のようなものを送ります:

+ +
GET /chat HTTP/1.1
+...
+Sec-WebSocket-Protocol: soap, wamp
+ +

または同様に:

+ +
...
+Sec-WebSocket-Protocol: soap
+Sec-WebSocket-Protocol: wamp
+ +

これでサーバーはクライアントが提案してサポートしているプロトコルの1つを選択する必要があります。複数ある場合は、クライアントが送信した最初のものを送信します。私たちのサーバーが soapwamp の両方を使用できると想像してください。 次に、レスポンスハンドシェイクで次のメッセージが送信されます。

+ +
Sec-WebSocket-Protocol: soap
+ +
+

サーバーは複数の Sec-Websocket-Protocol ヘッダーを送信できません。
+ サーバーがサブプロトコルを使用したくない場合、Sec-WebSocket-Protocol ヘッダーを送信すべきではありません。 空白のヘッダーを送信するのが間違っています。
+ クライアントは、必要なサブプロトコルを取得できない場合に接続を閉じることがあります。

+
+ +

サーバーが特定のサブプロトコルに従うようにしたいのであれば、必然的にサーバー上に特別なコードが必要になります。json サブプロトコルを使用しているとしましょう。このサブプロトコルではすべてのデータが JSON として渡されます。クライアントがこのプロトコルを要求し、サーバーがそれを使用したい場合、サーバーは JSON パーサーを持つ必要があります。実際に言えば、これはライブラリの一部になりますが、サーバーはデータを渡す必要があります。

+ +
+

Tip: 名前の競合を避けるため、サブプロトコル名をドメイン文字列の一部にすることをお勧めします。Example Inc. 専用の独自の形式を使用するカスタムチャットアプリを構築する場合は、次のように使用します: Sec-WebSocket-Protocol: chat.example.com。これは必須ではないことに注意してください。これは単なるオプションです。任意の文字列を使用できます。

+
+ +

関連

+ + -- cgit v1.2.3-54-g00ecf