+
+
+
diff --git a/examples/vweb/server_sent_events/server.v b/examples/vweb/server_sent_events/server.v
new file mode 100644
index 0000000000..e5216a837d
--- /dev/null
+++ b/examples/vweb/server_sent_events/server.v
@@ -0,0 +1,38 @@
+module main
+
+import rand
+import time
+import vweb
+import vweb.sse
+
+struct App {
+ vweb.Context
+}
+
+fn main() {
+ vweb.run(8081)
+}
+
+pub fn (mut app App) init_once() {
+ app.serve_static('/favicon.ico', 'favicon.ico', 'img/x-icon')
+ app.handle_static('.')
+}
+
+pub fn (mut app App) index() vweb.Result {
+ title := 'SSE Example'
+ return $vweb.html()
+}
+
+fn (mut app App) sse() vweb.Result {
+ mut session := sse.new_connection(app.conn)
+ // NB: you can setup session.write_timeout and session.headers here
+ session.start() or { return app.server_error(501) }
+ session.send_message(data: 'ok') or { return app.server_error(501) }
+ for {
+ data := '{"time": "$time.now().str()", "random_id": "$rand.ulid()"}'
+ session.send_message(event: 'ping', data: data) or { return app.server_error(501) }
+ println('> sent event: $data')
+ time.sleep_ms(1000)
+ }
+ return app.server_error(501)
+}
diff --git a/vlib/vweb/sse/sse.v b/vlib/vweb/sse/sse.v
new file mode 100644
index 0000000000..82cd50a04d
--- /dev/null
+++ b/vlib/vweb/sse/sse.v
@@ -0,0 +1,77 @@
+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.
+
+[ref_only]
+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
+}
+
+pub fn new_connection(conn &net.TcpConn) SSEConnection {
+ return SSEConnection{
+ 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)
+ start_sb.write('HTTP/1.1 200')
+ start_sb.write('\r\nConnection: keep-alive')
+ start_sb.write('\r\nCache-Control: no-cache')
+ start_sb.write('\r\nContent-Type: text/event-stream')
+ for k, v in sse.headers {
+ start_sb.write('\r\n$k: $v')
+ }
+ start_sb.write('\r\n')
+ 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 != '' {
+ sb.write('id: $message.id\n')
+ }
+ if message.event != '' {
+ sb.write('event: $message.event\n')
+ }
+ if message.data != '' {
+ sb.write('data: $message.data\n')
+ }
+ if message.retry != 0 {
+ sb.write('retry: $message.retry\n')
+ }
+ sb.write('\n')
+ sse.conn.write(sb.buf) ?
+}
diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v
index e84dcd1b56..f25451194b 100644
--- a/vlib/vweb/vweb.v
+++ b/vlib/vweb/vweb.v
@@ -158,6 +158,17 @@ pub fn (mut ctx Context) ok(s string) Result {
return Result{}
}
+pub fn (mut ctx Context) server_error(ecode int) Result {
+ $if debug {
+ eprintln('> ctx.server_error ecode: $ecode')
+ }
+ if ctx.done {
+ return Result{}
+ }
+ send_string(mut ctx.conn, vweb.http_500) or { }
+ return Result{}
+}
+
pub fn (mut ctx Context) redirect(url string) Result {
if ctx.done {
return Result{}
@@ -304,7 +315,7 @@ fn handle_conn(mut conn net.TcpConn, mut app T) {
page_gen_start := time.ticks()
first_line := reader.read_line() or {
$if debug {
- eprintln('Failed first_line') // show this only in debug mode, because it always would be shown after a chromium user visits the site
+ eprintln('Failed first_line') // show this only in debug mode, because it always would be shown after a chromium user visits the site
}
return
}
@@ -592,6 +603,7 @@ fn handle_conn(mut conn net.TcpConn, mut app T) {
// call action method
if method.args.len == vars.len {
app.$method(vars)
+ return
} else {
eprintln('warning: uneven parameters count ($method.args.len) in `$method.name`, compared to the vweb route `$method.attrs` ($vars.len)')
}