0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CloudflareAdvent Calendar 2024

Day 13

socket.io をCloudflare Worker に移植してみた

Posted at

背景

socket.ioとは

socket.ioは、クライアント-サーバー間のリアルタイム通信のためのNode.jsサーバーです。

プロトコルがちゃんと定義されており、公式のNode.js実装の他にも多くの言語でクライアント/サーバー向けに実装されています。そのため、多くの人々やソフトウェアがこれを基盤としているようです。例えば私の観察では、今年よく使用したGoogle colabはある古いバージョンのsocket.ioをつかっているっぽいです。

Socket.io は複数のトランスポート(HTTP / WebSocket / WebTransport)の上に、セッション、認証、ネームスペース、ルーム、ブロードキャストなどの高レベル機能を提供します。

なぜこのポートを作ったか

私は以前からSocket.ioを知っており、これを基にした小規模なプロジェクトをいくつか持っています。

最近の使用例は、マルチユーザーのホワイトボードのようなウェブアプリの開発でした。それを作る時期に、ちょうどCloudflare (以下CF) のサーバーレスプロダクトであるWorkerとDurable Object (以下DO) について読んでいたところで、Socket.io(またはその簡略版)をCFで動作させることができるのではないかと気づきました。

数ヶ月が経過し、動作するバージョンができました。最新バージョンはnpmに公開されました:
npm
。現時点ではWebSocketトランスポートと一部の機能(私に必要だった機能)のみをサポートしていますが、動作はします🔥。
コードはGitHub - jokester/socket.io-serverlessに公開しています。

開発

Socket.ioの本来の構造

Socket.io(トップレベルのライブラリ)には2つの主要コンポーネントがあり、npmパッケージのsocket.ioengine.ioです。

socket.ioパッケージは高レベルの概念:ネームスペース/ルーム/クラスタリング等を扱います。その下に http.Serverインスタンスを保持し、トランスポート関連のロジックを扱うengine.io が存在します。

Node.jsでは、この2つのコンポーネントは同じプロセス内で実行され、イベントエミッターAPIを通じて動きます。

CF worker / Durable Object のための開発

CF DO/workerでは、JSはNode.jsではない特殊なサーバーレス環境で実行されます。workerdだと思います。

Node.js / webと比較してJS開発者にとって最大の違いは、おそらく状態の揮発性でしょう。

Node.jsプロセスやブラウザタブのような従来の環境では、サーバーがダウンするかタブが閉じるまでメモリがクリアされない、コードもだいたい動き続けます。しかしCFのサーバーレス環境では、非アクティブになるとコードの実行を停止し、JSコードのメモリ内状態を破棄します。

JS の setTimeoutsetInterval タイマーがある場合はアクティブとみなされます。保留中のHTTPリクエストもアクティブとみなされます。アクティブなWebSocket接続は、使用されるAPIによってアクティブとみなされる場合とそうでない場合があります。

具体的には、DOの場合、メモリ内状態の破棄は実際にはhibernation / 休止と呼ばれています。開発者は提供されるKVストアのようなAPIを使用して、手動で状態の保存/復元を行うことができます。

また、利用可能な標準ライブラリも異なります。

JSの言語APIのみを使用するコードは、だいたいそのまま動作するはずです。Node.js APIを必要とするコードは、Node.js compatibility flag を有効にすればでNode.jsのポリフィルを使用できます。

2024年のある時点から、Node.jsの標準ライブラリのポリフィルはunenvをベースとし、nodejs_compat_v2フラグの背後にあります。この記事に詳しい説明があります:Cloudflare Workersのnodejs_compat_v2で何が変わったのか

v2以前は、私の非公式な調査によると、nodejs_compatフラグは最終的にwrangler CLIが使用するesbuildが使用する@esbuild-plugins/node-modules-polyfillが使用するionic-team/rollup-plugin-node-polyfillsに基づいていました。

socket.io-serverlessの構造

魔改造した socket.ioengine.io からのコードを実行するために2つのDOを使用しました。

class EngineActor extends DurableObject {...}engine.ioコードを実行するDOです。WebSocket接続を受け付け、SocketActorと実際のWS接続との間で双方向のWSメッセージを転送します。

class SocketActor extends DurableObject {...}socket.ioコードを実行するDOです。EngineActorからのRPC呼び出しに応答し、Namespaceのようなオブジェクトにメッセージを送信します。アプリケーションコードが engine.io Socket(異なるトランスポートの抽象化)にメッセージを送信すると、そのメッセージはEngineActorに転送され、WS接続の他端に流れます。

したがって、sio.Namespace sio.Client sio.Roomに基づくアプリケーションロジックコードは、制限事項はありますが、オリジナルのSocket.io 使うときと同様に動作できます。

2つのDOの他に、リクエストをEngineActorに転送する単純なHTTPハンドラーであるworker エントリポイントが必要です。

前述のhibernationを防ぐことは不可能ではありませんが(最初にできたバージョンではそうした)、サーバーレスバージョンではむしろhibernationを活用してエネルギーを節約し、地球を守るべきだと判断して、hibernationあっても動作するように作りました。

EngineActor SocketActor内の状態(接続ID、動的に作成される可能性のあるNamespaceなど)は、異なるライフサイクル間で保存/復元されます。

engine.io socket.ioのコードのほとんどは既にメッセージイベントによって駆動されていて、とくに何もしなくてOK。しかし、ハートビートチェックを駆動するpingタイマーがありました。ここは Alarms APIを代わりに使って、もとの setInterval 実装をスタブにしています。

現在、socket.io-serverlessは各DOクラスのインスタンスを1つだけ作成します。将来、パフォーマンスが問題になった場合は、より多くのDOで負荷を分散できるようにすることが可能のはずです(Socket.ioで使用されているアダプター/クラスター構造と近いイメージ)。

コードの開発とビルド方法

sio-serverlessの開発にはモノレポを使用しました。

socket.ioはオリジナルのTSコードをNPMで公開していないため、socket.ioリポジトリ(現在はモノレポ)をgitサブモジュールとして含めました。これで私のモノレポにはsocket.io-serverless socket.io/packages/socket.io socket.io/packages/engine.ioなどの子パッケージでできています。

一部のsocket.ioコードは、package.jsonのexport map を含めて、パッチを当てる必要がありました。パッチはモノレポに含まれており、Makefileによって適用されます。

esbuildsocket.io-serverlessコードを、Socket.ioや他の依存関係と共に、非圧縮バンドルにバンドルします。

依存関係の解決プロセスをカスタマイズするために、esbuild APIをビルドスクリプトで使用しています。多くのimportはCFでも動く実装(debugなど)に置き換えられるか、単純にスタブ化(node:httpなど)されています。

おわりに

これを作るのは楽しかったし、作るためにSocket.ioの内部を完全に理解したと思います。ついに実現できて嬉しく思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?