//allocates message and responds to subscriber
ngx_int_t ngx_http_push_alloc_for_subscriber_response(ngx_pool_t *pool, ngx_int_t shared, ngx_http_push_msg_t *msg, ngx_chain_t **chain, ngx_str_t **content_type, ngx_str_t **etag, time_t *last_modified) {
  if(etag != NULL && (*etag = ngx_http_push_store->message_etag(msg, pool))==NULL) {
    //oh, nevermind...
    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push module: unable to allocate memory for Etag header");
    return NGX_ERROR;
  }
  if(content_type != NULL && (*content_type= ngx_http_push_store->message_content_type(msg, pool))==NULL) {
    //oh, nevermind...
    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push module: unable to allocate memory for Content Type header");
    if(pool == NULL) {
      ngx_free(*etag);
    }
    else {
      ngx_pfree(pool, *etag);
    }
    return NGX_ERROR;
  }
  
  //preallocate output chain. yes, same one for every waiting subscriber
  if(chain != NULL && (*chain = ngx_http_push_create_output_chain(msg->buf, pool, ngx_cycle->log))==NULL) {
    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push module: unable to allocate buffer chain while responding to subscriber request");
    if(pool == NULL) {
      ngx_free(*etag);
      ngx_free(*content_type);
    }
    else {
      ngx_pfree(pool, *etag);
      ngx_pfree(pool, *content_type);
    }
    return NGX_ERROR;
  }
  
  if(last_modified != NULL) {
    *last_modified = msg->message_time;
  }
  ngx_http_push_store->unlock();
  
  
  if(pool!=NULL && shared == 0 && ((*chain)->buf->file!=NULL)) {
    //close file when we're done with it
    ngx_pool_cleanup_t *cln;
    ngx_pool_cleanup_file_t *clnf;
    
    if((cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t)))==NULL) {
      ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push module: unable to allocate buffer chain pool cleanup while responding to subscriber request");
      ngx_pfree(pool, *etag);
      ngx_pfree(pool, *content_type);
      ngx_pfree(pool, *chain);
      return NGX_ERROR;
    }
    cln->handler = ngx_pool_cleanup_file;
    clnf = cln->data;
    clnf->fd = (*chain)->buf->file->fd;
    clnf->name = (*chain)->buf->file->name.data;
    clnf->log = ngx_cycle->log;
  }
  return NGX_OK;
}
//print information about a channel
static ngx_int_t ngx_http_push_channel_info(ngx_http_request_t *r, ngx_uint_t messages, ngx_uint_t subscribers, time_t last_seen) {
	ngx_buf_t                      *b;
	ngx_uint_t                      len;
	ngx_str_t                       content_type = ngx_string("text/plain");
	const ngx_str_t                *format = &NGX_HTTP_PUSH_CHANNEL_INFO_PLAIN;
	time_t                          time_elapsed = ngx_time() - last_seen;
	
	if(r->headers_in.accept) {
		//lame content-negotiation (without regard for qvalues)
		u_char                    *accept = r->headers_in.accept->value.data;
		size_t                     len = r->headers_in.accept->value.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 = &NGX_HTTP_PUSH_CHANNEL_INFO_PLAIN;
					priority = cur+sizeof("text/")-1;
					//content-type is already set by default
				}
			}
			ngx_http_push_match_channel_info_subtype(sizeof("text/")-1, cur, rem, &priority, &format, &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);
			ngx_http_push_match_channel_info_subtype(sizeof("application/")-1, cur, rem, &priority, &format, &content_type);
		}
	}

	r->headers_out.content_type.len = content_type.len;
	r->headers_out.content_type.data = content_type.data;
	r->headers_out.content_type_len = r->headers_out.content_type.len;
	
	len = format->len - 8 - 1 + 3*NGX_INT_T_LEN; //minus 8 sprintf
	
	if ((b = ngx_create_temp_buf(r->pool, len)) == NULL) {
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}
	b->last = ngx_sprintf(b->last, (char *)format->data, messages, last_seen==0 ? -1 : (ngx_int_t) time_elapsed ,subscribers);
	
	//lastly, set the content-length, because if the status code isn't 200, nginx may not do so automatically
	r->headers_out.content_length_n = ngx_buf_size(b);
	
	if (ngx_http_send_header(r) > NGX_HTTP_SPECIAL_RESPONSE) {
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}
	
	return ngx_http_output_filter(r, ngx_http_push_create_output_chain(b, r->pool, r->connection->log));
}
static ngx_str_t * ngx_http_push_get_channel_id(ngx_http_request_t *r, ngx_http_push_loc_conf_t *cf) {
	ngx_http_variable_value_t      *vv = ngx_http_get_indexed_variable(r, cf->index);
	ngx_str_t                      *group = &cf->channel_group;
	size_t                          group_len = group->len;
	size_t                          var_len;
	size_t                          len;
	ngx_str_t                      *id;
    if (vv == NULL || vv->not_found || vv->len == 0) {
        ngx_buf_t *buf = ngx_create_temp_buf(r->pool, sizeof(NGX_HTTP_PUSH_NO_CHANNEL_ID_MESSAGE));
		ngx_chain_t *chain;
		if(buf==NULL) {
			return NULL;
		}
		buf->pos=(u_char *)NGX_HTTP_PUSH_NO_CHANNEL_ID_MESSAGE;
		buf->last=buf->pos + sizeof(NGX_HTTP_PUSH_NO_CHANNEL_ID_MESSAGE)-1;
		chain = ngx_http_push_create_output_chain(buf, r->pool, r->connection->log);
		buf->last_buf=1;
		r->headers_out.content_length_n=ngx_buf_size(buf);
		r->headers_out.status=NGX_HTTP_NOT_FOUND;
		r->headers_out.content_type.len = sizeof("text/plain") - 1;
		r->headers_out.content_type.data = (u_char *) "text/plain";
		r->headers_out.content_type_len = r->headers_out.content_type.len;
		ngx_http_send_header(r);
		ngx_http_output_filter(r, chain);
		ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
            "push module: the $push_channel_id variable is required but is not set");
		return NULL;
    }
	//maximum length limiter for channel id
	var_len = vv->len <= cf->max_channel_id_length ? vv->len : cf->max_channel_id_length; 
	len = group_len + 1 + var_len;
	if((id = ngx_palloc(r->pool, sizeof(*id) + len))==NULL) {
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
            "push module: unable to allocate memory for $push_channel_id string");
		return NULL;
	}
	id->len=len;
	id->data=(u_char *)(id+1);
	ngx_memcpy(id->data, group->data, group_len);
	id->data[group_len]='/';
	ngx_memcpy(id->data + group_len + 1, vv->data, var_len);
	return id;
}
static ngx_int_t ngx_http_push_subscriber_handler(ngx_http_request_t *r) {
	ngx_http_push_loc_conf_t       *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_module);
	ngx_slab_pool_t                *shpool = (ngx_slab_pool_t *)ngx_http_push_shm_zone->shm.addr;
	ngx_str_t                      *id;
	ngx_http_push_channel_t        *channel;
	ngx_http_push_msg_t            *msg;
	ngx_int_t                       msg_search_outcome;
	
	ngx_str_t                      *content_type=NULL;
	ngx_str_t                      *etag;
	
    if (r->method == NGX_HTTP_OPTIONS) {
        ngx_buf_t *buf = ngx_create_temp_buf(r->pool, sizeof(NGX_HTTP_PUSH_OPTIONS_OK_MESSAGE));
		ngx_chain_t *chain;
		buf->pos=(u_char *)NGX_HTTP_PUSH_OPTIONS_OK_MESSAGE;
		buf->last=buf->pos + sizeof(NGX_HTTP_PUSH_OPTIONS_OK_MESSAGE)-1;
		chain = ngx_http_push_create_output_chain(buf, r->pool, r->connection->log);
		buf->last_buf=1;
        r->headers_out.content_length_n=ngx_buf_size(buf);
		r->headers_out.status=NGX_HTTP_OK;
		ngx_http_send_header(r);
		ngx_http_output_filter(r, chain);
        return NGX_OK;
    }
    
	if (r->method != NGX_HTTP_GET) {
		ngx_http_push_add_response_header(r, &NGX_HTTP_PUSH_HEADER_ALLOW, &NGX_HTTP_PUSH_ALLOW_GET); //valid HTTP for the win
		return NGX_HTTP_NOT_ALLOWED;
	}    
	
	if((id=ngx_http_push_get_channel_id(r, cf)) == NULL) {
		return r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	//get the channel and check channel authorization while we're at it.
	ngx_shmtx_lock(&shpool->mutex);
	if (cf->authorize_channel==1) {
		channel = ngx_http_push_find_channel(id, r->connection->log);
	}else{
		channel = ngx_http_push_get_channel(id, r->connection->log, cf->channel_timeout);
	}

	if (channel==NULL) {
		//unable to allocate channel OR channel not found
		ngx_shmtx_unlock(&shpool->mutex);
		if(cf->authorize_channel) {
			return NGX_HTTP_FORBIDDEN;
		}
		else {
			ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate shared memory for channel");
			return NGX_HTTP_INTERNAL_SERVER_ERROR;
		}
	}

    msg = ngx_http_push_find_message_locked(channel, r, &msg_search_outcome); 
    channel->last_seen = ngx_time();
    channel->expires = ngx_time() + cf->channel_timeout;
    ngx_shmtx_unlock(&shpool->mutex);
    
    if (cf->ignore_queue_on_no_cache && !ngx_http_push_allow_caching(r)) {
        msg_search_outcome = NGX_HTTP_PUSH_MESSAGE_EXPECTED; 
        msg = NULL;
    }
	
	switch(ngx_http_push_handle_subscriber_concurrency(r, channel, cf)) {
		case NGX_DECLINED: //this request was declined for some reason.
			//status codes and whatnot should have already been written. just get out of here quickly.
			return NGX_OK;
		case NGX_ERROR:
			ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: error handling subscriber concurrency setting");
			return NGX_ERROR;
	}

	switch(msg_search_outcome) {
		//for message-found:
		ngx_chain_t                *chain;
		time_t                      last_modified;
		size_t                      content_type_len;

		case NGX_HTTP_PUSH_MESSAGE_EXPECTED:
			// ♫ It's gonna be the future soon ♫
			switch(cf->subscriber_poll_mechanism) {
				//for NGX_HTTP_PUSH_MECHANISM_LONGPOLL
				ngx_http_push_pid_queue_t  *sentinel, *cur, *found;
				ngx_http_push_subscriber_t *subscriber;
				ngx_http_push_subscriber_t *subscriber_sentinel;
				
				case NGX_HTTP_PUSH_MECHANISM_LONGPOLL:
					//long-polling subscriber. wait for a message.
					
					//subscribers are queued up in a local pool. Queue sentinels are separate and also local, but not in the pool.
					ngx_shmtx_lock(&shpool->mutex);
					sentinel = &channel->workers_with_subscribers;
					cur = (ngx_http_push_pid_queue_t *)ngx_queue_head(&sentinel->queue);
					found = NULL;
					
					ngx_http_push_subscriber_cleanup_t *clndata;
					ngx_pool_cleanup_t             *cln;
					while(cur!=sentinel) {
						if(cur->pid==ngx_pid) {
							found = cur;
							break;
						}
						cur = (ngx_http_push_pid_queue_t *)ngx_queue_next(&cur->queue);
					}
					if(found == NULL) { //found nothing
						if((found=ngx_http_push_slab_alloc_locked(sizeof(*found)))==NULL) {
							ngx_shmtx_unlock(&shpool->mutex);
							ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate worker subscriber queue marker in shared memory");
							return NGX_HTTP_INTERNAL_SERVER_ERROR;
						}
						//initialize
						ngx_queue_insert_tail(&sentinel->queue, &found->queue);
						found->pid=ngx_pid;
						found->slot=ngx_process_slot;
						found->subscriber_sentinel=NULL;
					}
					ngx_shmtx_unlock(&shpool->mutex);
					
					if((subscriber = ngx_palloc(ngx_http_push_pool, sizeof(*subscriber)))==NULL) { //unable to allocate request queue element
						return NGX_ERROR;
					}
					
					 //attach a cleaner to remove the request from the channel.
					if ((cln=ngx_pool_cleanup_add(r->pool, sizeof(*clndata))) == NULL) { //make sure we can.
						return NGX_ERROR;
					}
					cln->handler = (ngx_pool_cleanup_pt) ngx_http_push_subscriber_cleanup;
					clndata = (ngx_http_push_subscriber_cleanup_t *) cln->data;
					clndata->channel=channel;
					clndata->subscriber=subscriber;
					
					subscriber->request = r;
					subscriber->clndata=clndata;
					
					ngx_shmtx_lock(&shpool->mutex);
					channel->subscribers++; // do this only when we know everything went okay.
					
					//figure out the subscriber sentinel
					subscriber_sentinel = ((ngx_http_push_pid_queue_t *)found)->subscriber_sentinel;
					if(subscriber_sentinel==NULL) {
						//it's perfectly nornal for the sentinel to be NULL.
						if((subscriber_sentinel=ngx_palloc(ngx_http_push_pool, sizeof(*subscriber_sentinel)))==NULL) {
							ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate channel subscriber sentinel");
							return NGX_HTTP_INTERNAL_SERVER_ERROR;
						}
						ngx_queue_init(&subscriber_sentinel->queue);
						((ngx_http_push_pid_queue_t *)found)->subscriber_sentinel=subscriber_sentinel;
					}
					ngx_shmtx_unlock(&shpool->mutex);
					
					ngx_queue_insert_tail(&subscriber_sentinel->queue, &subscriber->queue);
					
					ngx_memzero(&subscriber->event, sizeof(subscriber->event));
					if (cf->subscriber_timeout > 0) {		
						subscriber->event.handler = ngx_http_push_clean_timeouted_subscriber;	
						subscriber->event.data = subscriber;
						subscriber->event.log = r->connection->log;
						ngx_add_timer(&subscriber->event, cf->subscriber_timeout * 1000);
					}

					//r->read_event_handler = ngx_http_test_reading;
					//r->write_event_handler = ngx_http_request_empty_handler;
					r->discard_body = 1;
					//r->keepalive = 1; //stayin' alive!!
					return NGX_DONE;
					
				case NGX_HTTP_PUSH_MECHANISM_INTERVALPOLL:
				
					//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=ngx_http_push_subscriber_get_etag(r)) != NULL) {
						r->headers_out.etag=ngx_http_push_add_response_header(r, &NGX_HTTP_PUSH_HEADER_ETAG, etag);
					}
					return NGX_HTTP_NOT_MODIFIED;
					
				default:
					//if this ever happens, there's a bug somewhere else. probably config stuff.
					return NGX_HTTP_INTERNAL_SERVER_ERROR;
			}
		
		case NGX_HTTP_PUSH_MESSAGE_EXPIRED:
			//subscriber wants an expired message
			//TODO: maybe respond with entity-identifiers for oldest available message?
			return NGX_HTTP_NO_CONTENT; 
		
		case NGX_HTTP_PUSH_MESSAGE_FOUND:
			//found the message
			ngx_shmtx_lock(&shpool->mutex);
			ngx_http_push_reserve_message_locked(channel, msg);
			NGX_HTTP_PUSH_MAKE_ETAG(msg->message_tag, etag, ngx_palloc, r->pool);
			if(etag==NULL) {
				//oh, nevermind...
				ngx_shmtx_unlock(&shpool->mutex);
				ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate memory for Etag header");
				return NGX_ERROR;
			}
			
			content_type_len = msg->content_type.len;
			if(content_type_len>0) {
				NGX_HTTP_PUSH_MAKE_CONTENT_TYPE(content_type, content_type_len, msg, r->pool);
				if(content_type==NULL) {
					ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate memory for content-type header while responding to subscriber request");
					ngx_shmtx_unlock(&shpool->mutex);
					return NGX_ERROR;
				}
			}
			
			//preallocate output chain. yes, same one for every waiting subscriber
			if((chain = ngx_http_push_create_output_chain_locked(msg->buf, r->pool, r->connection->log, shpool))==NULL) {
				ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push module: unable to allocate buffer chain while responding to subscriber request");
				ngx_shmtx_unlock(&shpool->mutex);
				return NGX_ERROR;
			}
			
			last_modified = msg->message_time;
			
			//is the message still needed?
			ngx_http_push_release_message_locked(channel, msg);
			ngx_shmtx_unlock(&shpool->mutex);
			
			if(chain->buf->file!=NULL) {
				//close file when we're done with it
				ngx_pool_cleanup_t *cln;
				ngx_pool_cleanup_file_t *clnf;
			 
				if((cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t)))==NULL) {
					return NGX_HTTP_INTERNAL_SERVER_ERROR;
				}
				cln->handler = ngx_pool_cleanup_file;
				clnf = cln->data;
				clnf->fd = chain->buf->file->fd;
				clnf->name = chain->buf->file->name.data;
				clnf->log = r->pool->log;
			}
			
			return ngx_http_push_prepare_response_to_subscriber_request(r, chain, content_type, etag, last_modified);
			
		default: //we shouldn't be here.
			return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}
}