diff --git a/examples/vweb/vweb_websocket/assets/websocket_client.js b/examples/vweb/vweb_websocket/assets/websocket_client.js new file mode 100644 index 0000000000..629d036ac9 --- /dev/null +++ b/examples/vweb/vweb_websocket/assets/websocket_client.js @@ -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 += `
  • > ${message}
  • `; + 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 += `
  • < ${data}
  • `; + setTimeout(() => { + send(`Roger ${i++}`); + }, 3000); +}); \ No newline at end of file diff --git a/examples/vweb/vweb_websocket/index.html b/examples/vweb/vweb_websocket/index.html new file mode 100644 index 0000000000..62b5b6e286 --- /dev/null +++ b/examples/vweb/vweb_websocket/index.html @@ -0,0 +1,11 @@ + + + + +vweb websocket example page + + +
      + + + \ No newline at end of file diff --git a/examples/vweb/vweb_websocket/vweb_websocket.v b/examples/vweb/vweb_websocket/vweb_websocket.v new file mode 100644 index 0000000000..b80ad003bb --- /dev/null +++ b/examples/vweb/vweb_websocket/vweb_websocket.v @@ -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('') +} diff --git a/vlib/net/http/header.v b/vlib/net/http/header.v index 39c2e85c1b..a36b929262 100644 --- a/vlib/net/http/header.v +++ b/vlib/net/http/header.v @@ -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 diff --git a/vlib/net/websocket/websocket_server.v b/vlib/net/websocket/websocket_server.v index d78efd8482..3995f9fee0 100644 --- a/vlib/net/websocket/websocket_server.v +++ b/vlib/net/websocket/websocket_server.v @@ -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 {