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 h2. Version 0.2.7
* Adding uptime information for server and workers on statistics * Adding uptime information for server and workers on statistics
......
...@@ -174,7 +174,7 @@ h3(#directives). Directives ...@@ -174,7 +174,7 @@ h3(#directives). Directives
(head). | directive | default value | values | context | location | (head). | directive | default value | values | context | location |
|push_stream_channels_statistics|-|-|location|-| |push_stream_channels_statistics|-|-|location|-|
|push_stream_publisher|-|-|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_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_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| |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 ...@@ -247,10 +247,45 @@ POST, publish a message to the channel
h4(#push_stream_subscriber). push_stream_subscriber h4(#push_stream_subscriber). push_stream_subscriber
default: streaming
context: location 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. 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 h3(#functionality). Functionality
......
...@@ -73,6 +73,7 @@ typedef struct { ...@@ -73,6 +73,7 @@ typedef struct {
ngx_uint_t keepalive; ngx_uint_t keepalive;
ngx_uint_t publisher_admin; ngx_uint_t publisher_admin;
ngx_flag_t subscriber_eventsource; ngx_flag_t subscriber_eventsource;
ngx_uint_t subscriber_mode;
} ngx_http_push_stream_loc_conf_t; } ngx_http_push_stream_loc_conf_t;
// shared memory segment name // shared memory segment name
...@@ -86,6 +87,7 @@ typedef struct { ...@@ -86,6 +87,7 @@ typedef struct {
ngx_flag_t deleted; ngx_flag_t deleted;
ngx_int_t id; ngx_int_t id;
ngx_str_t *raw; ngx_str_t *raw;
ngx_int_t tag;
ngx_str_t *event_id; ngx_str_t *event_id;
ngx_str_t *event_id_message; ngx_str_t *event_id_message;
ngx_str_t *formatted_messages; ngx_str_t *formatted_messages;
...@@ -97,6 +99,7 @@ typedef struct ngx_http_push_stream_subscriber_cleanup_s ngx_http_push_stream_su ...@@ -97,6 +99,7 @@ typedef struct ngx_http_push_stream_subscriber_cleanup_s ngx_http_push_stream_su
typedef struct { typedef struct {
ngx_queue_t queue; // this MUST be first ngx_queue_t queue; // this MUST be first
ngx_http_request_t *request; ngx_http_request_t *request;
ngx_flag_t longpolling;
} ngx_http_push_stream_subscriber_t; } ngx_http_push_stream_subscriber_t;
typedef struct { typedef struct {
...@@ -111,6 +114,8 @@ typedef struct { ...@@ -111,6 +114,8 @@ typedef struct {
ngx_rbtree_node_t node; // this MUST be first ngx_rbtree_node_t node; // this MUST be first
ngx_str_t id; ngx_str_t id;
ngx_uint_t last_message_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 stored_messages;
ngx_uint_t subscribers; ngx_uint_t subscribers;
ngx_http_push_stream_pid_queue_t workers_with_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 ...@@ -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_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_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_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_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_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 // other stuff
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_DELETE_METHODS = ngx_string("GET, POST, DELETE"); 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 ...@@ -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 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_ */ #endif /* NGX_HTTP_PUSH_STREAM_MODULE_SUBSCRIBER_H_ */
...@@ -30,8 +30,8 @@ ...@@ -30,8 +30,8 @@
#include <ngx_http_push_stream_module_ipc.h> #include <ngx_http_push_stream_module_ipc.h>
typedef struct { typedef struct {
ngx_queue_t queue; ngx_queue_t queue;
ngx_str_t *line; ngx_str_t *line;
} ngx_http_push_stream_line_t; } ngx_http_push_stream_line_t;
typedef struct { typedef struct {
...@@ -215,6 +215,7 @@ ngx_http_push_stream_msg_t *ngx_http_push_stream_ping_msg = NULL; ...@@ -215,6 +215,7 @@ ngx_http_push_stream_msg_t *ngx_http_push_stream_ping_msg = NULL;
// general request handling // 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_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); 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_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_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); 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 ...@@ -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! // now let's respond to some requests!
while ((cur = (ngx_http_push_stream_subscriber_t *) ngx_queue_next(&cur->queue)) != sentinel) { while ((cur = (ngx_http_push_stream_subscriber_t *) ngx_queue_next(&cur->queue)) != sentinel) {
ngx_http_push_stream_send_response_message(cur->request, channel, msg); 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) ...@@ -207,7 +207,9 @@ ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r)
// put messages on the queue // put messages on the queue
if (cf->store_messages) { if (cf->store_messages) {
// tag message with time stamp and a sequence tag
msg->time = ngx_time(); msg->time = ngx_time();
msg->tag = (msg->time == channel->last_message_time) ? (channel->last_message_tag + 1) : 0;
// set message expiration time // set message expiration time
msg->expires = (cf->buffer_timeout == NGX_CONF_UNSET ? 0 : (ngx_time() + cf->buffer_timeout)); msg->expires = (cf->buffer_timeout == NGX_CONF_UNSET ? 0 : (ngx_time() + cf->buffer_timeout));
ngx_queue_insert_tail(&channel->message_queue.queue, &msg->queue); 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) ...@@ -215,6 +217,9 @@ ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r)
// now see if the queue is too big // now see if the queue is too big
ngx_http_push_stream_ensure_qtd_of_messages_locked(channel, cf->max_messages, 0); 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); ngx_shmtx_unlock(&shpool->mutex);
......
...@@ -39,10 +39,10 @@ static ngx_command_t ngx_http_push_stream_commands[] = { ...@@ -39,10 +39,10 @@ static ngx_command_t ngx_http_push_stream_commands[] = {
0, 0,
NULL }, NULL },
{ ngx_string("push_stream_subscriber"), { 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_push_stream_subscriber,
NGX_HTTP_LOC_CONF_OFFSET, NGX_HTTP_LOC_CONF_OFFSET,
0, offsetof(ngx_http_push_stream_loc_conf_t, subscriber_mode),
NULL }, NULL },
{ ngx_string("push_stream_max_reserved_memory"), { ngx_string("push_stream_max_reserved_memory"),
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
...@@ -404,6 +404,7 @@ ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf) ...@@ -404,6 +404,7 @@ ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf)
lcf->keepalive = NGX_CONF_UNSET_UINT; lcf->keepalive = NGX_CONF_UNSET_UINT;
lcf->publisher_admin = NGX_CONF_UNSET_UINT; lcf->publisher_admin = NGX_CONF_UNSET_UINT;
lcf->subscriber_eventsource = NGX_CONF_UNSET_UINT; lcf->subscriber_eventsource = NGX_CONF_UNSET_UINT;
lcf->subscriber_mode = NGX_CONF_UNSET_UINT;
return lcf; return lcf;
} }
...@@ -650,6 +651,26 @@ ngx_http_push_stream_publisher(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ...@@ -650,6 +651,26 @@ ngx_http_push_stream_publisher(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
static char * static char *
ngx_http_push_stream_subscriber(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 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); char *rc = ngx_http_push_stream_setup_handler(cf, conf, &ngx_http_push_stream_subscriber_handler);
if (rc == NGX_CONF_OK) { if (rc == NGX_CONF_OK) {
......
...@@ -25,12 +25,15 @@ ...@@ -25,12 +25,15 @@
#include <ngx_http_push_stream_module_subscriber.h> #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 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_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_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_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_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_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 static ngx_int_t
ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r) 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) ...@@ -46,6 +49,8 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
ngx_http_push_stream_channel_t *channel; ngx_http_push_stream_channel_t *channel;
time_t if_modified_since; time_t if_modified_since;
ngx_str_t *last_event_id; ngx_str_t *last_event_id;
ngx_str_t *push_mode;
ngx_flag_t polling, longpolling;
// only accept GET method // only accept GET method
if (!(r->method & NGX_HTTP_GET)) { if (!(r->method & NGX_HTTP_GET)) {
...@@ -122,9 +127,21 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r) ...@@ -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); 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) { if ((worker_subscriber = ngx_http_push_stream_subscriber_prepare_request_to_keep_connected(r)) == NULL) {
ngx_destroy_pool(temp_pool); ngx_destroy_pool(temp_pool);
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
...@@ -139,7 +156,6 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r) ...@@ -139,7 +156,6 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }
ngx_shmtx_lock(&shpool->mutex); ngx_shmtx_lock(&shpool->mutex);
ngx_http_push_stream_registry_subscriber_locked(worker_subscriber); ngx_http_push_stream_registry_subscriber_locked(worker_subscriber);
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
...@@ -161,10 +177,130 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r) ...@@ -161,10 +177,130 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
return NGX_DONE; return NGX_DONE;
} }
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_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_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;
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_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;
}
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 ((subscription = ngx_http_push_stream_create_channel_subscription(r, channel, longpolling)) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
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 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_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_pid_queue_t *cur, *worker_subscribers_sentinel = NULL;
ngx_http_push_stream_channel_t *channel; ngx_http_push_stream_channel_t *channel;
ngx_http_push_stream_subscription_t *subscription; ngx_http_push_stream_subscription_t *subscription;
ngx_int_t result; ngx_int_t result;
...@@ -175,33 +311,15 @@ ngx_http_push_stream_subscriber_assign_channel(ngx_slab_pool_t *shpool, ngx_http ...@@ -175,33 +311,15 @@ ngx_http_push_stream_subscriber_assign_channel(ngx_slab_pool_t *shpool, ngx_http
return NGX_ERROR; return NGX_ERROR;
} }
cur = &channel->workers_with_subscribers; if ((subscription = ngx_http_push_stream_create_channel_subscription(r, channel, 0)) == NULL) {
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
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);
ngx_shmtx_unlock(&shpool->mutex);
if (worker_subscribers_sentinel == NULL) {
return NGX_ERROR;
}
}
if ((subscription = ngx_http_push_stream_create_channel_subscription(r, channel)) == NULL) {
return NGX_ERROR; return NGX_ERROR;
} }
// send old messages to new subscriber // 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); 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); ngx_shmtx_unlock(&shpool->mutex);
return result; return result;
...@@ -365,27 +483,52 @@ ngx_http_push_stream_registry_subscriber_locked(ngx_http_push_stream_worker_subs ...@@ -365,27 +483,52 @@ ngx_http_push_stream_registry_subscriber_locked(ngx_http_push_stream_worker_subs
thisworker_data->subscribers++; thisworker_data->subscribers++;
} }
static void static ngx_flag_t
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) 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_http_push_stream_msg_t *message, *message_sentinel; ngx_flag_t old_messages = 0;
ngx_http_push_stream_msg_t *message, *message_sentinel;
message_sentinel = &channel->message_queue;
message = message_sentinel;
if (channel->stored_messages > 0) { if (channel->stored_messages > 0) {
message_sentinel = &channel->message_queue;
message = message_sentinel; if (backtrack > 0) {
if (last_event_id != NULL) { old_messages = 1;
} else if ((last_event_id != NULL) || (if_modified_since >= 0)) {
ngx_flag_t found = 0; ngx_flag_t found = 0;
while ((!message->deleted) && ((message = (ngx_http_push_stream_msg_t *) ngx_queue_next(&message->queue)) != message_sentinel)) { 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; found = 1;
continue; 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) { 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 qtd = (backtrack > channel->stored_messages) ? channel->stored_messages : backtrack;
ngx_uint_t start = channel->stored_messages - qtd; ngx_uint_t start = channel->stored_messages - qtd;
// positioning at first message, and send the others // positioning at first message, and send the others
...@@ -397,13 +540,24 @@ ngx_http_push_stream_send_old_messages(ngx_http_request_t *r, ngx_http_push_stre ...@@ -397,13 +540,24 @@ ngx_http_push_stream_send_old_messages(ngx_http_request_t *r, ngx_http_push_stre
start--; start--;
} }
} }
} 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) && (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 ((backtrack == 0) && (if_modified_since != 0)) { 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)))) {
while ((!message->deleted) && ((message = (ngx_http_push_stream_msg_t *) ngx_queue_next(&message->queue)) != message_sentinel)) { found = 1;
if (message->time > if_modified_since) { if ((message->time == if_modified_since) && (message->tag == tag)) {
ngx_http_push_stream_send_response_message(r, channel, message); 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 ...@@ -437,7 +591,7 @@ ngx_http_push_stream_create_worker_subscriber_channel_sentinel_locked(ngx_slab_p
} }
static ngx_http_push_stream_subscription_t * 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_subscription_t *subscription;
ngx_http_push_stream_subscriber_t *subscriber; 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 ...@@ -453,6 +607,7 @@ ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http
} }
subscriber->request = r; subscriber->request = r;
subscriber->longpolling = longpolling;
subscription->channel = channel; subscription->channel = channel;
subscription->subscriber = subscriber; subscription->subscriber = subscriber;
...@@ -461,8 +616,9 @@ ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http ...@@ -461,8 +616,9 @@ ngx_http_push_stream_create_channel_subscription(ngx_http_request_t *r, ngx_http
} }
static ngx_int_t 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; ngx_http_push_stream_channel_t *channel;
// check if channel still exists // check if channel still exists
...@@ -470,6 +626,22 @@ ngx_http_push_stream_assing_subscription_to_channel_locked(ngx_str_t *channel_id ...@@ -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); 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; 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 channel->subscribers++; // do this only when we know everything went okay
ngx_queue_insert_tail(&subscriptions_sentinel->queue, &subscription->queue); ngx_queue_insert_tail(&subscriptions_sentinel->queue, &subscription->queue);
ngx_queue_insert_tail(&worker_subscribers_sentinel->subscriber_sentinel.queue, &subscription->subscriber->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_ ...@@ -1113,3 +1113,20 @@ ngx_http_push_stream_apply_template_to_each_line(ngx_str_t *text, const ngx_str_
return result; 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) ...@@ -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; 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_id = 0;
channel->last_message_time = 0;
channel->last_message_tag = 0;
channel->stored_messages = 0; channel->stored_messages = 0;
channel->subscribers = 0; channel->subscribers = 0;
channel->deleted = 0; channel->deleted = 0;
......
...@@ -155,6 +155,7 @@ module BaseTestCase ...@@ -155,6 +155,7 @@ module BaseTestCase
@channel_deleted_message_text = nil @channel_deleted_message_text = nil
@ping_message_text = nil @ping_message_text = nil
@subscriber_eventsource = 'off' @subscriber_eventsource = 'off'
@subscriber_mode = nil
self.send(:global_configuration) if self.respond_to?(:global_configuration) self.send(:global_configuration) if self.respond_to?(:global_configuration)
end end
...@@ -285,7 +286,7 @@ http { ...@@ -285,7 +286,7 @@ http {
location ~ /sub/(.*)? { location ~ /sub/(.*)? {
# activate subscriber mode for this location # 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 # activate event source support for this location
<%= "push_stream_subscriber_eventsource #{@subscriber_eventsource};" unless @subscriber_eventsource.nil? %> <%= "push_stream_subscriber_eventsource #{@subscriber_eventsource};" unless @subscriber_eventsource.nil? %>
......
...@@ -176,4 +176,49 @@ class TestSetuParameters < Test::Unit::TestCase ...@@ -176,4 +176,49 @@ class TestSetuParameters < Test::Unit::TestCase
ensure ensure
self.stop_server self.stop_server
end 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 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