mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
x.websocket: docs and cleanup (#7078)
This commit is contained in:
parent
953a51bec8
commit
7c394b9d58
@ -1,27 +1,30 @@
|
||||
module websocket
|
||||
|
||||
// All this plumbing will go awauy when we can do EventHandler<T> properly
|
||||
// Represents a callback on a new message
|
||||
struct MessageEventHandler {
|
||||
handler SocketMessageFn
|
||||
handler2 SocketMessageFn2
|
||||
is_ref bool
|
||||
ref voidptr
|
||||
handler SocketMessageFn // Callback function
|
||||
handler2 SocketMessageFn2 // Callback function with reference
|
||||
is_ref bool // Has a reference object
|
||||
ref voidptr // The referenced object
|
||||
}
|
||||
|
||||
// Represents a callback on error
|
||||
struct ErrorEventHandler {
|
||||
handler SocketErrorFn
|
||||
handler2 SocketErrorFn2
|
||||
is_ref bool
|
||||
ref voidptr
|
||||
handler SocketErrorFn // Callback function
|
||||
handler2 SocketErrorFn2 // Callback function with reference
|
||||
is_ref bool // Has a reference object
|
||||
ref voidptr // The referenced object
|
||||
}
|
||||
|
||||
// Represents a callback when connection is opened
|
||||
struct OpenEventHandler {
|
||||
handler SocketOpenFn
|
||||
handler2 SocketOpenFn2
|
||||
is_ref bool
|
||||
ref voidptr
|
||||
handler SocketOpenFn // Callback function
|
||||
handler2 SocketOpenFn2 // Callback function with reference
|
||||
is_ref bool // Has a reference object
|
||||
ref voidptr // The referenced object
|
||||
}
|
||||
|
||||
// Represents a callback on a closing event
|
||||
struct CloseEventHandler {
|
||||
handler SocketCloseFn
|
||||
handler2 SocketCloseFn2
|
||||
@ -47,6 +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
|
||||
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')
|
||||
@ -54,16 +58,6 @@ pub fn (mut s Server) on_connect(fun AcceptClientFn) ? {
|
||||
s.accept_client_callbacks << fun
|
||||
}
|
||||
|
||||
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
|
||||
return true
|
||||
}
|
||||
fun := s.accept_client_callbacks[0]
|
||||
res := fun(mut c)?
|
||||
return res
|
||||
}
|
||||
|
||||
// on_message, register a callback on new messages
|
||||
pub fn (mut s Server) on_message(fun SocketMessageFn) {
|
||||
s.message_callbacks << MessageEventHandler{
|
||||
@ -160,6 +154,18 @@ pub fn (mut ws Client) on_close_ref(fun SocketCloseFn2, ref voidptr) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
return true
|
||||
}
|
||||
fun := s.accept_client_callbacks[0]
|
||||
res := fun(mut c) ?
|
||||
return res
|
||||
}
|
||||
|
||||
// send_message_event invokes the on_message callback
|
||||
fn (mut ws Client) send_message_event(msg &Message) {
|
||||
ws.debug_log('sending on_message event')
|
||||
for ev_handler in ws.message_callbacks {
|
||||
@ -171,6 +177,7 @@ fn (mut ws Client) send_message_event(msg &Message) {
|
||||
}
|
||||
}
|
||||
|
||||
// send_error_event invokes the on_error callback
|
||||
fn (mut ws Client) send_error_event(err string) {
|
||||
ws.debug_log('sending on_error event')
|
||||
for ev_handler in ws.error_callbacks {
|
||||
@ -182,6 +189,7 @@ fn (mut ws Client) send_error_event(err string) {
|
||||
}
|
||||
}
|
||||
|
||||
// send_close_event invokes the on_close callback
|
||||
fn (mut ws Client) send_close_event(code int, reason string) {
|
||||
ws.debug_log('sending on_close event')
|
||||
for ev_handler in ws.close_callbacks {
|
||||
@ -193,6 +201,7 @@ fn (mut ws Client) send_close_event(code int, reason string) {
|
||||
}
|
||||
}
|
||||
|
||||
// send_open_event invokes the on_open callback
|
||||
fn (mut ws Client) send_open_event() {
|
||||
ws.debug_log('sending on_open event')
|
||||
for ev_handler in ws.open_callbacks {
|
||||
|
@ -3,7 +3,7 @@ module websocket
|
||||
import encoding.base64
|
||||
import strings
|
||||
|
||||
// handshake manage the handshake part of connecting
|
||||
// handshake manage the websocket handshake process
|
||||
fn (mut ws Client) handshake() ? {
|
||||
nonce := get_nonce(ws.nonce_size)
|
||||
seckey := base64.encode(nonce)
|
||||
@ -24,7 +24,6 @@ fn (mut ws Client) handshake() ? {
|
||||
sb.write('Sec-WebSocket-Key: ')
|
||||
sb.write(seckey)
|
||||
sb.write('\r\nSec-WebSocket-Version: 13\r\n\r\n')
|
||||
// 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'
|
||||
handshake := sb.str()
|
||||
defer {
|
||||
handshake.free()
|
||||
@ -36,7 +35,7 @@ fn (mut ws Client) handshake() ? {
|
||||
unsafe {handshake_bytes.free()}
|
||||
}
|
||||
|
||||
// handshake manage the handshake part of connecting
|
||||
// handle_server_handshake manage websocket server handshake
|
||||
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) ?
|
||||
@ -44,6 +43,7 @@ fn (mut s Server) handle_server_handshake(mut c Client) ?(string, &ServerClient)
|
||||
return handshake_response, client
|
||||
}
|
||||
|
||||
// parse_client_handshake parses handshake result
|
||||
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()
|
||||
@ -52,12 +52,13 @@ fn (mut s Server) parse_client_handshake(client_handshake string, mut c Client)
|
||||
return error_with_code('unexpected get operation, $get_tokens', 1)
|
||||
}
|
||||
if get_tokens[0].trim_space() != 'GET' {
|
||||
return error_with_code("unexpected request '${get_tokens[0]}', expected 'GET'", 2)
|
||||
return error_with_code("unexpected request '${get_tokens[0]}', expected 'GET'",
|
||||
2)
|
||||
}
|
||||
if get_tokens[2].trim_space() != 'HTTP/1.1' {
|
||||
return error_with_code("unexpected request $get_tokens, expected 'HTTP/1.1'", 3)
|
||||
return error_with_code("unexpected request $get_tokens, expected 'HTTP/1.1'",
|
||||
3)
|
||||
}
|
||||
// path := get_tokens[1].trim_space()
|
||||
mut seckey := ''
|
||||
mut flags := []Flag{}
|
||||
mut key := ''
|
||||
@ -106,6 +107,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
|
||||
fn (mut ws Client) read_handshake_str() ?string {
|
||||
mut total_bytes_read := 0
|
||||
mut msg := [1024]byte{}
|
||||
@ -133,12 +135,15 @@ fn (mut ws Client) read_handshake(seckey string) ? {
|
||||
unsafe {msg.free()}
|
||||
}
|
||||
|
||||
// check_handshake_response checks the response from handshake and returns
|
||||
// the response and secure key
|
||||
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()
|
||||
header := lines[0]
|
||||
if !header.starts_with('HTTP/1.1 101') && !header.starts_with('HTTP/1.0 101') {
|
||||
return error_with_code('handshake_handler: invalid HTTP status response code, $header', 6)
|
||||
return error_with_code('handshake_handler: invalid HTTP status response code, $header',
|
||||
6)
|
||||
}
|
||||
for i in 1 .. lines.len {
|
||||
if lines[i].len <= 0 || lines[i] == '\r\n' {
|
||||
@ -157,7 +162,8 @@ fn (mut ws Client) check_handshake_response(handshake_response string, seckey st
|
||||
challenge := create_key_challenge_response(seckey) ?
|
||||
ws.debug_log('challenge: $challenge, response: ${keys[1]}')
|
||||
if keys[1].trim_space() != challenge {
|
||||
return error_with_code('handshake_handler: Sec-WebSocket-Accept header does not match computed sha1/base64 response.', 7)
|
||||
return error_with_code('handshake_handler: Sec-WebSocket-Accept header does not match computed sha1/base64 response.',
|
||||
7)
|
||||
}
|
||||
ws.flags << .has_accept
|
||||
unsafe {challenge.free()}
|
||||
|
@ -2,11 +2,7 @@ module websocket
|
||||
|
||||
import net
|
||||
import time
|
||||
|
||||
interface WebsocketIO {
|
||||
socket_read(mut buffer []byte) ?int
|
||||
socket_write(bytes []byte) ?
|
||||
}
|
||||
import sync
|
||||
|
||||
// socket_read reads into the provided buffer with its length
|
||||
fn (mut ws Client) socket_read(mut buffer []byte) ?int {
|
||||
@ -31,12 +27,12 @@ fn (mut ws Client) socket_read(mut buffer []byte) ?int {
|
||||
}
|
||||
}
|
||||
|
||||
// socket_read reads 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 {
|
||||
return error('socket_read_ptr: trying to read a closed socket')
|
||||
}
|
||||
|
||||
if ws.is_ssl {
|
||||
r := ws.ssl_conn.socket_read_into_ptr(buf_ptr, len) ?
|
||||
return r
|
||||
@ -77,7 +73,7 @@ fn (mut ws Client) socket_write(bytes []byte) ? {
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown_socket, proper shutdown make PR in Emeliy repo
|
||||
// shutdown_socket, shut down socket properly closing the connection
|
||||
fn (mut ws Client) shutdown_socket() ? {
|
||||
ws.debug_log('shutting down socket')
|
||||
if ws.is_ssl {
|
||||
|
@ -3,33 +3,37 @@ module websocket
|
||||
import encoding.utf8
|
||||
|
||||
const (
|
||||
header_len_offset = 2
|
||||
buffer_size = 256
|
||||
extended_payload16_end_byte = 4
|
||||
extended_payload64_end_byte = 10
|
||||
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
|
||||
)
|
||||
|
||||
// A websocket data fragment
|
||||
struct Fragment {
|
||||
data []byte
|
||||
opcode OPCode
|
||||
data []byte // The included data payload data in a fragment
|
||||
opcode OPCode // Defines the interpretation of the payload data
|
||||
}
|
||||
|
||||
// Represents a data frame header
|
||||
struct Frame {
|
||||
mut:
|
||||
header_len int = 2
|
||||
// Lenght of the websocket header part
|
||||
frame_size int = 2
|
||||
fin bool
|
||||
rsv1 bool
|
||||
rsv2 bool
|
||||
rsv3 bool
|
||||
opcode OPCode
|
||||
has_mask bool
|
||||
payload_len int
|
||||
masking_key [4]byte
|
||||
// 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
|
||||
}
|
||||
|
||||
const (
|
||||
invalid_close_codes = [999, 1004, 1005, 1006, 1014, 1015, 1016, 1100, 2000, 2999, 5000, 65536]
|
||||
invalid_close_codes = [999, 1004, 1005, 1006, 1014, 1015, 1016, 1100, 2000, 2999, 5000, 65536] // List of invalid close codes
|
||||
)
|
||||
|
||||
// validate_client, validate client frame rules from RFC6455
|
||||
@ -66,22 +70,21 @@ pub fn (mut ws Client) validate_frame(frame &Frame) ? {
|
||||
}
|
||||
}
|
||||
|
||||
[inline]
|
||||
// is_control_frame returns true if the fram is a control frame
|
||||
fn is_control_frame(opcode OPCode) bool {
|
||||
return opcode !in [.text_frame, .binary_frame, .continuation]
|
||||
}
|
||||
|
||||
[inline]
|
||||
// is_data_frame returns true if the fram is a control frame
|
||||
fn is_data_frame(opcode OPCode) bool {
|
||||
return opcode in [.text_frame, .binary_frame]
|
||||
}
|
||||
|
||||
// read_payload, reads the payload from socket
|
||||
// read_payload, reads the payload from the socket
|
||||
fn (mut ws Client) read_payload(frame &Frame) ?[]byte {
|
||||
if frame.payload_len == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
// TODO: make a dynamic reusable memory pool here
|
||||
mut buffer := []byte{cap: frame.payload_len}
|
||||
mut read_buf := [1]byte{}
|
||||
mut bytes_read := 0
|
||||
@ -104,8 +107,8 @@ fn (mut ws Client) read_payload(frame &Frame) ?[]byte {
|
||||
return buffer
|
||||
}
|
||||
|
||||
// validate_utf_8, validates payload for valid utf encoding
|
||||
// todo: support fail fast utf errors for strict autobahn conformance
|
||||
// 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) {
|
||||
ws.logger.error('malformed utf8 payload, payload len: ($payload.len)')
|
||||
@ -130,9 +133,7 @@ pub fn (mut ws Client) read_next_message() ?Message {
|
||||
opcode: OPCode(frame.opcode)
|
||||
payload: frame_payload.clone()
|
||||
}
|
||||
unsafe {
|
||||
frame_payload.free()
|
||||
}
|
||||
unsafe {frame_payload.free()}
|
||||
return msg
|
||||
}
|
||||
// If the message is fragmented we just put it on fragments
|
||||
@ -142,9 +143,7 @@ pub fn (mut ws Client) read_next_message() ?Message {
|
||||
data: frame_payload.clone()
|
||||
opcode: frame.opcode
|
||||
}
|
||||
unsafe {
|
||||
frame_payload.free()
|
||||
}
|
||||
unsafe {frame_payload.free()}
|
||||
continue
|
||||
}
|
||||
if ws.fragments.len == 0 {
|
||||
@ -157,9 +156,7 @@ pub fn (mut ws Client) read_next_message() ?Message {
|
||||
opcode: OPCode(frame.opcode)
|
||||
payload: frame_payload.clone()
|
||||
}
|
||||
unsafe {
|
||||
frame_payload.free()
|
||||
}
|
||||
unsafe {frame_payload.free()}
|
||||
return msg
|
||||
}
|
||||
defer {
|
||||
@ -206,23 +203,19 @@ fn (ws Client) payload_from_fragments(fin_payload []byte) ?[]byte {
|
||||
return total_buffer
|
||||
}
|
||||
|
||||
// opcode_from_fragments, returns the opcode for message from the first fragment sent
|
||||
// opcode_from_fragments returns the opcode for message from the first fragment sent
|
||||
fn (ws Client) opcode_from_fragments() OPCode {
|
||||
return OPCode(ws.fragments[0].opcode)
|
||||
}
|
||||
|
||||
// parse_frame_header parses next message by decoding the incoming frames
|
||||
pub fn (mut ws Client) parse_frame_header() ?Frame {
|
||||
// TODO: make a dynamic reusable memory pool here
|
||||
// mut buffer := []byte{cap: buffer_size}
|
||||
mut buffer := [256]byte{}
|
||||
mut bytes_read := 0
|
||||
mut frame := Frame{}
|
||||
mut rbuff := [1]byte{}
|
||||
mut mask_end_byte := 0
|
||||
for ws.state == .open {
|
||||
// Todo: different error scenarios to make sure we close correctly on error
|
||||
// reader.read_into(mut rbuff) ?
|
||||
read_bytes := ws.socket_read_ptr(byteptr(rbuff), 1) ?
|
||||
if read_bytes == 0 {
|
||||
// This is probably a timeout or close
|
||||
@ -268,7 +261,7 @@ pub fn (mut ws Client) parse_frame_header() ?Frame {
|
||||
}
|
||||
}
|
||||
if frame.payload_len == 127 && bytes_read == u64(extended_payload64_end_byte) {
|
||||
frame.header_len += 8 // TODO Not sure...
|
||||
frame.header_len += 8
|
||||
frame.payload_len = 0
|
||||
frame.payload_len |= buffer[2] << 56
|
||||
frame.payload_len |= buffer[3] << 48
|
||||
|
@ -1,14 +1,16 @@
|
||||
module websocket
|
||||
|
||||
// Represents an Uri for websocket connections
|
||||
struct Uri {
|
||||
mut:
|
||||
url string
|
||||
hostname string
|
||||
port string
|
||||
resource string
|
||||
querystring string
|
||||
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
|
||||
}
|
||||
|
||||
// str returns the string representation of the Uri
|
||||
pub fn (u Uri) str() string {
|
||||
return u.url
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import rand
|
||||
import crypto.sha1
|
||||
import encoding.base64
|
||||
|
||||
// htonl64 converts payload lenght to header bits
|
||||
fn htonl64(payload_len u64) []byte {
|
||||
mut ret := []byte{len: 8}
|
||||
ret[0] = byte(((payload_len & (u64(0xff) << 56)) >> 56) & 0xff)
|
||||
@ -17,15 +18,15 @@ fn htonl64(payload_len u64) []byte {
|
||||
return ret
|
||||
}
|
||||
|
||||
// create_masking_key returs a new masking key byte array
|
||||
fn create_masking_key() []byte {
|
||||
mask_bit := byte(rand.intn(255))
|
||||
buf := []byte{len: 4, init: `0`}
|
||||
unsafe {
|
||||
C.memcpy(buf.data, &mask_bit, 4)
|
||||
}
|
||||
unsafe {C.memcpy(buf.data, &mask_bit, 4)}
|
||||
return buf
|
||||
}
|
||||
|
||||
// create_key_challenge_response creates a key challange response from security key
|
||||
fn create_key_challenge_response(seckey string) ?string {
|
||||
if seckey.len == 0 {
|
||||
return error('unexpected seckey lengt zero')
|
||||
@ -42,6 +43,7 @@ fn create_key_challenge_response(seckey string) ?string {
|
||||
return b64
|
||||
}
|
||||
|
||||
// get_nonce, returns 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'
|
||||
|
@ -23,27 +23,29 @@ const (
|
||||
pub struct Client {
|
||||
is_server bool
|
||||
mut:
|
||||
ssl_conn &openssl.SSLConn
|
||||
flags []Flag
|
||||
fragments []Fragment
|
||||
message_callbacks []MessageEventHandler
|
||||
error_callbacks []ErrorEventHandler
|
||||
open_callbacks []OpenEventHandler
|
||||
close_callbacks []CloseEventHandler
|
||||
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
|
||||
pub:
|
||||
is_ssl bool
|
||||
uri Uri
|
||||
id string
|
||||
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
|
||||
nonce_size int = 16 // you can try 18 too
|
||||
panic_on_callback bool
|
||||
state State
|
||||
logger &log.Log
|
||||
resource_name string
|
||||
last_pong_ut u64
|
||||
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
|
||||
}
|
||||
|
||||
// Flag of websocket handshake
|
||||
enum Flag {
|
||||
has_accept
|
||||
has_connection
|
||||
@ -133,16 +135,12 @@ pub fn (mut ws Client) listen() ? {
|
||||
.text_frame {
|
||||
ws.debug_log('read: text')
|
||||
ws.send_message_event(msg)
|
||||
unsafe {
|
||||
msg.free()
|
||||
}
|
||||
unsafe {msg.free()}
|
||||
}
|
||||
.binary_frame {
|
||||
ws.debug_log('read: binary')
|
||||
ws.send_message_event(msg)
|
||||
unsafe {
|
||||
msg.free()
|
||||
}
|
||||
unsafe {msg.free()}
|
||||
}
|
||||
.ping {
|
||||
ws.debug_log('read: ping, sending pong')
|
||||
@ -155,9 +153,7 @@ pub fn (mut ws Client) listen() ? {
|
||||
continue
|
||||
}
|
||||
if msg.payload.len > 0 {
|
||||
unsafe {
|
||||
msg.free()
|
||||
}
|
||||
unsafe {msg.free()}
|
||||
}
|
||||
}
|
||||
.pong {
|
||||
@ -165,9 +161,7 @@ pub fn (mut ws Client) listen() ? {
|
||||
ws.last_pong_ut = time.now().unix
|
||||
ws.send_message_event(msg)
|
||||
if msg.payload.len > 0 {
|
||||
unsafe {
|
||||
msg.free()
|
||||
}
|
||||
unsafe {msg.free()}
|
||||
}
|
||||
}
|
||||
.close {
|
||||
@ -195,18 +189,14 @@ pub fn (mut ws Client) listen() ? {
|
||||
r := reason.bytestr()
|
||||
ws.close(code, r) ?
|
||||
}
|
||||
unsafe {
|
||||
msg.free()
|
||||
}
|
||||
unsafe {msg.free()}
|
||||
} else {
|
||||
if ws.state !in [.closing, .closed] {
|
||||
ws.debug_log('close with reason, no code')
|
||||
// sending close back according to spec
|
||||
ws.close(1000, 'normal') ?
|
||||
}
|
||||
unsafe {
|
||||
msg.free()
|
||||
}
|
||||
unsafe {msg.free()}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -220,7 +210,7 @@ pub fn (mut ws Client) listen() ? {
|
||||
}
|
||||
}
|
||||
|
||||
// this function was needed for defer
|
||||
// manage_clean_close closes connection in a clean way
|
||||
fn (mut ws Client) manage_clean_close() {
|
||||
ws.send_close_event(1000, 'closed by client')
|
||||
}
|
||||
@ -231,7 +221,7 @@ pub fn (mut ws Client) ping() ? {
|
||||
ws.send_control_frame(.ping, 'PING', []) ?
|
||||
}
|
||||
|
||||
// pong, sends pog message to server,
|
||||
// pong, sends pong message to server,
|
||||
// pongs are normally automatically sent back to server
|
||||
pub fn (mut ws Client) pong() ? {
|
||||
ws.send_control_frame(.pong, 'PONG', []) ?
|
||||
@ -265,18 +255,11 @@ pub fn (mut ws Client) write_ptr(bytes byteptr, payload_len int, code OPCode) ?
|
||||
} else if payload_len > 125 && payload_len <= 0xffff {
|
||||
len16 := C.htons(payload_len)
|
||||
header[1] = 126
|
||||
// 0x80
|
||||
// todo: fix v style copy instead
|
||||
unsafe {
|
||||
C.memcpy(&header[2], &len16, 2)
|
||||
}
|
||||
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
|
||||
// todo: fix v style copy instead
|
||||
unsafe {
|
||||
C.memcpy(&header[2], len_bytes.data, 8)
|
||||
}
|
||||
unsafe {C.memcpy(&header[2], len_bytes.data, 8)}
|
||||
}
|
||||
} else {
|
||||
if payload_len <= 125 {
|
||||
@ -288,10 +271,7 @@ pub fn (mut ws Client) write_ptr(bytes byteptr, payload_len int, code OPCode) ?
|
||||
} else if payload_len > 125 && payload_len <= 0xffff {
|
||||
len16 := C.htons(payload_len)
|
||||
header[1] = (126 | 0x80)
|
||||
// todo: fix v style copy instead
|
||||
unsafe {
|
||||
C.memcpy(&header[2], &len16, 2)
|
||||
}
|
||||
unsafe {C.memcpy(&header[2], &len16, 2)}
|
||||
header[4] = masking_key[0]
|
||||
header[5] = masking_key[1]
|
||||
header[6] = masking_key[2]
|
||||
@ -299,16 +279,12 @@ pub fn (mut ws Client) write_ptr(bytes byteptr, payload_len int, code OPCode) ?
|
||||
} else if payload_len > 0xffff && payload_len <= 0xffffffffffffffff { // 65535 && 18446744073709551615
|
||||
len64 := htonl64(u64(payload_len))
|
||||
header[1] = (127 | 0x80)
|
||||
// todo: fix v style copy instead
|
||||
unsafe {
|
||||
C.memcpy(&header[2], len64.data, 8)
|
||||
}
|
||||
unsafe {C.memcpy(&header[2], len64.data, 8)}
|
||||
header[10] = masking_key[0]
|
||||
header[11] = masking_key[1]
|
||||
header[12] = masking_key[2]
|
||||
header[13] = masking_key[3]
|
||||
} else {
|
||||
// l.c('write: frame too large')
|
||||
ws.close(1009, 'frame too large') ?
|
||||
return error('frame too large')
|
||||
}
|
||||
@ -340,6 +316,7 @@ 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
|
||||
pub fn (mut ws Client) write_str(str string) ? {
|
||||
ws.write_ptr(str.str, str.len, .text_frame)
|
||||
}
|
||||
@ -362,7 +339,7 @@ pub fn (mut ws Client) close(code int, message string) ? {
|
||||
if code > 0 {
|
||||
code_ := C.htons(code)
|
||||
message_len := message.len + 2
|
||||
mut close_frame := []byte{len: message_len} // [`0`].repeat(message_len)
|
||||
mut close_frame := []byte{len: message_len}
|
||||
close_frame[0] = byte(code_ & 0xFF)
|
||||
close_frame[1] = byte(code_ >> 8)
|
||||
code32 = (close_frame[0] << 8) + close_frame[1]
|
||||
@ -371,9 +348,7 @@ pub fn (mut ws Client) close(code int, message string) ? {
|
||||
}
|
||||
ws.send_control_frame(.close, 'CLOSE', close_frame) ?
|
||||
ws.send_close_event(code, message)
|
||||
unsafe {
|
||||
close_frame.free()
|
||||
}
|
||||
unsafe {close_frame.free()}
|
||||
} else {
|
||||
ws.send_control_frame(.close, 'CLOSE', []) ?
|
||||
ws.send_close_event(code, '')
|
||||
@ -413,20 +388,14 @@ fn (mut ws Client) send_control_frame(code OPCode, frame_typ string, payload []b
|
||||
if payload.len >= 2 {
|
||||
if !ws.is_server {
|
||||
mut parsed_payload := []byte{len: payload.len + 1}
|
||||
unsafe {
|
||||
C.memcpy(parsed_payload.data, &payload[0], payload.len)
|
||||
}
|
||||
unsafe {C.memcpy(parsed_payload.data, &payload[0], payload.len)}
|
||||
parsed_payload[payload.len] = `\0`
|
||||
for i in 0 .. payload.len {
|
||||
control_frame[6 + i] = (parsed_payload[i] ^ masking_key[i % 4]) & 0xff
|
||||
}
|
||||
unsafe {
|
||||
parsed_payload.free()
|
||||
}
|
||||
unsafe {parsed_payload.free()}
|
||||
} else {
|
||||
unsafe {
|
||||
C.memcpy(&control_frame[2], &payload[0], payload.len)
|
||||
}
|
||||
unsafe {C.memcpy(&control_frame[2], &payload[0], payload.len)}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -438,9 +407,7 @@ fn (mut ws Client) send_control_frame(code OPCode, frame_typ string, payload []b
|
||||
}
|
||||
} else {
|
||||
if payload.len > 0 {
|
||||
unsafe {
|
||||
C.memcpy(&control_frame[2], &payload[0], payload.len)
|
||||
}
|
||||
unsafe {C.memcpy(&control_frame[2], &payload[0], payload.len)}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -480,7 +447,7 @@ fn (mut ws Client) set_state(state State) {
|
||||
}
|
||||
}
|
||||
|
||||
[inline]
|
||||
// assert_not_connected returns error if the connection is not connected
|
||||
fn (ws Client) assert_not_connected() ? {
|
||||
match ws.state {
|
||||
.connecting { return error('connect: websocket is connecting') }
|
||||
@ -500,6 +467,7 @@ fn (mut ws Client) reset_state() {
|
||||
}
|
||||
}
|
||||
|
||||
// debug_log makes debug logging output different depending if client or server
|
||||
fn (mut ws Client) debug_log(text string) {
|
||||
if ws.is_server {
|
||||
ws.logger.debug('server-> $text')
|
||||
@ -508,14 +476,12 @@ fn (mut ws Client) debug_log(text string) {
|
||||
}
|
||||
}
|
||||
|
||||
[unsafe]
|
||||
// free, manual free memory of Message struct
|
||||
pub fn (m &Message) free() {
|
||||
unsafe {
|
||||
m.payload.free()
|
||||
}
|
||||
unsafe {m.payload.free()}
|
||||
}
|
||||
|
||||
[unsafe]
|
||||
// free, manual free memory of Client struct
|
||||
pub fn (c &Client) free() {
|
||||
unsafe {
|
||||
c.flags.free()
|
||||
|
@ -1,9 +1,10 @@
|
||||
module websocket
|
||||
|
||||
// error_code returns the error code
|
||||
fn error_code() int {
|
||||
return C.errno
|
||||
}
|
||||
|
||||
const (
|
||||
error_ewouldblock = C.EWOULDBLOCK
|
||||
error_ewouldblock = C.EWOULDBLOCK // blocking error code
|
||||
)
|
||||
|
@ -8,31 +8,35 @@ import sync
|
||||
import time
|
||||
import rand
|
||||
|
||||
// Server holds state of websocket server connection
|
||||
pub struct Server {
|
||||
mut:
|
||||
clients map[string]&ServerClient
|
||||
logger &log.Log
|
||||
ls net.TcpListener
|
||||
accept_client_callbacks []AcceptClientFn
|
||||
message_callbacks []MessageEventHandler
|
||||
close_callbacks []CloseEventHandler
|
||||
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
|
||||
pub:
|
||||
port int
|
||||
is_ssl bool
|
||||
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 // in seconds
|
||||
state State
|
||||
ping_interval int = 30
|
||||
// Interval for automatic sending ping to connected clients in seconds
|
||||
state State // Current state of connection
|
||||
}
|
||||
|
||||
// ServerClient has state of connected clients
|
||||
struct ServerClient {
|
||||
pub:
|
||||
resource_name string
|
||||
client_key string
|
||||
resource_name string // The resource that the client access
|
||||
client_key string // Unique key of client
|
||||
pub mut:
|
||||
server &Server
|
||||
client &Client
|
||||
server &Server // The server instance
|
||||
client &Client // The client instance
|
||||
}
|
||||
|
||||
// new_server instance new websocket server on port and route
|
||||
pub fn new_server(port int, route string) &Server {
|
||||
return &Server{
|
||||
port: port
|
||||
@ -43,10 +47,12 @@ pub fn new_server(port int, route string) &Server {
|
||||
}
|
||||
}
|
||||
|
||||
// set_ping_interval sets the interval that the server will send ping messages to clients
|
||||
pub fn (mut s Server) set_ping_interval(seconds int) {
|
||||
s.ping_interval = seconds
|
||||
}
|
||||
|
||||
// listen, start listen to incoming connections
|
||||
pub fn (mut s Server) listen() ? {
|
||||
s.logger.info('websocket server: start listen on port $s.port')
|
||||
s.ls = net.listen_tcp(s.port) ?
|
||||
@ -61,10 +67,11 @@ pub fn (mut s Server) listen() ? {
|
||||
s.logger.info('websocket server: end listen on port $s.port')
|
||||
}
|
||||
|
||||
// Close server (not implemented)
|
||||
fn (mut s Server) close() {
|
||||
}
|
||||
|
||||
// Todo: make thread safe
|
||||
// handle_ping sends ping to all clients every set interval
|
||||
fn (mut s Server) handle_ping() {
|
||||
mut clients_to_remove := []string{}
|
||||
for s.state == .open {
|
||||
@ -74,8 +81,7 @@ fn (mut s Server) handle_ping() {
|
||||
if c.client.state == .open {
|
||||
c.client.ping() or {
|
||||
s.logger.debug('server-> error sending ping to client')
|
||||
// todo fix better close message, search the standard
|
||||
c.client.close(1002, 'Clossing connection: ping send error') or {
|
||||
c.client.close(1002, 'Closing connection: ping send error') or {
|
||||
// we want to continue even if error
|
||||
continue
|
||||
}
|
||||
@ -99,6 +105,7 @@ fn (mut s Server) handle_ping() {
|
||||
}
|
||||
}
|
||||
|
||||
// serve_client accepts incoming connection and setup the websocket handshake
|
||||
fn (mut s Server) serve_client(mut c Client) ? {
|
||||
c.logger.debug('server-> Start serve client ($c.id)')
|
||||
defer {
|
||||
@ -123,6 +130,7 @@ fn (mut s Server) serve_client(mut c Client) ? {
|
||||
}
|
||||
}
|
||||
|
||||
// setup_callbacks initialize all callback functions
|
||||
fn (mut s Server) setup_callbacks(mut sc ServerClient) {
|
||||
if s.message_callbacks.len > 0 {
|
||||
for cb in s.message_callbacks {
|
||||
@ -151,6 +159,7 @@ fn (mut s Server) setup_callbacks(mut sc ServerClient) {
|
||||
}, sc)
|
||||
}
|
||||
|
||||
// accept_new_client creates a new client instance for client connects to socket
|
||||
fn (mut s Server) accept_new_client() ?&Client {
|
||||
mut new_conn := s.ls.accept() ?
|
||||
c := &Client{
|
||||
@ -166,18 +175,16 @@ fn (mut s Server) accept_new_client() ?&Client {
|
||||
}
|
||||
|
||||
// set_state sets current state in a thread safe way
|
||||
[inline]
|
||||
fn (mut s Server) set_state(state State) {
|
||||
lock {
|
||||
s.state = state
|
||||
}
|
||||
}
|
||||
|
||||
// free, manual free memory of Server instance
|
||||
pub fn (mut s Server) free() {
|
||||
unsafe {
|
||||
s.clients.free()
|
||||
// s.logger.free()
|
||||
// s.ls.free()
|
||||
s.accept_client_callbacks.free()
|
||||
s.message_callbacks.free()
|
||||
s.close_callbacks.free()
|
||||
|
@ -2,10 +2,11 @@ module websocket
|
||||
|
||||
import net
|
||||
|
||||
// error_code returns the error code
|
||||
fn error_code() int {
|
||||
return C.WSAGetLastError()
|
||||
}
|
||||
|
||||
const (
|
||||
error_ewouldblock = net.WsaError.wsaewouldblock
|
||||
error_ewouldblock = net.WsaError.wsaewouldblock // blocking error code
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user