Commit 2ea1ec09 authored by Wandenberg Peixoto's avatar Wandenberg Peixoto

refactoring the module: organizing code, improving treatment of memory and validations

parent aa417e95
/*
* ngx_http_push_stream_module.h
*
* Created on: Oct 26, 2010
* Authors: Wandenberg Peixoto <wandenberg@gmail.com> & Rogério Schneider <stockrt@gmail.com>
*/
#ifndef NGX_HTTP_PUSH_STREAM_MODULE_H_
#define NGX_HTTP_PUSH_STREAM_MODULE_H_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <nginx.h>
typedef struct {
size_t shm_size;
} ngx_http_push_stream_main_conf_t;
typedef struct {
ngx_int_t index_channel_id;
ngx_int_t index_channels_path;
time_t buffer_timeout;
ngx_uint_t max_messages;
ngx_uint_t authorized_channels_only;
ngx_uint_t store_messages;
ngx_uint_t max_channel_id_length;
ngx_str_t header_template;
ngx_str_t message_template;
ngx_str_t content_type;
ngx_msec_t ping_message_interval;
ngx_msec_t subscriber_disconnect_interval;
time_t subscriber_connection_timeout;
ngx_str_t broadcast_channel_prefix;
ngx_uint_t broadcast_channel_max_qtd;
ngx_uint_t max_number_of_channels;
ngx_uint_t max_number_of_broadcast_channels;
ngx_msec_t memory_cleanup_interval;
time_t memory_cleanup_timeout;
} ngx_http_push_stream_loc_conf_t;
// shared memory segment name
static ngx_str_t ngx_push_stream_shm_name = ngx_string("push_stream_module");
// message queue
typedef struct {
ngx_queue_t queue; // this MUST be first
ngx_buf_t *buf;
time_t expires;
ngx_flag_t deleted;
} ngx_http_push_stream_msg_t;
typedef struct ngx_http_push_stream_subscriber_cleanup_s ngx_http_push_stream_subscriber_cleanup_t;
// subscriber request queue
typedef struct {
ngx_queue_t queue; // this MUST be first
ngx_http_request_t *request;
} ngx_http_push_stream_subscriber_t;
typedef struct {
ngx_queue_t queue;
pid_t pid;
ngx_int_t slot;
ngx_http_push_stream_subscriber_t subscriber_sentinel;
} ngx_http_push_stream_pid_queue_t;
// our typecast-friendly rbtree node (channel)
typedef struct {
ngx_rbtree_node_t node; // this MUST be first
ngx_str_t id;
ngx_uint_t last_message_id;
ngx_uint_t stored_messages;
ngx_uint_t subscribers;
ngx_http_push_stream_pid_queue_t workers_with_subscribers;
ngx_http_push_stream_msg_t message_queue;
time_t expires;
ngx_flag_t deleted;
ngx_flag_t broadcast;
} ngx_http_push_stream_channel_t;
typedef struct {
ngx_queue_t queue;
ngx_str_t id;
ngx_uint_t published_messages;
ngx_uint_t stored_messages;
ngx_uint_t subscribers;
} ngx_http_push_stream_channel_info_t;
typedef struct {
ngx_queue_t queue;
ngx_http_push_stream_subscriber_t *subscriber;
ngx_http_push_stream_channel_t *channel;
} ngx_http_push_stream_subscription_t;
typedef struct {
ngx_queue_t queue; // this MUST be first
ngx_http_request_t *request;
ngx_http_push_stream_subscription_t subscriptions_sentinel;
ngx_http_push_stream_subscriber_cleanup_t *clndata;
ngx_pid_t worker_subscribed_pid;
time_t expires;
} ngx_http_push_stream_worker_subscriber_t;
// cleaning supplies
struct ngx_http_push_stream_subscriber_cleanup_s {
ngx_http_push_stream_worker_subscriber_t *worker_subscriber;
};
// messages to worker processes
typedef struct {
ngx_queue_t queue;
ngx_http_push_stream_msg_t *msg; // ->shared memory
ngx_pid_t pid;
ngx_http_push_stream_channel_t *channel; // ->shared memory
ngx_http_push_stream_subscriber_t *subscriber_sentinel; // ->a worker's local pool
} ngx_http_push_stream_worker_msg_t;
typedef struct {
ngx_http_push_stream_worker_msg_t messages_queue;
ngx_http_push_stream_worker_subscriber_t worker_subscribers_sentinel;
} ngx_http_push_stream_worker_data_t;
// shared memory
typedef struct {
ngx_rbtree_t tree;
ngx_uint_t channels; // # of channels being used
ngx_uint_t broadcast_channels; // # of broadcast channels being used
ngx_uint_t published_messages; // # of published messagens in all channels
ngx_uint_t subscribers; // # of subscribers in all channels
ngx_http_push_stream_msg_t messages_to_delete;
ngx_rbtree_t channels_to_delete;
ngx_http_push_stream_worker_data_t *ipc; // interprocess stuff
} ngx_http_push_stream_shm_data_t;
ngx_int_t ngx_http_push_stream_worker_processes;
ngx_shm_zone_t *ngx_http_push_stream_shm_zone = NULL;
// channel
static ngx_str_t * ngx_http_push_stream_get_channel_id(ngx_http_request_t *r, ngx_http_push_stream_loc_conf_t *cf);
static ngx_int_t ngx_http_push_stream_send_response_channel_info(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel);
static ngx_int_t ngx_http_push_stream_send_response_all_channels_info_summarized(ngx_http_request_t *r);
static ngx_int_t ngx_http_push_stream_send_response_all_channels_info_detailed(ngx_http_request_t *r);
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID = ngx_string("ALL");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE = ngx_string("No channel id provided.");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_NOT_AUTHORIZED_MESSAGE = ngx_string("Channel id not authorized for this method.");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_EMPTY_POST_REQUEST_MESSAGE = ngx_string("Empty post requests are not allowed.");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE = ngx_string("Channel id is too large.");
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_NUMBER_OF_CHANNELS_EXCEEDED_MESSAGE = ngx_string("Number of channels were exceeded.");
#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_NUMBER_OF_CHANNELS_EXCEEDED (void *) -3
static ngx_str_t NGX_HTTP_PUSH_STREAM_EMPTY = ngx_string("");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_BACKTRACK_SEP = ngx_string(".b");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_SLASH = ngx_string("/");
static const ngx_str_t NGX_PUSH_STREAM_DATE_FORMAT_ISO_8601 = ngx_string("%4d-%02d-%02dT%02d:%02d:%02d");
//// headers
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");
// other stuff
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOWED_METHODS = ngx_string("GET, POST");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOW_GET = ngx_string("GET");
#define NGX_HTTP_PUSH_STREAM_CHECK_AND_FINALIZE_REQUEST_ON_ERROR(val, fail, r, errormessage) \
if (val == fail) { \
ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, errormessage); \
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); \
return; \
}
#endif /* NGX_HTTP_PUSH_STREAM_MODULE_H_ */
/*
* ngx_http_push_stream_module_ipc.h
*
* Created on: Oct 26, 2010
* Authors: Wandenberg Peixoto <wandenberg@gmail.com> & Rogério Schneider <stockrt@gmail.com>
*/
#ifndef NGX_HTTP_PUSH_STREAM_MODULE_IPC_H_
#define NGX_HTTP_PUSH_STREAM_MODULE_IPC_H_
#include <ngx_http_push_stream_module.h>
#include <ngx_http_push_stream_module_subscriber.h>
#include <ngx_channel.h>
// constants
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_DISCONNECT_SUBSCRIBERS = {51, 0, 0, -1};
static ngx_channel_t NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS = {52, 0, 0, -1};
// worker processes of the world, unite.
ngx_socket_t ngx_http_push_stream_socketpairs[NGX_MAX_PROCESSES][2];
static void ngx_http_push_stream_broadcast(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg, ngx_log_t *log);
static ngx_int_t ngx_http_push_stream_alert_worker(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log, ngx_channel_t command);
#define ngx_http_push_stream_alert_worker_check_messages(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_CHECK_MESSAGES);
#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_census_subscribers(pid, slot, log) ngx_http_push_stream_alert_worker(pid, slot, log, NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS);
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_init_ipc(ngx_cycle_t *cycle, ngx_int_t workers);
static void ngx_http_push_stream_ipc_exit_worker(ngx_cycle_t *cycle);
static ngx_int_t ngx_http_push_stream_init_ipc_shm(ngx_int_t workers);
static void ngx_http_push_stream_channel_handler(ngx_event_t *ev);
static ngx_inline void ngx_http_push_stream_process_worker_message(void);
static ngx_inline void ngx_http_push_stream_send_worker_ping_message(void);
static ngx_inline void ngx_http_push_stream_disconnect_worker_subscribers(ngx_flag_t force_disconnect);
static ngx_inline void ngx_http_push_stream_census_worker_subscribers(void);
static ngx_int_t ngx_http_push_stream_respond_to_subscribers(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_subscriber_t *sentinel, ngx_http_push_stream_msg_t *msg);
#endif /* NGX_HTTP_PUSH_STREAM_MODULE_IPC_H_ */
/*
* ngx_http_push_stream_module_publisher.h
*
* Created on: Oct 26, 2010
* Authors: Wandenberg Peixoto <wandenberg@gmail.com> & Rogério Schneider <stockrt@gmail.com>
*/
#ifndef NGX_HTTP_PUSH_STREAM_MODULE_PUBLISHER_H_
#define NGX_HTTP_PUSH_STREAM_MODULE_PUBLISHER_H_
#include <ngx_http_push_stream_module.h>
static ngx_int_t push_stream_channels_statistics_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_push_stream_publisher_handler(ngx_http_request_t *r);
static void ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r);
#endif /* NGX_HTTP_PUSH_STREAM_MODULE_PUBLISHER_H_ */
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
#include <ngx_http_push_stream_module_publisher.h> #include <ngx_http_push_stream_module_publisher.h>
#include <ngx_http_push_stream_module_subscriber.h> #include <ngx_http_push_stream_module_subscriber.h>
//#define NGX_HTTP_PUSH_STREAM_DEFAULT_SHM_SIZE 33554432 // 32 megs #define NGX_HTTP_PUSH_STREAM_DEFAULT_SHM_SIZE 33554432 // 32 megs
static time_t NGX_HTTP_PUSH_STREAM_DEFAULT_MEMORY_CLEANUP_TIMEOUT = 30; // 30 seconds
#define NGX_HTTP_PUSH_STREAM_DEFAULT_HEADER_TEMPLATE "" #define NGX_HTTP_PUSH_STREAM_DEFAULT_HEADER_TEMPLATE ""
#define NGX_HTTP_PUSH_STREAM_DEFAULT_MESSAGE_TEMPLATE "" #define NGX_HTTP_PUSH_STREAM_DEFAULT_MESSAGE_TEMPLATE ""
...@@ -34,7 +35,7 @@ static char * ngx_http_push_stream_publisher(ngx_conf_t *cf, ngx_command_t ...@@ -34,7 +35,7 @@ static char * ngx_http_push_stream_publisher(ngx_conf_t *cf, ngx_command_t
static char * ngx_http_push_stream_subscriber(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char * ngx_http_push_stream_subscriber(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
// setup // setup
static char * ngx_http_push_stream_setup_handler(ngx_conf_t *cf, void *conf, ngx_int_t (*handler) (ngx_http_request_t *)); static char * ngx_http_push_stream_setup_handler(ngx_conf_t *cf, void *conf, ngx_int_t (*handler) (ngx_http_request_t *));
static ngx_int_t ngx_http_push_stream_init_module(ngx_cycle_t *cycle); static ngx_int_t ngx_http_push_stream_init_module(ngx_cycle_t *cycle);
static ngx_int_t ngx_http_push_stream_init_worker(ngx_cycle_t *cycle); static ngx_int_t ngx_http_push_stream_init_worker(ngx_cycle_t *cycle);
static void ngx_http_push_stream_exit_worker(ngx_cycle_t *cycle); static void ngx_http_push_stream_exit_worker(ngx_cycle_t *cycle);
...@@ -44,6 +45,8 @@ static void * ngx_http_push_stream_create_main_conf(ngx_conf_t *cf); ...@@ -44,6 +45,8 @@ static void * ngx_http_push_stream_create_main_conf(ngx_conf_t *cf);
static void * ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf); static void * ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf);
static char * ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static char * ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
static ngx_int_t ngx_http_push_stream_movezig_channel_locked(ngx_http_push_stream_channel_t *channel, ngx_slab_pool_t *shpool); // shared memory
static ngx_int_t ngx_http_push_stream_set_up_shm(ngx_conf_t *cf, size_t shm_size);
static ngx_int_t ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data);
#endif /* NGX_HTTP_PUSH_STREAM_MODULE_SETUP_H_ */ #endif /* NGX_HTTP_PUSH_STREAM_MODULE_SETUP_H_ */
/*
* ngx_http_push_stream_module_subscriber.h
*
* Created on: Oct 26, 2010
* Authors: Wandenberg Peixoto <wandenberg@gmail.com> & Rogério Schneider <stockrt@gmail.com>
*/
#ifndef NGX_HTTP_PUSH_STREAM_MODULE_SUBSCRIBER_H_
#define NGX_HTTP_PUSH_STREAM_MODULE_SUBSCRIBER_H_
typedef struct {
ngx_queue_t queue; // this MUST be first
ngx_str_t *id;
ngx_uint_t backtrack_messages;
} ngx_http_push_stream_requested_channel_t;
static ngx_int_t ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r);
ngx_http_push_stream_requested_channel_t * ngx_http_push_stream_parse_channels_ids_from_path(ngx_http_request_t *r, ngx_pool_t *pool);
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, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_pool_t *temp_pool);
#endif /* NGX_HTTP_PUSH_STREAM_MODULE_SUBSCRIBER_H_ */
/*
* ngx_http_push_stream_module_utils.h
*
* Created on: Oct 26, 2010
* Authors: Wandenberg Peixoto <wandenberg@gmail.com> & Rogério Schneider <stockrt@gmail.com>
*/
#ifndef NGX_HTTP_PUSH_STREAM_MODULE_UTILS_H_
#define NGX_HTTP_PUSH_STREAM_MODULE_UTILS_H_
#include <ngx_http_push_stream_module.h>
#include <ngx_http_push_stream_module_ipc.h>
typedef struct {
char *subtype;
size_t len;
ngx_str_t *content_type;
ngx_str_t *format_item;
ngx_str_t *format_group_head;
ngx_str_t *format_group_item;
ngx_str_t *format_group_last_item;
ngx_str_t *format_group_tail;
ngx_str_t *format_summarized;
} ngx_http_push_stream_content_subtype_t;
#define NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN_PATTERN "channel: %s" CRLF"published_messages: %ui" CRLF"stored_messages: %ui" CRLF"active_subscribers: %ui"
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_PLAIN = ngx_string("hostname: %s, time: %s, channels: %ui, broadcast_channels: %ui, infos: " CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_PLAIN = ngx_string(CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_PLAIN = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN_PATTERN "," CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_PLAIN = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_PLAIN = ngx_string("hostname: %s" CRLF "time: %s" CRLF "channels: %ui" CRLF "broadcast_channels: %ui" CRLF "published_messages: %ui" CRLF "subscribers: %ui" CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_PLAIN = ngx_string("text/plain");
#define NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON_PATTERN "{\"channel\": \"%s\", \"published_messages\": \"%ui\", \"stored_messages\": \"%ui\", \"subscribers\": \"%ui\"}"
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_JSON = ngx_string("{\"hostname\": \"%s\", \"time\": \"%s\", \"channels\": \"%ui\", \"broadcast_channels\": \"%ui\", \"infos\": [" CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_JSON = ngx_string("]}" CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_JSON = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON_PATTERN "," CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_JSON = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_JSON = ngx_string("{\"hostname\": \"%s\", \"time\": \"%s\", \"channels\": \"%ui\", \"broadcast_channels\": \"%ui\", \"published_messages\": \"%ui\", \"subscribers\": \"%ui\"}" CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_JSON = ngx_string("application/json");
static ngx_str_t NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_X_JSON = ngx_string("text/x-json");
#define NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML_PATTERN " channel: %s" CRLF" published_messages: %ui" CRLF" stored_messages: %ui" CRLF" subscribers: %ui"
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_YAML = ngx_string("hostname: %s" CRLF"time: %s" CRLF"channels: %ui" CRLF"broadcast_channels: %ui" CRLF"infos: "CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_YAML = ngx_string(CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_YAML = ngx_string(" -" CRLF NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_YAML = ngx_string(" -" CRLF NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_YAML = ngx_string(" hostname: %s" CRLF" time: %s" CRLF" channels: %ui" CRLF" broadcast_channels: %ui" CRLF" published_messages: %ui" CRLF" subscribers: %ui" CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_YAML = ngx_string("application/yaml");
static ngx_str_t NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_X_YAML = ngx_string("text/x-yaml");
#define NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_XML_PATTERN \
"<channel>" CRLF \
" <name>%s</name>" CRLF \
" <published_messages>%ui</published_messages>" CRLF \
" <stored_messages>%ui</stored_messages>" CRLF \
" <subscribers>%ui</subscribers>" CRLF \
"</channel>" CRLF
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_XML = ngx_string("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" CRLF NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_XML_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_XML = ngx_string("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" CRLF "<root>" CRLF" <hostname>%s</hostname>" CRLF" <time>%s</time>" CRLF" <channels>%ui</channels>" CRLF" <broadcast_channels>%ui</broadcast_channels>" CRLF" <infos>" CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_XML = ngx_string(" </infos>" CRLF"</root>" CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_XML = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_XML_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_XML = ngx_string(NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_XML_PATTERN CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_XML = ngx_string(
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" CRLF \
"<infos>" CRLF \
" <hostname>%s</hostname>" CRLF \
" <time>%s</time>" CRLF \
" <channels>%ui</channels>" CRLF \
" <broadcast_channels>%ui</broadcast_channels>" CRLF \
" <published_messages>%ui</published_messages>" CRLF \
" <subscribers>%ui</subscribers>" CRLF\
"</infos>" CRLF);
static ngx_str_t NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_XML = ngx_string("application/xml");
static ngx_http_push_stream_content_subtype_t subtypes[] = {
{ "plain" , 5,
&NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_PLAIN,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_PLAIN,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_PLAIN,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_PLAIN,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_PLAIN,
&NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_PLAIN },
{ "json" , 4,
&NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_JSON },
{ "yaml" , 4,
&NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_YAML },
{ "xml" , 3,
&NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_XML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_XML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_XML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_XML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_XML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_XML,
&NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_XML },
{ "x-json", 6,
&NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_X_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_JSON,
&NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_JSON },
{ "x-yaml", 6,
&NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_X_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_ITEM_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_LAST_ITEM_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_YAML,
&NGX_HTTP_PUSH_STREAM_CHANNELS_INFO_SUMMARIZED_YAML }
};
static const ngx_str_t NGX_PUSH_STREAM_PING_MESSAGE_ID = ngx_string("-1");
static const ngx_str_t NGX_PUSH_STREAM_PING_MESSAGE_TEXT = ngx_string("");
static const ngx_str_t NGX_PUSH_STREAM_PING_CHANNEL_ID = ngx_string("");
static const ngx_str_t NGX_PUSH_STREAM_TOKEN_MESSAGE_ID = ngx_string("~id~");
static const ngx_str_t NGX_PUSH_STREAM_TOKEN_MESSAGE_CHANNEL = ngx_string("~channel~");
static const ngx_str_t NGX_PUSH_STREAM_TOKEN_MESSAGE_TEXT = ngx_string("~text~");
ngx_event_t ngx_http_push_stream_ping_event;
ngx_event_t ngx_http_push_stream_disconnect_event;
ngx_event_t ngx_http_push_stream_memory_cleanup_event;
ngx_buf_t *ngx_http_push_stream_ping_buf = NULL;
// general request handling
ngx_http_push_stream_msg_t *ngx_http_push_stream_convert_buffer_to_msg_on_shared_locked(ngx_buf_t *buf);
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_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 u_char * ngx_http_push_stream_str_replace(u_char *org, u_char *find, u_char *replace, ngx_pool_t *temp_pool);
static ngx_buf_t * ngx_http_push_stream_get_formatted_message(ngx_http_push_stream_loc_conf_t *pslcf, ngx_http_push_stream_channel_t *channel, ngx_buf_t *buf, ngx_pool_t *temp_pool);
static ngx_int_t ngx_http_push_stream_send_response_content_header(ngx_http_request_t *r, ngx_http_push_stream_loc_conf_t *pslcf);
static ngx_int_t ngx_http_push_stream_send_response_chunk(ngx_http_request_t *r, const ngx_str_t *chunk_text, ngx_flag_t last_buffer);
static ngx_int_t ngx_http_push_stream_send_ping(ngx_log_t *log, ngx_http_push_stream_loc_conf_t *pslcf);
static ngx_int_t ngx_http_push_stream_memory_cleanup(ngx_log_t *log, ngx_http_push_stream_loc_conf_t *pslcf);
static void ngx_http_push_stream_ping_timer_wake_handler(ngx_event_t *ev);
static void ngx_http_push_stream_ping_timer_set(ngx_http_push_stream_loc_conf_t *pslcf);
static void ngx_http_push_stream_disconnect_timer_wake_handler(ngx_event_t *ev);
static void ngx_http_push_stream_disconnect_timer_set(ngx_http_push_stream_loc_conf_t *pslcf);
static void ngx_http_push_stream_memory_cleanup_timer_wake_handler(ngx_event_t *ev);
static void ngx_http_push_stream_memory_cleanup_timer_set(ngx_http_push_stream_loc_conf_t *pslcf);
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);
u_char * ngx_http_push_stream_append_crlf(const ngx_str_t *str, ngx_pool_t *pool);
static void ngx_http_push_stream_collect_expired_messages_and_empty_channels(ngx_rbtree_t *tree, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force, time_t memory_cleanup_timeout);
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, time_t memory_cleanup_timeout);
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_str_t * ngx_http_push_stream_get_formatted_current_time(ngx_pool_t *pool);
#endif /* NGX_HTTP_PUSH_STREAM_MODULE_UTILS_H_ */
/*
* ngx_http_push_stream_rbtree_util.h
*
* Created on: Oct 26, 2010
* Authors: Wandenberg Peixoto <wandenberg@gmail.com> & Rogério Schneider <stockrt@gmail.com>
*/
#ifndef NGX_HTTP_PUSH_STREAM_RBTREE_UTIL_H_
#define NGX_HTTP_PUSH_STREAM_RBTREE_UTIL_H_
static ngx_http_push_stream_channel_t * ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log, ngx_http_push_stream_loc_conf_t *cf);
static ngx_http_push_stream_channel_t * ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log);
static void ngx_rbtree_generic_insert(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel, int (*compare) (const ngx_rbtree_node_t *left, const ngx_rbtree_node_t *right));
static void ngx_http_push_stream_rbtree_insert(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
static int ngx_http_push_stream_compare_rbtree_node(const ngx_rbtree_node_t *v_left, const ngx_rbtree_node_t *v_right);
#endif /* NGX_HTTP_PUSH_STREAM_RBTREE_UTIL_H_ */
pid logs/nginx.pid; pid logs/nginx.pid;
error_log logs/nginx-main_error.log; error_log logs/nginx-main_error.log debug;
# Development Mode
master_process off;
daemon off;
worker_processes 2; worker_processes 2;
events { events {
worker_connections 1024; worker_connections 1024;
use epoll;
} }
http { http {
...@@ -11,7 +15,7 @@ http { ...@@ -11,7 +15,7 @@ http {
default_type application/octet-stream; default_type application/octet-stream;
access_log logs/nginx-http_access.log; access_log logs/nginx-http_access.log;
error_log logs/nginx-http_error.log; error_log logs/nginx-http_error.log debug;
tcp_nopush on; tcp_nopush on;
tcp_nodelay on; tcp_nodelay on;
...@@ -26,11 +30,20 @@ http { ...@@ -26,11 +30,20 @@ http {
client_body_buffer_size 1k; client_body_buffer_size 1k;
ignore_invalid_headers on; ignore_invalid_headers on;
client_body_in_single_buffer on; client_body_in_single_buffer on;
push_stream_max_reserved_memory 10m;
server { server {
listen 80; listen 80;
server_name localhost; server_name localhost;
location /channels_stats {
# activate channels statistics mode for this location
push_stream_channels_statistics;
# query string based channel id
set $push_stream_channel_id $arg_id;
}
location /pub { location /pub {
# activate publisher mode for this location # activate publisher mode for this location
push_stream_publisher; push_stream_publisher;
...@@ -39,11 +52,15 @@ http { ...@@ -39,11 +52,15 @@ http {
set $push_stream_channel_id $arg_id; set $push_stream_channel_id $arg_id;
# message template # message template
push_stream_message_template "<script>p(~id~,'~channel~','~text~');</script>"; push_stream_message_template "<script>p(~id~,'~channel~','~text~');</script>";
# store messages
push_stream_store_messages on;
# max messages to store in memory # max messages to store in memory
push_stream_max_message_buffer_length 20; push_stream_max_message_buffer_length 20;
# message ttl # message ttl
push_stream_min_message_buffer_timeout 5m; push_stream_min_message_buffer_timeout 5m;
push_stream_max_channel_id_length 200;
# client_max_body_size MUST be equal to client_body_buffer_size or # client_max_body_size MUST be equal to client_body_buffer_size or
# you will be sorry. # you will be sorry.
client_max_body_size 32k; client_max_body_size 32k;
...@@ -56,6 +73,7 @@ http { ...@@ -56,6 +73,7 @@ http {
# positional channel path # positional channel path
set $push_stream_channels_path $1; set $push_stream_channels_path $1;
push_stream_max_channel_id_length 200;
# header to be sent when receiving new subscriber connection # header to be sent when receiving new subscriber connection
push_stream_header_template "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n<meta http-equiv=\"Cache-Control\" content=\"no-store\">\r\n<meta http-equiv=\"Cache-Control\" content=\"no-cache\">\r\n<meta http-equiv=\"Pragma\" content=\"no-cache\">\r\n<meta http-equiv=\"Expires\" content=\"Thu, 1 Jan 1970 00:00:00 GMT\">\r\n<script type=\"text/javascript\">\r\nwindow.onError = null;\r\ndocument.domain = 'localhost';\r\nparent.PushStream.register(this);\r\n</script>\r\n</head>\r\n<body onload=\"try { parent.PushStream.reset(this) } catch (e) {}\">"; push_stream_header_template "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n<meta http-equiv=\"Cache-Control\" content=\"no-store\">\r\n<meta http-equiv=\"Cache-Control\" content=\"no-cache\">\r\n<meta http-equiv=\"Pragma\" content=\"no-cache\">\r\n<meta http-equiv=\"Expires\" content=\"Thu, 1 Jan 1970 00:00:00 GMT\">\r\n<script type=\"text/javascript\">\r\nwindow.onError = null;\r\ndocument.domain = 'localhost';\r\nparent.PushStream.register(this);\r\n</script>\r\n</head>\r\n<body onload=\"try { parent.PushStream.reset(this) } catch (e) {}\">";
# message template # message template
...@@ -71,6 +89,12 @@ http { ...@@ -71,6 +89,12 @@ http {
push_stream_subscriber_disconnect_interval 30s; push_stream_subscriber_disconnect_interval 30s;
# connection ttl to enable recycle # connection ttl to enable recycle
push_stream_subscriber_connection_timeout 15m; push_stream_subscriber_connection_timeout 15m;
push_stream_broadcast_channel_prefix "broad_";
push_stream_broadcast_channel_max_qtd 3;
# solving some leakage problem with persitent connections in # solving some leakage problem with persitent connections in
# Nginx's chunked filter (ngx_http_chunked_filter_module.c) # Nginx's chunked filter (ngx_http_chunked_filter_module.c)
chunked_transfer_encoding off; chunked_transfer_encoding off;
......
...@@ -6,342 +6,254 @@ ...@@ -6,342 +6,254 @@
#include <ngx_http_push_stream_module_publisher.c> #include <ngx_http_push_stream_module_publisher.c>
#include <ngx_http_push_stream_module_subscriber.c> #include <ngx_http_push_stream_module_subscriber.c>
static void
ngx_http_push_stream_send_response_channel_id_not_provided(ngx_http_request_t *r)
{
ngx_buf_t *buf = ngx_create_temp_buf(r->pool, 0);
ngx_chain_t *chain;
if (buf != NULL) {
buf->pos = (u_char *) NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE;
buf->last = buf->pos + sizeof(NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE) - 1;
buf->start = buf->pos;
buf->end = buf->last;
chain = ngx_http_push_stream_create_output_chain(buf, r->pool, r->connection->log);
chain->buf->last_buf = 1;
r->headers_out.content_length_n = ngx_buf_size(buf);
r->headers_out.status = NGX_HTTP_NOT_FOUND;
r->headers_out.content_type = NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_TEXT_PLAIN;
ngx_http_send_header(r);
ngx_http_output_filter(r, chain);
ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: the $push_stream_channel_id variable is required but is not set");
}
}
static ngx_str_t * static ngx_str_t *
ngx_http_push_stream_get_channel_id(ngx_http_request_t *r, ngx_http_push_stream_loc_conf_t *cf) ngx_http_push_stream_get_channel_id(ngx_http_request_t *r, ngx_http_push_stream_loc_conf_t *cf)
{ {
ngx_http_variable_value_t *vv = ngx_http_get_indexed_variable(r, cf->index_channel_id); ngx_http_variable_value_t *vv = ngx_http_get_indexed_variable(r, cf->index_channel_id);
size_t len;
ngx_str_t *id; ngx_str_t *id;
if (vv == NULL || vv->not_found || vv->len == 0) { if (vv == NULL || vv->not_found || vv->len == 0) {
ngx_http_push_stream_send_response_channel_id_not_provided(r); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: the $push_stream_channel_id variable is required but is not set");
return NULL; return NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID;
} }
// maximum length limiter for channel id // maximum length limiter for channel id
len = vv->len <= cf->max_channel_id_length ? vv->len : cf->max_channel_id_length; if ((cf->max_channel_id_length != NGX_CONF_UNSET_UINT) && (vv->len > cf->max_channel_id_length)) {
ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: channel id is larger than allowed %d", vv->len);
return NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID;
}
if ((id = ngx_pcalloc(r->pool, sizeof(*id) + len + 1)) == NULL) { if ((id = ngx_pcalloc(r->pool, sizeof(ngx_str_t) + vv->len)) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for $push_stream_channel_id string"); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for $push_stream_channel_id string");
return NULL; return NULL;
} }
id->len = len;
id->data = (u_char *) (id + 1); id->data = (u_char *) (id + 1);
ngx_memcpy(id->data, vv->data, len); id->len = vv->len;
ngx_memcpy(id->data, vv->data, vv->len);
return id; return id;
} }
static void
ngx_http_push_stream_match_channel_info_subtype(size_t off, u_char *cur, size_t rem, u_char **priority, const ngx_str_t **format, ngx_str_t *content_type)
{
static ngx_http_push_stream_content_subtype_t subtypes[] = {
{ "json" , 4, &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON },
{ "yaml" , 4, &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML },
{ "xml" , 3, &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_XML },
{ "x-json", 6, &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON },
{ "x-yaml", 6, &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML }
};
u_char *start = cur + off;
ngx_uint_t i;
for(i=0; i<(sizeof(subtypes) / sizeof(ngx_http_push_stream_content_subtype_t)); i++) {
if (ngx_strncmp(start, subtypes[i].subtype, rem<subtypes[i].len ? rem : subtypes[i].len) == 0) {
if (*priority > start) {
*format = subtypes[i].format;
*priority = start;
content_type->data = cur;
content_type->len = off + 1 + subtypes[i].len;
}
}
}
}
static ngx_buf_t * static ngx_buf_t *
ngx_http_push_stream_channel_info_formatted(ngx_pool_t *pool, ngx_str_t channelId, ngx_uint_t published_messages, ngx_uint_t stored_messages, ngx_uint_t subscribers, const ngx_str_t *format) ngx_http_push_stream_channel_info_formatted(ngx_pool_t *pool, const ngx_str_t *format, ngx_str_t *id, ngx_uint_t published_messages, ngx_uint_t stored_messages, ngx_uint_t subscribers)
{ {
ngx_buf_t *b; ngx_buf_t *b;
ngx_uint_t len; ngx_uint_t len;
if ((format == NULL) || (id == NULL)) {
return NULL;
}
len = channelId.len + 3*NGX_INT_T_LEN + format->len - 8; // minus 8 sprintf len = 3*NGX_INT_T_LEN + format->len + id->len - 11;// minus 11 sprintf
if ((b = ngx_create_temp_buf(pool, len)) == NULL) { if ((b = ngx_create_temp_buf(pool, len)) == NULL) {
return NULL; return NULL;
} }
ngx_memset(b->start, '\0', len); ngx_memset(b->start, '\0', len);
b->last = ngx_sprintf(b->start, (char *) format->data, channelId.data, published_messages, stored_messages, subscribers); b->last = ngx_sprintf(b->start, (char *) format->data, id->data, published_messages, stored_messages, subscribers);
b->memory = 1;
return b; return b;
} }
// print information about a channel
static ngx_int_t static ngx_int_t
ngx_http_push_stream_channel_info(ngx_http_request_t *r, ngx_str_t channelId, ngx_uint_t published_messages, ngx_uint_t stored_messages, ngx_uint_t subscribers) ngx_http_push_stream_send_buf_response(ngx_http_request_t *r, ngx_buf_t *buf, const ngx_str_t *content_type, ngx_int_t status_code)
{ {
ngx_buf_t *b; ngx_chain_t *chain;
ngx_str_t content_type = NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_TEXT_PLAIN; ngx_int_t rc;
const ngx_str_t *format = &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN;
ngx_int_t rc; if ((r == NULL) || (buf == NULL) || (content_type == NULL)) {
ngx_chain_t *chain; return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (r->headers_in.accept) {
// lame content-negotiation (without regard for qvalues)
u_char *accept = r->headers_in.accept->value.data;
size_t len = r->headers_in.accept->value.len;
size_t rem;
u_char *cur = accept;
u_char *priority = &accept[len - 1];
for(rem=len; (cur = ngx_strnstr(cur, "text/", rem)) != NULL; cur += sizeof("text/") - 1) {
rem = len - ((size_t) (cur-accept) + sizeof("text/") - 1);
if (ngx_strncmp(cur + sizeof("text/") - 1, "plain", rem < 5 ? rem : 5) == 0) {
if (priority) {
format = &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN;
priority = cur + sizeof("text/") - 1;
// content-type is already set by default
}
}
ngx_http_push_stream_match_channel_info_subtype(sizeof("text/") - 1, cur, rem, &priority, &format, &content_type);
}
cur = accept; r->headers_out.content_type.len = content_type->len;
r->headers_out.content_type.data = content_type->data;
r->headers_out.content_length_n = ngx_buf_size(buf);
for(rem=len; (cur = ngx_strnstr(cur, "application/", rem)) != NULL; cur += sizeof("application/") - 1) { if ((chain = ngx_pcalloc(r->pool, sizeof(ngx_chain_t))) == NULL) {
rem = len - ((size_t) (cur-accept) + sizeof("application/") - 1); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for send buf response");
ngx_http_push_stream_match_channel_info_subtype(sizeof("application/") - 1, cur, rem, &priority, &format, &content_type); return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
} }
r->headers_out.content_type.len = content_type.len; chain->buf = buf;
r->headers_out.content_type.data = content_type.data; chain->next = NULL;
buf->memory = 1;
buf->last_buf = 1;
r->keepalive = 0;
r->headers_out.status = status_code;
ngx_http_discard_request_body(r);
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
rc = ngx_http_output_filter(r, chain);
return rc;
}
if ((b = ngx_http_push_stream_channel_info_formatted(r->pool, channelId, published_messages, stored_messages, subscribers, format)) == NULL) { // print information about a channel
static ngx_int_t
ngx_http_push_stream_send_response_channel_info(ngx_http_request_t *r, ngx_http_push_stream_channel_t *channel)
{
ngx_buf_t *b;
ngx_http_push_stream_content_subtype_t *subtype;
subtype = ngx_http_push_stream_match_channel_info_format_and_content_type(r, 1);
b = ngx_http_push_stream_channel_info_formatted(r->pool, subtype->format_item, &channel->id, channel->last_message_id, channel->stored_messages, channel->subscribers);
if (b == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }
// lastly, set the content-length, because if the status code isn't 200, nginx may not do so automatically return ngx_http_push_stream_send_buf_response(r, b, subtype->content_type, NGX_HTTP_OK);
r->headers_out.content_length_n = ngx_buf_size(b); }
static ngx_int_t
ngx_http_push_stream_send_response_all_channels_info_summarized(ngx_http_request_t *r) {
ngx_buf_t *b;
ngx_uint_t len;
ngx_str_t *currenttime;
ngx_http_push_stream_shm_data_t *shm_data;
ngx_http_push_stream_content_subtype_t *subtype;
subtype = ngx_http_push_stream_match_channel_info_format_and_content_type(r, 1);
currenttime = ngx_http_push_stream_get_formatted_current_time(r->pool);
shm_data = (ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data;
len = 3*NGX_INT_T_LEN + subtype->format_summarized->len + ngx_cycle->hostname.len + currenttime->len - 16;// minus 16 sprintf
if (ngx_http_send_header(r) > NGX_HTTP_SPECIAL_RESPONSE) { if ((b = ngx_create_temp_buf(r->pool, len)) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }
chain = ngx_http_push_stream_create_output_chain(b, r->pool, r->connection->log); ngx_memset(b->start, '\0', len);
rc = ngx_http_output_filter(r, chain); b->last = ngx_sprintf(b->start, (char *) subtype->format_summarized->data, ngx_cycle->hostname.data, currenttime->data, shm_data->channels, shm_data->broadcast_channels, shm_data->published_messages, shm_data->subscribers);
return rc; return ngx_http_push_stream_send_buf_response(r, b, subtype->content_type, NGX_HTTP_OK);
} }
static void static void
ngx_http_push_stream_rbtree_walker_channel_info_locked(ngx_rbtree_t *tree, ngx_pool_t *pool, ngx_rbtree_node_t *node, ngx_queue_t *queue_channel_info, const ngx_str_t *format) ngx_http_push_stream_rbtree_walker_channel_info_locked(ngx_rbtree_t *tree, ngx_pool_t *pool, ngx_rbtree_node_t *node, ngx_queue_t *queue_channel_info)
{ {
ngx_rbtree_node_t *sentinel = tree->sentinel; ngx_rbtree_node_t *sentinel = tree->sentinel;
if (node != sentinel) { if (node != sentinel) {
ngx_http_push_stream_channel_t *channel = (ngx_http_push_stream_channel_t *) node; ngx_http_push_stream_channel_t *channel = (ngx_http_push_stream_channel_t *) node;
ngx_http_push_stream_msg_t *msg; ngx_http_push_stream_channel_info_t *channel_info;
if ((msg = ngx_pcalloc(pool, sizeof(ngx_http_push_stream_msg_t))) == NULL) { if ((channel_info = ngx_pcalloc(pool, sizeof(ngx_http_push_stream_channel_info_t))) == NULL) {
return; return;
} }
if ((msg->buf = ngx_http_push_stream_channel_info_formatted(pool, channel->id, channel->last_message_id, channel->stored_messages, channel->subscribers, format)) == NULL) { channel_info->id.data = channel->id.data;
return; channel_info->id.len = channel->id.len;
} channel_info->published_messages = channel->last_message_id;
channel_info->stored_messages = channel->stored_messages;
channel_info->subscribers = channel->subscribers;
ngx_queue_insert_tail(queue_channel_info, &msg->queue); ngx_queue_insert_tail(queue_channel_info, &channel_info->queue);
if (node->left != NULL) { if (node->left != NULL) {
ngx_http_push_stream_rbtree_walker_channel_info_locked(tree, pool, node->left, queue_channel_info, format); ngx_http_push_stream_rbtree_walker_channel_info_locked(tree, pool, node->left, queue_channel_info);
} }
if (node->right != NULL) { if (node->right != NULL) {
ngx_http_push_stream_rbtree_walker_channel_info_locked(tree, pool, node->right, queue_channel_info, format); ngx_http_push_stream_rbtree_walker_channel_info_locked(tree, pool, node->right, queue_channel_info);
} }
} }
} }
// print information about all channels
static ngx_int_t static ngx_int_t
ngx_http_push_stream_all_channels_info(ngx_http_request_t *r) ngx_http_push_stream_send_response_all_channels_info_detailed(ngx_http_request_t *r) {
{ ngx_int_t rc;
ngx_buf_t *b; ngx_chain_t *chain;
ngx_uint_t len = 0; ngx_str_t *currenttime;
ngx_str_t content_type = NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_JSON; ngx_str_t header_response;
const ngx_str_t *format = &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON; ngx_queue_t queue_channel_info;
const ngx_str_t *head = &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_JSON; ngx_queue_t *cur, *next;
const ngx_str_t *tail = &NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_JSON; ngx_http_push_stream_shm_data_t *shm_data;
ngx_chain_t *chain; ngx_slab_pool_t *shpool;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
ngx_rbtree_t *tree = &((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->tree;
ngx_queue_t queue_channel_info;
ngx_queue_t *cur;
ngx_tm_t tm;
u_char currenttime[20];
u_char hostname[ngx_cycle->hostname.len + 1];
r->headers_out.content_type.len = content_type.len;
r->headers_out.content_type.data = content_type.data;
ngx_queue_init(&queue_channel_info); ngx_http_push_stream_content_subtype_t *subtype;
ngx_shmtx_lock(&shpool->mutex); subtype = ngx_http_push_stream_match_channel_info_format_and_content_type(r, 1);
ngx_http_push_stream_rbtree_walker_channel_info_locked(tree, r->pool, tree->root, &queue_channel_info, format);
ngx_shmtx_unlock(&shpool->mutex);
if ((chain = ngx_pcalloc(r->pool, sizeof(*chain))) == NULL) { const ngx_str_t *format;
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for response channels info"); const ngx_str_t *head = subtype->format_group_head;
return NGX_HTTP_INTERNAL_SERVER_ERROR; const ngx_str_t *tail = subtype->format_group_tail;
}
ngx_gmtime(ngx_time(), &tm); shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
ngx_sprintf(currenttime, (char *) NGX_PUSH_STREAM_DATE_FORMAT_ISO_8601.data, tm.ngx_tm_year, tm.ngx_tm_mon, tm.ngx_tm_mday, tm.ngx_tm_hour, tm.ngx_tm_min, tm.ngx_tm_sec); shm_data = (ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data;
currenttime[19] = '\0';
ngx_memcpy(hostname, (char *) ngx_cycle->hostname.data, ngx_cycle->hostname.len); r->headers_out.content_type.len = subtype->content_type->len;
hostname[ngx_cycle->hostname.len] = '\0'; r->headers_out.content_type.data = subtype->content_type->data;
r->headers_out.content_length_n = -1;
r->headers_out.status = NGX_HTTP_OK;
if ((b = ngx_create_temp_buf(r->pool, head->len + sizeof(hostname) + sizeof(currenttime))) == NULL) { ngx_http_discard_request_body(r);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for response channels info head/tail");
return NGX_HTTP_INTERNAL_SERVER_ERROR; rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
} }
b->last = ngx_sprintf(b->last, (char *) head->data, hostname, currenttime); // this method send the response as a streaming cause the content could be very big
r->keepalive = 1;
// calculates the size required to send the information from each channel ngx_queue_init(&queue_channel_info);
cur = ngx_queue_head(&queue_channel_info);
while (cur != &queue_channel_info) { ngx_shmtx_lock(&shpool->mutex);
ngx_http_push_stream_msg_t *msg = (ngx_http_push_stream_msg_t *) cur; ngx_http_push_stream_rbtree_walker_channel_info_locked(&shm_data->tree, r->pool, shm_data->tree.root, &queue_channel_info);
len += ngx_buf_size(msg->buf) + NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_JSON.len; ngx_shmtx_unlock(&shpool->mutex);
cur = ngx_queue_next(cur);
}
// sum the size of tail and formatted head messages
len += tail->len + ngx_buf_size(b);
// lastly, set the content-length, because if the status code isn't 200, nginx may not do so automatically // get formatted current time
r->headers_out.content_length_n = len; currenttime = ngx_http_push_stream_get_formatted_current_time(r->pool);
if (ngx_http_send_header(r) > NGX_HTTP_SPECIAL_RESPONSE) { // send content header
if ((header_response.data = ngx_pcalloc(r->pool, head->len + ngx_cycle->hostname.len + currenttime->len + 1)) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for response channels info");
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }
// send the head message ngx_sprintf(header_response.data, (char *) head->data, ngx_cycle->hostname.data, currenttime->data, shm_data->channels, shm_data->broadcast_channels);
b->last_buf = 1; header_response.len = ngx_strlen(header_response.data);
b->memory = 1; ngx_http_push_stream_send_response_chunk(r, &header_response, 0);
chain->buf = b;
ngx_http_output_filter(r, chain);
// send content body
cur = ngx_queue_head(&queue_channel_info); cur = ngx_queue_head(&queue_channel_info);
while (cur != &queue_channel_info) { while (cur != &queue_channel_info) {
ngx_http_push_stream_msg_t *msg = (ngx_http_push_stream_msg_t *) cur; next = ngx_queue_next(cur);
chain->buf = msg->buf; ngx_http_push_stream_channel_info_t *channel_info = (ngx_http_push_stream_channel_info_t *) cur;
ngx_http_output_filter(r, chain); if ((chain = ngx_pcalloc(r->pool, sizeof(ngx_chain_t))) == NULL) {
cur = ngx_queue_next(cur); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for response channels info");
// sends the separate information return NGX_HTTP_INTERNAL_SERVER_ERROR;
chain->buf = b;
if (cur != &queue_channel_info) {
chain->buf->pos = NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_JSON.data;
chain->buf->last = NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_JSON.data + NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_JSON.len;
} else {
chain->buf->pos = NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_LAST_ITEM_JSON.data;
chain->buf->last = NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_LAST_ITEM_JSON.data + NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_LAST_ITEM_JSON.len;
} }
chain->buf->start = chain->buf->pos;
chain->buf->end = chain->buf->last;
ngx_http_output_filter(r, chain);
}
// send the tail message
chain->buf = b;
chain->buf->pos = tail->data;
chain->buf->last = tail->data + tail->len;
chain->buf->start = chain->buf->pos;
chain->buf->end = chain->buf->last;
ngx_http_output_filter(r, chain);
return NGX_HTTP_OK;
}
format = (next != &queue_channel_info) ? subtype->format_group_item : subtype->format_group_last_item;
static void chain->buf = ngx_http_push_stream_channel_info_formatted(r->pool, format, &channel_info->id, channel_info->published_messages, channel_info->stored_messages, channel_info->subscribers);
ngx_http_push_stream_reserve_message_locked(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg) chain->buf->last_buf = 0;
{ chain->buf->flush = 1;
if (!msg->persistent) { ngx_http_output_filter(r, chain);
msg->refcount++;
}
// we need a refcount because channel messages MAY be dequed before they are used up. It thus falls on the IPC stuff to free it.
}
static void cur = next;
ngx_http_push_stream_release_message_locked(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg)
{
if (!msg->persistent) {
msg->refcount--;
if (msg->queue.next == NULL && msg->refcount <= 0) {
// message had been dequeued and nobody needs it anymore
ngx_http_push_stream_free_message_locked(msg, ngx_http_push_stream_shpool);
}
if (ngx_http_push_stream_get_oldest_message_locked(channel) == msg) {
ngx_http_push_stream_delete_message_locked(channel, msg, ngx_http_push_stream_shpool);
}
} }
}
static ngx_int_t r->keepalive = 0;
ngx_http_push_stream_respond_status_only(ngx_http_request_t *r, ngx_int_t status_code, const ngx_str_t *statusline)
{
r->headers_out.status = status_code;
if (statusline != NULL) {
r->headers_out.status_line.len = statusline->len;
r->headers_out.status_line.data = statusline->data;
}
r->headers_out.content_length_n = 0; // send content tail
r->header_only = 1; ngx_http_push_stream_send_response_chunk(r, tail, 1);
return ngx_http_send_header(r); return NGX_DONE;
} }
#ifndef NGX_HTTP_PUSH_STREAM_MODULE_H_
#define NGX_HTTP_PUSH_STREAM_MODULE_H_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_channel.h>
#include <nginx.h>
typedef struct {
size_t shm_size;
} ngx_http_push_stream_main_conf_t;
typedef struct {
ngx_int_t index_channel_id;
ngx_int_t index_channels_path;
time_t buffer_timeout;
ngx_int_t max_messages;
ngx_int_t authorize_channel;
ngx_int_t store_messages;
ngx_int_t max_channel_id_length;
ngx_str_t header_template;
ngx_str_t message_template;
ngx_str_t content_type;
ngx_msec_t ping_message_interval;
ngx_msec_t subscriber_disconnect_interval;
time_t subscriber_connection_timeout;
ngx_str_t broadcast_channel_prefix;
ngx_int_t broadcast_channel_max_qtd;
} ngx_http_push_stream_loc_conf_t;
// variables
static ngx_str_t ngx_http_push_stream_channel_id = ngx_string("push_stream_channel_id");
static ngx_str_t ngx_http_push_stream_channels_path = ngx_string("push_stream_channels_path");
// shared memory segment name
static ngx_str_t ngx_push_stream_shm_name = ngx_string("push_stream_module");
// message queue
typedef struct {
ngx_queue_t queue; // this MUST be first
ngx_buf_t *buf;
time_t expires;
ngx_int_t refcount;
ngx_flag_t persistent;
} ngx_http_push_stream_msg_t;
typedef struct ngx_http_push_stream_subscriber_cleanup_s ngx_http_push_stream_subscriber_cleanup_t;
// subscriber request queue
typedef struct {
ngx_queue_t queue; // this MUST be first
ngx_http_request_t *request;
ngx_http_push_stream_subscriber_cleanup_t *clndata;
} ngx_http_push_stream_subscriber_t;
typedef struct {
ngx_queue_t queue;
pid_t pid;
ngx_int_t slot;
ngx_http_push_stream_subscriber_t *subscriber_sentinel;
} ngx_http_push_stream_pid_queue_t;
// our typecast-friendly rbtree node (channel)
typedef struct {
ngx_rbtree_node_t node; // this MUST be first
ngx_str_t id;
ngx_http_push_stream_msg_t *message_queue;
ngx_uint_t last_message_id;
ngx_uint_t stored_messages;
ngx_http_push_stream_pid_queue_t workers_with_subscribers;
ngx_uint_t subscribers;
} ngx_http_push_stream_channel_t;
typedef struct {
ngx_queue_t queue;
ngx_http_push_stream_subscriber_t *subscriber;
ngx_http_push_stream_channel_t *channel;
} ngx_http_push_stream_subscription_t;
typedef struct {
ngx_queue_t queue; // this MUST be first
ngx_http_request_t *request;
ngx_http_push_stream_subscription_t *subscriptions_sentinel;
ngx_http_push_stream_subscriber_cleanup_t *clndata;
ngx_pid_t worker_subscribed_pid;
time_t expires;
} ngx_http_push_stream_worker_subscriber_t;
// cleaning supplies
struct ngx_http_push_stream_subscriber_cleanup_s {
ngx_http_push_stream_worker_subscriber_t *worker_subscriber;
};
typedef struct {
ngx_queue_t queue;
ngx_http_push_stream_msg_t *msg;
} ngx_http_push_stream_msg_queue_t;
// garbage collecting goodness
typedef struct {
ngx_queue_t queue;
ngx_http_push_stream_channel_t *channel;
} ngx_http_push_stream_channel_queue_t;
// messages to worker processes
typedef struct {
ngx_queue_t queue;
ngx_http_push_stream_msg_t *msg; // ->shared memory
ngx_int_t status_code;
ngx_pid_t pid;
ngx_http_push_stream_channel_t *channel; // ->shared memory
ngx_http_push_stream_subscriber_t *subscriber_sentinel; // ->a worker's local pool
} ngx_http_push_stream_worker_msg_t;
typedef struct {
ngx_queue_t messages_queue;
ngx_http_push_stream_worker_subscriber_t *worker_subscribers_sentinel;
} ngx_http_push_stream_worker_data_t;
// shared memory
typedef struct {
ngx_rbtree_t tree;
ngx_uint_t channels; // # of channels being used
ngx_http_push_stream_worker_data_t *ipc; // interprocess stuff
} ngx_http_push_stream_shm_data_t;
typedef struct {
char *subtype;
size_t len;
const ngx_str_t *format;
} ngx_http_push_stream_content_subtype_t;
ngx_event_t ngx_http_push_stream_ping_event;
ngx_event_t ngx_http_push_stream_disconnect_event;
ngx_int_t ngx_http_push_stream_worker_processes;
ngx_pool_t *ngx_http_push_stream_pool;
ngx_slab_pool_t *ngx_http_push_stream_shpool;
ngx_shm_zone_t *ngx_http_push_stream_shm_zone = NULL;
ngx_chain_t *ngx_http_push_stream_header_chain = NULL;
ngx_chain_t *ngx_http_push_stream_crlf_chain = NULL;
ngx_http_push_stream_msg_t *ngx_http_push_stream_ping_msg = NULL;
ngx_buf_t *ngx_http_push_stream_ping_buf = NULL;
// emergency garbage collecting goodness;
ngx_http_push_stream_channel_queue_t channel_gc_sentinel;
// garbage-collecting shared memory slab allocation
void * ngx_http_push_stream_slab_alloc_locked(size_t size);
static ngx_int_t ngx_http_push_stream_channel_collector(ngx_http_push_stream_channel_t *channel, ngx_slab_pool_t *shpool);
// setup
static ngx_int_t ngx_http_push_stream_init_module(ngx_cycle_t *cycle);
static ngx_int_t ngx_http_push_stream_init_worker(ngx_cycle_t *cycle);
static void ngx_http_push_stream_exit_worker(ngx_cycle_t *cycle);
static void ngx_http_push_stream_exit_master(ngx_cycle_t *cycle);
static ngx_int_t ngx_http_push_stream_postconfig(ngx_conf_t *cf);
static void * ngx_http_push_stream_create_main_conf(ngx_conf_t *cf);
static void * ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf);
static char * ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
// subscriber
static char * ngx_http_push_stream_subscriber(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r);
static ngx_int_t ngx_http_push_stream_broadcast_locked(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg, ngx_int_t status_code, const ngx_str_t *status_line, ngx_log_t *log, ngx_slab_pool_t *shpool);
#define ngx_http_push_stream_broadcast_status_locked(channel, status_code, status_line, log, shpool) ngx_http_push_stream_broadcast_locked(channel, NULL, status_code, status_line, log, shpool)
#define ngx_http_push_stream_broadcast_message_locked(channel, msg, log, shpool) ngx_http_push_stream_broadcast_locked(channel, msg, 0, NULL, log, shpool)
static ngx_int_t ngx_http_push_stream_respond_to_subscribers(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_subscriber_t *sentinel, ngx_http_push_stream_msg_t *msg, ngx_int_t status_code, const ngx_str_t *status_line);
static void ngx_http_push_stream_subscriber_cleanup(ngx_http_push_stream_subscriber_cleanup_t *data);
static void ngx_http_push_stream_worker_subscriber_cleanup_locked(ngx_http_push_stream_worker_subscriber_t *worker_subscriber);
// publisher
static char * ngx_http_push_stream_publisher(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_push_stream_publisher_handler(ngx_http_request_t *r);
static void ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r);
// channel
static void ngx_http_push_stream_send_response_channel_id_not_provided(ngx_http_request_t *r);
static ngx_str_t * ngx_http_push_stream_get_channel_id(ngx_http_request_t *r, ngx_http_push_stream_loc_conf_t *cf);
static ngx_int_t ngx_http_push_stream_channel_info(ngx_http_request_t *r, ngx_str_t channelId, ngx_uint_t published_message_queue_size, ngx_uint_t stored_message_queue_size, ngx_uint_t subscriber_queue_size);
static ngx_int_t ngx_http_push_stream_all_channels_info(ngx_http_request_t *r);
static ngx_http_push_stream_channel_t * ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log);
static ngx_http_push_stream_channel_t * ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log);
static ngx_int_t ngx_http_push_stream_delete_channel_locked(ngx_http_push_stream_channel_t *trash);
static ngx_http_push_stream_channel_t * ngx_http_push_stream_clean_channel_locked(ngx_http_push_stream_channel_t *channel);
// channel messages
static void ngx_http_push_stream_reserve_message_locked(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg);
static void ngx_http_push_stream_release_message_locked(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg);
static ngx_inline void ngx_http_push_stream_general_delete_message_locked(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg, ngx_int_t force, ngx_slab_pool_t *shpool);
#define ngx_http_push_stream_delete_message_locked(channel, msg, shpool) ngx_http_push_stream_general_delete_message_locked(channel, msg, 0, shpool)
#define ngx_http_push_stream_force_delete_message_locked(channel, msg, shpool) ngx_http_push_stream_general_delete_message_locked(channel, msg, 1, shpool)
static ngx_inline void ngx_http_push_stream_free_message_locked(ngx_http_push_stream_msg_t *msg, ngx_slab_pool_t *shpool);
// utilities
// general request handling
static void ngx_http_push_stream_copy_preallocated_buffer(ngx_buf_t *buf, ngx_buf_t *cbuf);
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_int_t ngx_http_push_stream_respond_status_only(ngx_http_request_t *r, ngx_int_t status_code, const ngx_str_t *statusline);
static ngx_chain_t * ngx_http_push_stream_create_output_chain_general(ngx_buf_t *buf, ngx_pool_t *pool, ngx_log_t *log, ngx_slab_pool_t *shpool);
#define ngx_http_push_stream_create_output_chain(buf, pool, log) ngx_http_push_stream_create_output_chain_general(buf, pool, log, NULL)
#define ngx_http_push_stream_create_output_chain_locked(buf, pool, log, shpool) ngx_http_push_stream_create_output_chain_general(buf, pool, log, shpool)
static ngx_int_t ngx_http_push_stream_send_body_header(ngx_http_request_t *r, ngx_http_push_stream_loc_conf_t *pslcf);
static ngx_int_t ngx_http_push_stream_send_ping(ngx_log_t *log, ngx_http_push_stream_loc_conf_t *pslcf);
static void ngx_http_push_stream_ping_timer_wake_handler(ngx_event_t *ev);
static void ngx_http_push_stream_ping_timer_set(ngx_http_push_stream_loc_conf_t *pslcf);
static void ngx_http_push_stream_ping_timer_reset(ngx_http_push_stream_loc_conf_t *pslcf);
static void ngx_http_push_stream_disconnect_timer_wake_handler(ngx_event_t *ev);
static void ngx_http_push_stream_disconnect_timer_set(ngx_http_push_stream_loc_conf_t *pslcf);
static void ngx_http_push_stream_disconnect_timer_reset(ngx_http_push_stream_loc_conf_t *pslcf);
static u_char * ngx_http_push_stream_str_replace_locked(u_char *org, u_char *find, u_char *replace, ngx_pool_t *temp_pool);
static ngx_buf_t * ngx_http_push_stream_get_formatted_message_locked(ngx_http_push_stream_loc_conf_t *pslcf, ngx_http_push_stream_channel_t *channel, ngx_buf_t *buf, ngx_pool_t *temp_pool);
// shared memory
static ngx_int_t ngx_http_push_stream_set_up_shm(ngx_conf_t *cf, size_t shm_size);
static ngx_int_t ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data);
static ngx_int_t ngx_http_push_stream_movezig_channel_locked(ngx_http_push_stream_channel_t *channel, ngx_slab_pool_t *shpool);
// ipc
static ngx_int_t ngx_http_push_stream_init_ipc(ngx_cycle_t *cycle, ngx_int_t workers);
static void ngx_http_push_stream_ipc_exit_worker(ngx_cycle_t *cycle);
static ngx_int_t ngx_http_push_stream_init_ipc_shm(ngx_int_t workers);
static void ngx_http_push_stream_channel_handler(ngx_event_t *ev);
static ngx_inline void ngx_http_push_stream_process_worker_message(void);
static ngx_inline void ngx_http_push_stream_send_worker_ping_message(void);
static ngx_inline void ngx_http_push_stream_disconnect_worker_subscribers(ngx_flag_t force_disconnect);
static ngx_inline void ngx_http_push_stream_census_worker_subscribers(void);
static void ngx_http_push_stream_ipc_exit_worker(ngx_cycle_t *cycle);
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_int_t status_code, ngx_log_t *log);
static ngx_int_t ngx_http_push_stream_alert_worker(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log);
static ngx_int_t ngx_http_push_stream_alert_worker_send_ping(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log);
static ngx_int_t ngx_http_push_stream_alert_worker_disconnect_subscribers(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log);
static ngx_int_t ngx_http_push_stream_alert_worker_census_subscribers(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log);
// constants
#define NGX_CMD_HTTP_PUSH_STREAM_CHECK_MESSAGES 49
#define NGX_CMD_HTTP_PUSH_STREAM_SEND_PING 50
#define NGX_CMD_HTTP_PUSH_STREAM_DISCONNECT_SUBSCRIBERS 51
#define NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS 52
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID = ngx_string("ALL");
#define NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE "No channel id provided."
#define NGX_HTTP_PUSH_STREAM_MAX_CHANNEL_ID_LENGTH 1024 // bytes
#define NGX_HTTP_PUSH_STREAM_DEFAULT_SHM_SIZE 33554432 // 32 megs
#define NGX_HTTP_PUSH_STREAM_DEFAULT_BUFFER_TIMEOUT 7200
#define NGX_HTTP_PUSH_STREAM_DEFAULT_MAX_MESSAGES 10
#define NGX_HTTP_PUSH_STREAM_DEFAULT_HEADER_TEMPLATE ""
#define NGX_HTTP_PUSH_STREAM_DEFAULT_MESSAGE_TEMPLATE ""
#define NGX_HTTP_PUSH_STREAM_DEFAULT_CONTENT_TYPE "text/plain"
#define NGX_HTTP_PUSH_STREAM_DEFAULT_BROADCAST_CHANNEL_PREFIX ""
static const ngx_str_t NGX_HTTP_PUSH_STREAM_BACKTRACK_SEP = ngx_string(".b");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_SLASH = ngx_string("/");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CRLF = ngx_string("\r\n");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_TEXT_PLAIN = ngx_string("text/plain");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CONTENT_TYPE_JSON = ngx_string("application/json");
static const ngx_str_t NGX_PUSH_STREAM_TOKEN_MESSAGE_ID = ngx_string("~id~");
static const ngx_str_t NGX_PUSH_STREAM_TOKEN_MESSAGE_CHANNEL = ngx_string("~channel~");
static const ngx_str_t NGX_PUSH_STREAM_TOKEN_MESSAGE_TEXT = ngx_string("~text~");
static const ngx_str_t NGX_PUSH_STREAM_PING_MESSAGE_ID = ngx_string("-1");
static const ngx_str_t NGX_PUSH_STREAM_PING_MESSAGE_TEXT = ngx_string("");
static const ngx_str_t NGX_PUSH_STREAM_PING_CHANNEL_ID = ngx_string("");
static const ngx_str_t NGX_PUSH_STREAM_DATE_FORMAT_ISO_8601 = ngx_string("%4d-%02d-%02dT%02d:%02d:%02d");
// message codes
#define NGX_HTTP_PUSH_STREAM_MESSAGE_RECEIVED 9000
#define NGX_HTTP_PUSH_STREAM_MESSAGE_QUEUED 9001
#define NGX_HTTP_PUSH_STREAM_MESSAGE_FOUND 1000
#define NGX_HTTP_PUSH_STREAM_MESSAGE_EXPECTED 1001
#define NGX_HTTP_PUSH_STREAM_MESSAGE_EXPIRED 1002
#ifndef NGX_HTTP_CONFLICT
#define NGX_HTTP_CONFLICT 409
#endif
#ifndef NGX_HTTP_GONE
#define NGX_HTTP_GONE 410
#endif
#ifndef NGX_HTTP_CREATED
#define NGX_HTTP_CREATED 201
#endif
#ifndef NGX_HTTP_ACCEPTED
#define NGX_HTTP_ACCEPTED 202
#endif
// headers
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_ETAG = ngx_string("Etag");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_VARY = ngx_string("Vary");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HEADER_ALLOW = ngx_string("Allow");
// header values
//const ngx_str_t NGX_HTTP_PUSH_CACHE_CONTROL_VALUE = ngx_string("no-cache");
// status strings
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HTTP_STATUS_409 = ngx_string("409 Conflict");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_HTTP_STATUS_410 = ngx_string("410 Gone");
// other stuff
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_PUT_DELETE = ngx_string("GET, POST, PUT, DELETE");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_ALLOW_GET = ngx_string("GET");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_VARY_HEADER_VALUE = ngx_string("If-None-Match, If-Modified-Since");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_PLAIN = ngx_string(
"channel: %s" CRLF
"published_messages: %ui" CRLF
"stored_messages: %ui" CRLF
"active_subscribers: %ui" CRLF
"\0");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_HEAD_JSON = ngx_string("{\"hostname\": \"%s\", \"time\": \"%s\", \"infos\": [" CRLF);
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_GROUP_TAIL_JSON = ngx_string("]}" CRLF);
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_JSON = ngx_string("," CRLF);
// have to be the same size of NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_JSON
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_ITEM_SEP_LAST_ITEM_JSON = ngx_string(" " CRLF);
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_JSON = ngx_string(
"{\"channel\": \"%s\", "
"\"published_messages\": \"%ui\", "
"\"stored_messages\": \"%ui\", "
"\"subscribers\": \"%ui\"}"
"\0");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_XML = ngx_string(
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" CRLF
"<channel>" CRLF
" <name>%s</name>" CRLF
" <published_messages>%ui</published_messages>" CRLF
" <stored_messages>%ui</stored_messages>" CRLF
" <subscribers>%ui</subscribers>" CRLF
"</channel>" CRLF
"\0");
static const ngx_str_t NGX_HTTP_PUSH_STREAM_CHANNEL_INFO_YAML = ngx_string(
"---" CRLF
"channel: %s" CRLF
"published_messages: %ui" CRLF
"stored_messages: %ui" CRLF
"subscribers %ui" CRLF
"\0");
#endif /* NGX_HTTP_PUSH_STREAM_MODULE_H_ */
#include <ngx_http_push_stream_module.h> #include <ngx_http_push_stream_module_ipc.h>
// worker processes of the world, unite.
ngx_socket_t ngx_http_push_stream_socketpairs[NGX_MAX_PROCESSES][2];
static ngx_int_t static ngx_int_t
...@@ -94,48 +90,46 @@ static ngx_int_t ...@@ -94,48 +90,46 @@ static ngx_int_t
ngx_http_push_stream_init_ipc_shm(ngx_int_t workers) ngx_http_push_stream_init_ipc_shm(ngx_int_t workers)
{ {
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_http_push_stream_shm_data_t *d = (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_worker_data_t *workers_data; ngx_http_push_stream_worker_data_t *workers_data;
int i; int i;
ngx_shmtx_lock(&shpool->mutex); ngx_shmtx_lock(&shpool->mutex);
if (d->ipc != NULL) { if (data->ipc != NULL) {
// already initialized... reset channel subscribers counters and census subscribers // already initialized... reset channel subscribers counters and census subscribers
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 = 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_queue_init(&sentinel->queue); ngx_queue_init(&sentinel->queue);
data->subscribers = 0;
ngx_http_push_stream_walk_rbtree(ngx_http_push_stream_reset_channel_subscribers_count_locked); ngx_http_push_stream_walk_rbtree(ngx_http_push_stream_reset_channel_subscribers_count_locked);
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
for(i=0; i<workers; i++) { for(i=0; i<workers; i++) {
ngx_http_push_stream_alert_worker_census_subscribers(ngx_pid, i, ngx_http_push_stream_pool->log); ngx_http_push_stream_alert_worker_census_subscribers(ngx_pid, i, ngx_cycle->log);
} }
return NGX_OK; return NGX_OK;
} }
// initialize worker message queues // initialize worker message queues
if ((workers_data = ngx_slab_alloc_locked(shpool, sizeof(*workers_data)*workers)) == NULL) { if ((workers_data = ngx_slab_alloc_locked(shpool, sizeof(ngx_http_push_stream_worker_data_t)*workers)) == NULL) {
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
return NGX_ERROR; return NGX_ERROR;
} }
for(i=0; i<workers; i++) { for(i=0; i<workers; i++) {
ngx_queue_init(&workers_data[i].messages_queue); ngx_queue_init(&workers_data[i].messages_queue.queue);
if ((workers_data[i].worker_subscribers_sentinel = ngx_slab_alloc_locked(shpool, sizeof(*workers_data[i].worker_subscribers_sentinel))) == NULL) { ngx_queue_init(&workers_data[i].worker_subscribers_sentinel.queue);
ngx_shmtx_unlock(&shpool->mutex);
return NGX_ERROR;
}
ngx_queue_init(&((ngx_http_push_stream_worker_subscriber_t *) workers_data[i].worker_subscribers_sentinel)->queue);
} }
d->ipc = workers_data; data->ipc = workers_data;
ngx_queue_init(&data->messages_to_delete.queue);
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
...@@ -187,16 +181,15 @@ ngx_http_push_stream_channel_handler(ngx_event_t *ev) ...@@ -187,16 +181,15 @@ ngx_http_push_stream_channel_handler(ngx_event_t *ev)
if (n == NGX_AGAIN) { if (n == NGX_AGAIN) {
return; return;
} }
//ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, "push stream module: channel command: %d", ch.command);
if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_CHECK_MESSAGES) { if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_CHECK_MESSAGES.command) {
ngx_http_push_stream_process_worker_message(); ngx_http_push_stream_process_worker_message();
} else if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_SEND_PING) { } else if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_SEND_PING.command) {
ngx_http_push_stream_send_worker_ping_message(); ngx_http_push_stream_send_worker_ping_message();
} else if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_DISCONNECT_SUBSCRIBERS) { } else if (ch.command == NGX_CMD_HTTP_PUSH_STREAM_DISCONNECT_SUBSCRIBERS.command) {
// disconnect only expired subscribers (force_disconnect = 0) // disconnect only expired subscribers (force_disconnect = 0)
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) { } 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();
} }
} }
...@@ -204,42 +197,9 @@ ngx_http_push_stream_channel_handler(ngx_event_t *ev) ...@@ -204,42 +197,9 @@ ngx_http_push_stream_channel_handler(ngx_event_t *ev)
static ngx_int_t static ngx_int_t
ngx_http_push_stream_alert_worker(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log) ngx_http_push_stream_alert_worker(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log, ngx_channel_t command)
{
// seems ch doesn't need to have fd set. odd, but roll with it. pid and process slot also unnecessary.
static ngx_channel_t ch = {NGX_CMD_HTTP_PUSH_STREAM_CHECK_MESSAGES, 0, 0, -1};
return ngx_write_channel(ngx_http_push_stream_socketpairs[slot][0], &ch, sizeof(ngx_channel_t), log);
}
static ngx_int_t
ngx_http_push_stream_alert_worker_send_ping(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log)
{
// seems ch doesn't need to have fd set. odd, but roll with it. pid and process slot also unnecessary.
static ngx_channel_t ch = {NGX_CMD_HTTP_PUSH_STREAM_SEND_PING, 0, 0, -1};
return ngx_write_channel(ngx_http_push_stream_socketpairs[slot][0], &ch, sizeof(ngx_channel_t), log);
}
static ngx_int_t
ngx_http_push_stream_alert_worker_disconnect_subscribers(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log)
{
// seems ch doesn't need to have fd set. odd, but roll with it. pid and process slot also unnecessary.
static ngx_channel_t ch = {NGX_CMD_HTTP_PUSH_STREAM_DISCONNECT_SUBSCRIBERS, 0, 0, -1};
return ngx_write_channel(ngx_http_push_stream_socketpairs[slot][0], &ch, sizeof(ngx_channel_t), log);
}
static ngx_int_t
ngx_http_push_stream_alert_worker_census_subscribers(ngx_pid_t pid, ngx_int_t slot, ngx_log_t *log)
{ {
// seems ch doesn't need to have fd set. odd, but roll with it. pid and process slot also unnecessary. return ngx_write_channel(ngx_http_push_stream_socketpairs[slot][0], &command, sizeof(ngx_channel_t), log);
static ngx_channel_t ch = {NGX_CMD_HTTP_PUSH_STREAM_CENSUS_SUBSCRIBERS, 0, 0, -1};
return ngx_write_channel(ngx_http_push_stream_socketpairs[slot][0], &ch, sizeof(ngx_channel_t), log);
} }
...@@ -247,26 +207,23 @@ static ngx_inline void ...@@ -247,26 +207,23 @@ static ngx_inline void
ngx_http_push_stream_census_worker_subscribers(void) ngx_http_push_stream_census_worker_subscribers(void)
{ {
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_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_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 = 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 *cur, *sentinel;
ngx_http_push_stream_subscription_t *cur_subscription, *sentinel_subscription;
ngx_http_push_stream_worker_subscriber_t *cur, *next;
ngx_shmtx_lock(&shpool->mutex); ngx_shmtx_lock(&shpool->mutex);
cur = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&sentinel->queue); sentinel = &thisworker_data->worker_subscribers_sentinel;
while (cur != sentinel) { cur = sentinel;
next = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&cur->queue); while ((cur = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&cur->queue)) != sentinel) {
ngx_http_push_stream_subscription_t *cur_subscription, *sentinel_subscription; sentinel_subscription = &cur->subscriptions_sentinel;
sentinel_subscription = cur->subscriptions_sentinel; cur_subscription = sentinel_subscription;
cur_subscription = (ngx_http_push_stream_subscription_t *) ngx_queue_head(&sentinel_subscription->queue); while ((cur_subscription = (ngx_http_push_stream_subscription_t *) ngx_queue_next(&cur_subscription->queue)) != sentinel_subscription) {
while (cur_subscription != sentinel_subscription) {
cur_subscription->channel->subscribers++; cur_subscription->channel->subscribers++;
cur_subscription = (ngx_http_push_stream_subscription_t *) ngx_queue_next(&cur_subscription->queue);
} }
cur = next; data->subscribers++;
} }
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
...@@ -276,100 +233,44 @@ ngx_http_push_stream_census_worker_subscribers(void) ...@@ -276,100 +233,44 @@ ngx_http_push_stream_census_worker_subscribers(void)
static ngx_inline void static ngx_inline void
ngx_http_push_stream_disconnect_worker_subscribers(ngx_flag_t force_disconnect) ngx_http_push_stream_disconnect_worker_subscribers(ngx_flag_t force_disconnect)
{ {
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
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_http_push_stream_worker_subscriber_t *cur, *next; ngx_http_push_stream_worker_subscriber_t *cur = sentinel;
ngx_shmtx_lock(&shpool->mutex);
time_t now = ngx_time(); time_t now = ngx_time();
cur = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&sentinel->queue);
while (cur != sentinel) { while ((cur = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&sentinel->queue)) != sentinel) {
next = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&cur->queue); if ((cur->request != NULL) && ((force_disconnect == 1) || ((cur->expires != 0) && (now > cur->expires)))) {
// in this block, nothing in shared memory should be dereferenced. ngx_http_push_stream_worker_subscriber_cleanup(cur);
ngx_http_request_t *r = cur->request; cur->request->keepalive = 0;
if (r != NULL) { ngx_http_send_special(cur->request, NGX_HTTP_LAST | NGX_HTTP_FLUSH);
if ((force_disconnect == 1) || ((cur->expires != 0) && (now > cur->expires))) { ngx_http_finalize_request(cur->request, NGX_HTTP_OK);
ngx_http_push_stream_worker_subscriber_cleanup_locked(cur); } else {
r->keepalive = 0; break;
ngx_http_finalize_request(r, NGX_HTTP_OK);
} else {
break;
}
} }
cur = next;
} }
ngx_shmtx_unlock(&shpool->mutex);
} }
static ngx_inline void static ngx_inline void
ngx_http_push_stream_send_worker_ping_message(void) ngx_http_push_stream_send_worker_ping_message(void)
{ {
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
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_http_push_stream_worker_subscriber_t *cur = sentinel;
// copy everything we need first
ngx_chain_t *chain; if ((ngx_http_push_stream_ping_buf != NULL) && (!ngx_queue_empty(&sentinel->queue))) {
ngx_http_request_t *r; ngx_str_t aux = ngx_string(ngx_http_push_stream_ping_buf->pos);
ngx_buf_t *buffer; aux.len = ngx_buf_size(ngx_http_push_stream_ping_buf);
u_char *pos; while ((cur = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&cur->queue)) != sentinel) {
ngx_http_push_stream_worker_subscriber_t *cur, *next; if (cur->request != NULL) {
ngx_pool_t *temp_pool; ngx_http_push_stream_send_response_chunk(cur->request, &aux, 0);
}
ngx_shmtx_lock(&shpool->mutex);
if ((temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, ngx_http_push_stream_pool->log)) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, ngx_http_push_stream_pool->log, 0, "push stream module: unable to allocate memory for temporary pool");
return;
}
// preallocate output chain. yes, same one for every waiting subscriber
if ((chain = ngx_http_push_stream_create_output_chain_locked(ngx_http_push_stream_ping_msg->buf, temp_pool, ngx_http_push_stream_pool->log, shpool)) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push stream module: unable to create output chain while responding to several subscriber request");
ngx_destroy_pool(temp_pool);
return;
}
buffer = chain->buf;
pos = buffer->pos;
ngx_shmtx_unlock(&shpool->mutex);
buffer->last_buf = 0;
cur = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&sentinel->queue);
// now let's respond to some requests!
while (cur != sentinel) {
next = (ngx_http_push_stream_worker_subscriber_t *) ngx_queue_next(&cur->queue);
// in this block, nothing in shared memory should be dereferenced.
r = cur->request;
if (r != NULL) {
r->discard_body = 0; // hacky hacky!
ngx_http_output_filter(r, chain);
ngx_http_send_special(r, NGX_HTTP_FLUSH);
// rewind the buffer, please
buffer->pos = pos;
buffer->last_buf = 0;
} }
cur = next;
} }
ngx_destroy_pool(temp_pool);
} }
...@@ -377,19 +278,9 @@ static ngx_inline void ...@@ -377,19 +278,9 @@ static ngx_inline void
ngx_http_push_stream_process_worker_message(void) ngx_http_push_stream_process_worker_message(void)
{ {
ngx_http_push_stream_worker_msg_t *prev_worker_msg, *worker_msg, *sentinel; ngx_http_push_stream_worker_msg_t *prev_worker_msg, *worker_msg, *sentinel;
const ngx_str_t *status_line = NULL;
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_http_push_stream_subscriber_t *subscriber_sentinel;
ngx_shmtx_lock(&shpool->mutex);
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_int_t status_code;
ngx_http_push_stream_msg_t *msg;
sentinel = (ngx_http_push_stream_worker_msg_t *) &thisworker_data->messages_queue; sentinel = (ngx_http_push_stream_worker_msg_t *) &thisworker_data->messages_queue;
...@@ -397,37 +288,7 @@ ngx_http_push_stream_process_worker_message(void) ...@@ -397,37 +288,7 @@ ngx_http_push_stream_process_worker_message(void)
while (worker_msg != sentinel) { while (worker_msg != sentinel) {
if (worker_msg->pid == ngx_pid) { if (worker_msg->pid == ngx_pid) {
// everything is okay // everything is okay
status_code = worker_msg->status_code; ngx_http_push_stream_respond_to_subscribers(worker_msg->channel, worker_msg->subscriber_sentinel, worker_msg->msg);
msg = worker_msg->msg;
channel = worker_msg->channel;
subscriber_sentinel = worker_msg->subscriber_sentinel;
if (msg == NULL) {
// just a status line, is all
// status code only
switch (status_code) {
case NGX_HTTP_CONFLICT:
status_line = &NGX_HTTP_PUSH_STREAM_HTTP_STATUS_409;
break;
case NGX_HTTP_GONE:
status_line = &NGX_HTTP_PUSH_STREAM_HTTP_STATUS_410;
break;
case 0:
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push stream module: worker message contains neither a channel message nor a status code");
ngx_shmtx_lock(&shpool->mutex);
// let's let the subscribers know that something went wrong and they might've missed a message
status_code = NGX_HTTP_INTERNAL_SERVER_ERROR;
// intentional fall-through
default:
status_line = NULL;
}
}
ngx_shmtx_unlock(&shpool->mutex);
ngx_http_push_stream_respond_to_subscribers(channel, subscriber_sentinel, msg, status_code, status_line);
ngx_shmtx_lock(&shpool->mutex);
} else { } else {
// that's quite bad you see. a previous worker died with an undelivered message. // that's quite bad you see. a previous worker died with an undelivered message.
// but all its subscribers' connections presumably got canned, too. so it's not so bad after all. // but all its subscribers' connections presumably got canned, too. so it's not so bad after all.
...@@ -435,36 +296,34 @@ ngx_http_push_stream_process_worker_message(void) ...@@ -435,36 +296,34 @@ ngx_http_push_stream_process_worker_message(void)
ngx_http_push_stream_pid_queue_t *channel_worker_sentinel = &worker_msg->channel->workers_with_subscribers; ngx_http_push_stream_pid_queue_t *channel_worker_sentinel = &worker_msg->channel->workers_with_subscribers;
ngx_http_push_stream_pid_queue_t *channel_worker_cur = channel_worker_sentinel; ngx_http_push_stream_pid_queue_t *channel_worker_cur = channel_worker_sentinel;
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push stream module: worker %i intercepted a message intended for another worker process (%i) that probably died", ngx_pid, worker_msg->pid); ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push stream module: worker %i intercepted a message intended for another worker process (%i) that probably died", ngx_pid, worker_msg->pid);
ngx_shmtx_lock(&shpool->mutex);
// delete that invalid sucker // delete that invalid sucker
while ((channel_worker_cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&channel_worker_cur->queue)) != channel_worker_sentinel) { while ((channel_worker_cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&channel_worker_cur->queue)) != channel_worker_sentinel) {
if (channel_worker_cur->pid == worker_msg->pid) { if (channel_worker_cur->pid == worker_msg->pid) {
ngx_shmtx_lock(&shpool->mutex);
ngx_queue_remove(&channel_worker_cur->queue); ngx_queue_remove(&channel_worker_cur->queue);
ngx_slab_free_locked(shpool, channel_worker_cur->subscriber_sentinel);
ngx_slab_free_locked(shpool, channel_worker_cur); ngx_slab_free_locked(shpool, channel_worker_cur);
ngx_shmtx_unlock(&shpool->mutex);
break; break;
} }
} }
} }
// It may be worth it to memzero worker_msg for debugging purposes.
prev_worker_msg = worker_msg; prev_worker_msg = worker_msg;
worker_msg = (ngx_http_push_stream_worker_msg_t *) ngx_queue_next(&worker_msg->queue); worker_msg = (ngx_http_push_stream_worker_msg_t *) ngx_queue_next(&worker_msg->queue);
// free worker_msg already sent
ngx_shmtx_lock(&shpool->mutex);
ngx_queue_remove(&prev_worker_msg->queue);
ngx_slab_free_locked(shpool, prev_worker_msg); ngx_slab_free_locked(shpool, prev_worker_msg);
ngx_shmtx_unlock(&shpool->mutex);
} }
ngx_queue_init(&thisworker_data->messages_queue); // reset the worker message sentinel
ngx_shmtx_unlock(&shpool->mutex);
return;
} }
static ngx_int_t 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_int_t status_code, ngx_log_t *log) 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)
{ {
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_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;
...@@ -474,20 +333,66 @@ ngx_http_push_stream_send_worker_message(ngx_http_push_stream_channel_t *channel ...@@ -474,20 +333,66 @@ ngx_http_push_stream_send_worker_message(ngx_http_push_stream_channel_t *channel
ngx_shmtx_lock(&shpool->mutex); ngx_shmtx_lock(&shpool->mutex);
if ((newmessage = ngx_slab_alloc_locked(shpool, sizeof(*newmessage))) == NULL) { if ((newmessage = ngx_slab_alloc_locked(shpool, sizeof(ngx_http_push_stream_worker_msg_t))) == NULL) {
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, log, 0, "push stream module: unable to allocate worker message"); ngx_log_error(NGX_LOG_ERR, log, 0, "push stream module: unable to allocate worker message");
return NGX_ERROR; return NGX_ERROR;
} }
ngx_queue_insert_tail(&thisworker_data->messages_queue, &newmessage->queue);
newmessage->msg = msg; newmessage->msg = msg;
newmessage->status_code = status_code;
newmessage->pid = pid; newmessage->pid = pid;
newmessage->subscriber_sentinel = subscriber_sentinel; newmessage->subscriber_sentinel = subscriber_sentinel;
newmessage->channel = channel; newmessage->channel = channel;
ngx_queue_insert_tail(&thisworker_data->messages_queue.queue, &newmessage->queue);
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
return NGX_OK; return NGX_OK;
} }
static void
ngx_http_push_stream_broadcast(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg, ngx_log_t *log)
{
// subscribers are queued up in a local pool. Queue heads, however, are located
// in shared memory, identified by pid.
ngx_http_push_stream_pid_queue_t *sentinel = &channel->workers_with_subscribers;
ngx_http_push_stream_pid_queue_t *cur = sentinel;
while ((cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&cur->queue)) != sentinel) {
pid_t worker_pid = cur->pid;
ngx_int_t worker_slot = cur->slot;
ngx_http_push_stream_subscriber_t *subscriber_sentinel = &cur->subscriber_sentinel;
// interprocess communication breakdown
if (ngx_http_push_stream_send_worker_message(channel, subscriber_sentinel, worker_pid, worker_slot, msg, log) != NGX_ERROR) {
ngx_http_push_stream_alert_worker_check_messages(worker_pid, worker_slot, log);
} else {
ngx_log_error(NGX_LOG_ERR, log, 0, "push stream module: error communicating with some other worker process");
}
}
}
static ngx_int_t
ngx_http_push_stream_respond_to_subscribers(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_subscriber_t *sentinel, ngx_http_push_stream_msg_t *msg)
{
ngx_http_push_stream_subscriber_t *cur;
if (sentinel == NULL) {
return NGX_ERROR;
}
cur = sentinel;
if (msg != NULL) {
ngx_str_t message = ngx_string(msg->buf->pos);
message.len = ngx_buf_size(msg->buf);
// now let's respond to some requests!
while ((cur = (ngx_http_push_stream_subscriber_t *) ngx_queue_next(&cur->queue)) != sentinel) {
ngx_http_push_stream_send_response_chunk(cur->request, &message, 0);
}
}
return NGX_OK;
}
#include <ngx_http_push_stream_module.h> #include <ngx_http_push_stream_module_publisher.h>
static ngx_int_t static ngx_int_t
ngx_http_push_stream_publisher_handler(ngx_http_request_t *r) ngx_http_push_stream_publisher_handler(ngx_http_request_t *r)
{ {
ngx_int_t rc; ngx_int_t rc;
ngx_str_t *id = NULL;
ngx_http_push_stream_channel_t *channel = NULL;
ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
// Publisher never do a keep alive connection
/*
* Instruct ngx_http_read_subscriber_request_body to store the request
* body entirely in a memory buffer or in a file.
*/
r->request_body_in_single_buf = 1;
r->request_body_in_persistent_file = 1;
r->request_body_in_clean_file = 0;
r->request_body_file_log_level = 0;
r->keepalive = 0; r->keepalive = 0;
rc = ngx_http_read_client_request_body(r, ngx_http_push_stream_publisher_body_handler); // only accept GET and POST methods
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_POST))) {
return rc; ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOWED_METHODS);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL);
} }
return NGX_DONE; // channel id is required
} id = ngx_http_push_stream_get_channel_id(r, cf);
if ((id == NULL) || (id == NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID) || (id == NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID)) {
if (id == NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID) {
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE);
}
if (id == NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID) {
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE);
}
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// search for a existing channel with this id
channel = ngx_http_push_stream_find_channel(id, r->connection->log);
if (r->method == NGX_HTTP_POST) {
// check if channel id isn't equals to ALL
if (ngx_memn2cmp(id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data, id->len, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.len) == 0) {
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_NOT_AUTHORIZED_MESSAGE);
}
// create the channel if doesn't exist
channel = ngx_http_push_stream_get_channel(id, r->connection->log, cf);
if (channel == NULL) {
ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: unable to allocate memory for new channel");
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL);
}
if (channel == NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED) {
ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: number of channels were exceeded");
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED_MESSAGE);
}
/*
* Instruct ngx_http_read_subscriber_request_body to store the request
* body entirely in a memory buffer or in a file.
*/
r->request_body_in_single_buf = 0;
r->request_body_in_persistent_file = 1;
r->request_body_in_clean_file = 0;
r->request_body_file_log_level = 0;
// parse the body message and return
rc = ngx_http_read_client_request_body(r, ngx_http_push_stream_publisher_body_handler);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}
// GET only make sense with a previous existing channel
if (channel == NULL) {
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_FOUND, NULL);
}
return ngx_http_push_stream_send_response_channel_info(r, channel);
}
static void static void
ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r) ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r)
...@@ -32,200 +82,148 @@ ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r) ...@@ -32,200 +82,148 @@ ngx_http_push_stream_publisher_body_handler(ngx_http_request_t *r)
ngx_str_t *id; ngx_str_t *id;
ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module); ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
ngx_slab_pool_t *shpool = (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_buf_t *buf = NULL, *buf_copy, *buf_msg = NULL; ngx_buf_t *buf = NULL, *buf_msg = NULL;
ngx_chain_t *chain;
ngx_http_push_stream_channel_t *channel; ngx_http_push_stream_channel_t *channel;
ngx_uint_t method = r->method; ssize_t n;
ngx_uint_t subscribers = 0; off_t len;
ngx_uint_t published_messages = 0; ngx_http_push_stream_msg_t *msg;
ngx_uint_t stored_messages = 0;
// check if body message wasn't empty
if (r->headers_in.content_length_n <= 0) {
ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: Post request was sent with no message");
ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_EMPTY_POST_REQUEST_MESSAGE);
return;
}
// get and check if has access to request body
NGX_HTTP_PUSH_STREAM_CHECK_AND_FINALIZE_REQUEST_ON_ERROR(r->request_body->bufs, NULL, r, "push stream module: unexpected publisher message request body buffer location. please report this to the push stream module developers.");
// get and check channel id value
id = ngx_http_push_stream_get_channel_id(r, cf);
NGX_HTTP_PUSH_STREAM_CHECK_AND_FINALIZE_REQUEST_ON_ERROR(id, NULL, r, "push stream module: something goes very wrong, arrived on ngx_http_push_stream_publisher_body_handler without channel id");
NGX_HTTP_PUSH_STREAM_CHECK_AND_FINALIZE_REQUEST_ON_ERROR(id, NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID, r, "push stream module: something goes very wrong, arrived on ngx_http_push_stream_publisher_body_handler without channel id");
NGX_HTTP_PUSH_STREAM_CHECK_AND_FINALIZE_REQUEST_ON_ERROR(id, NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID, r, "push stream module: something goes very wrong, arrived on ngx_http_push_stream_publisher_body_handler with channel id too large");
// just find the channel. if it's not there, NULL and return error.
channel = ngx_http_push_stream_find_channel(id, r->connection->log);
NGX_HTTP_PUSH_STREAM_CHECK_AND_FINALIZE_REQUEST_ON_ERROR(channel, NULL, r, "push stream module: something goes very wrong, arrived on ngx_http_push_stream_publisher_body_handler without created channel");
// copy request body to a memory buffer
buf = ngx_create_temp_buf(r->pool, r->headers_in.content_length_n);
NGX_HTTP_PUSH_STREAM_CHECK_AND_FINALIZE_REQUEST_ON_ERROR(buf, NULL, r, "push stream module: cannot allocate memory for read the message");
chain = r->request_body->bufs;
while ((chain != NULL) && (chain->buf != NULL)) {
len = ngx_buf_size(chain->buf);
// if buffer is equal to content length all the content is in this buffer
if (len == r->headers_in.content_length_n) {
buf->start = buf->pos;
buf->last = buf->pos;
}
if (chain->buf->in_file) {
n = ngx_read_file(chain->buf->file, buf->start, len, 0);
if (n == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: cannot read file with request body");
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
buf->last = buf->last + len;
if ((id = ngx_http_push_stream_get_channel_id(r, cf)) == NULL) { ngx_delete_file(chain->buf->file->name.data);
ngx_http_finalize_request(r, r->headers_out.status ? NGX_OK : NGX_HTTP_INTERNAL_SERVER_ERROR); chain->buf->file->fd = NGX_INVALID_FILE;
return; } else {
buf->last = ngx_copy(buf->start, chain->buf->pos, len);
}
chain = chain->next;
buf->start = buf->last;
} }
// discard request body it is no longer needed
ngx_http_discard_request_body(r);
// format message
buf_msg = ngx_http_push_stream_get_formatted_message(cf, channel, buf, r->pool);
NGX_HTTP_PUSH_STREAM_CHECK_AND_FINALIZE_REQUEST_ON_ERROR(buf_msg, NULL, r, "push stream module: unable to format message");
ngx_shmtx_lock(&shpool->mutex); ngx_shmtx_lock(&shpool->mutex);
// POST requests will need a channel created if it doesn't yet exist.
if ((ngx_strstr(id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data) != (const char *) id->data) && (method == NGX_HTTP_POST || method == NGX_HTTP_PUT)) {
channel = ngx_http_push_stream_get_channel(id, r->connection->log);
NGX_HTTP_PUSH_STREAM_PUBLISHER_CHECK_LOCKED(channel, NULL, r, "push stream module: unable to allocate memory for new channel", shpool);
} else { // no other request method needs that.
// just find the channel. if it's not there, NULL.
channel = ngx_http_push_stream_find_channel(id, r->connection->log);
}
if (channel != NULL) { // create a buffer copy in shared mem
subscribers = channel->subscribers; msg = ngx_http_push_stream_convert_buffer_to_msg_on_shared_locked(buf_msg);
published_messages = channel->last_message_id; if (msg == NULL) {
stored_messages = channel->stored_messages; ngx_shmtx_unlock(&(shpool)->mutex);
} else if ((method != NGX_HTTP_GET) || (ngx_strstr(id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data) != (const char *) id->data)) { ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: unable to allocate message in shared memory");
// 404! ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
ngx_shmtx_unlock(&shpool->mutex);
r->headers_out.status = NGX_HTTP_NOT_FOUND;
// just the headers, please. we don't care to describe the situation or
// respond with an html page
r->headers_out.content_length_n = 0;
r->header_only = 1;
ngx_http_finalize_request(r, ngx_http_send_header(r));
return; return;
} }
ngx_shmtx_unlock(&shpool->mutex);
switch (method) {
ngx_http_push_stream_msg_t *msg;
ngx_http_push_stream_msg_t *sentinel;
case NGX_HTTP_POST:
// first off, we'll want to extract the body buffer
// note: this works mostly because of r->request_body_in_single_buf = 1;
// which, i suppose, makes this module a little slower than it could be.
// this block is a little hacky. might be a thorn for forward-compatibility.
if (r->headers_in.content_length_n == -1 || r->headers_in.content_length_n == 0) {
buf = ngx_create_temp_buf(r->pool, 0);
// this buffer will get copied to shared memory in a few lines,
// so it does't matter what pool we make it in.
} else if (r->request_body->bufs->buf != NULL) { // everything in the first buffer, please
buf = r->request_body->bufs->buf;
} else if (r->request_body->bufs->next != NULL) {
buf = r->request_body->bufs->next->buf;
} else {
ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: unexpected publisher message request body buffer location. please report this to the push stream module developers.");
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
if ((r->headers_in.content_length_n > 0) && (buf != NULL)) { channel->last_message_id++;
buf->last = buf->pos + r->headers_in.content_length_n; ((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->published_messages++;
*buf->last = '\0';
}
NGX_HTTP_PUSH_STREAM_PUBLISHER_CHECK(buf, NULL, r, "push stream module: can't find or allocate publisher request body buffer");
ngx_shmtx_lock(&shpool->mutex); // put messages on the queue
if (cf->store_messages) {
// set message expiration time
msg->expires = (cf->buffer_timeout == NGX_CONF_UNSET ? 0 : (ngx_time() + cf->buffer_timeout));
ngx_queue_insert_tail(&channel->message_queue.queue, &msg->queue);
channel->stored_messages++;
}
buf_msg = ngx_http_push_stream_get_formatted_message_locked(cf, channel, buf, r->pool); // now see if the queue is too big
ngx_http_push_stream_ensure_qtd_of_messages_locked(channel, cf->max_messages, 0, cf->memory_cleanup_timeout);
// create a buffer copy in shared mem ngx_shmtx_unlock(&shpool->mutex);
msg = ngx_http_push_stream_slab_alloc_locked(sizeof(*msg));
NGX_HTTP_PUSH_STREAM_PUBLISHER_CHECK_LOCKED(msg, NULL, r, "push stream module: unable to allocate message in shared memory", shpool);
buf_copy = ngx_http_push_stream_slab_alloc_locked(NGX_HTTP_PUSH_STREAM_BUF_ALLOC_SIZE(buf_msg)); // send an alert to workers
NGX_HTTP_PUSH_STREAM_PUBLISHER_CHECK_LOCKED(buf_copy, NULL, r, "push stream module: unable to allocate buffer in shared memory", shpool) // magic nullcheck ngx_http_push_stream_broadcast(channel, msg, r->connection->log);
ngx_http_push_stream_copy_preallocated_buffer(buf_msg, buf_copy);
msg->buf = buf_copy; // turn on timer to cleanup memory of old messages an channels
ngx_http_push_stream_memory_cleanup_timer_set(cf);
channel->last_message_id++; ngx_http_push_stream_send_response_channel_info(r, channel);
return;
}
if (cf->store_messages) { static ngx_int_t
ngx_queue_insert_tail(&channel->message_queue->queue, &msg->queue); push_stream_channels_statistics_handler(ngx_http_request_t *r)
channel->stored_messages++; {
} ngx_str_t *id = NULL;
ngx_http_push_stream_channel_t *channel = NULL;
ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
// only accept GET method
if (!(r->method & NGX_HTTP_GET)) {
ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL);
}
// set message expiration time // get and check channel id value
time_t message_timeout = cf->buffer_timeout; id = ngx_http_push_stream_get_channel_id(r, cf);
msg->expires = (message_timeout == 0 ? 0 : (ngx_time() + message_timeout)); if ((id == NULL) || (id == NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID)) {
msg->persistent = (message_timeout == 0 ? 1 : 0); if (id == NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID) {
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE);
// FMI (For My Information): shm is still locked. }
switch (ngx_http_push_stream_broadcast_message_locked(channel, msg, r->connection->log, shpool)) { return NGX_HTTP_INTERNAL_SERVER_ERROR;
case NGX_HTTP_PUSH_STREAM_MESSAGE_QUEUED: }
// message was queued successfully, but there were no
// subscribers to receive it.
r->headers_out.status = NGX_HTTP_ACCEPTED;
r->headers_out.status_line.len = sizeof("202 Accepted") - 1;
r->headers_out.status_line.data = (u_char *) "202 Accepted";
break;
case NGX_HTTP_PUSH_STREAM_MESSAGE_RECEIVED:
// message was queued successfully, and it was already sent
// to at least one subscriber
r->headers_out.status = NGX_HTTP_CREATED;
r->headers_out.status_line.len = sizeof("201 Created") - 1;
r->headers_out.status_line.data = (u_char *) "201 Created";
// update the number of times the message was received.
// in the interest of premature optimization, I assume all
// current subscribers have received the message successfully.
break;
case NGX_ERROR:
// WTF?
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: error broadcasting message to workers");
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
default:
// for debugging, mostly. I don't expect this branch to be
// hit during regular operation
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: TOTALLY UNEXPECTED error broadcasting message to workers");
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
// shm is still locked I hope.
if (buf->file != NULL) { // if not specify a channel id, get info about all channels in a resumed way
// future subscribers won't be able to use this file descriptor -- if (id == NGX_HTTP_PUSH_STREAM_UNSET_CHANNEL_ID) {
// it will be closed once the publisher request is finalized. return ngx_http_push_stream_send_response_all_channels_info_summarized(r);
// (That's about to happen a handful of lines below.) }
msg->buf->file->fd = NGX_INVALID_FILE;
}
// now see if the queue is too big // if specify a channel id equals to ALL, get info about all channels in a detailed way
if (channel->stored_messages > (ngx_uint_t) cf->max_messages) { if (ngx_memn2cmp(id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data, id->len, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.len) == 0) {
// exceeeds max queue size. force-delete oldest message return ngx_http_push_stream_send_response_all_channels_info_detailed(r);
ngx_http_push_stream_force_delete_message_locked(channel, ngx_http_push_stream_get_oldest_message_locked(channel), shpool); }
}
published_messages = channel->last_message_id;
stored_messages = channel->stored_messages;
ngx_shmtx_unlock(&shpool->mutex);
ngx_http_finalize_request(r, ngx_http_push_stream_channel_info(r, channel->id, published_messages, stored_messages, subscribers));
return;
case NGX_HTTP_PUT:
case NGX_HTTP_GET:
r->headers_out.status = NGX_HTTP_OK;
if (ngx_strstr(id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data) == (const char *) id->data) {
ngx_http_finalize_request(r, ngx_http_push_stream_all_channels_info(r));
} else {
ngx_http_finalize_request(r, ngx_http_push_stream_channel_info(r, channel->id, published_messages, stored_messages, subscribers));
}
return;
case NGX_HTTP_DELETE: // if specify a channel id != ALL, get info about specified channel if it exists
ngx_shmtx_lock(&shpool->mutex); // search for a existing channel with this id
sentinel = channel->message_queue; channel = ngx_http_push_stream_find_channel(id, r->connection->log);
msg = sentinel;
while ((msg = (ngx_http_push_stream_msg_t *) ngx_queue_next(&msg->queue)) != sentinel) { if (channel == NULL) {
// force-delete all the messages return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_FOUND, NULL);
ngx_http_push_stream_force_delete_message_locked(NULL, msg, shpool);
}
channel->last_message_id = 0;
channel->stored_messages = 0;
published_messages = channel->last_message_id;
stored_messages = channel->stored_messages;
// 410 gone
NGX_HTTP_PUSH_STREAM_PUBLISHER_CHECK_LOCKED(ngx_http_push_stream_broadcast_status_locked(channel, NGX_HTTP_GONE, &NGX_HTTP_PUSH_STREAM_HTTP_STATUS_410, r->connection->log, shpool), NGX_ERROR, r, "push stream module: unable to send current subscribers a 410 Gone response", shpool);
ngx_http_push_stream_delete_channel_locked(channel);
ngx_shmtx_unlock(&shpool->mutex);
// done.
r->headers_out.status = NGX_HTTP_OK;
ngx_http_finalize_request(r, ngx_http_push_stream_channel_info(r, channel->id, published_messages, stored_messages, subscribers));
return;
default:
// some other weird request method
ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET_POST_PUT_DELETE);
ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
return;
} }
return ngx_http_push_stream_send_response_channel_info(r, channel);
} }
...@@ -122,6 +122,12 @@ static ngx_command_t ngx_http_push_stream_commands[] = { ...@@ -122,6 +122,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, max_number_of_broadcast_channels), offsetof(ngx_http_push_stream_loc_conf_t, max_number_of_broadcast_channels),
NULL }, NULL },
{ ngx_string("push_stream_memory_cleanup_timeout"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_sec_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_push_stream_loc_conf_t, memory_cleanup_timeout),
NULL },
ngx_null_command ngx_null_command
}; };
...@@ -181,11 +187,12 @@ ngx_http_push_stream_init_worker(ngx_cycle_t *cycle) ...@@ -181,11 +187,12 @@ ngx_http_push_stream_init_worker(ngx_cycle_t *cycle)
static void static void
ngx_http_push_stream_exit_master(ngx_cycle_t *cycle) ngx_http_push_stream_exit_master(ngx_cycle_t *cycle)
{ {
ngx_http_push_stream_shm_data_t *data = (ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr; ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
// destroy channel tree in shared memory // destroy channel tree in shared memory
ngx_shmtx_lock(&shpool->mutex); ngx_http_push_stream_collect_expired_messages_and_empty_channels(&data->tree, shpool, data->tree.root, 1, 0);
ngx_http_push_stream_walk_rbtree(ngx_http_push_stream_movezig_channel_locked); ngx_http_push_stream_free_memory_of_expired_messages_and_channels(1);
ngx_shmtx_unlock(&shpool->mutex);
} }
...@@ -203,6 +210,10 @@ ngx_http_push_stream_exit_worker(ngx_cycle_t *cycle) ...@@ -203,6 +210,10 @@ ngx_http_push_stream_exit_worker(ngx_cycle_t *cycle)
ngx_del_timer(&ngx_http_push_stream_disconnect_event); ngx_del_timer(&ngx_http_push_stream_disconnect_event);
} }
if (ngx_http_push_stream_memory_cleanup_event.timer_set) {
ngx_del_timer(&ngx_http_push_stream_memory_cleanup_event);
}
ngx_http_push_stream_ipc_exit_worker(cycle); ngx_http_push_stream_ipc_exit_worker(cycle);
} }
...@@ -273,6 +284,8 @@ ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf) ...@@ -273,6 +284,8 @@ ngx_http_push_stream_create_loc_conf(ngx_conf_t *cf)
lcf->broadcast_channel_max_qtd = NGX_CONF_UNSET_UINT; lcf->broadcast_channel_max_qtd = NGX_CONF_UNSET_UINT;
lcf->max_number_of_channels = NGX_CONF_UNSET_UINT; lcf->max_number_of_channels = NGX_CONF_UNSET_UINT;
lcf->max_number_of_broadcast_channels = NGX_CONF_UNSET_UINT; lcf->max_number_of_broadcast_channels = NGX_CONF_UNSET_UINT;
lcf->memory_cleanup_interval = NGX_CONF_UNSET_MSEC;
lcf->memory_cleanup_timeout = NGX_CONF_UNSET;
return lcf; return lcf;
} }
...@@ -298,6 +311,9 @@ ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ...@@ -298,6 +311,9 @@ ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_uint_value(conf->broadcast_channel_max_qtd, prev->broadcast_channel_max_qtd, NGX_CONF_UNSET_UINT); ngx_conf_merge_uint_value(conf->broadcast_channel_max_qtd, prev->broadcast_channel_max_qtd, NGX_CONF_UNSET_UINT);
ngx_conf_merge_uint_value(conf->max_number_of_channels, prev->max_number_of_channels, NGX_CONF_UNSET_UINT); ngx_conf_merge_uint_value(conf->max_number_of_channels, prev->max_number_of_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->max_number_of_broadcast_channels, prev->max_number_of_broadcast_channels, NGX_CONF_UNSET_UINT);
ngx_conf_merge_uint_value(conf->memory_cleanup_interval, prev->memory_cleanup_interval, NGX_CONF_UNSET_MSEC);
ngx_conf_merge_sec_value(conf->memory_cleanup_timeout, prev->memory_cleanup_timeout, NGX_CONF_UNSET);
// sanity checks // sanity checks
// ping message interval cannot be zero // ping message interval cannot be zero
...@@ -396,6 +412,12 @@ ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ...@@ -396,6 +412,12 @@ ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
return NGX_CONF_ERROR; return NGX_CONF_ERROR;
} }
// memory cleanup timeout cannot't be small
if ((conf->memory_cleanup_timeout != NGX_CONF_UNSET) && (conf->memory_cleanup_timeout < NGX_HTTP_PUSH_STREAM_DEFAULT_MEMORY_CLEANUP_TIMEOUT)) {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "memory cleanup timeout cannot't be less than %d.", NGX_HTTP_PUSH_STREAM_DEFAULT_MEMORY_CLEANUP_TIMEOUT);
return NGX_CONF_ERROR;
}
// append crlf to templates // append crlf to templates
if (conf->header_template.len > 0) { if (conf->header_template.len > 0) {
conf->header_template.data = ngx_http_push_stream_append_crlf(&conf->header_template, cf->pool); conf->header_template.data = ngx_http_push_stream_append_crlf(&conf->header_template, cf->pool);
...@@ -407,6 +429,22 @@ ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ...@@ -407,6 +429,22 @@ ngx_http_push_stream_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
conf->message_template.len = ngx_strlen(conf->message_template.data); conf->message_template.len = ngx_strlen(conf->message_template.data);
} }
// calc memory cleanup interval
if (conf->buffer_timeout != NGX_CONF_UNSET) {
ngx_uint_t interval = conf->buffer_timeout / 3;
conf->memory_cleanup_interval = (interval > 1) ? interval * 1000 : 1000; // min 1 second
} else if (conf->memory_cleanup_interval == NGX_CONF_UNSET_MSEC) {
conf->memory_cleanup_interval = 1000; // 1 second
}
// create ping message
if ((conf->message_template.len > 0) && (ngx_http_push_stream_ping_buf == NULL)) {
if ((ngx_http_push_stream_ping_buf = ngx_http_push_stream_get_formatted_message(conf, NULL, NULL, cf->pool)) == NULL) {
ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "push stream module: unable to format ping message.");
return NGX_CONF_ERROR;
}
}
return NGX_CONF_OK; return NGX_CONF_OK;
} }
...@@ -499,7 +537,7 @@ ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data) ...@@ -499,7 +537,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; ngx_rbtree_node_t *sentinel, *remove_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.
...@@ -513,17 +551,10 @@ ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data) ...@@ -513,17 +551,10 @@ ngx_http_push_stream_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data)
} }
ngx_rbtree_init(&d->tree, sentinel, ngx_http_push_stream_rbtree_insert); ngx_rbtree_init(&d->tree, sentinel, ngx_http_push_stream_rbtree_insert);
return NGX_OK; if ((remove_sentinel = ngx_slab_alloc(shpool, sizeof(*remove_sentinel))) == NULL) {
} return NGX_ERROR;
static ngx_int_t
ngx_http_push_stream_movezig_channel_locked(ngx_http_push_stream_channel_t *channel, ngx_slab_pool_t *shpool)
{
ngx_http_push_stream_msg_t *sentinel = &channel->message_queue;
while (!ngx_queue_empty(&sentinel->queue)) {
ngx_http_push_stream_force_delete_message_locked(channel, (ngx_http_push_stream_msg_t *) ngx_queue_next(&sentinel->queue), shpool);
} }
ngx_rbtree_init(&d->channels_to_delete, remove_sentinel, ngx_http_push_stream_rbtree_insert);
return NGX_OK; return NGX_OK;
} }
#include <ngx_http_push_stream_module.h> #include <ngx_http_push_stream_module_subscriber.h>
static ngx_int_t
ngx_http_push_stream_subscriber_assign_channel_locked(ngx_slab_pool_t *shpool, ngx_http_push_stream_loc_conf_t *cf, ngx_http_request_t *r, ngx_str_t *id, ngx_uint_t backtrack_messages, ngx_queue_t *messages_to_sent_queue, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_pool_t *temp_pool)
{
ngx_http_push_stream_pid_queue_t *sentinel, *cur, *found;
ngx_http_push_stream_channel_t *channel;
ngx_http_push_stream_subscriber_t *subscriber;
ngx_http_push_stream_subscriber_t *subscriber_sentinel;
ngx_queue_t *node;
ngx_http_push_stream_subscription_t *subscription;
ngx_flag_t is_broadcast_channel = 0;
if ((cf->broadcast_channel_max_qtd > 0) && (cf->broadcast_channel_prefix.len > 0)) {
u_char *broad_pos = (u_char *) ngx_strstr(id->data, cf->broadcast_channel_prefix.data);
if ((broad_pos != NULL) && (broad_pos == id->data)) {
is_broadcast_channel = 1;
}
}
channel = (((cf->authorize_channel == 1) && (is_broadcast_channel == 0)) ? ngx_http_push_stream_find_channel : ngx_http_push_stream_get_channel) (id, r->connection->log);
if (channel == NULL) {
// unable to allocate channel OR channel not found
ngx_shmtx_unlock(&shpool->mutex);
if (cf->authorize_channel) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: not authorized to access channel %s", id->data);
return NGX_HTTP_FORBIDDEN;
} else {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate shared memory for channel %s", id->data);
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
}
if ((channel->stored_messages == 0) && !is_broadcast_channel && cf->authorize_channel) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: not authorized to access channel %s, channel is empty of messages", id->data);
return NGX_HTTP_FORBIDDEN;
}
sentinel = &channel->workers_with_subscribers;
cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_head(&sentinel->queue);
found = NULL;
while (cur != sentinel) {
if (cur->pid == ngx_pid) {
found = cur;
break;
}
cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&cur->queue);
}
if (found == NULL) { // found nothing
if ((found = ngx_http_push_stream_slab_alloc_locked(sizeof(*found))) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate worker subscriber queue marker in shared memory");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// initialize
ngx_queue_insert_tail(&sentinel->queue, &found->queue);
found->pid = ngx_pid;
found->slot = ngx_process_slot;
found->subscriber_sentinel = NULL;
}
// figure out the subscriber sentinel
subscriber_sentinel = ((ngx_http_push_stream_pid_queue_t *) found)->subscriber_sentinel;
if (subscriber_sentinel == NULL) {
// it's perfectly nornal for the sentinel to be NULL
if ((subscriber_sentinel=ngx_http_push_stream_slab_alloc_locked(sizeof(*subscriber_sentinel))) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate channel subscriber sentinel");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_queue_init(&subscriber_sentinel->queue);
((ngx_http_push_stream_pid_queue_t *) found)->subscriber_sentinel=subscriber_sentinel;
}
if ((subscription = ngx_palloc(r->pool, sizeof(*subscription))) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate subscribed channel reference");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if ((subscriber = ngx_palloc(r->pool, sizeof(*subscriber))) == NULL) { // unable to allocate request queue element
return NGX_ERROR;
}
subscriber->request = r;
subscription->channel = channel;
subscription->subscriber = subscriber;
channel->subscribers++; // do this only when we know everything went okay
// get old messages to send to new subscriber
if (channel->stored_messages > 0) {
node = ngx_queue_last(&channel->message_queue->queue);
ngx_uint_t qtd = (backtrack_messages > channel->stored_messages) ? channel->stored_messages : backtrack_messages;
while (qtd > 0) {
ngx_http_push_stream_msg_queue_t *message = NULL;
if ((message = ngx_palloc(temp_pool, sizeof(*message))) != NULL) {
message->msg = (ngx_http_push_stream_msg_t *) node;
ngx_queue_insert_head(messages_to_sent_queue, &message->queue);
}
node = ngx_queue_prev(node);
qtd--;
}
}
ngx_queue_insert_tail(&subscriptions_sentinel->queue, &subscription->queue);
ngx_queue_insert_tail(&subscriber_sentinel->queue, &subscriber->queue);
return NGX_OK;
}
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)
{ {
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_slab_pool_t *shpool = (ngx_slab_pool_t *)ngx_http_push_stream_shm_zone->shm.addr;
ngx_str_t *id, *channels_path; ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
ngx_http_push_stream_worker_subscriber_t *worker_subscriber = NULL;
ngx_http_push_stream_subscriber_cleanup_t *clndata;
ngx_pool_cleanup_t *cln; ngx_pool_cleanup_t *cln;
ngx_http_push_stream_subscriber_cleanup_t *clndata;
ngx_http_push_stream_worker_subscriber_t *worker_subscriber;
ngx_http_push_stream_requested_channel_t *channels_ids, *cur;
ngx_pool_t *temp_pool; ngx_pool_t *temp_pool;
ngx_queue_t messages_to_sent_queue; ngx_uint_t subscribed_channels_qtd = 0;
ngx_http_variable_value_t *vv_channels_path = ngx_http_get_indexed_variable(r, cf->index_channels_path); ngx_uint_t subscribed_broadcast_channels_qtd = 0;
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;
if (vv_channels_path == NULL || vv_channels_path->not_found || vv_channels_path->len == 0) { ngx_flag_t is_broadcast_channel;
ngx_http_push_stream_send_response_channel_id_not_provided(r); ngx_http_push_stream_channel_t *channel;
return NGX_HTTP_NOT_FOUND;
// only accept GET method
if (!(r->method & NGX_HTTP_GET)) {
ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_NOT_ALLOWED, NULL);
} }
if (r->method != NGX_HTTP_GET) { ngx_http_discard_request_body(r);
ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_ALLOW, &NGX_HTTP_PUSH_STREAM_ALLOW_GET); // valid HTTP for teh win
return NGX_HTTP_NOT_ALLOWED;
}
//create a temporary pool to allocate temporary elements
if ((temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, r->connection->log)) == NULL) { if ((temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, r->connection->log)) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for temporary pool"); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for temporary pool");
return NGX_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if ((id = ngx_pcalloc(temp_pool, sizeof(*id) + vv_channels_path->len + 1)) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for channel_id string");
return NGX_ERROR;
}
id->data = (u_char *) (id + 1);
if ((channels_path = ngx_pcalloc(temp_pool, sizeof(*channels_path) + vv_channels_path->len + 1)) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for channels_path string");
return NGX_ERROR;
} }
channels_path->data = (u_char *) (channels_path + 1); //attach a cleaner to remove the request from the channel
channels_path->len = vv_channels_path->len; if ((cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_push_stream_subscriber_cleanup_t))) == NULL) {
ngx_memcpy(channels_path->data, vv_channels_path->data, vv_channels_path->len); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for cleanup");
if ((worker_subscriber=ngx_palloc(r->pool, sizeof(*worker_subscriber))) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate worker subscriber");
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }
if ((worker_subscriber->subscriptions_sentinel = ngx_palloc(r->pool, sizeof(*worker_subscriber->subscriptions_sentinel))) == NULL) { if ((worker_subscriber = ngx_pcalloc(r->pool, sizeof(ngx_http_push_stream_worker_subscriber_t))) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate subscribed channels sentinel"); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate worker subscriber");
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }
worker_subscriber->request = r; worker_subscriber->request = r;
worker_subscriber->worker_subscribed_pid = ngx_pid; worker_subscriber->worker_subscribed_pid = ngx_pid;
time_t subscriber_timeout = cf->subscriber_connection_timeout; worker_subscriber->expires = (cf->subscriber_connection_timeout == NGX_CONF_UNSET) ? 0 : (ngx_time() + cf->subscriber_connection_timeout);
worker_subscriber->expires = ((subscriber_timeout == NGX_CONF_UNSET) || (subscriber_timeout == 0)) ? 0 : (ngx_time() + subscriber_timeout);
ngx_queue_init(&worker_subscriber->queue); ngx_queue_init(&worker_subscriber->queue);
ngx_queue_init(&worker_subscriber->subscriptions_sentinel->queue); ngx_queue_init(&worker_subscriber->subscriptions_sentinel.queue);
// attach a cleaner to remove the request from the channel //get channels ids and backtracks from path
if ((cln = ngx_pool_cleanup_add(r->pool, sizeof(*clndata))) == NULL) { // make sure we can channels_ids = ngx_http_push_stream_parse_channels_ids_from_path(r, temp_pool);
return NGX_ERROR; if ((channels_ids == NULL) || ngx_queue_empty(&channels_ids->queue)) {
ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: the $push_stream_channel_path variable is required but is not set");
ngx_destroy_pool(temp_pool);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_MESSAGE);
} }
cln->handler = (ngx_pool_cleanup_pt) ngx_http_push_stream_subscriber_cleanup; //validate channels: name, length and quantity. check if channel exists when authorized_channels_only is on
clndata = (ngx_http_push_stream_subscriber_cleanup_t *) cln->data; cur = channels_ids;
clndata->worker_subscriber = worker_subscriber; while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) {
worker_subscriber->clndata = clndata; // could not be ALL channel
if (ngx_memn2cmp(cur->id->data, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.data, cur->id->len, NGX_HTTP_PUSH_STREAM_ALL_CHANNELS_INFO_ID.len) == 0) {
ngx_queue_init(&messages_to_sent_queue); ngx_destroy_pool(temp_pool);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_NO_CHANNEL_ID_NOT_AUTHORIZED_MESSAGE);
ngx_shmtx_lock(&shpool->mutex); }
u_char *channel_pos = channels_path->data;
u_char *end = NULL, *slash_pos = NULL;
ngx_uint_t len = 0;
ngx_uint_t backtrack_messages = 0;
ngx_uint_t subscribed_channels_qtd = 0;
ngx_uint_t subscribed_broadcast_channels_qtd = 0;
// doing the parser of given channel path
while (channel_pos != NULL) {
end = channels_path->data + channels_path->len;
slash_pos = (u_char *) ngx_strstr(channel_pos, NGX_HTTP_PUSH_STREAM_SLASH.data); // could not have a large size
if (slash_pos != NULL) { if ((cf->max_channel_id_length != NGX_CONF_UNSET_UINT) && (cur->id->len > cf->max_channel_id_length)) {
end = slash_pos; ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, "push stream module: channel id is larger than allowed %d", cur->id->len);
ngx_destroy_pool(temp_pool);
return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_BAD_REQUEST, &NGX_HTTP_PUSH_STREAM_TOO_LARGE_CHANNEL_ID_MESSAGE);
} }
backtrack_messages = 0; // count subscribed channel and boradcasts
len = end - channel_pos; subscribed_channels_qtd++;
is_broadcast_channel = 0;
if ((cf->broadcast_channel_prefix.len > 0) && (ngx_strncmp(cur->id->data, cf->broadcast_channel_prefix.data, cf->broadcast_channel_prefix.len) == 0)) {
is_broadcast_channel = 1;
subscribed_broadcast_channels_qtd++;
}
u_char *backtrack_pos = (u_char *) ngx_strstr(channel_pos, NGX_HTTP_PUSH_STREAM_BACKTRACK_SEP.data); // check if channel exists when authorized_channels_only is on
if ((backtrack_pos != NULL) && (end > backtrack_pos)) { if (cf->authorized_channels_only && !is_broadcast_channel && (((channel = ngx_http_push_stream_find_channel(cur->id, r->connection->log)) == NULL) || (channel->stored_messages == 0))) {
len = backtrack_pos - channel_pos; ngx_destroy_pool(temp_pool);
backtrack_pos = backtrack_pos + NGX_HTTP_PUSH_STREAM_BACKTRACK_SEP.len; return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_CANNOT_CREATE_CHANNELS);
if (end > backtrack_pos) {
backtrack_messages = ngx_atoi(backtrack_pos, end - backtrack_pos);
}
} }
}
if (len > 0) { // check if number of subscribed broadcast channels is acceptable
backtrack_messages = (backtrack_messages > 0) ? backtrack_messages : 0; if ((cf->broadcast_channel_max_qtd != NGX_CONF_UNSET_UINT) && (subscribed_broadcast_channels_qtd > 0) && ((subscribed_broadcast_channels_qtd > cf->broadcast_channel_max_qtd) || (subscribed_broadcast_channels_qtd == subscribed_channels_qtd))) {
id->len = len; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: max subscribed broadcast channels exceeded");
ngx_memcpy(id->data, channel_pos, len); ngx_destroy_pool(temp_pool);
*(id->data + id->len) = '\0'; return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_TOO_MUCH_BROADCAST_CHANNELS);
}
ngx_int_t ret = ngx_http_push_stream_subscriber_assign_channel_locked(shpool, cf, r, id, backtrack_messages, &messages_to_sent_queue, worker_subscriber->subscriptions_sentinel, temp_pool);
if (ret != NGX_OK) {
// if get here the shpool already is unlocked
ngx_destroy_pool(temp_pool);
return ret;
}
subscribed_channels_qtd++; // create the channels in advance, if doesn't exist, to ensure max number of channels in the server
if (cf->broadcast_channel_prefix.len > 0) { cur = channels_ids;
u_char *broad_pos = (u_char *) ngx_strstr(channel_pos, cf->broadcast_channel_prefix.data); while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) {
if ((broad_pos != NULL) && (broad_pos == channel_pos)) { channel = ngx_http_push_stream_get_channel(cur->id, r->connection->log, cf);
subscribed_broadcast_channels_qtd++; if (channel == NULL) {
if (subscribed_broadcast_channels_qtd > (ngx_uint_t)cf->broadcast_channel_max_qtd) { ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: unable to allocate memory for new channel");
ngx_shmtx_unlock(&shpool->mutex); return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_INTERNAL_SERVER_ERROR, NULL);
ngx_destroy_pool(temp_pool);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: max subscribed broadcast channels exceeded");
return NGX_HTTP_FORBIDDEN;
}
}
}
} }
channel_pos = NULL; if (channel == NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED) {
if (slash_pos != NULL) { ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: number of channels were exceeded");
channel_pos = slash_pos + NGX_HTTP_PUSH_STREAM_SLASH.len; return ngx_http_push_stream_send_only_header_response(r, NGX_HTTP_FORBIDDEN, &NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED_MESSAGE);
} }
} }
ngx_shmtx_unlock(&shpool->mutex);
if ((subscribed_channels_qtd - subscribed_broadcast_channels_qtd) == 0) { // set a cleaner to subscriber
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: subscribe broadcast channel whithout subscribe a common channel"); cln->handler = (ngx_pool_cleanup_pt) ngx_http_push_stream_subscriber_cleanup;
ngx_destroy_pool(temp_pool); clndata = (ngx_http_push_stream_subscriber_cleanup_t *) cln->data;
return NGX_HTTP_FORBIDDEN; clndata->worker_subscriber = worker_subscriber;
} clndata->worker_subscriber->clndata = clndata;
// responding subscriber
r->read_event_handler = ngx_http_test_reading; r->read_event_handler = ngx_http_test_reading;
r->write_event_handler = ngx_http_request_empty_handler; r->write_event_handler = ngx_http_request_empty_handler;
r->discard_body = 1; r->discard_body = 1;
...@@ -271,199 +125,237 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r) ...@@ -271,199 +125,237 @@ ngx_http_push_stream_subscriber_handler(ngx_http_request_t *r)
r->headers_out.status = NGX_HTTP_OK; r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = -1; r->headers_out.content_length_n = -1;
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_send_header(r); ngx_http_send_header(r);
#if defined nginx_version && nginx_version >= 8053
r->keepalive = 1; r->keepalive = 1;
#else // sending response content header
r->keepalive = 0; if (ngx_http_push_stream_send_response_content_header(r, cf) == NGX_ERROR) {
#endif ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: could not send content header to subscriber");
ngx_http_push_stream_send_body_header(r, cf); ngx_destroy_pool(temp_pool);
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_shmtx_lock(&shpool->mutex); ngx_shmtx_lock(&shpool->mutex);
ngx_queue_insert_tail(&thisworker_data->worker_subscribers_sentinel->queue, &worker_subscriber->queue);
ngx_shmtx_unlock(&shpool->mutex);
ngx_http_push_stream_ping_timer_set(cf); // adding subscriber to woker list of subscribers
ngx_http_push_stream_disconnect_timer_set(cf); ngx_queue_insert_tail(&thisworker_data->worker_subscribers_sentinel.queue, &worker_subscriber->queue);
// send old messages to subscriber
if (&messages_to_sent_queue != ngx_queue_next(&messages_to_sent_queue)) {
ngx_chain_t *chain = NULL;
ngx_int_t rc = NGX_OK;
NGX_HTTP_PUSH_STREAM_MAKE_IN_MEMORY_CHAIN(chain, temp_pool, "push stream module: unable to allocate chain to send old messages to new subscriber");
ngx_queue_t *message = ngx_queue_next(&messages_to_sent_queue); // increment global subscribers count
while (&messages_to_sent_queue != message) { ((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->subscribers++;
ngx_http_push_stream_msg_t *msg = ((ngx_http_push_stream_msg_queue_t *) message)->msg;
chain->buf->pos = msg->buf->pos;
chain->buf->last = msg->buf->last;
chain->buf->start = msg->buf->start;
chain->buf->end = msg->buf->end;
rc = ngx_http_output_filter(r, chain); ngx_shmtx_unlock(&shpool->mutex);
if (rc == NGX_OK) {
rc = ngx_http_send_special(r, NGX_HTTP_FLUSH);
}
if (rc != NGX_OK) {
break;
}
message = ngx_queue_next(message); // adding subscriber to channel(s) and send backtrack messages
cur = channels_ids;
while ((cur = (ngx_http_push_stream_requested_channel_t *) ngx_queue_next(&cur->queue)) != channels_ids) {
if (ngx_http_push_stream_subscriber_assign_channel(shpool, cf, r, cur, &worker_subscriber->subscriptions_sentinel, temp_pool) != NGX_OK) {
ngx_destroy_pool(temp_pool);
return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }
} }
ngx_destroy_pool(temp_pool); // setting disconnect and ping timer
ngx_http_push_stream_disconnect_timer_set(cf);
ngx_http_push_stream_ping_timer_set(cf);
// turn on timer to cleanup memory of old messages an channels
ngx_http_push_stream_memory_cleanup_timer_set(cf);
ngx_destroy_pool(temp_pool);
return NGX_DONE; return NGX_DONE;
} }
static ngx_int_t static ngx_int_t
ngx_http_push_stream_broadcast_locked(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg, ngx_int_t status_code, const ngx_str_t *status_line, ngx_log_t *log, ngx_slab_pool_t *shpool) 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, ngx_http_push_stream_subscription_t *subscriptions_sentinel, ngx_pool_t *temp_pool)
{ {
// subscribers are queued up in a local pool. Queue heads, however, are located ngx_http_push_stream_pid_queue_t *sentinel, *cur, *found;
// in shared memory, identified by pid. ngx_http_push_stream_channel_t *channel;
ngx_http_push_stream_pid_queue_t *sentinel = &channel->workers_with_subscribers; ngx_http_push_stream_subscriber_t *subscriber;
ngx_http_push_stream_pid_queue_t *cur = sentinel; ngx_http_push_stream_subscriber_t *subscriber_sentinel;
ngx_int_t received; ngx_http_push_stream_msg_t *message, *message_sentinel;
ngx_http_push_stream_subscription_t *subscription;
received = channel->subscribers > 0 ? NGX_HTTP_PUSH_STREAM_MESSAGE_RECEIVED : NGX_HTTP_PUSH_STREAM_MESSAGE_QUEUED; channel = ngx_http_push_stream_get_channel(requested_channel->id, r->connection->log, cf);
if (channel == NULL) {
// unable to allocate channel OR channel not found
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate shared memory for channel %s", requested_channel->id->data);
return NGX_ERROR;
}
if ((msg != NULL) && (received == NGX_HTTP_PUSH_STREAM_MESSAGE_RECEIVED)) { if (channel == NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED) {
ngx_http_push_stream_reserve_message_locked(channel, msg); ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, "push stream module: number of channels were exceeded");
return NGX_ERROR;
} }
while ((cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&cur->queue)) != sentinel) {
pid_t worker_pid = cur->pid;
ngx_int_t worker_slot = cur->slot;
ngx_http_push_stream_subscriber_t *subscriber_sentinel= cur->subscriber_sentinel;
ngx_shmtx_unlock(&shpool->mutex); sentinel = &channel->workers_with_subscribers;
cur = sentinel;
// interprocess communication breakdown found = NULL;
if (ngx_http_push_stream_send_worker_message(channel, subscriber_sentinel, worker_pid, worker_slot, msg, status_code, log) != NGX_ERROR) { while ((cur = (ngx_http_push_stream_pid_queue_t *) ngx_queue_next(&cur->queue)) != sentinel) {
ngx_http_push_stream_alert_worker(worker_pid, worker_slot, log); if (cur->pid == ngx_pid) {
} else { found = cur;
ngx_log_error(NGX_LOG_ERR, log, 0, "push stream module: error communicating with some other worker process"); break;
} }
ngx_shmtx_lock(&shpool->mutex);
} }
return received; if (found == NULL) { // found nothing
} ngx_shmtx_lock(&shpool->mutex);
if ((found = ngx_slab_alloc_locked(shpool, sizeof(ngx_http_push_stream_pid_queue_t))) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate worker subscriber queue marker in shared memory");
return NGX_ERROR;
}
// initialize
ngx_queue_insert_tail(&sentinel->queue, &found->queue);
static ngx_int_t found->pid = ngx_pid;
ngx_http_push_stream_respond_to_subscribers(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_subscriber_t *sentinel, ngx_http_push_stream_msg_t *msg, ngx_int_t status_code, const ngx_str_t *status_line) found->slot = ngx_process_slot;
{ ngx_queue_init(&found->subscriber_sentinel.queue);
ngx_slab_pool_t *shpool = ngx_http_push_stream_shpool; ngx_shmtx_unlock(&shpool->mutex);
ngx_http_push_stream_subscriber_t *cur, *next; }
ngx_int_t responded_subscribers = 0;
if ((subscription = ngx_palloc(r->pool, sizeof(ngx_http_push_stream_subscription_t))) == NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate subscribed channel reference");
return NGX_ERROR;
}
if (sentinel == NULL) { if ((subscriber = ngx_palloc(r->pool, sizeof(ngx_http_push_stream_subscriber_t))) == NULL) { // unable to allocate request queue element
return NGX_OK; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate subscribed channel reference");
return NGX_ERROR;
} }
cur = (ngx_http_push_stream_subscriber_t *) ngx_queue_head(&sentinel->queue); subscriber_sentinel = &found->subscriber_sentinel;
if (msg != NULL) { subscriber->request = r;
// copy everything we need first
ngx_chain_t *chain;
ngx_http_request_t *r;
ngx_buf_t *buffer;
u_char *pos;
ngx_pool_t *temp_pool;
ngx_shmtx_lock(&shpool->mutex); subscription->channel = channel;
subscription->subscriber = subscriber;
if ((temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, ngx_http_push_stream_pool->log)) == NULL) { // send old messages to new subscriber
ngx_shmtx_unlock(&shpool->mutex); if (channel->stored_messages > 0) {
ngx_log_error(NGX_LOG_ERR, ngx_http_push_stream_pool->log, 0, "push stream module: unable to allocate memory for temporary pool"); message_sentinel = &channel->message_queue;
return NGX_ERROR; message = message_sentinel;
ngx_uint_t qtd = (requested_channel->backtrack_messages > channel->stored_messages) ? channel->stored_messages : requested_channel->backtrack_messages;
ngx_uint_t start = channel->stored_messages - qtd;
// positioning at first message, and send the others
while ((qtd > 0) && (!message->deleted) && ((message = (ngx_http_push_stream_msg_t *) ngx_queue_next(&message->queue)) != message_sentinel)) {
if (start == 0) {
ngx_str_t msg = ngx_string(message->buf->pos);
msg.len = ngx_buf_size(message->buf);
ngx_http_push_stream_send_response_chunk(r, &msg, 0);
qtd--;
} else {
start--;
}
} }
}
// preallocate output chain. yes, same one for every waiting subscriber ngx_shmtx_lock(&shpool->mutex);
if ((chain = ngx_http_push_stream_create_output_chain_locked(msg->buf, temp_pool, ngx_cycle->log, shpool)) == NULL) { channel->subscribers++; // do this only when we know everything went okay
ngx_shmtx_unlock(&shpool->mutex); ngx_queue_insert_tail(&subscriptions_sentinel->queue, &subscription->queue);
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "push stream module: unable to create output chain while responding to several subscriber request"); ngx_queue_insert_tail(&subscriber_sentinel->queue, &subscriber->queue);
ngx_destroy_pool(temp_pool); ngx_shmtx_unlock(&shpool->mutex);
return NGX_ERROR;
} return NGX_OK;
}
buffer = chain->buf; ngx_http_push_stream_requested_channel_t *
pos = buffer->pos; ngx_http_push_stream_parse_channels_ids_from_path(ngx_http_request_t *r, ngx_pool_t *pool) {
ngx_http_push_stream_loc_conf_t *cf = ngx_http_get_module_loc_conf(r, ngx_http_push_stream_module);
ngx_http_variable_value_t *vv_channels_path = ngx_http_get_indexed_variable(r, cf->index_channels_path);
ngx_http_push_stream_requested_channel_t *channels_ids, *cur;
u_char *channel_pos, *slash_pos, *backtrack_pos;
ngx_uint_t len, backtrack_messages;
ngx_str_t *channels_path;
ngx_shmtx_unlock(&shpool->mutex); if (vv_channels_path == NULL || vv_channels_path->not_found || vv_channels_path->len == 0) {
buffer->last_buf = 0; return NULL;
}
// now let's respond to some requests! // make channels_path one unit larger than vv_channels_path to have allways a \0 in the end
while (cur != sentinel) { if ((channels_path = ngx_pcalloc(pool, sizeof(ngx_str_t) + vv_channels_path->len + 1)) == NULL) {
next = (ngx_http_push_stream_subscriber_t *) ngx_queue_next(&cur->queue); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for channels_path string");
return NULL;
}
// in this block, nothing in shared memory should be dereferenced if ((channels_ids = ngx_pcalloc(pool, sizeof(ngx_http_push_stream_requested_channel_t))) == NULL) {
r = cur->request; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for channels_ids queue");
return NULL;
}
r->discard_body = 0; // hacky hacky! channels_path->data = (u_char *) (channels_path + 1);
chain->buf->flush = 1; channels_path->len = vv_channels_path->len;
ngx_memcpy(channels_path->data, vv_channels_path->data, vv_channels_path->len);
ngx_http_output_filter(r, chain); ngx_queue_init(&channels_ids->queue);
ngx_http_send_special(r, NGX_HTTP_FLUSH);
responded_subscribers++; channel_pos = channels_path->data;
// rewind the buffer, please // doing the parser of given channel path
buffer->pos = pos; while (channel_pos != NULL) {
buffer->last_buf = 0; backtrack_messages = 0;
len = 0;
cur = next; backtrack_pos = (u_char *) ngx_strstr(channel_pos, NGX_HTTP_PUSH_STREAM_BACKTRACK_SEP.data);
} slash_pos = (u_char *) ngx_strstr(channel_pos, NGX_HTTP_PUSH_STREAM_SLASH.data);
// free everything relevant if ((backtrack_pos != NULL) && (slash_pos != NULL)) {
if (buffer->file) { if (slash_pos > backtrack_pos) {
ngx_close_file(buffer->file->fd); len = backtrack_pos - channel_pos;
backtrack_pos = backtrack_pos + NGX_HTTP_PUSH_STREAM_BACKTRACK_SEP.len;
if (slash_pos > backtrack_pos) {
backtrack_messages = ngx_atoi(backtrack_pos, slash_pos - backtrack_pos);
}
} else {
len = slash_pos - channel_pos;
}
} else if (backtrack_pos != NULL) {
len = backtrack_pos - channel_pos;
backtrack_pos = backtrack_pos + NGX_HTTP_PUSH_STREAM_BACKTRACK_SEP.len;
if ((channels_path->data + channels_path->len) > backtrack_pos) {
backtrack_messages = ngx_atoi(backtrack_pos, (channels_path->data + channels_path->len) - backtrack_pos);
}
} else if (slash_pos != NULL) {
len = slash_pos - channel_pos;
} else {
len = channels_path->data + channels_path->len - channel_pos;
} }
if (responded_subscribers && !msg->persistent) { if (len > 0) {
ngx_shmtx_lock(&shpool->mutex);
// message deletion if ((cur = ngx_pcalloc(pool, sizeof(ngx_http_push_stream_requested_channel_t))) == NULL) {
ngx_http_push_stream_release_message_locked(channel, msg); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for channel_id item");
ngx_shmtx_unlock(&shpool->mutex); return NULL;
} }
ngx_destroy_pool(temp_pool);
} else {
// headers only probably
ngx_http_request_t *r;
while (cur != sentinel) { if ((cur->id = ngx_pcalloc(pool, sizeof(ngx_str_t) + len)) == NULL) {
next = (ngx_http_push_stream_subscriber_t *) ngx_queue_next(&cur->queue); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "push stream module: unable to allocate memory for channel_id string");
r = cur->request; return NULL;
}
cur->id->data = (u_char *) (cur->id + 1);
cur->id->len = len;
ngx_memcpy(cur->id->data, channel_pos, len);
cur->backtrack_messages = (backtrack_messages > 0) ? backtrack_messages : 0;
// cleanup oughtn't dequeue anything. or decrement the subscriber count, for that matter ngx_queue_insert_tail(&channels_ids->queue, &cur->queue);
cur->clndata->worker_subscriber = NULL; }
ngx_http_push_stream_respond_status_only(r, status_code, status_line);
cur = next; channel_pos = NULL;
if (slash_pos != NULL) {
channel_pos = slash_pos + NGX_HTTP_PUSH_STREAM_SLASH.len;
} }
} }
return NGX_OK; return channels_ids;
} }
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)
{ {
if (data->worker_subscriber != NULL) { if (data->worker_subscriber != NULL) {
ngx_shmtx_lock(&ngx_http_push_stream_shpool->mutex); ngx_http_push_stream_worker_subscriber_cleanup(data->worker_subscriber);
ngx_http_push_stream_worker_subscriber_cleanup_locked(data->worker_subscriber);
ngx_shmtx_unlock(&ngx_http_push_stream_shpool->mutex);
} }
} }
#include <ngx_http_push_stream_module.h> #include <ngx_http_push_stream_module_utils.h>
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, time_t memory_cleanup_timeout) {
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_msg_t *sentinel, *msg;
#define NGX_HTTP_PUSH_STREAM_BUF_ALLOC_SIZE(buf) \ sentinel = &channel->message_queue;
(sizeof(*buf) + \
(((buf)->temporary || (buf)->memory) ? ngx_buf_size(buf) : 0) + \
(((buf)->file!=NULL) ? (sizeof(*(buf)->file) + (buf)->file->name.len + 1) : 0))
#define NGX_HTTP_PUSH_STREAM_PUBLISHER_CHECK(val, fail, r, errormessage) \
if (val == fail) { \
ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, errormessage); \
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); \
return; \
}
#define NGX_HTTP_PUSH_STREAM_PUBLISHER_CHECK_LOCKED(val, fail, r, errormessage, shpool) \ while (!ngx_queue_empty(&sentinel->queue) && ((channel->stored_messages > max_messages) || expired)) {
if (val == fail) { \ msg = (ngx_http_push_stream_msg_t *)ngx_queue_next(&sentinel->queue);
ngx_shmtx_unlock(&(shpool)->mutex); \
ngx_log_error(NGX_LOG_ERR, (r)->connection->log, 0, errormessage); \
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); \
return; \
}
#define NGX_HTTP_PUSH_STREAM_MAKE_IN_MEMORY_CHAIN(chain, pool, errormessage) \ if (expired && msg->expires > ngx_time()) {
if (chain == NULL) { \ break;
ngx_buf_t *buffer; \ }
chain = ngx_pcalloc(pool, sizeof(ngx_chain_t)); \
buffer = ngx_pcalloc(pool, sizeof(ngx_buf_t)); \ msg->deleted = 1;
if ((chain == NULL) || (buffer == NULL)) { \ msg->expires = ngx_time() + memory_cleanup_timeout;
ngx_log_error(NGX_LOG_ERR, pool->log, 0, errormessage); \ channel->stored_messages--;
return NGX_ERROR; \ ngx_queue_remove(&msg->queue);
} \ ngx_queue_insert_tail(&data->messages_to_delete.queue, &msg->queue);
buffer->pos = NULL; \
buffer->temporary = 0; \
buffer->memory = 1; \
buffer->last_buf = 0; \
chain->buf = buffer; \
chain->next = NULL; \
} }
}
// buffer is _copied_ ngx_http_push_stream_msg_t *
// if shpool is provided, it is assumed that shm it is locked ngx_http_push_stream_convert_buffer_to_msg_on_shared_locked(ngx_buf_t *buf)
static ngx_chain_t *
ngx_http_push_stream_create_output_chain_general(ngx_buf_t *buf, ngx_pool_t *pool, ngx_log_t *log, ngx_slab_pool_t *shpool)
{ {
ngx_chain_t *out; ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
ngx_file_t *file; ngx_http_push_stream_msg_t *msg;
off_t len;
len = ngx_buf_size(buf);
if ((out = ngx_pcalloc(pool, sizeof(*out))) == NULL) { msg = ngx_slab_alloc_locked(shpool, sizeof(ngx_http_push_stream_msg_t));
if (msg == NULL) {
return NULL; return NULL;
} }
ngx_buf_t *buf_copy;
if ((buf_copy = ngx_pcalloc(pool, NGX_HTTP_PUSH_STREAM_BUF_ALLOC_SIZE(buf))) == NULL) { msg->buf = ngx_slab_alloc_locked(shpool, sizeof(ngx_buf_t));
if (msg->buf == NULL) {
ngx_slab_free_locked(shpool, msg);
return NULL; return NULL;
} }
ngx_http_push_stream_copy_preallocated_buffer(buf, buf_copy);
msg->buf->start = ngx_slab_alloc_locked(shpool, len);
if (buf->file != NULL) { if (msg->buf->start == NULL) {
file = buf_copy->file; ngx_slab_free_locked(shpool, msg);
file->log = log; ngx_slab_free_locked(shpool, msg->buf);
if (file->fd == NGX_INVALID_FILE) { return NULL;
if (shpool) {
ngx_shmtx_unlock(&shpool->mutex);
file->fd = ngx_open_file(file->name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, NGX_FILE_OWNER_ACCESS);
ngx_shmtx_lock(&shpool->mutex);
} else {
file->fd = ngx_open_file(file->name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, NGX_FILE_OWNER_ACCESS);
}
}
if (file->fd == NGX_INVALID_FILE) {
return NULL;
}
} }
buf_copy->last_buf = 1;
out->buf = buf_copy;
out->next = NULL;
return out; // copy the message to shared memory
} msg->buf->last = ngx_copy(msg->buf->start, buf->pos, len);
msg->buf->pos = msg->buf->start;
msg->buf->end = msg->buf->last + len;
msg->buf->temporary = 1;
msg->buf->memory = 1;
static void return msg;
ngx_http_push_stream_copy_preallocated_buffer(ngx_buf_t *buf, ngx_buf_t *cbuf)
{
if (cbuf != NULL) {
ngx_memcpy(cbuf, buf, sizeof(*buf)); // overkill?
if (buf->temporary || buf->memory) { // we don't want to copy mmpapped memory, so no ngx_buf_in_momory(buf)
cbuf->pos = (u_char *) (cbuf + 1);
cbuf->last = cbuf->pos + ngx_buf_size(buf);
cbuf->start = cbuf->pos;
cbuf->end = cbuf->start + ngx_buf_size(buf);
ngx_memcpy(cbuf->pos, buf->pos, ngx_buf_size(buf));
cbuf->memory = ngx_buf_in_memory_only(buf) ? 1 : 0;
}
if (buf->file != NULL) {
cbuf->file = (ngx_file_t *) (cbuf + 1) + ((buf->temporary || buf->memory) ? ngx_buf_size(buf) : 0);
cbuf->file->fd = NGX_INVALID_FILE;
cbuf->file->log = NULL;
cbuf->file->offset = buf->file->offset;
cbuf->file->sys_offset = buf->file->sys_offset;
cbuf->file->name.len = buf->file->name.len;
cbuf->file->name.data = (u_char *) (cbuf->file + 1);
ngx_memcpy(cbuf->file->name.data, buf->file->name.data, buf->file->name.len);
}
}
} }
// remove a message from queue and free all associated memory static ngx_int_t
// assumes shpool is already locked ngx_http_push_stream_send_only_header_response(ngx_http_request_t *r, ngx_int_t status_code, const ngx_str_t *explain_error_message)
static ngx_inline void
ngx_http_push_stream_general_delete_message_locked(ngx_http_push_stream_channel_t *channel, ngx_http_push_stream_msg_t *msg, ngx_int_t force, ngx_slab_pool_t *shpool)
{ {
if (msg == NULL) { ngx_int_t rc;
return;
ngx_http_discard_request_body(r);
r->discard_body = 1;
r->keepalive = 0;
r->header_only = 1;
r->headers_out.content_length_n = 0;
r->headers_out.status = status_code;
if (explain_error_message != NULL) {
ngx_http_push_stream_add_response_header(r, &NGX_HTTP_PUSH_STREAM_HEADER_EXPLAIN, explain_error_message);
} }
if (!msg->persistent) {
if (channel != NULL) { rc = ngx_http_send_header(r);
ngx_queue_remove(&msg->queue);
channel->stored_messages--; if (rc > NGX_HTTP_SPECIAL_RESPONSE) {
} return NGX_HTTP_INTERNAL_SERVER_ERROR;
if (msg->refcount <= 0 || force) {
// nobody needs this message, or we were forced at integer-point to delete
ngx_http_push_stream_free_message_locked(msg, shpool);
}
} }
}
return rc;
}
// free memory for a message static ngx_table_elt_t *
static ngx_inline void ngx_http_push_stream_add_response_header(ngx_http_request_t *r, const ngx_str_t *header_name, const ngx_str_t *header_value)
ngx_http_push_stream_free_message_locked(ngx_http_push_stream_msg_t *msg, ngx_slab_pool_t *shpool)
{ {
if (msg->buf->file != NULL) { ngx_table_elt_t *h = ngx_list_push(&r->headers_out.headers);
ngx_shmtx_unlock(&shpool->mutex);
if (msg->buf->file->fd != NGX_INVALID_FILE) {
ngx_close_file(msg->buf->file->fd); if (h == NULL) {
} return NULL;
ngx_delete_file(msg->buf->file->name.data); // should I care about deletion errors? doubt it.
ngx_shmtx_lock(&shpool->mutex);
} }
ngx_slab_free_locked(shpool, msg->buf); // separate block, remember? h->hash = 1;
ngx_slab_free_locked(shpool, msg); h->key.len = header_name->len;
} h->key.data = header_name->data;
h->value.len = header_value->len;
h->value.data = header_value->data;
return h;
}
// garbage-collecting slab allocator static ngx_int_t
void * ngx_http_push_stream_send_response_content_header(ngx_http_request_t *r, ngx_http_push_stream_loc_conf_t *pslcf)
ngx_http_push_stream_slab_alloc_locked(size_t size)
{ {
void *p; ngx_int_t rc = NGX_OK;
if ((p = ngx_slab_alloc_locked(ngx_http_push_stream_shpool, size)) == NULL) {
ngx_http_push_stream_channel_queue_t *ccur, *cnext;
ngx_uint_t collected = 0;
// failed. emergency garbage sweep, then collect channels
ngx_queue_init(&channel_gc_sentinel.queue);
ngx_http_push_stream_walk_rbtree(ngx_http_push_stream_channel_collector);
for(ccur=(ngx_http_push_stream_channel_queue_t *) ngx_queue_next(&channel_gc_sentinel.queue); ccur!=&channel_gc_sentinel; ccur=cnext) {
cnext = (ngx_http_push_stream_channel_queue_t *) ngx_queue_next(&ccur->queue);
ngx_http_push_stream_delete_channel_locked(ccur->channel);
ngx_free(ccur);
collected++;
}
// TODO: collect worker messages maybe if (pslcf->header_template.len > 0) {
#if (NGX_DEBUG) rc = ngx_http_push_stream_send_response_chunk(r, &pslcf->header_template, 0);
// only enable this log in debug mode
ngx_log_error(NGX_LOG_WARN, ngx_cycle->log, 0, "push module: out of shared memory. emergency garbage collection deleted %ui unused channels.", collected);
#endif
return ngx_slab_alloc_locked(ngx_http_push_stream_shpool, size);
} }
return p; return rc;
} }
static ngx_int_t
//shpool must be locked. No memory is freed. O(1) ngx_http_push_stream_send_response_chunk(ngx_http_request_t *r, const ngx_str_t *chunk_text, ngx_flag_t las_buffer)
static ngx_http_push_stream_msg_t *
ngx_http_push_stream_get_oldest_message_locked(ngx_http_push_stream_channel_t *channel)
{ {
ngx_queue_t *sentinel = &channel->message_queue->queue; ngx_buf_t *b;
ngx_chain_t *out;
if (chunk_text == NULL) {
return NGX_ERROR;
}
if (ngx_queue_empty(sentinel)) { out = (ngx_chain_t *) ngx_pcalloc(r->pool, sizeof(ngx_chain_t));
return NULL; b = ngx_calloc_buf(r->pool);
if ((out == NULL) || (b == NULL)) {
return NGX_ERROR;
} }
ngx_queue_t *qmsg = ngx_queue_head(sentinel); b->last_buf = las_buffer;
b->flush = 1;
b->memory = 1;
b->pos = chunk_text->data;
b->start = b->pos;
b->end = b->pos + chunk_text->len;
b->last = b->end;
return ngx_queue_data(qmsg, ngx_http_push_stream_msg_t, queue); out->buf = b;
} out->next = NULL;
return ngx_http_output_filter(r, out);
}
static ngx_int_t static ngx_int_t
ngx_http_push_stream_channel_collector(ngx_http_push_stream_channel_t *channel, ngx_slab_pool_t *shpool) ngx_http_push_stream_send_ping(ngx_log_t *log, ngx_http_push_stream_loc_conf_t *pslcf)
{ {
if ((ngx_http_push_stream_clean_channel_locked(channel)) != NULL) { // we're up for deletion if (pslcf->message_template.len > 0) {
ngx_http_push_stream_channel_queue_t *trashy; ngx_http_push_stream_alert_worker_send_ping(ngx_pid, ngx_process_slot, ngx_cycle->log);
if ((trashy = ngx_alloc(sizeof(*trashy), ngx_cycle->log)) != NULL) {
// yeah, i'm allocating memory during garbage collection. sue me.
trashy->channel = channel;
ngx_queue_insert_tail(&channel_gc_sentinel.queue, &trashy->queue);
return NGX_OK;
}
return NGX_ERROR;
} }
return NGX_OK; return NGX_OK;
} }
static ngx_table_elt_t * static void
ngx_http_push_stream_add_response_header(ngx_http_request_t *r, const ngx_str_t *header_name, const ngx_str_t *header_value) ngx_http_push_stream_collect_expired_messages_and_empty_channels(ngx_rbtree_t *tree, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force, time_t memory_cleanup_timeout)
{ {
ngx_table_elt_t *h = ngx_list_push(&r->headers_out.headers); ngx_http_push_stream_shm_data_t *data = (ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data;
ngx_rbtree_node_t *sentinel;
ngx_http_push_stream_channel_t *channel;
sentinel = tree->sentinel;
if (h == NULL) { if (node != sentinel) {
return NULL;
}
h->hash = 1;
h->key.len = header_name->len;
h->key.data = header_name->data;
h->value.len = header_value->len;
h->value.data = header_value->data;
return h; if (node->left != NULL) {
ngx_http_push_stream_collect_expired_messages_and_empty_channels(tree, shpool, node->left, force, memory_cleanup_timeout);
}
if (node->right != NULL) {
ngx_http_push_stream_collect_expired_messages_and_empty_channels(tree, shpool, node->right, force, memory_cleanup_timeout);
}
ngx_shmtx_lock(&shpool->mutex);
channel = (ngx_http_push_stream_channel_t *) node;
ngx_http_push_stream_ensure_qtd_of_messages_locked(channel, (force) ? 0 : channel->stored_messages, 1, memory_cleanup_timeout);
if ((channel->stored_messages == 0) && (channel->subscribers == 0)) {
channel->deleted = 1;
channel->expires = ngx_time() + memory_cleanup_timeout;
(channel->broadcast) ? data->broadcast_channels-- : data->channels--;
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->channels_to_delete, (ngx_rbtree_node_t *) channel);
}
ngx_shmtx_unlock(&shpool->mutex);
}
} }
static ngx_int_t static void
ngx_http_push_stream_send_body_header(ngx_http_request_t *r, ngx_http_push_stream_loc_conf_t *pslcf) ngx_http_push_stream_free_memory_of_expired_channels_locked(ngx_rbtree_t *tree, ngx_slab_pool_t *shpool, ngx_rbtree_node_t *node, ngx_flag_t force)
{ {
ngx_int_t rc = NGX_OK; ngx_rbtree_node_t *sentinel;
ngx_http_push_stream_channel_t *channel;
if (pslcf->header_template.len > 0) { sentinel = tree->sentinel;
ngx_http_push_stream_header_chain->buf->pos = pslcf->header_template.data;
ngx_http_push_stream_header_chain->buf->last = pslcf->header_template.data + pslcf->header_template.len;
ngx_http_push_stream_header_chain->buf->start = ngx_http_push_stream_header_chain->buf->pos;
ngx_http_push_stream_header_chain->buf->end = ngx_http_push_stream_header_chain->buf->last;
rc = ngx_http_output_filter(r, ngx_http_push_stream_header_chain);
if (rc == NGX_OK) { if (node != sentinel) {
ngx_http_push_stream_crlf_chain->buf->pos = NGX_HTTP_PUSH_STREAM_CRLF.data;
ngx_http_push_stream_crlf_chain->buf->last = NGX_HTTP_PUSH_STREAM_CRLF.data + NGX_HTTP_PUSH_STREAM_CRLF.len;
ngx_http_push_stream_crlf_chain->buf->start = ngx_http_push_stream_crlf_chain->buf->pos;
ngx_http_push_stream_crlf_chain->buf->end = ngx_http_push_stream_crlf_chain->buf->last;
rc = ngx_http_output_filter(r, ngx_http_push_stream_crlf_chain); if (node->left != NULL) {
ngx_http_push_stream_free_memory_of_expired_channels_locked(tree, shpool, node->left, force);
}
if (rc == NGX_OK) { if (node->right != NULL) {
rc = ngx_http_send_special(r, NGX_HTTP_FLUSH); ngx_http_push_stream_free_memory_of_expired_channels_locked(tree, shpool, node->right, force);
}
channel = (ngx_http_push_stream_channel_t *) node;
if ((ngx_time() > channel->expires) || force) {
ngx_rbtree_delete(tree, node);
// delete the worker-subscriber queue
ngx_queue_t *workers_sentinel = (ngx_queue_t *) &channel->workers_with_subscribers;
ngx_queue_t *cur = ngx_queue_head(workers_sentinel);
ngx_queue_t *next;
while (cur != workers_sentinel) {
next = ngx_queue_next(cur);
ngx_slab_free_locked(shpool, cur);
cur = next;
} }
ngx_slab_free_locked(shpool, node);
} }
} }
return rc;
} }
static ngx_int_t static ngx_int_t
ngx_http_push_stream_send_ping(ngx_log_t *log, ngx_http_push_stream_loc_conf_t *pslcf) ngx_http_push_stream_memory_cleanup(ngx_log_t *log, ngx_http_push_stream_loc_conf_t *pslcf)
{ {
if (pslcf->message_template.len > 0) { ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
if (ngx_http_push_stream_ping_buf == NULL) { ngx_http_push_stream_shm_data_t *data = (ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
ngx_shmtx_lock(&shpool->mutex);
if (ngx_http_push_stream_ping_buf == NULL) {
ngx_buf_t *buf = NULL;
ngx_pool_t *temp_pool = NULL;
if ((temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log)) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, log, 0, "push stream module: unable to allocate memory for temporary pool");
return NGX_ERROR;
}
if ((buf = ngx_http_push_stream_get_formatted_message_locked(pslcf, NULL, NULL, temp_pool)) == NULL) { ngx_http_push_stream_collect_expired_messages_and_empty_channels(&data->tree, shpool, data->tree.root, 0, pslcf->memory_cleanup_timeout);
ngx_shmtx_unlock(&shpool->mutex); ngx_http_push_stream_free_memory_of_expired_messages_and_channels(0);
ngx_log_error(NGX_LOG_ERR, ngx_http_push_stream_pool->log, 0, "push stream module: unable to format ping message");
ngx_destroy_pool(temp_pool);
return NGX_ERROR;
}
if ((ngx_http_push_stream_ping_buf = ngx_http_push_stream_slab_alloc_locked(NGX_HTTP_PUSH_STREAM_BUF_ALLOC_SIZE(buf))) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(NGX_LOG_ERR, ngx_http_push_stream_pool->log, 0, "push stream module: unable to allocate memory for formatted ping message");
ngx_destroy_pool(temp_pool);
return NGX_ERROR;
}
ngx_http_push_stream_copy_preallocated_buffer(buf, ngx_http_push_stream_ping_buf);
ngx_http_push_stream_ping_msg->buf = ngx_http_push_stream_ping_buf;
ngx_destroy_pool(temp_pool);
}
ngx_shmtx_unlock(&shpool->mutex);
}
ngx_http_push_stream_alert_worker_send_ping(ngx_pid, ngx_process_slot, ngx_http_push_stream_pool->log);
}
return NGX_OK; return NGX_OK;
} }
static void static ngx_int_t
ngx_http_push_stream_ping_timer_wake_handler(ngx_event_t *ev) ngx_http_push_stream_free_memory_of_expired_messages_and_channels(ngx_flag_t force)
{ {
ngx_http_push_stream_loc_conf_t *pslcf = ev->data; 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_msg_t *sentinel, *cur, *next;
sentinel = &data->messages_to_delete;
cur = (ngx_http_push_stream_msg_t *)ngx_queue_next(&sentinel->queue);
ngx_http_push_stream_send_ping(ev->log, pslcf); ngx_shmtx_lock(&shpool->mutex);
ngx_http_push_stream_ping_timer_reset(pslcf);
while (cur != sentinel) {
next = (ngx_http_push_stream_msg_t *)ngx_queue_next(&cur->queue);
if ((ngx_time() > cur->expires) || force) {
ngx_queue_remove(&cur->queue);
ngx_slab_free_locked(shpool, cur->buf->start);
ngx_slab_free_locked(shpool, cur->buf);
ngx_slab_free_locked(shpool, cur);
}
cur = next;
}
ngx_http_push_stream_free_memory_of_expired_channels_locked(&data->channels_to_delete, shpool, data->channels_to_delete.root, force);
ngx_shmtx_unlock(&shpool->mutex);
return NGX_OK;
} }
static void static void
ngx_http_push_stream_ping_timer_set(ngx_http_push_stream_loc_conf_t *pslcf) ngx_http_push_stream_ping_timer_set(ngx_http_push_stream_loc_conf_t *pslcf)
{ {
if ((pslcf->message_template.len > 0) && (pslcf->ping_message_interval != NGX_CONF_UNSET_MSEC)) { if (pslcf->ping_message_interval != NGX_CONF_UNSET_MSEC) {
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;
if (ngx_http_push_stream_ping_event.handler == NULL) { if (ngx_http_push_stream_ping_event.handler == NULL) {
...@@ -327,44 +290,47 @@ ngx_http_push_stream_ping_timer_set(ngx_http_push_stream_loc_conf_t *pslcf) ...@@ -327,44 +290,47 @@ ngx_http_push_stream_ping_timer_set(ngx_http_push_stream_loc_conf_t *pslcf)
if (ngx_http_push_stream_ping_event.handler == NULL) { if (ngx_http_push_stream_ping_event.handler == NULL) {
ngx_http_push_stream_ping_event.handler = ngx_http_push_stream_ping_timer_wake_handler; ngx_http_push_stream_ping_event.handler = ngx_http_push_stream_ping_timer_wake_handler;
ngx_http_push_stream_ping_event.data = pslcf; ngx_http_push_stream_ping_event.data = pslcf;
ngx_http_push_stream_ping_event.log = ngx_http_push_stream_pool->log; ngx_http_push_stream_ping_event.log = ngx_cycle->log;
ngx_http_push_stream_ping_timer_reset(pslcf); ngx_http_push_stream_timer_reset(pslcf->ping_message_interval, &ngx_http_push_stream_ping_event);
} }
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
} }
} }
} }
static void static void
ngx_http_push_stream_ping_timer_reset(ngx_http_push_stream_loc_conf_t *pslcf) ngx_http_push_stream_disconnect_timer_set(ngx_http_push_stream_loc_conf_t *pslcf)
{ {
if ((pslcf->message_template.len > 0) && (pslcf->ping_message_interval != NGX_CONF_UNSET_MSEC)) { if (pslcf->subscriber_disconnect_interval != NGX_CONF_UNSET_MSEC) {
if (ngx_http_push_stream_ping_event.timedout) { ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
#if defined nginx_version && nginx_version >= 7066
ngx_time_update(); if (ngx_http_push_stream_disconnect_event.handler == NULL) {
#else ngx_shmtx_lock(&shpool->mutex);
ngx_time_update(0, 0); if (ngx_http_push_stream_disconnect_event.handler == NULL) {
#endif ngx_http_push_stream_disconnect_event.handler = ngx_http_push_stream_disconnect_timer_wake_handler;
ngx_http_push_stream_disconnect_event.data = pslcf;
ngx_http_push_stream_disconnect_event.log = ngx_cycle->log;
ngx_http_push_stream_timer_reset(pslcf->subscriber_disconnect_interval, &ngx_http_push_stream_disconnect_event);
}
ngx_shmtx_unlock(&shpool->mutex);
} }
ngx_add_timer(&ngx_http_push_stream_ping_event, pslcf->ping_message_interval);
} }
} }
static void static void
ngx_http_push_stream_disconnect_timer_set(ngx_http_push_stream_loc_conf_t *pslcf) ngx_http_push_stream_memory_cleanup_timer_set(ngx_http_push_stream_loc_conf_t *pslcf)
{ {
if (pslcf->subscriber_disconnect_interval != NGX_CONF_UNSET_MSEC) { if (pslcf->memory_cleanup_interval != NGX_CONF_UNSET_MSEC) {
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;
if (ngx_http_push_stream_disconnect_event.handler == NULL) { if (ngx_http_push_stream_memory_cleanup_event.handler == NULL) {
ngx_shmtx_lock(&shpool->mutex); ngx_shmtx_lock(&shpool->mutex);
if (ngx_http_push_stream_disconnect_event.handler == NULL) { if (ngx_http_push_stream_memory_cleanup_event.handler == NULL) {
ngx_http_push_stream_disconnect_event.handler = ngx_http_push_stream_disconnect_timer_wake_handler; ngx_http_push_stream_memory_cleanup_event.handler = ngx_http_push_stream_memory_cleanup_timer_wake_handler;
ngx_http_push_stream_disconnect_event.data = pslcf; ngx_http_push_stream_memory_cleanup_event.data = pslcf;
ngx_http_push_stream_disconnect_event.log = ngx_http_push_stream_pool->log; ngx_http_push_stream_memory_cleanup_event.log = ngx_cycle->log;
ngx_http_push_stream_disconnect_timer_reset(pslcf); ngx_http_push_stream_timer_reset(pslcf->memory_cleanup_interval, &ngx_http_push_stream_memory_cleanup_event);
} }
ngx_shmtx_unlock(&shpool->mutex); ngx_shmtx_unlock(&shpool->mutex);
} }
...@@ -373,34 +339,52 @@ ngx_http_push_stream_disconnect_timer_set(ngx_http_push_stream_loc_conf_t *pslcf ...@@ -373,34 +339,52 @@ ngx_http_push_stream_disconnect_timer_set(ngx_http_push_stream_loc_conf_t *pslcf
static void static void
ngx_http_push_stream_disconnect_timer_reset(ngx_http_push_stream_loc_conf_t *pslcf) ngx_http_push_stream_timer_reset(ngx_msec_t timer_interval, ngx_event_t *timer_event)
{ {
if (pslcf->subscriber_disconnect_interval != NGX_CONF_UNSET_MSEC) { if (timer_interval != NGX_CONF_UNSET_MSEC) {
if (ngx_http_push_stream_disconnect_event.timedout) { if (timer_event->timedout) {
#if defined nginx_version && nginx_version >= 7066 #if defined nginx_version && nginx_version >= 7066
ngx_time_update(); ngx_time_update();
#else #else
ngx_time_update(0, 0); ngx_time_update(0, 0);
#endif #endif
} }
ngx_add_timer(&ngx_http_push_stream_disconnect_event, pslcf->subscriber_disconnect_interval); ngx_add_timer(timer_event, timer_interval);
} }
} }
static void
ngx_http_push_stream_ping_timer_wake_handler(ngx_event_t *ev)
{
ngx_http_push_stream_loc_conf_t *pslcf = ev->data;
ngx_http_push_stream_send_ping(ev->log, pslcf);
ngx_http_push_stream_timer_reset(pslcf->ping_message_interval, &ngx_http_push_stream_ping_event);
}
static void static void
ngx_http_push_stream_disconnect_timer_wake_handler(ngx_event_t *ev) ngx_http_push_stream_disconnect_timer_wake_handler(ngx_event_t *ev)
{ {
ngx_http_push_stream_loc_conf_t *pslcf = ev->data; ngx_http_push_stream_loc_conf_t *pslcf = ev->data;
ngx_http_push_stream_alert_worker_disconnect_subscribers(ngx_pid, ngx_process_slot, ngx_cycle->log);
ngx_http_push_stream_timer_reset(pslcf->subscriber_disconnect_interval, &ngx_http_push_stream_disconnect_event);
}
static void
ngx_http_push_stream_memory_cleanup_timer_wake_handler(ngx_event_t *ev)
{
ngx_http_push_stream_loc_conf_t *pslcf = ev->data;
ngx_http_push_stream_alert_worker_disconnect_subscribers(ngx_pid, ngx_process_slot, ngx_http_push_stream_pool->log); ngx_http_push_stream_memory_cleanup(ev->log, pslcf);
ngx_http_push_stream_disconnect_timer_reset(pslcf); ngx_http_push_stream_timer_reset(pslcf->memory_cleanup_interval, &ngx_http_push_stream_memory_cleanup_event);
} }
static u_char * static u_char *
ngx_http_push_stream_str_replace_locked(u_char *org, u_char *find, u_char *replace, ngx_pool_t *pool) ngx_http_push_stream_str_replace(u_char *org, u_char *find, u_char *replace, ngx_pool_t *pool)
{ {
ngx_uint_t len_org = ngx_strlen(org); ngx_uint_t len_org = ngx_strlen(org);
ngx_uint_t len_find = ngx_strlen(find); ngx_uint_t len_find = ngx_strlen(find);
...@@ -418,7 +402,7 @@ ngx_http_push_stream_str_replace_locked(u_char *org, u_char *find, u_char *repla ...@@ -418,7 +402,7 @@ ngx_http_push_stream_str_replace_locked(u_char *org, u_char *find, u_char *repla
ngx_memcpy(tmp + len_found, replace, len_replace); ngx_memcpy(tmp + len_found, replace, len_replace);
ngx_memcpy(tmp + len_found + len_replace, org + len_found + len_find, len_org - len_found - len_find); ngx_memcpy(tmp + len_found + len_replace, org + len_found + len_find, len_org - len_found - len_find);
result = ngx_http_push_stream_str_replace_locked(tmp, find, replace, pool); result = ngx_http_push_stream_str_replace(tmp, find, replace, pool);
} }
} }
...@@ -427,81 +411,133 @@ ngx_http_push_stream_str_replace_locked(u_char *org, u_char *find, u_char *repla ...@@ -427,81 +411,133 @@ ngx_http_push_stream_str_replace_locked(u_char *org, u_char *find, u_char *repla
static ngx_buf_t * static ngx_buf_t *
ngx_http_push_stream_get_formatted_message_locked(ngx_http_push_stream_loc_conf_t *pslcf, ngx_http_push_stream_channel_t *channel, ngx_buf_t *buf, ngx_pool_t *pool) ngx_http_push_stream_get_formatted_message(ngx_http_push_stream_loc_conf_t *pslcf, ngx_http_push_stream_channel_t *channel, ngx_buf_t *buf, ngx_pool_t *pool)
{ {
if (buf != NULL) { ngx_uint_t len = 0;
// ensure the final string in a reusable buffer u_char *txt = NULL;
*buf->last = '\0';
buf->temporary = 1;
buf->memory = 1;
}
if (pslcf->message_template.len > 0) { if (pslcf->message_template.len > 0) {
u_char template_with_crlf[pslcf->message_template.len + NGX_HTTP_PUSH_STREAM_CRLF.len + 1]; u_char template[pslcf->message_template.len + 1];
ngx_memcpy(template_with_crlf, pslcf->message_template.data, pslcf->message_template.len); ngx_memcpy(template, pslcf->message_template.data, pslcf->message_template.len);
ngx_memcpy(template_with_crlf + pslcf->message_template.len, NGX_HTTP_PUSH_STREAM_CRLF.data, NGX_HTTP_PUSH_STREAM_CRLF.len); template[pslcf->message_template.len] = '\0';
template_with_crlf[pslcf->message_template.len + NGX_HTTP_PUSH_STREAM_CRLF.len] = '\0';
u_char char_id[10]; u_char char_id[NGX_INT_T_LEN];
u_char *msg = NGX_PUSH_STREAM_PING_MESSAGE_TEXT.data, *channel_id = NGX_PUSH_STREAM_PING_CHANNEL_ID.data; u_char *msg = NGX_PUSH_STREAM_PING_MESSAGE_TEXT.data;
u_char *channel_id = NGX_PUSH_STREAM_PING_CHANNEL_ID.data;
if ((channel != NULL) && (buf != NULL)) { if ((channel != NULL) && (buf != NULL)) {
ngx_memzero(char_id, sizeof(char_id)); ngx_memzero(char_id, NGX_INT_T_LEN);
ngx_sprintf(char_id, "%d", channel->last_message_id + 1); ngx_sprintf(char_id, "%d", channel->last_message_id + 1);
msg = buf->pos; msg = ngx_pcalloc(pool, ngx_buf_size(buf) + 1);
ngx_memcpy(msg, buf->pos, ngx_buf_size(buf));
channel_id = channel->id.data; channel_id = channel->id.data;
} else { } else {
ngx_memcpy(char_id, NGX_PUSH_STREAM_PING_MESSAGE_ID.data, NGX_PUSH_STREAM_PING_MESSAGE_ID.len + 1); ngx_memcpy(char_id, NGX_PUSH_STREAM_PING_MESSAGE_ID.data, NGX_PUSH_STREAM_PING_MESSAGE_ID.len + 1);
} }
u_char *txt = ngx_http_push_stream_str_replace_locked(template_with_crlf, NGX_PUSH_STREAM_TOKEN_MESSAGE_ID.data, char_id, pool); txt = ngx_http_push_stream_str_replace(template, NGX_PUSH_STREAM_TOKEN_MESSAGE_ID.data, char_id, pool);
txt = ngx_http_push_stream_str_replace_locked(txt, NGX_PUSH_STREAM_TOKEN_MESSAGE_CHANNEL.data, channel_id, pool); txt = ngx_http_push_stream_str_replace(txt, NGX_PUSH_STREAM_TOKEN_MESSAGE_CHANNEL.data, channel_id, pool);
txt = ngx_http_push_stream_str_replace_locked(txt, NGX_PUSH_STREAM_TOKEN_MESSAGE_TEXT.data, msg, pool); txt = ngx_http_push_stream_str_replace(txt, NGX_PUSH_STREAM_TOKEN_MESSAGE_TEXT.data, msg, pool);
ngx_buf_t *buf_msg = ngx_calloc_buf(pool); len = ngx_strlen(txt);
buf_msg->pos = txt; buf = ngx_calloc_buf(pool);
buf_msg->last = buf_msg->pos + ngx_strlen(txt);
buf_msg->start = buf_msg->pos;
buf_msg->end = buf_msg->last;
buf_msg->temporary = 1;
buf_msg->memory = 1;
return buf_msg;
} else if (buf != NULL) { } else if (buf != NULL) {
ngx_uint_t len_org = ngx_buf_size(buf); ngx_str_t msg = ngx_string(buf->pos);
ngx_uint_t len = len_org + NGX_HTTP_PUSH_STREAM_CRLF.len; msg.len = ngx_buf_size(buf);
u_char *txt_with_crlf = ngx_pcalloc(pool, len); txt = ngx_http_push_stream_append_crlf(&msg, pool);
ngx_memcpy(txt_with_crlf, buf->pos, len_org); len = ngx_strlen(txt);
ngx_memcpy(txt_with_crlf + len_org, NGX_HTTP_PUSH_STREAM_CRLF.data, NGX_HTTP_PUSH_STREAM_CRLF.len); }
buf->pos = txt_with_crlf; // global adjusts
if (buf != NULL) {
buf->pos = txt;
buf->last = buf->pos + len; buf->last = buf->pos + len;
buf->start = buf->pos; buf->start = buf->pos;
buf->end = buf->last; buf->end = buf->last;
*buf->last = '\0'; buf->temporary = 1;
buf->memory = 1;
} }
return buf; return buf;
} }
static void static void
ngx_http_push_stream_worker_subscriber_cleanup_locked(ngx_http_push_stream_worker_subscriber_t *worker_subscriber) ngx_http_push_stream_worker_subscriber_cleanup(ngx_http_push_stream_worker_subscriber_t *worker_subscriber)
{ {
ngx_http_push_stream_subscription_t *cur, *next, *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_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) {
cur = (ngx_http_push_stream_subscription_t *) ngx_queue_head(&sentinel->queue);
while (cur != sentinel) {
next = (ngx_http_push_stream_subscription_t *) ngx_queue_next(&cur->queue);
cur->channel->subscribers--; cur->channel->subscribers--;
ngx_queue_remove(&cur->subscriber->queue); ngx_queue_remove(&cur->subscriber->queue);
ngx_queue_remove(&cur->queue); ngx_queue_remove(&cur->queue);
cur = next;
} }
ngx_queue_init(&sentinel->queue); ngx_queue_init(&sentinel->queue);
ngx_queue_remove(&worker_subscriber->queue); ngx_queue_remove(&worker_subscriber->queue);
ngx_queue_init(&worker_subscriber->queue); ngx_queue_init(&worker_subscriber->queue);
worker_subscriber->clndata->worker_subscriber = NULL; worker_subscriber->clndata->worker_subscriber = NULL;
((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->subscribers--;
ngx_shmtx_unlock(&shpool->mutex);
}
u_char *
ngx_http_push_stream_append_crlf(const ngx_str_t *str, ngx_pool_t *pool)
{
u_char *last, *result;
ngx_str_t crlf = ngx_string(CRLF);
result = ngx_pcalloc(pool, str->len + crlf.len + 1);
last = ngx_copy(result, str->data, str->len);
last = ngx_copy(last, crlf.data, crlf.len);
return result;
}
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_uint_t i;
ngx_http_push_stream_content_subtype_t *subtype = &subtypes[default_subtype];
if (r->headers_in.accept) {
u_char *cur = r->headers_in.accept->value.data;
size_t rem = 0;
while((cur != NULL) && (cur = ngx_strnstr(cur, "/", r->headers_in.accept->value.len)) != NULL) {
cur = cur + 1;
rem = r->headers_in.accept->value.len - (r->headers_in.accept->value.data - cur);
for(i=0; i<(sizeof(subtypes) / sizeof(ngx_http_push_stream_content_subtype_t)); i++) {
if (ngx_strncmp(cur, subtypes[i].subtype, rem < subtypes[i].len ? rem : subtypes[i].len) == 0) {
subtype = &subtypes[i];
// force break while
cur = NULL;
break;
}
}
}
}
return subtype;
}
static ngx_str_t *
ngx_http_push_stream_get_formatted_current_time(ngx_pool_t *pool)
{
ngx_tm_t tm;
ngx_str_t *currenttime;
currenttime = (ngx_str_t *) ngx_pcalloc(pool, sizeof(ngx_str_t) + 20); //ISO 8601 pattern plus 1
if (currenttime != NULL) {
currenttime->data = (u_char *) currenttime + sizeof(ngx_str_t);
ngx_gmtime(ngx_time(), &tm);
ngx_sprintf(currenttime->data, (char *) NGX_PUSH_STREAM_DATE_FORMAT_ISO_8601.data, tm.ngx_tm_year, tm.ngx_tm_mon, tm.ngx_tm_mday, tm.ngx_tm_hour, tm.ngx_tm_min, tm.ngx_tm_sec);
currenttime->len = ngx_strlen(currenttime->data);
} else {
currenttime = &NGX_HTTP_PUSH_STREAM_EMPTY;
}
return currenttime;
} }
#include <ngx_http_push_stream_module.h> #include <ngx_http_push_stream_rbtree_util.h>
static void ngx_rbtree_generic_insert(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel, int (*compare) (const ngx_rbtree_node_t *left, const ngx_rbtree_node_t *right));
static void ngx_http_push_stream_rbtree_insert(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
static int ngx_http_push_stream_compare_rbtree_node(const ngx_rbtree_node_t *v_left, const ngx_rbtree_node_t *v_right);
static ngx_int_t ngx_http_push_stream_delete_node_locked(ngx_rbtree_t *tree, ngx_rbtree_node_t *trash, ngx_slab_pool_t *shpool);
static ngx_http_push_stream_channel_t *
ngx_http_push_stream_clean_channel_locked(ngx_http_push_stream_channel_t *channel)
{
ngx_queue_t *sentinel = &channel->message_queue->queue;
time_t now = ngx_time();
ngx_http_push_stream_msg_t *msg = NULL;
while (!ngx_queue_empty(sentinel)) {
msg = ngx_queue_data(ngx_queue_head(sentinel), ngx_http_push_stream_msg_t, queue);
if (msg != NULL && msg->expires != 0 && now > msg->expires) {
ngx_http_push_stream_delete_message_locked(channel, msg, ngx_http_push_stream_shpool);
} else { // definitely a message left to send
return NULL;
}
}
// at this point, the queue is empty
return channel->subscribers == 0 ? channel : NULL; // if no waiting requests, return this channel to be deleted
}
static ngx_int_t
ngx_http_push_stream_delete_channel_locked(ngx_http_push_stream_channel_t *trash)
{
ngx_int_t res;
res = ngx_http_push_stream_delete_node_locked(&((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->tree, (ngx_rbtree_node_t *) trash, ngx_http_push_stream_shpool);
if (res == NGX_OK) {
((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->channels--;
return NGX_OK;
}
return res;
}
static ngx_int_t
ngx_http_push_stream_delete_node_locked(ngx_rbtree_t *tree, ngx_rbtree_node_t *trash, ngx_slab_pool_t *shpool)
{
// assume the shm zone is already locked
if (trash != NULL) { // take out the trash
ngx_rbtree_delete(tree, trash);
// delete the worker-subscriber queue
ngx_queue_t *sentinel = (ngx_queue_t *) (&((ngx_http_push_stream_channel_t *) trash)->workers_with_subscribers);
ngx_queue_t *cur = ngx_queue_head(sentinel);
ngx_queue_t *next;
while (cur != sentinel) {
next = ngx_queue_next(cur);
ngx_slab_free_locked(shpool, cur);
cur = next;
}
ngx_slab_free_locked(shpool, trash);
return NGX_OK;
}
return NGX_DECLINED;
}
static ngx_http_push_stream_channel_t * static ngx_http_push_stream_channel_t *
ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log) ngx_http_push_stream_find_channel_on_tree(ngx_str_t *id, ngx_log_t *log, ngx_rbtree_t *tree)
{ {
ngx_rbtree_t *tree = &((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->tree;
uint32_t hash; uint32_t hash;
ngx_rbtree_node_t *node, *sentinel; ngx_rbtree_node_t *node, *sentinel;
ngx_int_t rc; ngx_int_t rc;
ngx_http_push_stream_channel_t *up = NULL; ngx_http_push_stream_channel_t *channel = NULL;
ngx_http_push_stream_channel_t *trash[] = { NULL, NULL, NULL };
ngx_uint_t i, trashed = 0;
if (tree == NULL) {
return NULL;
}
hash = ngx_crc32_short(id->data, id->len); hash = ngx_crc32_short(id->data, id->len);
...@@ -96,13 +14,6 @@ ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log) ...@@ -96,13 +14,6 @@ ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log)
sentinel = tree->sentinel; sentinel = tree->sentinel;
while (node != sentinel) { while (node != sentinel) {
// every search is responsible for deleting a couple of empty, if it comes across them
if (trashed < (sizeof(trash) / sizeof(*trash))) {
if ((trash[trashed] = ngx_http_push_stream_clean_channel_locked((ngx_http_push_stream_channel_t *) node)) != NULL) {
trashed++;
}
}
if (hash < node->key) { if (hash < node->key) {
node = node->left; node = node->left;
continue; continue;
...@@ -116,21 +27,11 @@ ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log) ...@@ -116,21 +27,11 @@ ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log)
/* hash == node->key */ /* hash == node->key */
do { do {
up = (ngx_http_push_stream_channel_t *) node; channel = (ngx_http_push_stream_channel_t *) node;
rc = ngx_memn2cmp(id->data, up->id.data, id->len, up->id.len);
rc = ngx_memn2cmp(id->data, channel->id.data, id->len, channel->id.len);
if (rc == 0) { if (rc == 0) {
// found return channel;
for(i=0; i<trashed; i++) {
if (trash[i] != up) { // take out the trash
ngx_http_push_stream_delete_channel_locked(trash[i]);
}
}
ngx_http_push_stream_clean_channel_locked(up);
return up;
} }
node = (rc < 0) ? node->left : node->right; node = (rc < 0) ? node->left : node->right;
...@@ -140,50 +41,96 @@ ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log) ...@@ -140,50 +41,96 @@ ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log)
break; break;
} }
// not found return NULL;
for(i=0; i<trashed; i++) { }
ngx_http_push_stream_delete_channel_locked(trash[i]);
static ngx_http_push_stream_channel_t *
ngx_http_push_stream_find_channel(ngx_str_t *id, ngx_log_t *log)
{
ngx_http_push_stream_shm_data_t *data = (ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data;
ngx_slab_pool_t *shpool = (ngx_slab_pool_t *) ngx_http_push_stream_shm_zone->shm.addr;
ngx_http_push_stream_channel_t *channel = NULL;
if (id == NULL) {
ngx_log_error(NGX_LOG_ERR, log, 0, "push stream module: tried to find a channel with a null id");
return NULL;
}
channel = ngx_http_push_stream_find_channel_on_tree(id, log, &data->tree);
if ((channel == NULL) || channel->deleted) {
ngx_shmtx_lock(&shpool->mutex);
channel = ngx_http_push_stream_find_channel_on_tree(id, log, &data->channels_to_delete);
if (channel != NULL) {
channel->deleted = 0;
channel->expires = 0;
(channel->broadcast) ? data->broadcast_channels++ : data->channels++;
ngx_rbtree_delete(&data->channels_to_delete, (ngx_rbtree_node_t *) channel);
channel->node.key = ngx_crc32_short(channel->id.data, channel->id.len);
ngx_rbtree_insert(&data->tree, (ngx_rbtree_node_t *) channel);
}
ngx_shmtx_unlock(&shpool->mutex);
} }
return NULL; return channel;
} }
// find a channel by id. if channel not found, make one, insert it, and return that. // find a channel by id. if channel not found, make one, insert it, and return that.
static ngx_http_push_stream_channel_t * static ngx_http_push_stream_channel_t *
ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log) ngx_http_push_stream_get_channel(ngx_str_t *id, ngx_log_t *log, ngx_http_push_stream_loc_conf_t *cf)
{ {
ngx_rbtree_t *tree; 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 *up = ngx_http_push_stream_find_channel(id, log); 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_flag_t is_broadcast_channel = 0;
channel = ngx_http_push_stream_find_channel(id, log);
if (channel != NULL) { // we found our channel
return channel;
}
ngx_shmtx_lock(&shpool->mutex);
if ((cf->broadcast_channel_prefix.len > 0) && (ngx_strncmp(id->data, cf->broadcast_channel_prefix.data, cf->broadcast_channel_prefix.len) == 0)) {
is_broadcast_channel = 1;
}
if (up != NULL) { // we found our channel if (((!is_broadcast_channel) && (cf->max_number_of_channels != NGX_CONF_UNSET_UINT) && (cf->max_number_of_channels == data->channels)) ||
return up; ((is_broadcast_channel) && (cf->max_number_of_broadcast_channels != NGX_CONF_UNSET_UINT) && (cf->max_number_of_broadcast_channels == data->broadcast_channels))) {
ngx_shmtx_unlock(&shpool->mutex);
return NGX_HTTP_PUSH_STREAM_NUMBER_OF_CHANNELS_EXCEEDED;
} }
tree = &((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->tree;
if ((up = ngx_http_push_stream_slab_alloc_locked(sizeof(*up) + id->len + 1 + sizeof(ngx_http_push_stream_msg_t))) == NULL) { if ((channel = ngx_slab_alloc_locked(shpool, sizeof(ngx_http_push_stream_channel_t) + id->len + 1)) == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
return NULL; return NULL;
} }
up->id.data = (u_char *) (up+1); // contiguous piggy
up->message_queue = (ngx_http_push_stream_msg_t *) (up->id.data + id->len + 1);
up->id.len = (u_char) id->len; channel->id.data = (u_char *) (channel+1); // contiguous piggy
ngx_memzero(up->id.data, up->id.len + 1);
ngx_memcpy(up->id.data, id->data, up->id.len);
up->node.key = ngx_crc32_short(id->data, id->len);
ngx_rbtree_insert(tree, (ngx_rbtree_node_t *) up);
// initialize queues channel->id.len = (u_char) id->len;
ngx_queue_init(&up->message_queue->queue); ngx_memzero(channel->id.data, channel->id.len + 1);
up->last_message_id = 0; ngx_memcpy(channel->id.data, id->data, channel->id.len);
up->stored_messages = 0; channel->node.key = ngx_crc32_short(id->data, id->len);
ngx_queue_init(&up->workers_with_subscribers.queue); channel->last_message_id = 0;
up->subscribers = 0; channel->stored_messages = 0;
channel->subscribers = 0;
channel->broadcast = is_broadcast_channel;
channel->message_queue.deleted = 0;
channel->deleted = 0;
// initialize queues
ngx_queue_init(&channel->message_queue.queue);
ngx_queue_init(&channel->workers_with_subscribers.queue);
((ngx_http_push_stream_shm_data_t *) ngx_http_push_stream_shm_zone->data)->channels++; ngx_rbtree_insert(&data->tree, (ngx_rbtree_node_t *) channel);
(is_broadcast_channel) ? data->broadcast_channels++ : data->channels++;
return up; ngx_shmtx_unlock(&shpool->mutex);
return channel;
} }
......
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