ngx_int_t nchan_set_msgid_http_response_headers(ngx_http_request_t *r, nchan_request_ctx_t *ctx, nchan_msg_id_t *msgid) { ngx_str_t *etag, *tmp_etag; nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_nchan_module); int8_t output_etag = 1, cross_origin; if(!ctx) { ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); } cross_origin = ctx && ctx->request_origin_header.data; if(!cf->msg_in_etag_only) { //last-modified if(msgid->time > 0) { r->headers_out.last_modified_time = msgid->time; } else { output_etag = 0; } tmp_etag = msgtag_to_str(msgid); } else { tmp_etag = msgid_to_str(msgid); } if((etag = ngx_palloc(r->pool, sizeof(*etag) + tmp_etag->len))==NULL) { return NGX_ERROR; } etag->data = (u_char *)(etag+1); etag->len = tmp_etag->len; ngx_memcpy(etag->data, tmp_etag->data, tmp_etag->len); if(cf->custom_msgtag_header.len == 0) { if(output_etag) { nchan_add_response_header(r, &NCHAN_HEADER_ETAG, etag); } if(cross_origin) { nchan_add_response_header(r, &NCHAN_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, &NCHAN_MSG_RESPONSE_ALLOWED_HEADERS); } } else { if(output_etag) { nchan_add_response_header(r, &cf->custom_msgtag_header, etag); } if(cross_origin) { u_char *cur = ngx_palloc(r->pool, 255); if(cur == NULL) { return NGX_ERROR; } ngx_str_t allowed; allowed.data = cur; cur = ngx_snprintf(cur, 255, NCHAN_MSG_RESPONSE_ALLOWED_CUSTOM_ETAG_HEADERS_STRF, &cf->custom_msgtag_header); allowed.len = cur - allowed.data; nchan_add_response_header(r, &NCHAN_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, &allowed); } } //Vary header needed for proper HTTP caching. nchan_add_response_header(r, &NCHAN_HEADER_VARY, &NCHAN_VARY_HEADER_VALUE); return NGX_OK; }
static void websocket_perform_handshake(full_subscriber_t *fsub) { static ngx_str_t magic = ngx_string("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); ngx_str_t ws_accept_key, sha1_str; u_char buf_sha1[21]; u_char buf[255]; ngx_str_t *tmp, *ws_key; ngx_int_t ws_version; ngx_http_request_t *r = fsub->sub.request; ngx_sha1_t sha1; ws_accept_key.data = buf; r->headers_out.content_length_n = 0; r->header_only = 1; if((tmp = nchan_get_header_value(r, NCHAN_HEADER_SEC_WEBSOCKET_VERSION)) == NULL) { r->headers_out.status = NGX_HTTP_BAD_REQUEST; fsub->sub.dequeue_after_response=1; } else { ws_version=ngx_atoi(tmp->data, tmp->len); if(ws_version != 13) { r->headers_out.status = NGX_HTTP_BAD_REQUEST; fsub->sub.dequeue_after_response=1; } } if((ws_key = nchan_get_header_value(r, NCHAN_HEADER_SEC_WEBSOCKET_KEY)) == NULL) { r->headers_out.status = NGX_HTTP_BAD_REQUEST; fsub->sub.dequeue_after_response=1; } if(r->headers_out.status != NGX_HTTP_BAD_REQUEST) { //generate accept key ngx_sha1_init(&sha1); ngx_sha1_update(&sha1, ws_key->data, ws_key->len); ngx_sha1_update(&sha1, magic.data, magic.len); ngx_sha1_final(buf_sha1, &sha1); sha1_str.len=20; sha1_str.data=buf_sha1; ws_accept_key.len=ngx_base64_encoded_length(sha1_str.len); assert(ws_accept_key.len < 255); ngx_encode_base64(&ws_accept_key, &sha1_str); nchan_include_access_control_if_needed(r, fsub->ctx); nchan_add_response_header(r, &NCHAN_HEADER_SEC_WEBSOCKET_ACCEPT, &ws_accept_key); nchan_add_response_header(r, &NCHAN_HEADER_UPGRADE, &NCHAN_WEBSOCKET); #if nginx_version < 1003013 nchan_add_response_header(r, &NCHAN_HEADER_CONNECTION, &NCHAN_UPGRADE); #endif r->headers_out.status_line = NCHAN_HTTP_STATUS_101; r->headers_out.status = NGX_HTTP_SWITCHING_PROTOCOLS; r->keepalive=0; //apparently, websocket must not use keepalive. } ngx_http_send_header(r); }
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 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; }
void nchan_include_access_control_if_needed(ngx_http_request_t *r, nchan_request_ctx_t *ctx) { ngx_str_t *origin; ngx_str_t *allow_origin_val; if(!ctx) { ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); } if(!ctx) { return; } if((origin = nchan_get_header_value_origin(r, ctx)) != NULL) { allow_origin_val = nchan_get_allow_origin_value(r, NULL, ctx); if(allow_origin_val && allow_origin_val->len == 1 && allow_origin_val->data[0]=='*') { nchan_add_response_header(r, &NCHAN_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, allow_origin_val); } else { //otherwise echo the origin nchan_add_response_header(r, &NCHAN_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, origin); } } }
void nchan_include_access_control_if_needed(ngx_http_request_t *r, nchan_request_ctx_t *ctx) { if(!ctx) { ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); } nchan_loc_conf_t *cf; if(!ctx) { return; } if(ctx->request_origin_header.data) { cf = ngx_http_get_module_loc_conf(r, ngx_nchan_module); nchan_add_response_header(r, &NCHAN_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, &cf->allow_origin); } }
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 void chunked_ensure_headers_sent(full_subscriber_t *fsub) { static ngx_str_t transfer_encoding_header = ngx_string("Transfer-Encoding"); static ngx_str_t transfer_encoding = ngx_string("chunked"); 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); if(!fsub->data.shook_hands) { clcf->chunked_transfer_encoding = 0; //r->headers_out.content_type.len = content_type.len; //r->headers_out.content_type.data = content_type.data; nchan_add_response_header(r, &transfer_encoding_header, &transfer_encoding); nchan_cleverly_output_headers_only_for_later_response(r); fsub->data.shook_hands = 1; } }
static void es_ensure_headers_sent(full_subscriber_t *fsub) { static const ngx_str_t content_type = ngx_string("text/event-stream; charset=utf-8"); static const ngx_str_t hello = ngx_string(": hi\n\n"); 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_buf_and_chain_t bc; nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, nchan_module); nchan_loc_conf_t *cf; if(!fsub->data.shook_hands) { clcf->chunked_transfer_encoding = 0; r->headers_out.content_type.len = content_type.len; r->headers_out.content_type.data = content_type.data; r->headers_out.content_length_n = -1; //send headers if(ctx->request_origin_header.len > 0) { cf = ngx_http_get_module_loc_conf(r, nchan_module); nchan_add_response_header(r, &NCHAN_HEADER_ALLOW_ORIGIN, &cf->allow_origin); } nchan_cleverly_output_headers_only_for_later_response(r); //send a ":hi" comment ngx_init_set_membuf(&bc.buf, hello.data, hello.data + hello.len); bc.chain.buf = &bc.buf; bc.buf.last_buf = 0; bc.buf.flush = 1; bc.chain.next = NULL; nchan_output_filter(fsub->sub.request, &bc.chain); fsub->data.shook_hands = 1; } }
static ngx_int_t longpoll_respond_message(subscriber_t *self, nchan_msg_t *msg) { full_subscriber_t *fsub = (full_subscriber_t *)self; ngx_int_t rc; char *err = NULL; 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; DBG("%p respond req %p msg %p", self, r, msg); ctx->prev_msg_id = self->last_msgid; update_subscriber_last_msg_id(self, msg); ctx->msg_id = self->last_msgid; //disable abort handler fsub->data.cln->handler = empty_handler; //verify_unique_response(&fsub->data.request->uri, &self->last_msgid, msg, self); if(!cf->longpoll_multimsg) { assert(fsub->data.already_responded != 1); fsub->data.already_responded = 1; if(ctx->request_origin_header.len > 0) { nchan_add_response_header(r, &NCHAN_HEADER_ALLOW_ORIGIN, &cf->allow_origin); } if((rc = nchan_respond_msg(r, msg, &self->last_msgid, 0, &err)) != NGX_OK) { return abort_response(self, err); } } else { if((rc = longpoll_multimsg_add(fsub, msg, &err)) != NGX_OK) { return abort_response(self, err); } } dequeue_maybe(self); return rc; }
static ngx_int_t longpoll_multimsg_respond(full_subscriber_t *fsub) { 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; char *err; ngx_int_t rc; u_char char_boundary[50]; u_char *char_boundary_last; ngx_int_t i; ngx_buf_t boundary[3]; //first, mid, and last boundary ngx_chain_t *chains, *first_chain = NULL, *last_chain = NULL; ngx_buf_t *buf; ngx_buf_t double_newline_buf; ngx_str_t *content_type; size_t size = 0; nchan_longpoll_multimsg_t *first, *cur; fsub->sub.dequeue_after_response = 1; if(ctx->request_origin_header.len > 0) { nchan_add_response_header(r, &NCHAN_HEADER_ALLOW_ORIGIN, &cf->allow_origin); } if(fsub->data.multimsg_first == fsub->data.multimsg_last) { //just one message. if((rc = nchan_respond_msg(r, fsub->data.multimsg_first->msg, &fsub->sub.last_msgid, 0, &err)) != NGX_OK) { return abort_response(&fsub->sub, err); } return NGX_OK; } //multi messages nchan_request_set_content_type_multipart_boundary_header(r, ctx); char_boundary_last = ngx_snprintf(char_boundary, 50, ("\r\n--%V--\r\n"), nchan_request_multipart_boundary(r, ctx)); ngx_memzero(&double_newline_buf, sizeof(double_newline_buf)); double_newline_buf.start = (u_char *)"\r\n\r\n"; double_newline_buf.end = double_newline_buf.start + 4; double_newline_buf.pos = double_newline_buf.start; double_newline_buf.last = double_newline_buf.end; double_newline_buf.memory = 1; //set up the boundaries for(i=0; i<3; i++) { ngx_memzero(&boundary[i], sizeof(ngx_buf_t)); boundary[i].memory = 1; if(i==0) { boundary[i].start = &char_boundary[2]; boundary[i].end = &char_boundary_last[-4]; } else if(i==1) { boundary[i].start = &char_boundary[0]; boundary[i].end = &char_boundary_last[-4]; } else if(i==2) { boundary[i].start = &char_boundary[0]; boundary[i].end = char_boundary_last; boundary[i].last_buf = 1; boundary[i].last_in_chain = 1; boundary[i].flush = 1; } boundary[i].pos = boundary[i].start; boundary[i].last = boundary[i].end; } first = fsub->data.multimsg_first; for(cur = first; cur != NULL; cur = cur->next) { chains = ngx_palloc(r->pool, sizeof(*chains)*4); if(last_chain) { last_chain->next = &chains[0]; } if(!first_chain) { first_chain = &chains[0]; } chains[0].buf = cur == first ? &boundary[0] : &boundary[1]; chains[0].next = &chains[1]; size += ngx_buf_size(chains[0].buf); content_type = &cur->msg->content_type; if (content_type->data != NULL) { buf = ngx_pcalloc(r->pool, sizeof(*buf) + content_type->len + 25); buf->memory = 1; buf->start = (u_char *)&buf[1]; buf->end = ngx_snprintf(buf->start, content_type->len + 25, "\r\nContent-Type: %V\r\n\r\n", content_type); buf->pos = buf->start; buf->last = buf->end; chains[1].buf = buf; } else { chains[1].buf = &double_newline_buf; } size += ngx_buf_size(chains[1].buf); if(ngx_buf_size(cur->msg->buf) > 0) { chains[1].next = &chains[2]; buf = ngx_palloc(r->pool, sizeof(*buf)); ngx_memcpy(buf, cur->msg->buf, sizeof(*buf)); nchan_msg_buf_open_fd_if_needed(buf, NULL, r); chains[2].buf = buf; size += ngx_buf_size(chains[2].buf); last_chain = &chains[2]; } else { last_chain = &chains[1]; } if(cur->next == NULL) { last_chain->next = &chains[3]; chains[3].buf = &boundary[2]; chains[3].next = NULL; last_chain = &chains[3]; size += ngx_buf_size(chains[3].buf); } } r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = size; nchan_set_msgid_http_response_headers(r, &fsub->data.multimsg_last->msg->id); ngx_http_send_header(r); nchan_output_filter(r, first_chain); return NGX_OK; }
static ngx_int_t subscriber_authorize_callback(ngx_int_t rc, ngx_http_request_t *sr, void *data) { nchan_subscribe_auth_request_data_t *d = data; subscriber_t *sub = d->sub; if(sub->status == DEAD) { nchan_requestmachine_request_cleanup_manual(d->subrequest); sub->fn->release(d->sub, 0); } else if (rc == NGX_HTTP_CLIENT_CLOSED_REQUEST) { //this shouldn't happen, but if it does, no big deal nchan_requestmachine_request_cleanup_manual(d->subrequest); sub->fn->release(d->sub, 1); sub->fn->respond_status(sub, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL, NULL); //couldn't reach upstream } else if(rc == NGX_OK) { ngx_int_t code = sr->headers_out.status; sub->fn->release(sub, 1); if(code >= 200 && code <299) { nchan_requestmachine_request_cleanup_manual(d->subrequest); nchan_subscriber_subscribe(sub, d->ch_id); } else { //forbidden, but with some data to forward to the subscriber ngx_http_request_t *r = d->sub->request; ngx_str_t *content_type; ngx_int_t content_length; ngx_chain_t *request_chain = NULL; content_type = (sr->upstream->headers_in.content_type ? &sr->upstream->headers_in.content_type->value : NULL); content_length = nchan_subrequest_content_length(sr); if(content_length > 0) { #if nginx_version >= 1013010 request_chain = sr->out; #else request_chain = sr->upstream->out_bufs; #endif } //copy headers ngx_uint_t i; ngx_list_part_t *part = &sr->headers_out.headers.part; ngx_table_elt_t *header= part->elts; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (!nchan_strmatch(&header[i].key, 4, "Content-Type", "Server", "Content-Length", "Connection")) { //copy header to main request's response nchan_add_response_header(r, &header[i].key, &header[i].value); } } if(content_type) { r->headers_out.content_type = *content_type; } r->headers_out.content_length_n = content_length; nchan_requestmachine_request_cleanup_on_request_finalize(d->subrequest, r); sub->fn->respond_status(sub, code, NULL, request_chain); //auto-closes subscriber } } else if(rc >= 500 && rc < 600) { nchan_requestmachine_request_cleanup_manual(d->subrequest); sub->fn->release(d->sub, 1); sub->fn->respond_status(sub, rc, NULL, NULL); //auto-closes subscriber } else { nchan_requestmachine_request_cleanup_manual(d->subrequest); sub->fn->release(d->sub, 1); d->sub->fn->respond_status(d->sub, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL, NULL); //auto-closes subscriber } return NGX_OK; }