WebSockets
Two-way messages between a client and a server.
On this page
A WebSocket keeps one connection open so the client and the server can both send messages at any time. Both sides use the same http.WebSocket type.
Accept WebSockets on a server#
A handler upgrades the request and passes a function that runs on the connection. The connection closes when that function returns:
use valk.http
fn handle(req: http.Request) http.Response {
if req.path == "/chat" {
return http.WebSocket.upgrade(req, fn(ws: http.WebSocket) {
while true {
let msg = ws.read() ! break // fails with `closed` once the client is gone
ws.write("echo: " + msg.data) ! break
}
})
}
return http.Response.html("<script>new WebSocket('ws://' + location.host + '/chat')</script>")
}
fn main() {
let server = http.Server.new("127.0.0.1", 9000, handle)
server.start() ! return
}
A request that is not a WebSocket upgrade gets a 400. A fast handler uses res.send_websocket(ctx, fn) instead.
Connect as a client#
use valk.http
fn main() {
let ws = http.WebSocket.connect("ws://127.0.0.1:9000/chat") ! {
println("Could not connect")
return
}
ws.write("hello") ! return
let reply = ws.read() ! return
println(reply.data) // echo: hello
ws.close() ! return
}
connect takes ws:// and wss:// URLs, and an http.Options for extra headers, TLS settings and timeouts.
Messages#
| Method | Does |
|---|---|
read() |
Waits for the next whole message; msg.data, msg.is_text |
write(text) |
Sends a text message (UTF-8) |
write_binary(data) |
Sends a binary message |
ping() |
Sends a ping; pings from the other side are answered by read |
close(code, reason) |
Runs the closing handshake |
set_timeouts(read_ms, write_ms) |
Socket timeouts; 0 waits forever |
set_cancel(token) |
Makes a waiting read or write fail with cancelled |
After a close from either side, read and write throw closed, and close_code / close_reason say why (1006 when the connection dropped). max_message_size (16 MB) limits one message.
Read and write at the same time#
A read and a write may run on different coroutines, so one waits for messages while another pushes updates:
use valk.http
use valk.time
fn push_time(ws: http.WebSocket) {
while !ws.is_closed {
ws.write("tick") ! return
time.sleep_ms(1000)
}
}
fn handle(req: http.Request) http.Response {
return http.WebSocket.upgrade(req, fn(ws: http.WebSocket) {
co push_time(ws)
while true {
let msg = ws.read() ! break
println("client said: " + msg.data)
}
})
}
fn main() {
http.serve("127.0.0.1", 9000, handle) ! return
}
Send one message to many clients#
Prepare a message once with http.WebSocketFrame.text(data) or .binary(data) and send it with write_frame. A frame can be shared between threads:
use valk.http
fn broadcast(sockets: Array[http.WebSocket], text: String) {
let frame = http.WebSocketFrame.text(text)
each sockets as ws {
ws.write_frame(frame) ! continue
}
}
Subprotocols, headers and compression#
upgrade(req, fn, protocol: "chat.v2") accepts a subprotocol the client offered, and ws.protocol tells both sides what was agreed. headers: adds fields to the 101 response, such as cookies.
Messages can be compressed (permessage-deflate). The client offers it by default; a server accepts it when server.websocket_compression = true, or per endpoint with upgrade(req, fn, true). Turn the client's offer off with http.Options { websocket_compression: false }.
WebSockets work over HTTP/1.1 only, not over HTTP/2. Shutting down the server closes open WebSockets.
