ws の「createWebSocketStream」を用いたストリームの処理を Node.js で試す

## はじめに

Node.js で WebSocketサーバーを作る時などによく使っているのパッケージの「[ws](https://www.npmjs.com/package/ws)」について、情報を見ていて気になった「createWebSocketStream」の話です。


GitHub上で言うと、以下の部分になります。

●websockets/ws: Simple to use, blazing fast and thoroughly tested WebSocket client and server for Node.js
https://github.com/websockets/ws?tab=readme-ov-file#use-the-nodejs-streams-api

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/c47667cf-3635-432b-b6c6-c7771ab9db21.png)

今まで、自分は使ったことがないものだったので、今回軽く試してみました。


## 実際に試してみる

試す内容について、上記のストリームの処理になりますが、その実装でサーバー側もクライアント側も、両方とも自前で用意する形にしてみます。

上記のサンプルでは、サーバー側は websocket-echo.com というものが指定されているところを、自分で用意した WebSocketサーバーを使う形でやってみます。


### 下準備

ここから実際に試していきます。下準備はパッケージのインストールのみです。

```zsh
npm i ws
```

これで準備は整いました。


### コード

ここからコードを準備してみます。


#### クライアント側

まずはクライアント側です。内容はシンプルに、上記のサンプルの接続先をローカルの自前のサーバーにする、ということだけ対応したものとします。

具体的には以下のとおりで、接続先を `ws://localhost:8080` としています。

```javascript
import WebSocket, { createWebSocketStream } from "ws";

// const ws = new WebSocket('wss://websocket-echo.com/');
const ws = new WebSocket("ws://localhost:8080");

const duplex = createWebSocketStream(ws, { encoding: "utf8" });

duplex.on("error", console.error);

duplex.pipe(process.stdout);
process.stdin.pipe(duplex);
```


#### サーバー側

次はサーバー側です。

基本的には、シンプルな WebSocketサーバーに、少しだけ手を加えたような以下になります。


```javascript
import WebSocket, { WebSocketServer, createWebSocketStream } from "ws";

const wss = new WebSocketServer({ port: 8080 });

wss.on("connection", (ws) => {
  const duplex = createWebSocketStream(ws, { encoding: "utf8" });

  duplex.on("error", (err) => {
    console.error("サーバー側ストリームでエラー:", err);
  });

  duplex.pipe(duplex);

  duplex.on("data", (chunk) => {
    console.log("サーバーで受信:", chunk);
  });
});

console.log("WebSocket サーバーをポート 8080 で起動");
```


ちなみに、以前からよく使っていたシンプルな WebSocketサーバーの処理だと、以下のような実装になります。

```javascript
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('error', console.error);

  ws.on('message', function message(data) {
    console.log('received: %s', data);
  });

  ws.send('something');
});
```

これら 2つの実装の差分としては、WebSocket関連の話では `createWebSocketStream(ws, { encoding: "utf8" });` の部分が加わったくらいです。これにより、Node.jsネイティブの双方向ストリームを扱うことができるようです。


#### 双方向ストリームについて

「双方向ストリーム」の duplex は、今回のコードでは createWebSocketStream(ws) を使って得られた「WebSocket をラップした Duplex ストリーム」になります。これは「Readable」と「Writable」の両方の機能を持ったものになるようです。

このため、コード内の `duplex.pipe(duplex);` というシンプルな記述で、「Readable 部分による WebSocket のメッセージ受信」を「（WebSocket で受信したデータに関しての）Writable 部分による相手への WebSocket での送信」ということが一気にできる形になります。


#### 動作させてみた時の様子

動作させてみた時の様子は以下のとおりです。

流れとしては「サーバー起動」⇒「クライアント実行」⇒「クライアント側から文字を送ると、サーバー側での受信文字列の出力 ＆ クライアント側で文字列の出力（サーバー側に送って返ってきた文字列を受信して出力）」という感じです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/YmLAvR4TEOY?si=ckRAQ2cpmqs7GLMf" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## おわりに

今回、ws の情報に出てくる「createWebSocketStream」が気になり、それを用いたストリームの処理を Node.js で試してみました。

今回の内容だと、ありがたみが分かりにくいですが、「Node.js のストリームを扱える」という話について、以下の記事にあるようなメリットを出せる使い方ができそうです（※ 詳細は未確認）。

●Node.js Stream を使ってみる｜ラキール公式｜株式会社ラキールのエンジニアたちによるTECH BLOG
　https://tech-blog.lakeel.com/n/n62073e6f3101

もう少し、今回の内容は周辺情報なども含め、技術情報を見ていければと思っています