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
ws.receive()gives the next message,WsMessage.TextorWsMessage.Binary, ornoneonce the other end closed, so?? breakends the loop.- A request that isn't a WebSocket gets 400.
- Pings are answered without the program seeing them.
- One task may
receivewhile 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
Every function
| Written | Gives |
|---|---|
http.websocket(r, session: fn(WebSocket) ! NetError) -> Response | Accepts a WebSocket (RFC 6455): 101 Switching Protocols, then session; 400 for a request that isn't one |
http.events(r, feed: fn(EventStream) ! NetError) -> Response | Server-sent events: 200 with text/event-stream, then feed |
http.websocket_connect(url) -> WebSocket ! HttpError | A client's WebSocket to ws:// or wss:// url |
ws.receive() -> WsMessage? ! NetError | The 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_bytescloses 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.