Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Small Tips to use Bun with WebSocket Server and WebAssembly Modules

mganeko
November 25, 2022

Small Tips to use Bun with WebSocket Server and WebAssembly Modules

Bun と WebSocket Server とオマケのWebAssembly
Slide for JSConf.JP 2022 LT

mganeko

November 25, 2022
Tweet

More Decks by mganeko

Other Decks in Technology

Transcript

  1. Small Tips to use Bun with WebSocket Server and WebAssembly

    Modules JSConf.JP 2022 @massie_g / がねこまさし Bun と WebSocket Server とオマケの WebAssembly
  2. 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 英語メインで、一部日本語をミックスしてお話しします
  3. What is Bun? / Bun とは?

  4. 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とは?
  5. 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 [email protected] (WASM ver.) Chakra/ChakraCore Internet Explorer MS Edge Legacy (node-chakracore) QuickJS qjs WasmEdge (wasmedge-quickjs)
  6. 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
  7. 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とは?
  8. WebSocket Server/Client

  9. 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
  10. 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
  11. 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
  12. 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
  13. 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 に書いてある
  14. 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
  15. 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
  16. 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 では不要
  17. 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
  18. 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()
  19. 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
  20. WebAssembly

  21. 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
  22. 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 ---" );
  23. 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