2021-02-03 16:40:06 +03:00
|
|
|
module sse
|
|
|
|
|
|
|
|
import net
|
|
|
|
import time
|
|
|
|
import strings
|
|
|
|
|
|
|
|
// This module implements the server side of `Server Sent Events`.
|
|
|
|
// See https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format
|
|
|
|
// as well as https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
|
|
|
|
// for detailed description of the protocol, and a simple web browser client example.
|
|
|
|
//
|
|
|
|
// > Event stream format
|
|
|
|
// > The event stream is a simple stream of text data which must be encoded using UTF-8.
|
|
|
|
// > Messages in the event stream are separated by a pair of newline characters.
|
|
|
|
// > A colon as the first character of a line is in essence a comment, and is ignored.
|
|
|
|
// > Note: The comment line can be used to prevent connections from timing out;
|
|
|
|
// > a server can send a comment periodically to keep the connection alive.
|
|
|
|
// >
|
|
|
|
// > Each message consists of one or more lines of text listing the fields for that message.
|
|
|
|
// > Each field is represented by the field name, followed by a colon, followed by the text
|
|
|
|
// > data for that field's value.
|
|
|
|
|
2021-02-13 17:52:01 +03:00
|
|
|
[heap]
|
2021-02-03 16:40:06 +03:00
|
|
|
pub struct SSEConnection {
|
|
|
|
pub mut:
|
|
|
|
headers map[string]string
|
|
|
|
conn &net.TcpConn
|
|
|
|
write_timeout time.Duration = 600 * time.second
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct SSEMessage {
|
|
|
|
id string
|
|
|
|
event string
|
|
|
|
data string
|
|
|
|
retry int
|
|
|
|
}
|
|
|
|
|
2021-02-13 02:47:37 +03:00
|
|
|
pub fn new_connection(conn &net.TcpConn) &SSEConnection {
|
|
|
|
return &SSEConnection{
|
2021-02-03 16:40:06 +03:00
|
|
|
conn: conn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sse_start is used to send the start of a Server Side Event response.
|
|
|
|
pub fn (mut sse SSEConnection) start() ? {
|
|
|
|
sse.conn.set_write_timeout(sse.write_timeout)
|
|
|
|
mut start_sb := strings.new_builder(512)
|
2021-02-22 14:18:11 +03:00
|
|
|
start_sb.write_string('HTTP/1.1 200')
|
|
|
|
start_sb.write_string('\r\nConnection: keep-alive')
|
|
|
|
start_sb.write_string('\r\nCache-Control: no-cache')
|
|
|
|
start_sb.write_string('\r\nContent-Type: text/event-stream')
|
2021-02-03 16:40:06 +03:00
|
|
|
for k, v in sse.headers {
|
2021-02-22 14:18:11 +03:00
|
|
|
start_sb.write_string('\r\n$k: $v')
|
2021-02-03 16:40:06 +03:00
|
|
|
}
|
2021-02-22 14:18:11 +03:00
|
|
|
start_sb.write_string('\r\n')
|
2021-02-03 16:40:06 +03:00
|
|
|
sse.conn.write(start_sb.buf) or { return error('could not start sse response') }
|
|
|
|
}
|
|
|
|
|
|
|
|
// send_message sends a single message to the http client that listens for SSE.
|
|
|
|
// It does not close the connection, so you can use it many times in a loop.
|
|
|
|
pub fn (mut sse SSEConnection) send_message(message SSEMessage) ? {
|
|
|
|
mut sb := strings.new_builder(512)
|
|
|
|
if message.id != '' {
|
2021-02-22 14:18:11 +03:00
|
|
|
sb.write_string('id: $message.id\n')
|
2021-02-03 16:40:06 +03:00
|
|
|
}
|
|
|
|
if message.event != '' {
|
2021-02-22 14:18:11 +03:00
|
|
|
sb.write_string('event: $message.event\n')
|
2021-02-03 16:40:06 +03:00
|
|
|
}
|
|
|
|
if message.data != '' {
|
2021-02-22 14:18:11 +03:00
|
|
|
sb.write_string('data: $message.data\n')
|
2021-02-03 16:40:06 +03:00
|
|
|
}
|
|
|
|
if message.retry != 0 {
|
2021-02-22 14:18:11 +03:00
|
|
|
sb.write_string('retry: $message.retry\n')
|
2021-02-03 16:40:06 +03:00
|
|
|
}
|
2021-02-22 14:18:11 +03:00
|
|
|
sb.write_string('\n')
|
2021-02-03 16:40:06 +03:00
|
|
|
sse.conn.write(sb.buf) ?
|
|
|
|
}
|