static ngx_int_t
ngx_http_push_stream_subscriber_polling_handler(ngx_http_request_t *r, ngx_http_push_stream_requested_channel_t *channels_ids, time_t if_modified_since, ngx_str_t *last_event_id, ngx_flag_t longpolling, ngx_pool_t *temp_pool)
{
    ngx_http_push_stream_loc_conf_t                *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
    ngx_slab_pool_t                                *shpool = (ngx_slab_pool_t *)ngx_http_push_stream_shm_zone->shm.addr;
    ngx_http_push_stream_subscriber_ctx_t          *ctx = ngx_http_get_module_ctx(r, ngx_http_push_stream_module);
    ngx_http_push_stream_requested_channel_t       *cur;
    ngx_http_push_stream_subscriber_t              *worker_subscriber;
    ngx_http_push_stream_channel_t                 *channel;
    ngx_http_push_stream_subscription_t            *subscription;
    ngx_str_t                                      *etag = NULL, vv_etag = ngx_null_string;
    ngx_int_t                                       tag;
    time_t                                          greater_message_time;
    ngx_int_t                                       greater_message_tag;
    ngx_flag_t                                      has_message_to_send = 0;
    ngx_str_t                                       callback_function_name;

    if (cf->last_received_message_tag != NULL) {
        ngx_http_push_stream_complex_value(r, cf->last_received_message_tag, &vv_etag);
        etag = vv_etag.len ? &vv_etag : NULL;
    } else {
        etag = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_IF_NONE_MATCH);
    }

    if (ngx_http_arg(r, NGX_HTTP_PUSH_STREAM_CALLBACK.data, NGX_HTTP_PUSH_STREAM_CALLBACK.len, &callback_function_name) == NGX_OK) {
        ngx_http_push_stream_unescape_uri(&callback_function_name);
        if ((ctx->callback = ngx_http_push_stream_get_formatted_chunk(callback_function_name.data, callback_function_name.len, r->pool)) == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for callback function name");
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    tag = ((etag != NULL) && ((tag = ngx_atoi(etag->data, etag->len)) != NGX_ERROR)) ? ngx_abs(tag) : -1;

    greater_message_tag = tag;
    greater_message_time = if_modified_since = (if_modified_since < 0) ? 0 : if_modified_since;

    ngx_shmtx_lock(&shpool->mutex);

    // check if has any message to send
    cur = channels_ids;
    while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) {
        channel = ngx_http_push_stream_find_channel(cur->id, r->connection->log);
        if (channel == NULL) {
            // channel not found
            ngx_shmtx_unlock(&shpool->mutex);
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate shared memory for channel %s", cur->id->data);
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (ngx_http_push_stream_has_old_messages_to_send(channel, cur->backtrack_messages, if_modified_since, tag, greater_message_time, greater_message_tag, last_event_id)) {
            has_message_to_send = 1;
            if (channel->last_message_time > greater_message_time) {
                greater_message_time = channel->last_message_time;
                greater_message_tag = channel->last_message_tag;
            } else {
                if ((channel->last_message_time == greater_message_time) && (channel->last_message_tag > greater_message_tag) ) {
                    greater_message_tag = channel->last_message_tag;
                }
            }
        }
    }


    if (longpolling && !has_message_to_send) {
        // long polling mode without messages
        if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) {
            ngx_shmtx_unlock(&shpool->mutex);
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
        worker_subscriber->longpolling = 1;

        if (ngx_http_push_stream_registry_subscriber_locked(r, worker_subscriber) == NGX_ERROR) {
            ngx_shmtx_unlock(&shpool->mutex);
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        // adding subscriber to channel(s)
        cur = channels_ids;
        while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) {
            if ((channel = ngx_http_push_stream_find_channel(cur->id, r->connection->log)) == NULL) {
                // channel not found
                ngx_shmtx_unlock(&shpool->mutex);
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate shared memory for channel %s", cur->id->data);
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            if ((subscription = ngx_http_push_stream_create_channel_subscription(r, channel, worker_subscriber)) == NULL) {
                ngx_shmtx_unlock(&shpool->mutex);
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            ngx_http_push_stream_assing_subscription_to_channel_locked(shpool, cur->id, subscription, &worker_subscriber->subscriptions_sentinel, r->connection->log);
        }

        ngx_shmtx_unlock(&shpool->mutex);
        return NGX_DONE;
    }

    ngx_shmtx_unlock(&shpool->mutex);

    // polling or long polling without messages to send

    ngx_http_push_stream_add_polling_headers(r, greater_message_time, greater_message_tag, temp_pool);

    if (!has_message_to_send) {
        // polling subscriber requests get a 304 with their entity tags preserved if don't have new messages.
        return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_MODIFIED, NULL);
    }

    // polling with messages or long polling without messages to send
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = -1;

    ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_TRANSFER_ENCODING, &NGX_HTTP_PUSH_STREAM_HEADER_CHUNCKED);
    ngx_http_send_header(r);

    // sending response content header
    if (ngx_http_push_stream_send_response_content_header(r, cf) == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: could not send content header to subscriber");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (ctx->callback != NULL) {
        ngx_http_push_stream_send_response_text(r, ctx->callback->data, ctx->callback->len, 0);
        ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_CALLBACK_INIT_CHUNK.data, NGX_HTTP_PUSH_STREAM_CALLBACK_INIT_CHUNK.len, 0);
    }

    cur = channels_ids;
    while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) {
        channel = ngx_http_push_stream_find_channel(cur->id, r->connection->log);
        if (channel == NULL) {
            // channel not found
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate shared memory for channel %s", cur->id->data);
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
        ngx_http_push_stream_send_old_messages(r, channel, cur->backtrack_messages, if_modified_since, tag, greater_message_time, greater_message_tag, last_event_id);
    }

    if (ctx->callback != NULL) {
        ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_CALLBACK_END_CHUNK.data, NGX_HTTP_PUSH_STREAM_CALLBACK_END_CHUNK.len, 0);
    }

    if (cf->footer_template.len > 0) {
        ngx_http_push_stream_send_response_text(r, cf->footer_template.data, cf->footer_template.len, 0);
    }

    ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.data, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.len, 1);

    return NGX_OK;
}
static ngx_int_t
ngx_http_push_stream_send_response_all_channels_info_detailed(ngx_http_request_t *r, ngx_str_t *prefix) {
    ngx_int_t                                 rc, content_len = 0;
    ngx_chain_t                              *chain, *first = NULL, *last = NULL;
    ngx_str_t                                *currenttime, *hostname, *text, *header_response;
    ngx_queue_t                               queue_channel_info;
    ngx_queue_t                              *cur, *next;
    ngx_http_push_stream_shm_data_t          *data = (ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data;
    ngx_slab_pool_t                          *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
    ngx_http_push_stream_content_subtype_t   *subtype = ngx_http_push_stream_match_channel_info_format_and_content_type(r, 1);
    ngx_pool_t                               *temp_pool = ngx_http_push_stream_get_temp_pool(r);

    const ngx_str_t *format;
    const ngx_str_t *head = subtype->format_group_head;
    const ngx_str_t *tail = subtype->format_group_tail;

    ngx_queue_init(&queue_channel_info);

    ngx_shmtx_lock(&shpool->mutex);
    ngx_http_push_stream_rbtree_walker_channel_info_locked(&data->tree, temp_pool, data->tree.root, &queue_channel_info, prefix);
    ngx_shmtx_unlock(&shpool->mutex);

    // format content body
    cur = ngx_queue_head(&queue_channel_info);
    while (cur != &queue_channel_info) {
        next = ngx_queue_next(cur);
        ngx_http_push_stream_channel_info_t *channel_info = (ngx_http_push_stream_channel_info_t *) cur;
        if ((chain = ngx_http_push_stream_get_buf(r)) == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for response channels info");
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        format = (next != &queue_channel_info) ? subtype->format_group_item : subtype->format_group_last_item;
        if ((text = ngx_http_push_stream_channel_info_formatted(temp_pool, format, &channel_info->id, channel_info->published_messages, channel_info->stored_messages, channel_info->subscribers)) == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory to format channel info");
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        chain->buf->last_buf = 0;
        chain->buf->memory = 1;
        chain->buf->pos = text->data;
        chain->buf->last = text->data + text->len;
        chain->buf->start = chain->buf->pos;
        chain->buf->end = chain->buf->last;

        content_len += text->len;

        if (first == NULL) {
            first = chain;
        }

        if (last != NULL) {
            last->next = chain;
        }

        last = chain;
        cur = next;
    }

    // get formatted current time
    currenttime = ngx_http_push_stream_get_formatted_current_time(temp_pool);

    // get formatted hostname
    hostname = ngx_http_push_stream_get_formatted_hostname(temp_pool);

    // format content header
    if ((header_response = ngx_http_push_stream_create_str(temp_pool, head->len + hostname->len + currenttime->len + NGX_INT_T_LEN)) == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for response channels info");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ngx_sprintf(header_response->data, (char *) head->data, hostname->data, currenttime->data, data->channels, data->broadcast_channels, ngx_time() - data->startup);
    header_response->len = ngx_strlen(header_response->data);

    content_len += header_response->len + tail->len;

    r->headers_out.content_type.len = subtype->content_type->len;
    r->headers_out.content_type.data = subtype->content_type->data;
    r->headers_out.content_length_n = content_len;
    r->headers_out.status = NGX_HTTP_OK;

    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    // send content header
    ngx_http_push_stream_send_response_text(r, header_response->data, header_response->len,0);
    // send content body
    if (first != NULL) {
        ngx_http_push_stream_output_filter(r, first);
    }
    // send content footer
    return ngx_http_push_stream_send_response_text(r, tail->data, tail->len, 1);
}
void
ngx_http_push_stream_websocket_reading(ngx_http_request_t *r)
{
    ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
    u_char                           buf[8];
    ngx_err_t                        err;
    ngx_event_t                     *rev;
    ngx_connection_t                *c;
    ngx_http_push_stream_frame_t     frame;
    ngx_str_t                       *aux;
    uint64_t                         i;
    ngx_pool_t                      *temp_pool = NULL;

    c = r->connection;
    rev = c->read;

#if (NGX_HAVE_KQUEUE)

    if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {

        if (!rev->pending_eof) {
            return;
        }

        rev->eof = 1;
        c->error = 1;
        err = rev->kq_errno;

        goto closed;
    }

#endif

    //reading frame header
    if (ngx_http_push_stream_recv(c, rev, &err, buf, 2) == NGX_ERROR) {
        goto closed;
    }

    frame.fin  = (buf[0] >> 7) & 1;
    frame.rsv1 = (buf[0] >> 6) & 1;
    frame.rsv2 = (buf[0] >> 5) & 1;
    frame.rsv3 = (buf[0] >> 4) & 1;
    frame.opcode = buf[0] & 0xf;

    frame.mask = (buf[1] >> 7) & 1;
    frame.payload_len = buf[1] & 0x7f;

    if (frame.payload_len == 126) {
        if (ngx_http_push_stream_recv(c, rev, &err, buf, 2) == NGX_ERROR) {
            goto closed;
        }
        uint16_t len;
        ngx_memcpy(&len, buf, 2);
        frame.payload_len = ntohs(len);
    } else if (frame.payload_len == 127) {
        if (ngx_http_push_stream_recv(c, rev, &err, buf, 8) == NGX_ERROR) {
            goto closed;
        }
        uint64_t len;
        ngx_memcpy(&len, buf, 8);
        frame.payload_len = ngx_http_push_stream_ntohll(len);
    }

    if (frame.mask && (ngx_http_push_stream_recv(c, rev, &err, frame.mask_key, 4) == NGX_ERROR)) {
        goto closed;
    }

    if (frame.payload_len > 0) {
        //create a temporary pool to allocate temporary elements
        if ((temp_pool = ngx_create_pool(4096, r->connection->log)) == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for temporary pool");
            ngx_http_finalize_request(r, NGX_OK);
            return;
        }

        aux = ngx_http_push_stream_create_str(temp_pool, frame.payload_len);
        if (ngx_http_push_stream_recv(c, rev, &err, aux->data, (ssize_t) frame.payload_len) == NGX_ERROR) {
            goto closed;
        }

        if (cf->websocket_allow_publish && (frame.opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_TEXT_OPCODE)) {
            frame.payload = aux->data;
            if (frame.mask) {
                for (i = 0; i < frame.payload_len; i++) {
                    frame.payload[i] = frame.payload[i] ^ frame.mask_key[i % 4];
                }
            }

            ngx_http_push_stream_subscriber_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_push_stream_module);
            ngx_http_push_stream_subscription_t *subscription = (ngx_http_push_stream_subscription_t *)ngx_queue_head(&ctx->subscriber->subscriptions_sentinel.queue);
            if (ngx_http_push_stream_add_msg_to_channel(r, &subscription->channel->id, frame.payload, frame.payload_len, NULL, NULL, temp_pool) == NULL) {
                ngx_http_finalize_request(r, NGX_OK);
                ngx_destroy_pool(temp_pool);
                return;
            } else {
                ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_WEBSOCKET_PING_LAST_FRAME_BYTE, sizeof(NGX_HTTP_PUSH_STREAM_WEBSOCKET_PING_LAST_FRAME_BYTE), 1);
            }
        }

        ngx_destroy_pool(temp_pool);
    }

    if (frame.opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_OPCODE) {
        ngx_http_push_stream_send_response_finalize(r);
    }

    /* aio does not call this handler */

    if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && rev->active) {

        if (ngx_del_event(rev, NGX_READ_EVENT, 0) != NGX_OK) {
            ngx_http_finalize_request(r, NGX_OK);
        }
    }
    return;

closed:
    if (temp_pool != NULL) {
        ngx_destroy_pool(temp_pool);
    }

    if (err) {
        rev->error = 1;
    }

    ngx_log_error(NGX_LOG_INFO, c->log, err, "client closed prematurely connection");

    ngx_http_finalize_request(r, NGX_OK);
}
void
ngx_http_push_stream_websocket_reading(ngx_http_request_t *r)
{
    ngx_http_push_stream_main_conf_t  *mcf = ngx_http_get_module_main_conf(r, ngx_http_push_stream_module);
    ngx_http_push_stream_loc_conf_t   *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
    ngx_http_push_stream_module_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_push_stream_module);
    ngx_int_t                          rc = NGX_OK;
    ngx_event_t                       *rev;
    ngx_connection_t                  *c;
    uint64_t                           i;
    ngx_buf_t                          buf;
    ngx_queue_t                       *q;

    ngx_http_push_stream_set_buffer(&buf, ctx->frame->header, ctx->frame->last, 8);

    c = r->connection;
    rev = c->read;

    for (;;) {
        if (c->error || c->timedout || c->close || c->destroyed || rev->closed || rev->eof) {
            goto finalize;
        }

        switch (ctx->frame->step) {
            case NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_START_STEP:
                //reading frame header
                if ((rc = ngx_http_push_stream_recv(c, rev, &buf, 2)) != NGX_OK) {
                    goto exit;
                }

                ctx->frame->fin  = (ctx->frame->header[0] >> 7) & 1;
                ctx->frame->rsv1 = (ctx->frame->header[0] >> 6) & 1;
                ctx->frame->rsv2 = (ctx->frame->header[0] >> 5) & 1;
                ctx->frame->rsv3 = (ctx->frame->header[0] >> 4) & 1;
                ctx->frame->opcode = ctx->frame->header[0] & 0xf;

                ctx->frame->mask = (ctx->frame->header[1] >> 7) & 1;
                ctx->frame->payload_len = ctx->frame->header[1] & 0x7f;

                ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_REAL_SIZE_STEP;

                break;

            case NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_REAL_SIZE_STEP:

                if (ctx->frame->payload_len == 126) {
                    if ((rc = ngx_http_push_stream_recv(c, rev, &buf, 2)) != NGX_OK) {
                        goto exit;
                    }
                    uint16_t len;
                    ngx_memcpy(&len, ctx->frame->header, 2);
                    ctx->frame->payload_len = ntohs(len);
                } else if (ctx->frame->payload_len == 127) {
                    if ((rc = ngx_http_push_stream_recv(c, rev, &buf, 8)) != NGX_OK) {
                        goto exit;
                    }
                    uint64_t len;
                    ngx_memcpy(&len, ctx->frame->header, 8);
                    ctx->frame->payload_len = ngx_http_push_stream_ntohll(len);
                }

                ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_MASK_KEY_STEP;

                break;

            case NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_MASK_KEY_STEP:

                if (ctx->frame->mask) {
                    if ((rc = ngx_http_push_stream_recv(c, rev, &buf, 4)) != NGX_OK) {
                        goto exit;
                    }

                    ngx_memcpy(ctx->frame->mask_key, buf.start, 4);
                }

                ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_PAYLOAD_STEP;

                break;

            case NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_GET_PAYLOAD_STEP:
                if (
                    (ctx->frame->opcode != NGX_HTTP_PUSH_STREAM_WEBSOCKET_TEXT_OPCODE) &&
                    (ctx->frame->opcode != NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_OPCODE) &&
                    (ctx->frame->opcode != NGX_HTTP_PUSH_STREAM_WEBSOCKET_PING_OPCODE) &&
                    (ctx->frame->opcode != NGX_HTTP_PUSH_STREAM_WEBSOCKET_PONG_OPCODE)
                   ) {
                    rc = ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_LAST_FRAME_BYTE, sizeof(NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_LAST_FRAME_BYTE), 1);
                    goto finalize;
                }

                if (ctx->frame->payload_len > 0) {
                    //create a temporary pool to allocate temporary elements
                    if (ctx->temp_pool == NULL) {
                        if ((ctx->temp_pool = ngx_create_pool(4096, r->connection->log)) == NULL) {
                            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for temporary pool");
                            goto finalize;
                        }

                        if ((ctx->frame->payload = ngx_pcalloc(ctx->temp_pool, ctx->frame->payload_len)) == NULL) {
                            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for payload");
                            goto finalize;
                        }

                        ctx->frame->last = ctx->frame->payload;
                    }

                    ngx_http_push_stream_set_buffer(&buf, ctx->frame->payload, ctx->frame->last, ctx->frame->payload_len);

                    if ((rc = ngx_http_push_stream_recv(c, rev, &buf, ctx->frame->payload_len)) != NGX_OK) {
                        goto exit;
                    }

                    if (ctx->frame->mask) {
                        for (i = 0; i < ctx->frame->payload_len; i++) {
                            ctx->frame->payload[i] = ctx->frame->payload[i] ^ ctx->frame->mask_key[i % 4];
                        }
                    }

                    if (!ngx_http_push_stream_is_utf8(ctx->frame->payload, ctx->frame->payload_len)) {
                        goto finalize;
                    }

                    if (cf->websocket_allow_publish && (ctx->frame->opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_TEXT_OPCODE)) {
                        for (q = ngx_queue_head(&ctx->subscriber->subscriptions); q != ngx_queue_sentinel(&ctx->subscriber->subscriptions); q = ngx_queue_next(q)) {
                            ngx_http_push_stream_subscription_t *subscription = ngx_queue_data(q, ngx_http_push_stream_subscription_t, queue);
                            if (subscription->channel->for_events) {
                                // skip events channel on publish by websocket connections
                                continue;
                            }

                            if (ngx_http_push_stream_add_msg_to_channel(mcf, r->connection->log, subscription->channel, ctx->frame->payload, ctx->frame->payload_len, NULL, NULL, cf->store_messages, ctx->temp_pool) != NGX_OK) {
                                goto finalize;
                            }
                        }
                    }

                    if (ctx->temp_pool != NULL) {
                        ngx_destroy_pool(ctx->temp_pool);
                        ctx->temp_pool = NULL;
                    }
                }

                ctx->frame->step = NGX_HTTP_PUSH_STREAM_WEBSOCKET_READ_START_STEP;
                ctx->frame->last = NULL;

                if (ctx->frame->opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_PING_OPCODE) {
                    ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_WEBSOCKET_PONG_LAST_FRAME_BYTE, sizeof(NGX_HTTP_PUSH_STREAM_WEBSOCKET_PONG_LAST_FRAME_BYTE), 1);
                }

                if (ctx->frame->opcode == NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_OPCODE) {
                    rc = ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_LAST_FRAME_BYTE, sizeof(NGX_HTTP_PUSH_STREAM_WEBSOCKET_CLOSE_LAST_FRAME_BYTE), 1);
                    goto finalize;
                }
                return;

                break;

            default:
                ngx_log_debug(NGX_LOG_DEBUG, c->log, 0, "push stream module: unknown websocket step (%d)", ctx->frame->step);
                goto finalize;
                break;
        }

        ngx_http_push_stream_set_buffer(&buf, ctx->frame->header, NULL, 8);
    }

