// 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()
}
})
}///|
#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()
})
}///|
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)///|
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)type Conn#as_free_fn
async fn Conn::connect(url : String, headers? : Map[CaseInsensitiveString, String], proxy? : Client) -> Connlet ws = Client::connect("ws://example.com/endpoint")#as_free_fn
async fn Conn::from_http_client(conn : Client, path : StringView, extra_headers? : Map[CaseInsensitiveString, String]) -> ConnInstall
Download zipAsynchronous programming library for MoonBit