diff --git a/examples/veb/websocket/assets/client.js b/examples/veb/websocket/assets/client.js
new file mode 100644
index 00000000000000..cc2b05877da183
--- /dev/null
+++ b/examples/veb/websocket/assets/client.js
@@ -0,0 +1,44 @@
+const messageList = document.getElementById('message-list');
+const messageInput = document.getElementById('message-input');
+const messageStatus = document.getElementById('message-status');
+const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
+//
+let status = '---';
+function set_status(s) { status = s; }
+function show_status() {
+ messageStatus.innerHTML = `${status}, ${socket.readyState}`;
+ requestAnimationFrame(show_status);
+}
+requestAnimationFrame(show_status);
+//
+var socket = start_reconnecting_socket(1000, `${protocol}://${location.host}/ws`);
+function start_reconnecting_socket(timeout, target) {
+ var nsocket = new WebSocket(target);
+ nsocket.addEventListener('open', (event) => { console.log('Connected to WS server'); set_status('connected'); });
+ nsocket.addEventListener('message', (event) => {
+ messageList.innerHTML += `
received: ${event.data} `;
+ set_status('received message');
+ });
+ nsocket.addEventListener('close', (event) => {
+ console.log('on close');
+ set_status('connection closed');
+ setTimeout(()=>{
+ console.log('Try reconnecting ...')
+ socket = start_reconnecting_socket(timeout, target);
+ }, timeout);
+ });
+ nsocket.addEventListener('error', (event) => { console.log('on error'); set_status('error'); });
+ return nsocket;
+}
+
+//
+function send(message) {
+ socket.send(message);
+ set_status('sending message');
+}
+messageInput.addEventListener('keyup', ({key}) => {
+ if (key === 'Enter') {
+ send(messageInput.value);
+ messageInput.value = '';
+ }
+});
diff --git a/examples/veb/websocket/assets/favicon.ico b/examples/veb/websocket/assets/favicon.ico
new file mode 100644
index 00000000000000..e4f68ade5ac711
Binary files /dev/null and b/examples/veb/websocket/assets/favicon.ico differ
diff --git a/examples/veb/websocket/assets/style.css b/examples/veb/websocket/assets/style.css
new file mode 100644
index 00000000000000..177863b2f884f5
--- /dev/null
+++ b/examples/veb/websocket/assets/style.css
@@ -0,0 +1,41 @@
+html {
+ box-sizing: border-box;
+}
+*, *:before, *:after {
+ box-sizing: inherit;
+}
+body {
+ padding: 0px;
+ margin: 0px;
+ margin-left: 30px;
+ margin-right: 30px;
+}
+
+#message-list {
+ width: 100%;
+ height: 250px;
+ padding: 5px;
+ box-sizing: border-box;
+ margin: 0px;
+ margin-bottom: 5px;
+ border: 1px solid lightgray;
+ overflow-y: scroll;
+ overscroll-behavior-y: contain;
+ scroll-snap-type: y proximity;
+}
+#message-list > li {
+ list-style-type: none;
+}
+#message-list > li:last-child {
+ scroll-snap-align: end;
+ border-top: 1px solid #eeeeff !important;
+}
+
+#message-input {
+ width: 100%;
+ margin: 0px;
+ padding: 0px;
+ font-size: 24px;
+ line-height: 1;
+ font-weight: 600;
+}
diff --git a/examples/veb/websocket/index.html b/examples/veb/websocket/index.html
new file mode 100644
index 00000000000000..a24b4c6aa939da
--- /dev/null
+++ b/examples/veb/websocket/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+ veb websocket example
+
+
+
+ This is an example of using V's `veb` and `net.websocket` modules in
+ the same app, where the websocket connection happens on the /ws route of
+ your veb app.
+
+
+ Client status: ---
+
+
+
diff --git a/examples/veb/websocket/server.v b/examples/veb/websocket/server.v
new file mode 100644
index 00000000000000..f6eafefe776544
--- /dev/null
+++ b/examples/veb/websocket/server.v
@@ -0,0 +1,98 @@
+module main
+
+import os
+import veb
+import log
+import time
+import term
+import net
+import net.http
+import net.websocket
+
+const app_port = 8990
+
+fn main() {
+ mut app := &App{
+ wss: new_websocker_server()!
+ }
+ app.mount_static_folder_at(os.resource_abs_path('assets'), '/assets')!
+ app.serve_static('/favicon.ico', os.resource_abs_path('assets/favicon.ico'))!
+ veb.run[App, Context](mut app, app_port)
+}
+
+pub struct Context {
+ veb.Context
+}
+
+pub struct App {
+ veb.StaticHandler
+mut:
+ wss &websocket.Server
+}
+
+pub fn (mut app App) index(mut ctx Context) veb.Result {
+ return $veb.html()
+}
+
+pub fn (mut app App) ws(mut ctx Context) veb.Result {
+ key := ctx.get_header(http.CommonHeader.sec_websocket_key) or { '' }
+ if key == '' {
+ ctx.error('Invalid websocket handshake. Key is missing.')
+ return ctx.redirect('/')
+ }
+ dump(ctx.req.cookie('token') or { http.Cookie{} }.value)
+ wlog('> transferring connection with key: ${key}, to the websocket server ${voidptr(app.wss)} ...')
+ ctx.takeover_conn()
+ ctx.conn.set_write_timeout(time.infinite)
+ ctx.conn.set_read_timeout(time.infinite)
+ spawn fn (mut wss websocket.Server, mut connection net.TcpConn, key string) {
+ wss.handle_handshake(mut connection, key) or { wlog('handle_handshake error: ${err}') }
+ wlog('>> wss.handle_handshake finished, key: ${key}')
+ }(mut app.wss, mut ctx.conn, key)
+ wlog('> done transferring connection')
+ return veb.no_result()
+}
+
+fn slog(message string) {
+ eprintln(term.colorize(term.bright_yellow, message))
+}
+
+fn wlog(message string) {
+ eprintln(term.colorize(term.bright_blue, message))
+}
+
+fn new_websocker_server() !&websocket.Server {
+ mut logger := &log.Log{}
+ logger.set_level(.info)
+ mut wss := websocket.new_server(.ip, app_port, '', logger: logger)
+ wss.set_ping_interval(100)
+ wss.on_connect(fn [mut logger] (mut server_client websocket.ServerClient) !bool {
+ server_client.client.logger = logger
+ slog('wss.on_connect client.id: ${server_client.client.id} | server_client.client_key: ${server_client.client_key}')
+ return true
+ })!
+ wss.on_close(fn (mut client websocket.Client, code int, reason string) ! {
+ slog('wss.on_close client.id: ${client.id} | code: ${code}, reason: ${reason}')
+ })
+ wss.on_message(fn [mut wss] (mut client websocket.Client, msg &websocket.Message) ! {
+ slog('wss.on_message client.id: ${client.id} | msg.opcode: ${msg.opcode} | msg.payload: `${msg.payload.bytestr()}`')
+ text := '${client.id} says: "${msg.payload.bytestr()}"'
+ // client.write_string(text) or { slog('client.write err: ${err}') return err }
+ for i, _ in rlock wss.server_state {
+ wss.server_state.clients
+ } {
+ mut c := rlock wss.server_state {
+ wss.server_state.clients[i] or { continue }
+ }
+ if c.client.get_state() == .open {
+ c.client.write_string(text) or {
+ slog('error while broadcasting to i: ${i}, ${voidptr(c)}, err: ${err}')
+ continue
+ }
+ }
+ }
+ })
+
+ slog('Websocket Server initialized, wss: ${voidptr(wss)}')
+ return wss
+}