Skip to content
Kavachकवच

HTTP: WebSockets and events

● Intermediate4 minBefore this: HTTP: Serving

A handler can answer with a connection that stays open: a WebSocket, for messages both ways, or an event stream, for news the server sends.

http.websocket(r, session) accepts a WebSocket, then hands the connection to session, on the connection's own task, until it returns. This one answers every message:

use std.http
use std.net

fn chat(r: Request) -> Response {
    http.websocket(r, ws => {
        while true {
            const message = (try ws.receive()) ?? break
            match message {
                WsMessage.Text(text) => try ws.send_text("you said {text}"),
                WsMessage.Binary(data) => try ws.send_bytes(data),
            }
        }
    })
}

fn main() ! Error {
    const listener = try net.listen("127.0.0.1:0")
    const _server = spawn http.serve(listener, chat)
    const ws = try http.websocket_connect("ws://{listener.address()}/chat")
    for word in ["hello", "bye"] {
        try ws.send_text(word)
        match (try ws.receive()) ?? WsMessage.Text(text: "closed") {
            WsMessage.Text(text) => print(text),
            WsMessage.Binary(data) => print("{data.len()} bytes"),
        }
    }
    ws.close()
}

The program ran and printed:

kavach run websocket.kav

you said helloyou said bye
websocket.kav: Kavach code, then what running it prints
  • ws.receive() gives the next message, WsMessage.Text or WsMessage.Binary, or none once the other end closed, so ?? break ends the loop.
  • A request that isn't a WebSocket gets 400.
  • Pings are answered without the program seeing them.
  • One task may receive while other tasks send on the same WebSocket. Sends take turns, in the order they asked, and each writes its whole message, so two messages never mix on the wire. Receiving is for one task at a time.

Events

http.events(r, feed) answers with server-sent events, which a browser reads with EventSource. Each line of the data becomes a data: line:

use std.http
use std.net

fn news(r: Request) -> Response {
    http.events(r, out => {
        try out.send("prices are down", event: "news", id: "1")
        try out.send("shop opens\nat nine", event: "news", id: "2")
    })
}

fn main() ! Error {
    const listener = try net.listen("127.0.0.1:0")
    const _server = spawn http.serve(listener, news)
    const r = try http.get("http://{listener.address()}/news")
    print(r.header("content-type") ?? "")
    print(r.body.text() ?? "")
}

The program ran and printed:

kavach run events.kav

text/event-streamevent: newsid: 1data: prices are down event: newsid: 2data: shop opensdata: at nine
events.kav: Kavach code, then what running it prints

Every function

WrittenGives
http.websocket(r, session: fn(WebSocket) ! NetError) -> ResponseAccepts a WebSocket (RFC 6455): 101 Switching Protocols, then session; 400 for a request that isn't one
http.events(r, feed: fn(EventStream) ! NetError) -> ResponseServer-sent events: 200 with text/event-stream, then feed
http.websocket_connect(url) -> WebSocket ! HttpErrorA client's WebSocket to ws:// or wss:// url
ws.receive() -> WsMessage? ! NetErrorThe next message, WsMessage.Text(text) or WsMessage.Binary(data), or none once the other end closed
ws.send_text(text), ws.send_bytes(data)Sends one message
ws.close()Says goodbye and closes the connection
out.send(data, event: "", id: "")Sends one event; each line of data is a data: line
  • A message larger than max_body_bytes closes the socket with status 1009.
  • A WebSocket or an event stream counts as a connection, not as a request in flight.
  • Over HTTP/2 neither is offered yet: such a request gets 501.