Node-RED Websocket ノード Deep Dive

# 1. はじめに
みなさんフロー書いてますかぁ！（[2022年](https://qiita.com/utaani/items/185343988f8ae86ba1ba) [2023年](https://qiita.com/utaani/items/a4ea0cba414f63adf09f) と同じ）

複雑な機能を持つWebsocketを、フローをちゃちゃっと書くだけで使いこなせるのは、Node-REDのアドバンテージの一つだと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/5c1d6261-535d-b4d7-dee8-5e7094c2f588.png)

でも、[Coreノードとして標準搭載](https://github.com/node-red/node-red/tree/master/packages/node_modules/%40node-red/nodes/core/network)で、こんなに便利なのに、ほとんど詳細な解説がされていないWebsocketノード。本稿では（これまでのDeep Diveと同様に）websocket in / websocket outノードのほぼ全機能を順に紹介していくことにします。なお、2024/11末現在最新のNode-RED 4.0.5を基に記述しますが、以降のリリースでの仕様変更については本稿もなるべく追従して更新していきます。

# 2. Websocketとは

Websocketは、[RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455) にて定義されたプロトコルで、HTTPとともにモダンなブラウザやサーバーミドルウェア、開発フレームワークに実装されています。

トポロジーはTCP/TLS上に実現された[クライアントサーバモデル](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E3%82%B5%E3%83%BC%E3%83%90%E3%83%A2%E3%83%87%E3%83%AB) です。HTTPを基に設計され、クライアントからサーバーへの接続確立はHTTPに準じますが、一旦サーバーとクライアントの間で接続が確立したあとは接続を維持し続け、それを通じて、TCPのSocketのような双方向通信を複数同時にリアルタイムで行うことができます。

多くの実装では、クライアントは一つのサーバーに接続し、サーバーは複数のクライアントから同時に接続することができます。複数のクライアントが同時に接続されているサーバーからは、すべてのクライアントに対して同時に同じメッセージをブロードキャストすることも、接続されているクライアントを選んで個別にメッセージを送信することもできます。

メッセージの形式に制約はなく、テキスト形式やバイナリー形式で送受信することができます。大きなメッセージを小さい単位に分割して段階的に送信する機能を持ちます。

サブプロトコルという仕組みで、Websocketプロトコル上にプロトコルを拡張することができます。MQTTやXMPPなど、TCP/UDPで実装されたプロトコルをWebsocketを通じて使う(over Websocket) 利用例があります。

接続先を指定するURIのスキーム（プロトコル）部は、「ws://」 TLSを使う場合には 「wss://」 が使われます。([RFC 8307](https://datatracker.ietf.org/doc/html/rfc8307)で定義)


# 3. Node-RED におけるWebsocket

Node-REDにおいてWebsocketの実装は、標準ノードの「ネットワーク」カテゴリに websocket-inノード と websocket-outノードの２種類 が準備されています。どちらのノードからも設定ノードを通じてwebsocketサーバーとwebsocketクライアント両方の機能を持ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/bec1e294-f3fb-59c0-f05a-a9e01831ce5f.png)

httpについてはhttpサーバーを構成するhttp-in/-responseノードとhttpクライアントを実現するhttp-requestノードに分かれていますが、Websocketはサーバーとクライアントは設定ノードで区別されます。（後述）

