v2 / vlib / veb / sse / sse.v
92 lines · 84 sloc · 2.98 KB · 184883b418c7ec2febd5d4def70259ad81f7bb4b
Raw
1module sse
2
3import veb
4import net
5import strings
6
7// This module implements the server side of `Server Sent Events`.
8// See https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format
9// as well as https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
10// for detailed description of the protocol, and a simple web browser client example.
11//
12// > Event stream format
13// > The event stream is a simple stream of text data which must be encoded using UTF-8.
14// > Messages in the event stream are separated by a pair of newline characters.
15// > A colon as the first character of a line is in essence a comment, and is ignored.
16// > Note: The comment line can be used to prevent connections from timing out;
17// > a server can send a comment periodically to keep the connection alive.
18// >
19// > Each message consists of one or more lines of text listing the fields for that message.
20// > Each field is represented by the field name, followed by a colon, followed by the text
21// > data for that field's value.
22
23@[params]
24pub struct SSEMessage {
25pub mut:
26 id string
27 event string
28 data string
29 retry int
30}
31
32@[heap]
33pub struct SSEConnection {
34pub mut:
35 conn &net.TcpConn @[required]
36}
37
38// start an SSE connection
39pub fn start_connection(mut ctx veb.Context) &SSEConnection {
40 if ctx.conn == unsafe { nil } {
41 eprintln('[veb.sse] WARNING: SSE requires a direct TCP connection (ctx.conn) which is not available. Use `ctx.takeover_conn()` before starting an SSE connection.')
42 return &SSEConnection{
43 conn: unsafe { nil }
44 }
45 }
46 // Build and send HTTP response headers directly.
47 // SSE responses must NOT include Content-Length since data is streamed.
48 mut sb := strings.new_builder(256)
49 sb.write_string('HTTP/1.1 200 OK\r\n')
50 sb.write_string('Content-Type: text/event-stream\r\n')
51 sb.write_string('Connection: keep-alive\r\n')
52 sb.write_string('Cache-Control: no-cache\r\n')
53 sb.write_string('Server: veb\r\n')
54 sb.write_string('\r\n')
55 ctx.conn.write(sb) or {}
56
57 return &SSEConnection{
58 conn: ctx.conn
59 }
60}
61
62// send_message sends a single message to the http client that listens for SSE.
63// It does not close the connection, so you can use it many times in a loop.
64pub fn (mut sse SSEConnection) send_message(message SSEMessage) ! {
65 if sse.conn == unsafe { nil } {
66 return error('SSE connection is not available (no TCP connection)')
67 }
68 mut sb := strings.new_builder(512)
69 if message.id != '' {
70 sb.write_string('id: ${message.id}\n')
71 }
72 if message.event != '' {
73 sb.write_string('event: ${message.event}\n')
74 }
75 if message.data != '' {
76 sb.write_string('data: ${message.data}\n')
77 }
78 if message.retry != 0 {
79 sb.write_string('retry: ${message.retry}\n')
80 }
81 sb.write_string('\n')
82 sse.conn.write(sb)!
83}
84
85// send a 'close' event and close the tcp connection.
86pub fn (mut sse SSEConnection) close() {
87 if sse.conn == unsafe { nil } {
88 return
89 }
90 sse.send_message(event: 'close', data: 'Closing the connection', retry: -1) or {}
91 sse.conn.close() or {}
92}
93