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); }
static ngx_int_t nchan_subscriber_get_msg_id(ngx_http_request_t *r, nchan_msg_id_t *id) { static ngx_str_t last_event_id_header = ngx_string("Last-Event-ID"); ngx_str_t *last_event_id; ngx_str_t *if_none_match; if((last_event_id = nchan_get_header_value(r, last_event_id_header)) != NULL) { u_char *split, *last; ngx_int_t time; //"<msg_time>:<msg_tag>" last = last_event_id->data + last_event_id->len; if((split = ngx_strlchr(last_event_id->data, last, ':')) != NULL) { time = ngx_atoi(last_event_id->data, split - last_event_id->data); split++; if(time != NGX_ERROR) { id->time = time; nchan_parse_msg_tag(split, last, id); return NGX_OK; } } } if_none_match = nchan_subscriber_get_etag(r); id->time=(r->headers_in.if_modified_since == NULL) ? 0 : ngx_http_parse_time(r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len); if(if_none_match==NULL) { int i; for(i=0; i< NCHAN_MULTITAG_MAX; i++) { id->tag[i]=0; } id->tagcount=1; } else { nchan_parse_msg_tag(if_none_match->data, if_none_match->data + if_none_match->len, id); } return NGX_OK; }
ngx_int_t nchan_detect_chunked_subscriber_request(ngx_http_request_t *r) { static ngx_str_t TE_HEADER = ngx_string("TE"); ngx_str_t *tmp; u_char *cur, *last; if(r->method != NGX_HTTP_GET) { return 0; } if((tmp = nchan_get_header_value(r, TE_HEADER)) != NULL) { last = tmp->data + tmp->len; cur = ngx_strlcasestrn(tmp->data, last, (u_char *)"chunked", 7 - 1); if(cur == NULL) { return 0; } //see if there's a qvalue cur += 7; if((cur + 1 <= last) && cur[0]==' ') { //no qvalue. assume non-zero, meaning it's legit return 1; } else if((cur + 4) < last) { //maybe there is... if(cur[0]==';' && cur[1]=='q' && cur[2]=='=') { //parse the freaking qvalue cur += 3; ngx_int_t qval_fp; qval_fp = ngx_atofp(cur, last - cur, 2); if(qval_fp == NGX_ERROR) { DBG("invalid qval. reject."); return 0; } else if(qval_fp > 0) { //got nonzero qval. accept return 1; } else { //qval=0. reject return 0; } } else { //we're looking at "chunkedsomething", not "chunked;q=<...>". reject. return 0; } } else if (cur == last){ //last thing in the header. "chunked". accept return 1; } else { //too small to have a qvalue, not followed by a space. must be "chunkedsomething" return 0; } } else return 0; }
static ngx_int_t nchan_detect_websocket_handshake(ngx_http_request_t *r) { ngx_str_t *tmp; if(r->method != NGX_HTTP_GET) { return 0; } if((tmp = nchan_get_header_value(r, NCHAN_HEADER_CONNECTION))) { if(ngx_strncasecmp(tmp->data, NCHAN_UPGRADE.data, NCHAN_UPGRADE.len) != 0) return 0; } else return 0; if((tmp = nchan_get_header_value(r, NCHAN_HEADER_UPGRADE))) { if(ngx_strncasecmp(tmp->data, NCHAN_WEBSOCKET.data, NCHAN_WEBSOCKET.len) != 0) return 0; } else return 0; return 1; }
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; struct timeval tv; nchan_msg_t *msg; ngx_str_t *eventsource_event; #if FAKESHARD memstore_pub_debug_start(); #endif if((msg = ngx_pcalloc(r->pool, sizeof(*msg))) == NULL) { ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "nchan: can't allocate msg in request pool"); ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } msg->shared = 0; 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 { 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.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, nchan_module); msg->start_tv = ctx->start_tv; #endif cf->storage_engine->publish(channel_id, msg, cf, (callback_pt) &publish_callback, r); #if FAKESHARD memstore_pub_debug_end(); #endif }
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; }
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 }