なお、Node-REDの内部でも、Websocketはフローエディタ内部でブラウザとNode-REDサーバの通信に使われています。また、[@flowfuse/node-red-dashboard](https://flows.nodered.org/node/@flowfuse/node-red-dashboard) や[node-red-contrib-uibuilder](https://flows.nodered.org/node/node-red-contrib-uibuilder)、[node-red-contrib-web-worldmap](https://flows.nodered.org/node/node-red-contrib-web-worldmap) など、Node-REDではよく使われるサードパーティノードでもWebsocketが活用されています。これらの実装には、[ws: a Node.js WebSocket library](https://github.com/websockets/ws)が使われています。

## 3.1 ユースケース

Node-REDの開発では、Websocketを使ったフローは以下のユースケースで活用されています。

### 3.1.1 Websocketを提供するサービスへの接続

リアルタイム性能を確保したいWebサービスでWebsocket APIが提供されはじめており、これらのサービスを活用するためにWebsocketノードを活用することができます。

[AWS IoT Core](https://aws.amazon.com/jp/iot-core/)ではMQTTとともにWebsocket APIが提供されていますし、[Amazon API Gateway](https://aws.amazon.com/jp/api-gateway/)でも標準でWebsocket APIが使えるようになっています。

最近では、ChatGPTのリアルタイム機能を下支えする[Realtime API](https://platform.openai.com/docs/guides/realtime) にWebsocketが使われています。

### 3.1.2 複数のNode-RED間を接続

フローの規模が大きくなってフローエディタでの可視性を高めるために使われる、離れたノード間を接続できるLinkノードがあります。しかし、Linkノードで接続できるのは同じNode-RED内のフローのみです。複数のホストで動作しているNode-REDのフローをつなぎ合わせたい場合、同じネットワーク内もしくは直接接続可能な場合は tcp やmqttノードも利用できますが、ネットワークが離れていてhttp/httpsでしか接続できない場合に、Websocketノードを活用できます。インターネット接続できるNode-REDホストをWebsocketサーバーとし、それ以外のホストでクライアントを動作させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/2bb75ed3-f4a0-be25-ad72-126366e45656.png)



Raspberry Piや組み込みLinuxでもNode-REDは稼働しますので、クラウドとエッジを両方でNode-REDを使用している場合はWebsocketを使うとシステム全体をNode-REDのフローで記述することができ、リアルタイムで通信することができます。

### 3.1.3  ブラウザとリアルタイム通信するWebアプリ

ゲームやチャットアプリのように、アプリサーバと遅延なく双方向通信したいニーズにWebsocketが使えます。モダンブラウザではJavaScriptでWebsocketが使えますので、Node-REDから配信されるHTMLの中に10行程度のJavascriptを記載することで、Node-REDのWebsocketノードとリアルタイム通信できるWebアプリを作成することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/f52fc057-8f18-8548-77f8-cfd347abab45.png)



## 3.2 仕組み

Node-REDのWebsocketノードは「接続」（クライアント）と「待ち受け」（サーバー/リスナー）の２つの機能を持ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/964943a4-2d85-200c-7fc9-ca2e5c5ccf51.png)

さらに、ノードの端子の見た目でわかるように、フロー上では受信したメッセージをフローに流すwebsocket-inと、フローから受け取ったメッセージを送信するwebsocket-outの２種類があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/79d4acee-b9c6-24ec-140f-73626e08c09d.png)


ということで、Websocketノードは、機能と方向を組み合わせて４つの使い方ができます。

1. websocket-out / 接続 : websocketサーバーに接続し、フローからのメッセージを接続先に送信
2. websocket-in / 待ち受け : websocketクライアントから接続され、接続先からのメッセージをフローに流す
3. websocket-out / 待ち受け : websocketクライアントから接続され、フローからのメッセージを接続先に送信
4. websocket-in / 接続 : websocketサーバーに接続し、接続先からのメッセージをフローに流す


2つのNode-REDホストをWebsocketで接続する場合、4つの使い方を図にするとこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/ad979d94-5bf3-f52d-c27d-3d805c6add29.png)



## 3.3 Websocketノード詳細
Node-REDのフローエディタのノードパレットに表示されている２つのノード「websocket-in」「websocket-out」については、それぞれ以下の役割を持ちます。

* websocket-in
Websocketからの通信を受信し、次のフローに流します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/9bceab6f-24c3-3df9-f6da-14145bff13fd.png)

* websocket-out
フローから受け取ったメッセージを、Websocket接続先に送信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/59ee6650-985c-fcc6-d0cc-d826391afdfd.png)

websocket-in / -out ともに、設定可能なプロパティは以下の３項目です。

