mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
websocket: enable using an already existing connection (from vweb or another http server) (#20103)
This commit is contained in:
parent
db6ae6ee9b
commit
5be5cd9be1
5 changed files with 161 additions and 12 deletions
22
examples/vweb/vweb_websocket/assets/websocket_client.js
Normal file
22
examples/vweb/vweb_websocket/assets/websocket_client.js
Normal 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>> ${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>< ${data}</li>`;
|
||||
setTimeout(() => {
|
||||
send(`Roger ${i++}`);
|
||||
}, 3000);
|
||||
});
|
11
examples/vweb/vweb_websocket/index.html
Normal file
11
examples/vweb/vweb_websocket/index.html
Normal 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>
|
77
examples/vweb/vweb_websocket/vweb_websocket.v
Normal file
77
examples/vweb/vweb_websocket/vweb_websocket.v
Normal 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('')
|
||||
}
|
|
@ -103,6 +103,7 @@ pub enum CommonHeader {
|
|||
sec_fetch_site
|
||||
sec_fetch_user
|
||||
sec_websocket_accept
|
||||
sec_websocket_key
|
||||
server
|
||||
server_timing
|
||||
set_cookie
|
||||
|
@ -209,6 +210,7 @@ pub fn (h CommonHeader) str() string {
|
|||
.sec_fetch_site { 'Sec-Fetch-Site' }
|
||||
.sec_fetch_user { 'Sec-Fetch-User' }
|
||||
.sec_websocket_accept { 'Sec-WebSocket-Accept' }
|
||||
.sec_websocket_key { 'Sec-WebSocket-Key' }
|
||||
.server { 'Server' }
|
||||
.server_timing { 'Server-Timing' }
|
||||
.set_cookie { 'Set-Cookie' }
|
||||
|
@ -314,6 +316,7 @@ const common_header_map = {
|
|||
'sec-fetch-site': .sec_fetch_site
|
||||
'sec-fetch-user': .sec_fetch_user
|
||||
'sec-websocket-accept': .sec_websocket_accept
|
||||
'sec_websocket_key': .sec_websocket_key
|
||||
'server': .server
|
||||
'server-timing': .server_timing
|
||||
'set-cookie': .set_cookie
|
||||
|
|
|
@ -134,24 +134,60 @@ fn (mut s Server) serve_client(mut c Client) ! {
|
|||
c.logger.debug('server-> End serve client (${c.id})')
|
||||
}
|
||||
mut handshake_response, mut server_client := s.handle_server_handshake(mut c)!
|
||||
accept := s.send_connect_event(mut server_client)!
|
||||
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)
|
||||
s.attach_client(mut server_client, handshake_response)!
|
||||
c.listen() or {
|
||||
s.logger.error(err.msg())
|
||||
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
|
||||
fn (mut s Server) setup_callbacks(mut sc ServerClient) {
|
||||
if s.message_callbacks.len > 0 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue