Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web/API/WebSockets_API/Writing_WebSocket_servers を更新 #25797

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
title: WebSocket サーバーを書く
slug: Web/API/WebSockets_API/Writing_WebSocket_servers
l10n:
sourceCommit: c69e36924e1849fdc9b7fc49a3f4c550efa3468a
sourceCommit: 5b20f5f4265f988f80f513db0e4b35c7e0cd70dc
---

{{DefaultAPISidebar("WebSockets API")}}

WebSocket サーバーは、特定のプロトコルに従うサーバーの任意のポートを待ち受けする TCP アプリケーションに他なりません。カスタムサーバーを作成したことがない人にとっては、カスタムサーバーの作成は大変な作業のように思えるかもしれません。しかし、実際には、選択したプラットフォームに基本的な WebSocket サーバーを実装するのは、それほど難しいことではありません。

WebSocket サーバーは、 [Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) が利用可能なサーバーサイドプログラミング言語、例えば C(++)、Python、{{Glossary("PHP")}}、[サーバーサイド JavaScript](/ja/docs/Learn/Server-side/Node_server_without_framework) などで記述することができます。これは特定の言語のチュートリアルではありませんが、独自のサーバーの作成を容易にするガイドとして役立ちます。
WebSocket サーバーは、 [Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) が利用可能なサーバーサイドプログラミング言語、例えば C(++)、Python、{{Glossary("PHP")}}、[サーバーサイド JavaScript](/ja/docs/Learn_web_development/Extensions/Server-side/Node_server_without_framework) などで記述することができます。これは特定の言語のチュートリアルではありませんが、独自のサーバーの作成を容易にするガイドとして役立ちます。

この記事は、既に {{Glossary("HTTP")}} の仕組みに精通しており、中程度のプログラミング経験があることを前提に書かれています。言語によっては、 TCP ソケットの知識が必要な場合があります。このガイドの範囲は、 WebSocket サーバーを書くために必要な最小限の知識を提示することです。

Expand Down Expand Up @@ -65,12 +65,12 @@ Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
```

さらに、サーバーはここで拡張/サブプロトコルのリクエストを決定することができます。詳しくは[その他](#その他)を参照してください。 `Sec-WebSocket-Accept` ヘッダーは、クライアントが送信した {{HTTPHeader("Sec-WebSocket-Key")}} からサーバーが導き出す必要がある点で重要です。これを得るには、クライアントの `Sec-WebSocket-Key` と "`258EAFA5-E914-47DA-95CA-C5AB0DC85B11`" という文字列(これは「[マジック文字列](https://en.wikipedia.org/wiki/Magic_string)」)を連結して、その結果の [SHA-1 hash](https://en.wikipedia.org/wiki/SHA-1) をとり、そのハッシュの [base64](https://en.wikipedia.org/wiki/Base64) エンコーディング値を返せばいいのです。
さらに、サーバーはここで拡張/サブプロトコルのリクエストを決定することができます。詳しくは[その他](#その他)を参照してください。 `Sec-WebSocket-Accept` ヘッダーは、クライアントが送信した {{HTTPHeader("Sec-WebSocket-Key")}} からサーバーが導き出す必要がある点で重要です。これを得るには、クライアントの `Sec-WebSocket-Key` と `"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"` という文字列(これは「[マジック文字列](https://en.wikipedia.org/wiki/Magic_string)」)を連結して、その結果の [SHA-1 hash](https://en.wikipedia.org/wiki/SHA-1) をとり、そのハッシュの [base64](https://en.wikipedia.org/wiki/Base64) エンコーディング値を返せばいいのです。

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

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

> [!NOTE]
> サーバーは、 {{HTTPHeader("Set-Cookie")}} のような他のヘッダーを送信したり、レスポンスハンドシェイクを送信する前に他のステータスコードで認証またはリダイレクトを要求したりすることができます。
Expand Down Expand Up @@ -126,13 +126,13 @@ Frame format:

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

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

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

### 本体長のデコード

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

1. ビット 9 から 15 までを読み取り、それを符号なし整数として解釈します。それが 125 以下であれば、それが長さです。これで**完了**です。 126 の場合は手順 2 に、 127 の場合は手順 3 に進んでください。
2. 次の 16 ビットを読み取り、それを符号なし整数として解釈します。これで**完了**です。
Expand All @@ -156,7 +156,7 @@ const DECODED = Uint8Array.from(ENCODED, (elt, i) => elt ^ MASK[i % 4]); // マ

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

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

```plain
Client: FIN=1, opcode=0x1, msg="hello"
Expand All @@ -169,13 +169,13 @@ 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 節](https://datatracker.ietf.org/doc/html/rfc6455#section-5.4)では、メッセージフラグメンテーションについて説明があります。
最初のフレームにメッセージ全体が含まれていることに注意してください (`FIN=1` および `opcode!=0x0`)、それによりサーバーは適切に処理または応答できます。クライアントが送信した 2 番目のフレームにはテキスト本体 (`opcode=0x1`) がありますが、メッセージ全体がまだ到着していません (`FIN=0`)。そのメッセージの残りの部分はすべて継続フレーム (`opcode=0x0`) と共に送信され、メッセージの最終フレームは `FIN=1` でマークされます。[仕様書の 5.4 節](https://datatracker.ietf.org/doc/html/rfc6455#section-5.4)では、メッセージフラグメンテーションについて説明があります。

## Ping と Pong: WebSockets の鼓動

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

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

> [!NOTE]
> pong を送信する機会を得る前に複数の ping を受信した場合でも、送信する pong は 1 つだけです。
Expand All @@ -189,7 +189,7 @@ ping や pong は単なる通常のフレームですが、**制御フレーム*
> [!NOTE]
> WebSocket のコード、拡張機能、サブプロトコルなどは、[IANA WebSocket プロトコルレジストリー](https://www.iana.org/assignments/websocket/websocket.xml)に登録されています。

WebSocket の拡張機能とサブプロトコルは、[ハンドシェイク](#websocket_ハンドシェイク)中にヘッダーを介して交渉されます。拡張機能とサブプロトコルはとても似ていますが、明確な区別があります。拡張機能は WebSocket **フレーム**を制御し、ペイロードを**変更**しますが、サブプロトコルは WebSocket **ペイロード**を構造化し、**何も変更しません**。拡張機能は任意のもので一般化されています(圧縮など)。サブプロトコルは必須のもので、ローカライズされています(チャットや MMORPG ゲームなど)。
WebSocket の拡張機能とサブプロトコルは、[ハンドシェイク](#websocket_ハンドシェイク)中にヘッダーを介して交渉されます。拡張機能とサブプロトコルはとても似ていますが、明確な区別があります。拡張機能は WebSocket **フレーム**を制御し、本体を**変更**しますが、サブプロトコルは WebSocket **本体**を構造化し、**何も変更しません**。拡張機能は任意のもので一般化されています(圧縮など)。サブプロトコルは必須のもので、ローカライズされています(チャットや MMORPG ゲームなど)。

## 拡張機能

Expand Down Expand Up @@ -228,7 +228,7 @@ Sec-WebSocket-Protocol: soap
```

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

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