static ngx_int_t ngx_http_push_stream_channels_statistics_handler(ngx_http_request_t *r) { char *pos = NULL; ngx_str_t *id = NULL; ngx_http_push_stream_channel_t *channel = NULL; ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module); r->keepalive = cf->keepalive; ngx_http_push_stream_set_expires(r, NGX_HTTP_PUSH_STREAM_EXPIRES_EPOCH, 0); // only accept GET method if (!(r->method & NGX_HTTP_GET)) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_TAG, &NGX_HTTP_PUSH_STREAM_TAG); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_COMMIT, &NGX_HTTP_PUSH_STREAM_COMMIT); // get and check channel id value id = ngx_http_push_stream_get_channel_id(r, cf); if ((id == NULL) || (id == NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID)) { if (id == NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE); } return NGX_HTTP_INTERNAL_SERVER_ERROR; } // if not specify a channel id, get info about all channels in a resumed way if (id == NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID) { return ngx_http_push_stream_send_response_all_channels_info_summarized(r); } if ((pos = ngx_strchr(id->data, '*')) != NULL) { ngx_str_t *aux = NULL; if (pos != (char *) id->data) { *pos = '\0'; id->len = ngx_strlen(id->data); aux = id; } return ngx_http_push_stream_send_response_all_channels_info_detailed(r, aux); } // if specify a channel id equals to ALL, get info about all channels in a detailed way if (ngx_memn2cmp(id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data, id->len, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.len) == 0) { return ngx_http_push_stream_send_response_all_channels_info_detailed(r, NULL); } // if specify a channel id != ALL, get info about specified channel if it exists // search for a existing channel with this id channel = ngx_http_push_stream_find_channel(id, r->connection->log); if (channel == NULL) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_FOUND, NULL); } return ngx_http_push_stream_send_response_channel_info(r, channel); }
static ngx_int_t ngx_http_push_stream_channels_statistics_handler(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); char *pos = NULL; ngx_http_push_stream_requested_channel_t *requested_channels, *requested_channel; ngx_queue_t *q; ngx_http_push_stream_set_expires(r, NGX_HTTP_PUSH_STREAM_EXPIRES_EPOCH, 0); // only accept GET method if (!(r->method & NGX_HTTP_GET)) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_TAG, &NGX_HTTP_PUSH_STREAM_TAG); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_COMMIT, &NGX_HTTP_PUSH_STREAM_COMMIT); //get channels ids requested_channels = ngx_http_push_stream_parse_channels_ids_from_path(r, r->pool); // if not specify a channel id, get info about all channels in a resumed way if ((requested_channels == NULL) || ngx_queue_empty(&requested_channels->queue)) { return ngx_http_push_stream_send_response_all_channels_info_summarized(r); } for (q = ngx_queue_head(&requested_channels->queue); q != ngx_queue_sentinel(&requested_channels->queue); q = ngx_queue_next(q)) { requested_channel = ngx_queue_data(q, ngx_http_push_stream_requested_channel_t, queue); // could not have a large size if ((mcf->max_channel_id_length != NGX_CONF_UNSET_UINT) && (requested_channel->id->len > mcf->max_channel_id_length)) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: channel id is larger than allowed %d", requested_channel->id->len); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE); } if ((pos = ngx_strchr(requested_channel->id->data, '*')) != NULL) { ngx_str_t *aux = NULL; if (pos != (char *) requested_channel->id->data) { *pos = '\0'; requested_channel->id->len = ngx_strlen(requested_channel->id->data); aux = requested_channel->id; } return ngx_http_push_stream_send_response_all_channels_info_detailed(r, aux); } // if specify a channel id equals to ALL, get info about all channels in a detailed way if (ngx_memn2cmp(requested_channel->id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data, requested_channel->id->len, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.len) == 0) { return ngx_http_push_stream_send_response_all_channels_info_detailed(r, NULL); } requested_channel->channel = ngx_http_push_stream_find_channel(requested_channel->id, r->connection->log, mcf); } // if specify a channels ids != ALL, get info about specified channels if they exists return ngx_http_push_stream_send_response_channels_info_detailed(r, requested_channels); }
static ngx_int_t ngx_http_push_stream_publisher_handler(ngx_http_request_t *r) { ngx_str_t *id = NULL; ngx_http_push_stream_channel_t *channel = NULL; ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module); r->keepalive = cf->keepalive; ngx_http_push_stream_set_expires(r, NGX_HTTP_PUSH_STREAM_EXPIRES_EPOCH, 0); if (cf->allowed_origins.len > 0) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, &cf->allowed_origins); } // only accept GET, POST and DELETE methods if enable publisher administration if ((cf->location_type == NGX_HTTP_PUSH_STREAM_PUBLISHER_MODE_ADMIN) && !(r->method & (NGX_HTTP_GET|NGX_HTTP_POST|NGX_HTTP_DELETE))) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_DELETE_METHODS); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } // only accept GET and POST methods if NOT enable publisher administration if ((cf->location_type != NGX_HTTP_PUSH_STREAM_PUBLISHER_MODE_ADMIN) && !(r->method & (NGX_HTTP_GET|NGX_HTTP_POST))) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_METHODS); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } // channel id is required id = ngx_http_push_stream_get_channel_id(r, cf); if ((id == NULL) || (id == NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID) || (id == NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID)) { if (id == NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: the $push_stream_channel_id variable is required but is not set"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE); } if (id == NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE); } return NGX_HTTP_INTERNAL_SERVER_ERROR; } // search for a existing channel with this id channel = ngx_http_push_stream_find_channel(id, r->connection->log); if (r->method == NGX_HTTP_POST) { return ngx_http_push_stream_publisher_handle_post(cf, r, id); } // GET or DELETE only make sense with a previous existing channel if (channel == NULL) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_FOUND, NULL); } if ((cf->location_type == NGX_HTTP_PUSH_STREAM_PUBLISHER_MODE_ADMIN) && (r->method == NGX_HTTP_DELETE)) { ngx_http_push_stream_delete_channel(id, r->pool); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_OK, &NGX_HTTP_PUSH_STREAM_CHANNEL_DELETED); } return ngx_http_push_stream_send_response_channel_info(r, channel); }
static ngx_int_t ngx_http_push_stream_respond_to_subscribers(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_queue_elem_t *subscribers_sentinel, ngx_http_push_stream_msg_t *msg) { ngx_http_push_stream_queue_elem_t *cur = subscribers_sentinel; if (subscribers_sentinel == NULL) { return NGX_ERROR; } if (msg != NULL) { // now let's respond to some requests! while ((cur = (ngx_http_push_stream_queue_elem_t *) ngx_queue_next(&cur->queue)) != subscribers_sentinel) { ngx_http_push_stream_subscriber_t *subscriber = (ngx_http_push_stream_subscriber_t *) cur->value; if (subscriber->longpolling) { ngx_http_push_stream_queue_elem_t *prev = (ngx_http_push_stream_queue_elem_t *) ngx_queue_prev(&cur->queue); ngx_http_push_stream_add_response_header(subscriber->request, &NGX_HTTP_PUSH_STREAM_HEADER_TRANSFER_ENCODING, &NGX_HTTP_PUSH_STREAM_HEADER_CHUNCKED); ngx_http_push_stream_add_polling_headers(subscriber->request, msg->time, msg->tag, subscriber->request->pool); ngx_http_send_header(subscriber->request); //Adam Konrad: putting If-Modified-Since and etag to the response body ngx_http_push_stream_send_http_time( subscriber->request, msg->time, msg->tag, subscriber->request->pool); ngx_http_push_stream_send_response_content_header(subscriber->request, ngx_http_get_module_loc_conf(subscriber->request, ngx_http_push_stream_module)); ngx_http_push_stream_send_response_message(subscriber->request, channel, msg, 1, 0); ngx_http_push_stream_send_response_finalize(subscriber->request); cur = prev; } else { if (ngx_http_push_stream_send_response_message(subscriber->request, channel, msg, 0, 0) != NGX_OK) { ngx_http_push_stream_queue_elem_t *prev = (ngx_http_push_stream_queue_elem_t *) ngx_queue_prev(&cur->queue); ngx_http_push_stream_send_response_finalize(subscriber->request); cur = prev; } else { ngx_http_push_stream_subscriber_ctx_t *ctx = ngx_http_get_module_ctx(subscriber->request, ngx_http_push_stream_module); ngx_http_push_stream_loc_conf_t *pslcf = ngx_http_get_module_loc_conf(subscriber->request, ngx_http_push_stream_module); ngx_http_push_stream_timer_reset(pslcf->ping_message_interval, ctx->ping_timer); } } } } return NGX_OK; }
static ngx_int_t ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r) { ngx_slab_pool_t *shpool = (ngx_slab_pool_t *)ngx_http_push_stream_shm_zone->shm.addr; ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module); ngx_http_push_stream_subscriber_t *worker_subscriber; ngx_http_push_stream_requested_channel_t *channels_ids, *cur; ngx_http_push_stream_subscriber_ctx_t *ctx; time_t if_modified_since; ngx_str_t *last_event_id, vv_time = ngx_null_string; ngx_str_t *push_mode; ngx_flag_t polling, longpolling; ngx_int_t rc; ngx_int_t status_code; ngx_str_t *explain_error_message; // add headers to support cross domain requests if (cf->allowed_origins.len > 0) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, &cf->allowed_origins); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_METHODS, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, &NGX_HTTP_PUSH_STREAM_ALLOWED_HEADERS); } if (r->method & NGX_HTTP_OPTIONS) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_OK, NULL); } // only accept GET method if (!(r->method & NGX_HTTP_GET)) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } if ((ctx = ngx_http_push_stream_add_request_context(r)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to create request context"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } //get channels ids and backtracks from path channels_ids = ngx_http_push_stream_parse_channels_ids_from_path(r, ctx->temp_pool); if ((channels_ids == NULL) || ngx_queue_empty(&channels_ids->queue)) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: the $push_stream_channels_path variable is required but is not set"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE); } //validate channels: name, length and quantity. check if channel exists when authorized_channels_only is on. check if channel is full of subscribers if (ngx_http_push_stream_validate_channels(r, channels_ids, &status_code, &explain_error_message) == NGX_ERROR) { return ngx_http_push_stream_send_only_header_response(r, status_code, explain_error_message); } if (cf->last_received_message_time != NULL) { ngx_http_push_stream_complex_value(r, cf->last_received_message_time, &vv_time); } else if (r->headers_in.if_modified_since != NULL) { vv_time = r->headers_in.if_modified_since->value; } // get control headers if_modified_since = vv_time.len ? ngx_http_parse_time(vv_time.data, vv_time.len) : -1; last_event_id = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_LAST_EVENT_ID); push_mode = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_MODE); polling = ((cf->location_type == NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_POLLING) || ((push_mode != NULL) && (push_mode->len == NGX_HTTP_PUSH_STREAM_MODE_POLLING.len) && (ngx_strncasecmp(push_mode->data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.len) == 0))); longpolling = ((cf->location_type == NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_LONGPOLLING) || ((push_mode != NULL) && (push_mode->len == NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.len) && (ngx_strncasecmp(push_mode->data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.len) == 0))); if (polling || longpolling) { ngx_int_t result = ngx_http_push_stream_subscriber_polling_handler(r, channels_ids, if_modified_since, last_event_id, longpolling, ctx->temp_pool); if (ctx->temp_pool != NULL) { ngx_destroy_pool(ctx->temp_pool); ctx->temp_pool = NULL; } return result; } ctx->padding = ngx_http_push_stream_get_padding_by_user_agent(r); // stream access if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_TRANSFER_ENCODING, &NGX_HTTP_PUSH_STREAM_HEADER_CHUNCKED); ngx_http_send_header(r); // sending response content header if (ngx_http_push_stream_send_response_content_header(r, cf) == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: could not send content header to subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_shmtx_lock(&shpool->mutex); rc = ngx_http_push_stream_registry_subscriber_locked(r, worker_subscriber); ngx_shmtx_unlock(&shpool->mutex); if (rc == NGX_ERROR) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } // adding subscriber to channel(s) and send backtrack messages cur = channels_ids; while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) { if (ngx_http_push_stream_subscriber_assign_channel(shpool, cf, r, cur, if_modified_since, last_event_id, worker_subscriber, ctx->temp_pool) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } if (ctx->temp_pool != NULL) { ngx_destroy_pool(ctx->temp_pool); ctx->temp_pool = NULL; } return NGX_DONE; }
static ngx_int_t ngx_http_push_stream_subscriber_polling_handler(ngx_http_request_t *r, ngx_http_push_stream_requested_channel_t *channels_ids, time_t if_modified_since, ngx_str_t *last_event_id, ngx_flag_t longpolling, ngx_pool_t *temp_pool) { ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module); ngx_slab_pool_t *shpool = (ngx_slab_pool_t *)ngx_http_push_stream_shm_zone->shm.addr; ngx_http_push_stream_subscriber_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_push_stream_module); ngx_http_push_stream_requested_channel_t *cur; ngx_http_push_stream_subscriber_t *worker_subscriber; ngx_http_push_stream_channel_t *channel; ngx_http_push_stream_subscription_t *subscription; ngx_str_t *etag = NULL, vv_etag = ngx_null_string; ngx_int_t tag; time_t greater_message_time; ngx_int_t greater_message_tag; ngx_flag_t has_message_to_send = 0; ngx_str_t callback_function_name; if (cf->last_received_message_tag != NULL) { ngx_http_push_stream_complex_value(r, cf->last_received_message_tag, &vv_etag); etag = vv_etag.len ? &vv_etag : NULL; } else { etag = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_IF_NONE_MATCH); } if (ngx_http_arg(r, NGX_HTTP_PUSH_STREAM_CALLBACK.data, NGX_HTTP_PUSH_STREAM_CALLBACK.len, &callback_function_name) == NGX_OK) { ngx_http_push_stream_unescape_uri(&callback_function_name); if ((ctx->callback = ngx_http_push_stream_get_formatted_chunk(callback_function_name.data, callback_function_name.len, r->pool)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for callback function name"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } } tag = ((etag != NULL) && ((tag = ngx_atoi(etag->data, etag->len)) != NGX_ERROR)) ? ngx_abs(tag) : -1; greater_message_tag = tag; greater_message_time = if_modified_since = (if_modified_since < 0) ? 0 : if_modified_since; ngx_shmtx_lock(&shpool->mutex); // check if has any message to send cur = channels_ids; while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) { channel = ngx_http_push_stream_find_channel(cur->id, r->connection->log); if (channel == NULL) { // channel not found ngx_shmtx_unlock(&shpool->mutex); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate shared memory for channel %s", cur->id->data); return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_push_stream_has_old_messages_to_send(channel, cur->backtrack_messages, if_modified_since, tag, greater_message_time, greater_message_tag, last_event_id)) { has_message_to_send = 1; if (channel->last_message_time > greater_message_time) { greater_message_time = channel->last_message_time; greater_message_tag = channel->last_message_tag; } else { if ((channel->last_message_time == greater_message_time) && (channel->last_message_tag > greater_message_tag) ) { greater_message_tag = channel->last_message_tag; } } } } if (longpolling && !has_message_to_send) { // long polling mode without messages if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) { ngx_shmtx_unlock(&shpool->mutex); return NGX_HTTP_INTERNAL_SERVER_ERROR; } worker_subscriber->longpolling = 1; if (ngx_http_push_stream_registry_subscriber_locked(r, worker_subscriber) == NGX_ERROR) { ngx_shmtx_unlock(&shpool->mutex); return NGX_HTTP_INTERNAL_SERVER_ERROR; } // adding subscriber to channel(s) cur = channels_ids; while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) { if ((channel = ngx_http_push_stream_find_channel(cur->id, r->connection->log)) == NULL) { // channel not found ngx_shmtx_unlock(&shpool->mutex); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate shared memory for channel %s", cur->id->data); return NGX_HTTP_INTERNAL_SERVER_ERROR; } if ((subscription = ngx_http_push_stream_create_channel_subscription(r, channel, worker_subscriber)) == NULL) { ngx_shmtx_unlock(&shpool->mutex); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_push_stream_assing_subscription_to_channel_locked(shpool, cur->id, subscription, &worker_subscriber->subscriptions_sentinel, r->connection->log); } ngx_shmtx_unlock(&shpool->mutex); return NGX_DONE; } ngx_shmtx_unlock(&shpool->mutex); // polling or long polling without messages to send ngx_http_push_stream_add_polling_headers(r, greater_message_time, greater_message_tag, temp_pool); if (!has_message_to_send) { // polling subscriber requests get a 304 with their entity tags preserved if don't have new messages. return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_MODIFIED, NULL); } // polling with messages or long polling without messages to send r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = -1; ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_TRANSFER_ENCODING, &NGX_HTTP_PUSH_STREAM_HEADER_CHUNCKED); ngx_http_send_header(r); // sending response content header if (ngx_http_push_stream_send_response_content_header(r, cf) == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: could not send content header to subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ctx->callback != NULL) { ngx_http_push_stream_send_response_text(r, ctx->callback->data, ctx->callback->len, 0); ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_CALLBACK_INIT_CHUNK.data, NGX_HTTP_PUSH_STREAM_CALLBACK_INIT_CHUNK.len, 0); } cur = channels_ids; while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) { channel = ngx_http_push_stream_find_channel(cur->id, r->connection->log); if (channel == NULL) { // channel not found ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate shared memory for channel %s", cur->id->data); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_push_stream_send_old_messages(r, channel, cur->backtrack_messages, if_modified_since, tag, greater_message_time, greater_message_tag, last_event_id); } if (ctx->callback != NULL) { ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_CALLBACK_END_CHUNK.data, NGX_HTTP_PUSH_STREAM_CALLBACK_END_CHUNK.len, 0); } if (cf->footer_template.len > 0) { ngx_http_push_stream_send_response_text(r, cf->footer_template.data, cf->footer_template.len, 0); } ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.data, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.len, 1); return NGX_OK; }
static ngx_int_t ngx_http_push_stream_websocket_handler(ngx_http_request_t *r) { #if !(NGX_HAVE_SHA1) ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: sha1 support is needed to use WebSocket"); return NGX_OK; #endif ngx_slab_pool_t *shpool = (ngx_slab_pool_t *)ngx_http_push_stream_shm_zone->shm.addr; ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module); ngx_http_push_stream_subscriber_t *worker_subscriber; ngx_http_push_stream_requested_channel_t *channels_ids, *cur; ngx_http_push_stream_subscriber_ctx_t *ctx; ngx_int_t rc; ngx_int_t status_code; ngx_str_t *explain_error_message; ngx_str_t *upgrade_header, *connection_header, *sec_key_header, *sec_version_header, *sec_accept_header; ngx_int_t version; // only accept GET method if (!(r->method & NGX_HTTP_GET)) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } ngx_http_push_stream_set_expires(r, NGX_HTTP_PUSH_STREAM_EXPIRES_EPOCH, 0); upgrade_header = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_UPGRADE); connection_header = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_CONNECTION); sec_key_header = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_SEC_WEBSOCKET_KEY); sec_version_header = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_SEC_WEBSOCKET_VERSION); if ((upgrade_header == NULL) || (connection_header == NULL) || (sec_key_header == NULL) || (sec_version_header == NULL)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: %V", &NGX_HTTP_PUSH_STREAM_NO_MANDATORY_HEADERS_MESSAGE); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_MANDATORY_HEADERS_MESSAGE); } version = ngx_atoi(sec_version_header->data, sec_version_header->len); if ((version != NGX_HTTP_PUSH_STREAM_WEBSOCKET_VERSION_8) && (version != NGX_HTTP_PUSH_STREAM_WEBSOCKET_VERSION_13)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: version: %d %V", version, &NGX_HTTP_PUSH_STREAM_WRONG_WEBSOCKET_VERSION_MESSAGE); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_SEC_WEBSOCKET_VERSION, &NGX_HTTP_PUSH_STREAM_WEBSOCKET_SUPPORTED_VERSIONS); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_WRONG_WEBSOCKET_VERSION_MESSAGE); } if ((ctx = ngx_http_push_stream_add_request_context(r)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to create request context"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } //get channels ids and backtracks from path channels_ids = ngx_http_push_stream_parse_channels_ids_from_path(r, ctx->temp_pool); if ((channels_ids == NULL) || ngx_queue_empty(&channels_ids->queue)) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: the $push_stream_channels_path variable is required but is not set"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE); } //validate channels: name, length and quantity. check if channel exists when authorized_channels_only is on. check if channel is full of subscribers if (ngx_http_push_stream_validate_channels(r, channels_ids, &status_code, &explain_error_message) == NGX_ERROR) { return ngx_http_push_stream_send_only_header_response(r, status_code, explain_error_message); } // stream access if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if ((sec_accept_header = ngx_http_push_stream_generate_websocket_accept_value(r, sec_key_header, ctx->temp_pool)) == NULL) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: could not generate security accept heade value"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_UPGRADE, &NGX_HTTP_PUSH_STREAM_WEBSOCKET_UPGRADE); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_CONNECTION, &NGX_HTTP_PUSH_STREAM_WEBSOCKET_CONNECTION); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_SEC_WEBSOCKET_ACCEPT, sec_accept_header); r->headers_out.status_line = NGX_HTTP_PUSH_STREAM_101_STATUS_LINE; r->read_event_handler = ngx_http_push_stream_websocket_reading; ngx_http_push_stream_send_only_added_headers(r); // sending response content header if (ngx_http_push_stream_send_response_content_header(r, cf) == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: could not send content header to subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_shmtx_lock(&shpool->mutex); rc = ngx_http_push_stream_registry_subscriber_locked(r, worker_subscriber); ngx_shmtx_unlock(&shpool->mutex); if (rc == NGX_ERROR) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } // adding subscriber to channel(s) and send backtrack messages cur = channels_ids; while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) { if (ngx_http_push_stream_subscriber_assign_channel(shpool, cf, r, cur, -1, NULL, worker_subscriber, ctx->temp_pool) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } if (ctx->temp_pool != NULL) { ngx_destroy_pool(ctx->temp_pool); ctx->temp_pool = NULL; } return NGX_DONE; }
static ngx_int_t ngx_http_push_stream_publisher_handler(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_push_stream_requested_channel_t *requested_channels, *requested_channel; ngx_str_t vv_allowed_origins = ngx_null_string; ngx_queue_t *q; ngx_http_push_stream_set_expires(r, NGX_HTTP_PUSH_STREAM_EXPIRES_EPOCH, 0); if (cf->allowed_origins != NULL) { ngx_http_push_stream_complex_value(r, cf->allowed_origins, &vv_allowed_origins); } if (vv_allowed_origins.len > 0) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, &vv_allowed_origins); const ngx_str_t *header_value = (cf->location_type == NGX_HTTP_PUSH_STREAM_PUBLISHER_MODE_ADMIN) ? &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_PUT_DELETE_METHODS : &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_PUT_METHODS; ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_METHODS, header_value); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, &NGX_HTTP_PUSH_STREAM_ALLOWED_HEADERS); } if (r->method & NGX_HTTP_OPTIONS) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_OK, NULL); } // only accept GET, POST, PUT and DELETE methods if enable publisher administration if ((cf->location_type == NGX_HTTP_PUSH_STREAM_PUBLISHER_MODE_ADMIN) && !(r->method & (NGX_HTTP_GET|NGX_HTTP_POST|NGX_HTTP_PUT|NGX_HTTP_DELETE))) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_PUT_DELETE_METHODS); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } // only accept GET, POST and PUT methods if NOT enable publisher administration if ((cf->location_type != NGX_HTTP_PUSH_STREAM_PUBLISHER_MODE_ADMIN) && !(r->method & (NGX_HTTP_GET|NGX_HTTP_POST|NGX_HTTP_PUT))) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_PUT_METHODS); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } if ((ctx = ngx_http_push_stream_add_request_context(r)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to create request context"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL); } //get channels ids requested_channels = ngx_http_push_stream_parse_channels_ids_from_path(r, r->pool); if ((requested_channels == NULL) || ngx_queue_empty(&requested_channels->queue)) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: the push_stream_channels_path is required but is not set"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE); } for (q = ngx_queue_head(&requested_channels->queue); q != ngx_queue_sentinel(&requested_channels->queue); q = ngx_queue_next(q)) { requested_channel = ngx_queue_data(q, ngx_http_push_stream_requested_channel_t, queue); // check if channel id isn't equals to ALL or contain wildcard if ((ngx_memn2cmp(requested_channel->id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data, requested_channel->id->len, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.len) == 0) || (ngx_strchr(requested_channel->id->data, '*') != NULL)) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_CHANNEL_ID_NOT_AUTHORIZED_MESSAGE); } // could not have a large size if ((mcf->max_channel_id_length != NGX_CONF_UNSET_UINT) && (requested_channel->id->len > mcf->max_channel_id_length)) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: channel id is larger than allowed %d", requested_channel->id->len); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE); } if (r->method & (NGX_HTTP_POST|NGX_HTTP_PUT)) { // create the channel if doesn't exist requested_channel->channel = ngx_http_push_stream_get_channel(requested_channel->id, r->connection->log, mcf); if (requested_channel->channel == NULL) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL); } if (requested_channel->channel == NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: number of channels were exceeded"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED_MESSAGE); } } else { requested_channel->channel = ngx_http_push_stream_find_channel(requested_channel->id, r->connection->log, mcf); } if ((r->method != NGX_HTTP_GET) && (requested_channel->channel != NULL) && requested_channel->channel->for_events) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: only internal routines can change events channel"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_INTERNAL_ONLY_EVENTS_CHANNEL_MESSAGE); } } ctx->requested_channels = requested_channels; if (r->method & (NGX_HTTP_POST|NGX_HTTP_PUT)) { return ngx_http_push_stream_publisher_handle_after_read_body(r, ngx_http_push_stream_publisher_body_handler); } if ((cf->location_type == NGX_HTTP_PUSH_STREAM_PUBLISHER_MODE_ADMIN) && (r->method == NGX_HTTP_DELETE)) { return ngx_http_push_stream_publisher_handle_after_read_body(r, ngx_http_push_stream_publisher_delete_handler); } return ngx_http_push_stream_send_response_channels_info_detailed(r, requested_channels); }
static ngx_int_t ngx_http_push_stream_websocket_handler(ngx_http_request_t *r) { #if !(NGX_HAVE_SHA1) ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: sha1 support is needed to use WebSocket"); return NGX_OK; #endif 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_subscriber_t *worker_subscriber; ngx_http_push_stream_requested_channel_t *requested_channels, *requested_channel; ngx_queue_t *q; ngx_http_push_stream_module_ctx_t *ctx; ngx_int_t tag; time_t if_modified_since; ngx_str_t *last_event_id = NULL; ngx_int_t status_code; ngx_str_t *explain_error_message; ngx_str_t *upgrade_header, *connection_header, *sec_key_header, *sec_version_header, *sec_accept_header, *user_agent; ngx_int_t version; // WebSocket connections must not use keepalive r->keepalive = 0; // only accept GET method if (!(r->method & NGX_HTTP_GET)) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } ngx_http_push_stream_set_expires(r, NGX_HTTP_PUSH_STREAM_EXPIRES_EPOCH, 0); upgrade_header = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_UPGRADE); connection_header = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_CONNECTION); sec_key_header = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_SEC_WEBSOCKET_KEY); sec_version_header = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_SEC_WEBSOCKET_VERSION); user_agent = ngx_http_push_stream_get_header(r, &NGX_HTTP_HEADER_USER_AGENT); if ((upgrade_header == NULL) || (connection_header == NULL) || (sec_key_header == NULL) || (sec_version_header == NULL)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: %V, user_agent: %s, key %s, version: %s", &NGX_HTTP_PUSH_STREAM_NO_MANDATORY_HEADERS_MESSAGE, user_agent == NULL ? (void*)"N/A" : user_agent->data, sec_key_header == NULL ? (void*)"N/A" : sec_key_header == NULL ? (void*)"N/A" : sec_key_header->data, sec_version_header == NULL ? (void*)"N/A" : sec_version_header->data); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, ":%d, %d, %d, %d:", upgrade_header == NULL ? 0 : upgrade_header->len, connection_header == NULL ? 0 : connection_header->len, sec_key_header == NULL ? 0 : sec_key_header->len, sec_version_header == NULL ? 0 : sec_version_header->len); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_MANDATORY_HEADERS_MESSAGE); } version = ngx_atoi(sec_version_header->data, sec_version_header->len); if ((version != NGX_HTTP_PUSH_STREAM_WEBSOCKET_VERSION_8) && (version != NGX_HTTP_PUSH_STREAM_WEBSOCKET_VERSION_13)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: version: %d %V", version, &NGX_HTTP_PUSH_STREAM_WRONG_WEBSOCKET_VERSION_MESSAGE); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_SEC_WEBSOCKET_VERSION, &NGX_HTTP_PUSH_STREAM_WEBSOCKET_SUPPORTED_VERSIONS); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_WRONG_WEBSOCKET_VERSION_MESSAGE); } if ((ctx = ngx_http_push_stream_add_request_context(r)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to create request context"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } if ((ctx->frame = ngx_pcalloc(r->pool, sizeof(ngx_http_push_stream_frame_t))) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to create frame structure"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_START_STEP; ctx->frame->last = NULL; ctx->frame->payload = NULL; if ((sec_accept_header = ngx_http_push_stream_generate_websocket_accept_value(r, sec_key_header, ctx->temp_pool)) == NULL) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: could not generate security accept header value"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_UPGRADE, &NGX_HTTP_PUSH_STREAM_WEBSOCKET_UPGRADE); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_CONNECTION, &NGX_HTTP_PUSH_STREAM_WEBSOCKET_CONNECTION); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_SEC_WEBSOCKET_ACCEPT, sec_accept_header); r->headers_out.status_line = NGX_HTTP_PUSH_STREAM_101_STATUS_LINE; ngx_http_push_stream_send_only_added_headers(r); //get channels ids and backtracks from path requested_channels = ngx_http_push_stream_parse_channels_ids_from_path(r, ctx->temp_pool); if ((requested_channels == NULL) || ngx_queue_empty(&requested_channels->queue)) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: the push_stream_channels_path is required but is not set"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE); } //validate channels: name, length and quantity. check if channel exists when authorized_channels_only is on. check if channel is full of subscribers if (ngx_http_push_stream_validate_channels(r, requested_channels, &status_code, &explain_error_message) == NGX_ERROR) { return ngx_http_push_stream_send_websocket_close_frame(r, status_code, explain_error_message); } // get control values ngx_http_push_stream_get_last_received_message_values(r, &if_modified_since, &tag, &last_event_id); // stream access if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) { return ngx_http_push_stream_send_websocket_close_frame(r, NGX_HTTP_INTERNAL_SERVER_ERROR, &NGX_HTTP_PUSH_STREAM_EMPTY); } // sending response content header if (ngx_http_push_stream_send_response_content_header(r, cf) == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: could not send content header to subscriber"); return ngx_http_push_stream_send_websocket_close_frame(r, NGX_HTTP_INTERNAL_SERVER_ERROR, &NGX_HTTP_PUSH_STREAM_EMPTY); } if (ngx_http_push_stream_registry_subscriber(r, worker_subscriber) == NGX_ERROR) { return ngx_http_push_stream_send_websocket_close_frame(r, NGX_HTTP_INTERNAL_SERVER_ERROR, &NGX_HTTP_PUSH_STREAM_EMPTY); } // adding subscriber to channel(s) and send backtrack messages for (q = ngx_queue_head(&requested_channels->queue); q != ngx_queue_sentinel(&requested_channels->queue); q = ngx_queue_next(q)) { requested_channel = ngx_queue_data(q, ngx_http_push_stream_requested_channel_t, queue); if (ngx_http_push_stream_subscriber_assign_channel(mcf, cf, r, requested_channel, if_modified_since, tag, last_event_id, worker_subscriber, ctx->temp_pool) != NGX_OK) { return ngx_http_push_stream_send_websocket_close_frame(r, NGX_HTTP_INTERNAL_SERVER_ERROR, &NGX_HTTP_PUSH_STREAM_EMPTY); } } if (ctx->temp_pool != NULL) { ngx_destroy_pool(ctx->temp_pool); ctx->temp_pool = NULL; } return NGX_DONE; }
static ngx_int_t ngx_http_push_stream_subscriber_handler(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_subscriber_t *worker_subscriber; ngx_http_push_stream_requested_channel_t *requested_channels, *requested_channel; ngx_queue_t *q; ngx_http_push_stream_module_ctx_t *ctx; ngx_int_t tag; time_t if_modified_since; ngx_str_t *last_event_id = NULL; ngx_str_t *push_mode; ngx_flag_t polling, longpolling; ngx_int_t status_code; ngx_str_t *explain_error_message; ngx_str_t vv_allowed_origins = ngx_null_string; // add headers to support cross domain requests if (cf->allowed_origins != NULL) { ngx_http_push_stream_complex_value(r, cf->allowed_origins, &vv_allowed_origins); } if (vv_allowed_origins.len > 0) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, &vv_allowed_origins); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_METHODS, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, &NGX_HTTP_PUSH_STREAM_ALLOWED_HEADERS); } if (r->method & NGX_HTTP_OPTIONS) { return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_OK, NULL); } ngx_http_push_stream_set_expires(r, NGX_HTTP_PUSH_STREAM_EXPIRES_EPOCH, 0); // only accept GET method if (!(r->method & NGX_HTTP_GET)) { ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); } if ((ctx = ngx_http_push_stream_add_request_context(r)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to create request context"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } //get channels ids and backtracks from path requested_channels = ngx_http_push_stream_parse_channels_ids_from_path(r, r->pool); if ((requested_channels == NULL) || ngx_queue_empty(&requested_channels->queue)) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: the push_stream_channels_path is required but is not set"); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE); } //validate channels: name, length and quantity. check if channel exists when authorized_channels_only is on. check if channel is full of subscribers if (ngx_http_push_stream_validate_channels(r, requested_channels, &status_code, &explain_error_message) == NGX_ERROR) { return ngx_http_push_stream_send_only_header_response(r, status_code, explain_error_message); } // get control values ngx_http_push_stream_get_last_received_message_values(r, &if_modified_since, &tag, &last_event_id); push_mode = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_MODE); polling = ((cf->location_type == NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_POLLING) || ((push_mode != NULL) && (push_mode->len == NGX_HTTP_PUSH_STREAM_MODE_POLLING.len) && (ngx_strncasecmp(push_mode->data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.len) == 0))); longpolling = ((cf->location_type == NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_LONGPOLLING) || ((push_mode != NULL) && (push_mode->len == NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.len) && (ngx_strncasecmp(push_mode->data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.len) == 0))); if (polling || longpolling) { ngx_int_t result = ngx_http_push_stream_subscriber_polling_handler(r, requested_channels, if_modified_since, tag, last_event_id, longpolling, ctx->temp_pool); if (ctx->temp_pool != NULL) { ngx_destroy_pool(ctx->temp_pool); ctx->temp_pool = NULL; } return result; } ctx->padding = ngx_http_push_stream_get_padding_by_user_agent(r); // stream access if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_send_header(r); // sending response content header if (ngx_http_push_stream_send_response_content_header(r, cf) == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: could not send content header to subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_push_stream_registry_subscriber(r, worker_subscriber) == NGX_ERROR) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } // adding subscriber to channel(s) and send old messages for (q = ngx_queue_head(&requested_channels->queue); q != ngx_queue_sentinel(&requested_channels->queue); q = ngx_queue_next(q)) { requested_channel = ngx_queue_data(q, ngx_http_push_stream_requested_channel_t, queue); if (ngx_http_push_stream_subscriber_assign_channel(mcf, cf, r, requested_channel, if_modified_since, tag, last_event_id, worker_subscriber, ctx->temp_pool) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } } if (ctx->temp_pool != NULL) { ngx_destroy_pool(ctx->temp_pool); ctx->temp_pool = NULL; } return NGX_DONE; }