Commit 9b272cf3 authored by Wandenberg Peixoto's avatar Wandenberg Peixoto

publisher admin implementation to delete channels

parent f7210a24
* Fixing bug which removed default message template * Fixing bug which removed default message template
* Adding publisher administrator feature to delete channels (Suggested by Alexey Vdovin)
* Removing support for versions 0.7.x * Removing support for versions 0.7.x
* Fixing messages sent to subscribers to be a truly transfer encoding chunked connection * Fixing messages sent to subscribers to be a truly transfer encoding chunked connection
* Removing hack to keep connection open (Thanks _Maxim Dounin_) * Removing hack to keep connection open (Thanks _Maxim Dounin_)
......
...@@ -185,6 +185,7 @@ h3(#directives). Directives ...@@ -185,6 +185,7 @@ h3(#directives). Directives
|push_stream_max_number_of_broadcast_channels|unset|number|http, location|(push_stream_subscriber and push_stream_publisher) or main nginx configuration| |push_stream_max_number_of_broadcast_channels|unset|number|http, location|(push_stream_subscriber and push_stream_publisher) or 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_keepalive|off|on, off|http, location|(push_stream_publisher and push_stream_channels_statistics) or main nginx configuration| |push_stream_keepalive|off|on, off|http, location|(push_stream_publisher and push_stream_channels_statistics) or main nginx configuration|
|push_stream_publisher_admin|off|on, off|location|push_stream_publisher|
h4(#push_stream_channels_statistics). push_stream_channels_statistics h4(#push_stream_channels_statistics). push_stream_channels_statistics
...@@ -382,11 +383,39 @@ h4(#push_stream_keepalive). push_stream_keepalive [ on | off ] ...@@ -382,11 +383,39 @@ h4(#push_stream_keepalive). push_stream_keepalive [ on | off ]
New in version 0.2.4 New in version 0.2.4
default: off default: off
context: location context: http, location
location: (push_stream_publisher and push_stream_channels_statistics) or main nginx configuration location: (push_stream_publisher and push_stream_channels_statistics) or main nginx configuration
Enable keepalive connections, on publisher or channels statistics locations. Enable keepalive connections, on publisher or channels statistics locations.
h4(#push_stream_publisher_admin). push_stream_publisher_admin [ on | off ]
New in version 0.2.5
default: off
context: location
location: push_stream_publisher
Enable admin features for publishers.
They can delete channels removing any existent subscribers using DELETE http method.
<pre>
<code>
# Pub create channel 1
curl -s -v -X POST "http://localhost/pub?id=my_channel_1" -d "Hello World 1!"
# Pub create channel 2
curl -s -v -X POST "http://localhost/pub?id=my_channel_2" -d "Hello World 2!"
# Sub on both channels
curl -s -v "http://localhost/sub/my_channel_1.b1/my_channel_2.b1"
# Pub delete channel 2
curl -s -v -X DELETE "http://localhost/pub?id=my_channel_2"
</code>
</pre>
h2(#attention). Attention h2(#attention). Attention
This module controls everything needed to send the messages to subscribers. This module controls everything needed to send the messages to subscribers.
......
...@@ -67,6 +67,7 @@ typedef struct { ...@@ -67,6 +67,7 @@ typedef struct {
ngx_uint_t max_number_of_broadcast_channels; ngx_uint_t max_number_of_broadcast_channels;
ngx_msec_t buffer_cleanup_interval; ngx_msec_t buffer_cleanup_interval;
ngx_uint_t keepalive; ngx_uint_t keepalive;
ngx_uint_t publisher_admin;
} ngx_http_push_stream_loc_conf_t; } ngx_http_push_stream_loc_conf_t;
// shared memory segment name // shared memory segment name
...@@ -110,6 +111,7 @@ typedef struct { ...@@ -110,6 +111,7 @@ typedef struct {
time_t expires; time_t expires;
ngx_flag_t deleted; ngx_flag_t deleted;
ngx_flag_t broadcast; ngx_flag_t broadcast;
ngx_http_push_stream_msg_t *channel_deleted_message;
} ngx_http_push_stream_channel_t; } ngx_http_push_stream_channel_t;
typedef struct { typedef struct {
...@@ -166,6 +168,7 @@ typedef struct { ...@@ -166,6 +168,7 @@ typedef struct {
ngx_uint_t subscribers; // # of subscribers in all channels ngx_uint_t subscribers; // # of subscribers in all channels
ngx_http_push_stream_msg_t messages_to_delete; ngx_http_push_stream_msg_t messages_to_delete;
ngx_rbtree_t channels_to_delete; ngx_rbtree_t channels_to_delete;
ngx_rbtree_t unrecoverable_channels;
ngx_http_push_stream_worker_data_t ipc[NGX_MAX_PROCESSES]; // interprocess stuff ngx_http_push_stream_worker_data_t ipc[NGX_MAX_PROCESSES]; // interprocess stuff
} ngx_http_push_stream_shm_data_t; } ngx_http_push_stream_shm_data_t;
...@@ -190,6 +193,7 @@ static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE = ngx_s ...@@ -190,6 +193,7 @@ static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE = ngx_s
static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOO_MUCH_BROADCAST_CHANNELS = ngx_string("Subscribed too much broadcast channels."); static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOO_MUCH_BROADCAST_CHANNELS = ngx_string("Subscribed too much broadcast channels.");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CANNOT_CREATE_CHANNELS = ngx_string("Subscriber could not create channels."); static const ngx_str_t NGX_HTTP_PUSH_STREAM_CANNOT_CREATE_CHANNELS = ngx_string("Subscriber could not create channels.");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED_MESSAGE = ngx_string("Number of channels were exceeded."); static const ngx_str_t NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED_MESSAGE = ngx_string("Number of channels were exceeded.");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_DELETED = ngx_string("Channel deleted.");
#define NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID (void *) -1 #define NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID (void *) -1
#define NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID (void *) -2 #define NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID (void *) -2
...@@ -208,6 +212,7 @@ static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_TRANSFER_ENCODING = ngx_stri ...@@ -208,6 +212,7 @@ static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_TRANSFER_ENCODING = ngx_stri
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");
// 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_METHODS = ngx_string("GET, POST"); static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_METHODS = ngx_string("GET, POST");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOW_GET = ngx_string("GET"); static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOW_GET = ngx_string("GET");
......
...@@ -44,6 +44,7 @@ static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_CHECK_MESSAGES = {49, 0, 0, -1}; ...@@ -44,6 +44,7 @@ static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_CHECK_MESSAGES = {49, 0, 0, -1};
static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_SEND_PING = {50, 0, 0, -1}; static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_SEND_PING = {50, 0, 0, -1};
static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_DISCONNECT_SUBSCRIBERS = {51, 0, 0, -1}; static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_DISCONNECT_SUBSCRIBERS = {51, 0, 0, -1};
static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS = {52, 0, 0, -1}; static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS = {52, 0, 0, -1};
static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_DELETE_CHANNEL = {53, 0, 0, -1};
// worker processes of the world, unite. // worker processes of the world, unite.
ngx_socket_t ngx_http_push_stream_socketpairs[NGX_MAX_PROCESSES][2]; ngx_socket_t ngx_http_push_stream_socketpairs[NGX_MAX_PROCESSES][2];
...@@ -57,6 +58,7 @@ static ngx_int_t ngx_http_push_stream_alert_worker(ngx_pid_t pid, ngx_int ...@@ -57,6 +58,7 @@ static ngx_int_t ngx_http_push_stream_alert_worker(ngx_pid_t pid, ngx_int
#define ngx_http_push_stream_alert_worker_send_ping(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_SEND_PING); #define ngx_http_push_stream_alert_worker_send_ping(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_SEND_PING);
#define ngx_http_push_stream_alert_worker_disconnect_subscribers(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_DISCONNECT_SUBSCRIBERS); #define ngx_http_push_stream_alert_worker_disconnect_subscribers(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_DISCONNECT_SUBSCRIBERS);
#define ngx_http_push_stream_alert_worker_census_subscribers(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS); #define ngx_http_push_stream_alert_worker_census_subscribers(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS);
#define ngx_http_push_stream_alert_worker_delete_channel(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_DELETE_CHANNEL);
static ngx_int_t ngx_http_push_stream_send_worker_message(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_subscriber_t *subscriber_sentinel, ngx_pid_t pid, ngx_int_t worker_slot, ngx_http_push_stream_msg_t *msg, ngx_log_t *log); static ngx_int_t ngx_http_push_stream_send_worker_message(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_subscriber_t *subscriber_sentinel, ngx_pid_t pid, ngx_int_t worker_slot, ngx_http_push_stream_msg_t *msg, ngx_log_t *log);
......
...@@ -183,6 +183,9 @@ static const ngx_int_t NGX_HTTP_PUSH_STREAM_PING_MESSAGE_ID = -1; ...@@ -183,6 +183,9 @@ static const ngx_int_t NGX_HTTP_PUSH_STREAM_PING_MESSAGE_ID = -1;
static const ngx_str_t NGX_HTTP_PUSH_STREAM_PING_MESSAGE_TEXT = ngx_string(""); static const ngx_str_t NGX_HTTP_PUSH_STREAM_PING_MESSAGE_TEXT = ngx_string("");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_PING_CHANNEL_ID = ngx_string(""); static const ngx_str_t NGX_HTTP_PUSH_STREAM_PING_CHANNEL_ID = ngx_string("");
static const ngx_int_t NGX_HTTP_PUSH_STREAM_CHANNEL_DELETED_MESSAGE_ID = -2;
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_DELETED_MESSAGE_TEXT = ngx_string("Channel deleted");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOKEN_MESSAGE_ID = ngx_string("~id~"); static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOKEN_MESSAGE_ID = ngx_string("~id~");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOKEN_MESSAGE_CHANNEL = ngx_string("~channel~"); static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOKEN_MESSAGE_CHANNEL = ngx_string("~channel~");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOKEN_MESSAGE_TEXT = ngx_string("~text~"); static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOKEN_MESSAGE_TEXT = ngx_string("~text~");
...@@ -223,15 +226,17 @@ static void ngx_http_push_stream_buffer_cleanup_timer_set(ngx_ht ...@@ -223,15 +226,17 @@ static void ngx_http_push_stream_buffer_cleanup_timer_set(ngx_ht
static void ngx_http_push_stream_timer_reset(ngx_msec_t timer_interval, ngx_event_t *timer_event); static void ngx_http_push_stream_timer_reset(ngx_msec_t timer_interval, ngx_event_t *timer_event);
static void ngx_http_push_stream_worker_subscriber_cleanup(ngx_http_push_stream_worker_subscriber_t *worker_subscriber); static void ngx_http_push_stream_worker_subscriber_cleanup_locked(ngx_http_push_stream_worker_subscriber_t *worker_subscriber);
u_char * ngx_http_push_stream_append_crlf(const ngx_str_t *str, ngx_pool_t *pool); u_char * ngx_http_push_stream_append_crlf(const ngx_str_t *str, ngx_pool_t *pool);
static void ngx_http_push_stream_mark_message_to_delete_locked(ngx_http_push_stream_msg_t *msg); static void ngx_http_push_stream_mark_message_to_delete_locked(ngx_http_push_stream_msg_t *msg);
static void ngx_http_push_stream_delete_channel(ngx_str_t *id);
static void ngx_http_push_stream_collect_expired_messages(ngx_http_push_stream_shm_data_t *data, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force); static void ngx_http_push_stream_collect_expired_messages(ngx_http_push_stream_shm_data_t *data, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force);
static void ngx_http_push_stream_collect_expired_messages_and_empty_channels(ngx_http_push_stream_shm_data_t *data, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force); static void ngx_http_push_stream_collect_expired_messages_and_empty_channels(ngx_http_push_stream_shm_data_t *data, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force);
static void ngx_http_push_stream_free_message_memory_locked(ngx_slab_pool_t *shpool, ngx_http_push_stream_msg_t *msg); static void ngx_http_push_stream_free_message_memory_locked(ngx_slab_pool_t *shpool, ngx_http_push_stream_msg_t *msg);
static ngx_int_t ngx_http_push_stream_free_memory_of_expired_messages_and_channels(ngx_flag_t force); static ngx_int_t ngx_http_push_stream_free_memory_of_expired_messages_and_channels(ngx_flag_t force);
static ngx_inline void ngx_http_push_stream_ensure_qtd_of_messages_locked(ngx_http_push_stream_channel_t *channel, ngx_uint_t max_messages, ngx_flag_t expired); static ngx_inline void ngx_http_push_stream_ensure_qtd_of_messages_locked(ngx_http_push_stream_channel_t *channel, ngx_uint_t max_messages, ngx_flag_t expired);
static ngx_inline void ngx_http_push_stream_delete_worker_channel(void);
static ngx_http_push_stream_content_subtype_t * ngx_http_push_stream_match_channel_info_format_and_content_type(ngx_http_request_t *r, ngx_uint_t default_subtype); static ngx_http_push_stream_content_subtype_t * ngx_http_push_stream_match_channel_info_format_and_content_type(ngx_http_request_t *r, ngx_uint_t default_subtype);
......
...@@ -240,6 +240,8 @@ ngx_http_push_stream_channel_handler(ngx_event_t *ev) ...@@ -240,6 +240,8 @@ ngx_http_push_stream_channel_handler(ngx_event_t *ev)
ngx_http_push_stream_disconnect_worker_subscribers(0); ngx_http_push_stream_disconnect_worker_subscribers(0);
} else if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS.command) { } else if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS.command) {
ngx_http_push_stream_census_worker_subscribers(); ngx_http_push_stream_census_worker_subscribers();
} else if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_DELETE_CHANNEL.command) {
ngx_http_push_stream_delete_worker_channel();
} }
} }
} }
...@@ -286,20 +288,23 @@ ngx_http_push_stream_disconnect_worker_subscribers(ngx_flag_t force_disconnect) ...@@ -286,20 +288,23 @@ ngx_http_push_stream_disconnect_worker_subscribers(ngx_flag_t force_disconnect)
ngx_http_push_stream_worker_data_t *workers_data = ((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->ipc; ngx_http_push_stream_worker_data_t *workers_data = ((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->ipc;
ngx_http_push_stream_worker_data_t *thisworker_data = workers_data + ngx_process_slot; ngx_http_push_stream_worker_data_t *thisworker_data = workers_data + ngx_process_slot;
ngx_http_push_stream_worker_subscriber_t *sentinel = thisworker_data->worker_subscribers_sentinel; ngx_http_push_stream_worker_subscriber_t *sentinel = thisworker_data->worker_subscribers_sentinel;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
ngx_http_push_stream_worker_subscriber_t *cur = sentinel; ngx_http_push_stream_worker_subscriber_t *cur = sentinel;
time_t now = ngx_time(); time_t now = ngx_time();
ngx_shmtx_lock(&shpool->mutex);
while ((cur = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&sentinel->queue)) != sentinel) { while ((cur = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&sentinel->queue)) != sentinel) {
if ((cur->request != NULL) && (ngx_exiting || (force_disconnect == 1) || ((cur->expires != 0) && (now > cur->expires)))) { if ((cur->request != NULL) && (ngx_exiting || (force_disconnect == 1) || ((cur->expires != 0) && (now > cur->expires)))) {
ngx_http_push_stream_worker_subscriber_cleanup(cur); ngx_http_push_stream_worker_subscriber_cleanup_locked(cur);
ngx_http_push_stream_send_response_text(cur->request, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.data, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.len, 1); ngx_http_push_stream_send_response_text(cur->request, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.data, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.len, 1);
ngx_http_finalize_request(cur->request, NGX_HTTP_OK); ngx_http_finalize_request(cur->request, NGX_HTTP_OK);
} else { } else {
break; break;
} }
} }
ngx_shmtx_unlock(&shpool->mutex);
} }
......
...@@ -36,8 +36,14 @@ ngx_http_push_stream_publisher_handler(ngx_http_request_t *r) ...@@ -36,8 +36,14 @@ ngx_http_push_stream_publisher_handler(ngx_http_request_t *r)
r->keepalive = cf->keepalive; r->keepalive = cf->keepalive;
// only accept GET and POST methods // only accept GET, POST and DELETE methods if enable publisher administration
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_POST))) { if (cf->publisher_admin && !(r->method & (NGX_HTTP_GET|NGX_HTTP_POST|NGX_HTTP_DELETE))) {
ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_DELETE_METHODS);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL);
}
// only accept GET and POST methods if NOT enable publisher administration
if (!cf->publisher_admin && !(r->method & (NGX_HTTP_GET|NGX_HTTP_POST))) {
ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_METHODS); ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_METHODS);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL);
} }
...@@ -62,14 +68,20 @@ ngx_http_push_stream_publisher_handler(ngx_http_request_t *r) ...@@ -62,14 +68,20 @@ ngx_http_push_stream_publisher_handler(ngx_http_request_t *r)
return ngx_http_push_stream_publisher_handle_post(cf, r, id); return ngx_http_push_stream_publisher_handle_post(cf, r, id);
} }
// GET only make sense with a previous existing channel // GET or DELETE only make sense with a previous existing channel
if (channel == NULL) { if (channel == NULL) {
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_FOUND, NULL); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_FOUND, NULL);
} }
if (cf->publisher_admin && (r->method == NGX_HTTP_DELETE)) {
ngx_http_push_stream_delete_channel(id);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_OK, &NGX_HTTP_PUSH_STREAM_CHANNEL_DELETED);
}
return ngx_http_push_stream_send_response_channel_info(r, channel); return ngx_http_push_stream_send_response_channel_info(r, channel);
} }
static ngx_int_t static ngx_int_t
ngx_http_push_stream_publisher_handle_post(ngx_http_push_stream_loc_conf_t *cf, ngx_http_request_t *r, ngx_str_t *id) ngx_http_push_stream_publisher_handle_post(ngx_http_push_stream_loc_conf_t *cf, ngx_http_request_t *r, ngx_str_t *id)
{ {
......
...@@ -146,6 +146,12 @@ static ngx_command_t ngx_http_push_stream_commands[] = { ...@@ -146,6 +146,12 @@ static ngx_command_t ngx_http_push_stream_commands[] = {
NGX_HTTP_LOC_CONF_OFFSET, NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_push_stream_loc_conf_t, keepalive), offsetof(ngx_http_push_stream_loc_conf_t, keepalive),
NULL }, NULL },
{ ngx_string("push_stream_publisher_admin"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_push_stream_loc_conf_t, publisher_admin),
NULL },
ngx_null_command ngx_null_command
}; };
...@@ -303,6 +309,7 @@ ngx_http_push_stream_create_main_conf(ngx_conf_t *cf) ...@@ -303,6 +309,7 @@ ngx_http_push_stream_create_main_conf(ngx_conf_t *cf)
return mcf; return mcf;
} }
static char * static char *
ngx_http_push_stream_init_main_conf(ngx_conf_t *cf, void *parent) ngx_http_push_stream_init_main_conf(ngx_conf_t *cf, void *parent)
{ {
...@@ -358,6 +365,7 @@ ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf) ...@@ -358,6 +365,7 @@ ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf)
lcf->max_number_of_broadcast_channels = NGX_CONF_UNSET_UINT; lcf->max_number_of_broadcast_channels = NGX_CONF_UNSET_UINT;
lcf->buffer_cleanup_interval = NGX_CONF_UNSET_MSEC; lcf->buffer_cleanup_interval = NGX_CONF_UNSET_MSEC;
lcf->keepalive = NGX_CONF_UNSET_UINT; lcf->keepalive = NGX_CONF_UNSET_UINT;
lcf->publisher_admin = NGX_CONF_UNSET_UINT;
return lcf; return lcf;
} }
...@@ -385,6 +393,7 @@ ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ...@@ -385,6 +393,7 @@ ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_uint_value(conf->max_number_of_broadcast_channels, prev->max_number_of_broadcast_channels, NGX_CONF_UNSET_UINT); ngx_conf_merge_uint_value(conf->max_number_of_broadcast_channels, prev->max_number_of_broadcast_channels, NGX_CONF_UNSET_UINT);
ngx_conf_merge_uint_value(conf->buffer_cleanup_interval, prev->buffer_cleanup_interval, NGX_CONF_UNSET_MSEC); ngx_conf_merge_uint_value(conf->buffer_cleanup_interval, prev->buffer_cleanup_interval, NGX_CONF_UNSET_MSEC);
ngx_conf_merge_uint_value(conf->keepalive, prev->keepalive, 0); ngx_conf_merge_uint_value(conf->keepalive, prev->keepalive, 0);
ngx_conf_merge_uint_value(conf->publisher_admin, prev->publisher_admin, 0);
// sanity checks // sanity checks
...@@ -506,6 +515,7 @@ ngx_http_push_stream_setup_handler(ngx_conf_t *cf, void *conf, ngx_int_t (*handl ...@@ -506,6 +515,7 @@ ngx_http_push_stream_setup_handler(ngx_conf_t *cf, void *conf, ngx_int_t (*handl
return NGX_CONF_OK; return NGX_CONF_OK;
} }
static char * static char *
ngx_http_push_stream_channels_statistics(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_http_push_stream_channels_statistics(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{ {
...@@ -522,6 +532,7 @@ ngx_http_push_stream_channels_statistics(ngx_conf_t *cf, ngx_command_t *cmd, voi ...@@ -522,6 +532,7 @@ ngx_http_push_stream_channels_statistics(ngx_conf_t *cf, ngx_command_t *cmd, voi
return rc; return rc;
} }
static char * static char *
ngx_http_push_stream_publisher(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_http_push_stream_publisher(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{ {
...@@ -585,7 +596,7 @@ ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data) ...@@ -585,7 +596,7 @@ ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data)
} }
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
ngx_rbtree_node_t *sentinel, *remove_sentinel; ngx_rbtree_node_t *sentinel, *remove_sentinel, *unrecoverable_sentinel;
ngx_http_push_stream_shm_data_t *d; ngx_http_push_stream_shm_data_t *d;
if ((d = (ngx_http_push_stream_shm_data_t *) ngx_slab_alloc(shpool, sizeof(*d))) == NULL) { //shm_data plus an array. if ((d = (ngx_http_push_stream_shm_data_t *) ngx_slab_alloc(shpool, sizeof(*d))) == NULL) { //shm_data plus an array.
...@@ -611,6 +622,11 @@ ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data) ...@@ -611,6 +622,11 @@ ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data)
} }
ngx_rbtree_init(&d->channels_to_delete, remove_sentinel, ngx_http_push_stream_rbtree_insert); ngx_rbtree_init(&d->channels_to_delete, remove_sentinel, ngx_http_push_stream_rbtree_insert);
if ((unrecoverable_sentinel = ngx_slab_alloc(shpool, sizeof(*unrecoverable_sentinel))) == NULL) {
return NGX_ERROR;
}
ngx_rbtree_init(&d->unrecoverable_channels, unrecoverable_sentinel, ngx_http_push_stream_rbtree_insert);
// create ping message // create ping message
ngx_http_push_stream_ping_msg = ngx_http_push_stream_convert_char_to_msg_on_shared_locked(NGX_HTTP_PUSH_STREAM_PING_MESSAGE_TEXT.data, NGX_HTTP_PUSH_STREAM_PING_MESSAGE_TEXT.len, NULL, NGX_HTTP_PUSH_STREAM_PING_MESSAGE_ID, ngx_cycle->pool); ngx_http_push_stream_ping_msg = ngx_http_push_stream_convert_char_to_msg_on_shared_locked(NGX_HTTP_PUSH_STREAM_PING_MESSAGE_TEXT.data, NGX_HTTP_PUSH_STREAM_PING_MESSAGE_TEXT.len, NULL, NGX_HTTP_PUSH_STREAM_PING_MESSAGE_ID, ngx_cycle->pool);
if (ngx_http_push_stream_ping_msg == NULL) { if (ngx_http_push_stream_ping_msg == NULL) {
......
...@@ -394,7 +394,11 @@ ngx_http_push_stream_parse_channels_ids_from_path(ngx_http_request_t *r, ngx_poo ...@@ -394,7 +394,11 @@ ngx_http_push_stream_parse_channels_ids_from_path(ngx_http_request_t *r, ngx_poo
static void static void
ngx_http_push_stream_subscriber_cleanup(ngx_http_push_stream_subscriber_cleanup_t *data) ngx_http_push_stream_subscriber_cleanup(ngx_http_push_stream_subscriber_cleanup_t *data)
{ {
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
if (data->worker_subscriber != NULL) { if (data->worker_subscriber != NULL) {
ngx_http_push_stream_worker_subscriber_cleanup(data->worker_subscriber); ngx_shmtx_lock(&shpool->mutex);
ngx_http_push_stream_worker_subscriber_cleanup_locked(data->worker_subscriber);
ngx_shmtx_unlock(&shpool->mutex);
} }
} }
...@@ -48,12 +48,91 @@ ngx_http_push_stream_ensure_qtd_of_messages_locked(ngx_http_push_stream_channel_ ...@@ -48,12 +48,91 @@ ngx_http_push_stream_ensure_qtd_of_messages_locked(ngx_http_push_stream_channel_
} }
static ngx_inline void
ngx_http_push_stream_delete_worker_channel(void)
{
ngx_http_push_stream_channel_t *channel;
ngx_http_push_stream_pid_queue_t *cur_worker;
ngx_http_push_stream_subscriber_t *cur;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
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_worker_data_t *workers_data = ((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->ipc;
ngx_http_push_stream_worker_data_t *thisworker_data = workers_data + ngx_process_slot;
ngx_http_push_stream_worker_subscriber_t *worker_subscriber;
ngx_http_push_stream_subscription_t *cur_subscription;
while (data->unrecoverable_channels.root != data->unrecoverable_channels.sentinel) {
ngx_shmtx_lock(&shpool->mutex);
// try to delete the channel at the root of the tree
if (data->unrecoverable_channels.root != data->unrecoverable_channels.sentinel) {
channel = (ngx_http_push_stream_channel_t *) data->unrecoverable_channels.root;
if (channel->subscribers > 0) {
cur_worker = &channel->workers_with_subscribers;
// find the current work
while ((cur_worker = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&cur_worker->queue)) != &channel->workers_with_subscribers) {
if (cur_worker->slot == ngx_process_slot) {
// to each subscriber of this channel in this worker
while(!ngx_queue_empty(&cur_worker->subscriber_sentinel.queue)) {
cur = (ngx_http_push_stream_subscriber_t *) ngx_queue_next(&cur_worker->subscriber_sentinel.queue);
// find the subscriber subscriptions on the worker
worker_subscriber = thisworker_data->worker_subscribers_sentinel;
while ((worker_subscriber = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&worker_subscriber->queue)) != thisworker_data->worker_subscribers_sentinel) {
if (worker_subscriber->request == cur->request) {
// find the subscription for the channel being deleted
cur_subscription = &worker_subscriber->subscriptions_sentinel;
while ((cur_subscription = (ngx_http_push_stream_subscription_t *) ngx_queue_next(&cur_subscription->queue)) != &worker_subscriber->subscriptions_sentinel) {
if (cur_subscription->channel == channel) {
channel->subscribers--;
// remove the reference from subscription for channel
ngx_queue_remove(&cur_subscription->queue);
// remove the reference from channel for subscriber
ngx_queue_remove(&cur->queue);
ngx_str_t *str = ngx_http_push_stream_get_formatted_message(cur->request, channel, channel->channel_deleted_message, cur->request->pool);
if (str != NULL) {
ngx_http_push_stream_send_response_text(cur->request, str->data, str->len, 0);
}
break;
}
}
// subscriber does not have any other subscription, the connection may be closed
if (ngx_queue_empty(&worker_subscriber->subscriptions_sentinel.queue)) {
ngx_http_push_stream_worker_subscriber_cleanup_locked(worker_subscriber);
ngx_http_push_stream_send_response_text(worker_subscriber->request, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.data, NGX_HTTP_PUSH_STREAM_LAST_CHUNK.len, 1);
ngx_http_finalize_request(worker_subscriber->request, NGX_HTTP_OK);
}
break;
}
}
}
}
}
} else {
ngx_rbtree_delete(&data->unrecoverable_channels, &channel->node);
nxg_http_push_stream_free_channel_memory_locked(shpool, channel);
}
}
ngx_shmtx_unlock(&shpool->mutex);
}
}
ngx_http_push_stream_msg_t * 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_pool_t *temp_pool) 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_pool_t *temp_pool)
{ {
return ngx_http_push_stream_convert_char_to_msg_on_shared_locked(buf->pos, ngx_buf_size(buf), channel, id, temp_pool); return ngx_http_push_stream_convert_char_to_msg_on_shared_locked(buf->pos, ngx_buf_size(buf), channel, id, temp_pool);
} }
ngx_http_push_stream_msg_t * 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_pool_t *temp_pool) 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_pool_t *temp_pool)
{ {
...@@ -212,13 +291,49 @@ ngx_http_push_stream_send_ping(ngx_log_t *log, ngx_http_push_stream_loc_conf_t * ...@@ -212,13 +291,49 @@ ngx_http_push_stream_send_ping(ngx_log_t *log, ngx_http_push_stream_loc_conf_t *
} }
static void
ngx_http_push_stream_delete_channel(ngx_str_t *id) {
ngx_http_push_stream_channel_t *channel;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
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_pid_queue_t *cur;
ngx_shmtx_lock(&shpool->mutex);
channel = ngx_http_push_stream_find_channel_locked(id, ngx_cycle->log);
if (channel != NULL) {
// remove channel from tree
channel->deleted = 1;
(channel->broadcast) ? data->broadcast_channels-- : data->channels--;
// move the channel to unrecoverable tree
ngx_rbtree_delete(&data->tree, (ngx_rbtree_node_t *) channel);
channel->node.key = ngx_crc32_short(channel->id.data, channel->id.len);
ngx_rbtree_insert(&data->unrecoverable_channels, (ngx_rbtree_node_t *) channel);
// remove all messages
ngx_http_push_stream_ensure_qtd_of_messages_locked(channel, 0, 0);
// send signal to each worker with subscriber to this channel
cur = &channel->workers_with_subscribers;
while ((cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&cur->queue)) != &channel->workers_with_subscribers) {
ngx_http_push_stream_alert_worker_delete_channel(cur->pid, cur->slot, ngx_cycle->log);
}
}
ngx_shmtx_unlock(&(shpool)->mutex);
}
static void static void
ngx_http_push_stream_collect_expired_messages_and_empty_channels(ngx_http_push_stream_shm_data_t *data, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force) ngx_http_push_stream_collect_expired_messages_and_empty_channels(ngx_http_push_stream_shm_data_t *data, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force)
{ {
ngx_http_push_stream_channel_t *channel; ngx_http_push_stream_channel_t *channel;
channel = (ngx_http_push_stream_channel_t *) node; channel = (ngx_http_push_stream_channel_t *) node;
if ((channel != NULL) && (channel->deleted == 0) && (&channel->node != data->tree.sentinel) && (&channel->node != data->channels_to_delete.sentinel)) { if ((channel != NULL) && (channel->deleted == 0) && (&channel->node != data->tree.sentinel) && (&channel->node != data->channels_to_delete.sentinel) && (&channel->node != data->unrecoverable_channels.sentinel)) {
if ((channel != NULL) && (channel->deleted == 0) && (channel->node.left != NULL)) { if ((channel != NULL) && (channel->deleted == 0) && (channel->node.left != NULL)) {
ngx_http_push_stream_collect_expired_messages_and_empty_channels(data, shpool, node->left, force); ngx_http_push_stream_collect_expired_messages_and_empty_channels(data, shpool, node->left, force);
...@@ -257,7 +372,7 @@ ngx_http_push_stream_collect_expired_messages(ngx_http_push_stream_shm_data_t *d ...@@ -257,7 +372,7 @@ ngx_http_push_stream_collect_expired_messages(ngx_http_push_stream_shm_data_t *d
ngx_http_push_stream_channel_t *channel; ngx_http_push_stream_channel_t *channel;
channel = (ngx_http_push_stream_channel_t *) node; channel = (ngx_http_push_stream_channel_t *) node;
if ((channel != NULL) && (channel->deleted == 0) && (&channel->node != data->tree.sentinel) && (&channel->node != data->channels_to_delete.sentinel)) { if ((channel != NULL) && (channel->deleted == 0) && (&channel->node != data->tree.sentinel) && (&channel->node != data->channels_to_delete.sentinel) && (&channel->node != data->unrecoverable_channels.sentinel)) {
if ((channel != NULL) && (channel->deleted == 0) && (channel->node.left != NULL)) { if ((channel != NULL) && (channel->deleted == 0) && (channel->node.left != NULL)) {
ngx_http_push_stream_collect_expired_messages(data, shpool, node->left, force); ngx_http_push_stream_collect_expired_messages(data, shpool, node->left, force);
...@@ -623,13 +738,11 @@ ngx_http_push_stream_format_message(ngx_http_push_stream_channel_t *channel, ngx ...@@ -623,13 +738,11 @@ ngx_http_push_stream_format_message(ngx_http_push_stream_channel_t *channel, ngx
static void static void
ngx_http_push_stream_worker_subscriber_cleanup(ngx_http_push_stream_worker_subscriber_t *worker_subscriber) ngx_http_push_stream_worker_subscriber_cleanup_locked(ngx_http_push_stream_worker_subscriber_t *worker_subscriber)
{ {
ngx_http_push_stream_subscription_t *cur, *sentinel; ngx_http_push_stream_subscription_t *cur, *sentinel;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
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;
ngx_shmtx_lock(&shpool->mutex);
sentinel = &worker_subscriber->subscriptions_sentinel; sentinel = &worker_subscriber->subscriptions_sentinel;
while ((cur = (ngx_http_push_stream_subscription_t *) ngx_queue_next(&sentinel->queue)) != sentinel) { while ((cur = (ngx_http_push_stream_subscription_t *) ngx_queue_next(&sentinel->queue)) != sentinel) {
...@@ -643,7 +756,6 @@ ngx_http_push_stream_worker_subscriber_cleanup(ngx_http_push_stream_worker_subsc ...@@ -643,7 +756,6 @@ ngx_http_push_stream_worker_subscriber_cleanup(ngx_http_push_stream_worker_subsc
worker_subscriber->clndata->worker_subscriber = NULL; worker_subscriber->clndata->worker_subscriber = NULL;
data->subscribers--; data->subscribers--;
(data->ipc + ngx_process_slot)->subscribers--; (data->ipc + ngx_process_slot)->subscribers--;
ngx_shmtx_unlock(&shpool->mutex);
} }
u_char * u_char *
...@@ -659,6 +771,7 @@ ngx_http_push_stream_append_crlf(const ngx_str_t *str, ngx_pool_t *pool) ...@@ -659,6 +771,7 @@ ngx_http_push_stream_append_crlf(const ngx_str_t *str, ngx_pool_t *pool)
return result; return result;
} }
static ngx_http_push_stream_content_subtype_t * static ngx_http_push_stream_content_subtype_t *
ngx_http_push_stream_match_channel_info_format_and_content_type(ngx_http_request_t *r, ngx_uint_t default_subtype) ngx_http_push_stream_match_channel_info_format_and_content_type(ngx_http_request_t *r, ngx_uint_t default_subtype)
{ {
......
...@@ -174,6 +174,7 @@ ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log, ngx_http_push_st ...@@ -174,6 +174,7 @@ ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log, ngx_http_push_st
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;
ngx_http_push_stream_channel_t *channel; ngx_http_push_stream_channel_t *channel;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr; ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
ngx_pool_t *temp_pool;
ngx_flag_t is_broadcast_channel = 0; ngx_flag_t is_broadcast_channel = 0;
channel = ngx_http_push_stream_find_channel(id, log); channel = ngx_http_push_stream_find_channel(id, log);
...@@ -200,6 +201,13 @@ ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log, ngx_http_push_st ...@@ -200,6 +201,13 @@ ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log, ngx_http_push_st
return NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED; return NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED;
} }
//create a temporary pool to allocate temporary elements
if ((temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log)) == NULL) {
ngx_log_error(NGX_LOG_ERR, log, 0, "push stream module: unable to allocate memory for temporary pool");
ngx_shmtx_unlock(&shpool->mutex);
return NULL;
}
if ((channel = ngx_slab_alloc_locked(shpool, sizeof(ngx_http_push_stream_channel_t))) == NULL) { if ((channel = ngx_slab_alloc_locked(shpool, sizeof(ngx_http_push_stream_channel_t))) == NULL) {
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
return NULL; return NULL;
...@@ -220,6 +228,8 @@ ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log, ngx_http_push_st ...@@ -220,6 +228,8 @@ ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log, ngx_http_push_st
ngx_http_push_stream_initialize_channel(channel); ngx_http_push_stream_initialize_channel(channel);
channel->channel_deleted_message = ngx_http_push_stream_convert_char_to_msg_on_shared_locked(NGX_HTTP_PUSH_STREAM_CHANNEL_DELETED_MESSAGE_TEXT.data, NGX_HTTP_PUSH_STREAM_CHANNEL_DELETED_MESSAGE_TEXT.len, channel, NGX_HTTP_PUSH_STREAM_CHANNEL_DELETED_MESSAGE_ID, temp_pool);
ngx_rbtree_insert(&data->tree, (ngx_rbtree_node_t *) channel); ngx_rbtree_insert(&data->tree, (ngx_rbtree_node_t *) channel);
(is_broadcast_channel) ? data->broadcast_channels++ : data->channels++; (is_broadcast_channel) ? data->broadcast_channels++ : data->channels++;
......
...@@ -147,6 +147,9 @@ module BaseTestCase ...@@ -147,6 +147,9 @@ module BaseTestCase
@memory_cleanup_timeout = '5m' @memory_cleanup_timeout = '5m'
@config_template = nil @config_template = nil
@keepalive = 'off' @keepalive = 'off'
@publisher_admin = 'off'
self.send(:global_configuration) if self.respond_to?(:global_configuration)
end end
def publish_message(channel, headers, body) def publish_message(channel, headers, body)
...@@ -238,6 +241,9 @@ http { ...@@ -238,6 +241,9 @@ http {
# activate publisher mode for this location # activate publisher mode for this location
push_stream_publisher; push_stream_publisher;
# activate publisher mode for this location
<%= "push_stream_publisher_admin #{@publisher_admin};" unless @publisher_admin.nil? %>
# query string based channel id # query string based channel id
set $push_stream_channel_id $arg_id; set $push_stream_channel_id $arg_id;
# store messages # store messages
......
require File.expand_path('base_test_case', File.dirname(__FILE__))
class TestPublisherAdmin < Test::Unit::TestCase
include BaseTestCase
def global_configuration
@publisher_admin = 'on'
end
def test_admin_access_whithout_channel_id
headers = {'accept' => 'application/json'}
EventMachine.run {
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=').get :head => headers, :timeout => 30
pub.callback {
assert_equal(0, pub.response_header.content_length, "Should response only with headers")
assert_equal(400, pub.response_header.status, "Request was not understood as a bad request")
assert_equal("No channel id provided.", pub.response_header['X_NGINX_PUSHSTREAM_EXPLAIN'], "Didn't receive the right error message")
EventMachine.stop
}
}
end
def test_admin_access_whith_channel_id_to_absent_channel
headers = {'accept' => 'application/json'}
channel_1 = 'ch_test_admin_access_whith_channel_id_to_absent_channel_1'
channel_2 = 'ch_test_admin_access_whith_channel_id_to_absent_channel_2'
body = 'body'
EventMachine.run {
pub_1 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel_1.to_s).get :head => headers, :timeout => 30
pub_1.callback {
assert_equal(404, pub_1.response_header.status, "Channel was founded")
assert_equal(0, pub_1.response_header.content_length, "Recieved a non empty response")
EventMachine.stop
}
}
EventMachine.run {
pub_2 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel_2.to_s ).post :head => headers, :body => body, :timeout => 30
pub_2.callback {
assert_equal(200, pub_2.response_header.status, "Request was not accepted")
assert_not_equal(0, pub_2.response_header.content_length, "Empty response was received")
response = JSON.parse(pub_2.response)
assert_equal(channel_2, response["channel"].to_s, "Channel was not recognized")
EventMachine.stop
}
}
end
def test_admin_access_whith_channel_id_to_existing_channel
headers = {'accept' => 'application/json'}
channel = 'ch_test_admin_access_whith_channel_id_to_existing_channel'
body = 'body'
#create channel
EventMachine.run {
pub_1 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s).post :head => headers, :body => body, :timeout => 30
pub_1.callback {
assert_equal(200, pub_1.response_header.status, "Request was not accepted")
assert_not_equal(0, pub_1.response_header.content_length, "Empty response was received")
response = JSON.parse(pub_1.response)
assert_equal(channel, response["channel"].to_s, "Channel was not recognized")
EventMachine.stop
}
}
EventMachine.run {
pub_2 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s).get :head => headers, :timeout => 30
pub_2.callback {
assert_equal(200, pub_2.response_header.status, "Request was not accepted")
assert_not_equal(0, pub_2.response_header.content_length, "Empty response was received")
response = JSON.parse(pub_2.response)
assert_equal(channel, response["channel"].to_s, "Channel was not recognized")
EventMachine.stop
}
}
end
def test_admin_accepted_methods
EventMachine.run {
multi = EventMachine::MultiRequest.new
multi.add(EventMachine::HttpRequest.new(nginx_address + '/pub?id=ch_test_admin_accepted_methods_1').get)
multi.add(EventMachine::HttpRequest.new(nginx_address + '/pub?id=ch_test_admin_accepted_methods_2').put :body => 'body')
multi.add(EventMachine::HttpRequest.new(nginx_address + '/pub?id=ch_test_admin_accepted_methods_3').post)
multi.add(EventMachine::HttpRequest.new(nginx_address + '/pub?id=ch_test_admin_accepted_methods_4').delete)
multi.add(EventMachine::HttpRequest.new(nginx_address + '/pub?id=ch_test_admin_accepted_methods_5').head)
multi.callback {
assert_equal(5, multi.responses[:succeeded].length)
assert_not_equal(405, multi.responses[:succeeded][0].response_header.status, "Publisher does accept GET")
assert_equal("GET", multi.responses[:succeeded][0].method, "Array is with wrong order")
assert_equal(405, multi.responses[:succeeded][1].response_header.status, "Publisher does not accept PUT")
assert_equal("GET, POST, DELETE", multi.responses[:succeeded][1].response_header['ALLOW'], "Didn't receive the right error message")
assert_equal("PUT", multi.responses[:succeeded][1].method, "Array is with wrong order")
assert_not_equal(405, multi.responses[:succeeded][2].response_header.status, "Publisher does accept POST")
assert_equal("POST", multi.responses[:succeeded][2].method, "Array is with wrong order")
assert_not_equal(405, multi.responses[:succeeded][3].response_header.status, "Publisher does accept DELETE")
assert_equal("DELETE", multi.responses[:succeeded][3].method, "Array is with wrong order")
assert_equal(405, multi.responses[:succeeded][4].response_header.status, "Publisher does not accept HEAD")
assert_equal("HEAD", multi.responses[:succeeded][4].method, "Array is with wrong order")
assert_equal("GET, POST, DELETE", multi.responses[:succeeded][4].response_header['ALLOW'], "Didn't receive the right error message")
EventMachine.stop
}
}
end
def test_admin_cannot_create_a_channel_with_id_ALL
headers = {'accept' => 'application/json'}
channel = 'ALL'
body = 'body'
EventMachine.run {
pub_1 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s).post :head => headers, :body => body, :timeout => 30
pub_1.callback {
assert_equal(403, pub_1.response_header.status, "Channel was created")
assert_equal(0, pub_1.response_header.content_length, "Received response for creating channel with id ALL")
assert_equal("Channel id not authorized for this method.", pub_1.response_header['X_NGINX_PUSHSTREAM_EXPLAIN'], "Didn't receive the right error message")
EventMachine.stop
}
}
end
def config_test_admin_post_message_larger_than_max_body_size_should_be_rejected
@client_max_body_size = '2k'
@client_body_buffer_size = '1k'
end
def test_admin_post_message_larger_than_max_body_size_should_be_rejected
headers = {'accept' => 'application/json'}
channel = 'ch_test_admin_post_message_larger_than_max_body_size_should_be_rejected'
body = '^'
(1..40).each do |n|
body += '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789|'
end
body += '$'
EventMachine.run {
pub_1 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s).post :head => headers, :body => body, :timeout => 30
pub_1.callback {
assert_equal(413, pub_1.response_header.status, "Request was accepted")
EventMachine.stop
}
}
end
def config_test_admin_post_message_larger_than_body_buffer_size_should_be_accepted
@client_max_body_size = '10k'
@client_body_buffer_size = '1k'
end
def test_admin_post_message_larger_than_body_buffer_size_should_be_accepted
headers = {'accept' => 'application/json'}
channel = 'ch_test_admin_post_message_larger_than_body_buffer_size_should_be_accepted'
body = '^'
(1..80).each do |n|
body += '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789|'
end
body += '$'
EventMachine.run {
pub_1 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s).post :head => headers, :body => body, :timeout => 30
pub_1.callback {
assert_equal(200, pub_1.response_header.status, "Request was not accepted")
EventMachine.stop
}
}
end
def config_test_admin_post_message_shorter_than_body_buffer_size_should_be_accepted
@client_max_body_size = '10k'
@client_body_buffer_size = '6k'
end
def test_admin_post_message_shorter_than_body_buffer_size_should_be_accepted
headers = {'accept' => 'application/json'}
channel = 'ch_test_admin_post_message_shorter_than_body_buffer_size_should_be_accepted'
body = '^'
(1..40).each do |n|
body += '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789|'
end
body += '$'
EventMachine.run {
pub_1 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s).post :head => headers, :body => body, :timeout => 30
pub_1.callback {
assert_equal(200, pub_1.response_header.status, "Request was not accepted")
EventMachine.stop
}
}
end
def config_test_admin_stored_messages
@store_messages = "on"
end
def test_admin_stored_messages
headers = {'accept' => 'application/json'}
body = 'published message'
channel = 'ch_test_admin_stored_messages'
EventMachine.run {
pub_1 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s ).post :head => headers, :body => body, :timeout => 30
pub_1.callback {
response = JSON.parse(pub_1.response)
assert_equal(1, response["stored_messages"].to_i, "Not stored messages")
pub_2 = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s ).post :head => headers, :body => body, :timeout => 30
pub_2.callback {
response = JSON.parse(pub_2.response)
assert_equal(2, response["stored_messages"].to_i, "Not stored messages")
EventMachine.stop
}
}
}
end
def config_test_admin_not_stored_messages
@store_messages = "off"
end
def test_admin_not_stored_messages
headers = {'accept' => 'application/json'}
body = 'published message'
channel = 'ch_test_admin_not_stored_messages'
EventMachine.run {
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s ).post :head => headers, :body => body, :timeout => 30
pub.callback {
response = JSON.parse(pub.response)
assert_equal(0, response["stored_messages"].to_i, "Stored messages")
EventMachine.stop
}
}
end
def config_test_admin_max_stored_messages
@store_messages = "on"
@max_message_buffer_length = 4
end
def test_admin_max_stored_messages
headers = {'accept' => 'application/json'}
body_prefix = 'published message '
channel = 'ch_test_admin_max_stored_messages'
messagens_to_publish = 10
EventMachine.run {
i = 0
stored_messages = 0
EM.add_periodic_timer(0.001) do
i += 1
if i <= messagens_to_publish
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s ).post :head => headers, :body => body_prefix + i.to_s, :timeout => 30
pub.callback {
response = JSON.parse(pub.response)
stored_messages = response["stored_messages"].to_i
}
else
EventMachine.stop
assert(stored_messages == @max_message_buffer_length, "Stored more messages then configured")
end
end
}
end
def config_test_admin_max_channel_id_length
@max_channel_id_length = 5
end
def test_admin_max_channel_id_length
headers = {'accept' => 'application/json'}
body = 'published message'
channel = '123456'
EventMachine.run {
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s ).post :head => headers, :body => body, :timeout => 30
pub.callback {
assert_equal(0, pub.response_header.content_length, "Should response only with headers")
assert_equal(400, pub.response_header.status, "Request was not understood as a bad request")
assert_equal("Channel id is too large.", pub.response_header['X_NGINX_PUSHSTREAM_EXPLAIN'], "Didn't receive the right error message")
EventMachine.stop
}
}
end
def config_test_admin_max_number_of_channels
@max_number_of_channels = 1
end
def test_admin_max_number_of_channels
headers = {'accept' => 'application/json'}
body = 'published message'
channel = 'ch_test_admin_max_number_of_channels_'
EventMachine.run {
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s + 1.to_s).post :head => headers, :body => body, :timeout => 30
pub.callback {
assert_equal(200, pub.response_header.status, "Channel was not created")
assert_not_equal(0, pub.response_header.content_length, "Should response channel info")
EventMachine.stop
}
}
EventMachine.run {
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s + 2.to_s).post :head => headers, :body => body, :timeout => 30
pub.callback {
assert_equal(403, pub.response_header.status, "Request was not forbidden")
assert_equal(0, pub.response_header.content_length, "Should response only with headers")
assert_equal("Number of channels were exceeded.", pub.response_header['X_NGINX_PUSHSTREAM_EXPLAIN'], "Didn't receive the right error message")
EventMachine.stop
}
}
end
def config_test_admin_max_number_of_broadcast_channels
@max_number_of_broadcast_channels = 1
@broadcast_channel_prefix = 'bd_'
@broadcast_channel_max_qtd = 1
end
def test_admin_max_number_of_broadcast_channels
headers = {'accept' => 'application/json'}
body = 'published message'
channel = 'bd_test_admin_max_number_of_broadcast_channels_'
EventMachine.run {
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s + 1.to_s).post :head => headers, :body => body, :timeout => 30
pub.callback {
assert_equal(200, pub.response_header.status, "Channel was not created")
assert_not_equal(0, pub.response_header.content_length, "Should response channel info")
EventMachine.stop
}
}
EventMachine.run {
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s + 2.to_s).post :head => headers, :body => body, :timeout => 30
pub.callback {
assert_equal(403, pub.response_header.status, "Request was not forbidden")
assert_equal(0, pub.response_header.content_length, "Should response only with headers")
assert_equal("Number of channels were exceeded.", pub.response_header['X_NGINX_PUSHSTREAM_EXPLAIN'], "Didn't receive the right error message")
EventMachine.stop
}
}
end
def test_delete_channel_whithout_subscribers
headers = {'accept' => 'application/json'}
body = 'published message'
channel = 'test_delete_channel_whithout_subscribers'
publish_message(channel, headers, body)
EventMachine.run {
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s).delete :head => headers, :timeout => 30
pub.callback {
assert_equal(200, pub.response_header.status, "Request was not received")
assert_equal(0, pub.response_header.content_length, "Should response only with headers")
assert_equal("Channel deleted.", pub.response_header['X_NGINX_PUSHSTREAM_EXPLAIN'], "Didn't receive the right error message")
stats = EventMachine::HttpRequest.new(nginx_address + '/channels-stats').get :head => headers, :timeout => 30
stats.callback {
assert_equal(200, stats.response_header.status, "Don't get channels statistics")
assert_not_equal(0, stats.response_header.content_length, "Don't received channels statistics")
begin
response = JSON.parse(stats.response)
assert(response.has_key?("channels"), "Didn't received the correct answer with channels info")
assert_equal(0, response["channels"].to_i, "Returned values with channels created")
rescue JSON::ParserError
fail("Didn't receive a valid response")
end
EventMachine.stop
}
}
}
end
def config_test_delete_channel_whith_subscriber_in_one_channel
@header_template = " " # send a space as header to has a chunk received
@ping_message_interval = nil
@message_template = '{\"id\":\"~id~\", \"channel\":\"~channel~\", \"text\":\"~text~\"}'
end
def test_delete_channel_whith_subscriber_in_one_channel
headers = {'accept' => 'application/json'}
body = 'published message'
channel = 'test_delete_channel_whith_subscriber_in_one_channel'
resp = ""
EventMachine.run {
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel.to_s).get :head => headers, :timeout => 30
sub_1.stream { |chunk|
resp = resp + chunk
if resp.strip.empty?
stats = EventMachine::HttpRequest.new(nginx_address + '/channels-stats').get :head => {'accept' => 'application/json'}, :timeout => 30
stats.callback {
assert_equal(200, stats.response_header.status, "Don't get channels statistics")
assert_not_equal(0, stats.response_header.content_length, "Don't received channels statistics")
begin
response = JSON.parse(stats.response)
assert_equal(1, response["subscribers"].to_i, "Subscriber was not created")
assert_equal(1, response["channels"].to_i, "Channel was not created")
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel.to_s).delete :head => headers, :timeout => 30
pub.callback {
assert_equal(200, pub.response_header.status, "Request was not received")
assert_equal(0, pub.response_header.content_length, "Should response only with headers")
assert_equal("Channel deleted.", pub.response_header['X_NGINX_PUSHSTREAM_EXPLAIN'], "Didn't receive the right error message")
}
rescue JSON::ParserError
fail("Didn't receive a valid response")
end
}
else
begin
response = JSON.parse(resp)
assert_equal(channel, response["channel"], "Wrong channel")
assert_equal(-2, response["id"].to_i, "Wrong message id")
stats = EventMachine::HttpRequest.new(nginx_address + '/channels-stats').get :head => {'accept' => 'application/json'}, :timeout => 30
stats.callback {
assert_equal(200, stats.response_header.status, "Don't get channels statistics")
assert_not_equal(0, stats.response_header.content_length, "Don't received channels statistics")
response = JSON.parse(stats.response)
assert_equal(0, response["subscribers"].to_i, "Subscriber was not deleted")
assert_equal(0, response["channels"].to_i, "Channel was not deleted")
}
rescue JSON::ParserError
fail("Didn't receive a valid response")
end
EventMachine.stop
end
}
}
end
def config_test_delete_channel_whith_subscriber_in_two_channels
@header_template = " " # send a space as header to has a chunk received
@ping_message_interval = nil
@message_template = '{\"id\":\"~id~\", \"channel\":\"~channel~\", \"text\":\"~text~\"}'
end
def test_delete_channel_whith_subscriber_in_two_channels
headers = {'accept' => 'application/json'}
body = 'published message'
channel_1 = 'test_delete_channel_whith_subscriber_in_two_channels_1'
channel_2 = 'test_delete_channel_whith_subscriber_in_two_channels_2'
stage1_complete = false
resp = ""
EventMachine.run {
sub_1 = EventMachine::HttpRequest.new(nginx_address + '/sub/' + channel_1.to_s + '/' + channel_2.to_s).get :head => headers, :timeout => 30
sub_1.stream { |chunk|
resp = resp + chunk
if resp.strip.empty?
stats = EventMachine::HttpRequest.new(nginx_address + '/channels-stats').get :head => {'accept' => 'application/json'}, :timeout => 30
stats.callback {
assert_equal(200, stats.response_header.status, "Don't get channels statistics")
assert_not_equal(0, stats.response_header.content_length, "Don't received channels statistics")
begin
response = JSON.parse(stats.response)
assert_equal(1, response["subscribers"].to_i, "Subscriber was not created")
assert_equal(2, response["channels"].to_i, "Channel was not created")
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel_1.to_s).delete :head => headers, :timeout => 30
pub.callback {
assert_equal(200, pub.response_header.status, "Request was not received")
assert_equal(0, pub.response_header.content_length, "Should response only with headers")
assert_equal("Channel deleted.", pub.response_header['X_NGINX_PUSHSTREAM_EXPLAIN'], "Didn't receive the right error message")
}
rescue JSON::ParserError
fail("Didn't receive a valid response")
end
}
else
begin
if !stage1_complete
stage1_complete = true
response = JSON.parse(resp)
assert_equal(channel_1, response["channel"], "Wrong channel")
assert_equal(-2, response["id"].to_i, "Wrong message id")
stats = EventMachine::HttpRequest.new(nginx_address + '/channels-stats').get :head => {'accept' => 'application/json'}, :timeout => 30
stats.callback {
assert_equal(200, stats.response_header.status, "Don't get channels statistics")
assert_not_equal(0, stats.response_header.content_length, "Don't received channels statistics")
response = JSON.parse(stats.response)
assert_equal(1, response["subscribers"].to_i, "Subscriber was not deleted")
assert_equal(1, response["channels"].to_i, "Channel was not deleted")
pub = EventMachine::HttpRequest.new(nginx_address + '/pub?id=' + channel_2.to_s).post :head => headers, :body=> body, :timeout => 30
pub.callback {
assert_equal(200, pub.response_header.status, "Request was not received")
}
}
else
response = JSON.parse(resp.split("\r\n")[2])
assert_equal(channel_2, response["channel"], "Wrong channel")
assert_equal(1, response["id"].to_i, "Wrong message id")
assert_equal(body, response["text"], "Wrong message id")
EventMachine.stop
end
rescue JSON::ParserError
EventMachine.stop
fail("Didn't receive a valid response")
end
end
}
}
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