static ngx_int_t chunked_respond_message(subscriber_t *sub, nchan_msg_t *msg) { static u_char chunk_start[15]; //that's enough static u_char *chunk_end=(u_char *)"\r\n"; full_subscriber_t *fsub = (full_subscriber_t *)sub; ngx_buf_t *msg_buf = msg->buf; ngx_int_t rc; nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(fsub->sub.request, nchan_module); if (ngx_buf_size(msg_buf) == 0) { //empty messages are skipped, because a zero-length chunk finalizes the request return NGX_OK; } nchan_buf_and_chain_t bc[3]; bc[0].chain.buf = &bc[0].buf; bc[0].chain.next = &bc[1].chain; ngx_memzero(&bc[0].buf, sizeof(ngx_buf_t)); bc[0].buf.memory = 1; bc[0].buf.start = chunk_start; bc[0].buf.pos = chunk_start; bc[0].buf.end = ngx_snprintf(chunk_start, 15, "%xi\r\n", ngx_buf_size(msg_buf)); bc[0].buf.last = bc[0].buf.end; bc[1].chain.buf = &bc[1].buf; bc[1].chain.next = &bc[2].chain; ngx_memcpy(&bc[1].buf, msg_buf, sizeof(*msg_buf)); bc[1].buf.last_buf = 0; bc[1].buf.last_in_chain = 0; bc[1].buf.flush = 0; bc[2].chain.buf = &bc[2].buf; bc[2].chain.next = NULL; ngx_memzero(&bc[2].buf, sizeof(ngx_buf_t)); bc[2].buf.start = chunk_end; bc[2].buf.pos = chunk_end; bc[2].buf.end = chunk_end + 2; bc[2].buf.last = bc[2].buf.end; bc[2].buf.memory = 1; bc[2].buf.last_buf = 0; bc[2].buf.last_in_chain = 1; bc[2].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; chunked_ensure_headers_sent(fsub); DBG("%p output msg to subscriber", sub); rc = nchan_output_filter(fsub->sub.request, &bc[0].chain); return rc; }
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 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 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 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 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 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 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 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; ngx_http_request_t *r = fsub->request; ngx_str_t *accept_header = NULL; ngx_buf_t *tmp_buf; if(ch) { subscribers = ch->subscribers; last_seen = ch->last_seen; messages = ch->messages; } 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, NULL); ngx_memcpy(&fsub->msg_buf, tmp_buf, sizeof(*tmp_buf)); fsub->msg_buf.last_buf=1; nchan_output_filter(fsub->request, websocket_frame_header_chain(fsub, WEBSOCKET_TEXT_LAST_FRAME_BYTE, ngx_buf_size((&fsub->msg_buf)))); break; case NGX_ERROR: case NGX_HTTP_INTERNAL_SERVER_ERROR: assert(0); break; } return NGX_OK; }
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; }
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; } }
static ngx_int_t es_respond_message(subscriber_t *sub, nchan_msg_t *msg) { static ngx_str_t terminal_newlines=ngx_string("\n\n"); full_subscriber_t *fsub = (full_subscriber_t *)sub; u_char *cur = NULL, *last = NULL; ngx_buf_t *msg_buf = msg->buf; ngx_buf_t databuf; ngx_pool_t *pool; nchan_buf_and_chain_t *bc; size_t len; ngx_chain_t *first_link = NULL, *last_link = NULL; ngx_file_t *msg_file; ngx_int_t rc; nchan_request_ctx_t *ctx = ngx_http_get_module_ctx(fsub->sub.request, nchan_module); ctx->prev_msg_id = fsub->sub.last_msgid; update_subscriber_last_msg_id(sub, msg); ctx->msg_id = fsub->sub.last_msgid; es_ensure_headers_sent(fsub); DBG("%p output msg to subscriber", sub); pool = ngx_create_pool(NGX_DEFAULT_LINEBREAK_POOL_SIZE, ngx_cycle->log); assert(pool); ngx_memcpy(&databuf, msg_buf, sizeof(*msg_buf)); databuf.last_buf = 0; if(!databuf.in_file) { cur = msg_buf->start; last = msg_buf->end; do { databuf.start = cur; databuf.pos = cur; databuf.end = last; databuf.last = last; cur = ngx_strlchr(cur, last, '\n'); if(cur == NULL) { //sweet, no newlines! //let's get out of this hellish loop databuf.end = last; databuf.last = last; cur = last + 1; } else { cur++; //include the newline databuf.end = cur; databuf.last = cur; } create_dataline_bufchain(pool, &first_link, &last_link, &databuf); } while(cur <= last); } else { //great, we've gotta scan this whole damn file for line breaks. //EventStream really isn't designed for large chunks of data off_t fcur, flast; ngx_fd_t fd; int chr_int; FILE *stream; msg_file = ngx_palloc(pool, sizeof(*msg_file)); databuf.file = msg_file; ngx_memcpy(msg_file, msg_buf->file, sizeof(*msg_file)); if(msg_file->fd == NGX_INVALID_FILE) { msg_file->fd = nchan_fdcache_get(&msg_file->name); } fd = msg_file->fd; stream = fdopen(dup(fd), "r"); fcur = databuf.file_pos; flast = databuf.file_last; fseek(stream, fcur, SEEK_SET); do { databuf.file_pos = fcur; databuf.file_last = flast; //getc that shit for(;;) { chr_int = getc(stream); if(chr_int == EOF) { break; } else if(chr_int == (int )'\n') { fcur++; break; } fcur++; } databuf.file_last = fcur; create_dataline_bufchain(pool, &first_link, &last_link, &databuf); } while(fcur < flast); fclose(stream); } //now 2 newlines at the end if(last_link) { bc = ngx_palloc(pool, sizeof(*bc)); last_link->next=&bc->chain; ngx_init_set_membuf(&bc->buf, terminal_newlines.data, terminal_newlines.data + terminal_newlines.len); bc->buf.flush = 1; bc->buf.last_buf = 0; bc->chain.next = NULL; bc->chain.buf = &bc->buf; last_link = &bc->chain; } //okay, this crazy data chain is finished. now how about the mesage tag? len = 10 + 2*NGX_INT_T_LEN; bc = ngx_palloc(pool, sizeof(*bc) + len); ngx_memzero(&bc->buf, sizeof(bc->buf)); cur = (u_char *)&bc[1]; ngx_init_set_membuf(&bc->buf, cur, ngx_snprintf(cur, len, "id: %V\n", msgid_to_str(&sub->last_msgid))); bc->chain.buf = &bc->buf; bc->chain.next = first_link; first_link=&bc->chain; rc = nchan_output_filter(fsub->sub.request, first_link); ngx_destroy_pool(pool); 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 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, nchan_module); 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; //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 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; } int n=0; for(cur = first; cur != NULL; cur = cur->next) { chains = ngx_palloc(r->pool, sizeof(*chains)*4); n++; if(last_chain) { last_chain->next = &chains[0]; } if(!first_chain) { first_chain = &chains[0]; } // 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) buf = ngx_pcalloc(r->pool, sizeof(*buf)); *buf = cur == first ? boundary[0] : boundary[1]; chains[0].buf = buf; 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 { buf = ngx_palloc(r->pool, sizeof(*buf)); chains[1].buf = buf; *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)); *buf = *cur->msg->buf; nchan_msg_buf_open_fd_if_needed(buf, NULL, r); buf->last_buf = 0; 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, 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; }