Slide 1

Slide 1 text

Small Tips to use Bun with WebSocket Server and WebAssembly Modules JSConf.JP 2022 @massie_g / がねこまさし Bun と WebSocket Server とオマケの WebAssembly

Slide 2

Slide 2 text

Disclaimer / 言い訳 ● I was planning to introduce some tips for WebSocket, but I find that these tips are not necessary in most situation. ● So I add some trivia about Bun and WebSocket. ○ Please relax and enjoy ● This slide is based on my personal study ○ Not official information ○ All errors are my mistakes. ● 当初 WebSocket にまつわる Tips をご紹 介する予定でしたが、その後ほとんどの場 面では不要なことがわかりました ● そのため、雑学を追加してお話しします ○ リラックスして、聞き流してください ● このスライドの内容は、個人的な実験結果 にもとづきます ○ 公式の見解ではありません ○ 誤りについてはすべて私個人の責任です My talk is mostly in English, some part is mixed with Japanese 英語メインで、一部日本語をミックスしてお話しします

Slide 3

Slide 3 text

What is Bun? / Bun とは?

Slide 4

Slide 4 text

What is Bun? https://bun.sh/ Bun is a fast all-in-one JavaScript runtime - using JavaScriptCore (JavaScript Engine) - implemented with Zig - support TypeScript - support some of npm modules - not socket.io - built-in modules - sqlite - tuned for some selected use cases (according to my impression) - For build tools - For dev server - Not for demon server process, but event hunder of serverless / edge servers https://bun.sh/ 「速い」「全部入り」の JavaScriptランタイム - JavaScriptCore をエンジンに使っている - Zigで開発されている - TypeScriptがそのまま動く - npmモジュールが(一部)使える - socket.io は未サポート - モジュールを内蔵(ビルトイン) - sqlite など - 特定のケースに対してチューニング(個人的 印象) - ビルドツール - 開発サーバ - 常駐サーバープロセスではなく、サーバーレ ス/エッジ環境のイベントハンドラ Bunとは?

Slide 5

Slide 5 text

List of JavaScript Engine - Browser - Runtime JavaScript Engine Browser JavaScript Runtime Edge V8 Chrome/Chromium MS Edge Node.js Deno Cloudflare workers(V8 Isolate) Vercel Edge Runtime JavaScriptCore Safari/Webkit Bun SpiderMonky Firefox Fastly Compute@Edge (WASM ver.) Chakra/ChakraCore Internet Explorer MS Edge Legacy (node-chakracore) QuickJS qjs WasmEdge (wasmedge-quickjs)

Slide 6

Slide 6 text

Matrix of JavaScript Engine - Language - Runtime Implementation language of runtime → ↓ used JS Engine C C++ Rust Zig Go V8 Node.js Deno JavaScriptCore Bun SpiderMonky Chakra/ChakraCore (node-chakracore) QuickJS qjs

Slide 7

Slide 7 text

What is Bun? https://bun.sh/ Bun is a fast all-in-one JavaScript runtime - using JavaScriptCore (JavaScript Engine) - implemented with Zig - support TypeScript - support some of npm modules - not socket.io yet - built-in modules - sqlite - tuned for some selected use cases (according to my impression) - For build tools - For dev server - Not for demon server process, but event handler of serverless / edge servers https://bun.sh/ 「速い」「全部入り」の JavaScriptランタイム - JavaScriptCore をエンジンに使っている - Zigで開発されている - TypeScriptがそのまま動く - npmモジュールが(一部)使える - socket.io は未サポート - モジュールを内蔵(ビルトイン) - sqlite など - 特定のケースに対してチューニング(個人的 印象) - ビルドツール - 開発サーバ - 常駐サーバープロセスではなく、サーバーレ ス/エッジ環境のイベントハンドラ Bunとは?

Slide 8

Slide 8 text

WebSocket Server/Client

Slide 9

Slide 9 text

Example Echo Server / エコーサーバーの例 ● trial of simple echo server ○ 1 Server - 1 Client ○ exchange few messages, then exit Echo Server prosess (WebSocket Server) Client prosess (WebSocket Client) same machine (localhost) ● 実験用のシンプルなエコーサーバー ○ サーバー 1: クライアント1 ○ メッセージを何回か交換したら終了 'connected!' 'Hello' 'Echoback:Hello' 'QUIT' CONNECT process.exit() DISCONNECT start listen

