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

    View Slide

  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
    英語メインで、一部日本語をミックスしてお話しします

    View Slide

  3. What is Bun? / Bun とは?

    View Slide

  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とは?

    View Slide

  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 Compute@Edge
    (WASM ver.)
    Chakra/ChakraCore Internet Explorer
    MS Edge Legacy
    (node-chakracore)
    QuickJS qjs WasmEdge
    (wasmedge-quickjs)

    View Slide

  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

    View Slide

  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とは?

    View Slide

  8. WebSocket Server/Client

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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 に書いてある

    View Slide

  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

    View Slide

  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

    View Slide

  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 では不要

    View Slide

  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

    View Slide

  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()

    View Slide

  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

    View Slide

  20. WebAssembly

    View Slide

  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

    View Slide

  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 ---" );

    View Slide

  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

    View Slide