* 種類: Websocketサーバーとして使う場合は「待ち受け」、Websocketクライアントとして使う場合は「接続」を選択します
* パス / URL:「待ち受け」の場合は「パス」としてURLのパス部分を指定、「接続」の場合はURLとして接続先WebsocketサーバーのURL(URI)を入力します
* 名前: フローエディタ内に表示されるノードの名前を指定します。指定しない場合はパス、もしくはURLがフローエディタに表示されます

「待ち受け」(サーバー）のプロパティ設定例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/12355a31-45e6-32be-b3f0-905a777135bd.png)

「接続」（クライアント）のプロパティ設定例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/ff9e5c41-3b3f-4d93-fde6-69d91db459cd.png)



パス、もしくはURLの右側の＋をクリックすると「待ち受け」の場合はwebsocket-listenerノード、「接続」の場合はwebsocket-clientノードの新しいプロパティを作成する設定画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/35091b5c-456f-2f36-7c55-5be096555609.png)

一度開いた設定ノードは、鉛筆マークをクリックすることで編集することができます。

またフローエディタの設定ノードタブから直接設定ノードのプロパティ設定画面を開くこともできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/cab030fa-e865-f230-1ad0-32bb63ff274c.png)

なお、Node-RED 4.0.0以降で、設定ノードのUIが変更になっているので、古くからのNode-REDユーザや、システム内でバージョンの異なるNode-REDを混在して運用している場合は間違いやすいので注意しましょう。

## 3.4 Websocket設定ノード

Websocketを使うにあたっては、以下の3つの設定ノードを設定する必要があります。設定ノードは複数のノードで共通して使うことができ、一つのwebsocket-client ⇔ websocket-listenerの接続の上で複数の websocket-in / websocket-outを同時に接続することができます。

設定ノードはデフォルトでglobalスコープを持ち、すべてのフロータブ内のノードから利用可能となっていますが、Node-RED 4.0.0以降ではフロータブ内やサブフロー内にスコープを限定して設定ノードを配置することができるようになりました。設定ノードのスコープはプロパティ画面下部で設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/6c85b1a4-0657-7c58-2440-bf7fcdeaa39d.png)

スコープを設定した設定ノードは、間違って他のノードで上書き編集することを防げますが、設置した以外のフロータブやサブフロー以外のwebsocket-in/-outノードでは使うことができませんので注意が必要です。


### 3.4.1 websocket-listener
websocket-in/-outノードで「種類」を「待ち受け」に設定した場合、Websocketサーバーの設定をwebsocket-listenerノードで設定する必要があります。

新規にwebsocket-listenerノードを設定する場合は、パスの右側の+をクリックして新しく設定します。すでに設定済みのwebsocket-listenerノードを使う場合はパスから選択することもでき、設定内容を変更する場合は鉛筆アイコンをクリックして変更できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/35091b5c-456f-2f36-7c55-5be096555609.png)

変更する場合、その設定ノードが他のwebsocket-in/-outノードで使われている場合には注意が必要です。設定ノード左下にその設定ノードを使っているノードの数が表示されているので確認してください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/ac854080-c252-2813-24fc-d943a2728b78.png)


websocket-listenerノードで設定可能なプロパティは以下の２項目です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/e83468f4-bbba-d944-a629-9e21ae44224b.png)

* パス:websocketサーバーの位置を示すパスを指定

Websocketサーバーにおけるパスとは、接続を待ち受けする位置です。ひとつのNode-REDホストで、異なるパスを持つWebsocketサーバーを複数設定することができます。スラッシュで始まり（ない場合は付加）、１つ以上のスラッシュを含み、スラッシュで終わらない文字列を指定します。パスに「/debug/ws」を指定するとエラーになります。またすでに設定済みのwebsocket-listenerがある場合、同じパスを指定できません。
 
パスの文字列によりwebsocketサーバのURLが決まります。例えば、Node-REDフローエディタのURLが「 https://example.com/admin 」、パスが「/ws/sample1」の場合、WebsocketサーバーのURLは「wss://example.com/ws/sample1」となります。

