Commit f0f2b472 authored by Wandenberg Peixoto's avatar Wandenberg Peixoto

adding polling and long-polling support

parent 78409d82
* Adding Polling support
* Adding Long Polling support
h2. Version 0.2.7
* Adding uptime information for server and workers on statistics
......
......@@ -174,7 +174,7 @@ h3(#directives). Directives
(head). | directive | default value | values | context | location |
|push_stream_channels_statistics|-|-|location|-|
|push_stream_publisher|-|-|location|-|
|push_stream_subscriber|-|-|location|-|
|push_stream_subscriber|streaming|streaming, polling, long-polling|location|-|
|push_stream_max_reserved_memory|16 * ngx_pagesize|size greater than 16 * ngx_pagesize|http|main nginx configuration|
|push_stream_memory_cleanup_timeout|30 seconds|time constant|http|main nginx configuration|
|push_stream_channel_deleted_message_text|"Channel deleted"|any string|http|main nginx configuration|
......@@ -247,10 +247,45 @@ POST, publish a message to the channel
h4(#push_stream_subscriber). push_stream_subscriber
default: streaming
context: location
values: streaming, polling, long-polling
Defines a location as a subscriber. This location represents a subscriber's interface to a channel's message queue.
This location only supports GET http method to receive published messages in a stream.
This location only supports GET http method to receive published messages.
And has three possible values to set push mode: streaming, polling, long-polling. The default values is streaming.
The polling and long-polling modes could be set by the request header *X-Nginx-PushStream-Mode* overriding push_stream_subscriber directive.
<pre>
<code>
location /sub/(.*) {
push_stream_subscriber;
# positional channel path
set $push_stream_channels_path $1;
}
curl localhost/sub/ch1 -H 'X-Nginx-PushStream-Mode:polling' #polling request on a streaming location
curl localhost/sub/ch1 -H 'X-Nginx-PushStream-Mode:long-polling' #long-polling request on a streaming location
location /sub/(.*) {
push_stream_subscriber polling;
# positional channel path
set $push_stream_channels_path $1;
}
curl localhost/sub/ch1 #polling request
curl localhost/sub/ch1 -H 'X-Nginx-PushStream-Mode:long-polling' #long-polling request on a polling location
location /sub/(.*) {
push_stream_subscriber long-polling;
# positional channel path
set $push_stream_channels_path $1;
}
curl localhost/sub/ch1 #long-polling request
curl localhost/sub/ch1 -H 'X-Nginx-PushStream-Mode:polling' #polling request on a logn-polling location
</code>
</pre>
h3(#functionality). Functionality
......
......@@ -73,6 +73,7 @@ typedef struct {
ngx_uint_t keepalive;
ngx_uint_t publisher_admin;
ngx_flag_t subscriber_eventsource;
ngx_uint_t subscriber_mode;
} ngx_http_push_stream_loc_conf_t;
// shared memory segment name
......@@ -86,6 +87,7 @@ typedef struct {
ngx_flag_t deleted;
ngx_int_t id;
ngx_str_t *raw;
ngx_int_t tag;
ngx_str_t *event_id;
ngx_str_t *event_id_message;
ngx_str_t *formatted_messages;
......@@ -97,6 +99,7 @@ typedef struct ngx_http_push_stream_subscriber_cleanup_s ngx_http_push_stream_su
typedef struct {
ngx_queue_t queue; // this MUST be first
ngx_http_request_t *request;
ngx_flag_t longpolling;
} ngx_http_push_stream_subscriber_t;
typedef struct {
......@@ -111,6 +114,8 @@ typedef struct {
ngx_rbtree_node_t node; // this MUST be first
ngx_str_t id;
ngx_uint_t last_message_id;
time_t last_message_time;
ngx_int_t last_message_tag;
ngx_uint_t stored_messages;
ngx_uint_t subscribers;
ngx_http_push_stream_pid_queue_t workers_with_subscribers;
......@@ -219,8 +224,22 @@ static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_EVENT_ID = ngx_string("Event
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_LAST_EVENT_ID = ngx_string("Last-Event-Id");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_ALLOW = ngx_string("Allow");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_EXPLAIN = ngx_string("X-Nginx-PushStream-Explain");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_MODE = ngx_string("X-Nginx-PushStream-Mode");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_TRANSFER_ENCODING = ngx_string("Transfer-Encoding");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_CHUNCKED = ngx_string("chunked");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_ETAG = ngx_string("Etag");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_IF_NONE_MATCH = ngx_string("If-None-Match");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_VARY = ngx_string("Vary");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_MODE_STREAMING = ngx_string("streaming");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_MODE_POLLING = ngx_string("polling");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING = ngx_string("long-polling");
#define NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_STREAMING 0
#define NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_POLLING 1
#define NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_LONGPOLLING 2
// other stuff
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_DELETE_METHODS = ngx_string("GET, POST, DELETE");
......
......@@ -37,7 +37,4 @@ ngx_http_push_stream_requested_channel_t * ngx_http_push_stream_parse_channels_i
static void ngx_http_push_stream_subscriber_cleanup(ngx_http_push_stream_subscriber_cleanup_t *data);
static ngx_int_t ngx_http_push_stream_subscriber_assign_channel(ngx_slab_pool_t *shpool, ngx_http_push_stream_loc_conf_t *cf, ngx_http_request_t *r, ngx_http_push_stream_requested_channel_t *requested_channel, time_t if_modified_since, ngx_str_t *last_event_id, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_pool_t *temp_pool);
#endif /* NGX_HTTP_PUSH_STREAM_MODULE_SUBSCRIBER_H_ */
......@@ -215,6 +215,7 @@ ngx_http_push_stream_msg_t *ngx_http_push_stream_ping_msg = NULL;
// general request handling
ngx_http_push_stream_msg_t *ngx_http_push_stream_convert_buffer_to_msg_on_shared_locked(ngx_buf_t *buf, ngx_http_push_stream_channel_t *channel, ngx_int_t id, ngx_str_t *event_id, ngx_pool_t *temp_pool);
ngx_http_push_stream_msg_t *ngx_http_push_stream_convert_char_to_msg_on_shared_locked(u_char *data, size_t len, ngx_http_push_stream_channel_t *channel, ngx_int_t id, ngx_str_t *event_id, ngx_pool_t *temp_pool);
static void ngx_http_push_stream_add_polling_headers(ngx_http_request_t *r, time_t last_modified_time, ngx_int_t tag, ngx_pool_t *temp_pool);
static ngx_table_elt_t * ngx_http_push_stream_add_response_header(ngx_http_request_t *r, const ngx_str_t *header_name, const ngx_str_t *header_value);
static ngx_str_t * ngx_http_push_stream_get_header(ngx_http_request_t *r, const ngx_str_t *header_name);
static ngx_int_t ngx_http_push_stream_send_only_header_response(ngx_http_request_t *r, ngx_int_t status, const ngx_str_t *explain_error_message);
......
......@@ -449,7 +449,20 @@ ngx_http_push_stream_respond_to_subscribers(ngx_http_push_stream_channel_t *chan
// now let's respond to some requests!
while ((cur = (ngx_http_push_stream_subscriber_t *) ngx_queue_next(&cur->queue)) != sentinel) {
if (cur->longpolling) {
ngx_http_push_stream_subscriber_t *prev = (ngx_http_push_stream_subscriber_t *) ngx_queue_prev(&cur->queue);
ngx_http_push_stream_add_polling_headers(cur->request, msg->time, msg->tag, cur->request->pool);
ngx_http_send_header(cur->request);
ngx_http_push_stream_send_response_content_header(cur->request, ngx_http_get_module_loc_conf(cur->request, ngx_http_push_stream_module));
ngx_http_push_stream_send_response_message(cur->request, channel, msg);
ngx_http_push_stream_send_response_finalize(cur->request);
cur = prev;
} else {
ngx_http_push_stream_send_response_message(cur->request, channel, msg);
}
}
}
......
......@@ -207,7 +207,9 @@ ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r)
// put messages on the queue
if (cf->store_messages) {
// tag message with time stamp and a sequence tag
msg->time = ngx_time();
msg->tag = (msg->time == channel->last_message_time) ? (channel->last_message_tag + 1) : 0;
// set message expiration time
msg->expires = (cf->buffer_timeout == NGX_CONF_UNSET ? 0 : (ngx_time() + cf->buffer_timeout));
ngx_queue_insert_tail(&channel->message_queue.queue, &msg->queue);
......@@ -215,6 +217,9 @@ ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r)
// now see if the queue is too big
ngx_http_push_stream_ensure_qtd_of_messages_locked(channel, cf->max_messages, 0);
channel->last_message_time = msg->time;
channel->last_message_tag = msg->tag;
}
ngx_shmtx_unlock(&shpool->mutex);
......
......@@ -39,10 +39,10 @@ static ngx_command_t ngx_http_push_stream_commands[] = {
0,
NULL },
{ ngx_string("push_stream_subscriber"),
NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
ngx_http_push_stream_subscriber,
NGX_HTTP_LOC_CONF_OFFSET,
0,
offsetof(ngx_http_push_stream_loc_conf_t, subscriber_mode),
NULL },
{ ngx_string("push_stream_max_reserved_memory"),
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
......@@ -404,6 +404,7 @@ ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf)
lcf->keepalive = NGX_CONF_UNSET_UINT;
lcf->publisher_admin = NGX_CONF_UNSET_UINT;
lcf->subscriber_eventsource = NGX_CONF_UNSET_UINT;
lcf->subscriber_mode = NGX_CONF_UNSET_UINT;
return lcf;
}
......@@ -650,6 +651,26 @@ ngx_http_push_stream_publisher(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
static char *
ngx_http_push_stream_subscriber(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_int_t *field = (ngx_int_t *) ((char *) conf + cmd->offset);
if (*field != NGX_CONF_UNSET) {
return "is duplicate";
}
*field = NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_STREAMING; //default
if(cf->args->nelts > 1) {
ngx_str_t value = (((ngx_str_t *) cf->args->elts)[1]);
if ((value.len == NGX_HTTP_PUSH_STREAM_MODE_STREAMING.len) && (ngx_strncasecmp(value.data, NGX_HTTP_PUSH_STREAM_MODE_STREAMING.data, NGX_HTTP_PUSH_STREAM_MODE_STREAMING.len) == 0)) {
*field = NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_STREAMING;
} else if ((value.len == NGX_HTTP_PUSH_STREAM_MODE_POLLING.len) && (ngx_strncasecmp(value.data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.len) == 0)) {
*field = NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_POLLING;
} else if ((value.len == NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.len) && (ngx_strncasecmp(value.data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.len) == 0)) {
*field = NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_LONGPOLLING;
} else {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "invalid push_stream_subscriber mode value: %V, accepted values (%s, %s, %s)", &value, NGX_HTTP_PUSH_STREAM_MODE_STREAMING.data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.data);
return NGX_CONF_ERROR;
}
}
char *rc = ngx_http_push_stream_setup_handler(cf, conf, &ngx_http_push_stream_subscriber_handler);
if (rc == NGX_CONF_OK) {
......
......@@ -25,12 +25,15 @@
#include <ngx_http_push_stream_module_subscriber.h>
static ngx_int_t ngx_http_push_stream_subscriber_assign_channel(ngx_slab_pool_t *shpool, ngx_http_push_stream_loc_conf_t *cf, ngx_http_request_t *r, ngx_http_push_stream_requested_channel_t *requested_channel, time_t if_modified_since, ngx_str_t *last_event_id, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_pool_t *temp_pool);
static ngx_http_push_stream_worker_subscriber_t *ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(ngx_http_request_t *r);
static void ngx_http_push_stream_registry_subscriber_locked(ngx_http_push_stream_worker_subscriber_t *worker_subscriber);
static void ngx_http_push_stream_send_old_messages(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel, ngx_uint_t backtrack, time_t if_modified_since, ngx_str_t *last_event_id);
static ngx_flag_t ngx_http_push_stream_has_old_messages_to_send(ngx_http_push_stream_channel_t *channel, ngx_uint_t backtrack, time_t if_modified_since, ngx_int_t tag, time_t greater_message_time, ngx_int_t greater_message_tag, ngx_str_t *last_event_id);
static void ngx_http_push_stream_send_old_messages(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel, ngx_uint_t backtrack, time_t if_modified_since, ngx_int_t tag, time_t greater_message_time, ngx_int_t greater_message_tag, ngx_str_t *last_event_id);
static ngx_http_push_stream_pid_queue_t *ngx_http_push_stream_create_worker_subscriber_channel_sentinel_locked(ngx_slab_pool_t *shpool, ngx_str_t *channel_id, ngx_log_t *log);
static ngx_http_push_stream_subscription_t *ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel);
static ngx_int_t ngx_http_push_stream_assing_subscription_to_channel_locked(ngx_str_t *channel_id, ngx_http_push_stream_subscription_t *subscription, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_http_push_stream_pid_queue_t *worker_subscribers_sentinel, ngx_log_t *log);
static ngx_http_push_stream_subscription_t *ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel, ngx_flag_t longpolling);
static ngx_int_t ngx_http_push_stream_assing_subscription_to_channel_locked(ngx_slab_pool_t *shpool, ngx_str_t *channel_id, ngx_http_push_stream_subscription_t *subscription, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_log_t *log);
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);
static ngx_int_t
ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
......@@ -46,6 +49,8 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
ngx_http_push_stream_channel_t *channel;
time_t if_modified_since;
ngx_str_t *last_event_id;
ngx_str_t *push_mode;
ngx_flag_t polling, longpolling;
// only accept GET method
if (!(r->method & NGX_HTTP_GET)) {
......@@ -122,9 +127,21 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
}
}
if_modified_since = (r->headers_in.if_modified_since != NULL) ? ngx_http_parse_time(r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len) : 0;
// get control headers
if_modified_since = (r->headers_in.if_modified_since != NULL) ? ngx_http_parse_time(r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len) : -1;
last_event_id = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_LAST_EVENT_ID);
push_mode = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_MODE);
polling = ((cf->subscriber_mode == NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_POLLING) || ((push_mode != NULL) && (push_mode->len == NGX_HTTP_PUSH_STREAM_MODE_POLLING.len) && (ngx_strncasecmp(push_mode->data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.data, NGX_HTTP_PUSH_STREAM_MODE_POLLING.len) == 0)));
longpolling = ((cf->subscriber_mode == NGX_HTTP_PUSH_STREAM_SUBSCRIBER_MODE_LONGPOLLING) || ((push_mode != NULL) && (push_mode->len == NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.len) && (ngx_strncasecmp(push_mode->data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.data, NGX_HTTP_PUSH_STREAM_MODE_LONGPOLLING.len) == 0)));
if (polling || longpolling) {
ngx_int_t result = ngx_http_push_stream_subscriber_polling_handler(r, channels_ids, if_modified_since, last_event_id, longpolling, temp_pool);
ngx_destroy_pool(temp_pool);
return result;
}
// stream access
if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) {
ngx_destroy_pool(temp_pool);
return NGX_HTTP_INTERNAL_SERVER_ERROR;
......@@ -139,7 +156,6 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_shmtx_lock(&shpool->mutex);
ngx_http_push_stream_registry_subscriber_locked(worker_subscriber);
ngx_shmtx_unlock(&shpool->mutex);
......@@ -162,46 +178,148 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
}
static ngx_int_t
ngx_http_push_stream_subscriber_assign_channel(ngx_slab_pool_t *shpool, ngx_http_push_stream_loc_conf_t *cf, ngx_http_request_t *r, ngx_http_push_stream_requested_channel_t *requested_channel, time_t if_modified_since, ngx_str_t *last_event_id, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_pool_t *temp_pool)
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_pid_queue_t *cur, *worker_subscribers_sentinel = NULL;
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_requested_channel_t *cur;
ngx_http_push_stream_worker_subscriber_t *worker_subscriber;
ngx_http_push_stream_channel_t *channel;
ngx_http_push_stream_subscription_t *subscription;
ngx_int_t result;
ngx_str_t *etag = ngx_http_push_stream_get_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_IF_NONE_MATCH);
ngx_int_t tag = ((etag != NULL) && ((tag = ngx_atoi(etag->data, etag->len)) != NGX_ERROR)) ? ngx_abs(tag) : -1;
time_t greater_message_time;
ngx_int_t greater_message_tag = tag;
ngx_flag_t has_message_to_send = 0;
if ((channel = ngx_http_push_stream_find_channel(requested_channel->id, r->connection->log)) == NULL) {
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_locked(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", requested_channel->id->data);
return NGX_ERROR;
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;
}
cur = &channel->workers_with_subscribers;
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;
}
}
}
}
while ((cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&cur->queue)) != &channel->workers_with_subscribers) {
if (cur->pid == ngx_pid) {
worker_subscribers_sentinel = cur;
break;
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;
}
ngx_http_push_stream_registry_subscriber_locked(worker_subscriber);
// 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_locked(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_ERROR;
}
if (worker_subscribers_sentinel == NULL) { // found nothing
ngx_shmtx_lock(&shpool->mutex);
worker_subscribers_sentinel = ngx_http_push_stream_create_worker_subscriber_channel_sentinel_locked(shpool, requested_channel->id, r->connection->log);
if ((subscription = ngx_http_push_stream_create_channel_subscription(r, channel, longpolling)) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
if (worker_subscribers_sentinel == NULL) {
return NGX_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.content_type = cf->content_type;
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;
}
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_locked(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 (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_subscriber_assign_channel(ngx_slab_pool_t *shpool, ngx_http_push_stream_loc_conf_t *cf, ngx_http_request_t *r, ngx_http_push_stream_requested_channel_t *requested_channel, time_t if_modified_since, ngx_str_t *last_event_id, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_pool_t *temp_pool)
{
ngx_http_push_stream_channel_t *channel;
ngx_http_push_stream_subscription_t *subscription;
ngx_int_t result;
if ((channel = ngx_http_push_stream_find_channel(requested_channel->id, r->connection->log)) == 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", requested_channel->id->data);
return NGX_ERROR;
}
if ((subscription = ngx_http_push_stream_create_channel_subscription(r, channel)) == NULL) {
if ((subscription = ngx_http_push_stream_create_channel_subscription(r, channel, 0)) == NULL) {
return NGX_ERROR;
}
// send old messages to new subscriber
ngx_http_push_stream_send_old_messages(r, channel, requested_channel->backtrack_messages, if_modified_since, last_event_id);
ngx_http_push_stream_send_old_messages(r, channel, requested_channel->backtrack_messages, if_modified_since, 0, 0, -1, last_event_id);
ngx_shmtx_lock(&shpool->mutex);
result = ngx_http_push_stream_assing_subscription_to_channel_locked(requested_channel->id, subscription, subscriptions_sentinel, worker_subscribers_sentinel, r->connection->log);
result = ngx_http_push_stream_assing_subscription_to_channel_locked(shpool, requested_channel->id, subscription, subscriptions_sentinel, r->connection->log);
ngx_shmtx_unlock(&shpool->mutex);
return result;
......@@ -365,27 +483,52 @@ ngx_http_push_stream_registry_subscriber_locked(ngx_http_push_stream_worker_subs
thisworker_data->subscribers++;
}
static void
ngx_http_push_stream_send_old_messages(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel, ngx_uint_t backtrack, time_t if_modified_since, ngx_str_t *last_event_id)
static ngx_flag_t
ngx_http_push_stream_has_old_messages_to_send(ngx_http_push_stream_channel_t *channel, ngx_uint_t backtrack, time_t if_modified_since, ngx_int_t tag, time_t greater_message_time, ngx_int_t greater_message_tag, ngx_str_t *last_event_id)
{
ngx_flag_t old_messages = 0;
ngx_http_push_stream_msg_t *message, *message_sentinel;
if (channel->stored_messages > 0) {
message_sentinel = &channel->message_queue;
message = message_sentinel;
if (last_event_id != NULL) {
if (channel->stored_messages > 0) {
if (backtrack > 0) {
old_messages = 1;
} else if ((last_event_id != NULL) || (if_modified_since >= 0)) {
ngx_flag_t found = 0;
while ((!message->deleted) && ((message = (ngx_http_push_stream_msg_t *) ngx_queue_next(&message->queue)) != message_sentinel)) {
if ((!found) && (message->event_id != NULL) && (ngx_memn2cmp(message->event_id->data, last_event_id->data, message->event_id->len, last_event_id->len) == 0)) {
if ((!found) && (last_event_id != NULL) && (message->event_id != NULL) && (ngx_memn2cmp(message->event_id->data, last_event_id->data, message->event_id->len, last_event_id->len) == 0)) {
found = 1;
continue;
}
if ((!found) && (last_event_id == NULL) && (if_modified_since >= 0) && ((message->time > if_modified_since) || ((message->time == if_modified_since) && (tag >= 0) && (message->tag >= tag)))) {
found = 1;
if ((message->time == if_modified_since) && (message->tag == tag)) {
continue;
}
}
if (found) {
ngx_http_push_stream_send_response_message(r, channel, message);
old_messages = 1;
break;
}
}
} else {
}
}
return old_messages;
}
static void
ngx_http_push_stream_send_old_messages(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel, ngx_uint_t backtrack, time_t if_modified_since, ngx_int_t tag, time_t greater_message_time, ngx_int_t greater_message_tag, ngx_str_t *last_event_id)
{
ngx_http_push_stream_msg_t *message, *message_sentinel;
if (ngx_http_push_stream_has_old_messages_to_send(channel, backtrack, if_modified_since, tag, greater_message_time, greater_message_tag, last_event_id)) {
message_sentinel = &channel->message_queue;
message = message_sentinel;
if (backtrack > 0) {
ngx_uint_t qtd = (backtrack > channel->stored_messages) ? channel->stored_messages : backtrack;
ngx_uint_t start = channel->stored_messages - qtd;
// positioning at first message, and send the others
......@@ -397,12 +540,23 @@ ngx_http_push_stream_send_old_messages(ngx_http_request_t *r, ngx_http_push_stre
start--;
}
}
if ((backtrack == 0) && (if_modified_since != 0)) {
} else if ((last_event_id != NULL) || (if_modified_since >= 0)) {
ngx_flag_t found = 0;
while ((!message->deleted) && ((message = (ngx_http_push_stream_msg_t *) ngx_queue_next(&message->queue)) != message_sentinel)) {
if (message->time > if_modified_since) {
ngx_http_push_stream_send_response_message(r, channel, message);
if ((!found) && (last_event_id != NULL) && (message->event_id != NULL) && (ngx_memn2cmp(message->event_id->data, last_event_id->data, message->event_id->len, last_event_id->len) == 0)) {
found = 1;
continue;
}
if ((!found) && (last_event_id == NULL) && (if_modified_since >= 0) && ((message->time > if_modified_since) || ((message->time == if_modified_since) && (tag >= 0) && (message->tag >= tag)))) {
found = 1;
if ((message->time == if_modified_since) && (message->tag == tag)) {
continue;
}
}
if (found && (((greater_message_time == 0) && (greater_message_tag == -1)) || ((greater_message_time >= message->time) && (greater_message_tag >= message->tag)))) {
ngx_http_push_stream_send_response_message(r, channel, message);
}
}
}
......@@ -437,7 +591,7 @@ ngx_http_push_stream_create_worker_subscriber_channel_sentinel_locked(ngx_slab_p
}
static ngx_http_push_stream_subscription_t *
ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel)
ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel, ngx_flag_t longpolling)
{
ngx_http_push_stream_subscription_t *subscription;
ngx_http_push_stream_subscriber_t *subscriber;
......@@ -453,6 +607,7 @@ ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http
}
subscriber->request = r;
subscriber->longpolling = longpolling;
subscription->channel = channel;
subscription->subscriber = subscriber;
......@@ -461,8 +616,9 @@ ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http
}
static ngx_int_t
ngx_http_push_stream_assing_subscription_to_channel_locked(ngx_str_t *channel_id, ngx_http_push_stream_subscription_t *subscription, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_http_push_stream_pid_queue_t *worker_subscribers_sentinel, ngx_log_t *log)
ngx_http_push_stream_assing_subscription_to_channel_locked(ngx_slab_pool_t *shpool, ngx_str_t *channel_id, ngx_http_push_stream_subscription_t *subscription, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_log_t *log)
{
ngx_http_push_stream_pid_queue_t *cur, *worker_subscribers_sentinel = NULL;
ngx_http_push_stream_channel_t *channel;
// check if channel still exists
......@@ -470,6 +626,22 @@ ngx_http_push_stream_assing_subscription_to_channel_locked(ngx_str_t *channel_id
ngx_log_error(NGX_LOG_ERR, log, 0, "push stream module: something goes very wrong, arrived on ngx_http_push_stream_subscriber_assign_channel without created channel %s", channel_id->data);
return NGX_ERROR;
}
cur = &channel->workers_with_subscribers;
while ((cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&cur->queue)) != &channel->workers_with_subscribers) {
if (cur->pid == ngx_pid) {
worker_subscribers_sentinel = cur;
break;
}
}
if (worker_subscribers_sentinel == NULL) { // found nothing
worker_subscribers_sentinel = ngx_http_push_stream_create_worker_subscriber_channel_sentinel_locked(shpool, channel_id, log);
if (worker_subscribers_sentinel == NULL) {
return NGX_ERROR;
}
}
channel->subscribers++; // do this only when we know everything went okay
ngx_queue_insert_tail(&subscriptions_sentinel->queue, &subscription->queue);
ngx_queue_insert_tail(&worker_subscribers_sentinel->subscriber_sentinel.queue, &subscription->subscriber->queue);
......
......@@ -1113,3 +1113,20 @@ ngx_http_push_stream_apply_template_to_each_line(ngx_str_t *text, const ngx_str_
return result;
}
static void
ngx_http_push_stream_add_polling_headers(ngx_http_request_t *r, time_t last_modified_time, ngx_int_t tag, ngx_pool_t *temp_pool)
{
if (last_modified_time > 0) {
r->headers_out.last_modified_time = last_modified_time;
}
if (tag >= 0) {
ngx_str_t *etag = ngx_http_push_stream_create_str(temp_pool, NGX_INT_T_LEN);
if (etag != NULL) {
ngx_sprintf(etag->data, "%ui", tag);
etag->len = ngx_strlen(etag->data);
r->headers_out.etag = ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ETAG, etag);
}
}
}
......@@ -83,6 +83,8 @@ ngx_http_push_stream_initialize_channel(ngx_http_push_stream_channel_t *channel)
ngx_http_push_stream_shm_data_t *data = (ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data;
channel->last_message_id = 0;
channel->last_message_time = 0;
channel->last_message_tag = 0;
channel->stored_messages = 0;
channel->subscribers = 0;
channel->deleted = 0;
......
......@@ -155,6 +155,7 @@ module BaseTestCase
@channel_deleted_message_text = nil
@ping_message_text = nil
@subscriber_eventsource = 'off'
@subscriber_mode = nil
self.send(:global_configuration) if self.respond_to?(:global_configuration)
end
......@@ -285,7 +286,7 @@ http {
location ~ /sub/(.*)? {
# activate subscriber mode for this location
push_stream_subscriber;
push_stream_subscriber <%= @subscriber_mode unless @subscriber_mode.nil? || @subscriber_mode == "streaming" %>;
# activate event source support for this location
<%= "push_stream_subscriber_eventsource #{@subscriber_eventsource};" unless @subscriber_eventsource.nil? %>
......
......@@ -176,4 +176,49 @@ class TestSetuParameters < Test::Unit::TestCase
ensure
self.stop_server
end
def test_invalid_push_mode
expected_error_message = "invalid push_stream_subscriber mode value: unknown, accepted values (streaming, polling, long-polling)"
@subscriber_mode = "unknown"
self.create_config_file
stderr_msg = self.start_server
assert(stderr_msg.include?(expected_error_message), "Message error not founded: '#{ expected_error_message }' recieved '#{ stderr_msg }'")
end
def test_valid_push_mode
expected_error_message = "invalid push_stream_subscriber mode value"
@subscriber_mode = ""
self.create_config_file
stderr_msg = self.start_server
assert(!stderr_msg.include?(expected_error_message), "Message error founded: '#{ stderr_msg }'")
self.stop_server
@subscriber_mode = "streaming"
self.create_config_file
stderr_msg = self.start_server
assert(!stderr_msg.include?(expected_error_message), "Message error founded: '#{ stderr_msg }'")
self.stop_server
@subscriber_mode = "polling"
self.create_config_file
stderr_msg = self.start_server
assert(!stderr_msg.include?(expected_error_message), "Message error founded: '#{ stderr_msg }'")
self.stop_server
@subscriber_mode = "long-polling"
self.create_config_file
stderr_msg = self.start_server
assert(!stderr_msg.include?(expected_error_message), "Message error founded: '#{ stderr_msg }'")
self.stop_server
end
end
require File.expand_path('base_test_case', File.dirname(__FILE__))
class TestSubscriberLongPolling < Test::Unit::TestCase
include BaseTestCase
def global_configuration
@ping_message_interval = nil
@header_template = nil
@footer_template = nil
@message_template = nil
@subscriber_mode = 'long-polling'
end
def test_disconnect_after_receive_a_message_when_longpolling_is_on
headers = {'accept' => 'application/json'}
channel = 'ch_test_disconnect_after_receive_a_message_when_longpolling_is_on'
body = 'body'
response = ""
EventMachine.run {
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.stream { |chunk|
response += chunk
}
sub_1.callback { |chunk|
assert_equal("#{body}\r\n", response, "Wrong message")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
response = ""
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_2.stream { |chunk|
response += chunk
}
sub_2.callback { |chunk|
assert_equal("#{body} 1\r\n", response, "Wrong message")
EventMachine.stop
}
publish_message_inline(channel, {'accept' => 'text/html'}, body + " 1")
}
publish_message_inline(channel, {'accept' => 'text/html'}, body)
add_test_timeout
}
end
def test_disconnect_after_receive_old_messages_by_backtrack_when_longpolling_is_on
channel = 'ch_test_disconnect_after_receive_old_messages_by_backtrack_when_longpolling_is_on'
response = ''
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 1')
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 2')
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 3')
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 4')
sub = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s + '.b2').get
sub.stream { | chunk |
response += chunk
}
sub.callback { |chunk|
assert_equal("msg 3\r\nmsg 4\r\n", response, "The published message was not received correctly")
response = ''
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => {'If-Modified-Since' => sub.response_header['LAST_MODIFIED'], 'If-None-Match' => sub.response_header['ETAG']}
sub_1.stream { | chunk |
response += chunk
}
sub_1.callback { |chunk|
assert_equal("msg 5\r\n", response, "The published message was not received correctly")
EventMachine.stop
}
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 5')
}
add_test_timeout
}
end
def test_disconnect_after_receive_old_messages_by_last_event_id_when_longpolling_is_on
channel = 'ch_test_disconnect_after_receive_old_messages_by_last_event_id_when_longpolling_is_on'
response = ''
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 1' }, 'msg 1')
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 2' }, 'msg 2')
publish_message_inline(channel, {'accept' => 'text/html' }, 'msg 3')
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 3' }, 'msg 4')
sub = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => {'Last-Event-Id' => 'event 2' }
sub.stream { | chunk |
response += chunk
}
sub.callback { |chunk|
assert_equal("msg 3\r\nmsg 4\r\n", response, "The published message was not received correctly")
EventMachine.stop
}
add_test_timeout
}
end
def test_receive_old_messages_from_different_channels
headers = {'accept' => 'application/json'}
channel_1 = 'ch_test_receive_old_messages_from_different_channels_1'
channel_2 = 'ch_test_receive_old_messages_from_different_channels_2'
body = 'body'
response = ''
EventMachine.run {
publish_message_inline(channel_1, {'accept' => 'text/html'}, body + "_1")
publish_message_inline(channel_2, {'accept' => 'text/html'}, body + "_2")
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_not_equal("", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}_2\r\n#{body}_1\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(200, sub_2.response_header.status, "Wrong status")
assert_not_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_2.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1_1\r\n", sub_2.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1_2\r\n", sub_3.response, "The published message was not received correctly")
EventMachine.stop
}
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel_2, {'accept' => 'text/html'}, body + "1_2")
}
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel_1, {'accept' => 'text/html'}, body + "1_1")
}
add_test_timeout
}
end
def config_test_disconnect_after_receive_a_message_when_has_header_mode_longpolling
@subscriber_mode = nil
end
def test_disconnect_after_receive_a_message_when_has_header_mode_longpolling
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'long-polling'}
channel = 'ch_test_disconnect_after_receive_a_message_when_has_header_mode_longpolling'
body = 'body'
response = ""
EventMachine.run {
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.stream { |chunk|
response += chunk
}
sub_1.callback { |chunk|
assert_equal("#{body}\r\n", response, "Wrong message")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
response = ""
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_2.stream { |chunk|
response += chunk
}
sub_2.callback { |chunk|
assert_equal("#{body} 1\r\n", response, "Wrong message")
EventMachine.stop
}
publish_message_inline(channel, {'accept' => 'text/html'}, body + " 1")
}
publish_message_inline(channel, {'accept' => 'text/html'}, body)
add_test_timeout
}
end
def config_test_disconnect_after_receive_old_messages_by_backtrack_when_has_header_mode_longpolling
@subscriber_mode = nil
end
def test_disconnect_after_receive_old_messages_by_backtrack_when_has_header_mode_longpolling
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'long-polling'}
channel = 'ch_test_disconnect_after_receive_old_messages_by_backtrack_when_has_header_mode_longpolling'
response = ''
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 1')
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 2')
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 3')
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 4')
sub = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s + '.b2').get :head => headers
sub.stream { | chunk |
response += chunk
}
sub.callback { |chunk|
assert_equal("msg 3\r\nmsg 4\r\n", response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub.response_header['LAST_MODIFIED'], 'If-None-Match' => sub.response_header['ETAG']})
response = ''
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers
sub_1.stream { | chunk |
response += chunk
}
sub_1.callback { |chunk|
assert_equal("msg 5\r\n", response, "The published message was not received correctly")
EventMachine.stop
}
publish_message_inline(channel, {'accept' => 'text/html'}, 'msg 5')
}
add_test_timeout
}
end
def config_test_disconnect_after_receive_old_messages_by_last_event_id_when_has_header_mode_longpolling
@subscriber_mode = nil
end
def test_disconnect_after_receive_old_messages_by_last_event_id_when_has_header_mode_longpolling
channel = 'ch_test_disconnect_after_receive_old_messages_by_last_event_id_when_has_header_mode_longpolling'
response = ''
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 1' }, 'msg 1')
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 2' }, 'msg 2')
publish_message_inline(channel, {'accept' => 'text/html' }, 'msg 3')
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 3' }, 'msg 4')
sub = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => {'Last-Event-Id' => 'event 2', 'X-Nginx-PushStream-Mode' => 'long-polling' }
sub.stream { | chunk |
response += chunk
}
sub.callback { |chunk|
assert_equal("msg 3\r\nmsg 4\r\n", response, "The published message was not received correctly")
EventMachine.stop
}
add_test_timeout
}
end
def config_test_receive_old_messages_from_different_channels_when_has_header_mode_longpolling
@subscriber_mode = nil
end
def test_receive_old_messages_from_different_channels_when_has_header_mode_longpolling
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'long-polling'}
channel_1 = 'ch_test_receive_old_messages_from_different_channels_when_has_header_mode_longpolling_1'
channel_2 = 'ch_test_receive_old_messages_from_different_channels_when_has_header_mode_longpolling_2'
body = 'body'
response = ''
EventMachine.run {
publish_message_inline(channel_1, {'accept' => 'text/html'}, body + "_1")
publish_message_inline(channel_2, {'accept' => 'text/html'}, body + "_2")
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_not_equal("", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}_2\r\n#{body}_1\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(200, sub_2.response_header.status, "Wrong status")
assert_not_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_2.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1_1\r\n", sub_2.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1_2\r\n", sub_3.response, "The published message was not received correctly")
EventMachine.stop
}
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel_2, {'accept' => 'text/html'}, body + "1_2")
}
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel_1, {'accept' => 'text/html'}, body + "1_1")
}
add_test_timeout
}
end
end
require File.expand_path('base_test_case', File.dirname(__FILE__))
class TestSubscriberPolling < Test::Unit::TestCase
include BaseTestCase
def global_configuration
@ping_message_interval = nil
@header_template = nil
@footer_template = nil
@message_template = nil
@subscriber_mode = 'polling'
end
def test_receive_a_304_when_has_no_messages
headers = {'accept' => 'application/json'}
channel = 'ch_test_receive_a_304_when_has_no_messages'
body = 'body'
EventMachine.run {
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(304, sub_1.response_header.status, "Wrong status")
assert_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("", sub_1.response_header['ETAG'].to_s, "Wrong header")
EventMachine.stop
}
add_test_timeout
}
end
def test_receive_a_304_when_has_no_messages_keeping_headers
headers = {'accept' => 'application/json'}
channel = 'ch_test_receive_a_304_when_has_no_messages_keeping_headers'
body = 'body'
headers = headers.merge({'If-Modified-Since' => Time.now.utc.strftime("%a, %d %b %Y %T %Z"), 'If-None-Match' => '3'})
EventMachine.run {
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(304, sub_1.response_header.status, "Wrong status")
assert_equal(headers['If-Modified-Since'], sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(headers['If-None-Match'], sub_1.response_header['ETAG'].to_s, "Wrong header")
EventMachine.stop
}
add_test_timeout
}
end
def test_receive_specific_headers_when_has_messages
headers = {'accept' => 'application/json'}
channel = 'ch_test_receive_specific_headers_when_has_messages'
body = 'body'
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html'}, body)
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}\r\n", sub_1.response, "The published message was not received correctly")
EventMachine.stop
}
add_test_timeout
}
end
def test_receive_old_messages_by_if_modified_since_header
headers = {'accept' => 'application/json'}
channel = 'ch_test_getting_messages_by_if_modified_since_header'
body = 'body'
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html'}, body)
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_not_equal("", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(304, sub_2.response_header.status, "Wrong status")
assert_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_1.response_header['ETAG'], sub_2.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel, {'accept' => 'text/html'}, body + "1")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1\r\n", sub_3.response, "The published message was not received correctly")
EventMachine.stop
}
}
}
add_test_timeout
}
end
def test_receive_old_messages_by_backtrack
headers = {'accept' => 'application/json'}
channel = 'ch_test_getting_messages_by_if_modified_since_header'
body = 'body'
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html'}, body)
publish_message_inline(channel, {'accept' => 'text/html'}, body + "1")
publish_message_inline(channel, {'accept' => 'text/html'}, body + "2")
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s + '.b1').get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("2", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}2\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(304, sub_2.response_header.status, "Wrong status")
assert_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_1.response_header['ETAG'], sub_2.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel, {'accept' => 'text/html'}, body + "3")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}3\r\n", sub_3.response, "The published message was not received correctly")
EventMachine.stop
}
}
}
add_test_timeout
}
end
def test_receive_old_messages_by_last_event_id
headers = {'accept' => 'application/json'}
channel = 'ch_test_receive_old_messages_by_last_event_id'
body = 'body'
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 1' }, 'msg 1')
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 2' }, 'msg 2')
publish_message_inline(channel, {'accept' => 'text/html' }, 'msg 3')
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 3' }, 'msg 4')
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => {'Last-Event-Id' => 'event 2' }
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("3", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("msg 3\r\nmsg 4\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(304, sub_2.response_header.status, "Wrong status")
assert_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_1.response_header['ETAG'], sub_2.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel, {'accept' => 'text/html'}, body + "3")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}3\r\n", sub_3.response, "The published message was not received correctly")
EventMachine.stop
}
}
}
add_test_timeout
}
end
def test_receive_old_messages_from_different_channels
headers = {'accept' => 'application/json'}
channel_1 = 'ch_test_receive_old_messages_from_different_channels_1'
channel_2 = 'ch_test_receive_old_messages_from_different_channels_2'
body = 'body'
EventMachine.run {
publish_message_inline(channel_1, {'accept' => 'text/html'}, body + "_1")
publish_message_inline(channel_2, {'accept' => 'text/html'}, body + "_2")
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_not_equal("", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}_2\r\n#{body}_1\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(304, sub_2.response_header.status, "Wrong status")
assert_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_1.response_header['ETAG'], sub_2.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel_1, {'accept' => 'text/html'}, body + "1_1")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1_1\r\n", sub_3.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_3.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_3.response_header['ETAG']})
sub_4 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_4.callback {
assert_equal(304, sub_4.response_header.status, "Wrong status")
assert_equal(sub_3.response_header['LAST_MODIFIED'], sub_4.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_3.response_header['ETAG'], sub_4.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel_2, {'accept' => 'text/html'}, body + "1_2")
headers.merge!({'If-Modified-Since' => sub_4.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_4.response_header['ETAG']})
sub_5 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_5.callback {
assert_equal(200, sub_5.response_header.status, "Wrong status")
assert_not_equal(sub_4.response_header['LAST_MODIFIED'], sub_5.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_5.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1_2\r\n", sub_5.response, "The published message was not received correctly")
EventMachine.stop
}
}
}
}
}
add_test_timeout
}
end
def conf_test_receive_a_304_when_has_no_messages_using_push_mode_header
@subscriber_mode = nil
end
def test_receive_a_304_when_has_no_messages_using_push_mode_header
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'polling'}
channel = 'ch_test_receive_a_304_when_has_no_messages_using_push_mode_header'
body = 'body'
EventMachine.run {
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(304, sub_1.response_header.status, "Wrong status")
assert_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("", sub_1.response_header['ETAG'].to_s, "Wrong header")
EventMachine.stop
}
add_test_timeout
}
end
def conf_test_receive_a_304_when_has_no_messages_keeping_headers_using_push_mode_header
@subscriber_mode = nil
end
def test_receive_a_304_when_has_no_messages_keeping_headers_using_push_mode_header
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'polling'}
channel = 'ch_test_receive_a_304_when_has_no_messages_keeping_headers_using_push_mode_header'
body = 'body'
headers = headers.merge({'If-Modified-Since' => Time.now.utc.strftime("%a, %d %b %Y %T %Z"), 'If-None-Match' => '3'})
EventMachine.run {
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(304, sub_1.response_header.status, "Wrong status")
assert_equal(headers['If-Modified-Since'], sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(headers['If-None-Match'], sub_1.response_header['ETAG'].to_s, "Wrong header")
EventMachine.stop
}
add_test_timeout
}
end
def conf_test_receive_specific_headers_when_has_messages_using_push_mode_header
@subscriber_mode = nil
end
def test_receive_specific_headers_when_has_messages_using_push_mode_header
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'polling'}
channel = 'ch_test_receive_specific_headers_when_has_messages_using_push_mode_header'
body = 'body'
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html'}, body)
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}\r\n", sub_1.response, "The published message was not received correctly")
EventMachine.stop
}
add_test_timeout
}
end
def conf_test_receive_old_messages_by_if_modified_since_header_using_push_mode_header
@subscriber_mode = nil
end
def test_receive_old_messages_by_if_modified_since_header_using_push_mode_header
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'polling'}
channel = 'ch_test_getting_messages_by_if_modified_since_header_using_push_mode_header'
body = 'body'
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html'}, body)
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_not_equal("", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(304, sub_2.response_header.status, "Wrong status")
assert_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_1.response_header['ETAG'], sub_2.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel, {'accept' => 'text/html'}, body + "1")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1\r\n", sub_3.response, "The published message was not received correctly")
EventMachine.stop
}
}
}
add_test_timeout
}
end
def conf_test_receive_old_messages_by_backtrack_using_push_mode_header
@subscriber_mode = nil
end
def test_receive_old_messages_by_backtrack_using_push_mode_header
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'polling'}
channel = 'ch_test_getting_messages_by_if_modified_since_header_using_push_mode_header'
body = 'body'
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html'}, body)
publish_message_inline(channel, {'accept' => 'text/html'}, body + "1")
publish_message_inline(channel, {'accept' => 'text/html'}, body + "2")
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s + '.b1').get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("2", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}2\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(304, sub_2.response_header.status, "Wrong status")
assert_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_1.response_header['ETAG'], sub_2.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel, {'accept' => 'text/html'}, body + "3")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}3\r\n", sub_3.response, "The published message was not received correctly")
EventMachine.stop
}
}
}
add_test_timeout
}
end
def conf_test_receive_old_messages_by_last_event_id_using_push_mode_header
@subscriber_mode = nil
end
def test_receive_old_messages_by_last_event_id_using_push_mode_header
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'polling'}
channel = 'ch_test_receive_old_messages_by_last_event_id_using_push_mode_header'
body = 'body'
EventMachine.run {
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 1' }, 'msg 1')
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 2' }, 'msg 2')
publish_message_inline(channel, {'accept' => 'text/html' }, 'msg 3')
publish_message_inline(channel, {'accept' => 'text/html', 'Event-Id' => 'event 3' }, 'msg 4')
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers.merge({'Last-Event-Id' => 'event 2'})
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("3", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("msg 3\r\nmsg 4\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(304, sub_2.response_header.status, "Wrong status")
assert_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_1.response_header['ETAG'], sub_2.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel, {'accept' => 'text/html'}, body + "3")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}3\r\n", sub_3.response, "The published message was not received correctly")
EventMachine.stop
}
}
}
add_test_timeout
}
end
def conf_test_receive_old_messages_from_different_channels_using_push_mode_header
@subscriber_mode = nil
end
def test_receive_old_messages_from_different_channels_using_push_mode_header
headers = {'accept' => 'application/json', 'X-Nginx-PushStream-Mode' => 'polling'}
channel_1 = 'ch_test_receive_old_messages_from_different_channels_using_push_mode_header_1'
channel_2 = 'ch_test_receive_old_messages_from_different_channels_using_push_mode_header_2'
body = 'body'
EventMachine.run {
publish_message_inline(channel_1, {'accept' => 'text/html'}, body + "_1")
publish_message_inline(channel_2, {'accept' => 'text/html'}, body + "_2")
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_1.callback {
assert_equal(200, sub_1.response_header.status, "Wrong status")
assert_not_equal("", sub_1.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_not_equal("", sub_1.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}_2\r\n#{body}_1\r\n", sub_1.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_1.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_1.response_header['ETAG']})
sub_2 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_2.callback {
assert_equal(304, sub_2.response_header.status, "Wrong status")
assert_equal(sub_1.response_header['LAST_MODIFIED'], sub_2.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_1.response_header['ETAG'], sub_2.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel_1, {'accept' => 'text/html'}, body + "1_1")
headers.merge!({'If-Modified-Since' => sub_2.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_2.response_header['ETAG']})
sub_3 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_3.callback {
assert_equal(200, sub_3.response_header.status, "Wrong status")
assert_not_equal(sub_2.response_header['LAST_MODIFIED'], sub_3.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_3.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1_1\r\n", sub_3.response, "The published message was not received correctly")
headers.merge!({'If-Modified-Since' => sub_3.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_3.response_header['ETAG']})
sub_4 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_4.callback {
assert_equal(304, sub_4.response_header.status, "Wrong status")
assert_equal(sub_3.response_header['LAST_MODIFIED'], sub_4.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal(sub_3.response_header['ETAG'], sub_4.response_header['ETAG'].to_s, "Wrong header")
sleep (1) # to publish the second message in a different second from the first
publish_message_inline(channel_2, {'accept' => 'text/html'}, body + "1_2")
headers.merge!({'If-Modified-Since' => sub_4.response_header['LAST_MODIFIED'], 'If-None-Match' => sub_4.response_header['ETAG']})
sub_5 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_2.to_s + '/' + channel_1.to_s).get :head => headers, :timeout => 30
sub_5.callback {
assert_equal(200, sub_5.response_header.status, "Wrong status")
assert_not_equal(sub_4.response_header['LAST_MODIFIED'], sub_5.response_header['LAST_MODIFIED'].to_s, "Wrong header")
assert_equal("0", sub_5.response_header['ETAG'].to_s, "Wrong header")
assert_equal("#{body}1_2\r\n", sub_5.response, "The published message was not received correctly")
EventMachine.stop
}
}
}
}
}
add_test_timeout
}
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment