websocket_return_t websocket_queue_close(websocket_t *websocket, const char *close_message) { int r = WEBSOCKET_SUCCESS; if (websocket == NULL) { WEBSOCKET_DEBUG("NULL parameter\n"); return WEBSOCKET_ALLOCATION_ERROR; } if (close_message == NULL) { close_message = "\0"; } if (websocket->ctx != NULL && websocket->state != WEBSOCKET_STOP) { if (wslay_event_queue_close(websocket->ctx, 1000, (const uint8_t *)close_message, strlen(close_message)) != WEBSOCKET_SUCCESS) { WEBSOCKET_DEBUG("fail to queue close message\n"); r = WEBSOCKET_SEND_ERROR; goto EXIT_QUEUE_CLOSE; } websocket_wait_state(websocket, WEBSOCKET_STOP, 100000); WEBSOCKET_DEBUG("websocket handler successfully stopped, closing\n"); } EXIT_QUEUE_CLOSE: WEBSOCKET_CLOSE(websocket->fd); if (websocket->ctx) { wslay_event_context_free(websocket->ctx); websocket->ctx = NULL; } websocket_update_state(websocket, WEBSOCKET_STOP); return r; }
static mrb_value mrb_wslay_event_queue_close(mrb_state *mrb, mrb_value self) { mrb_wslay_user_data *data = (mrb_wslay_user_data *) DATA_PTR(self); mrb_assert(data); mrb_sym status_code; char *reason = NULL; mrb_int reason_length = 0; mrb_get_args(mrb, "n|s!", &status_code, &reason, &reason_length); mrb_int stc = mrb_fixnum(MRB_GET_STATUSCODE(mrb_symbol_value(status_code))); int err = wslay_event_queue_close(data->ctx, stc, (const uint8_t *) reason, reason_length); if (err == WSLAY_ERR_NOMEM) { mrb_sys_fail(mrb, "wslay_event_queue_close"); } else if (err == WSLAY_ERR_NO_MORE_MSG) { mrb_raise(mrb, E_WSLAY_ERROR, "further message queueing is not allowed"); } else if (err == WSLAY_ERR_INVALID_ARGUMENT) { mrb_raise(mrb, E_WSLAY_ERROR, "the given message is invalid"); } else if (err != 0) { return MRB_WSLAY_ERROR(mrb_fixnum_value(err)); } return self; }
static int wslay_event_queue_close_wrapper(wslay_event_context_ptr ctx, uint16_t status_code, const uint8_t *reason, size_t reason_length) { int r; ctx->read_enabled = 0; if ((r = wslay_event_queue_close(ctx, status_code, reason, reason_length)) && r != WSLAY_ERR_NO_MORE_MSG) { return r; } return 0; }
/** * \brief The function with websocket infinite loop */ void *vs_websocket_loop(void *arg) { /* The vContext is passed as *user_data* in callback functions. */ struct vContext *C = (struct vContext*)arg; struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VStreamConn *stream_conn = CTX_current_stream_conn(C); struct VSession *vsession = CTX_current_session(C); struct VMessage *r_message=NULL, *s_message=NULL; wslay_event_context_ptr wslay_ctx; fd_set read_set, write_set; struct timeval tv; int ret, flags; unsigned int int_size; struct wslay_event_callbacks callbacks = { vs_recv_ws_callback_data, vs_send_ws_callback_data, NULL, NULL, NULL, NULL, vs_ws_recv_msg_callback }; /* Set socket blocking */ flags = fcntl(io_ctx->sockfd, F_GETFL, 0); fcntl(io_ctx->sockfd, F_SETFL, flags & ~O_NONBLOCK); http_handshake(io_ctx->sockfd); /* Try to get size of TCP buffer */ int_size = sizeof(int_size); getsockopt(io_ctx->sockfd, SOL_SOCKET, SO_RCVBUF, (void *)&stream_conn->socket_buffer_size, &int_size); r_message = (struct VMessage*)calloc(1, sizeof(struct VMessage)); s_message = (struct VMessage*)calloc(1, sizeof(struct VMessage)); CTX_r_message_set(C, r_message); CTX_s_message_set(C, s_message); stream_conn->host_state = TCP_SERVER_STATE_RESPOND_METHODS; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: RESPOND_methods\n"); printf("%c[%dm", 27, 0); } /* Set non-blocking */ flags = fcntl(io_ctx->sockfd, F_GETFL, 0); fcntl(io_ctx->sockfd, F_SETFL, flags | O_NONBLOCK); wslay_event_context_server_init(&wslay_ctx, &callbacks, C); /* "Never ending" loop */ while(vsession->stream_conn->host_state != TCP_SERVER_STATE_CLOSED) { if(vs_ctx->state != SERVER_STATE_READY) { vsession->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; /* Try to close connection with websocket client */ wslay_event_queue_close(wslay_ctx, WSLAY_CODE_GOING_AWAY, (uint8_t*)"Server shutdown", /* Close message */ 15); /* The length of close message s*/ } /* Initialize read set */ FD_ZERO(&read_set); FD_SET(io_ctx->sockfd, &read_set); /* Initialize write set */ FD_ZERO(&write_set); FD_SET(io_ctx->sockfd, &write_set); /* Set timeout for select() */ if(stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { /* Use negotiated FPS */ tv.tv_sec = 0; tv.tv_usec = 1000000/vsession->fps_host; } else { /* User have to send something in 30 seconds */ tv.tv_sec = VRS_TIMEOUT; tv.tv_usec = 0; } if( (ret = select(io_ctx->sockfd+1, &read_set, &write_set, NULL, /* Exception*/ &tv)) == -1) { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "%s:%s():%d select(): %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); goto end; /* Was event on the listen socket */ } else if(ret>0){ if(FD_ISSET(io_ctx->sockfd, &read_set)) { if( wslay_event_recv(wslay_ctx) != 0 ) { goto end; } } else if (FD_ISSET(io_ctx->sockfd, &write_set)) { if( wslay_event_send(wslay_ctx) != 0 ) { goto end; } } } if(stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { /* Check if there is any command in outgouing queue * and eventually pack these commands to buffer */ if((ret = v_STREAM_pack_message(C)) == 0 ) { goto end; } /* When at least one command was packed to buffer, then * queue this buffer to WebSocket layer */ if(ret == 1) { struct wslay_event_msg msgarg; msgarg.opcode = WSLAY_BINARY_FRAME; msgarg.msg = (uint8_t*)io_ctx->buf; msgarg.msg_length = io_ctx->buf_size; wslay_event_queue_msg(wslay_ctx, &msgarg); } } } end: if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: CLOSING\n"); printf("%c[%dm", 27, 0); } /* Set up TCP CLOSING state (non-blocking) */ vs_CLOSING(C); /* Receive and Send messages are not neccessary any more */ if(r_message!=NULL) { free(r_message); r_message = NULL; CTX_r_message_set(C, NULL); } if(s_message!=NULL) { free(s_message); s_message = NULL; CTX_s_message_set(C, NULL); } /* TCP connection is considered as CLOSED, but it is not possible to use * this connection for other client */ stream_conn->host_state = TCP_SERVER_STATE_CLOSED; /* NULL pointer at stream connection */ CTX_current_stream_conn_set(C, NULL); /* Set TCP connection to CLOSED */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: CLOSED\n"); printf("%c[%dm", 27, 0); } pthread_mutex_lock(&vs_ctx->data.mutex); /* Unsubscribe this session (this avatar) from all nodes */ vs_node_free_avatar_reference(vs_ctx, vsession); /* Try to destroy avatar node */ vs_node_destroy_avatar_node(vs_ctx, vsession); pthread_mutex_unlock(&vs_ctx->data.mutex); /* This session could be used again for authentication */ stream_conn->host_state=TCP_SERVER_STATE_LISTEN; /* Clear session flags */ vsession->flags = 0; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: LISTEN\n"); printf("%c[%dm", 27, 0); } free(C); C = NULL; pthread_exit(NULL); return NULL; }
/** * \brief WebSocket callback function for recived message */ void vs_ws_recv_msg_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { struct vContext *C = (struct vContext*)user_data; struct VSession *session = CTX_current_session(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); (void)ctx; if(!wslay_is_ctrl_frame(arg->opcode)) { /* Verse server uses binary message for communication */ if(arg->opcode == WSLAY_BINARY_FRAME) { struct wslay_event_msg msgarg; v_print_log(VRS_PRINT_DEBUG_MSG, "WS Callback received binary message\n"); /* Copy received data to IO context */ memcpy(io_ctx->buf, arg->msg, arg->msg_length); io_ctx->buf_size = arg->msg_length; if(session->stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { if(v_STREAM_handle_messages(C) == 0) { /* TODO: end connection */ return; } } else { if( vs_handle_handshake(C, NULL) == -1 ) { /* TODO: end connection */ return; } msgarg.opcode = WSLAY_BINARY_FRAME; msgarg.msg = (uint8_t*)io_ctx->buf; msgarg.msg_length = io_ctx->buf_size; wslay_event_queue_msg(ctx, &msgarg); } } else if(arg->opcode == WSLAY_TEXT_FRAME) { v_print_log(VRS_PRINT_ERROR, "WebSocket text frame is not supported\n"); return; } } else { /* Print opcode of control message */ v_print_log(VRS_PRINT_DEBUG_MSG, "WS Callback Received Ctrl Message: opcode: %d\n", arg->opcode); /* Is it closing message? */ if(arg->opcode & WSLAY_CONNECTION_CLOSE) { v_print_log(VRS_PRINT_DEBUG_MSG, "Close message with code: %d, message: %s\n", arg->status_code, arg->msg); if(session->stream_conn->host_state == TCP_SERVER_STATE_CLOSING) { session->stream_conn->host_state = TCP_SERVER_STATE_CLOSED; } else { session->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; wslay_event_queue_close(ctx, WSLAY_CODE_NORMAL_CLOSURE, (uint8_t*)"Closing connection", 15); } } } }
/** * \brief WebSocket callback function for received message */ void vs_ws_recv_msg_callback(wslay_event_context_ptr wslay_ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { struct vContext *C = (struct vContext*)user_data; struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct VSession *session = CTX_current_session(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); int ret; if(!wslay_is_ctrl_frame(arg->opcode)) { /* Verse server uses only binary message for communication */ if(arg->opcode == WSLAY_BINARY_FRAME) { struct wslay_event_msg msgarg; #if DEBUG_WEB_SOCKET unsigned int i; v_print_log(VRS_PRINT_DEBUG_MSG, "WS Callback received binary message\n"); v_print_log(VRS_PRINT_DEBUG_MSG, "Binary dump\n"); /* Print dump of received data */ for(i=0; i<arg->msg_length; i++) { v_print_log_simple(VRS_PRINT_DEBUG_MSG, "%d,", arg->msg[i]); } v_print_log_simple(VRS_PRINT_DEBUG_MSG, "\n"); #endif /* Copy received data to IO context */ memcpy(io_ctx->buf, arg->msg, arg->msg_length); io_ctx->buf_size = arg->msg_length; if(session->stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { if(v_STREAM_handle_messages(C) != 0) { /* When some payload data were received, then poke data thread */ sem_post(vs_ctx->data.sem); } else { /* End connection */ session->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; /* Try to close connection with WebSocket client */ wslay_event_queue_close(wslay_ctx, WSLAY_CODE_PROTOCOL_ERROR, (uint8_t*)"Wrong command", /* Close message */ 13); /* The length of close message */ return; } } else { if( vs_handle_handshake(C) == -1 ) { /* End connection */ session->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; /* Try to close connection with WebSocket client */ wslay_event_queue_close(wslay_ctx, WSLAY_CODE_PROTOCOL_ERROR, (uint8_t*)"Wrong command", /* Close message */ 13); /* The length of close message */ return; } /* During handshake send response immediately. */ /* TODO: optionally send message fragmented, when it is needed using: * wslay_event_queue_fragmented_msg() */ msgarg.opcode = WSLAY_BINARY_FRAME; msgarg.msg = (uint8_t*)io_ctx->buf; msgarg.msg_length = io_ctx->buf_size; /* Queue message for sending */ if((ret = wslay_event_queue_msg(wslay_ctx, &msgarg)) != 0) { v_print_log(VRS_PRINT_ERROR, "Unable to queue message to WebSocket: %d\n", ret); return; } else { v_print_log(VRS_PRINT_DEBUG_MSG, "WebSocket message successfully queued\n"); } } } else if(arg->opcode == WSLAY_TEXT_FRAME) { v_print_log(VRS_PRINT_ERROR, "WebSocket text frame is not supported\n"); return; } } else { /* Print opcode of control message */ v_print_log(VRS_PRINT_DEBUG_MSG, "WS Callback Received Ctrl Message: opcode: %d\n", arg->opcode); /* Is it closing message? */ if(arg->opcode & WSLAY_CONNECTION_CLOSE) { v_print_log(VRS_PRINT_DEBUG_MSG, "Close message with code: %d, message: %s\n", arg->status_code, arg->msg); /* When this control message was received at second time, then * switch to the state CLOSED. Otherwise switch to the state * CLOSING */ if(session->stream_conn->host_state == TCP_SERVER_STATE_CLOSING) { session->stream_conn->host_state = TCP_SERVER_STATE_CLOSED; } else { session->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; /* When server wasn't in the state closing, then send * confirmation to the client, that this connection will be * closed */ wslay_event_queue_close(wslay_ctx, WSLAY_CODE_NORMAL_CLOSURE, (uint8_t*)"Closing connection", 15); } } } }
HIDDEN void ws_input(struct transaction_t *txn) { struct ws_context *ctx = (struct ws_context *) txn->ws_ctx; wslay_event_context_ptr ev = ctx->event; int want_read = wslay_event_want_read(ev); int want_write = wslay_event_want_write(ev); int goaway = txn->flags.conn & CONN_CLOSE; syslog(LOG_DEBUG, "ws_input() eof: %d, want read: %d, want write: %d", txn->conn->pin->eof, want_read, want_write); if (want_read && !goaway) { /* Read frame(s) */ if (txn->conn->sess_ctx) { /* Data has been read into request body */ ctx->h2_pin = prot_readmap(buf_base(&txn->req_body.payload), buf_len(&txn->req_body.payload)); } int r = wslay_event_recv(ev); if (txn->conn->sess_ctx) { buf_reset(&txn->req_body.payload); prot_free(ctx->h2_pin); } if (!r) { /* Successfully received frames */ syslog(LOG_DEBUG, "ws_event_recv: success"); } else if (r == WSLAY_ERR_NO_MORE_MSG) { /* Client closed connection */ syslog(LOG_DEBUG, "client closed connection"); txn->flags.conn = CONN_CLOSE; } else { /* Failure */ syslog(LOG_DEBUG, "ws_event_recv: %s (%s)", wslay_strerror(r), prot_error(txn->conn->pin)); goaway = 1; if (r == WSLAY_ERR_CALLBACK_FAILURE) { /* Client timeout */ txn->error.desc = prot_error(txn->conn->pin); } else { txn->error.desc = wslay_strerror(r); } } } else if (!want_write) { /* Connection is done */ syslog(LOG_DEBUG, "connection closed"); txn->flags.conn = CONN_CLOSE; } if (goaway) { /* Tell client we are closing session */ syslog(LOG_WARNING, "%s, closing connection", txn->error.desc); syslog(LOG_DEBUG, "wslay_event_queue_close()"); int r = wslay_event_queue_close(ev, WSLAY_CODE_GOING_AWAY, (uint8_t *) txn->error.desc, strlen(txn->error.desc)); if (r) { syslog(LOG_ERR, "wslay_event_queue_close: %s", wslay_strerror(r)); } txn->flags.conn = CONN_CLOSE; } /* Write frame(s) */ ws_output(txn); return; }
static void on_msg_recv_cb(wslay_event_context_ptr ev, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { struct transaction_t *txn = (struct transaction_t *) user_data; struct ws_context *ctx = (struct ws_context *) txn->ws_ctx; struct buf inbuf = BUF_INITIALIZER, outbuf = BUF_INITIALIZER; struct wslay_event_msg msgarg = { arg->opcode, NULL, 0 }; uint8_t rsv = WSLAY_RSV_NONE; double cmdtime, nettime; const char *err_msg; int r, err_code = 0; /* Place client request into a buf */ buf_init_ro(&inbuf, (const char *) arg->msg, arg->msg_length); /* Decompress request, if necessary */ if (wslay_get_rsv1(arg->rsv)) { /* Add trailing 4 bytes */ buf_appendmap(&inbuf, "\x00\x00\xff\xff", 4); r = zlib_decompress(txn, buf_base(&inbuf), buf_len(&inbuf)); if (r) { syslog(LOG_ERR, "on_msg_recv_cb(): zlib_decompress() failed"); err_code = WSLAY_CODE_PROTOCOL_ERROR; err_msg = DECOMP_FAILED_ERR; goto err; } buf_move(&inbuf, &txn->zbuf); } /* Log the uncompressed client request */ buf_truncate(&ctx->log, ctx->log_tail); buf_appendcstr(&ctx->log, " ("); if (txn->strm_ctx) { buf_printf(&ctx->log, "stream-id=%d; ", http2_get_streamid(txn->strm_ctx)); } buf_printf(&ctx->log, "opcode=%s; rsv=0x%x; length=%ld", wslay_str_opcode(arg->opcode), arg->rsv, arg->msg_length); switch (arg->opcode) { case WSLAY_CONNECTION_CLOSE: buf_printf(&ctx->log, "; status=%d; msg='%s'", arg->status_code, buf_len(&inbuf) ? buf_cstring(&inbuf)+2 : ""); txn->flags.conn = CONN_CLOSE; break; case WSLAY_TEXT_FRAME: case WSLAY_BINARY_FRAME: if (txn->conn->logfd != -1) { /* Telemetry logging */ struct iovec iov[2]; int niov = 0; assert(!buf_len(&txn->buf)); buf_printf(&txn->buf, "<%ld<", time(NULL)); /* timestamp */ WRITEV_ADD_TO_IOVEC(iov, niov, buf_base(&txn->buf), buf_len(&txn->buf)); WRITEV_ADD_TO_IOVEC(iov, niov, buf_base(&inbuf), buf_len(&inbuf)); writev(txn->conn->logfd, iov, niov); buf_reset(&txn->buf); } /* Process the request */ r = ctx->data_cb(&inbuf, &outbuf, &ctx->log, &ctx->cb_rock); if (r) { err_code = (r == HTTP_SERVER_ERROR ? WSLAY_CODE_INTERNAL_SERVER_ERROR : WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA); err_msg = error_message(r); goto err; } if (txn->conn->logfd != -1) { /* Telemetry logging */ struct iovec iov[2]; int niov = 0; assert(!buf_len(&txn->buf)); buf_printf(&txn->buf, ">%ld>", time(NULL)); /* timestamp */ WRITEV_ADD_TO_IOVEC(iov, niov, buf_base(&txn->buf), buf_len(&txn->buf)); WRITEV_ADD_TO_IOVEC(iov, niov, buf_base(&outbuf), buf_len(&outbuf)); writev(txn->conn->logfd, iov, niov); buf_reset(&txn->buf); } /* Compress the server response, if supported by the client */ if (ctx->ext & EXT_PMCE_DEFLATE) { r = zlib_compress(txn, ctx->pmce.deflate.no_context ? COMPRESS_START : 0, buf_base(&outbuf), buf_len(&outbuf)); if (r) { syslog(LOG_ERR, "on_msg_recv_cb(): zlib_compress() failed"); err_code = WSLAY_CODE_INTERNAL_SERVER_ERROR; err_msg = COMP_FAILED_ERR; goto err; } /* Trim the trailing 4 bytes */ buf_truncate(&txn->zbuf, buf_len(&txn->zbuf) - 4); buf_move(&outbuf, &txn->zbuf); rsv |= WSLAY_RSV1_BIT; } /* Queue the server response */ msgarg.msg = (const uint8_t *) buf_base(&outbuf); msgarg.msg_length = buf_len(&outbuf); wslay_event_queue_msg_ex(ev, &msgarg, rsv); /* Log the server response */ buf_printf(&ctx->log, ") => \"Success\" (opcode=%s; rsv=0x%x; length=%ld", wslay_str_opcode(msgarg.opcode), rsv, msgarg.msg_length); break; } err: if (err_code) { size_t err_msg_len = strlen(err_msg); syslog(LOG_DEBUG, "wslay_event_queue_close()"); wslay_event_queue_close(ev, err_code, (uint8_t *) err_msg, err_msg_len); /* Log the server response */ buf_printf(&ctx->log, ") => \"Fail\" (opcode=%s; rsv=0x%x; length=%ld" "; status=%d; msg='%s'", wslay_str_opcode(WSLAY_CONNECTION_CLOSE), rsv, err_msg_len, err_code, err_msg); } /* Add timing stats */ cmdtime_endtimer(&cmdtime, &nettime); buf_printf(&ctx->log, ") [timing: cmd=%f net=%f total=%f]", cmdtime, nettime, cmdtime + nettime); syslog(LOG_INFO, "%s", buf_cstring(&ctx->log)); buf_free(&inbuf); buf_free(&outbuf); }