* 送信/受信: 送信/受信するメッセージの形式を設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/977698f7-f922-6c76-09ec-bc472beff3a2.png)
「ペイロードを送信/受信」を選択した場合、websocketノードとの通信はmsg.payloadを通じて行われます。「メッセージ全体を送信/受信」を選択した場合、websocketノードとの通信はmsgオブジェクト全体を通じて行われます。

msgオブジェクトにはWebsocketを通じて送受信される内容だけでなく、接続先の情報やsocketの内容も含まれますので、通信内容だけでなくWebsocketの状況を確認したい場合や、送受信するクライアントの区別をしたい場合は「メッセージ全体を送信/受信」を選択します。


なお、websocket-listener設定ノードをつかったWebsocketサーバーは、タイムアウトやTLSなどの細かな設定はNode-REDのフローエディタ等と共通の設定が使われ、Node-REDの設定ファイルである [settings.js](https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js)内で設定します。

WebsocketサーバーのTLSの設定については、デフォルトの設定ファイルではkeyとcertの2つのプロパティのみ記載されていますが、[Node.jsのtls.createSecureContext](https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions)に説明があるようにpfx形式で設定したり、caで自己署名証明書の設定を行ったり、TLSクライアント認証の設定を行うことができます。


### 3.4.2 websocket-client
websocket-in/-outノードで「種類」を「接続」に設定した場合、Websocketクライアントの設定をwebsocket-clientノードで設定する必要があります。

websocket-listenerノードで設定可能なプロパティは以下の5項目です。(wssを使う場合は6項目）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/80ef08b9-1ac3-ec1f-4ddd-be96fd471fe4.png)

* URL: 接続する先のWebsocketサーバのURLを指定します。ws:// で始まる場合はTCP、wss:// で始まる場合はTLSで接続されます。Node-REDのwebsocket-listenerノードにws://で接続する場合、IPアドレスに加えて、Node-REDのフローエディタと同じポート番号（デフォルトで1880）を指定します

* TLS設定: 後述のtls-config設定ノードでTLS設定を行います。TLS設定はhttp-requestノードと共通で使えます。＋をクリックして新しいTLS設定を作成することも、鉛筆アイコンをクリックして選択中のTLS設定を書き換えることもできます

* サブプロトコル: Websocketのサブプロトコルを指定します。接続先で必要な場合に文字列で設定します。カンマで区切って複数のサブプロトコルを指定できます

* ハートビートを送信: 接続するネットワークによっては、Websocket上に長期間通信がないと切断される（サイレントクローズ）することがあります。これを防ぐために、通信がない期間でも一定期間ごとに接続を維持する通信を送信することができます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/ff06a018-95cf-29f0-9654-b4bb8b51dc77.png)

送信する場合はチェックをいれて、送信間隔を整数秒で指定します。サイレントクローズの大きな要因は動的NATルータのテーブル維持時間と、サーバー側のリバースプロキシやロードバランサーの設定です。ネットワーク環境によってよって異なりますが、一般的には60秒通信がないと切断されることが多いので、59秒、ないし念のため29秒ごとにハートビートを送る設定にするといいでしょう。なお、間隔を必要以上に短くすると通信コストやサーバー運営コストに影響することがあるので注意が必要です。

* ヘッダ: Websocketの接続時にヘッダを指定する必要がある場合に、Key-Value形式で指定します

接続するサービスによっては、接続時の認証に必要な場合があります。認証にAuthorizationヘッダを使う場合、Barere認証文字列を直接設定してしまうとフローにアクセスできるユーザから見えてしまい悪用される危険があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/93cae88a-89bc-ff7e-cfe6-ee0d8d9640c9.png)


環境変数を指定した上で、フロータブ環境変数、もしくはフローエディタ全体のグローバル環境変数(Node-RED 4.0以降)で「認証情報」を使うことで、Node-REDのクレデンシャル領域に安全に保管されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/6a15bc8e-d49a-8867-a47d-d9910bcf377c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/95679f02-aba9-4967-1303-802ba0770485.png)


なお、Websocket通信の確立に必要なUpgradeヘッダやConnectionヘッダは自動で付加されているので、別途指定する必要はありません

