void h2o_websocket_proceed(h2o_websocket_conn_t *conn) { int handled; /* run the loop until getting to a point where no more progress can be achieved */ do { handled = 0; if (!h2o_socket_is_writing(conn->sock) && wslay_event_want_write(conn->ws_ctx)) { if (wslay_event_send(conn->ws_ctx) != 0) { goto Close; } handled = 1; } if (conn->sock->input->size != 0 && wslay_event_want_read(conn->ws_ctx)) { if (wslay_event_recv(conn->ws_ctx) != 0) { goto Close; } handled = 1; } } while (handled); if (wslay_event_want_read(conn->ws_ctx)) { h2o_socket_read_start(conn->sock, on_recv); } else if (h2o_socket_is_writing(conn->sock) || wslay_event_want_write(conn->ws_ctx)) { h2o_socket_read_stop(conn->sock); } else { /* nothing is going on... close the socket */ goto Close; } return; Close: on_close(conn); }
void h2o_websocket_proceed(h2o_websocket_conn_t *conn) { int handled; /* run the loop until getting to a point where no more progress can be achieved */ do { handled = 0; if (!h2o_socket_is_writing(conn->sock) && wslay_event_want_write(conn->ws_ctx)) { if (wslay_event_send(conn->ws_ctx) != 0) { goto Close; } /* avoid infinite loop when user want send more bufers count than ours in on_msg_callback() */ if (conn->_write_buf.cnt < sizeof(conn->_write_buf.bufs) / sizeof(conn->_write_buf.bufs[0])) { handled = 1; } } if (conn->sock->input->size != 0 && wslay_event_want_read(conn->ws_ctx)) { if (wslay_event_recv(conn->ws_ctx) != 0) { goto Close; } handled = 1; } } while (handled); if (!h2o_socket_is_writing(conn->sock) && conn->_write_buf.cnt > 0) { /* write */ h2o_socket_write(conn->sock, conn->_write_buf.bufs, conn->_write_buf.cnt, on_write_complete); } if (wslay_event_want_read(conn->ws_ctx)) { h2o_socket_read_start(conn->sock, on_recv); } else if (h2o_socket_is_writing(conn->sock) || wslay_event_want_write(conn->ws_ctx)) { h2o_socket_read_stop(conn->sock); } else { /* nothing is going on... close the socket */ goto Close; } return; Close: on_close(conn); }
HIDDEN void ws_output(struct transaction_t *txn) { struct ws_context *ctx = (struct ws_context *) txn->ws_ctx; wslay_event_context_ptr ev = ctx->event; int want_write = wslay_event_want_write(ev); syslog(LOG_DEBUG, "ws_output() eof: %d, want write: %d", txn->conn->pin->eof, want_write); if (want_write) { /* Send queued frame(s) */ int r = wslay_event_send(ev); if (r) { syslog(LOG_ERR, "wslay_event_send: %s", wslay_strerror(r)); txn->flags.conn = CONN_CLOSE; } } }
int websocket_handler(websocket_t *websocket) { int r; int fd = websocket->fd; int timeout = 0; fd_set read_fds; fd_set write_fds; wslay_event_context_ptr ctx = (wslay_event_context_ptr) websocket->ctx; struct timeval tv; while (websocket->state != WEBSOCKET_STOP) { FD_ZERO(&read_fds); FD_ZERO(&write_fds); if (wslay_event_want_read(ctx)) { FD_SET(fd, &read_fds); } if (wslay_event_want_write(ctx)) { FD_SET(fd, &write_fds); } tv.tv_sec = (WEBSOCKET_HANDLER_TIMEOUT / 1000); tv.tv_usec = ((WEBSOCKET_HANDLER_TIMEOUT % 1000) * 1000); r = select(fd + 1, &read_fds, &write_fds, NULL, &tv); if (r < 0) { if (errno == EAGAIN || errno == EBUSY || errno == EINTR) { continue; } WEBSOCKET_DEBUG("select function returned errno == %d\n", errno); return WEBSOCKET_SOCKET_ERROR; } else if (r == 0) { if (WEBSOCKET_HANDLER_TIMEOUT != 0) { timeout++; if ((WEBSOCKET_HANDLER_TIMEOUT * timeout) >= (WEBSOCKET_PING_INTERVAL * 10)) { timeout = 0; if (websocket_ping_counter(websocket) != WEBSOCKET_SUCCESS) { return WEBSOCKET_SOCKET_ERROR; } } } continue; } else { timeout = 0; if (FD_ISSET(fd, &read_fds)) { r = wslay_event_recv(ctx); if (r != WEBSOCKET_SUCCESS) { WEBSOCKET_DEBUG("fail to process recv event, result : %d\n", r); websocket_update_state(websocket, WEBSOCKET_ERROR); return WEBSOCKET_SOCKET_ERROR; } } if (FD_ISSET(fd, &write_fds)) { r = wslay_event_send(ctx); if (r != WEBSOCKET_SUCCESS) { WEBSOCKET_DEBUG("fail to process send event, result : %d\n", r); websocket_update_state(websocket, WEBSOCKET_ERROR); return WEBSOCKET_SOCKET_ERROR; } } } } return WEBSOCKET_SUCCESS; }
/* * Communicate with the client. This function performs HTTP handshake * and WebSocket data transfer until close handshake is done or an * error occurs. *fd* is the file descriptor of the connection to the * client. This function returns 0 if it succeeds, or returns 0. */ int communicate(int fd) { wslay_event_context_ptr ctx; struct wslay_event_callbacks callbacks = { recv_callback, send_callback, NULL, NULL, NULL, NULL, on_msg_recv_callback }; struct Session session = { fd }; int val = 1; struct pollfd event; int res = 0; if(http_handshake(fd) == -1) { return -1; } if(make_non_block(fd) == -1) { return -1; } if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)) == -1) { perror("setsockopt: TCP_NODELAY"); return -1; } memset(&event, 0, sizeof(struct pollfd)); event.fd = fd; event.events = POLLIN; wslay_event_context_server_init(&ctx, &callbacks, &session); /* * Event loop: basically loop until both wslay_event_want_read(ctx) * and wslay_event_want_write(ctx) return 0. */ while(wslay_event_want_read(ctx) || wslay_event_want_write(ctx)) { int r; while((r = poll(&event, 1, -1)) == -1 && errno == EINTR); if(r == -1) { perror("poll"); res = -1; break; } if(((event.revents & POLLIN) && wslay_event_recv(ctx) != 0) || ((event.revents & POLLOUT) && wslay_event_send(ctx) != 0) || (event.revents & (POLLERR | POLLHUP | POLLNVAL))) { /* * If either wslay_event_recv() or wslay_event_send() return * non-zero value, it means serious error which prevents wslay * library from processing further data, so WebSocket connection * must be closed. */ res = -1; break; } event.events = 0; if(wslay_event_want_read(ctx)) { event.events |= POLLIN; } if(wslay_event_want_write(ctx)) { event.events |= POLLOUT; } } return res; }
static mrb_value mrb_wslay_event_want_write(mrb_state *mrb, mrb_value self) { return mrb_bool_value(wslay_event_want_write(((mrb_wslay_user_data *) DATA_PTR(self))->ctx)); }
bool want_write() { return wslay_event_want_write(ctx_); }
/** * \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 }; vsession->flags |= VRS_TP_WEBSOCKET; /* Set socket non-blocking */ flags = fcntl(io_ctx->sockfd, F_GETFL, 0); if( fcntl(io_ctx->sockfd, F_SETFL, flags | O_NONBLOCK) == -1) { v_print_log(VRS_PRINT_ERROR, "fcntl(): %s\n", strerror(errno)); goto end; } /* Listen for HTTP request from web client and try to do * WebSocket handshake */ if(http_handshake(io_ctx->sockfd) != 0 ) { goto end; } /* Try to get size of TCP buffer */ int_size = sizeof(int_size); if( getsockopt(io_ctx->sockfd, SOL_SOCKET, SO_RCVBUF, (void *)&stream_conn->socket_buffer_size, &int_size) != 0) { v_print_log(VRS_PRINT_ERROR, "Unable to get TCP buffer size of WebSocket connection.\n"); goto end; } r_message = (struct VMessage*)calloc(1, sizeof(struct VMessage)); s_message = (struct VMessage*)calloc(1, sizeof(struct VMessage)); if(r_message == NULL || s_message == NULL) { v_print_log(VRS_PRINT_ERROR, "Out of memory\n"); goto end; } CTX_r_message_set(C, r_message); CTX_s_message_set(C, s_message); /* Try to initialize WebSocket server context */ if(wslay_event_context_server_init(&wslay_ctx, &callbacks, C) != 0) { v_print_log(VRS_PRINT_ERROR, "Unable to initialize WebSocket server context\n"); goto end; } /* Set initial state */ 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 WebSocket state: RESPOND_methods\n"); printf("%c[%dm", 27, 0); } /* "Never ending" loop */ while(vsession->stream_conn->host_state != TCP_SERVER_STATE_CLOSED && ( wslay_event_want_read(wslay_ctx) == 1 || wslay_event_want_write(wslay_ctx) == 1) ) { /* When server is going to stop, then close connection with WebSocket * client */ if(vs_ctx->state != SERVER_STATE_READY) { v_print_log(VRS_PRINT_DEBUG_MSG, "Closing WebSocket connection: Server shutdown\n"); 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); if(wslay_event_want_read(wslay_ctx) == 1) { /* v_print_log(VRS_PRINT_DEBUG_MSG, "Waiting for WebSocket message ...\n"); */ FD_SET(io_ctx->sockfd, &read_set); } /* Initialize write set */ FD_ZERO(&write_set); if(wslay_event_want_write(wslay_ctx) == 1) { #if DEBUG_WEB_SOCKET v_print_log(VRS_PRINT_DEBUG_MSG, "Going to write message to WebSocket ...\n"); #endif 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, /* Don't care about exception */ &tv)) == -1) { 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; } } if (FD_ISSET(io_ctx->sockfd, &write_set)) { if( wslay_event_send(wslay_ctx) != 0 ) { goto end; } } } else if(ret == 0 && stream_conn->host_state != TCP_SERVER_STATE_STREAM_OPEN) { /* When handshake is not finished during VRS_TIMEOT seconds, then * close connection with WebSocket client. */ v_print_log(VRS_PRINT_DEBUG_MSG, "Closing WebSocket connection: Handshake timed-out\n"); stream_conn->host_state = TCP_SERVER_STATE_CLOSING; wslay_event_queue_close(wslay_ctx, WSLAY_CODE_PROTOCOL_ERROR, (uint8_t*)"Handshake timed-out", /* Close message */ 19); /* The length of close message */ } if(stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { /* Check if there is any command in outgoing 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 WebSocket state: CLOSING\n"); printf("%c[%dm", 27, 0); } /* Set up TCP CLOSING state (non-blocking) */ vs_CLOSING(C); /* Receive and Send messages are not necessary 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 WebSocket 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 WebSocket state: LISTEN\n"); printf("%c[%dm", 27, 0); } free(C); C = NULL; pthread_exit(NULL); return NULL; }
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; }