x.websocket: vdoc (#7091)
parent
cae3bd7f32
commit
d12f5f7ba0
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) ?
|
||||
|
|
Loading…
Reference in New Issue