#WebSocket API for MoonBit Async Library

    This module provides RFC 6455 compilant WebSocket client & server support for moonbitlanga/async.

    #Features

    • WebSocket server with support for integration into a normal HTTP server
    • WebSocket client with support for TLS and proxy

    #Quick Start

    #WebSocket server

    // A simple WebSocket echo server that listen on path `/ws`,
    // and reply clients with whatever they send.

    ///|
    #cfg(any(target="native", target="wasm"))
    async fn websocket_echo_server(addr : @socket.Addr) -> Unit {
    let server = @http.Server(addr)
    // WebSocket server are built on top of a HTTP server.
    // This allows mixing WebSocket and HTTP in a single server.
    server.run_forever((request, _, conn) => {
    guard request.path is "/ws" else { conn.send_response(404, "NotFound") }
    let ws = @websocket.from_http_server(request, conn)
    defer ws.close()
    for ;; {
    // Receive a new message using `ws.recv()`
    // WebSocket message can be very long,
    // so `ws.recv()` does not read the full message into memory.
    // Instead, it returns a `@websocket.Message` type,
    // whose content can be read lazily via `@io.Reader`.
    let msg = ws.recv()
    // Reply the client with the message we just received.
    // `msg.kind` is either `Text` (UTF-8 encoded text message)
    // or `Binary` (plain binary message).
    //
    // Sending message can also be performed lazily.
    // `.start_message(message_kind)` begins a new message,
    // after which message content can be written lazily to the WebSocket tunnel.
    // After all message content is written,
    // `.end_message()` should be called to terminate the message.
    // Fragmentation of message will be performed automatically.
    ws..start_message(msg.kind)..write_reader(msg).end_message()
    }
    })
    }

    #WebSocket client

    ///|
    #cfg(any(target="native", target="wasm"))
    async test "WebSocket client example" {
    @async.with_task_group(group => {
    let port = 42080
    let addr = @socket.Addr::parse("127.0.0.1:\{port}")
    group.spawn_bg(no_wait=true, () => websocket_echo_server(addr))

    // Create a new client via `@websocket.connect`.
    // TLs encrypted WebSocket tunnel can be created via `wss://` URL.
    let ws = @websocket.connect("ws://localhost:\{port}/ws")
    defer ws.close()
    // convenient helper for sending small text message
    ws.send_text("abcd")
    inspect(ws.recv().read_all().text(), content="abcd")
    // gracefully terminate the WebSocket tunnel
    ws.send_close()
    })
    }

    #API Reference

    #Conn

    The type @websocket.Conn represents a WebSocket tunnel. It can be created in the following three ways:

    • @websocket.connect(url : String, headers?, proxy?): connect to url via HTTP, perform WebSocket handshake and establish a new tunnel. If the url starts with ws://, the tunnel will be unencrypted, and handshake is performed via plain HTTP. If the url starts with wss://, a TLS encrypted tunnel will be created, and handshake is performed via HTTPS. The optional argument headers can be used to specify addition headers in the handshake. The optional argument proxy can be used to specify a HTTP(s) proxy for the tunnel.
    • @websocket.from_http_client(client : @http.Client, path: String): similar to @websocket.connect, but create the WebSocket tunnel using an existing http client.
    • @websocket.from_http_server(request : @http.Request, conn : @http.ServerConnection): process a WebSocket handshake request request from a client, and upgrade the HTTP server connection conn to a WebSocket tunnel.

    After successfully establishing a WebSocket tunnel, the following API can be used to do things with the tunnel:

    • recv() -> Message: receive a new message from the tunnel. The kind of the message can be retrieved via msg.kind. recv() will not actually receive the content of the message, the content of the message should be obtained by reading from the Message type.
    • send_text(text : StringView): send a new text message
    • send_binary(data : BytesView): send a new binary message
    • start_message(kind : MessageKind): start a new message of kind kind. The content of the new message can be written to the WebSocket tunnel via API in @io.Writer
    • end_message(): terminate current message created via start_message(), flush remaining content of message to the server
    • send_close(): gracefully terminate the WebSocket tunnel by sending a CLOSE frame. This method should only be used when no network or protocol error occurs.
    • close(): dispose a WebSocket tunnel, release related resource. The underlying HTTP connection will be closed as well.
    • impl @io.Writer for Conn: write content to the message currently being sent, created via .start_message()
    • ping(): actively send a WebSocket PING message and wait for corresponding PONG reply. .ping() can be called inside another message being sent. However, .ping() must be called when there is another task receiving message from the tunnel in parallel, otherwise the PONG reply can not be fetched, and .ping() will hang forever.

    #Message

    A single message received from a WebSocket tunnel.

    • field kind: the kind of the message
    • impl @io.Reader for Message: read content of the message. For example, content of the whole message can be obtained via .read_all()

    #CloseCode

    WebSocket close code.

    ///|
    pub(all) enum CloseCode {
    Normal // 1000
    GoingAway // 1001
    ProtocolError // 1002
    UnsupportedData // 1003
    Abnormal // 1006
    InvalidFramePayload // 1007
    PolicyViolation // 1008
    MessageTooBig // 1009
    MissingExtension // 1010
    InternalError // 1011
    Other(UInt16)
    } derive(Debug, Eq)

    #Error Types

    #WebSocketError

    ///|
    suberror WebSocketError {
    // Connection was closed with specific code.
    // with an optional message for close reason.
    ConnectionClosed(CloseCode, String?)
    InvalidHandshake(String) // Handshake failed with detailed reason
    ProtocolError // Malformed frame
    } derive(Debug)

    WebSocketError

    pub suberror WebSocketError {
    ConnectionClosed(CloseCode, String?)
    InvalidHandshake(String)
    HandshakeRejected(String,
    Response
    )
    ProtocolError(String)
    } derive(
    Debug
    )

    CloseCode

    pub(all) enum CloseCode {
    Normal
    GoingAway
    ProtocolError
    UnsupportedData
    Abnormal
    InvalidFramePayload
    PolicyViolation
    MessageTooBig
    MissingExtension
    InternalError
    Other(UInt16)
    } derive(Eq,
    Debug
    )

    WebSocket close status codes
    impl Show for CloseCode

    Conn

    type Conn

    A WebSocket connection
    impl Writer for Conn

    Conn::close

    fn Conn::close(self : Conn) -> Unit

    Close a WebSocket connection. The underlying HTTP connection will be closed as well.

    Conn::connect

    #as_free_fn
    async fn Conn::connect(url : String, headers? : Map[
    CaseInsensitiveString
    , String], proxy? :
    Client
    ) -> Conn

    Connect to a WebSocket server via the given URL. The protocol of the URL must be either ws (for unencrypted WebSocket connection) or wss (for TLS encrypted WebSocket connection).

    Extra headers during the WebSocket handshake can be specified via extra_headers. Some headers are reserved for the WebSocket protocol and must NOT be set in extra_headers:

    • those headers set by @http.Client, see @http.Client::new
    • Connection, Upgrade
    • Sec-WebSocket-Key, Sec-WebSocket,Version

    If proxy is present, the websocket client will tunnel traffic through the proxy. See @http.Client::new for more details.

    Example:
    let ws = Client::connect("ws://example.com/endpoint")

    Conn::end_message

    async fn Conn::end_message(self : Conn) -> Unit

    End the message currently being sent, flush all buffered data and tell the server the termination of current message.

    end_message must be called after the start_message.

    Conn::from_http_client

    #as_free_fn
    async fn Conn::from_http_client(conn :
    Client
    , path : StringView, extra_headers? : Map[
    CaseInsensitiveString
    , String]) -> Conn

    Create a WebSocket tunnel from an existing HTTP client. The HTTP client must be in a clean state (i.e. not in the middle of a request). A WebSocket handshake will be sent to path via the HTTP client. If the handshake succeeds, a WebSocket tunnel will be established and returned.

    The ownership of the HTTP client will be transferred to this function. So caller must not use the client anymore, nor close it.

    Extra headers during the WebSocket handshake can be specified via extra_headers. Some headers are reserved for the WebSocket protocol and must NOT be set in extra_headers:

    • those headers set by @http.Client, see @http.Client::new
    • Connection, Upgrade
    • Sec-WebSocket-Key, Sec-WebSocket,Version

    Conn::from_http_server

    Handle a WebSocket handshake request and convert an existing HTTP server connection to a WebSocket tunnel.

    • request: the WebSocket handshake request
    • conn: the HTTP server connection

    If the handshake succeeds, a new WebSocket tunnel will be created and returned.

    The ownership of conn will be transferred to @websocket.ServerConnection::from_http, so the user must NOT use or close the HTTP server connection anymore.

    Conn::ping

    async fn Conn::ping(self : Conn, msg? : BytesView) -> Unit

    Send a PING frame to the peer, and wait for PONG reply. If msg is provided, its content will become the body of the PING message. Otherwise, ping() automatically generate random bytes as message body.

    The PONG reply may not come immediately. In particular, it may arrive after several other messages already on the wire. To avoid data loss and race condition, .ping() itself will not wait for the PONG reply directly. User must receive data from the same WebSocket connection somewhere else, in order to get the PONG reply of a PING request. Calling .ping() without another task running .recv() on parallel WILL RESULT IN DEAD LOCK.

    If a PING request with the same message body is still waiting for reply, .ping() will fail immediately with error.

    Conn::recv

    async fn Conn::recv(self : Conn) -> Message

    Conn::send_binary

    async fn Conn::send_binary(self : Conn, data : BytesView) -> Unit

    Convenient helper for sending a single binary message. To send large message lazily, see start_message.

    Conn::send_close

    async fn Conn::send_close(self : Conn, code? : CloseCode, reason? : String) -> Unit

    Initiating the connection closing process of WebSocket. Note that send_close merely performs closing at WebSocket protocol level, so:

    • Conn::close should be called anyway, even if send_close is called
    • Conn::send_close should only be called when everything goes well. For example it should NOT be called when protocol error or network error occurs.

    Conn::send_text

    async fn Conn::send_text(self : Conn, text : StringView) -> Unit

    Convenient helper for sending a single text message. To send large message lazily, see start_message.

    Conn::start_message

    fn Conn::start_message(self : Conn, kind : MessageKind) -> Unit

    Start sending a new message to the server. The content of the message can be sent by using the self as a @io.Writer after calling start_message. end_message must be explicitly called to terminate the message.

    Writing message content is buffered. To ensure immediate delivery of data, split them into multiple messages, and use end_message to ensure data is actually sent to the server.

    start_message must NOT be called before the last message ends.

    Conn::write

    async fn Conn::write(self : Conn, data : &
    Data
    ) -> Unit

    Conn::write_once

    async fn Conn::write_once(self : Conn, buf : Bytes, offset~ : Int, len~ : Int) -> Int

    Conn::write_reader

    async fn Conn::write_reader(self : Conn, reader : &
    Reader
    ) -> Unit

    Message

    pub struct Message {
    kind : MessageKind
    // private fields
    }

    A message received from a WebSocket tunnel
    impl Reader for Message

    Message::drop

    async fn Message::drop(self : Message, len : Int) -> Int

    Message::read

    async fn Message::read(self : Message, dst : FixedArray[Byte], offset? : Int, max_len? : Int) -> Int

    Message::read_all

    async fn Message::read_all(self : Message) -> &
    Data

    Message::read_exactly

    async fn Message::read_exactly(self : Message, len : Int) -> Bytes

    Message::read_some

    async fn Message::read_some(self : Message, max_len? : Int) -> Bytes?

    Message::read_until

    async fn Message::read_until(self : Message, sep : StringView) -> String?

    MessageKind

    pub(all) enum MessageKind {
    Binary
    Text
    } derive(
    Debug
    )

    WebSocket message
    impl Show for MessageKind

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io