static ngx_int_t subscribe_intervalpoll_callback(nchan_msg_status_t msg_search_outcome, nchan_msg_t *msg, ngx_http_request_t *r) { //inefficient, but close enough for now ngx_str_t *etag; char *err; switch(msg_search_outcome) { case MSG_EXPECTED: //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=nchan_subscriber_get_etag(r)) != NULL) { nchan_add_response_header(r, &NCHAN_HEADER_ETAG, etag); } nchan_respond_status(r, NGX_HTTP_NOT_MODIFIED, NULL, 1); break; case MSG_FOUND: if(nchan_respond_msg(r, msg, NULL, 1, &err) != NGX_OK) { nchan_respond_cstring(r, NGX_HTTP_INTERNAL_SERVER_ERROR, &TEXT_PLAIN, err, 1); } break; case MSG_NOTFOUND: case MSG_EXPIRED: nchan_respond_status(r, NGX_HTTP_NOT_FOUND, NULL, 1); break; default: nchan_respond_status(r, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL, 1); return NGX_ERROR; } return NGX_DONE; }
static ngx_int_t nchan_response_channel_ptr_info(nchan_channel_t *channel, ngx_http_request_t *r, ngx_int_t status_code) { static const ngx_str_t CREATED_LINE = ngx_string("201 Created"); static const ngx_str_t ACCEPTED_LINE = ngx_string("202 Accepted"); time_t last_seen = 0; ngx_uint_t subscribers = 0; ngx_uint_t messages = 0; if(channel!=NULL) { subscribers = channel->subscribers; last_seen = channel->last_seen; messages = channel->messages; r->headers_out.status = status_code == (ngx_int_t) NULL ? NGX_HTTP_OK : status_code; if (status_code == NGX_HTTP_CREATED) { ngx_memcpy(&r->headers_out.status_line, &CREATED_LINE, sizeof(ngx_str_t)); } else if (status_code == NGX_HTTP_ACCEPTED) { ngx_memcpy(&r->headers_out.status_line, &ACCEPTED_LINE, sizeof(ngx_str_t)); } nchan_channel_info(r, messages, subscribers, last_seen); } else { //404! nchan_respond_status(r, NGX_HTTP_NOT_FOUND, NULL, 0); } return NGX_OK; }
ngx_int_t subscriber_respond_unqueued_status(full_subscriber_t *fsub, ngx_int_t status_code, const ngx_str_t *status_line) { ngx_http_request_t *r = fsub->sub.request; fsub->data.cln->handler = (ngx_http_cleanup_pt )empty_handler; fsub->data.finalize_request = 0; fsub->sub.status = DEAD; fsub->sub.fn->dequeue(&fsub->sub); return nchan_respond_status(r, status_code, status_line, 1); }
ngx_str_t *nchan_get_channel_id(ngx_http_request_t *r, pub_or_sub_t what, ngx_int_t fail_hard) { static const ngx_str_t NO_CHANNEL_ID_MESSAGE = ngx_string("No channel id provided."); nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, nchan_module); ngx_int_t rc; ngx_str_t *id; nchan_chid_loc_conf_t *chid_conf; chid_conf = what == PUB ? &cf->pub_chid : &cf->sub_chid; if(chid_conf->n == 0) { chid_conf = &cf->pubsub_chid; } if(chid_conf->n > 0) { rc = nchan_process_multi_channel_id(r, chid_conf, cf, &id); } else { //fallback to legacy $push_channel_id rc = nchan_process_legacy_channel_id(r, cf, &id); } if(id == NULL && fail_hard) { assert(rc != NGX_OK); switch(rc) { case NGX_ERROR: nchan_respond_status(r, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL, 0); break; case NGX_DECLINED: nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); break; case NGX_ABORT: nchan_respond_string(r, NGX_HTTP_NOT_FOUND, &TEXT_PLAIN, &NO_CHANNEL_ID_MESSAGE, 0); break; } DBG("%s channel id NULL", what == PUB ? "pub" : "sub"); } else { DBG("%s channel id %V", what == PUB ? "pub" : "sub", id); } return id; }
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 "}; switch(r->method) { case NGX_HTTP_GET: cf->storage_engine->find_channel(channel_id, (callback_pt) &channel_info_callback, (void *)r); 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; 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) { ERR("can't allocate memory for publisher auth subrequest"); ngx_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); } break; case NGX_HTTP_DELETE: cf->storage_engine->delete_channel(channel_id, (callback_pt) &channel_info_callback, (void *)r); nchan_maybe_send_channel_event_message(r, CHAN_DELETE); break; default: nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); } }
ngx_int_t nchan_OPTIONS_respond(ngx_http_request_t *r, const ngx_str_t *allowed_headers, const ngx_str_t *allowed_methods) { nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); nchan_add_response_header(r, &NCHAN_HEADER_ALLOW, allowed_methods); if(ctx && nchan_get_header_value_origin(r, ctx)) { //Access-Control-Allow-Origin is included later nchan_add_response_header(r, &NCHAN_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, allowed_headers); nchan_add_response_header(r, &NCHAN_HEADER_ACCESS_CONTROL_ALLOW_METHODS, allowed_methods); } return nchan_respond_status(r, NGX_HTTP_OK, NULL, NULL, 0); }
static ngx_int_t multipart_respond_status(subscriber_t *sub, ngx_int_t status_code, const ngx_str_t *status_line){ nchan_buf_and_chain_t *bc; static u_char *end_boundary=(u_char *)"--\r\n"; full_subscriber_t *fsub = (full_subscriber_t *)sub; //nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(fsub->sub.request, nchan_module); if(status_code == NGX_HTTP_NO_CONTENT || (status_code == NGX_HTTP_NOT_MODIFIED && !status_line)) { //ignore return NGX_OK; } if(fsub->data.shook_hands == 0 && status_code >= 400 && status_code <600) { nchan_respond_status(sub->request, status_code, status_line, 1); return NGX_OK; } multipart_ensure_headers_sent(fsub); if((bc = nchan_bufchain_pool_reserve(fsub_bcp(fsub), 1)) == NULL) { nchan_respond_status(sub->request, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL, 1); return NGX_ERROR; } ngx_memzero(&bc->buf, sizeof(ngx_buf_t)); bc->buf.memory = 1; bc->buf.last_buf = 1; bc->buf.last_in_chain = 1; bc->buf.flush = 1; bc->buf.start = end_boundary; bc->buf.pos = end_boundary; bc->buf.end = end_boundary + 4; bc->buf.last = bc->buf.end; nchan_output_filter(fsub->sub.request, &bc->chain); subscriber_maybe_dequeue_after_status_response(fsub, status_code); return NGX_OK; }
static ngx_int_t chunked_respond_status(subscriber_t *sub, ngx_int_t status_code, const ngx_str_t *status_line){ nchan_buf_and_chain_t bc; ngx_chain_t *chain = NULL; full_subscriber_t *fsub = (full_subscriber_t *)sub; if(chain == NULL) { bc.chain.buf=&bc.buf; bc.chain.next=NULL; ngx_memzero(&bc.buf, sizeof(ngx_buf_t)); bc.buf.last_buf = 1; bc.buf.last_in_chain = 1; bc.buf.flush = 1; bc.buf.memory = 1; chain = &bc.chain; } bc.buf.start = (u_char *)"0\r\n\r\n"; bc.buf.end = bc.buf.start + 5; bc.buf.pos = bc.buf.start; bc.buf.last = bc.buf.end; if(status_code == NGX_HTTP_NO_CONTENT || status_code == NGX_HTTP_NOT_MODIFIED) { //ignore return NGX_OK; } if(fsub->data.shook_hands == 0 && status_code >= 400 && status_code <600) { nchan_respond_status(sub->request, status_code, status_line, 1); return NGX_OK; } chunked_ensure_headers_sent(fsub); nchan_output_filter(fsub->sub.request, chain); if(status_code >=400 && status_code <599) { fsub->data.cln->handler = (ngx_http_cleanup_pt )empty_handler; fsub->sub.request->keepalive=0; fsub->data.finalize_request=1; sub->fn->dequeue(sub); } return NGX_OK; }
static ngx_int_t rawstream_respond_status(subscriber_t *sub, ngx_int_t status_code, const ngx_str_t *status_line){ full_subscriber_t *fsub = (full_subscriber_t *)sub; //nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(fsub->sub.request, ngx_nchan_module); if(status_code == NGX_HTTP_NO_CONTENT || (status_code == NGX_HTTP_NOT_MODIFIED && !status_line)) { //ignore return NGX_OK; } if(fsub->data.shook_hands == 0 && status_code >= 400 && status_code <600) { nchan_respond_status(sub->request, status_code, status_line, 1); return NGX_OK; } subscriber_maybe_dequeue_after_status_response(fsub, status_code); return NGX_OK; }
ngx_int_t nchan_respond_sprintf(ngx_http_request_t *r, ngx_int_t status_code, const ngx_str_t *content_type, const ngx_int_t finalize, char *fmt, ...) { u_char *p; ngx_str_t str; va_list args; str.len = 1024; str.data = ngx_palloc(r->pool, 1024); if(str.data == NULL) { return nchan_respond_status(r, status_code, NULL, NULL, finalize); return NGX_ERROR; } va_start(args, fmt); p = ngx_vslprintf(str.data, str.data + str.len, fmt, args); va_end(args); str.len = p - str.data; return nchan_respond_string(r, status_code, content_type, &str, finalize); }
static ngx_int_t longpoll_respond_status(subscriber_t *self, ngx_int_t status_code, const ngx_str_t *status_line) { full_subscriber_t *fsub = (full_subscriber_t *)self; ngx_http_request_t *r = fsub->sub.request; nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, nchan_module); nchan_loc_conf_t *cf = fsub->sub.cf; if(fsub->data.act_as_intervalpoll) { if(status_code == NGX_HTTP_NO_CONTENT || status_code == NGX_HTTP_NOT_MODIFIED || status_code == NGX_HTTP_NOT_FOUND ) { status_code = NGX_HTTP_NOT_MODIFIED; } } else if(status_code == NGX_HTTP_NO_CONTENT || status_code == NGX_HTTP_NOT_MODIFIED) { if(cf->longpoll_multimsg) { if(fsub->data.multimsg_first != NULL) { longpoll_multimsg_respond(fsub); dequeue_maybe(self); } return NGX_OK; } else { //don't care, ignore return NGX_OK; } } DBG("%p respond req %p status %i", self, r, status_code); nchan_set_msgid_http_response_headers(r, &self->last_msgid); //disable abort handler fsub->data.cln->handler = empty_handler; if(ctx->request_origin_header.len > 0) { nchan_add_response_header(r, &NCHAN_HEADER_ALLOW_ORIGIN, &cf->allow_origin); } nchan_respond_status(r, status_code, status_line, 0); dequeue_maybe(self); return NGX_OK; }
static ngx_int_t longpoll_respond_status(subscriber_t *self, ngx_int_t status_code, const ngx_str_t *status_line) { full_subscriber_t *fsub = (full_subscriber_t *)self; ngx_http_request_t *r = fsub->sub.request; nchan_loc_conf_t *cf = fsub->sub.cf; //DBG("%p got status %i", self, status_code); if(fsub->data.act_as_intervalpoll) { if(status_code == NGX_HTTP_NO_CONTENT || status_code == NGX_HTTP_NOT_MODIFIED || status_code == NGX_HTTP_NOT_FOUND ) { status_code = NGX_HTTP_NOT_MODIFIED; } } else if(status_code == NGX_HTTP_NO_CONTENT || (status_code == NGX_HTTP_NOT_MODIFIED && !status_line)) { if(cf->longpoll_multimsg) { if(fsub->data.multimsg_first != NULL) { longpoll_multipart_respond(fsub); dequeue_maybe(self); } return NGX_OK; } else { //don't care, ignore return NGX_OK; } } DBG("%p respond req %p status %i", self, r, status_code); fsub->sub.dequeue_after_response = 1; nchan_set_msgid_http_response_headers(r, NULL, &self->last_msgid); //disable abort handler fsub->data.cln->handler = empty_handler; nchan_respond_status(r, status_code, status_line, 0); dequeue_maybe(self); return NGX_OK; }
static ngx_int_t chunked_respond_status(subscriber_t *sub, ngx_int_t status_code, const ngx_str_t *status_line){ nchan_buf_and_chain_t bc; ngx_chain_t *chain = NULL; full_subscriber_t *fsub = (full_subscriber_t *)sub; if(chain == NULL) { bc.chain.buf=&bc.buf; bc.chain.next=NULL; ngx_memzero(&bc.buf, sizeof(ngx_buf_t)); bc.buf.last_buf = 1; bc.buf.last_in_chain = 1; bc.buf.flush = 1; bc.buf.memory = 1; chain = &bc.chain; } bc.buf.start = (u_char *)"0\r\n\r\n"; bc.buf.end = bc.buf.start + 5; bc.buf.pos = bc.buf.start; bc.buf.last = bc.buf.end; if(status_code == NGX_HTTP_NO_CONTENT || (status_code == NGX_HTTP_NOT_MODIFIED && !status_line)) { //ignore return NGX_OK; } if(fsub->data.shook_hands == 0 && status_code >= 400 && status_code < 600) { nchan_respond_status(sub->request, status_code, status_line, 1); return NGX_OK; } chunked_ensure_headers_sent(fsub); nchan_output_filter(fsub->sub.request, chain); subscriber_maybe_dequeue_after_status_response(fsub, status_code); return NGX_OK; }
static ngx_int_t es_respond_status(subscriber_t *sub, ngx_int_t status_code, const ngx_str_t *status_line){ static ngx_str_t empty_line = ngx_string(""); full_subscriber_t *fsub = (full_subscriber_t *)sub; u_char resp_buf[256]; nchan_buf_and_chain_t bc; if(status_code == NGX_HTTP_NO_CONTENT || status_code == NGX_HTTP_NOT_MODIFIED) { //ignore return NGX_OK; } if(fsub->data.shook_hands == 0 && status_code >= 400 && status_code <600) { nchan_respond_status(sub->request, status_code, status_line, 1); return NGX_OK; } es_ensure_headers_sent(fsub); DBG("%p output status to subscriber", sub); bc.chain.buf = &bc.buf; bc.chain.next = NULL; ngx_init_set_membuf(&bc.buf, resp_buf, ngx_snprintf(resp_buf, 256, ":%i: %V\n", status_code, status_line ? status_line : &empty_line)); bc.buf.flush = 1; bc.buf.last_buf = 1; nchan_output_filter(fsub->sub.request, &bc.chain); if(status_code >=400 && status_code <599) { fsub->data.cln->handler = (ngx_http_cleanup_pt )empty_handler; fsub->sub.request->keepalive=0; fsub->data.finalize_request=1; sub->fn->dequeue(sub); } return NGX_OK; }
static void multipart_ensure_headers_sent(full_subscriber_t *fsub) { nchan_buf_and_chain_t *bc; ngx_http_request_t *r = fsub->sub.request; ngx_http_core_loc_conf_t *clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, nchan_module); multipart_privdata_t *mpd = (multipart_privdata_t *)fsub->privdata; if(!fsub->data.shook_hands) { clcf->chunked_transfer_encoding = 0; nchan_request_set_content_type_multipart_boundary_header(r, ctx); nchan_cleverly_output_headers_only_for_later_response(r); //set preamble in the request ctx. it would be nicer to store in in the subscriber data, //but that would mean not reusing longpoll's fsub directly if((bc = nchan_bufchain_pool_reserve(ctx->bcp, 1)) == NULL) { ERR("can't reserve bufchain for multipart headers"); nchan_respond_status(fsub->sub.request, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL, 1); return; } ngx_memzero(&bc->buf, sizeof(ngx_buf_t)); bc->buf.start = mpd->boundary + 2; bc->buf.pos = bc->buf.start; bc->buf.end = mpd->boundary_end; bc->buf.last = bc->buf.end; bc->buf.memory = 1; bc->buf.last_buf = 0; bc->buf.last_in_chain = 1; bc->buf.flush = 1; nchan_output_filter(r, &bc->chain); fsub->data.shook_hands = 1; } }
ngx_int_t nchan_pubsub_handler(ngx_http_request_t *r) { nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, nchan_module); ngx_str_t *channel_id; subscriber_t *sub; nchan_msg_id_t *msg_id; ngx_int_t rc = NGX_DONE; nchan_request_ctx_t *ctx; ngx_str_t *origin_header; #if NCHAN_BENCHMARK struct timeval tv; ngx_gettimeofday(&tv); #endif if((ctx = ngx_pcalloc(r->pool, sizeof(nchan_request_ctx_t))) == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, ctx, nchan_module); #if NCHAN_BENCHMARK ctx->start_tv = tv; #endif if((origin_header = nchan_get_header_value(r, NCHAN_HEADER_ORIGIN)) != NULL) { ctx->request_origin_header = *origin_header; if(!(cf->allow_origin.len == 1 && cf->allow_origin.data[0] == '*')) { if(!(origin_header->len == cf->allow_origin.len && ngx_strnstr(origin_header->data, (char *)cf->allow_origin.data, origin_header->len) != NULL)) { //CORS origin match failed! return a 403 forbidden goto forbidden; } } } else { ctx->request_origin_header.len=0; ctx->request_origin_header.data=NULL; } if((channel_id = nchan_get_channel_id(r, SUB, 1)) == NULL) { //just get the subscriber_channel_id for now. the publisher one is handled elsewhere return r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR; } if(nchan_detect_websocket_request(r)) { //want websocket? if(cf->sub.websocket) { //we prefer to subscribe #if FAKESHARD memstore_sub_debug_start(); #endif if((msg_id = nchan_subscriber_get_msg_id(r)) == NULL) { goto bad_msgid; } if((sub = websocket_subscriber_create(r, msg_id)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unable to create websocket subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } sub->fn->subscribe(sub, channel_id); #if FAKESHARD memstore_sub_debug_end(); #endif } else if(cf->pub.websocket) { //no need to subscribe, but keep a connection open for publishing //not yet implemented nchan_create_websocket_publisher(r); } else goto forbidden; return NGX_DONE; } else { subscriber_t *(*sub_create)(ngx_http_request_t *r, nchan_msg_id_t *msg_id) = NULL; switch(r->method) { case NGX_HTTP_GET: if(cf->sub.eventsource && nchan_detect_eventsource_request(r)) { sub_create = eventsource_subscriber_create; } else if(cf->sub.http_chunked && nchan_detect_chunked_subscriber_request(r)) { sub_create = http_chunked_subscriber_create; } else if(cf->sub.http_multipart && nchan_detect_multipart_subscriber_request(r)) { sub_create = http_multipart_subscriber_create; } else if(cf->sub.poll) { sub_create = intervalpoll_subscriber_create; } else if(cf->sub.longpoll) { sub_create = longpoll_subscriber_create; } else if(cf->pub.http) { nchan_http_publisher_handler(r); } else { goto forbidden; } if(sub_create) { #if FAKESHARD memstore_sub_debug_start(); #endif if((msg_id = nchan_subscriber_get_msg_id(r)) == NULL) { goto bad_msgid; } if((sub = sub_create(r, msg_id)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unable to create subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } sub->fn->subscribe(sub, channel_id); #if FAKESHARD memstore_sub_debug_end(); #endif } break; case NGX_HTTP_POST: case NGX_HTTP_PUT: if(cf->pub.http) { nchan_http_publisher_handler(r); } else goto forbidden; break; case NGX_HTTP_DELETE: if(cf->pub.http) { nchan_http_publisher_handler(r); } else goto forbidden; break; case NGX_HTTP_OPTIONS: if(cf->pub.http) { nchan_OPTIONS_respond(r, &cf->allow_origin, &NCHAN_ACCESS_CONTROL_ALLOWED_PUBLISHER_HEADERS, &NCHAN_ALLOW_GET_POST_PUT_DELETE); } else if(cf->sub.poll || cf->sub.longpoll || cf->sub.eventsource || cf->sub.websocket) { nchan_OPTIONS_respond(r, &cf->allow_origin, &NCHAN_ACCESS_CONTROL_ALLOWED_SUBSCRIBER_HEADERS, &NCHAN_ALLOW_GET); } else goto forbidden; break; } } return rc; forbidden: nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); return NGX_OK; bad_msgid: nchan_respond_cstring(r, NGX_HTTP_BAD_REQUEST, &NCHAN_CONTENT_TYPE_TEXT_PLAIN, "Message ID invalid", 0); return NGX_OK; }
ngx_int_t nchan_pubsub_handler(ngx_http_request_t *r) { nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, nchan_module); ngx_str_t *channel_id; subscriber_t *sub; nchan_msg_id_t msg_id = {0}; //memzeroed for debugging ngx_int_t rc = NGX_DONE; nchan_request_ctx_t *ctx; if((ctx = ngx_pcalloc(r->pool, sizeof(nchan_request_ctx_t))) == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, ctx, nchan_module); if((channel_id = nchan_get_channel_id(r, SUB, 1)) == NULL) { //just get the subscriber_channel_id for now. the publisher one is handled elsewhere return r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR; } if(nchan_detect_websocket_handshake(r)) { //want websocket? if(cf->sub.websocket) { //we prefer to subscribe memstore_sub_debug_start(); nchan_subscriber_get_msg_id(r, &msg_id); if((sub = websocket_subscriber_create(r, &msg_id)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unable to create websocket subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } cf->storage_engine->subscribe(channel_id, sub, (callback_pt )&subscribe_websocket_callback, (void *)r); memstore_sub_debug_end(); } else if(cf->pub.websocket) { //no need to subscribe, but keep a connection open for publishing //not yet implemented nchan_create_websocket_publisher(r); } else goto forbidden; return NGX_DONE; } else { switch(r->method) { case NGX_HTTP_GET: if(cf->sub.eventsource && nchan_detect_eventsource_request(r)) { memstore_sub_debug_start(); nchan_subscriber_get_msg_id(r, &msg_id); if((sub = eventsource_subscriber_create(r, &msg_id)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unable to create longpoll subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } cf->storage_engine->subscribe(channel_id, sub, (callback_pt )&subscribe_eventsource_callback, (void *)r); memstore_sub_debug_end(); } else if(cf->sub.poll) { memstore_sub_debug_start(); nchan_subscriber_get_msg_id(r, &msg_id); assert(msg_id.tagcount == 1); r->main->count++; cf->storage_engine->get_message(channel_id, &msg_id, (callback_pt )&subscribe_intervalpoll_callback, (void *)r); memstore_sub_debug_end(); } else if(cf->sub.longpoll) { memstore_sub_debug_start(); nchan_subscriber_get_msg_id(r, &msg_id); if((sub = longpoll_subscriber_create(r, &msg_id)) == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unable to create longpoll subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } cf->storage_engine->subscribe(channel_id, sub, (callback_pt )&subscribe_longpoll_callback, (void *)r); memstore_sub_debug_end(); } else if(cf->pub.http) { nchan_http_publisher_handler(r); } else goto forbidden; break; case NGX_HTTP_POST: case NGX_HTTP_PUT: if(cf->pub.http) { nchan_http_publisher_handler(r); } else goto forbidden; break; case NGX_HTTP_DELETE: if(cf->pub.http) { nchan_http_publisher_handler(r); } else goto forbidden; break; case NGX_HTTP_OPTIONS: if(cf->pub.http) { nchan_OPTIONS_respond(r, &NCHAN_ANYSTRING, &NCHAN_ACCESS_CONTROL_ALLOWED_PUBLISHER_HEADERS, &NCHAN_ALLOW_GET_POST_PUT_DELETE_OPTIONS); } else if(cf->sub.poll || cf->sub.longpoll || cf->sub.eventsource || cf->sub.websocket) { nchan_OPTIONS_respond(r, &NCHAN_ANYSTRING, &NCHAN_ACCESS_CONTROL_ALLOWED_SUBSCRIBER_HEADERS, &NCHAN_ALLOW_GET_OPTIONS); } else goto forbidden; break; } } return rc; forbidden: nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); return NGX_OK; }
ngx_int_t nchan_pubsub_handler(ngx_http_request_t *r) { nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_nchan_module); ngx_str_t *channel_id; subscriber_t *sub; nchan_msg_id_t *msg_id; ngx_int_t rc = NGX_DONE; nchan_request_ctx_t *ctx; nchan_group_limits_t group_limits; #if NCHAN_BENCHMARK struct timeval tv; ngx_gettimeofday(&tv); #endif if(r->connection && (r->connection->read->eof || r->connection->read->pending_eof)) { ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST); return NGX_ERROR; } if((ctx = ngx_pcalloc(r->pool, sizeof(nchan_request_ctx_t))) == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, ctx, ngx_nchan_module); #if NCHAN_BENCHMARK ctx->start_tv = tv; #endif //X-Accel-Redirected requests get their method mangled to GET. De-mangle it if necessary if(r->upstream && r->upstream->headers_in.x_accel_redirect) { //yep, we got x-accel-redirected. what was the original method?... nchan_recover_x_accel_redirected_request_method(r); } if(!nchan_match_origin_header(r, cf, ctx)) { goto forbidden; } if((channel_id = nchan_get_channel_id(r, SUB, 1)) == NULL) { //just get the subscriber_channel_id for now. the publisher one is handled elsewhere return r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR; } if((msg_id = nchan_subscriber_get_msg_id(r)) == NULL) { goto bad_msgid; } if(parse_group_limits(r, cf, &group_limits) == NGX_OK) { // unless the group already exists, these limits may only be set after this incoming request. // TODO: fix this, although that will lead to even gnarlier control flow. cf->storage_engine->set_group_limits(nchan_get_group_name(r, cf, ctx), cf, &group_limits, NULL, NULL); } else { // there waas an error parsing group limit strings, and it has already been sent in the response. // just quit. return NGX_OK; } if(cf->pub.websocket || cf->pub.http) { char *err; if(!nchan_parse_message_buffer_config(r, cf, &err)) { if(err) { nchan_respond_cstring(r, NGX_HTTP_FORBIDDEN, &NCHAN_CONTENT_TYPE_TEXT_PLAIN, err, 0); return NGX_OK; } else { nchan_respond_status(r, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL, 0); return NGX_OK; } } } if(nchan_detect_websocket_request(r)) { //want websocket? if(cf->sub.websocket) { //we prefer to subscribe #if FAKESHARD memstore_sub_debug_start(); #endif if((msg_id = nchan_subscriber_get_msg_id(r)) == NULL) { goto bad_msgid; } if((sub = websocket_subscriber_create(r, msg_id)) == NULL) { nchan_log_request_error(r, "unable to create websocket subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } sub->fn->subscribe(sub, channel_id); #if FAKESHARD memstore_sub_debug_end(); #endif } else if(cf->pub.websocket) { //no need to subscribe, but keep a connection open for publishing nchan_create_websocket_publisher(r); } else goto forbidden; return NGX_DONE; } else { subscriber_t *(*sub_create)(ngx_http_request_t *r, nchan_msg_id_t *msg_id) = NULL; switch(r->method) { case NGX_HTTP_GET: if(cf->sub.eventsource && nchan_detect_eventsource_request(r)) { sub_create = eventsource_subscriber_create; } else if(cf->sub.http_chunked && nchan_detect_chunked_subscriber_request(r)) { sub_create = http_chunked_subscriber_create; } else if(cf->sub.http_multipart && nchan_detect_multipart_subscriber_request(r)) { sub_create = http_multipart_subscriber_create; } else if(cf->sub.poll) { sub_create = intervalpoll_subscriber_create; } else if(cf->sub.http_raw_stream) { sub_create = http_raw_stream_subscriber_create; } else if(cf->sub.longpoll) { sub_create = longpoll_subscriber_create; } else if(cf->pub.http) { nchan_http_publisher_handler(r); } else { goto forbidden; } if(sub_create) { #if FAKESHARD memstore_sub_debug_start(); #endif if((msg_id = nchan_subscriber_get_msg_id(r)) == NULL) { goto bad_msgid; } if((sub = sub_create(r, msg_id)) == NULL) { nchan_log_request_error(r, "unable to create subscriber"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } sub->fn->subscribe(sub, channel_id); #if FAKESHARD memstore_sub_debug_end(); #endif } break; case NGX_HTTP_POST: case NGX_HTTP_PUT: if(cf->pub.http) { nchan_http_publisher_handler(r); } else goto forbidden; break; case NGX_HTTP_DELETE: if(cf->pub.http) { nchan_http_publisher_handler(r); } else goto forbidden; break; case NGX_HTTP_OPTIONS: if(cf->pub.http) { nchan_OPTIONS_respond(r, &NCHAN_ACCESS_CONTROL_ALLOWED_PUBLISHER_HEADERS, &NCHAN_ALLOW_GET_POST_PUT_DELETE); } else if(cf->sub.poll || cf->sub.longpoll || cf->sub.eventsource || cf->sub.websocket) { nchan_OPTIONS_respond(r, &NCHAN_ACCESS_CONTROL_ALLOWED_SUBSCRIBER_HEADERS, &NCHAN_ALLOW_GET); } else goto forbidden; break; } } ctx->request_ran_content_handler = 1; return rc; forbidden: nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); ctx->request_ran_content_handler = 1; return NGX_OK; bad_msgid: nchan_respond_cstring(r, NGX_HTTP_BAD_REQUEST, &NCHAN_CONTENT_TYPE_TEXT_PLAIN, "Message ID invalid", 0); ctx->request_ran_content_handler = 1; return NGX_OK; }
ngx_int_t nchan_group_handler(ngx_http_request_t *r) { nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_nchan_module); nchan_request_ctx_t *ctx; ngx_int_t rc = NGX_DONE; ngx_str_t *group; if((ctx = ngx_pcalloc(r->pool, sizeof(nchan_request_ctx_t))) == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, ctx, ngx_nchan_module); if(r->connection && (r->connection->read->eof || r->connection->read->pending_eof)) { ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST); return NGX_ERROR; } if(!cf->group.enable_accounting) { nchan_respond_cstring(r, NGX_HTTP_FORBIDDEN, &NCHAN_CONTENT_TYPE_TEXT_PLAIN, "Channel group accounting is disabled.", 0); return NGX_OK; } group = nchan_get_group_name(r, cf, ctx); if(group == NULL) { nchan_respond_cstring(r, NGX_HTTP_BAD_REQUEST, &NCHAN_CONTENT_TYPE_TEXT_PLAIN, "No group specified", 0); return NGX_OK; } switch(r->method) { case NGX_HTTP_GET: if(!cf->group.get) { rc = nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); } r->main->count++; cf->storage_engine->get_group(group, cf, (callback_pt )group_handler_callback, r); break; case NGX_HTTP_POST: if(!cf->group.set) { rc = nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); } nchan_group_limits_t limits; if(parse_group_limits(r, cf, &limits) != NGX_OK) { return NGX_OK; } r->main->count++; cf->storage_engine->set_group_limits(group, cf, &limits, (callback_pt )group_handler_callback, r); break; case NGX_HTTP_DELETE: if(!cf->group.delete) { rc = nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); } r->main->count++; cf->storage_engine->delete_group(group, cf, (callback_pt )group_handler_callback, r); break; case NGX_HTTP_OPTIONS: rc= nchan_OPTIONS_respond(r, &NCHAN_ACCESS_CONTROL_ALLOWED_GROUP_HEADERS, &NCHAN_ALLOW_GET_POST_DELETE); break; } ctx->request_ran_content_handler = 1; return rc; }
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, nchan_module); ngx_buf_t *buf; size_t content_type_len; nchan_msg_t *msg; struct timeval tv; if((channel_id = nchan_get_channel_id(r, PUB, 1))==NULL) { ngx_http_finalize_request(r, r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR); return; } switch(r->method) { case NGX_HTTP_GET: cf->storage_engine->find_channel(channel_id, (callback_pt) &channel_info_callback, (void *)r); break; case NGX_HTTP_PUT: case NGX_HTTP_POST: memstore_pub_debug_start(); msg = ngx_pcalloc(r->pool, sizeof(*msg)); msg->shared = 0; NGX_REQUEST_VAL_CHECK(msg, NULL, r, "nchan: can't allocate msg in request pool"); //buf = ngx_create_temp_buf(r->pool, 0); //NGX_REQUEST_VAL_CHECK(buf, NULL, r, "nchan: can't allocate buf in request pool"); //content type content_type_len = (r->headers_in.content_type!=NULL ? r->headers_in.content_type->value.len : 0); if(content_type_len > 0) { msg->content_type.len = content_type_len; msg->content_type.data = r->headers_in.content_type->value.data; } if(r->headers_in.content_length_n == -1 || r->headers_in.content_length_n == 0) { buf = ngx_create_temp_buf(r->pool, 0); } else if(r->request_body->bufs!=NULL) { buf = nchan_request_body_to_single_buffer(r); } else { ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "nchan: unexpected publisher message request body buffer location. please report this to the nchan developers."); ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_gettimeofday(&tv); msg->id.time = tv.tv_sec; msg->id.tag[0] = 0; msg->id.tagactive = 0; msg->id.tagcount = 1; msg->buf = buf; #if NCHAN_MSG_LEAK_DEBUG msg->lbl = r->uri; #endif cf->storage_engine->publish(channel_id, msg, cf, (callback_pt) &publish_callback, r); memstore_pub_debug_end(); break; case NGX_HTTP_DELETE: cf->storage_engine->delete_channel(channel_id, (callback_pt) &channel_info_callback, (void *)r); break; default: nchan_respond_status(r, NGX_HTTP_FORBIDDEN, NULL, 0); } }