### 3.4.3 tls-config
websocket-clientノードで、接続先URLが wss:// で始まる場合にTLS設定メニューが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/7c08e741-9a26-e0c6-d2f8-32574022c909.png)


接続先とのTLSの確立に必要な情報を指定します。仕様はhttp-requestノードと共通です。設定可能なプロパティは9項目です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/73b7563b-04ac-9716-819b-412b39358ba3.png)

* ローカルファイルの秘密鍵と証明書を使用
* 証明書: 
* 秘密鍵
* パスフレーズ
* CA証明書
* サーバ証明書を確認
* サーバ名
* ALPNプロトコル
* 名前

Node-REDホストに導入されたNode.jsやOSが持つ公開CAのTLSを使い接続する場合は、「サーバ証明書を確認」のみチェックを入れることで最小限のTLS設定になります。接続先サーバーで自己認証のTLSサーバー証明書を使っている場合は、証明書、もしくはCA証明書を設定する必要があります。接続先サーバーでTLSクライアント認証を行っている場合は、加えて秘密鍵とそのパスフレーズを設定する必要があります。

SNI（一つのTLSサーバーで複数のFQDNを持つサーバを運用）を使っている場合は、接続先のサーバFQDNを「サーバ名」欄に記述しなければならない場合があります。

各項目の詳細は拙作[Node-RED http request ノード Deep Dive](https://qiita.com/utaani/items/a4ea0cba414f63adf09f) の [tls-configノード](https://qiita.com/utaani/items/a4ea0cba414f63adf09f#ssltls%E8%A8%AD%E5%AE%9Atls-config%E3%83%8E%E3%83%BC%E3%83%89) の項目をご参照ください。


## 3.5 ノードステータス

websocket-in / -outノードには、接続状況をノードの下部に表示するノードステータス表示機能があり、使っているwebsocket-listener / websocket-clientの接続状態などをステータス表示することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/c5693ba7-b36f-351f-9c3a-1967b95b8980.png)


### 3.5.1 websocket-listener

* 表示なし: デプロイされてから一度も接続されていない
* connected (数字) : 現在の接続数 

### 3.5.2 websockcket-client

* 表示なし: デプロイ前
* connected: 接続された
* 切断: 切断された
* error: なんらかのエラー

(切断だけ日本語になってるのは少々変ですね...)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/99b7a472-5b5a-bd96-9c32-0edd8ede1e8d.png)


### 3.5.3 接続状態の管理

ノードステータスは、Statusノードで取得することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/a203afcc-9b2f-fdd5-ad2d-7de46b227a51.png)

websocket-clientを持つ特定、もしくは複数のWebsocket-in / -out ノードを指定することで、接続状態を取得することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/47c458e4-9c95-963d-6c15-fbecd5ed4015.png)

msg.status.textは表示されている文字列が取得できますが、切断だけ日本語になってるように、Node-REDの言語設定によって異なることがあるので、接続状態の管理にはmsg.status.eventを使いましょう。

* connect
* error
* disconnect

また、msg.status._session.id に接続先のセッションを識別するIDがあるので、msg.statusとセットでコンテキストなどに永続化することで、接続先ごとの接続状態を管理することができます。

# 4. Websocketノード基本のキ

## 4.1 Node-RED同士をつなぐ
[3.1.2 複数のNode-RED間を接続](https://qiita.com/drafts/9c38625b97323af286c2#312-%E8%A4%87%E6%95%B0%E3%81%AEnode-red%E9%96%93%E3%82%92%E6%8E%A5%E7%B6%9A) で示したように、Websocket ノードをつかって、複数のNode-REDホストのフロー間を接続することができます。

Node-RED同士をつなぐユースケースとしては、例えば以下のようなものがあります

* IoTサービスでエッジ側とクラウド側のフローを接続して協調処理

Node-REDは組み込みLinuxでも動作しますので、プロトタイピングの場面でよく活用されている Raspberry Piでエッジ側の処理を実施、クラウド側のNode-REDホストと連携させることでIoTプロトタイピングを効率よく行うことができます。httpを用いても連携できますが、音声やビデオのような大量の通信をリアルタイムで行いたい場合や、クラウド→エッジの方向でリアルタイムデータ通信が必要な場合にWebsocketが活躍します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/ed5a0c3b-60b3-ae75-d400-84365b37bf9c.png)


