websocket: enable using an already existing connection (from vweb or another http server) (#20103)

This commit is contained in:
el-gringo 2023-12-19 13:16:07 +01:00 committed by GitHub
parent db6ae6ee9b
commit 5be5cd9be1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 161 additions and 12 deletions

View file

@ -0,0 +1,22 @@
const messageList = document.getElementById('message-list');
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
const socket = new WebSocket(`${protocol}://${location.host}/ws`);
let i = 0;
function send(message) {
messageList.innerHTML += `<li>&gt; ${message}</li>`;
socket.send(message);
}
socket.addEventListener("open", (event) => {
console.log('Connected to WS server');
send('Hey everyone !');
});
socket.addEventListener("message", (event) => {
const { data } = event;
messageList.innerHTML += `<li>&lt; ${data}</li>`;
setTimeout(() => {
send(`Roger ${i++}`);
}, 3000);
});

View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>vweb websocket example page</title>
</head>
<body>
<ol id="message-list"></ol>
<script type="text/javascript" src="websocket_client.js"></script>
</body>
</html>

View file

@ -0,0 +1,77 @@
module main
import log
import net.http
import net.websocket
import term
import vweb
const http_port = 8080
struct App {
vweb.Context
mut:
wss &websocket.Server @[vweb_global]
}
fn slog(message string) {
eprintln(term.colorize(term.bright_yellow, message))
}
fn clog(message string) {
eprintln(term.colorize(term.cyan, message))
}
fn wlog(message string) {
eprintln(term.colorize(term.bright_blue, message))
}
fn main() {
mut app := new_app() or { panic(err) }
vweb.run(app, http_port)
}
fn new_app() !&App {
mut app := &App{
wss: new_websocker_server()!
}
app.handle_static('assets', true)
return app
}
fn new_websocker_server() !&websocket.Server {
mut wss := &websocket.Server{
logger: &log.Log{
level: .debug
}
}
wss.on_connect(fn (mut server_client websocket.ServerClient) !bool {
slog('ws.on_connect, server_client.client_key: ${server_client.client_key}')
return true
})!
wss.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ! {
slog('s.on_message msg.opcode: ${msg.opcode} | msg.payload: ${msg.payload}')
ws.write(msg.payload, msg.opcode) or {
eprintln('ws.write err: ${err}')
return err
}
})
wss.on_close(fn (mut ws websocket.Client, code int, reason string) ! {
slog('s.on_close code: ${code}, reason: ${reason}')
})
slog('Websocket Server initialized')
return wss
}
pub fn (mut app App) index() vweb.Result {
return $vweb.html()
}
pub fn (mut app App) ws() !vweb.Result {
key := app.req.header.get(http.CommonHeader.sec_websocket_key)!
app.wss.handle_handshake(mut app.conn, key) or {
wlog('handle_handshake error: ${err.msg()}')
return err
}
return app.text('')
}

View file

@ -103,6 +103,7 @@ pub enum CommonHeader {
sec_fetch_site sec_fetch_site
sec_fetch_user sec_fetch_user
sec_websocket_accept sec_websocket_accept
sec_websocket_key
server server
server_timing server_timing
set_cookie set_cookie
@ -209,6 +210,7 @@ pub fn (h CommonHeader) str() string {
.sec_fetch_site { 'Sec-Fetch-Site' } .sec_fetch_site { 'Sec-Fetch-Site' }
.sec_fetch_user { 'Sec-Fetch-User' } .sec_fetch_user { 'Sec-Fetch-User' }
.sec_websocket_accept { 'Sec-WebSocket-Accept' } .sec_websocket_accept { 'Sec-WebSocket-Accept' }
.sec_websocket_key { 'Sec-WebSocket-Key' }
.server { 'Server' } .server { 'Server' }
.server_timing { 'Server-Timing' } .server_timing { 'Server-Timing' }
.set_cookie { 'Set-Cookie' } .set_cookie { 'Set-Cookie' }
@ -314,6 +316,7 @@ const common_header_map = {
'sec-fetch-site': .sec_fetch_site 'sec-fetch-site': .sec_fetch_site
'sec-fetch-user': .sec_fetch_user 'sec-fetch-user': .sec_fetch_user
'sec-websocket-accept': .sec_websocket_accept 'sec-websocket-accept': .sec_websocket_accept
'sec_websocket_key': .sec_websocket_key
'server': .server 'server': .server
'server-timing': .server_timing 'server-timing': .server_timing
'set-cookie': .set_cookie 'set-cookie': .set_cookie

View file

@ -134,24 +134,60 @@ fn (mut s Server) serve_client(mut c Client) ! {
c.logger.debug('server-> End serve client (${c.id})') c.logger.debug('server-> End serve client (${c.id})')
} }
mut handshake_response, mut server_client := s.handle_server_handshake(mut c)! mut handshake_response, mut server_client := s.handle_server_handshake(mut c)!
accept := s.send_connect_event(mut server_client)! s.attach_client(mut server_client, handshake_response)!
if !accept {
s.logger.debug('server-> client not accepted')
c.shutdown_socket()!
return
}
// the client is accepted
c.socket_write(handshake_response.bytes())!
lock s.server_state {
s.server_state.clients[server_client.client.id] = server_client
}
s.setup_callbacks(mut server_client)
c.listen() or { c.listen() or {
s.logger.error(err.msg()) s.logger.error(err.msg())
return err return err
} }
} }
// handle_handshake use an existing connection to respond to the handshake for a given key
pub fn (mut s Server) handle_handshake(mut conn net.TcpConn, key string) !&ServerClient {
mut c := &Client{
is_server: true
conn: conn
is_ssl: false
logger: &log.Log{
level: .debug
}
client_state: ClientState{
state: .open
}
last_pong_ut: time.now().unix
id: rand.uuid_v4()
}
mut server_client := &ServerClient{
resource_name: 'GET'
client_key: key
client: unsafe { c }
server: unsafe { &s }
}
digest := create_key_challenge_response(key)!
handshake_response := 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${digest}\r\n\r\n'
s.attach_client(mut server_client, handshake_response)!
spawn s.handle_ping()
c.listen() or {
s.logger.error(err.msg())
return err
}
return server_client
}
fn (mut s Server) attach_client(mut server_client ServerClient, handshake_response string) ! {
accept := s.send_connect_event(mut server_client)!
if !accept {
s.logger.debug('server-> client not accepted')
server_client.client.shutdown_socket()!
return
}
// the client is accepted
server_client.client.socket_write(handshake_response.bytes())!
lock s.server_state {
s.server_state.clients[server_client.client.id] = unsafe { server_client }
}
s.setup_callbacks(mut server_client)
}
// setup_callbacks initialize all callback functions // setup_callbacks initialize all callback functions
fn (mut s Server) setup_callbacks(mut sc ServerClient) { fn (mut s Server) setup_callbacks(mut sc ServerClient) {
if s.message_callbacks.len > 0 { if s.message_callbacks.len > 0 {