static ngx_int_t spool_respond_general(subscriber_pool_t *self, nchan_msg_t *msg, ngx_int_t code, void *code_data, unsigned notice) { ngx_uint_t numsubs[SUBSCRIBER_TYPES]; spooled_subscriber_t *nsub, *nnext; subscriber_t *sub; //channel_spooler_t *spl = self->spooler; //validate_spooler(spl, "before respond_general"); //nchan_msg_id_t unid; //nchan_msg_id_t unprevid; //int8_t i, max; ngx_memzero(numsubs, sizeof(numsubs)); self->generation++; DBG("spool %p (%V) (subs: %i) respond with msg %p or code %i", self, msgid_to_str(&self->id), self->sub_count, msg, code); if(msg) { DBG("msgid: %V", msgid_to_str(&msg->id)); DBG("prev: %V", msgid_to_str(&msg->prev_id)); } /* if(msg && msg->prev_id.time > 0 && msg->id.tagcount > 1) { assert(msg->shared == 0); max = msg->id.tagcount; for(i=0; i< max; i++) { unid.tag[i] = msg->id.tag[i]; unprevid.tag[i] = msg->prev_id.tag[i]; if(unid.tag[i] == -1) msg->id.tag[i] = self->id.tag[i]; if(unprevid.tag[i] == -1) msg->prev_id.tag[i] = self->id.tag[i]; } } */ //uint8_t publish_events = self->spooler->publish_events; for(nsub = self->first; nsub != NULL; nsub = nnext) { sub = nsub->sub; nnext = nsub->next; if(msg) { self->responded_count++; sub->fn->respond_message(sub, msg); } else if(!notice) { self->responded_count++; sub->fn->respond_status(sub, code, code_data); } else { sub->fn->notify(sub, code, code_data); } } if(!notice && code != NGX_HTTP_NO_CONTENT) self->responded_count++; //assert(validate_spooler(spl, "after respond_general")); return NGX_OK; }
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 ngx_int_t spooler_respond_message(channel_spooler_t *self, nchan_msg_t *msg) { spooler_respond_data_t srdata; subscriber_pool_t *spool; ngx_int_t responded_subs = 0; srdata.min = msg->prev_id; srdata.max = msg->id; srdata.multi = msg->id.tagcount; srdata.overflow = NULL; srdata.msg = msg; srdata.n = 0; //find all spools between msg->prev_id and msg->id rbtree_conditional_walk(&self->spoolseed, (rbtree_walk_conditional_callback_pt )collect_spool_range_callback, &srdata); if(srdata.n == 0) { DBG("no spools in range %V -- ", msgid_to_str(&msg->prev_id)); DBG(" -- %V", msgid_to_str(&msg->id)); } while((spool = spoolcollector_unwind_nextspool(&srdata)) != NULL) { responded_subs += spool->sub_count; if(msg->id.tagcount > NCHAN_FIXED_MULTITAG_MAX) { assert(spool->id.tag.allocd != msg->id.tag.allocd); } spool_respond_general(spool, msg, 0, NULL, 0); if(msg->id.tagcount > NCHAN_FIXED_MULTITAG_MAX) { assert(spool->id.tag.allocd != msg->id.tag.allocd); } spool_nextmsg(spool, &msg->id); } spool = get_spool(self, &latest_msg_id); if(spool->sub_count > 0) { #if NCHAN_BENCHMARK responded_subs += spool->sub_count; #endif spool_respond_general(spool, msg, 0, NULL, 0); spool_nextmsg(spool, &msg->id); } nchan_copy_msg_id(&self->prev_msg_id, &msg->id, NULL); #if NCHAN_BENCHMARK self->last_responded_subscriber_count = responded_subs; #endif return NGX_OK; }
ngx_str_t nchan_subscriber_set_recyclable_msgid_str(nchan_request_ctx_t *ctx, nchan_msg_id_t *msgid) { ngx_str_t ret; msgidbuf_t *msgidbuf; msgidbuf = nchan_reuse_queue_push(ctx->output_str_queue); ret.data = &msgidbuf->chr[0]; nchan_strcpy(&ret, msgid_to_str(msgid), MSGID_BUF_LEN); return ret; }
static ngx_int_t spool_fetch_msg(subscriber_pool_t *spool) { fetchmsg_data_t *data; channel_spooler_t *spl = spool->spooler; if(*spl->channel_status != READY) { DBG("%p wanted to fetch msg %V, but channel %V not ready", spool, msgid_to_str(&spool->id), spl->chid); spool->msg_status = MSG_CHANNEL_NOTREADY; return NGX_DECLINED; } DBG("%p fetch msg %V for channel %V", spool, msgid_to_str(&spool->id), spl->chid); data = ngx_alloc(sizeof(*data), ngx_cycle->log); //correctness over efficiency (at first). //TODO: optimize this alloc away assert(data); data->next = spl->fetchmsg_cb_data_list; if(data->next) { data->next->prev = data; } spl->fetchmsg_cb_data_list = data; data->prev = NULL; nchan_copy_new_msg_id(&data->msgid, &spool->id); data->spooler = spool->spooler; assert(spool->msg == NULL); assert(spool->msg_status == MSG_INVALID); spool->msg_status = MSG_PENDING; if(spl->handlers->get_message_start) { spl->handlers->get_message_start(spl, spl->handlers_privdata); } switch(spl->fetching_strategy) { case FETCH: case FETCH_IGNORE_MSG_NOTFOUND: spool->spooler->store->get_message(spool->spooler->chid, &spool->id, spool->spooler->cf, (callback_pt )spool_fetch_msg_callback, data); break; case NO_FETCH: //do nothing break; } return NGX_OK; }
static ngx_int_t nchan_message_id_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { static u_char msgidbuf[100]; ngx_str_t *msgid; nchan_request_ctx_t *ctx = get_main_request_ctx(r); if(ctx == NULL || (ctx->msg_id.time == 0 && ctx->msg_id.tagcount == 0)) { v->not_found = 1; return NGX_OK; } msgid = msgid_to_str(&ctx->msg_id); ngx_memcpy(msgidbuf, msgid->data, msgid->len); set_varval(v, msgidbuf, msgid->len); return NGX_OK; }
static subscriber_pool_t *get_spool(channel_spooler_t *spl, nchan_msg_id_t *id) { rbtree_seed_t *seed = &spl->spoolseed; ngx_rbtree_node_t *node; subscriber_pool_t *spool; if(id->time == NCHAN_NEWEST_MSGID_TIME) { spool = &spl->current_msg_spool; spool->msg_status = MSG_EXPECTED; return &spl->current_msg_spool; } if((node = rbtree_find_node(seed, id)) == NULL) { if((node = rbtree_create_node(seed, sizeof(*spool))) == NULL) { ERR("can't create rbtree node for spool"); return NULL; } // DBG("CREATED spool node %p for msgid %V", node, msgid_to_str(id)); spool = (subscriber_pool_t *)rbtree_data_from_node(node); init_spool(spl, spool, id); if(rbtree_insert_node(seed, node) != NGX_OK) { ERR("couldn't insert spool node"); rbtree_destroy_node(seed, node); return NULL; } } else { spool = (subscriber_pool_t *)rbtree_data_from_node(node); DBG("found spool node %p with msgid %V", node, msgid_to_str(id)); assert(spool->id.time == id->time); } return spool; }
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 sub_respond_message(ngx_int_t status, nchan_msg_t *msg, sub_data_t* d) { nchan_msg_copy_t remsg; //nchan_msg_id_t *last_msgid; ngx_int_t mcount; int16_t tags[NCHAN_MULTITAG_MAX], prevtags[NCHAN_MULTITAG_MAX]; //remsg = ngx_alloc(sizeof(*remsg), ngx_cycle->log); //assert(remsg); assert( msg->id.tagcount == 1 ); assert( msg->prev_id.tagcount == 1 ); remsg.original = msg; remsg.copy = *msg; remsg.copy.shared = 0; remsg.copy.temp_allocd = 0; mcount = d->multi_chanhead->multi_count; remsg.copy.prev_id.tagcount = mcount; remsg.copy.prev_id.tagactive = d->n; remsg.copy.id.tagcount = mcount; remsg.copy.id.tagactive = d->n; if(mcount > NCHAN_FIXED_MULTITAG_MAX) { remsg.copy.id.tag.allocd = tags; tags[0]=msg->id.tag.fixed[0]; remsg.copy.prev_id.tag.allocd = prevtags; prevtags[0]=msg->prev_id.tag.fixed[0]; } nchan_expand_msg_id_multi_tag(&remsg.copy.prev_id, 0, d->n, -1); nchan_expand_msg_id_multi_tag(&remsg.copy.id, 0, d->n, -1); memstore_ensure_chanhead_is_ready(d->multi_chanhead, 1); DBG("%p respond with transformed message %p %V (%p %V %i) %V", d->multi->sub, &remsg.copy, msgid_to_str(&remsg.copy.id), d->multi_chanhead, &d->multi_chanhead->id, d->n, &d->multi->id); nchan_memstore_publish_generic(d->multi_chanhead, &remsg.copy, 0, NULL); return NGX_OK; }
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 spool_fetch_msg_callback(nchan_msg_status_t findmsg_status, nchan_msg_t *msg, fetchmsg_data_t *data) { nchan_msg_status_t prev_status; subscriber_pool_t *spool, *nuspool; channel_spooler_t *spl = data->spooler; int free_msg_id = 1; if(spl && data == spl->fetchmsg_cb_data_list) { spl->fetchmsg_cb_data_list = data->next; } if(data->next) { data->next->prev = data->prev; } if(data->prev) { data->prev->next = data->next; } if(spl == NULL) { //channel already deleted nchan_free_msg_id(&data->msgid); ngx_free(data); return NGX_OK; } if(spl->handlers->get_message_finish) { spl->handlers->get_message_finish(spl, spl->handlers_privdata); } if((spool = find_spool(spl, &data->msgid)) == NULL) { DBG("spool for msgid %V not found. discarding getmsg callback response.", msgid_to_str(&data->msgid)); nchan_free_msg_id(&data->msgid); ngx_free(data); return NGX_ERROR; } prev_status = spool->msg_status; switch(findmsg_status) { case MSG_FOUND: spool->msg_status = findmsg_status; DBG("fetchmsg callback for spool %p msg FOUND %p %V", spool, msg, msgid_to_str(&msg->id)); assert(msg != NULL); spool->msg = msg; spool_respond_general(spool, spool->msg, 0, NULL, 0); spool_nextmsg(spool, &msg->id); break; case MSG_EXPECTED: // ♫ It's gonna be the future soon ♫ if(spool->id.time == NCHAN_NTH_MSGID_TIME) { //wait for message in the NEWEST_ID spool nchan_msg_id_t newest_id = NCHAN_NEWEST_MSGID; spool_nextmsg(spool, &newest_id); } else { spool->msg_status = findmsg_status; DBG("fetchmsg callback for spool %p msg EXPECTED", spool); spool_respond_general(spool, NULL, NGX_HTTP_NO_CONTENT, NULL, 0); assert(msg == NULL); spool->msg = NULL; } break; case MSG_NORESPONSE: if(prev_status == MSG_PENDING) { spool->msg_status = MSG_INVALID; if(spool->sub_count > 0) { nomsg_retry_data_t *retry_data = ngx_alloc(sizeof(*retry_data), ngx_cycle->log); retry_data->spooler = spl; free_msg_id = 0; retry_data->msg_id = data->msgid; spooler_add_timer(spl, NCHAN_MSG_NORESPONSE_RETRY_TIME, spool_fetch_msg_noresponse_retry_callback, spool_fetch_msg_noresponse_retry_cancel, retry_data); } } break; case MSG_NOTFOUND: if(spl->fetching_strategy == FETCH_IGNORE_MSG_NOTFOUND) { spool->msg_status = prev_status; break; } case MSG_EXPIRED: //is this right? //TODO: maybe message-expired notification spool->msg_status = findmsg_status; spool_respond_general(spool, NULL, NGX_HTTP_NO_CONTENT, NULL, 0); nuspool = get_spool(spool->spooler, &oldest_msg_id); if(spool != nuspool) { spool_transfer_subscribers(spool, nuspool, 1); destroy_spool(spool); } else { ERR("Unexpected spool == nuspool during spool fetch_msg_callback. This is weird, please report this to the developers. findmsg_status: %i", findmsg_status); assert(0); } break; case MSG_PENDING: ERR("spool %p set status to MSG_PENDING", spool); break; default: assert(0); break; } if(free_msg_id) { nchan_free_msg_id(&data->msgid); } ngx_free(data); return NGX_OK; }
static ngx_int_t spool_nextmsg(subscriber_pool_t *spool, nchan_msg_id_t *new_last_id) { subscriber_pool_t *newspool; channel_spooler_t *spl = spool->spooler; ngx_int_t immortal_spool = spool->id.time == NCHAN_NEWEST_MSGID_TIME; int16_t largetags[NCHAN_MULTITAG_MAX]; nchan_msg_id_t new_id = NCHAN_ZERO_MSGID; nchan_copy_msg_id(&new_id, &spool->id, largetags); nchan_update_multi_msgid(&new_id, new_last_id, largetags); //ERR("spool %p nextmsg (%V) --", spool, msgid_to_str(&spool->id)); //ERR(" -- update with (%V) --", msgid_to_str(new_last_id)); //ERR(" -- newid %V", msgid_to_str(&new_id)); if(msg_ids_equal(&spool->id, &new_id)) { ERR("nextmsg id same as curmsg (%V)", msgid_to_str(&spool->id)); assert(0); } else { newspool = !immortal_spool ? find_spool(spl, &new_id) : get_spool(spl, &new_id); if(newspool != NULL) { assert(spool != newspool); spool_transfer_subscribers(spool, newspool, 0); if(!immortal_spool) destroy_spool(spool); } else { ngx_rbtree_node_t *node; assert(!immortal_spool); node = rbtree_node_from_data(spool); rbtree_remove_node(&spl->spoolseed, node); nchan_copy_msg_id(&spool->id, &new_id, NULL); rbtree_insert_node(&spl->spoolseed, node); spool->msg_status = MSG_INVALID; spool->msg = NULL; newspool = spool; /* newspool = get_spool(spl, &new_id); assert(spool != newspool); spool_transfer_subscribers(spool, newspool, 0); destroy_spool(spool); */ } if(newspool->non_internal_sub_count > 0 && spl->handlers->bulk_post_subscribe != NULL) { spl->handlers->bulk_post_subscribe(spl, newspool->non_internal_sub_count, spl->handlers_privdata); } if(newspool->sub_count > 0) { switch(newspool->msg_status) { case MSG_CHANNEL_NOTREADY: newspool->msg_status = MSG_INVALID; case MSG_INVALID: spool_fetch_msg(newspool); break; case MSG_EXPECTED: spool_respond_general(newspool, NULL, NGX_HTTP_NO_CONTENT, NULL, 0); break; default: break; } } } return NGX_OK; }
ngx_buf_t *nchan_channel_info_buf(ngx_str_t *accept_header, ngx_uint_t messages, ngx_uint_t subscribers, time_t last_seen, nchan_msg_id_t *last_msgid, ngx_str_t **generated_content_type) { ngx_buf_t *b = &channel_info_buf; ngx_uint_t len; const ngx_str_t *format = &NCHAN_CHANNEL_INFO_PLAIN; time_t time_elapsed = ngx_time() - last_seen; static nchan_msg_id_t zero_msgid = NCHAN_ZERO_MSGID; if(!last_msgid) { last_msgid = &zero_msgid; } ngx_memcpy(&channel_info_content_type, &NCHAN_CONTENT_TYPE_TEXT_PLAIN, sizeof(NCHAN_CONTENT_TYPE_TEXT_PLAIN)); b->start = channel_info_buf_str; b->pos = b->start; b->last_buf = 1; b->last_in_chain = 1; b->flush = 1; b->memory = 1; if(accept_header) { //lame content-negotiation (without regard for qvalues) u_char *accept = accept_header->data; len = accept_header->len; size_t rem; u_char *cur = accept; u_char *priority=&accept[len-1]; for(rem=len; (cur = ngx_strnstr(cur, "text/", rem))!=NULL; cur += sizeof("text/")-1) { rem=len - ((size_t)(cur-accept)+sizeof("text/")-1); if(ngx_strncmp(cur+sizeof("text/")-1, "plain", rem<5 ? rem : 5)==0) { if(priority) { format = &NCHAN_CHANNEL_INFO_PLAIN; priority = cur+sizeof("text/")-1; //content-type is already set by default } } nchan_match_channel_info_subtype(sizeof("text/")-1, cur, rem, &priority, &format, &channel_info_content_type); } cur = accept; for(rem=len; (cur = ngx_strnstr(cur, "application/", rem))!=NULL; cur += sizeof("application/")-1) { rem=len - ((size_t)(cur-accept)+sizeof("application/")-1); nchan_match_channel_info_subtype(sizeof("application/")-1, cur, rem, &priority, &format, &channel_info_content_type); } } if(generated_content_type) { *generated_content_type = &channel_info_content_type; } len = format->len - 8 - 1 + 3*NGX_INT_T_LEN; //minus 8 sprintf if(len > 512) { ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "NCHAN: Channel info string too long: max: 512, is: %i", len); len = 512; } b->last = ngx_sprintf(b->start, (char *)format->data, messages, last_seen==0 ? -1 : (ngx_int_t) time_elapsed, subscribers, msgid_to_str(last_msgid)); b->end = b->last; return b; }