* リクエストをフロント処理するNode-REDホストとバックエンドで時間のかかる処理を行うNode-REDホストを接続して処理量を分散

Node-REDは内部的には１プロセスで動作するので、フローの規模が大きくなってくると単一ホストでの処理に時間がかかるケースが発生します。ホストのスケールアップ（CPUやメモリを増やす、記憶装置やネットワークを高速化する）では改善されない場合、複数のNode-REDホストに処理を分散してスケールアウトさせたいケースがでてきます。非同期的に連携させる場合、間にキューを挟むことがありますが、同期的に連携させる場合はNode-REDホスト間をWebsocketで接続して連携させることも有用です。


## 4.2 ブラウザHTMLとリアルタイム通信

サービスやアプリのプロトタイピング用途で、まずブラウザ上で動作するWebアプリを簡単に作成したいケースがあります。Node-REDには http-in / http-response / template ノードがあり、これらを使うとNode-REDだけで動的なWebアプリホストを開発することが可能です。

Node-REDホストでWebアプリページを提供し、そのWebアプリ内でNode-REDのwebsocket-listenerに接続するように設定することで、Node-REDホストからリアルタイムにブラウザ内HTMLの要素を書き換えることが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/f52fc057-8f18-8548-77f8-cfd347abab45.png)

サンプルフローを[6.3](https://qiita.com/drafts/9c38625b97323af286c2/edit#63-%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%A8%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E9%80%9A%E4%BF%A1%E3%81%99%E3%82%8Bweb%E3%82%A2%E3%83%97%E3%83%AA)に掲載しています。

Node-REDフローの全景はこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/1aba9b67-568b-e0b2-dd88-fa1f185c99d8.png)

templateノード「HTMLapp」の中身を以下に示します。

```HTML:
<!DOCTYPE html>
<html>
<body>
    ここにタイムスタンプが表示
    <p id="timestamp">
    </p>
    <script>
        var wsurl;
        if (window.location.protocol == 'https:'){
            wsurl = 'wss://'+window.location.host+'/ws/wspage';
        } else{
            wsurl = 'ws://'+window.location.host+'/ws/wspage';
        }
        var ws = new WebSocket(wsurl);
        ws.addEventListener('message',function(e){
            document.getElementById('timestamp').innerText = String(e.data);
        });
    </script>
</body>
</html>
```

Websocketの接続先はTLSの有無によって変わるため、windows.location.protocolでTLSかどうかを判別した上で「ws://」か「wss://」を切り替えています。Injectノードから送信されたmsg.payloadはブラウザ側ではe.dataとして受信できるので、innerTextをつかってHTMLを書き換えて更新を表現しています。



# 5. 特殊な使い方

## 5.1 サーバーからメッセージ送信先を指定

websocket-listenerノードは、複数のWebsocketクライアントからの接続を同時に受けることができます。サーバーからクライアントの送信時、メッセージに msg._session が含まれていない場合、接続されているWebsocketクラアイントすべてに同じメッセージが送信されますが、 msg._session が含まれている場合、msg._sessionに対応するWebsocketクライアントにのみメッセージが送信されます。

Websocketクライアントからのメッセージをwebsocket-inノードで受信した場合、msgオブジェクトにはmsg._sessionが含まれるので、そのまま同じフローでmsgオブジェクトをwebsocket-outノードで送信すると、受信したWebsocketクライアントにのみ送信されます。一つのWebsocketクライアントからの受信をトリガに接続されているすべてのWebsocketクライアントにブロードキャストしたい場合は、Changeノードなどを使ってmsg._settionオブジェクトを削除する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/141ec079-e61f-4a2d-241a-227be7455ba1.png)


