From d12f5f7ba0599a38bdb0a7370907d0f5dd2bda44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Hellstr=C3=B6m?= Date: Fri, 4 Dec 2020 01:52:26 +0100 Subject: [PATCH] x.websocket: vdoc (#7091) --- vlib/x/websocket/events.v | 69 ++++++++-------- vlib/x/websocket/handshake.v | 16 ++-- vlib/x/websocket/io.v | 10 +-- vlib/x/websocket/message.v | 63 +++++++-------- vlib/x/websocket/uri.v | 12 +-- vlib/x/websocket/utils.v | 6 +- vlib/x/websocket/websocket_client.v | 118 ++++++++++++---------------- vlib/x/websocket/websocket_server.v | 53 ++++++------- vlib/x/websocket/websocket_test.v | 15 ++-- 9 files changed, 169 insertions(+), 193 deletions(-) diff --git a/vlib/x/websocket/events.v b/vlib/x/websocket/events.v index 3994309962..8674ac7e13 100644 --- a/vlib/x/websocket/events.v +++ b/vlib/x/websocket/events.v @@ -1,35 +1,35 @@ module websocket -// Represents a callback on a new message +// MessageEventHandler represents a callback on a new message struct MessageEventHandler { - handler SocketMessageFn // Callback function - handler2 SocketMessageFn2 // Callback function with reference - is_ref bool // Has a reference object - ref voidptr // The referenced object + handler SocketMessageFn // callback function + handler2 SocketMessageFn2 // callback function with reference + is_ref bool // true if has a reference object + ref voidptr // referenced object } -// Represents a callback on error +// ErrorEventHandler represents a callback on error struct ErrorEventHandler { - handler SocketErrorFn // Callback function - handler2 SocketErrorFn2 // Callback function with reference - is_ref bool // Has a reference object - ref voidptr // The referenced object + handler SocketErrorFn // callback function + handler2 SocketErrorFn2 // callback function with reference + is_ref bool // true if has a reference object + ref voidptr // referenced object } -// Represents a callback when connection is opened +// OpenEventHandler represents a callback when connection is opened struct OpenEventHandler { - handler SocketOpenFn // Callback function - handler2 SocketOpenFn2 // Callback function with reference - is_ref bool // Has a reference object - ref voidptr // The referenced object + handler SocketOpenFn // callback function + handler2 SocketOpenFn2 // callback function with reference + is_ref bool // true if has a reference object + ref voidptr // referenced object } -// Represents a callback on a closing event +// CloseEventHandler represents a callback on a closing event struct CloseEventHandler { - handler SocketCloseFn - handler2 SocketCloseFn2 - is_ref bool - ref voidptr + handler SocketCloseFn // callback function + handler2 SocketCloseFn2 // callback function with reference + is_ref bool // true if has a reference object + ref voidptr // referenced object } pub type AcceptClientFn = fn (mut c ServerClient) ?bool @@ -50,7 +50,7 @@ pub type SocketCloseFn = fn (mut c Client, code int, reason string) ? pub type SocketCloseFn2 = fn (mut c Client, code int, reason string, v voidptr) ? -// on_connect register callback when client connects to the server +// on_connect registers a callback when client connects to the server pub fn (mut s Server) on_connect(fun AcceptClientFn) ? { if s.accept_client_callbacks.len > 0 { return error('only one callback can be registered for accept client') @@ -58,14 +58,14 @@ pub fn (mut s Server) on_connect(fun AcceptClientFn) ? { s.accept_client_callbacks << fun } -// on_message, register a callback on new messages +// on_message registers a callback on new messages pub fn (mut s Server) on_message(fun SocketMessageFn) { s.message_callbacks << MessageEventHandler{ handler: fun } } -// on_message_ref, register a callback on new messages and provide a reference +// on_message_ref registers a callback on new messages and provides a reference object pub fn (mut s Server) on_message_ref(fun SocketMessageFn2, ref voidptr) { s.message_callbacks << MessageEventHandler{ handler2: fun @@ -74,14 +74,14 @@ pub fn (mut s Server) on_message_ref(fun SocketMessageFn2, ref voidptr) { } } -// on_close, register a callback on closed socket +// on_close registers a callback on closed socket pub fn (mut s Server) on_close(fun SocketCloseFn) { s.close_callbacks << CloseEventHandler{ handler: fun } } -// on_close_ref, register a callback on closed socket and provide a reference +// on_close_ref registers a callback on closed socket and provides a reference object pub fn (mut s Server) on_close_ref(fun SocketCloseFn2, ref voidptr) { s.close_callbacks << CloseEventHandler{ handler2: fun @@ -90,14 +90,14 @@ pub fn (mut s Server) on_close_ref(fun SocketCloseFn2, ref voidptr) { } } -// on_message, register a callback on new messages +// on_message registers a callback on new messages pub fn (mut ws Client) on_message(fun SocketMessageFn) { ws.message_callbacks << MessageEventHandler{ handler: fun } } -// on_message_ref, register a callback on new messages and provide a reference +// on_message_ref registers a callback on new messages and provides a reference object pub fn (mut ws Client) on_message_ref(fun SocketMessageFn2, ref voidptr) { ws.message_callbacks << MessageEventHandler{ handler2: fun @@ -106,14 +106,14 @@ pub fn (mut ws Client) on_message_ref(fun SocketMessageFn2, ref voidptr) { } } -// on_error, register a callback on errors +// on_error registers a callback on errors pub fn (mut ws Client) on_error(fun SocketErrorFn) { ws.error_callbacks << ErrorEventHandler{ handler: fun } } -// on_error_ref, register a callback on errors and provida a reference +// on_error_ref registers a callback on errors and provides a reference object pub fn (mut ws Client) on_error_ref(fun SocketErrorFn2, ref voidptr) { ws.error_callbacks << ErrorEventHandler{ handler2: fun @@ -122,14 +122,15 @@ pub fn (mut ws Client) on_error_ref(fun SocketErrorFn2, ref voidptr) { } } -// on_open, register a callback on successful open +// on_open registers a callback on successful opening the websocket pub fn (mut ws Client) on_open(fun SocketOpenFn) { ws.open_callbacks << OpenEventHandler{ handler: fun } } -// on_open_ref, register a callback on successful open and provide a reference +// on_open_ref registers a callback on successful opening the websocket +// and provides a reference object pub fn (mut ws Client) on_open_ref(fun SocketOpenFn2, ref voidptr) { ws.open_callbacks << OpenEventHandler{ handler2: fun @@ -138,14 +139,14 @@ pub fn (mut ws Client) on_open_ref(fun SocketOpenFn2, ref voidptr) { } } -// on_close, register a callback on closed socket +// on_close registers a callback on closed socket pub fn (mut ws Client) on_close(fun SocketCloseFn) { ws.close_callbacks << CloseEventHandler{ handler: fun } } -// on_close_ref, register a callback on closed socket and provide a reference +// on_close_ref registers a callback on closed socket and provides a reference object pub fn (mut ws Client) on_close_ref(fun SocketCloseFn2, ref voidptr) { ws.close_callbacks << CloseEventHandler{ handler2: fun @@ -154,7 +155,7 @@ pub fn (mut ws Client) on_close_ref(fun SocketCloseFn2, ref voidptr) { } } -// send_connect_event, invokes the on_connect callback +// send_connect_event invokes the on_connect callback fn (mut s Server) send_connect_event(mut c ServerClient) ?bool { if s.accept_client_callbacks.len == 0 { // If no callback all client will be accepted diff --git a/vlib/x/websocket/handshake.v b/vlib/x/websocket/handshake.v index f9ac605850..9eda706459 100644 --- a/vlib/x/websocket/handshake.v +++ b/vlib/x/websocket/handshake.v @@ -3,13 +3,11 @@ module websocket import encoding.base64 import strings -// handshake manage the websocket handshake process +// handshake manages the websocket handshake process fn (mut ws Client) handshake() ? { nonce := get_nonce(ws.nonce_size) seckey := base64.encode(nonce) - // handshake := 'GET $ws.uri.resource$ws.uri.querystring HTTP/1.1\r\nHost: $ws.uri.hostname:$ws.uri.port\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: $seckey\r\nSec-WebSocket-Version: 13\r\n\r\n' mut sb := strings.new_builder(1024) - // todo, remove when autofree defer { sb.free() } @@ -35,7 +33,7 @@ fn (mut ws Client) handshake() ? { unsafe {handshake_bytes.free()} } -// handle_server_handshake manage websocket server handshake +// handle_server_handshake manages websocket server handshake process fn (mut s Server) handle_server_handshake(mut c Client) ?(string, &ServerClient) { msg := c.read_handshake_str() ? handshake_response, client := s.parse_client_handshake(msg, mut c) ? @@ -43,7 +41,7 @@ fn (mut s Server) handle_server_handshake(mut c Client) ?(string, &ServerClient) return handshake_response, client } -// parse_client_handshake parses handshake result +// parse_client_handshake parses result from handshake process fn (mut s Server) parse_client_handshake(client_handshake string, mut c Client) ?(string, &ServerClient) { s.logger.debug('server-> client handshake:\n$client_handshake') lines := client_handshake.split_into_lines() @@ -82,7 +80,7 @@ fn (mut s Server) parse_client_handshake(client_handshake string, mut c Client) flags << .has_accept } else { - // We ignore other headers like protocol for now + // we ignore other headers like protocol for now } } unsafe {keys.free()} @@ -107,7 +105,7 @@ fn (mut s Server) parse_client_handshake(client_handshake string, mut c Client) return server_handshake, server_client } -// / read_handshake_str returns the handshake response +// read_handshake_str returns the handshake response fn (mut ws Client) read_handshake_str() ?string { mut total_bytes_read := 0 mut msg := [1024]byte{} @@ -128,7 +126,7 @@ fn (mut ws Client) read_handshake_str() ?string { return res } -// read_handshake reads the handshake and check if valid +// read_handshake reads the handshake result and check if valid fn (mut ws Client) read_handshake(seckey string) ? { mut msg := ws.read_handshake_str() ? ws.check_handshake_response(msg, seckey) ? @@ -136,7 +134,7 @@ fn (mut ws Client) read_handshake(seckey string) ? { } // check_handshake_response checks the response from handshake and returns -// the response and secure key +// the response and secure key provided by the websocket client fn (mut ws Client) check_handshake_response(handshake_response string, seckey string) ? { ws.debug_log('handshake response:\n$handshake_response') lines := handshake_response.split_into_lines() diff --git a/vlib/x/websocket/io.v b/vlib/x/websocket/io.v index ca81099762..225cbb7e8c 100644 --- a/vlib/x/websocket/io.v +++ b/vlib/x/websocket/io.v @@ -4,7 +4,7 @@ import net import time import sync -// socket_read reads into the provided buffer with its length +// socket_read reads from socket into the provided buffer fn (mut ws Client) socket_read(mut buffer []byte) ?int { lock { if ws.state in [.closed, .closing] || ws.conn.sock.handle <= 1 { @@ -27,7 +27,7 @@ fn (mut ws Client) socket_read(mut buffer []byte) ?int { } } -// socket_read reads into the provided byte pointer and length +// socket_read reads from socket into the provided byte pointer and length fn (mut ws Client) socket_read_ptr(buf_ptr byteptr, len int) ?int { lock { if ws.state in [.closed, .closing] || ws.conn.sock.handle <= 1 { @@ -50,7 +50,7 @@ fn (mut ws Client) socket_read_ptr(buf_ptr byteptr, len int) ?int { } } -// socket_write, writes the whole byte array provided to the socket +// socket_write writes the provided byte array to the socket fn (mut ws Client) socket_write(bytes []byte) ? { lock { if ws.state == .closed || ws.conn.sock.handle <= 1 { @@ -73,7 +73,7 @@ fn (mut ws Client) socket_write(bytes []byte) ? { } } -// shutdown_socket, shut down socket properly closing the connection +// shutdown_socket shuts down the socket properly when connection is closed fn (mut ws Client) shutdown_socket() ? { ws.debug_log('shutting down socket') if ws.is_ssl { @@ -84,7 +84,7 @@ fn (mut ws Client) shutdown_socket() ? { return none } -// dial_socket, setup socket communication, options and timeouts +// dial_socket connects tcp socket and initializes default configurations fn (mut ws Client) dial_socket() ?net.TcpConn { mut t := net.dial_tcp('$ws.uri.hostname:$ws.uri.port') ? optval := int(1) diff --git a/vlib/x/websocket/message.v b/vlib/x/websocket/message.v index b3badffa6d..9eb075eecc 100644 --- a/vlib/x/websocket/message.v +++ b/vlib/x/websocket/message.v @@ -3,40 +3,38 @@ module websocket import encoding.utf8 const ( - header_len_offset = 2 // Offset for lenghtpart of websocket header - buffer_size = 256 // Default buffer size - extended_payload16_end_byte = 4 // Offset for extended lenght 16 bit of websocket header - extended_payload64_end_byte = 10 // Offset for extended lenght 64 bit of websocket header + header_len_offset = 2 // offset for lengthpart of websocket header + buffer_size = 256 // default buffer size + extended_payload16_end_byte = 4 // header length with 16-bit extended payload + extended_payload64_end_byte = 10 // header length with 64-bit extended payload ) -// A websocket data fragment +// Fragment represents a websocket data fragment struct Fragment { - data []byte // The included data payload data in a fragment - opcode OPCode // Defines the interpretation of the payload data + data []byte // included data payload data in a fragment + opcode OPCode // interpretation of the payload data } -// Represents a data frame header +// Frame represents a data frame header struct Frame { mut: - header_len int = 2 - // Lenght of the websocket header part - frame_size int = 2 - // Size of the total frame - fin bool // Indicates if final fragment of message - rsv1 bool // Reserved for future use in websocket RFC - rsv2 bool // Reserved for future use in websocket RFC - rsv3 bool // Reserved for future use in websocket RFC - opcode OPCode // Defines the interpretation of the payload data - has_mask bool // Defines whether the payload data is masked. - payload_len int // Payload lenght - masking_key [4]byte // All frames from client to server is masked with this key + header_len int = 2 // length of the websocket header part + frame_size int = 2 // size of total frame + fin bool // true if final fragment of message + rsv1 bool // reserved for future use in websocket RFC + rsv2 bool // reserved for future use in websocket RFC + rsv3 bool // reserved for future use in websocket RFC + opcode OPCode // interpretation of the payload data + has_mask bool // true if the payload data is masked + payload_len int // payload length + masking_key [4]byte // all frames from client to server is masked with this key } const ( - invalid_close_codes = [999, 1004, 1005, 1006, 1014, 1015, 1016, 1100, 2000, 2999, 5000, 65536] // List of invalid close codes + invalid_close_codes = [999, 1004, 1005, 1006, 1014, 1015, 1016, 1100, 2000, 2999, 5000, 65536] ) -// validate_client, validate client frame rules from RFC6455 +// validate_client validates client frame rules from RFC6455 pub fn (mut ws Client) validate_frame(frame &Frame) ? { if frame.rsv1 || frame.rsv2 || frame.rsv3 { ws.close(1002, 'rsv cannot be other than 0, not negotiated') @@ -48,7 +46,7 @@ pub fn (mut ws Client) validate_frame(frame &Frame) ? { return error('use of reserved opcode') } if frame.has_mask && !ws.is_server { - // Server should never send masked frames + // server should never send masked frames // to client, close connection ws.close(1002, 'client got masked frame') ? return error('client sent masked frame') @@ -70,17 +68,17 @@ pub fn (mut ws Client) validate_frame(frame &Frame) ? { } } -// is_control_frame returns true if the fram is a control frame +// is_control_frame returns true if the frame is a control frame fn is_control_frame(opcode OPCode) bool { return opcode !in [.text_frame, .binary_frame, .continuation] } -// is_data_frame returns true if the fram is a control frame +// is_data_frame returns true if the frame is a control frame fn is_data_frame(opcode OPCode) bool { return opcode in [.text_frame, .binary_frame] } -// read_payload, reads the payload from the socket +// read_payload reads the message payload from the socket fn (mut ws Client) read_payload(frame &Frame) ?[]byte { if frame.payload_len == 0 { return []byte{} @@ -107,7 +105,7 @@ fn (mut ws Client) read_payload(frame &Frame) ?[]byte { return buffer } -// validate_utf_8, validates payload for valid utf8 encoding +// validate_utf_8 validates payload for valid utf8 encoding // - Future implementation needs to support fail fast utf errors for strict autobahn conformance fn (mut ws Client) validate_utf_8(opcode OPCode, payload []byte) ? { if opcode in [.text_frame, .close] && !utf8.validate(payload.data, payload.len) { @@ -122,8 +120,6 @@ fn (mut ws Client) validate_utf_8(opcode OPCode, payload []byte) ? { pub fn (mut ws Client) read_next_message() ?Message { for { frame := ws.parse_frame_header() ? - // This debug message leaks so remove if needed - // ws.debug_log('read_next_message: frame\n$frame') ws.validate_frame(&frame) ? frame_payload := ws.read_payload(&frame) ? if is_control_frame(frame.opcode) { @@ -136,7 +132,7 @@ pub fn (mut ws Client) read_next_message() ?Message { unsafe {frame_payload.free()} return msg } - // If the message is fragmented we just put it on fragments + // if the message is fragmented we just put it on fragments // a fragment is allowed to have zero size payload if !frame.fin { ws.fragments << &Fragment{ @@ -181,7 +177,7 @@ pub fn (mut ws Client) read_next_message() ?Message { } } -// payload_from_fragments, returs the whole paylaod from fragmented message +// payload_from_fragments returs the whole paylaod from fragmented message fn (ws Client) payload_from_fragments(fin_payload []byte) ?[]byte { mut total_size := 0 for f in ws.fragments { @@ -218,7 +214,7 @@ pub fn (mut ws Client) parse_frame_header() ?Frame { for ws.state == .open { read_bytes := ws.socket_read_ptr(byteptr(rbuff), 1) ? if read_bytes == 0 { - // This is probably a timeout or close + // this is probably a timeout or close continue } buffer[bytes_read] = rbuff[0] @@ -242,7 +238,7 @@ pub fn (mut ws Client) parse_frame_header() ?Frame { header_len_offset + 12 } else { 0 - } // Impossible + } // impossible } frame.payload_len = frame.payload_len frame.frame_size = frame.header_len + frame.payload_len @@ -275,7 +271,6 @@ pub fn (mut ws Client) parse_frame_header() ?Frame { break } } - // We have a mask and we read the whole mask data if frame.has_mask && bytes_read == mask_end_byte { frame.masking_key[0] = buffer[mask_end_byte - 4] frame.masking_key[1] = buffer[mask_end_byte - 3] diff --git a/vlib/x/websocket/uri.v b/vlib/x/websocket/uri.v index 992009395b..7d388e17d5 100644 --- a/vlib/x/websocket/uri.v +++ b/vlib/x/websocket/uri.v @@ -1,13 +1,13 @@ module websocket -// Represents an Uri for websocket connections +// Uri represents an Uri for websocket connections struct Uri { mut: - url string // The url to the websocket endpoint - hostname string // The hostname to the websocket endpoint - port string // The port to the websocket endpoint - resource string // The resource used on the websocket endpoint - querystring string // The query string on the websocket endpoint + url string // url to the websocket endpoint + hostname string // hostname of the websocket endpoint + port string // port of the websocket endpoint + resource string // resource of the websocket endpoint + querystring string // query string of the websocket endpoint } // str returns the string representation of the Uri diff --git a/vlib/x/websocket/utils.v b/vlib/x/websocket/utils.v index 439cce03dc..d16822a24a 100644 --- a/vlib/x/websocket/utils.v +++ b/vlib/x/websocket/utils.v @@ -4,7 +4,7 @@ import rand import crypto.sha1 import encoding.base64 -// htonl64 converts payload lenght to header bits +// htonl64 converts payload length to header bits fn htonl64(payload_len u64) []byte { mut ret := []byte{len: 8} ret[0] = byte(((payload_len & (u64(0xff) << 56)) >> 56) & 0xff) @@ -18,7 +18,7 @@ fn htonl64(payload_len u64) []byte { return ret } -// create_masking_key returs a new masking key byte array +// create_masking_key returs a new masking key to use when masking websocket messages fn create_masking_key() []byte { mask_bit := byte(rand.intn(255)) buf := []byte{len: 4, init: `0`} @@ -43,7 +43,7 @@ fn create_key_challenge_response(seckey string) ?string { return b64 } -// get_nonce, returns a randomized array used in handshake process +// get_nonce creates a randomized array used in handshake process fn get_nonce(nonce_size int) string { mut nonce := []byte{len: nonce_size, cap: nonce_size} alphanum := '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz' diff --git a/vlib/x/websocket/websocket_client.v b/vlib/x/websocket/websocket_client.v index e6c26363e7..1ba7705531 100644 --- a/vlib/x/websocket/websocket_client.v +++ b/vlib/x/websocket/websocket_client.v @@ -1,10 +1,5 @@ -// The websocket client implements the websocket capabilities -// it is a refactor of the original V-websocket client class -// from @thecoderr. -// There are quite a few manual memory management free() going on -// int the code. This will be refactored once the memory management -// is done. For now there are no leaks on message levels. Please -// check with valgrind if you do any changes in the free calls +// websocket module implements websocket client and a websocket server +// attribution: @thecoderr the author of original websocket client module websocket import net @@ -16,44 +11,42 @@ import sync import rand const ( - empty_bytearr = []byte{} + empty_bytearr = []byte{} // used as empty response to avoid allocation ) -// Client represents websocket client state +// Client represents websocket client pub struct Client { is_server bool mut: - ssl_conn &openssl.SSLConn // Secure connection used when wss is used - flags []Flag // Flags - fragments []Fragment // Current fragments - message_callbacks []MessageEventHandler // All callbacks on_message - error_callbacks []ErrorEventHandler // All callbacks on_error - open_callbacks []OpenEventHandler // All callbacks on_open - close_callbacks []CloseEventHandler // All callbacks on_close + ssl_conn &openssl.SSLConn // secure connection used when wss is used + flags []Flag // flags used in handshake + fragments []Fragment // current fragments + message_callbacks []MessageEventHandler // all callbacks on_message + error_callbacks []ErrorEventHandler // all callbacks on_error + open_callbacks []OpenEventHandler // all callbacks on_open + close_callbacks []CloseEventHandler // all callbacks on_close pub: - is_ssl bool // True if secure socket is used - uri Uri // Uri of current connection - id string // Unique id of client + is_ssl bool // true if secure socket is used + uri Uri // uri of current connection + id string // unique id of client pub mut: - conn net.TcpConn // Underlying TCP connection - nonce_size int = 16 - // you can try 18 too - panic_on_callback bool // Set to true of callbacks can panic - state State // Current state of connection - logger &log.Log // Logger used to log messages - resource_name string // Name of current resource - last_pong_ut u64 // Last time in unix time we got a pong message + conn net.TcpConn // underlying TCP socket connection + nonce_size int = 16 // size of nounce used for masking + panic_on_callback bool // set to true of callbacks can panic + state State // current state of connection + logger &log.Log // logger used to log messages + resource_name string // name of current resource + last_pong_ut u64 // last time in unix time we got a pong message } -// Flag of websocket handshake +// Flag represents different types of headers in websocket handshake enum Flag { - has_accept + has_accept // Webs has_connection has_upgrade } -// State of the websocket connection. -// Messages should be sent only on state .open +// State represents the state of the websocket connection. enum State { connecting = 0 open @@ -61,14 +54,14 @@ enum State { closed } -// Message, represents a whole message conbined from 1 to n frames +// Message represents a whole message combined from 1 to n frames pub struct Message { pub: - opcode OPCode - payload []byte + opcode OPCode // websocket frame type of this message + payload []byte // payload of the message } -// OPCode, the supported websocket frame types +// OPCode represents the supported websocket frame types pub enum OPCode { continuation = 0x00 text_frame = 0x01 @@ -78,7 +71,7 @@ pub enum OPCode { pong = 0x0A } -// new_client, instance a new websocket client +// new_client instance a new websocket client pub fn new_client(address string) ?&Client { uri := parse_uri(address) ? return &Client{ @@ -94,7 +87,7 @@ pub fn new_client(address string) ?&Client { } } -// connect, connects and do handshake procedure with remote server +// connect connects to remote websocket server pub fn (mut ws Client) connect() ? { ws.assert_not_connected() ? ws.set_state(.connecting) @@ -109,7 +102,7 @@ pub fn (mut ws Client) connect() ? { ws.send_open_event() } -// listen, listens to incoming messages and handles them +// listen listens and processes incoming messages pub fn (mut ws Client) listen() ? { ws.logger.info('Starting client listener, server($ws.is_server)...') defer { @@ -130,7 +123,7 @@ pub fn (mut ws Client) listen() ? { if ws.state in [.closed, .closing] { return } - ws.debug_log('got message: $msg.opcode') // , payload: $msg.payload') leaks + ws.debug_log('got message: $msg.opcode') match msg.opcode { .text_frame { ws.debug_log('read: text') @@ -210,33 +203,28 @@ pub fn (mut ws Client) listen() ? { } } -// manage_clean_close closes connection in a clean way +// manage_clean_close closes connection in a clean websocket way fn (mut ws Client) manage_clean_close() { ws.send_close_event(1000, 'closed by client') } -// ping, sends ping message to server, -// ping response will be pushed to message callback +// ping sends ping message to server pub fn (mut ws Client) ping() ? { ws.send_control_frame(.ping, 'PING', []) ? } -// pong, sends pong message to server, -// pongs are normally automatically sent back to server +// pong sends pong message to server, pub fn (mut ws Client) pong() ? { ws.send_control_frame(.pong, 'PONG', []) ? } -// write_ptr, writes len bytes provided a byteptr with a websocket messagetype +// write_ptr writes len bytes provided a byteptr with a websocket messagetype pub fn (mut ws Client) write_ptr(bytes byteptr, payload_len int, code OPCode) ? { - // Temporary, printing bytes are leaking - ws.debug_log('write code: $code') - // ws.debug_log('write code: $code, payload: $bytes') + ws.debug_log('write_ptr code: $code') if ws.state != .open || ws.conn.sock.handle < 1 { - // send error here later + // todo: send error here later return error('trying to write on a closed socket!') } - // payload_len := bytes.len mut header_len := 2 + if payload_len > 125 { 2 } else { 0 } + if payload_len > 0xffff { 6 } else { 0 } if !ws.is_server { header_len += 4 @@ -251,14 +239,13 @@ pub fn (mut ws Client) write_ptr(bytes byteptr, payload_len int, code OPCode) ? if ws.is_server { if payload_len <= 125 { header[1] = byte(payload_len) - // 0x80 } else if payload_len > 125 && payload_len <= 0xffff { len16 := C.htons(payload_len) header[1] = 126 unsafe {C.memcpy(&header[2], &len16, 2)} } else if payload_len > 0xffff && payload_len <= 0xffffffffffffffff { len_bytes := htonl64(u64(payload_len)) - header[1] = 127 // 0x80 + header[1] = 127 unsafe {C.memcpy(&header[2], len_bytes.data, 8)} } } else { @@ -276,7 +263,7 @@ pub fn (mut ws Client) write_ptr(bytes byteptr, payload_len int, code OPCode) ? header[5] = masking_key[1] header[6] = masking_key[2] header[7] = masking_key[3] - } else if payload_len > 0xffff && payload_len <= 0xffffffffffffffff { // 65535 && 18446744073709551615 + } else if payload_len > 0xffff && payload_len <= 0xffffffffffffffff { len64 := htonl64(u64(payload_len)) header[1] = (127 | 0x80) unsafe {C.memcpy(&header[2], len64.data, 8)} @@ -303,7 +290,6 @@ pub fn (mut ws Client) write_ptr(bytes byteptr, payload_len int, code OPCode) ? } } ws.socket_write(frame_buf) ? - // Temporary hack until memory management is done unsafe { frame_buf.free() masking_key.free() @@ -311,17 +297,17 @@ pub fn (mut ws Client) write_ptr(bytes byteptr, payload_len int, code OPCode) ? } } -// write, writes a byte array with a websocket messagetype +// write writes a byte array with a websocket messagetype to socket pub fn (mut ws Client) write(bytes []byte, code OPCode) ? { ws.write_ptr(byteptr(bytes.data), bytes.len, code) ? } -// write_str, writes a string with a websocket texttype +// write_str, writes a string with a websocket texttype to socket pub fn (mut ws Client) write_str(str string) ? { ws.write_ptr(str.str, str.len, .text_frame) } -// close, closes the websocket connection +// close closes the websocket connection pub fn (mut ws Client) close(code int, message string) ? { ws.debug_log('sending close, $code, $message') if ws.state in [.closed, .closing] || ws.conn.sock.handle <= 1 { @@ -356,15 +342,15 @@ pub fn (mut ws Client) close(code int, message string) ? { ws.fragments = [] } -// send_control_frame, sends a control frame to the server +// send_control_frame sends a control frame to the server fn (mut ws Client) send_control_frame(code OPCode, frame_typ string, payload []byte) ? { - ws.debug_log('send control frame $code, frame_type: $frame_typ') // , payload: $payload') + ws.debug_log('send control frame $code, frame_type: $frame_typ') if ws.state !in [.open, .closing] && ws.conn.sock.handle > 1 { return error('socket is not connected') } header_len := if ws.is_server { 2 } else { 6 } frame_len := header_len + payload.len - mut control_frame := []byte{len: frame_len} // [`0`].repeat(frame_len) + mut control_frame := []byte{len: frame_len} mut masking_key := if !ws.is_server { create_masking_key() } else { empty_bytearr } defer { unsafe { @@ -416,7 +402,7 @@ fn (mut ws Client) send_control_frame(code OPCode, frame_typ string, payload []b } } -// parse_uri, parses the url string to it's components +// parse_uri parses the url to a Uri fn parse_uri(url string) ?&Uri { u := urllib.parse(url) ? v := u.request_uri().split('?') @@ -440,7 +426,7 @@ fn parse_uri(url string) ?&Uri { } } -// set_state sets current state in a thread safe way +// set_state sets current state of the websocket connection fn (mut ws Client) set_state(state State) { lock { ws.state = state @@ -457,7 +443,7 @@ fn (ws Client) assert_not_connected() ? { } } -// reset_state, resets the websocket and can connect again +// reset_state resets the websocket and initialize default settings fn (mut ws Client) reset_state() { lock { ws.state = .closed @@ -467,7 +453,7 @@ fn (mut ws Client) reset_state() { } } -// debug_log makes debug logging output different depending if client or server +// debug_log handles debug logging output for client and server fn (mut ws Client) debug_log(text string) { if ws.is_server { ws.logger.debug('server-> $text') @@ -476,12 +462,12 @@ fn (mut ws Client) debug_log(text string) { } } -// free, manual free memory of Message struct +// free handles manual free memory of Message struct pub fn (m &Message) free() { unsafe {m.payload.free()} } -// free, manual free memory of Client struct +// free handles manual free memory of Client struct pub fn (c &Client) free() { unsafe { c.flags.free() diff --git a/vlib/x/websocket/websocket_server.v b/vlib/x/websocket/websocket_server.v index 5c11cf54e3..7680c9d878 100644 --- a/vlib/x/websocket/websocket_server.v +++ b/vlib/x/websocket/websocket_server.v @@ -1,4 +1,3 @@ -// The module websocket implements the websocket server capabilities module websocket import net @@ -8,35 +7,34 @@ import sync import time import rand -// Server holds state of websocket server connection +// Server represents a websocket server connection pub struct Server { mut: - clients map[string]&ServerClient // Clients connected to this server - logger &log.Log // Logger used to log - ls net.TcpListener // TCpLister used to get incoming connection to socket - accept_client_callbacks []AcceptClientFn // Accept client callback functions - message_callbacks []MessageEventHandler // New message callback functions - close_callbacks []CloseEventHandler // Close message callback functions + clients map[string]&ServerClient // clients connected to this server + logger &log.Log // logger used to log + ls net.TcpListener // listener used to get incoming connection to socket + accept_client_callbacks []AcceptClientFn // accept client callback functions + message_callbacks []MessageEventHandler // new message callback functions + close_callbacks []CloseEventHandler // close message callback functions pub: - port int // Port used as listen to incoming connections - is_ssl bool // True if secure connection (not supported yet on server) + port int // port used as listen to incoming connections + is_ssl bool // true if secure connection (not supported yet on server) pub mut: - ping_interval int = 30 - // Interval for automatic sending ping to connected clients in seconds - state State // Current state of connection + ping_interval int = 30 // interval for sending ping to clients (seconds) + state State // current state of connection } -// ServerClient has state of connected clients +// ServerClient represents a connected client struct ServerClient { pub: - resource_name string // The resource that the client access - client_key string // Unique key of client + resource_name string // resource that the client access + client_key string // unique key of client pub mut: - server &Server // The server instance - client &Client // The client instance + server &Server + client &Client } -// new_server instance new websocket server on port and route +// new_server instance a new websocket server on provided port and route pub fn new_server(port int, route string) &Server { return &Server{ port: port @@ -52,7 +50,7 @@ pub fn (mut s Server) set_ping_interval(seconds int) { s.ping_interval = seconds } -// listen, start listen to incoming connections +// listen start listen and process to incoming connections from websocket clients pub fn (mut s Server) listen() ? { s.logger.info('websocket server: start listen on port $s.port') s.ls = net.listen_tcp(s.port) ? @@ -67,8 +65,9 @@ pub fn (mut s Server) listen() ? { s.logger.info('websocket server: end listen on port $s.port') } -// Close server (not implemented) +// Close closes server (not implemented yet) fn (mut s Server) close() { + // TODO: implement close when moving to net from x.net } // handle_ping sends ping to all clients every set interval @@ -95,7 +94,7 @@ fn (mut s Server) handle_ping() { } } } - // TODO replace for with s.clients.delete_all(clients_to_remove) if (https://github.com/vlang/v/pull/6020) merges + // TODO: replace for with s.clients.delete_all(clients_to_remove) if (https://github.com/vlang/v/pull/6020) merges for client in clients_to_remove { lock { s.clients.delete(client) @@ -105,7 +104,7 @@ fn (mut s Server) handle_ping() { } } -// serve_client accepts incoming connection and setup the websocket handshake +// serve_client accepts incoming connection and sets up the callbacks fn (mut s Server) serve_client(mut c Client) ? { c.logger.debug('server-> Start serve client ($c.id)') defer { @@ -118,7 +117,7 @@ fn (mut s Server) serve_client(mut c Client) ? { c.shutdown_socket() ? return } - // The client is accepted + // the client is accepted c.socket_write(handshake_response.bytes()) ? lock { s.clients[server_client.client.id] = server_client @@ -150,7 +149,7 @@ fn (mut s Server) setup_callbacks(mut sc ServerClient) { } } } - // Set standard close so we can remove client if closed + // set standard close so we can remove client if closed sc.client.on_close_ref(fn (mut c Client, code int, reason string, mut sc ServerClient) ? { c.logger.debug('server-> Delete client') lock { @@ -159,7 +158,7 @@ fn (mut s Server) setup_callbacks(mut sc ServerClient) { }, sc) } -// accept_new_client creates a new client instance for client connects to socket +// accept_new_client creates a new client instance for client that connects to the socket fn (mut s Server) accept_new_client() ?&Client { mut new_conn := s.ls.accept() ? c := &Client{ @@ -181,7 +180,7 @@ fn (mut s Server) set_state(state State) { } } -// free, manual free memory of Server instance +// free manages manual free of memory for Server instance pub fn (mut s Server) free() { unsafe { s.clients.free() diff --git a/vlib/x/websocket/websocket_test.v b/vlib/x/websocket/websocket_test.v index 74e7659881..628b138b77 100644 --- a/vlib/x/websocket/websocket_test.v +++ b/vlib/x/websocket/websocket_test.v @@ -1,7 +1,7 @@ import x.websocket import time -// Tests with external ws & wss servers +// tests with internal ws servers fn test_ws() { go start_server() time.sleep_ms(100) @@ -12,10 +12,10 @@ fn test_ws() { fn start_server() ? { mut s := websocket.new_server(30000, '') - // Make that in execution test time give time to execute at least one time + // make that in execution test time give time to execute at least one time s.ping_interval = 100 s.on_connect(fn (mut s websocket.ServerClient) ?bool { - // Here you can look att the client info and accept or not accept + // here you can look att the client info and accept or not accept // just returning a true/false if s.resource_name != '/' { panic('unexpected resource name in test') @@ -24,20 +24,17 @@ fn start_server() ? { return true }) ? s.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ? { - // payload := if msg.payload.len == 0 { '' } else { string(msg.payload, msg.payload.len) } - // println('server client ($ws.id) got message: opcode: $msg.opcode, payload: $payload') ws.write(msg.payload, msg.opcode) or { panic(err) } }) s.on_close(fn (mut ws websocket.Client, code int, reason string) ? { - // println('client ($ws.id) closed connection') + // not used }) - s.listen() or { - // println('error on server listen: $err') - } + s.listen() or {} } +// ws_test tests connect to the websocket server from websocket client fn ws_test(uri string) ? { eprintln('connecting to $uri ...') mut ws := websocket.new_client(uri) ?