Slide 10

Slide 10 text

Echo Server case (1): Node.js server / client // sever.js const PORT = 8000; const Server = require('ws').WebSocketServer; const wss = new Server({port:PORT}); wss.on('connection' , function connection (ws) { ws.on('message' , function message(data) { console.log('received: %s' , data); ws.send('Echoback:' + data); const text = '' + data; if (text === 'QUIT') { console.log('QUIT Server' ); process.exit(0); } }); ws.send('connected!' ); }); // client.js const PORT = 8000; const URL = 'ws://localhost:' + PORT; const Client = require('ws').WebSocket; const ws = new Client(URL); ws.on('open', function open() { ws.send('Hello'); }); ws.on('message' , function message(data) { console.log('received: %s' , data); const text = '' + data; if (text === 'Echoback:Hello' ) { console.log('got hello' ); ws.send('QUIT'); } }); $ node server.js $ node client.js OK

Slide 11

Slide 11 text

Echo Server case (2): Node.js server / Bun client // sever.js const PORT = 8000; const Server = require('ws').WebSocketServer; const wss = new Server({port:PORT}); wss.on('connection' , function connection (ws) { ws.on('message' , function message(data) { console.log('received: %s' , data); ws.send('Echoback:' + data); const text = '' + data; if (text === 'QUIT') { console.log('QUIT Server' ); process.exit(0); } }); ws.send('connected!' ); }); // client.js const PORT = 8000; const URL = 'ws://localhost:' + PORT; const Client = require('ws').WebSocket; const ws = new Client(URL); ws.on('open', function open() { ws.send('Hello'); }); ws.on('message', function message(data) { console.log('received: %s' , data); const text = '' + data; if (text === 'Echoback:Hello' ) { console.log('got hello'); ws.send('QUIT'); } }); $ node server.js $ bun client.js OK

Slide 12

Slide 12 text

Echo Server case (3): Bun server / Bun client // sever.js const PORT = 8000; const Server = require('ws').WebSocketServer ; const wss = new Server({port:PORT}); wss.on('connection' , function connection (ws) { ws.on('message' , function message(data) { console.log('received: %s' , data); ws.send('Echoback:' + data); const text = '' + data; if (text === 'QUIT') { console.log('QUIT Server' ); process.exit(0); } }); ws.send('connected!' ); }); // client.js const PORT = 8000; const URL = 'ws://localhost:' + PORT; const Client = require('ws').WebSocket; const ws = new Client(URL); ws.on('open', function open() { ws.send('Hello'); }); ws.on('message', function message(data) { console.log('received: %s' , data); const text = '' + data; if (text === 'Echoback:Hello' ) { console.log('got hello'); ws.send('QUIT'); } }); $ bun server.js FAIL to start Exception error: Not implemented yet! at new WebSocketServer

Slide 13

Slide 13 text

Trivia 1: ws module for Bun / Bun と wsモジュール ● It seems that Bun supports "ws" module of npm. ● Actually, npm module is not used. ○ able to use "ws" without install. ○ cf. https://github.com/oven-sh/bun/blob/ma in/src/bun.js/ws.exports.js ● WebSocket Client ○ built-in WebSocket client is used, instead of ws.WebSocket ● WebSocket Sever ○ ws.WebSocketServer exist, but not implemented yet. ■ exception in constructor. ○ ServerWebSocket is available ■ used from Bun.serve() ■ described in README.md ● Bun でも npm モジュールの wsが使えるよう に見える ● 実は npm モジュールは利用されていない ○ ※ ws をインストールしなくても使える ○ 参考 https://github.com/oven-sh/bun/blob/ma in/src/bun.js/ws.exports.js ● WebSocket クライアント ○ ws.WebSocketの代わりにBun組み込みの WebSocketクライアントが使われる ● WebSocket サーバー ○ ws.WebSockerServerに対応する組み込み サーバーは未実装 ■ コンストラクタで例外を投げるだけ ○ 代わりに、ServerWebSocketを使う ■ Bun.serve() から利用する ■ ※README.md に書いてある

Slide 14

Slide 14 text