なお、websocket-listenerで作成したWebsocketサーバに複数のWebsocketクライアントが接続されている場合、Node-RED内部では接続先の一覧とmsg._sessionは管理されていません。msg._sessionを使って送信先を指定したい場合には、最初の通信で送信されたのちにメッセージ中のmsg._sessionをファイルやコンテキストなどに永続化するなどして接続先の管理を行う必要があります。

## 5.2 切断検知、復旧

websocket-clientノード自体には自動復旧機能があり、切断を検知すると自動でサーバーに再接続します。ノードステータスに接続状況が表示されますので、Statusノードをつかうと切断や再接続を検知することができます。Statusノードをつかって、接続状況をコンテキストで永続化して管理することにより、接続中のみ送信するようにフローを記述することができます。

サイレントクローズのように、Websocketの切断のされかたによっては切断検知できないことがあります。その場合一度サーバーとの接続が切断されてしまうとサーバー側が復旧しても再接続されず、フローを再起動/再デプロイする必要があります。

また、ハートビートを送信する設定にしていても、ネットワークやサーバー側要因でWebsocketが切断されてしまうことがあります。通信の信頼度を高めるためには、フローをつかって一定期間ごとに生存確認通信を行い、長期間サーバーからの返信がない場合はフロー自体を再起動する仕組みを導入する必要があります。

## 5.3 他のプロトコルとの連携

Node-REDにはhttp mqtt tcp udp などの標準ノードにより多彩なプロトコル通信をサポートしており、さらにサードパーティノードを使うと対応プロトコルを増やすことができます。エッジサーバとしてNode-REDを使っている場合、Websocketノードをこれらのプロトコルノードと組み合わせることで、サーバーよりエッジサーバーの各種プロトコルを「over Websocket」化して透過させることができます。

## 5.4 Websocket接続の認証について

websocket-client設定ノードには、接続時にヘッダを指定する機能があり、Authorizationヘッダに「Bearer token文字列」を指定することなどで接続時に認証を必要とするWebsocketサーバに接続することができます。しかし、websocket-listener設定ノードは認証機能を持たないため、Node-REDの機能だけではWebsocketサーバーに認証をかけることはできません。

Node-REDによるWebsocketサーバーに認証をかけたい場合、以下の手段が考えられます

* パスの文字列を複雑なものにする
* Node-REDの前段にnginxなどでリバースプロキシを設置し、プロキシで認証設定をする
* TLSクライアント認証設定を行う

# 6 Websocket利用サンプル

## 6.1 OpenAI APIのリアルタイムAPI

https://flows.nodered.org/flow/cb0123996b8ccbc8f026ebe780bfd76d
に、Websocketノードを活用したOpenAIのRealtime APIを使ったデモフローがあります。注意として、websocket-clientに認証用ヘッダの付与が必要になるので、Node-RED 4.0以降を使う必要があります。

## 6.2 Node-RED同士の接続

[3.1.2](https://qiita.com/drafts/9c38625b97323af286c2/edit#312-%E8%A4%87%E6%95%B0%E3%81%AEnode-red%E9%96%93%E3%82%92%E6%8E%A5%E7%B6%9A) で説明したNode-RED同士を接続するサンプルフローを共有します。

まずクライアント側のNode-REDのフローはこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/8699ec4c-754f-22b8-ed8a-884b72f70702.png)

```JSON:flows-client.json
[{"id":"28998c9318d28598","type":"websocket out","z":"29e6f1b2171d4518","name":"ws-out : client","server":"","client":"87ead668e4847d83","x":340,"y":80,"wires":[]},{"id":"881955ade96ac803","type":"websocket in","z":"29e6f1b2171d4518","name":"ws-in : client","server":"","client":"87ead668e4847d83","x":110,"y":220,"wires":[["cfa28746c5df23ae"]]},{"id":"cfa28746c5df23ae","type":"debug","z":"29e6f1b2171d4518","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":300,"y":220,"wires":[]},{"id":"114ade7a269a99c5","type":"inject","z":"29e6f1b2171d4518","name":"","props":[{"p":"_session","v":"close","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":80,"wires":[["28998c9318d28598"]]},{"id":"87ead668e4847d83","type":"websocket-client","path":"ws://10.61.116.219:1880/ws/qiita01","tls":"","wholemsg":"false","hb":"29","subprotocol":"","headers":[]}]
```

