static void nchan_publisher_body_handler(ngx_http_request_t *r) { ngx_str_t *channel_id; nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_nchan_module); ngx_table_elt_t *content_length_elt; ngx_http_complex_value_t *authorize_request_url_ccv = cf->authorize_request_url; if((channel_id = nchan_get_channel_id(r, PUB, 1))==NULL) { nchan_http_finalize_request(r, r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if(!authorize_request_url_ccv) { nchan_publisher_body_handler_continued(r, channel_id, cf); } else { nchan_pub_subrequest_stuff_t *psr_stuff; if((psr_stuff = ngx_palloc(r->pool, sizeof(*psr_stuff))) == NULL) { nchan_log_request_error(r, "can't allocate memory for publisher auth subrequest"); nchan_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_post_subrequest_t *psr = &psr_stuff->psr; nchan_pub_subrequest_data_t *psrd = &psr_stuff->psr_data; ngx_http_request_t *sr; ngx_str_t auth_request_url; ngx_http_complex_value(r, authorize_request_url_ccv, &auth_request_url); psr->handler = nchan_publisher_body_authorize_handler; psr->data = psrd; psrd->ch_id = channel_id; ngx_http_subrequest(r, &auth_request_url, NULL, &sr, psr, 0); if((sr->request_body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t))) == NULL) { nchan_log_request_error(r, "can't allocate memory for publisher auth subrequest body"); nchan_http_finalize_request(r, r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if((content_length_elt = ngx_palloc(r->pool, sizeof(*content_length_elt))) == NULL) { nchan_log_request_error(r, "can't allocate memory for publisher auth subrequest content-length header"); nchan_http_finalize_request(r, r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if(sr->headers_in.content_length) { *content_length_elt = *sr->headers_in.content_length; content_length_elt->value.len=1; content_length_elt->value.data=(u_char *)"0"; sr->headers_in.content_length = content_length_elt; } sr->headers_in.content_length_n = 0; sr->args = r->args; sr->header_only = 1; } }
static ngx_int_t channel_info_callback(ngx_int_t status, void *rptr, void *pd) { ngx_http_request_t *r = nchan_get_safe_request_ptr(pd); if(r == NULL) { return NGX_ERROR; } if(status>=500 && status <= 599) { nchan_http_finalize_request(r, status); } else { nchan_http_finalize_request(r, nchan_response_channel_ptr_info( (nchan_channel_t *)rptr, r, 0)); } return NGX_OK; }
static ngx_int_t nchan_publisher_upstream_handler(ngx_http_request_t *sr, void *data, ngx_int_t rc) { ngx_http_request_t *r = sr->parent; nchan_pub_upstream_data_t *d = (nchan_pub_upstream_data_t *)data; //switch(r->headers_out if(rc == NGX_OK) { nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_nchan_module); ngx_int_t code = sr->headers_out.status; ngx_str_t *content_type; ngx_int_t content_length; ngx_chain_t *request_chain; switch(code) { case NGX_HTTP_OK: case NGX_HTTP_CREATED: case NGX_HTTP_ACCEPTED: if(sr->upstream) { content_type = (sr->upstream->headers_in.content_type ? &sr->upstream->headers_in.content_type->value : NULL); content_length = nchan_subrequest_content_length(sr); request_chain = sr->upstream->out_bufs; } else { content_type = NULL; content_length = 0; request_chain = NULL; } nchan_publisher_post_request(r, content_type, content_length, request_chain, d->ch_id, cf); break; case NGX_HTTP_NOT_MODIFIED: content_type = (r->headers_in.content_type ? &r->headers_in.content_type->value : NULL); content_length = r->headers_in.content_length_n > 0 ? r->headers_in.content_length_n : 0; nchan_publisher_post_request(r, content_type, content_length, r->request_body->bufs, d->ch_id, cf); break; case NGX_HTTP_NO_CONTENT: //cancel publication nchan_http_finalize_request(r, NGX_HTTP_NO_CONTENT); break; default: nchan_http_finalize_request(r, NGX_HTTP_FORBIDDEN); } } else { nchan_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); } return NGX_OK; }
static ngx_int_t nchan_publisher_body_authorize_handler(ngx_http_request_t *r, void *data, ngx_int_t rc) { nchan_pub_subrequest_data_t *d = data; if(rc == NGX_OK) { nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r->parent, ngx_nchan_module); ngx_int_t code = r->headers_out.status; if(code >= 200 && code <299) { //authorized. proceed as planned nchan_publisher_body_handler_continued(r->parent, d->ch_id, cf); } else { //anything else means forbidden nchan_http_finalize_request(r->parent, NGX_HTTP_FORBIDDEN); } } else { nchan_http_finalize_request(r->parent, rc); } return NGX_OK; }
ngx_int_t nchan_respond_string(ngx_http_request_t *r, ngx_int_t status_code, const ngx_str_t *content_type, const ngx_str_t *body, ngx_int_t finalize) { ngx_int_t rc = NGX_OK; ngx_buf_t *b = REQUEST_PCALLOC(r, b); ngx_chain_t *chain = REQUEST_PALLOC(r, chain); //assume both were alloc'd fine r->headers_out.status=status_code; r->headers_out.content_length_n = body->len; if(content_type) { r->headers_out.content_type.len = content_type->len; r->headers_out.content_type.data = content_type->data; } nchan_include_access_control_if_needed(r, NULL); if ((!b) || (!chain)) { ERR("Couldn't allocate ngx buf or chain."); r->headers_out.status=NGX_HTTP_INTERNAL_SERVER_ERROR; r->headers_out.content_length_n = 0; r->header_only = 1; ngx_http_send_header(r); rc=NGX_ERROR; } else { chain->buf=b; chain->next=NULL; b->last_buf = 1; b->last_in_chain = 1; b->flush = 1; //flush just to be sure, although I should perhaps rethink this b->memory = 1; b->start = body->data; b->pos = body->data; b->end = body->data + body->len; b->last = b->end; if ((rc = ngx_http_send_header(r)) == NGX_OK) { rc= nchan_output_filter(r, chain); } } if(finalize) { nchan_http_finalize_request(r, rc); } return rc; }
static ngx_int_t subscriber_unsubscribe_request_callback(ngx_http_request_t *r, void *data, ngx_int_t rc) { nchan_subrequest_data_t *d = data; nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(d->sub->request, ngx_nchan_module); ngx_int_t finalize_code = ctx->unsubscribe_request_callback_finalize_code; DBG("callback %p %p %i", r, data, rc); if(d->sub->request->main->blocked) { d->sub->request->main->blocked = 0; } if(finalize_code != NGX_DONE) { nchan_http_finalize_request(d->sub->request, finalize_code); } ctx->unsubscribe_request_callback_finalize_code = NGX_OK; d->sub->fn->release(d->sub, 0); return NGX_OK; }
ngx_int_t nchan_respond_status(ngx_http_request_t *r, ngx_int_t status_code, const ngx_str_t *status_line, ngx_int_t finalize) { ngx_int_t rc = NGX_OK; r->headers_out.status=status_code; if(status_line!=NULL) { r->headers_out.status_line.len =status_line->len; r->headers_out.status_line.data=status_line->data; } r->headers_out.content_length_n = 0; r->header_only = 1; nchan_include_access_control_if_needed(r, NULL); rc= ngx_http_send_header(r); if(finalize) { nchan_http_finalize_request(r, rc); } return rc; }
static ngx_int_t group_handler_callback(ngx_int_t status, nchan_group_t *group, ngx_http_request_t *r) { nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); if(!group) { group = ngx_pcalloc(r->pool, sizeof(*group)); } if(!ctx->request_ran_content_handler) { r->main->count--; nchan_group_info(r, group); } else { nchan_http_finalize_request(r, nchan_group_info(r, group)); } return NGX_OK; }
static safe_request_ptr_t *nchan_set_safe_request_ptr(ngx_http_request_t *r) { safe_request_ptr_t *data = ngx_alloc(sizeof(*data), ngx_cycle->log); ngx_http_cleanup_t *cln = ngx_http_cleanup_add(r, 0); if(!data || !cln) { nchan_log_request_error(r, "couldn't allocate request cleanup stuff."); if(cln) { cln->data = NULL; cln->handler = (ngx_http_cleanup_pt )clear_request_pointer; } nchan_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NULL; } data->cln = cln; data->r = r; cln->data = data; cln->handler = (ngx_http_cleanup_pt )clear_request_pointer; return data; }
static void nchan_publisher_body_handler_continued(ngx_http_request_t *r, ngx_str_t *channel_id, nchan_loc_conf_t *cf) { ngx_http_complex_value_t *publisher_upstream_request_url_ccv; static ngx_str_t POST_REQUEST_STRING = {4, (u_char *)"POST "}; safe_request_ptr_t *pd; switch(r->method) { case NGX_HTTP_GET: if((pd = nchan_set_safe_request_ptr(r)) == NULL){ return; } cf->storage_engine->find_channel(channel_id, cf, (callback_pt) &channel_info_callback, pd); break; case NGX_HTTP_PUT: case NGX_HTTP_POST: publisher_upstream_request_url_ccv = cf->publisher_upstream_request_url; if(publisher_upstream_request_url_ccv == NULL) { ngx_str_t *content_type = (r->headers_in.content_type ? &r->headers_in.content_type->value : NULL); ngx_int_t content_length = r->headers_in.content_length_n > 0 ? r->headers_in.content_length_n : 0; // no need to check for chunked transfer-encoding, nginx automatically sets the // content-length either way. nchan_publisher_post_request(r, content_type, content_length, r->request_body->bufs, channel_id, cf); } else { nchan_pub_upstream_stuff_t *psr_stuff; if((psr_stuff = ngx_palloc(r->pool, sizeof(*psr_stuff))) == NULL) { nchan_log_request_error(r, "can't allocate memory for publisher auth subrequest"); nchan_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_post_subrequest_t *psr = &psr_stuff->psr; nchan_pub_upstream_data_t *psrd = &psr_stuff->psr_data; ngx_http_request_t *sr; ngx_str_t publisher_upstream_request_url; ngx_http_complex_value(r, publisher_upstream_request_url_ccv, &publisher_upstream_request_url); psr->handler = nchan_publisher_upstream_handler; psr->data = psrd; psrd->ch_id = channel_id; ngx_http_subrequest(r, &publisher_upstream_request_url, NULL, &sr, psr, NGX_HTTP_SUBREQUEST_IN_MEMORY); nchan_adjust_subrequest(sr, NGX_HTTP_POST, &POST_REQUEST_STRING, r->request_body, r->headers_in.content_length_n, NULL); sr->args = r->args; } break; case NGX_HTTP_DELETE: if((pd = nchan_set_safe_request_ptr(r)) == NULL){ return; } cf->storage_engine->delete_channel(channel_id, cf, (callback_pt) &channel_info_callback, pd); nchan_maybe_send_channel_event_message(r, CHAN_DELETE); break; default: nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); } }
static void nchan_publisher_post_request(ngx_http_request_t *r, ngx_str_t *content_type, size_t content_length, ngx_chain_t *request_body_chain, ngx_str_t *channel_id, nchan_loc_conf_t *cf) { ngx_buf_t *buf; nchan_msg_t *msg; ngx_str_t *eventsource_event; safe_request_ptr_t *pd; #if FAKESHARD memstore_pub_debug_start(); #endif if((msg = ngx_pcalloc(r->pool, sizeof(*msg))) == NULL) { nchan_log_request_error(r, "can't allocate msg in request pool"); nchan_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } msg->storage = NCHAN_MSG_POOL; if(cf->eventsource_event.len > 0) { msg->eventsource_event = &cf->eventsource_event; } else if((eventsource_event = nchan_get_header_value(r, NCHAN_HEADER_EVENTSOURCE_EVENT)) != NULL) { msg->eventsource_event = eventsource_event; } //content type if(content_type) { msg->content_type = content_type; } if(content_length == 0) { buf = ngx_create_temp_buf(r->pool, 0); } else if(request_body_chain!=NULL) { buf = nchan_chain_to_single_buffer(r->pool, request_body_chain, content_length); } else { nchan_log_request_error(r, "unexpected publisher message request body buffer location. please report this to the nchan developers."); nchan_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } msg->id.time = 0; msg->id.tag.fixed[0] = 0; msg->id.tagactive = 0; msg->id.tagcount = 1; msg->buf = *buf; #if NCHAN_MSG_LEAK_DEBUG msg->lbl = r->uri; #endif #if NCHAN_BENCHMARK nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); msg->start_tv = ctx->start_tv; #endif nchan_deflate_message_if_needed(msg, cf, r, r->pool); if((pd = nchan_set_safe_request_ptr(r)) == NULL) { return; } cf->storage_engine->publish(channel_id, msg, cf, (callback_pt) &publish_callback, pd); nchan_update_stub_status(total_published_messages, 1); #if FAKESHARD memstore_pub_debug_end(); #endif }
static ngx_int_t publish_callback(ngx_int_t status, void *data, safe_request_ptr_t *pd) { nchan_request_ctx_t *ctx; static nchan_msg_id_t empty_msgid = NCHAN_ZERO_MSGID; nchan_channel_t *ch = data; ngx_http_request_t *r = nchan_get_safe_request_ptr(pd); if(r == NULL) { // the request has since disappered return NGX_ERROR; } ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); //DBG("publish_callback %V owner %i status %i", ch_id, memstore_channel_owner(ch_id), status); switch(status) { case NCHAN_MESSAGE_QUEUED: //message was queued successfully, but there were no subscribers to receive it. ctx->prev_msg_id = ctx->msg_id; ctx->msg_id = ch != NULL ? ch->last_published_msg_id : empty_msgid; nchan_maybe_send_channel_event_message(r, CHAN_PUBLISH); nchan_http_finalize_request(r, nchan_response_channel_ptr_info(ch, r, NGX_HTTP_ACCEPTED)); return NGX_OK; case NCHAN_MESSAGE_RECEIVED: //message was queued successfully, and it was already sent to at least one subscriber ctx->prev_msg_id = ctx->msg_id; ctx->msg_id = ch != NULL ? ch->last_published_msg_id : empty_msgid; nchan_maybe_send_channel_event_message(r, CHAN_PUBLISH); nchan_http_finalize_request(r, nchan_response_channel_ptr_info(ch, r, NGX_HTTP_CREATED)); return NGX_OK; case NGX_ERROR: status = NGX_HTTP_INTERNAL_SERVER_ERROR; /*fallthrough*/ case NGX_HTTP_INSUFFICIENT_STORAGE: case NGX_HTTP_INTERNAL_SERVER_ERROR: case NGX_HTTP_SERVICE_UNAVAILABLE: //WTF? nchan_log_request_error(r, "error publishing message (HTTP status code %i)", status); ctx->prev_msg_id = empty_msgid; ctx->msg_id = empty_msgid; nchan_http_finalize_request(r, status); return NGX_ERROR; case NGX_HTTP_FORBIDDEN: ctx->prev_msg_id = empty_msgid; ctx->msg_id = empty_msgid; if(data) { nchan_respond_cstring(r, NGX_HTTP_FORBIDDEN, &NCHAN_CONTENT_TYPE_TEXT_PLAIN, (char *)data, 1); } else { nchan_http_finalize_request(r, NGX_HTTP_FORBIDDEN); } return NGX_OK; default: //for debugging, mostly. I don't expect this branch to behit during regular operation ctx->prev_msg_id = empty_msgid;; ctx->msg_id = empty_msgid; nchan_log_request_error(r, "TOTALLY UNEXPECTED error publishing message (HTTP status code %i)", status); nchan_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } }
ngx_int_t nchan_respond_msg(ngx_http_request_t *r, nchan_msg_t *msg, nchan_msg_id_t *msgid, ngx_int_t finalize, char **err) { ngx_buf_t *buffer = &msg->buf; nchan_buf_and_chain_t *cb; ngx_int_t rc; ngx_chain_t *rchain = NULL; ngx_buf_t *rbuffer; nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); if(ngx_buf_size(buffer) > 0) { cb = ngx_palloc(r->pool, sizeof(*cb)); if (!cb) { if(err) *err = "couldn't allocate memory for buf-and-chain while responding with msg"; return NGX_ERROR; } rchain = &cb->chain; rbuffer = &cb->buf; rchain->next = NULL; rchain->buf = rbuffer; ngx_memcpy(rbuffer, buffer, sizeof(*buffer)); nchan_msg_buf_open_fd_if_needed(rbuffer, NULL, r); r->headers_out.content_length_n=ngx_buf_size(rbuffer); } else { r->headers_out.content_length_n = 0; r->header_only = 1; } if (msg->content_type) { r->headers_out.content_type = *msg->content_type; } if(msgid == NULL) { msgid = &msg->id; } if(nchan_set_msgid_http_response_headers(r, ctx, msgid) != NGX_OK) { if(err) *err = "can't set msgid headers"; return NGX_ERROR; } r->headers_out.status=NGX_HTTP_OK; nchan_include_access_control_if_needed(r, ctx); //we know the entity length, and we're using just one buffer. so no chunking please. if((rc = ngx_http_send_header(r)) >= NGX_HTTP_SPECIAL_RESPONSE) { ERR("request %p, send_header response %i", r, rc); if(err) *err="WTF just happened to request?"; return NGX_ERROR; } if(rchain) { rc= nchan_output_filter(r, rchain); if(rc != NGX_OK && err) *err="failed to write data to connection socket, probably because the connection got closed"; } if(finalize) { nchan_http_finalize_request(r, rc); } return rc; }
static void nchan_flush_pending_output(ngx_http_request_t *r) { int rc; ngx_event_t *wev; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; c = r->connection; wev = c->write; //ngx_log_debug2(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http writer handler: \"%V?%V\"", &r->uri, &r->args); clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module); if (wev->timedout) { if (!wev->delayed) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "request timed out"); c->timedout = 1; nchan_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); return; } wev->timedout = 0; wev->delayed = 0; if (!wev->ready) { ngx_add_timer(wev, clcf->send_timeout); if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { nchan_http_finalize_request(r, 0); } return; } } if (wev->delayed || r->aio) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http writer delayed"); if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { nchan_http_finalize_request(r, 0); } return; } rc = nchan_output_filter(r, NULL); //ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http writer output filter: %d, \"%V?%V\"", rc, &r->uri, &r->args); if (rc == NGX_ERROR) { nchan_http_finalize_request(r, rc); return; } if (r->buffered || r->postponed || (r == r->main && c->buffered)) { if (!wev->delayed) { ngx_add_timer(wev, clcf->send_timeout); } if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { nchan_http_finalize_request(r, 0); return; } } //ngx_log_debug2(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http writer done: \"%V?%V\"", &r->uri, &r->args); if(r->out == NULL) { r->write_event_handler = ngx_http_request_empty_handler; } }