exit:
    if (rc == NGX_AGAIN) {
        ctx->frame->last = buf.last;
        if (!c->read->ready) {
            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno, "push stream module: failed to restore read events");
                goto finalize;
            }
        }
    }

    if (rc == NGX_ERROR) {
        rev->eof = 1;
        c->error = 1;
        ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno, "push stream module: client closed prematurely connection");
        goto finalize;
    }

    return;

finalize:
    ngx_http_push_stream_run_cleanup_pool_handler(r->pool, (ngx_pool_cleanup_pt) ngx_http_push_stream_cleanup_request_context);
    ngx_http_finalize_request(r, c->error ? NGX_HTTP_CLIENT_CLOSED_REQUEST : NGX_OK);
}
static ngx_int_t
ngx_http_push_stream_subscriber_polling_handler(ngx_http_request_t *r, ngx_http_push_stream_requested_channel_t *requested_channels, time_t if_modified_since, ngx_int_t tag, ngx_str_t *last_event_id, ngx_flag_t longpolling, ngx_pool_t *temp_pool)
{
    ngx_http_push_stream_main_conf_t               *mcf = ngx_http_get_module_main_conf(r, ngx_http_push_stream_module);
    ngx_http_push_stream_loc_conf_t                *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
    ngx_slab_pool_t                                *shpool = mcf->shpool;
    ngx_http_push_stream_module_ctx_t              *ctx = ngx_http_get_module_ctx(r, ngx_http_push_stream_module);
    ngx_http_push_stream_requested_channel_t       *requested_channel;
    ngx_queue_t                                    *q;
    ngx_http_push_stream_subscriber_t              *worker_subscriber;
    ngx_http_push_stream_subscription_t            *subscription;
    time_t                                          greater_message_time;
    ngx_int_t                                       greater_message_tag;
    ngx_flag_t                                      has_message_to_send = 0;
    ngx_str_t                                       callback_function_name;

    if (ngx_http_arg(r, NGX_HTTP_PUSH_STREAM_CALLBACK.data, NGX_HTTP_PUSH_STREAM_CALLBACK.len, &callback_function_name) == NGX_OK) {
        ngx_http_push_stream_unescape_uri(&callback_function_name);
        if ((ctx->callback = ngx_pcalloc(r->pool, sizeof(ngx_str_t))) == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for callback function name");
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
        ctx->callback->data = callback_function_name.data;
        ctx->callback->len = callback_function_name.len;
    }

    greater_message_tag = tag;
    greater_message_time = (if_modified_since < 0) ? 0 : if_modified_since;

    // check if has any message to send
    for (q = ngx_queue_head(&requested_channels->queue); q != ngx_queue_sentinel(&requested_channels->queue); q = ngx_queue_next(q)) {
        requested_channel = ngx_queue_data(q, ngx_http_push_stream_requested_channel_t, queue);

        if (ngx_http_push_stream_has_old_messages_to_send(requested_channel->channel, requested_channel->backtrack_messages, if_modified_since, tag, greater_message_time, greater_message_tag, last_event_id)) {
            has_message_to_send = 1;
            if (requested_channel->channel->last_message_time > greater_message_time) {
                greater_message_time = requested_channel->channel->last_message_time;
                greater_message_tag = requested_channel->channel->last_message_tag;
            } else {
                if ((requested_channel->channel->last_message_time == greater_message_time) && (requested_channel->channel->last_message_tag > greater_message_tag) ) {
                    greater_message_tag = requested_channel->channel->last_message_tag;
                }
            }
        }
    }


    if (longpolling && !has_message_to_send) {
        // long polling mode without messages
        if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
        worker_subscriber->longpolling = 1;

        if (ngx_http_push_stream_registry_subscriber(r, worker_subscriber) == NGX_ERROR) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        // adding subscriber to channel(s)
        for (q = ngx_queue_head(&requested_channels->queue); q != ngx_queue_sentinel(&requested_channels->queue); q = ngx_queue_next(q)) {
            requested_channel = ngx_queue_data(q, ngx_http_push_stream_requested_channel_t, queue);

            if ((subscription = ngx_http_push_stream_create_channel_subscription(r, requested_channel->channel, worker_subscriber)) == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            ngx_http_push_stream_assing_subscription_to_channel(shpool, requested_channel->channel, subscription, &worker_subscriber->subscriptions, r->connection->log);
        }

        return NGX_DONE;
    }

    // polling or long polling with messages to send

    ngx_http_push_stream_add_polling_headers(r, greater_message_time, greater_message_tag, temp_pool);

    if (!has_message_to_send) {
        // polling subscriber requests get a 304 with their entity tags preserved if don't have new messages.
        return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_MODIFIED, NULL);
    }

    // polling with messages or long polling without messages to send
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = -1;

    ngx_http_send_header(r);

    // sending response content header
    if (ngx_http_push_stream_send_response_content_header(r, cf) == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: could not send content header to subscriber");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (ctx->callback != NULL) {
        ngx_http_push_stream_send_response_text(r, ctx->callback->data, ctx->callback->len, 0);
        ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_CALLBACK_INIT_CHUNK.data, NGX_HTTP_PUSH_STREAM_CALLBACK_INIT_CHUNK.len, 0);
    }

    for (q = ngx_queue_head(&requested_channels->queue); q != ngx_queue_sentinel(&requested_channels->queue); q = ngx_queue_next(q)) {
        requested_channel = ngx_queue_data(q, ngx_http_push_stream_requested_channel_t, queue);
        ngx_http_push_stream_send_old_messages(r, requested_channel->channel, requested_channel->backtrack_messages, if_modified_since, tag, greater_message_time, greater_message_tag, last_event_id);
    }

    if (ctx->callback != NULL) {
        ngx_http_push_stream_send_response_text(r, NGX_HTTP_PUSH_STREAM_CALLBACK_END_CHUNK.data, NGX_HTTP_PUSH_STREAM_CALLBACK_END_CHUNK.len, 0);
    }

    if (cf->footer_template.len > 0) {
        ngx_http_push_stream_send_response_text(r, cf->footer_template.data, cf->footer_template.len, 0);
    }

    ngx_http_send_special(r, NGX_HTTP_LAST | NGX_HTTP_FLUSH);

    return NGX_OK;
}
static ngx_int_t
ngx_http_push_stream_send_response_channels_info(ngx_http_request_t *r, ngx_queue_t *queue_channel_info) {
    ngx_int_t                                 rc, content_len = 0;
    ngx_chain_t                              *chain, *first = NULL, *last = NULL;
    ngx_str_t                                *currenttime, *hostname, *text, *header_response;
    ngx_queue_t                              *q;
    ngx_http_push_stream_main_conf_t         *mcf = ngx_http_get_module_main_conf(r, ngx_http_push_stream_module);
    ngx_http_push_stream_shm_data_t          *data = mcf->shm_data;
    ngx_http_push_stream_content_subtype_t   *subtype = ngx_http_push_stream_match_channel_info_format_and_content_type(r, 1);

    const ngx_str_t *format;
    const ngx_str_t *head = subtype->format_group_head;
    const ngx_str_t *tail = subtype->format_group_tail;

    // format content body
    for (q = ngx_queue_head(queue_channel_info); q != ngx_queue_sentinel(queue_channel_info); q = ngx_queue_next(q)) {
        ngx_http_push_stream_channel_info_t *channel_info = ngx_queue_data(q, ngx_http_push_stream_channel_info_t, queue);
        if ((chain = ngx_http_push_stream_get_buf(r)) == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for response channels info");
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        format = (q != ngx_queue_last(queue_channel_info)) ? subtype->format_group_item : subtype->format_group_last_item;
        if ((text = ngx_http_push_stream_channel_info_formatted(r->pool, format, &channel_info->id, channel_info->published_messages, channel_info->stored_messages, channel_info->subscribers)) == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory to format channel info");
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        chain->buf->last_buf = 0;
        chain->buf->memory = 1;
        chain->buf->temporary = 0;
        chain->buf->pos = text->data;
        chain->buf->last = text->data + text->len;
        chain->buf->start = chain->buf->pos;
        chain->buf->end = chain->buf->last;

        content_len += text->len;

        if (first == NULL) {
            first = chain;
        }

        if (last != NULL) {
            last->next = chain;
        }

        last = chain;
    }

    // get formatted current time
    currenttime = ngx_http_push_stream_get_formatted_current_time(r->pool);

    // get formatted hostname
    hostname = ngx_http_push_stream_get_formatted_hostname(r->pool);

    // format content header
    if ((header_response = ngx_http_push_stream_create_str(r->pool, head->len + hostname->len + currenttime->len + NGX_INT_T_LEN)) == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for response channels info");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ngx_sprintf(header_response->data, (char *) head->data, hostname->data, currenttime->data, data->channels, data->wildcard_channels, ngx_time() - data->startup);
    header_response->len = ngx_strlen(header_response->data);

    content_len += header_response->len + tail->len;

    r->headers_out.content_type_len = subtype->content_type->len;
    r->headers_out.content_type     = *subtype->content_type;
    r->headers_out.content_length_n = content_len;
    r->headers_out.status = NGX_HTTP_OK;

    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    // send content header
    ngx_http_push_stream_send_response_text(r, header_response->data, header_response->len,0);
    // send content body
    if (first != NULL) {
        ngx_http_push_stream_output_filter(r, first);
    }
    // send content footer
    return ngx_http_push_stream_send_response_text(r, tail->data, tail->len, 1);
}