実際に使う場合には、websocket-client設定ノード内の、接続先IPアドレス/host名を設定する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/931a1c2a-43a6-6c10-a241-df3deb39c0d9.png)


サーバー側のフローはこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/b24467bc-f071-8355-96c4-71a2206e647f.png)


```JSON:flows-listener.json
[{"id":"4b321f5cd2020411","type":"websocket in","z":"6dbc4069937aaf8c","name":"ws-in : listener","server":"13bd03d3916e91e8","client":"","x":370,"y":60,"wires":[["48aba529ef064d38"]]},{"id":"7adcf084a512156c","type":"websocket out","z":"6dbc4069937aaf8c","name":"ws-out : listener","server":"13bd03d3916e91e8","client":"","x":580,"y":160,"wires":[]},{"id":"5344f4591d226f56","type":"inject","z":"6dbc4069937aaf8c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":360,"y":160,"wires":[["7adcf084a512156c"]]},{"id":"48aba529ef064d38","type":"debug","z":"6dbc4069937aaf8c","name":"debug 240","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":60,"wires":[]},{"id":"13bd03d3916e91e8","type":"websocket-listener","path":"/ws/qiita01","wholemsg":"false"}]
```

## 6.3 ブラウザとリアルタイム通信するWebアプリ

[3.1.3](https://qiita.com/utaani/private/9c38625b97323af286c2#313--%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%A8%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E9%80%9A%E4%BF%A1%E3%81%99%E3%82%8Bweb%E3%82%A2%E3%83%97%E3%83%AA) で紹介したブラウザとリアルタイム通信するWebアプリは、Node-REDに以下のflows.jsonをインポートして試すことができます。

```JSON:flows.json
[{"id":"65fab62d9003158a","type":"tab","label":"WS4WebBrowser","disabled":false,"info":"","env":[]},{"id":"01b641e2a77962e5","type":"http in","z":"65fab62d9003158a","name":"","url":"/wspage","method":"get","upload":false,"swaggerDoc":"","x":130,"y":100,"wires":[["c1a682d292aafe0c"]]},{"id":"c1a682d292aafe0c","type":"template","z":"65fab62d9003158a","name":"HTMLapp","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!DOCTYPE html>\n<html>\n<body>\n    ここにタイムスタンプが表示\n    <p id=\"timestamp\">\n    </p>\n    <script>\n        var wsurl;\n        if (window.location.protocol == 'https:'){\n            wsurl = 'wss://'+window.location.host+'/ws/wspage';\n        } else{\n            wsurl = 'ws://'+window.location.host+'/ws/wspage';\n        }\n        var ws = new WebSocket(wsurl);\n        ws.addEventListener('message',function(e){\n            document.getElementById('timestamp').innerText = String(e.data);\n        });\n    </script>\n</body>\n</html>","output":"str","x":320,"y":100,"wires":[["b1216fb32ca546e7"]]},{"id":"b1216fb32ca546e7","type":"http response","z":"65fab62d9003158a","name":"","statusCode":"","headers":{},"x":490,"y":100,"wires":[]},{"id":"d7970e13487a3aa7","type":"websocket out","z":"65fab62d9003158a","name":"","server":"c855e0641b02a2d5","client":"","x":380,"y":220,"wires":[]},{"id":"19a8c565ececba1d","type":"inject","z":"65fab62d9003158a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":220,"wires":[["d7970e13487a3aa7"]]},{"id":"c855e0641b02a2d5","type":"websocket-listener","path":"/ws/wspage","wholemsg":"false"}]
```


# 7 おわりに

Websocket ノードは複雑なWebsocketを簡単に使える、Node-REDのキラー機能というべきすばらしいノードだと思います。本記事で全貌を明らかにしましたが、今後も新機能がたくさん追加されていくことを期待します。

みなさま、よきNode-REDライフを！