static ngx_int_t websocket_publish_callback(ngx_int_t status, nchan_channel_t *ch, full_subscriber_t *fsub) { time_t last_seen = 0; ngx_uint_t subscribers = 0; ngx_uint_t messages = 0; nchan_msg_id_t *msgid = NULL; ngx_http_request_t *r = fsub->sub.request; ngx_str_t *accept_header = NULL; ngx_buf_t *tmp_buf; nchan_buf_and_chain_t *bc = nchan_bufchain_pool_reserve(fsub->ctx->bcp, 1); if(ch) { subscribers = ch->subscribers; last_seen = ch->last_seen; messages = ch->messages; msgid = &ch->last_published_msg_id; } if(websocket_release(&fsub->sub, 0) == NGX_ABORT) { //zombie publisher //nothing more to do, we're finished here return NGX_OK; } switch(status) { case NCHAN_MESSAGE_QUEUED: case NCHAN_MESSAGE_RECEIVED: if(fsub->sub.cf->sub.websocket) { //don't reply with status info, this websocket is used for subscribing too, //so it should only be recieving messages return NGX_OK; } if(r->headers_in.accept) { accept_header = &r->headers_in.accept->value; } tmp_buf = nchan_channel_info_buf(accept_header, messages, subscribers, last_seen, msgid, NULL); ngx_memcpy(&bc->buf, tmp_buf, sizeof(*tmp_buf)); bc->buf.last_buf=1; nchan_output_filter(fsub->sub.request, websocket_frame_header_chain(fsub, WEBSOCKET_TEXT_LAST_FRAME_BYTE, ngx_buf_size((&bc->buf)), &bc->chain)); break; case NGX_ERROR: case NGX_HTTP_INTERNAL_SERVER_ERROR: assert(0); break; } return NGX_OK; }
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 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; } }
static ngx_int_t multipart_respond_message(subscriber_t *sub, nchan_msg_t *msg) { full_subscriber_t *fsub = (full_subscriber_t *)sub; ngx_buf_t *buf, *msg_buf = msg->buf, *msgid_buf; ngx_int_t rc; nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(fsub->sub.request, nchan_module); nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(fsub->sub.request, nchan_module); ngx_int_t n; nchan_buf_and_chain_t *bc; ngx_chain_t *chain; ngx_file_t *file_copy; multipart_privdata_t *mpd = (multipart_privdata_t *)fsub->privdata; headerbuf_t *headerbuf = nchan_reuse_queue_push(ctx->output_str_queue); u_char *cur = headerbuf->charbuf; if(fsub->data.timeout_ev.timer_set) { ngx_del_timer(&fsub->data.timeout_ev); ngx_add_timer(&fsub->data.timeout_ev, sub->cf->subscriber_timeout * 1000); } //generate the headers if(!cf->msg_in_etag_only) { //msgtime cur = ngx_cpymem(cur, "\r\nLast-Modified: ", sizeof("\r\nLast-Modified: ") - 1); cur = ngx_http_time(cur, msg->id.time); *cur++ = CR; *cur++ = LF; //msgtag cur = ngx_cpymem(cur, "Etag: ", sizeof("Etag: ") - 1); cur += msgtag_to_strptr(&msg->id, (char *)cur); *cur++ = CR; *cur++ = LF; } else { ngx_str_t *tmp_etag = msgid_to_str(&msg->id); cur = ngx_snprintf(cur, 58 + 10*NCHAN_FIXED_MULTITAG_MAX, "\r\nEtag: %V\r\n", tmp_etag); } n=4; if(msg->content_type.len == 0) { //don't need content_type buf'n'chain n--; } if(ngx_buf_size(msg_buf) == 0) { //don't need msgbuf n --; } if((bc = nchan_bufchain_pool_reserve(ctx->bcp, n)) == NULL) { ERR("cant allocate buf-and-chains for multipart/mixed client output"); return NGX_ERROR; } chain = &bc->chain; msgid_buf = chain->buf; //message id ngx_memzero(chain->buf, sizeof(ngx_buf_t)); chain->buf->memory = 1; chain->buf->start = headerbuf->charbuf; chain->buf->pos = headerbuf->charbuf; //content_type maybe if(msg->content_type.len > 0) { chain = chain->next; buf = chain->buf; msgid_buf->last = cur; msgid_buf->end = cur; ngx_memzero(buf, sizeof(ngx_buf_t)); buf->memory = 1; buf->start = cur; buf->pos = cur; buf->last = ngx_snprintf(cur, 255, "Content-Type: %V\r\n\r\n", &msg->content_type); buf->end = buf->last; } else { *cur++ = CR; *cur++ = LF; msgid_buf->last = cur; msgid_buf->end = cur; } chain = chain->next; buf = chain->buf; //msgbuf if(ngx_buf_size(msg_buf) > 0) { ngx_memcpy(buf, msg_buf, sizeof(*msg_buf)); if(msg_buf->file) { file_copy = nchan_bufchain_pool_reserve_file(ctx->bcp); nchan_msg_buf_open_fd_if_needed(buf, file_copy, NULL); } buf->last_buf = 0; buf->last_in_chain = 0; buf->flush = 0; } chain = chain->next; buf = chain->buf; ngx_memzero(buf, sizeof(ngx_buf_t)); buf->start = &mpd->boundary[0]; buf->pos = buf->start; buf->end = mpd->boundary_end; buf->last = buf->end; buf->memory = 1; buf->last_buf = 0; buf->last_in_chain = 1; buf->flush = 1; ctx->prev_msg_id = fsub->sub.last_msgid; update_subscriber_last_msg_id(sub, msg); ctx->msg_id = fsub->sub.last_msgid; multipart_ensure_headers_sent(fsub); DBG("%p output msg to subscriber", sub); rc = nchan_output_msg_filter(fsub->sub.request, msg, &bc->chain); return rc; }
static ngx_int_t rawstream_respond_message(subscriber_t *sub, nchan_msg_t *msg) { full_subscriber_t *fsub = (full_subscriber_t *)sub; ngx_buf_t *buf, *msg_buf = msg->buf; ngx_int_t rc; nchan_loc_conf_t *cf = ngx_http_get_module_loc_conf(fsub->sub.request, ngx_nchan_module); nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(fsub->sub.request, ngx_nchan_module); nchan_buf_and_chain_t *bc; ngx_chain_t *chain; ngx_file_t *file_copy; size_t separator_len = cf->subscriber_http_raw_stream_separator.len; size_t msg_len = ngx_buf_size((msg->buf)); if(fsub->data.timeout_ev.timer_set) { ngx_del_timer(&fsub->data.timeout_ev); ngx_add_timer(&fsub->data.timeout_ev, sub->cf->subscriber_timeout * 1000); } if(msg_len + separator_len == 0) { //nothing to output return NGX_OK; } if((bc = nchan_bufchain_pool_reserve(ctx->bcp, (1 + (msg_len > 0 ? 1: 0)))) == NULL) { ERR("cant allocate buf-and-chains for http-raw-stream client output"); return NGX_ERROR; } chain = &bc->chain; //message if(msg_len > 0) { buf = chain->buf; *buf = *msg_buf; if(buf->file) { file_copy = nchan_bufchain_pool_reserve_file(ctx->bcp); nchan_msg_buf_open_fd_if_needed(buf, file_copy, NULL); } buf->last_buf = 0; buf->last_in_chain = 0; buf->flush = 0; chain = chain->next; } //separator buf = chain->buf; ngx_memzero(buf, sizeof(ngx_buf_t)); buf->start = cf->subscriber_http_raw_stream_separator.data; buf->pos = buf->start; buf->end = buf->start + separator_len; buf->last = buf->end; buf->memory = 1; buf->last_buf = 0; buf->last_in_chain = 1; buf->flush = 1; rawstream_ensure_headers_sent(fsub); DBG("%p output msg to subscriber", sub); rc = nchan_output_msg_filter(fsub->sub.request, msg, &bc->chain); return rc; }
static ngx_int_t longpoll_multipart_respond(full_subscriber_t *fsub) { ngx_http_request_t *r = fsub->sub.request; nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_nchan_module); char *err; ngx_int_t rc; u_char *char_boundary = NULL; u_char *char_boundary_last; ngx_buf_t boundary[3]; //first, mid, and last boundary ngx_buf_t newline_buf; ngx_chain_t *chain, *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_loc_conf_t *cf = fsub->sub.cf; int use_raw_stream_separator = cf->longpoll_multimsg_use_raw_stream_separator; nchan_buf_and_chain_t *bc; nchan_longpoll_multimsg_t *first, *cur; //disable abort handler fsub->data.cln->handler = empty_handler; first = fsub->data.multimsg_first; fsub->sub.dequeue_after_response = 1; //cleanup to release msgs fsub->data.cln = ngx_http_cleanup_add(fsub->sub.request, 0); fsub->data.cln->data = first; fsub->data.cln->handler = (ngx_http_cleanup_pt )multipart_request_cleanup_handler; 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 if(!use_raw_stream_separator) { nchan_request_set_content_type_multipart_boundary_header(r, ctx); char_boundary = ngx_palloc(r->pool, 50); char_boundary_last = ngx_snprintf(char_boundary, 50, ("\r\n--%V--\r\n"), nchan_request_multipart_boundary(r, ctx)); ngx_init_set_membuf_char(&double_newline_buf, "\r\n\r\n"); //set up the boundaries ngx_init_set_membuf(&boundary[0], &char_boundary[2], &char_boundary_last[-4]); ngx_init_set_membuf(&boundary[1], &char_boundary[0], &char_boundary_last[-4]); ngx_init_set_membuf(&boundary[2], &char_boundary[0], char_boundary_last); ngx_init_set_membuf_char(&newline_buf, "\n"); } int n=0; for(cur = first; cur != NULL; cur = cur->next) { bc = nchan_bufchain_pool_reserve(ctx->bcp, 4); chain = &bc->chain; n++; if(last_chain) { last_chain->next = chain; } if(!first_chain) { first_chain = chain; } if(!use_raw_stream_separator) { // each buffer needs to be unique for the purpose of dealing with nginx output guts // (something about max. 64 iovecs per write call and counting the number of bytes already sent) *chain->buf = cur == first ? boundary[0] : boundary[1]; size += ngx_buf_size((chain->buf)); chain = chain->next; content_type = &cur->msg->content_type; buf = chain->buf; if (content_type->data != NULL) { u_char *char_cur = ngx_pcalloc(r->pool, content_type->len + 25); ngx_init_set_membuf(buf, char_cur, ngx_snprintf(char_cur, content_type->len + 25, "\r\nContent-Type: %V\r\n\r\n", content_type)); } else { *buf = double_newline_buf; } size += ngx_buf_size(buf); chain = chain->next; } if(ngx_buf_size(cur->msg->buf) > 0) { buf = chain->buf; *buf = *cur->msg->buf; if(buf->file) { ngx_file_t *file_copy = nchan_bufchain_pool_reserve_file(ctx->bcp); nchan_msg_buf_open_fd_if_needed(buf, file_copy, NULL); } buf->last_buf = 0; size += ngx_buf_size(buf); } if(use_raw_stream_separator) { chain = chain->next; ngx_init_set_membuf_str(chain->buf, &cf->subscriber_http_raw_stream_separator); size += ngx_buf_size((chain->buf)); } else { if(cur->next == NULL) { chain = chain->next; chain->buf = &boundary[2]; size += ngx_buf_size((chain->buf)); } } last_chain = chain; } buf = last_chain->buf; buf->last_buf = 1; buf->last_in_chain = 1; buf->flush = 1; last_chain->next = NULL; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = size; nchan_set_msgid_http_response_headers(r, ctx, &fsub->data.multimsg_last->msg->id); nchan_include_access_control_if_needed(r, ctx); ngx_http_send_header(r); nchan_output_filter(r, first_chain); return NGX_OK; }
static ngx_int_t chunked_respond_message(subscriber_t *sub, nchan_msg_t *msg) { full_subscriber_t *fsub = (full_subscriber_t *)sub; nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(fsub->sub.request, ngx_nchan_module); chunksizebuf_t *chunksizebuf = nchan_reuse_queue_push(ctx->output_str_queue); u_char *chunk_start = &chunksizebuf->chr[0]; static u_char *chunk_end=(u_char *)"\r\n"; ngx_file_t *file_copy; nchan_buf_and_chain_t *bc = nchan_bufchain_pool_reserve(ctx->bcp, 3); ngx_chain_t *chain; ngx_buf_t *buf, *msg_buf = &msg->buf; ngx_int_t rc; if(fsub->data.timeout_ev.timer_set) { ngx_del_timer(&fsub->data.timeout_ev); ngx_add_timer(&fsub->data.timeout_ev, sub->cf->subscriber_timeout * 1000); } ctx->prev_msg_id = fsub->sub.last_msgid; update_subscriber_last_msg_id(sub, msg); ctx->msg_id = fsub->sub.last_msgid; if (ngx_buf_size(msg_buf) == 0) { //empty messages are skipped, because a zero-length chunk finalizes the request return NGX_OK; } //chunk size chain = &bc->chain; buf = chain->buf; ngx_memzero(buf, sizeof(*buf)); buf->memory = 1; buf->start = chunk_start; buf->pos = chunk_start; buf->end = ngx_snprintf(chunk_start, 15, "%xi\r\n", ngx_buf_size(msg_buf)); buf->last = buf->end; //message chain = chain->next; buf = chain->buf; *buf = *msg_buf; if(buf->file) { file_copy = nchan_bufchain_pool_reserve_file(ctx->bcp); nchan_msg_buf_open_fd_if_needed(buf, file_copy, NULL); } buf->last_buf = 0; buf->last_in_chain = 0; buf->flush = 0; //trailing newlines chain = chain->next; buf = chain->buf; ngx_memzero(buf, sizeof(*buf)); buf->start = chunk_end; buf->pos = chunk_end; buf->end = chunk_end + 2; buf->last = buf->end; buf->memory = 1; buf->last_buf = 0; buf->last_in_chain = 1; buf->flush = 1; chunked_ensure_headers_sent(fsub); DBG("%p output msg to subscriber", sub); rc = nchan_output_msg_filter(fsub->sub.request, msg, &bc->chain); return rc; }