Echo Server case (3b): Bun modified server/ Bun client // bun_server.js const PORT=8000; Bun.serve({ port: PORT, websocket: { open(ws) { ws.send( 'connected!' ); }, message(ws, message) { ws.send( 'Echoback:' + message); const text = '' + message; if (text === 'QUIT') { process.exit( 0);} }, }, fetch(req, server) { if (server.upgrade(req)) return; return new Response("HTTP response" ); }, }); // client.js const PORT = 8000; const URL = 'ws://localhost:' + PORT; const Client = require('ws').WebSocket; const ws = new Client(URL); ws.on('open', function open() { ws.send('Hello'); }); ws.on('message', function message(data) { console.log('received: %s' , data); const text = '' + data; if (text === 'Echoback:Hello' ) { console.log('got hello' ); ws.send('QUIT'); } }); $ bun bun_server.js $ bun client.js 2 problems in some case

Slide 15

Slide 15 text

Tips1 : using send() in open handler of ServerWebSocket ● Using send() in websocket open() handler cause disconnect ○ → issue #1469 ● Tips1: Workaround (choose a or b) ○ (a) stop using send() in open() ○ (b) use setTimeout() for send() ● Precondition ○ Only Bun Server - Bun Client ○ NOT necessary for other case ■ Bun Server - Node.js Client ■ Bun Server - Deno Client ■ Bun Server - Browsers ■ Node.js Server - Bun Client ■ Deno Sever - Bun Client ● websocketのopenイベントハンドラ内で send() を使うと、 切断される ○ → issue #1469 ● Tips1: 対策 ( a または b) ○ (a) openイベントハンドラでは send()を使わな い ○ (b) setTimeout() を入れてから send() を使う ● 前提条件 ○ Bun Server - Bun Client の組合せだけ発生 ○ 他のケースでは不要 ■ Bun Server - Node.js Client ■ Bun Server - Deno Client ■ Bun Server - Browsers ■ Node.js Server - Bun Client ■ Deno Sever - Bun Client

Slide 16

Slide 16 text

Tips2: localhost reslovging in IPv6 / IPv4 ● when using "localhost" in client, fails to connect ○ → Issue #1389 ○ "localhost" is resolved in IPv6 address [::1]. ○ Server is listing on "0.0.0.0" (IPv4) ● Tips2 : Workaround (one of a or b) ○ (a) Use "127.0.0.1" in the client. (IPv4) ○ (b) Use host option in the server (undocmented) - host : "localhost" (IPv6) ● Precondition ○ Only Bun Server - Bun Client ■ NOT necessary for other case ○ Only in some environment (Ubuntu 22.04) ■ NOT necessary for Ubuntu 20.04, macOS 12 ● クライアント側で接続先に "localhost" を指定す ると、接続できない ○ → Issue #1389 ○ "localhost" は IPv6の[::1]に解決される ○ サーバーは "0.0.0.0" (IPv4) でリッスン中 ● Tips2 : 回避策 (a, b のどちらか一方だけ ) ○ (a) クライアント側で"127.0.0.1"を指定(IPv4) ○ (b) サーバー側でパラメータを指定 ■ host : "localhost" (IPv6) ● 前提条件 ○ Bun Server - Bun Client の組合せだけ発生 ■ 他のケースでは不要 ○ 特定の環境だけ必要(Ubuntu 22.04 など) ■ Ubuntu 20.04, macOS 12 では不要

Slide 17

Slide 17 text

Echo Server case (3c): Bun server/ Bun client // bun_server.js const PORT = 8000; Bun.serve({ host: "localhost", port: PORT, websocket: { open(ws) { setTimeout(() => { ws.send('connected!');   }, 10); }, message(ws, message) { console.log( 'received: %s' , message); ws.send( 'Echoback:' + message); const text = '' + message; if (text === 'QUIT') { process.exit( 0);} }, fetch(req, server) { if (server.upgrade(req)) return; return new Response("HTTP response" ); }, }); // client.js const PORT = 8000; const URL = 'ws://127.0.0.1:' + PORT; const Client = require('ws').WebSocket; const ws = new Client(URL); ws.on('open', function open() { ws.send('Hello'); }); ws.on('message', function message(data) { console.log('received: %s' , data); const text = '' + data; if (text === 'Echoback:Hello' ) { console.log('got hello' ); ws.send('QUIT'); } }); $ bun bun_server.js $ bun client.js Tips2: for Ubuntu 22.04 NOT both, only one OK Tips1

Slide 18

Slide 18 text

