static ngx_int_t ngx_http_push_subscriber_handler(ngx_http_request_t *r) { ngx_http_push_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_module); ngx_slab_pool_t *shpool = (ngx_slab_pool_t *)ngx_http_push_shm_zone->shm.addr; ngx_str_t *id; ngx_http_push_channel_t *channel; ngx_http_push_msg_t *msg; ngx_int_t msg_search_outcome; ngx_str_t *content_type=NULL; ngx_str_t *etag; if (r->method != NGX_HTTP_GET) { ngx_http_push_add_response_header(r, &NGX_HTTP_PUSH_HEADER_ALLOW, &NGX_HTTP_PUSH_ALLOW_GET); //valid HTTP for teh win return NGX_HTTP_NOT_ALLOWED; } if((id=ngx_http_push_get_channel_id(r, cf)) == NULL) { return r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR; } //get the channel and check channel authorization while we're at it. ngx_shmtx_lock(&shpool->mutex); channel = (cf->authorize_channel==1 ? ngx_http_push_find_channel : ngx_http_push_get_channel)(id, &((ngx_http_push_shm_data_t *) ngx_http_push_shm_zone->data)->tree, shpool, r->connection->log); if (channel==NULL) { //unable to allocate channel OR channel not found ngx_shmtx_unlock(&shpool->mutex); if(cf->authorize_channel) { return NGX_HTTP_FORBIDDEN; } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate shared memory for channel"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } } msg = ngx_http_push_find_message_locked(channel, r, &msg_search_outcome); channel->last_seen = ngx_time(); ngx_shmtx_unlock(&shpool->mutex); switch(ngx_http_push_handle_subscriber_concurrency_setting(cf->subscriber_concurrency, channel, r, shpool)) { case NGX_DECLINED: //this request was declined for some reason. //status codes and whatnot should have already been written. just get out of here quickly. return NGX_OK; case NGX_ERROR: ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: error handling subscriber concurrency setting"); return NGX_ERROR; } switch(msg_search_outcome) { //for message-found: ngx_chain_t *chain; time_t last_modified; size_t content_type_len; ngx_http_postponed_request_t *pr, *p; case NGX_HTTP_PUSH_MESSAGE_EXPECTED: // ♫ It's gonna be the future soon ♫ switch(cf->subscriber_poll_mechanism) { //for NGX_HTTP_PUSH_MECHANISM_LONGPOLL ngx_http_push_pid_queue_t *sentinel, *cur, *found; ngx_http_push_subscriber_t *subscriber; ngx_http_push_subscriber_t *subscriber_sentinel; case NGX_HTTP_PUSH_MECHANISM_LONGPOLL: //long-polling subscriber. wait for a message. //subscribers are queued up in a local pool. Queue sentinels are separate and also local, but not in the pool. ngx_shmtx_lock(&shpool->mutex); sentinel = &channel->workers_with_subscribers; cur = (ngx_http_push_pid_queue_t *)ngx_queue_head(&sentinel->queue); found = NULL; ngx_http_push_subscriber_cleanup_t *clndata; ngx_pool_cleanup_t *cln; while(cur!=sentinel) { if(cur->pid==ngx_pid) { found = cur; break; } cur = (ngx_http_push_pid_queue_t *)ngx_queue_next(&cur->queue); } if(found==NULL) { //found nothing if((found=ngx_slab_alloc_locked(shpool, sizeof(*found)))==NULL) { ngx_shmtx_unlock(&shpool->mutex); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate worker subscriber queue marker in shared memory"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } //initialize ngx_queue_insert_tail(&sentinel->queue, &found->queue); found->pid=ngx_pid; found->slot=ngx_process_slot; found->subscriber_sentinel=NULL; } ngx_shmtx_unlock(&shpool->mutex); if((subscriber = ngx_palloc(ngx_http_push_pool, sizeof(*subscriber)))==NULL) { //unable to allocate request queue element return NGX_ERROR; } //postpone the request. this seems to be magical. pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t)); if (pr == NULL) { return NGX_ERROR; } pr->request = r; //really? pr->out = NULL; pr->next = NULL; if (r->postponed) { for (p = r->postponed; p->next; p = p->next) { /* void */ } p->next = pr; } else { r->postponed = pr; } //attach a cleaner to remove the request from the channel. if ((cln=ngx_pool_cleanup_add(r->pool, sizeof(*clndata))) == NULL) { //make sure we can. return NGX_ERROR; } cln->handler = (ngx_pool_cleanup_pt) ngx_http_push_subscriber_cleanup; clndata = (ngx_http_push_subscriber_cleanup_t *) cln->data; clndata->channel=channel; clndata->subscriber=subscriber; subscriber->request = r; subscriber->clndata=clndata; ngx_shmtx_lock(&shpool->mutex); channel->subscribers++; // do this only when we know everything went okay. //figure out the subscriber sentinel subscriber_sentinel = ((ngx_http_push_pid_queue_t *)found)->subscriber_sentinel; if(subscriber_sentinel==NULL) { if((subscriber_sentinel=ngx_palloc(ngx_http_push_pool, sizeof(*subscriber_sentinel)))==NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate channel subscriber sentinel"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_queue_init(&subscriber_sentinel->queue); ((ngx_http_push_pid_queue_t *)found)->subscriber_sentinel=subscriber_sentinel; } ngx_shmtx_unlock(&shpool->mutex); ngx_queue_insert_tail(&subscriber_sentinel->queue, &subscriber->queue); #if defined(nginx_version) && nginx_version >= 7000 return NGX_OK; //do recall that the request was postponed #else return NGX_DONE; //oldschool #endif case NGX_HTTP_PUSH_MECHANISM_INTERVALPOLL: //interval-polling subscriber requests get a 304 with their entity tags preserved. if (r->headers_in.if_modified_since != NULL) { r->headers_out.last_modified_time=ngx_http_parse_time(r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len); } if ((etag=ngx_http_push_subscriber_get_etag(r)) != NULL) { r->headers_out.etag=ngx_http_push_add_response_header(r, &NGX_HTTP_PUSH_HEADER_ETAG, etag); } return NGX_HTTP_NOT_MODIFIED; default: //if this ever happens, there's a bug somewhere else. probably config stuff. return NGX_HTTP_INTERNAL_SERVER_ERROR; } case NGX_HTTP_PUSH_MESSAGE_EXPIRED: //subscriber wants an expired message //TODO: maybe respond with entity-identifiers for oldest available message? return NGX_HTTP_NO_CONTENT; case NGX_HTTP_PUSH_MESSAGE_FOUND: //found the message ngx_shmtx_lock(&shpool->mutex); msg->refcount++; // this probably isn't necessary, but i'm not thinking too straight at the moment. so just in case. if((msg->received)!=(ngx_uint_t) NGX_MAX_UINT32_VALUE){ //overflow check? msg->received++; } NGX_HTTP_PUSH_MAKE_ETAG(msg->message_tag, etag, ngx_palloc, r->pool); if(etag==NULL) { //oh, nevermind... ngx_shmtx_unlock(&shpool->mutex); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate memory for Etag header"); return NGX_ERROR; } content_type_len = msg->content_type.len; if(content_type_len>0) { NGX_HTTP_PUSH_MAKE_CONTENT_TYPE(content_type, content_type_len, msg, r->pool); if(content_type==NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate memory for content-type header while responding to subscriber request"); ngx_shmtx_unlock(&shpool->mutex); return NGX_ERROR; } } //preallocate output chain. yes, same one for every waiting subscriber if((chain = ngx_http_push_create_output_chain_locked(msg->buf, r->pool, r->connection->log, shpool))==NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate buffer chain while responding to subscriber request"); ngx_shmtx_unlock(&shpool->mutex); return NGX_ERROR; } last_modified = msg->message_time; if(msg->received!=NGX_MAX_UINT32_VALUE) { msg->received++; } //is the message still needed? if(msg!=NULL && (--msg->refcount)==0 && msg->queue.next==NULL) { //message was dequeued, and nobody needs it anymore ngx_http_push_free_message_locked(msg, shpool); } ngx_shmtx_unlock(&shpool->mutex); if(chain->buf->file!=NULL) { //close file when we're done with it ngx_pool_cleanup_t *cln; ngx_pool_cleanup_file_t *clnf; if((cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t)))==NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } cln->handler = ngx_pool_cleanup_file; clnf = cln->data; clnf->fd = chain->buf->file->fd; clnf->name = chain->buf->file->name.data; clnf->log = r->pool->log; } return ngx_http_push_prepare_response_to_subscriber_request(r, chain, content_type, etag, last_modified); default: //we shouldn't be here. return NGX_HTTP_INTERNAL_SERVER_ERROR; } }
static ngx_int_t ngx_http_push_respond_to_subscribers(ngx_http_push_channel_t *channel, ngx_http_push_subscriber_t *sentinel, ngx_http_push_msg_t *msg, ngx_int_t status_code, const ngx_str_t *status_line) { ngx_slab_pool_t *shpool = (ngx_slab_pool_t *)ngx_http_push_shm_zone->shm.addr; ngx_queue_t *cur, *next; ngx_int_t responded_subscribers=0; if(sentinel==NULL) { return NGX_OK; } cur=ngx_queue_head(&sentinel->queue); if(msg!=NULL) { //copy everything we need first ngx_str_t *content_type=NULL; ngx_str_t *etag=NULL; time_t last_modified_time; ngx_chain_t *chain; size_t content_type_len; ngx_http_request_t *r; ngx_buf_t *buffer; u_char *pos; ngx_shmtx_lock(&shpool->mutex); //etag NGX_HTTP_PUSH_MAKE_ETAG(msg->message_tag, etag, ngx_pcalloc, ngx_http_push_pool); if(etag==NULL) { //oh, nevermind... ngx_shmtx_unlock(&shpool->mutex); return NGX_ERROR; } //content-type content_type_len = msg->content_type.len; if(content_type_len>0) { NGX_HTTP_PUSH_MAKE_CONTENT_TYPE(content_type, content_type_len, msg, ngx_http_push_pool); if(content_type==NULL) { ngx_shmtx_unlock(&shpool->mutex); ngx_pfree(ngx_http_push_pool, etag); ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push module: unable to allocate memory for content-type header while responding to several subscriber request"); return NGX_ERROR; } } //preallocate output chain. yes, same one for every waiting subscriber if((chain = ngx_http_push_create_output_chain_locked(msg->buf, ngx_http_push_pool, ngx_cycle->log, shpool))==NULL) { ngx_shmtx_unlock(&shpool->mutex); ngx_pfree(ngx_http_push_pool, etag); ngx_pfree(ngx_http_push_pool, content_type); ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push module: unable to create output chain while responding to several subscriber request"); return NGX_ERROR; } buffer = chain->buf; pos = buffer->pos; last_modified_time = msg->message_time; ngx_shmtx_unlock(&shpool->mutex); //now let's respond to some requests! while(cur!=(ngx_queue_t *)sentinel) { next=ngx_queue_next(cur); //in this block, nothing in shared memory should be dereferenced. r=((ngx_http_push_subscriber_t *)cur)->request; //cleanup oughtn't dequeue anything. or decrement the subscriber count, for that matter ((ngx_http_push_subscriber_t *)cur)->clndata->subscriber=NULL; ((ngx_http_push_subscriber_t *)cur)->clndata->channel=NULL; //unpostpone request r->postponed=NULL; ngx_http_finalize_request(r, ngx_http_push_prepare_response_to_subscriber_request(r, chain, content_type, etag, last_modified_time)); //BAM! responded_subscribers++; ngx_pfree(ngx_http_push_pool, cur); //rewind the buffer, please buffer->pos = pos; buffer->last_buf=1; cur=next; } //free everything relevant ngx_pfree(ngx_http_push_pool, etag); ngx_pfree(ngx_http_push_pool, content_type); if(buffer->file) { ngx_close_file(buffer->file->fd); } ngx_pfree(ngx_http_push_pool, buffer); ngx_pfree(ngx_http_push_pool, chain); } else { //headers only probably ngx_http_request_t *r; while(cur!=(ngx_queue_t *)sentinel) { next=ngx_queue_next(cur); r=((ngx_http_push_subscriber_t *)cur)->request; //cleanup oughtn't dequeue anything. or decrement the subscriber count, for that matter ((ngx_http_push_subscriber_t *)cur)->clndata->subscriber=NULL; ((ngx_http_push_subscriber_t *)cur)->clndata->channel=NULL; ngx_http_push_respond_status_only(((ngx_http_push_subscriber_t *)cur)->request, status_code, status_line); responded_subscribers++; ngx_pfree(ngx_http_push_pool, cur); cur=next; } } ngx_shmtx_lock(&shpool->mutex); channel->subscribers-=responded_subscribers; //is the message still needed? if(msg!=NULL && (--msg->refcount)==0 && msg->queue.next==NULL) { //message was dequeued, and nobody needs it anymore ngx_http_push_free_message_locked(msg, shpool); } ngx_shmtx_unlock(&shpool->mutex); ngx_pfree(ngx_http_push_pool, sentinel); return NGX_OK; }
static ngx_int_t ngx_http_push_subscriber_handler(ngx_http_request_t *r) { ngx_http_push_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_module); ngx_slab_pool_t *shpool = (ngx_slab_pool_t *)ngx_http_push_shm_zone->shm.addr; ngx_str_t *id; ngx_http_push_channel_t *channel; ngx_http_push_msg_t *msg; ngx_int_t msg_search_outcome; ngx_str_t *content_type=NULL; ngx_str_t *etag; if (r->method == NGX_HTTP_OPTIONS) { ngx_buf_t *buf = ngx_create_temp_buf(r->pool, sizeof(NGX_HTTP_PUSH_OPTIONS_OK_MESSAGE)); ngx_chain_t *chain; buf->pos=(u_char *)NGX_HTTP_PUSH_OPTIONS_OK_MESSAGE; buf->last=buf->pos + sizeof(NGX_HTTP_PUSH_OPTIONS_OK_MESSAGE)-1; chain = ngx_http_push_create_output_chain(buf, r->pool, r->connection->log); buf->last_buf=1; r->headers_out.content_length_n=ngx_buf_size(buf); r->headers_out.status=NGX_HTTP_OK; ngx_http_send_header(r); ngx_http_output_filter(r, chain); return NGX_OK; } if (r->method != NGX_HTTP_GET) { ngx_http_push_add_response_header(r, &NGX_HTTP_PUSH_HEADER_ALLOW, &NGX_HTTP_PUSH_ALLOW_GET); //valid HTTP for the win return NGX_HTTP_NOT_ALLOWED; } if((id=ngx_http_push_get_channel_id(r, cf)) == NULL) { return r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR; } //get the channel and check channel authorization while we're at it. ngx_shmtx_lock(&shpool->mutex); if (cf->authorize_channel==1) { channel = ngx_http_push_find_channel(id, r->connection->log); }else{ channel = ngx_http_push_get_channel(id, r->connection->log, cf->channel_timeout); } if (channel==NULL) { //unable to allocate channel OR channel not found ngx_shmtx_unlock(&shpool->mutex); if(cf->authorize_channel) { return NGX_HTTP_FORBIDDEN; } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate shared memory for channel"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } } msg = ngx_http_push_find_message_locked(channel, r, &msg_search_outcome); channel->last_seen = ngx_time(); channel->expires = ngx_time() + cf->channel_timeout; ngx_shmtx_unlock(&shpool->mutex); if (cf->ignore_queue_on_no_cache && !ngx_http_push_allow_caching(r)) { msg_search_outcome = NGX_HTTP_PUSH_MESSAGE_EXPECTED; msg = NULL; } switch(ngx_http_push_handle_subscriber_concurrency(r, channel, cf)) { case NGX_DECLINED: //this request was declined for some reason. //status codes and whatnot should have already been written. just get out of here quickly. return NGX_OK; case NGX_ERROR: ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: error handling subscriber concurrency setting"); return NGX_ERROR; } switch(msg_search_outcome) { //for message-found: ngx_chain_t *chain; time_t last_modified; size_t content_type_len; case NGX_HTTP_PUSH_MESSAGE_EXPECTED: // ♫ It's gonna be the future soon ♫ switch(cf->subscriber_poll_mechanism) { //for NGX_HTTP_PUSH_MECHANISM_LONGPOLL ngx_http_push_pid_queue_t *sentinel, *cur, *found; ngx_http_push_subscriber_t *subscriber; ngx_http_push_subscriber_t *subscriber_sentinel; case NGX_HTTP_PUSH_MECHANISM_LONGPOLL: //long-polling subscriber. wait for a message. //subscribers are queued up in a local pool. Queue sentinels are separate and also local, but not in the pool. ngx_shmtx_lock(&shpool->mutex); sentinel = &channel->workers_with_subscribers; cur = (ngx_http_push_pid_queue_t *)ngx_queue_head(&sentinel->queue); found = NULL; ngx_http_push_subscriber_cleanup_t *clndata; ngx_pool_cleanup_t *cln; while(cur!=sentinel) { if(cur->pid==ngx_pid) { found = cur; break; } cur = (ngx_http_push_pid_queue_t *)ngx_queue_next(&cur->queue); } if(found == NULL) { //found nothing if((found=ngx_http_push_slab_alloc_locked(sizeof(*found)))==NULL) { ngx_shmtx_unlock(&shpool->mutex); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate worker subscriber queue marker in shared memory"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } //initialize ngx_queue_insert_tail(&sentinel->queue, &found->queue); found->pid=ngx_pid; found->slot=ngx_process_slot; found->subscriber_sentinel=NULL; } ngx_shmtx_unlock(&shpool->mutex); if((subscriber = ngx_palloc(ngx_http_push_pool, sizeof(*subscriber)))==NULL) { //unable to allocate request queue element return NGX_ERROR; } //attach a cleaner to remove the request from the channel. if ((cln=ngx_pool_cleanup_add(r->pool, sizeof(*clndata))) == NULL) { //make sure we can. return NGX_ERROR; } cln->handler = (ngx_pool_cleanup_pt) ngx_http_push_subscriber_cleanup; clndata = (ngx_http_push_subscriber_cleanup_t *) cln->data; clndata->channel=channel; clndata->subscriber=subscriber; subscriber->request = r; subscriber->clndata=clndata; ngx_shmtx_lock(&shpool->mutex); channel->subscribers++; // do this only when we know everything went okay. //figure out the subscriber sentinel subscriber_sentinel = ((ngx_http_push_pid_queue_t *)found)->subscriber_sentinel; if(subscriber_sentinel==NULL) { //it's perfectly nornal for the sentinel to be NULL. if((subscriber_sentinel=ngx_palloc(ngx_http_push_pool, sizeof(*subscriber_sentinel)))==NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate channel subscriber sentinel"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_queue_init(&subscriber_sentinel->queue); ((ngx_http_push_pid_queue_t *)found)->subscriber_sentinel=subscriber_sentinel; } ngx_shmtx_unlock(&shpool->mutex); ngx_queue_insert_tail(&subscriber_sentinel->queue, &subscriber->queue); ngx_memzero(&subscriber->event, sizeof(subscriber->event)); if (cf->subscriber_timeout > 0) { subscriber->event.handler = ngx_http_push_clean_timeouted_subscriber; subscriber->event.data = subscriber; subscriber->event.log = r->connection->log; ngx_add_timer(&subscriber->event, cf->subscriber_timeout * 1000); } //r->read_event_handler = ngx_http_test_reading; //r->write_event_handler = ngx_http_request_empty_handler; r->discard_body = 1; //r->keepalive = 1; //stayin' alive!! return NGX_DONE; case NGX_HTTP_PUSH_MECHANISM_INTERVALPOLL: //interval-polling subscriber requests get a 304 with their entity tags preserved. if (r->headers_in.if_modified_since != NULL) { r->headers_out.last_modified_time=ngx_http_parse_time(r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len); } if ((etag=ngx_http_push_subscriber_get_etag(r)) != NULL) { r->headers_out.etag=ngx_http_push_add_response_header(r, &NGX_HTTP_PUSH_HEADER_ETAG, etag); } return NGX_HTTP_NOT_MODIFIED; default: //if this ever happens, there's a bug somewhere else. probably config stuff. return NGX_HTTP_INTERNAL_SERVER_ERROR; } case NGX_HTTP_PUSH_MESSAGE_EXPIRED: //subscriber wants an expired message //TODO: maybe respond with entity-identifiers for oldest available message? return NGX_HTTP_NO_CONTENT; case NGX_HTTP_PUSH_MESSAGE_FOUND: //found the message ngx_shmtx_lock(&shpool->mutex); ngx_http_push_reserve_message_locked(channel, msg); NGX_HTTP_PUSH_MAKE_ETAG(msg->message_tag, etag, ngx_palloc, r->pool); if(etag==NULL) { //oh, nevermind... ngx_shmtx_unlock(&shpool->mutex); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate memory for Etag header"); return NGX_ERROR; } content_type_len = msg->content_type.len; if(content_type_len>0) { NGX_HTTP_PUSH_MAKE_CONTENT_TYPE(content_type, content_type_len, msg, r->pool); if(content_type==NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate memory for content-type header while responding to subscriber request"); ngx_shmtx_unlock(&shpool->mutex); return NGX_ERROR; } } //preallocate output chain. yes, same one for every waiting subscriber if((chain = ngx_http_push_create_output_chain_locked(msg->buf, r->pool, r->connection->log, shpool))==NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate buffer chain while responding to subscriber request"); ngx_shmtx_unlock(&shpool->mutex); return NGX_ERROR; } last_modified = msg->message_time; //is the message still needed? ngx_http_push_release_message_locked(channel, msg); ngx_shmtx_unlock(&shpool->mutex); if(chain->buf->file!=NULL) { //close file when we're done with it ngx_pool_cleanup_t *cln; ngx_pool_cleanup_file_t *clnf; if((cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t)))==NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } cln->handler = ngx_pool_cleanup_file; clnf = cln->data; clnf->fd = chain->buf->file->fd; clnf->name = chain->buf->file->name.data; clnf->log = r->pool->log; } return ngx_http_push_prepare_response_to_subscriber_request(r, chain, content_type, etag, last_modified); default: //we shouldn't be here. return NGX_HTTP_INTERNAL_SERVER_ERROR; } }