void ngx_http_push_stream_websocket_reading(ngx_http_request_t *r) { ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module); u_char buf[8]; ngx_err_t err; ngx_event_t *rev; ngx_connection_t *c; ngx_http_push_stream_frame_t frame; ngx_str_t *aux; uint64_t i; ngx_pool_t *temp_pool = NULL; c = r->connection; rev = c->read; #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { if (!rev->pending_eof) { return; } rev->eof = 1; c->error = 1; err = rev->kq_errno; goto closed; } #endif //reading frame header if (ngx_http_push_stream_recv(c, rev, &err, buf, 2) == NGX_ERROR) { goto closed; } frame.fin = (buf[0] >> 7) & 1; frame.rsv1 = (buf[0] >> 6) & 1; frame.rsv2 = (buf[0] >> 5) & 1; frame.rsv3 = (buf[0] >> 4) & 1; frame.opcode = buf[0] & 0xf; frame.mask = (buf[1] >> 7) & 1; frame.payload_len = buf[1] & 0x7f; if (frame.payload_len == 126) { if (ngx_http_push_stream_recv(c, rev, &err, buf, 2) == NGX_ERROR) { goto closed; } uint16_t len; ngx_memcpy(&len, buf, 2); frame.payload_len = ntohs(len); } else if (frame.payload_len == 127) { if (ngx_http_push_stream_recv(c, rev, &err, buf, 8) == NGX_ERROR) { goto closed; } uint64_t len; ngx_memcpy(&len, buf, 8); frame.payload_len = ngx_http_push_stream_ntohll(len); } if (frame.mask && (ngx_http_push_stream_recv(c, rev, &err, frame.mask_key, 4) == NGX_ERROR)) { goto closed; } if (frame.payload_len > 0) { //create a temporary pool to allocate temporary elements if ((temp_pool = ngx_create_pool(4096, r->connection->log)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for temporary pool"); ngx_http_finalize_request(r, NGX_OK); return; } aux = ngx_http_push_stream_create_str(temp_pool, frame.payload_len); if (ngx_http_push_stream_recv(c, rev, &err, aux->data, (ssize_t) frame.payload_len) == NGX_ERROR) { goto closed; } if (cf->websocket_allow_publish && (frame.opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_TEXT_OPCODE)) { frame.payload = aux->data; if (frame.mask) { for (i = 0; i < frame.payload_len; i++) { frame.payload[i] = frame.payload[i] ^ frame.mask_key[i % 4]; } } ngx_http_push_stream_subscriber_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_push_stream_module); ngx_http_push_stream_subscription_t *subscription = (ngx_http_push_stream_subscription_t *)ngx_queue_head(&ctx->subscriber->subscriptions_sentinel.queue); if (ngx_http_push_stream_add_msg_to_channel(r, &subscription->channel->id, frame.payload, frame.payload_len, NULL, NULL, temp_pool) == NULL) { ngx_http_finalize_request(r, NGX_OK); ngx_destroy_pool(temp_pool); return; } else { ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_WEBSOCKET_PING_LAST_FRAME_BYTE, sizeof(NGX_HTTP_PUSH_STREAM_WEBSOCKET_PING_LAST_FRAME_BYTE), 1); } } ngx_destroy_pool(temp_pool); } if (frame.opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_OPCODE) { ngx_http_push_stream_send_response_finalize(r); } /* aio does not call this handler */ if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && rev->active) { if (ngx_del_event(rev, NGX_READ_EVENT, 0) != NGX_OK) { ngx_http_finalize_request(r, NGX_OK); } } return; closed: if (temp_pool != NULL) { ngx_destroy_pool(temp_pool); } if (err) { rev->error = 1; } ngx_log_error(NGX_LOG_INFO, c->log, err, "client closed prematurely connection"); ngx_http_finalize_request(r, NGX_OK); }
void ngx_http_push_stream_websocket_reading(ngx_http_request_t *r) { ngx_http_push_stream_main_conf_t *mcf = ngx_http_get_module_main_conf(r, ngx_http_push_stream_module); ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module); ngx_http_push_stream_module_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_push_stream_module); ngx_int_t rc = NGX_OK; ngx_event_t *rev; ngx_connection_t *c; uint64_t i; ngx_buf_t buf; ngx_queue_t *q; ngx_http_push_stream_set_buffer(&buf, ctx->frame->header, ctx->frame->last, 8); c = r->connection; rev = c->read; for (;;) { if (c->error || c->timedout || c->close || c->destroyed || rev->closed || rev->eof) { goto finalize; } switch (ctx->frame->step) { case NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_START_STEP: //reading frame header if ((rc = ngx_http_push_stream_recv(c, rev, &buf, 2)) != NGX_OK) { goto exit; } ctx->frame->fin = (ctx->frame->header[0] >> 7) & 1; ctx->frame->rsv1 = (ctx->frame->header[0] >> 6) & 1; ctx->frame->rsv2 = (ctx->frame->header[0] >> 5) & 1; ctx->frame->rsv3 = (ctx->frame->header[0] >> 4) & 1; ctx->frame->opcode = ctx->frame->header[0] & 0xf; ctx->frame->mask = (ctx->frame->header[1] >> 7) & 1; ctx->frame->payload_len = ctx->frame->header[1] & 0x7f; ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_REAL_SIZE_STEP; break; case NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_REAL_SIZE_STEP: if (ctx->frame->payload_len == 126) { if ((rc = ngx_http_push_stream_recv(c, rev, &buf, 2)) != NGX_OK) { goto exit; } uint16_t len; ngx_memcpy(&len, ctx->frame->header, 2); ctx->frame->payload_len = ntohs(len); } else if (ctx->frame->payload_len == 127) { if ((rc = ngx_http_push_stream_recv(c, rev, &buf, 8)) != NGX_OK) { goto exit; } uint64_t len; ngx_memcpy(&len, ctx->frame->header, 8); ctx->frame->payload_len = ngx_http_push_stream_ntohll(len); } ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_MASK_KEY_STEP; break; case NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_MASK_KEY_STEP: if (ctx->frame->mask) { if ((rc = ngx_http_push_stream_recv(c, rev, &buf, 4)) != NGX_OK) { goto exit; } ngx_memcpy(ctx->frame->mask_key, buf.start, 4); } ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_PAYLOAD_STEP; break; case NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_PAYLOAD_STEP: if ( (ctx->frame->opcode != NGX_HTTP_PUSH_STREAM_WEBSOCKET_TEXT_OPCODE) && (ctx->frame->opcode != NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_OPCODE) && (ctx->frame->opcode != NGX_HTTP_PUSH_STREAM_WEBSOCKET_PING_OPCODE) && (ctx->frame->opcode != NGX_HTTP_PUSH_STREAM_WEBSOCKET_PONG_OPCODE) ) { rc = ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_LAST_FRAME_BYTE, sizeof(NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_LAST_FRAME_BYTE), 1); goto finalize; } if (ctx->frame->payload_len > 0) { //create a temporary pool to allocate temporary elements if (ctx->temp_pool == NULL) { if ((ctx->temp_pool = ngx_create_pool(4096, r->connection->log)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for temporary pool"); goto finalize; } if ((ctx->frame->payload = ngx_pcalloc(ctx->temp_pool, ctx->frame->payload_len)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for payload"); goto finalize; } ctx->frame->last = ctx->frame->payload; } ngx_http_push_stream_set_buffer(&buf, ctx->frame->payload, ctx->frame->last, ctx->frame->payload_len); if ((rc = ngx_http_push_stream_recv(c, rev, &buf, ctx->frame->payload_len)) != NGX_OK) { goto exit; } if (ctx->frame->mask) { for (i = 0; i < ctx->frame->payload_len; i++) { ctx->frame->payload[i] = ctx->frame->payload[i] ^ ctx->frame->mask_key[i % 4]; } } if (!ngx_http_push_stream_is_utf8(ctx->frame->payload, ctx->frame->payload_len)) { goto finalize; } if (cf->websocket_allow_publish && (ctx->frame->opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_TEXT_OPCODE)) { for (q = ngx_queue_head(&ctx->subscriber->subscriptions); q != ngx_queue_sentinel(&ctx->subscriber->subscriptions); q = ngx_queue_next(q)) { ngx_http_push_stream_subscription_t *subscription = ngx_queue_data(q, ngx_http_push_stream_subscription_t, queue); if (subscription->channel->for_events) { // skip events channel on publish by websocket connections continue; } if (ngx_http_push_stream_add_msg_to_channel(mcf, r->connection->log, subscription->channel, ctx->frame->payload, ctx->frame->payload_len, NULL, NULL, cf->store_messages, ctx->temp_pool) != NGX_OK) { goto finalize; } } } if (ctx->temp_pool != NULL) { ngx_destroy_pool(ctx->temp_pool); ctx->temp_pool = NULL; } } ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_START_STEP; ctx->frame->last = NULL; if (ctx->frame->opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_PING_OPCODE) { ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_WEBSOCKET_PONG_LAST_FRAME_BYTE, sizeof(NGX_HTTP_PUSH_STREAM_WEBSOCKET_PONG_LAST_FRAME_BYTE), 1); } if (ctx->frame->opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_OPCODE) { rc = ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_LAST_FRAME_BYTE, sizeof(NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_LAST_FRAME_BYTE), 1); goto finalize; } return; break; default: ngx_log_debug(NGX_LOG_DEBUG, c->log, 0, "push stream module: unknown websocket step (%d)", ctx->frame->step); goto finalize; break; } ngx_http_push_stream_set_buffer(&buf, ctx->frame->header, NULL, 8); } exit: if (rc == NGX_AGAIN) { ctx->frame->last = buf.last; if (!c->read->ready) { if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno, "push stream module: failed to restore read events"); goto finalize; } } } if (rc == NGX_ERROR) { rev->eof = 1; c->error = 1; ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno, "push stream module: client closed prematurely connection"); goto finalize; } return; finalize: ngx_http_push_stream_run_cleanup_pool_handler(r->pool, (ngx_pool_cleanup_pt) ngx_http_push_stream_cleanup_request_context); ngx_http_finalize_request(r, c->error ? NGX_HTTP_CLIENT_CLOSED_REQUEST : NGX_OK); }