Trivia 2 : Topic support / トピック機能 - from README.md ● ServerWebSocket of Bun supports subscribing topic ○ subscribe(topic) / unsubscribe(topic) ○ publish(topic, message) ● Similar to Rooms of Socket.IO ● → Easy to build multiple channel chat ● Bun の ServerWebSocket では、トピックが 使える ○ subscribe(topic) … トピックの購読 ○ unsubscribe(topic) … トピックの解除 ○ publish(topic, message) … 一斉送信 ● Socket.IOのRoom機能のようなもの ● → 複数のチャネルがあるチャットが簡単に 実装できる Chat Server topic-A topic-B Chat Client 1 Chat Client 2 Chat Client 3 Chat Client 4 Chat Client 5 send() publish()

Slide 19

Slide 19 text

Trivia 3 : Inside of WebSocket Server of Bun / 内側を覗く ● The HTTP server and server-side websockets are based on uWebSockets. (README.md) ● uWebSockets ○ https://github.com/uNetworking/uWebSock ets ○ optimized for speed and memory footprint ● cf. uWebSockets.js ○ https://github.com/uNetworking/uWebSock ets.js ○ seamless integration for Node.js backends ○ 3x fast than ws ● uSockets ○ https://github.com/uNetworking/uSockets ○ Optimized TCP, TLS, QUIC & HTTP3 transports, used by µWebSockets ● Bun HTTP/WebSocketサーバーの実装は uWebSocket (README.mdによる) ● uWebSockets ○ https://github.com/uNetworking/uWebSock ets ○ スピードとメモリ使用量を最適化 ● 参考: uWebSockets.js ○ https://github.com/uNetworking/uWebSock ets.js ○ Node.js用モジュール ○ ws モジュールに比べて3倍速い ● uSockets ○ https://github.com/uNetworking/uSockets ○ µWebSockets 向けの、最適化された TCP, TLS, QUIC&HTTP3 トランスポート実装 QUIC/HTTP3 Server support may be coming ??? Bun's Roadmap #159: https://github.com/oven-sh/bun/issues/159

Slide 20

Slide 20 text

WebAssembly

Slide 21

Slide 21 text

Using WebAssembly module in Bun // simple example to call wasm const fs = require( "fs"); const content = fs.readFileSync( "./func.wasm" ); WebAssembly.compile(content) .then((module) => { const lib = new WebAssembly.Instance(module, { env: {},}).exports; // --- call func --- const ret = lib.func(); console.log(ret); // 42 }) .catch((e) => { console.error( "ERROR:", e) }); console.warn( "--- end ---" ); ● Node.js works well ● Bun process exit before calling wasm function. ○ does not wait for WebAssembly.compile(), ● Node.js ではうまく動く ● Bunのプロセスは、WebAssembly.compile()の完 了を待たずに終了してしまう。そのため wasmモ ジュールの関数を呼び出せない → Issue #1189 Tips(workaround) ● use setTimeout() , or just use await ○ Top level await is enabled in Bun. (even not .mjs) ● setTimeout()で待つか、単に await すればOK

Slide 22

Slide 22 text

Tips for WebAssembly (setTimeout() or await) // use setTimeout() WebAssembly.compile(content) .then((module) => { // ... const ret = lib.func(); console.log(ret); // 42 }) .catch((e) => { console.error( "ERROR:", e); }); // --- wait for WebAssembly.compile() --- setTimeout(function() { console.warn("timeout .."); }, 100); console.warn( "--- end ---" ); // use awit import fs from "fs"; const content = fs.readFileSync( "./func.wasm" ); const module = await WebAssembly.compile(content) .catch((e) => { console.error( "ERROR:", e); }); const lib = new WebAssembly.Instance(module, { env: {}, }).exports; // --- call func --- const ret = lib.func(); console.log(ret); // 42 console.warn( "--- end ---" );

Slide 23

Slide 23 text

Thank you! WebSocket Server/Client ● Trivia ○ ws module works in Client ○ use ServerWebSocket for Server ■ subscirbe/unsubscribe, publish ● Precondition: ○ in case of Bun server - Bun client ● Tips 1 ○ DO NOT use send() in open() handler of server ○ If you want, use with setTimeout() ● Tips 2: in Ubuntu 22.04 only ○ (a) use IPv4 address "127.0.0.1" in client ○ (b) use " host: 'localhost' " in server ■ only one of a/b, NOT both using WebAssembly module - just use await - top level await is available even in .js - or, use setTimeout() A staff of WebRTC Meetup Tokyo