Skip to content

Commit

Permalink
examples: add examples/veb/websocket, to show how to use http connect…
Browse files Browse the repository at this point in the history
…ion upgrade to a websocket, from a `veb` route (#22128)
  • Loading branch information
spytheman authored Aug 29, 2024
1 parent 1fbe2a1 commit 01fd719
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 0 deletions.
44 changes: 44 additions & 0 deletions examples/veb/websocket/assets/client.js
Original file line number Diff line number Diff line change
@@ -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 += `<li>received: <b>${event.data}</b></li>`;
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 = '';
}
});
Binary file added examples/veb/websocket/assets/favicon.ico
Binary file not shown.
41 changes: 41 additions & 0 deletions examples/veb/websocket/assets/style.css
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions examples/veb/websocket/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>veb websocket example</title>
<link rel="stylesheet" type="text/css" href="/assets/style.css">
</head>
<body>
<p>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.</p>
<ul id="message-list"></ul>
<input id="message-input" placeholder="Enter your message here" value=""/>
<div>Client status: <span id="message-status">---</span></div>
<script type="text/javascript" src="/assets/client.js"></script>
</body>
</html>
98 changes: 98 additions & 0 deletions examples/veb/websocket/server.v
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 01fd719

Please sign in to comment.