diff --git a/README.md b/README.md index fbe3d92..d665524 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,11 @@ Nginx module developed for logging and displaying statistic of websocket proxy c ## Installation - 1. Configure nginx adding this module with: + 1. Configure nginx adding this module with or build this module as a dynamic module: ```sh ./configure (...) --add-module=./ngx_http_websocket_stat_module + # or + ./configure (...) --add-dynamic-module=./ngx_http_websocket_stat_module && make modules ``` 2. Build nginx with make -j command where n is number of cpu cores on your build machine @@ -22,9 +24,10 @@ Nginx module developed for logging and displaying statistic of websocket proxy c ## Usage -To enable websocket logging specify log file in server section of nginx config file with ws_log directibe. +To enable websocket frames logging specify `log_enabled on` and `ws_log_format` in server section of nginx config file. Additionally, specify `ws_log` to override the log file, which is used to log ws frames. -You can specify your own websocket log format using ws_log_format directive in server section. To customize connection open and close log messages use "open" and "close" parameter for ws_log_format directive. +To customize connection open and close log messages use "open" and "close" parameter for ws_log_format directive. +To log only when the full message is received/sent use "message" parameter for ws_log_format directive. Maximum number of concurrent websocket connections could be specified with ws_max_connections on server section. This value applies to whole connections that are on nginx. Argument should be integer representing maximum connections. When client tries to open more connections it recevies close framee with 1013 error code and connection is closed on nginx side. If zero number of connections is given there would be no limit on websocket connections. @@ -34,7 +37,8 @@ To set maximum single connection lifetime use ws_conn_age parameter. Argument is Here is a list of variables you can use in log format string: * $ws_opcode - websocket packet opcode. Look into https://tools.ietf.org/html/rfc6455 Section 5.2, Base Framing Protocol. - * $ws_payload_size - Websocket packet size without protocol specific data. Only data that been sent or received by the client + * $ws_payload_size - Size of the WS frame without protocol specific data. Only data that been sent or received by the client + * $ws_message_size - Size of the WS message without protocol specific data. Only data that been sent or received by the client * $ws_packet_source - Could be "client" if packet has been sent by the user or "upstream" if it has been received from the server * $ws_conn_age - Number of seconds connection is alive * $time_local - Nginx local time, date and timezone @@ -48,8 +52,6 @@ Here is a list of variables you can use in log format string: * $server_port - Server's port * $upstream_addr - websocket backend address -To read websocket statistic there is GET request should be set up at "location" location of nginx config file with ws_stat command in it. Look into example section for details. - ## Example of configuration ``` @@ -62,10 +64,6 @@ server ws_log_format close "$time_local: Connection closed"; ws_max_connections 200; ws_conn_age 12h; -# set up location for statistic - location /websocket_status { - ws_stat; - } ... } diff --git a/config b/config index 862d16c..84835c6 100644 --- a/config +++ b/config @@ -1,8 +1,17 @@ ngx_addon_name=ngx_http_websocket_stat_module -ngx_module_type=HTTP -ngx_module_name=ngx_http_websocket_stat_module -ngx_module_srcs="$ngx_addon_dir/ngx_http_websocket_stat_module.c \ - $ngx_addon_dir/ngx_http_websocket_stat_format.c \ - $ngx_addon_dir/ngx_http_websocket_stat_frame_counter.c" -. auto/module +if test -n "$ngx_module_link"; then + ngx_module_type=HTTP + ngx_module_name=ngx_http_websocket_stat_module + ngx_module_srcs="$ngx_addon_dir/ngx_http_websocket_stat_module.c \ + $ngx_addon_dir/ngx_http_websocket_stat_format.c \ + $ngx_addon_dir/ngx_http_websocket_stat_frame_counter.c" + . auto/module +else + HTTP_MODULES="$HTTP_MODULES ngx_http_websocket_stat_module" + HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_websocket_stat_module" + NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ + $ngx_addon_dir/ngx_http_websocket_stat_module.c \ + $ngx_addon_dir/ngx_http_websocket_stat_format.c \ + $ngx_addon_dir/ngx_http_websocket_stat_frame_counter.c" +fi diff --git a/ngx_http_websocket_stat_frame_counter.c b/ngx_http_websocket_stat_frame_counter.c index 1224f2d..4725ef3 100644 --- a/ngx_http_websocket_stat_frame_counter.c +++ b/ngx_http_websocket_stat_frame_counter.c @@ -37,6 +37,12 @@ frame_counter_process_message(u_char **buffer, ssize_t *size, switch (frame_counter->stage) { case HEADER: frame_counter->current_frame_type = **buffer & 0x0f; + frame_counter->fragment_final = **buffer >> 7; + + if (frame_counter->current_frame_type != CONTINUATION) { + frame_counter->current_message_size = 0; + } + move_buffer(buffer, size, 1); frame_counter->stage = PAYLOAD_LEN; frame_counter->bytes_consumed = @@ -102,6 +108,7 @@ frame_counter_process_message(u_char **buffer, ssize_t *size, } break; case PAYLOAD: + frame_counter->current_message_size += *size; if (*size >= (u_int)(frame_counter->current_payload_size - frame_counter->bytes_consumed)) { move_buffer(buffer, size, diff --git a/ngx_http_websocket_stat_frame_counter.h b/ngx_http_websocket_stat_frame_counter.h index 5285dd4..1496a7d 100644 --- a/ngx_http_websocket_stat_frame_counter.h +++ b/ngx_http_websocket_stat_frame_counter.h @@ -15,12 +15,13 @@ typedef enum { CONTINUATION, TEXT, BINARY, CLOSE = 8, PING, PONG } frame_type; // Structure representing frame statistic and parsing stage typedef struct { - ngx_int_t total_payload_size; + ngx_int_t current_message_size; - // private fields representing current parcing stage + // private fields representing current parsing stage ngx_int_t bytes_consumed; packet_reading_stage stage; char payload_masked : 1; + char fragment_final : 1; frame_type current_frame_type; ngx_int_t current_payload_size; } ngx_frame_counter_t; diff --git a/ngx_http_websocket_stat_module.c b/ngx_http_websocket_stat_module.c index b81bf2d..0c45e4f 100644 --- a/ngx_http_websocket_stat_module.c +++ b/ngx_http_websocket_stat_module.c @@ -38,25 +38,28 @@ ngx_frame_counter_t frame_counter_out; ngx_http_websocket_stat_ctx *stat_counter; typedef struct { - int from_client; + char from_client : 1; ngx_http_websocket_stat_ctx *ws_ctx; } template_ctx_s; -static char *ngx_http_websocket_stat(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); +typedef ssize_t (*send_func)(ngx_connection_t *c, u_char *buf, size_t size); +send_func orig_recv, orig_send; + static char *ngx_http_websocket_max_conn_setup(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_websocket_max_conn_age(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static char *ngx_http_ws_logfile(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); -static char *ngx_http_ws_log_format(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); -static ngx_int_t ngx_http_websocket_stat_handler(ngx_http_request_t *r); -static ngx_int_t ngx_http_websocket_stat_init(ngx_conf_t *cf); - -static void *ngx_http_websocket_stat_create_main_conf(ngx_conf_t *cf); +static char *ngx_http_websocket_log_format(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_websocket_log_enabled(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_websocket_log_file(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_http_websocket_stat_configure(ngx_conf_t *cf); + +static void *ngx_http_websocket_stat_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_websocket_stat_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); const char *get_core_var(ngx_http_request_t *r, const char *variable); static void send_close_packet(ngx_connection_t *connection, int status, @@ -65,7 +68,6 @@ static void send_close_packet(ngx_connection_t *connection, int status, static ngx_atomic_t *ngx_websocket_stat_active; char CARET_RETURN = '\n'; -ngx_log_t *ws_log = NULL; const char *UNKNOWN_VAR = "???"; static void @@ -87,71 +89,43 @@ Base64Encode(unsigned char *hash, int hash_len, char *buffer, int len) BIO_free_all(b64); } -void -websocket_log(char *str) -{ - if (!ws_log) - return; - ngx_write_fd(ws_log->file->fd, str, strlen(str)); - ngx_write_fd(ws_log->file->fd, &CARET_RETURN, sizeof(char)); -} - -void -ws_do_log(compiled_template *template, ngx_http_request_t *r, void *ctx) -{ - if (ws_log) { - char *log_line = apply_template(template, r, ctx); - websocket_log(log_line); - free(log_line); - } -} - -typedef struct ngx_http_websocket_main_conf_s { +typedef struct ngx_http_websocket_srv_conf_s { int max_ws_connections; int max_ws_age; -} ngx_http_websocket_main_conf_t; - -compiled_template *log_template; -compiled_template *log_close_template; -compiled_template *log_open_template; - -char *default_log_template_str = - "$time_local: packet received from $ws_packet_source"; -char *default_open_log_template_str = "websocket connection opened"; -char *default_close_log_template_str = "websocket connection closed"; + ngx_flag_t enabled; + compiled_template *log_template; + compiled_template *log_message_template; + compiled_template *log_open_template; + compiled_template *log_close_template; + ngx_log_t *ws_log; +} ngx_http_websocket_srv_conf_t; ssize_t (*orig_recv)(ngx_connection_t *c, u_char *buf, size_t size); static ngx_command_t ngx_http_websocket_stat_commands[] = { - - {ngx_string("ws_stat"), /* directive */ - NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, /* location context and takes - no arguments*/ - ngx_http_websocket_stat, /* configuration setup function */ - 0, /* No offset. Only one context is supported. */ - 0, /* No offset when storing the module configuration on struct. */ - NULL}, {ngx_string("ws_max_connections"), NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1, - ngx_http_websocket_max_conn_setup, 0, 0, NULL}, + ngx_http_websocket_max_conn_setup, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL}, {ngx_string("ws_conn_age"), NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1, - ngx_http_websocket_max_conn_age, 0, 0, NULL}, + ngx_http_websocket_max_conn_age, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL}, + {ngx_string("ws_log_enabled"), NGX_HTTP_SRV_CONF | NGX_CONF_FLAG, + ngx_http_websocket_log_enabled, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL}, {ngx_string("ws_log"), NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1, - ngx_http_ws_logfile, 0, 0, NULL}, + ngx_http_websocket_log_file, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL}, {ngx_string("ws_log_format"), NGX_HTTP_SRV_CONF | NGX_CONF_1MORE, - ngx_http_ws_log_format, 0, 0, NULL}, + ngx_http_websocket_log_format, NGX_HTTP_SRV_CONF_OFFSET, 0, NULL}, ngx_null_command /* command termination */ }; /* The module context. */ static ngx_http_module_t ngx_http_websocket_stat_module_ctx = { - NULL, /* preconfiguration */ - ngx_http_websocket_stat_init, /* postconfiguration */ + NULL, /* preconfiguration */ + ngx_http_websocket_stat_configure, /* postconfiguration */ - ngx_http_websocket_stat_create_main_conf, /* create main configuration */ - NULL, /* init main configuration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ - NULL, /* create server configuration */ - NULL, /* merge server configuration */ + ngx_http_websocket_stat_create_srv_conf, /* create server configuration */ + ngx_http_websocket_stat_merge_srv_conf, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ @@ -185,136 +159,54 @@ static u_char responce_template[] = u_char msg[sizeof(responce_template) + 6 * NGX_ATOMIC_T_LEN]; -static ngx_int_t -ngx_http_websocket_stat_handler(ngx_http_request_t *r) -{ - ngx_buf_t *b; - ngx_chain_t out; - - /* Set the Content-Type header. */ - r->headers_out.content_type.len = sizeof("text/plain") - 1; - r->headers_out.content_type.data = (u_char *)"text/plain:"; - - /* Allocate a new buffer for sending out the reply. */ - b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); - - /* Insertion in the buffer chain. */ - out.buf = b; - out.next = NULL; - sprintf((char *)msg, (char *)responce_template, *ngx_websocket_stat_active, - *frames_in.frames, *frames_in.total_payload_size, - *frames_in.total_size, *frames_out.frames, - *frames_out.total_payload_size, *frames_out.total_size); - - b->pos = msg; /* first position in memory of the data */ - b->last = - msg + strlen((char *)msg); /* last position in memory of the data */ - b->memory = 1; /* content is in read-only memory */ - b->last_buf = 1; /* there will be buffers in the request */ - - /* Sending the headers for the reply. */ - r->headers_out.status = NGX_HTTP_OK; - /* Get the content length of the body. */ - r->headers_out.content_length_n = strlen((char *)msg); - ngx_http_send_header(r); /* Send the headers */ - - /* Send the body, and return the status code of the output filter chain. */ - return ngx_http_output_filter(r, &out); -} - -/** - * Configuration setup function that installs the content handler. - * - * @param cf - * Module configuration structure pointer. - * @param cmd - * Module directives structure pointer. - * @param conf - * Module configuration structure pointer. - * @return string - * Status of the configuration setup. - */ -static char * -ngx_http_websocket_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_http_core_loc_conf_t *clcf; /* pointer to core location configuration */ - - /* Install the hello world handler. */ - clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); - clcf->handler = ngx_http_websocket_stat_handler; - - return NGX_CONF_OK; -} /* ngx_http_hello_world */ - -static char * -ngx_http_websocket_max_conn_setup(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf) -{ - ngx_str_t *value; - value = cf->args->elts; - ngx_http_websocket_main_conf_t *main_conf = conf; - main_conf->max_ws_connections = atoi((char *)value[1].data); - return NGX_CONF_OK; -} -static char * -ngx_http_websocket_max_conn_age(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +void +ws_do_log(compiled_template *template, ngx_http_request_t *r, void *ctx) { - ngx_str_t *value; - value = cf->args->elts; - ngx_int_t timeout; - timeout = ngx_parse_time(&value[1], 1); - if (timeout == NGX_ERROR) { - return NGX_CONF_ERROR; - } - ngx_http_websocket_main_conf_t *main_conf = conf; - main_conf->max_ws_age = timeout; - - return NGX_CONF_OK; + ngx_http_websocket_srv_conf_t *srvcf = ngx_http_get_module_srv_conf(r, ngx_http_websocket_stat_module); + + if (!srvcf->enabled || !template || !template->compiled_template_str) return; + char *log_line = apply_template(template, r, ctx); + if (!log_line) return; + ngx_write_fd(srvcf->ws_log->file->fd, log_line, strlen(log_line)); + ngx_write_fd(srvcf->ws_log->file->fd, &CARET_RETURN, sizeof(char)); + free(log_line); } -static char * -ngx_http_ws_logfile(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - - ws_log = ngx_palloc(cf->pool, sizeof(ngx_log_t)); - ngx_memzero(ws_log, sizeof(ngx_log_t)); - - ngx_str_t *value; - value = cf->args->elts; - ws_log->log_level = NGX_LOG_NOTICE; - assert(cf->args->nelts >= 2); - ws_log->file = ngx_conf_open_file(cf->cycle, &value[1]); - if (!ws_log->file) - return NGX_CONF_ERROR; - - return NGX_CONF_OK; -} -typedef ssize_t (*send_func)(ngx_connection_t *c, u_char *buf, size_t size); -send_func orig_recv, orig_send; - static int check_ws_age(time_t conn_start_time, ngx_http_request_t *r) { - ngx_http_websocket_main_conf_t *conf; - conf = ngx_http_get_module_main_conf(r, ngx_http_websocket_stat_module); - if (conf->max_ws_age > 0 && - ngx_time() - conn_start_time >= conf->max_ws_age) { + ngx_http_websocket_srv_conf_t *srvcf = ngx_http_get_module_srv_conf(r, ngx_http_websocket_stat_module); + if (srvcf->max_ws_age > 0 && + ngx_time() - conn_start_time >= srvcf->max_ws_age) { send_close_packet(r->connection, 4001, "Connection is Aged"); return NGX_ERROR; } return NGX_OK; } + +static compiled_template * +get_ws_log_template(ngx_http_websocket_stat_ctx *ctx, ngx_http_websocket_srv_conf_t *srvcf) { + if (srvcf->log_template) { + return srvcf->log_template; + } else if (ctx->frame_counter.fragment_final && ctx->frame_counter.current_frame_type < CLOSE) { + return srvcf->log_message_template; + } + + return NULL; +} + // Packets that being send to a client ssize_t my_send(ngx_connection_t *c, u_char *buf, size_t size) { - + ngx_http_request_t *r = c->data; + ngx_http_websocket_srv_conf_t *srvcf = + ngx_http_get_module_srv_conf(r, ngx_http_websocket_stat_module); ngx_http_websocket_stat_ctx *ctx; ssize_t sz = size; u_char *buffer = buf; ngx_http_websocket_stat_statistic_t *frame_counter = &frames_out; ngx_atomic_fetch_add(frame_counter->total_size, sz); - ngx_http_request_t *r = c->data; ctx = ngx_http_get_module_ctx(r, ngx_http_websocket_stat_module); if (check_ws_age(ctx->ws_conn_start_time, r) != NGX_OK) { @@ -329,14 +221,15 @@ my_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_atomic_fetch_add(frame_counter->frames, 1); ngx_atomic_fetch_add(frame_counter->total_payload_size, ctx->frame_counter.current_payload_size); - ws_do_log(log_template, r, &template_ctx); + ws_do_log(get_ws_log_template(ctx, srvcf), r, &template_ctx); } } + int n = orig_send(c, buf, size); if (n < 0) { if(!ngx_atomic_cmp_set(ngx_websocket_stat_active, 0, 0)){ ngx_atomic_fetch_add(ngx_websocket_stat_active, -1); - ws_do_log(log_close_template, r, &template_ctx); + ws_do_log(srvcf->log_close_template, r, &template_ctx); } } return n; @@ -346,31 +239,33 @@ my_send(ngx_connection_t *c, u_char *buf, size_t size) ssize_t my_recv(ngx_connection_t *c, u_char *buf, size_t size) { + ngx_http_request_t *r = c->data; + ngx_http_websocket_srv_conf_t *srvcf = + ngx_http_get_module_srv_conf(r, ngx_http_websocket_stat_module); + ngx_http_websocket_stat_ctx *ctx = + ngx_http_get_module_ctx(r, ngx_http_websocket_stat_module); + ngx_http_websocket_stat_statistic_t *frame_counter = &frames_in; + template_ctx_s template_ctx; + template_ctx.from_client = 1; + template_ctx.ws_ctx = ctx; int n = orig_recv(c, buf, size); if (n <= 0) { return n; } - ngx_http_websocket_stat_ctx *ctx; ssize_t sz = n; - ngx_http_websocket_stat_statistic_t *frame_counter = &frames_in; - ngx_http_request_t *r = c->data; - ctx = ngx_http_get_module_ctx(r, ngx_http_websocket_stat_module); + if (check_ws_age(ctx->ws_conn_start_time, r) != NGX_OK) { return NGX_ERROR; } ngx_atomic_fetch_add(frame_counter->total_size, n); - template_ctx_s template_ctx; - template_ctx.from_client = 1; - template_ctx.ws_ctx = ctx; while (sz > 0) { if (frame_counter_process_message(&buf, &sz, &ctx->frame_counter)) { - ngx_atomic_fetch_add(frame_counter->frames, 1); ngx_atomic_fetch_add(frame_counter->total_payload_size, ctx->frame_counter.current_payload_size); - ws_do_log(log_template, r, &template_ctx); + ws_do_log(get_ws_log_template(ctx, srvcf), r, &template_ctx); } } @@ -389,15 +284,16 @@ ngx_http_websocket_stat_body_filter(ngx_http_request_t *r, ngx_chain_t *in) if (!r->upstream) return ngx_http_next_body_filter(r, in); - ngx_http_websocket_stat_ctx *ctx; - ctx = ngx_http_get_module_ctx(r, ngx_http_websocket_stat_module); - template_ctx_s template_ctx; - template_ctx.ws_ctx = ctx; + ngx_http_websocket_srv_conf_t *srvcf = + ngx_http_get_module_srv_conf(r, ngx_http_websocket_stat_module); + + if (!srvcf->enabled) + return ngx_http_next_body_filter(r, in); if (r->headers_in.upgrade) { if (r->upstream->peer.connection) { // connection opened - ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_websocket_stat_ctx)); + ngx_http_websocket_stat_ctx *ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_websocket_stat_ctx)); if (ctx == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -406,8 +302,10 @@ ngx_http_websocket_stat_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ctx->connection_id.len = UID_LENGTH; memcpy(ctx->connection_id.data, request_id_str, UID_LENGTH + 1); - ws_do_log(log_open_template, r, &template_ctx); ngx_http_set_ctx(r, ctx, ngx_http_websocket_stat_module); + template_ctx_s template_ctx; + template_ctx.ws_ctx = ctx; + ws_do_log(srvcf->log_open_template, r, &template_ctx); orig_recv = r->connection->recv; r->connection->recv = my_recv; orig_send = r->connection->send; @@ -415,9 +313,12 @@ ngx_http_websocket_stat_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_atomic_fetch_add(ngx_websocket_stat_active, 1); ctx->ws_conn_start_time = ngx_time(); } else { - if(!ngx_atomic_cmp_set(ngx_websocket_stat_active, 0, 0)){ + if(!ngx_atomic_cmp_set(ngx_websocket_stat_active, 0, 0)){ ngx_atomic_fetch_add(ngx_websocket_stat_active, -1); - ws_do_log(log_close_template, r, &template_ctx); + ngx_http_websocket_stat_ctx *ctx = ngx_http_get_module_ctx(r, ngx_http_websocket_stat_module); + template_ctx_s template_ctx; + template_ctx.ws_ctx = ctx; + ws_do_log(srvcf->log_close_template, r, &template_ctx); } } } @@ -431,10 +332,9 @@ const char * ws_packet_type(ngx_http_request_t *r, void *data) { template_ctx_s *ctx = data; - ngx_frame_counter_t *frame_cntr = &(ctx->ws_ctx->frame_counter); - if (!ctx || !frame_cntr) + if (!ctx || !ctx->ws_ctx) return UNKNOWN_VAR; - sprintf(buff, "%d", frame_cntr->current_frame_type); + sprintf(buff, "%d", ctx->ws_ctx->frame_counter.current_frame_type); return buff; } @@ -442,10 +342,19 @@ const char * ws_packet_size(ngx_http_request_t *r, void *data) { template_ctx_s *ctx = data; - ngx_frame_counter_t *frame_cntr = &ctx->ws_ctx->frame_counter; - if (!ctx || !frame_cntr) + if (!ctx || !ctx->ws_ctx) + return UNKNOWN_VAR; + sprintf(buff, "%lu", ctx->ws_ctx->frame_counter.current_payload_size); + return (char *)buff; +} + +const char * +ws_message_size(ngx_http_request_t *r, void *data) +{ + template_ctx_s *ctx = data; + if (!ctx || !ctx->ws_ctx) return UNKNOWN_VAR; - sprintf(buff, "%lu", frame_cntr->current_payload_size); + sprintf(buff, "%lu", ctx->ws_ctx->frame_counter.current_message_size); return (char *)buff; } @@ -515,9 +424,6 @@ request_id(ngx_http_request_t *r, void *data) const char * upstream_addr(ngx_http_request_t *r, void *data) { - template_ctx_s *ctx = data; - if (!ctx || !ctx->ws_ctx) - return UNKNOWN_VAR; if (r->upstream_states == NULL || r->upstream_states->nelts == 0) return UNKNOWN_VAR; ngx_http_upstream_state_t *state; @@ -542,6 +448,7 @@ GEN_CORE_GET_FUNC(server_port, "server_port") const template_variable variables[] = { {VAR_NAME("$ws_opcode"), sizeof("ping") - 1, ws_packet_type}, {VAR_NAME("$ws_payload_size"), NGX_SIZE_T_LEN, ws_packet_size}, + {VAR_NAME("$ws_message_size"), NGX_SIZE_T_LEN, ws_message_size}, {VAR_NAME("$ws_packet_source"), sizeof("upstream") - 1, ws_packet_source}, {VAR_NAME("$ws_conn_age"), NGX_SIZE_T_LEN, ws_connection_age}, {VAR_NAME("$time_local"), sizeof("Mon, 23 Oct 2017 11:27:42 GMT") - 1, @@ -559,40 +466,87 @@ const template_variable variables[] = { {VAR_NAME("$remote_ip"), sizeof("000.000.000.000") - 1, remote_ip}, {NULL, 0, 0, NULL}}; -static void * -ngx_http_websocket_stat_create_main_conf(ngx_conf_t *cf) +static char * +ngx_http_websocket_max_conn_setup(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) { - ngx_http_websocket_main_conf_t *conf; + ngx_http_websocket_srv_conf_t *srvcf = conf; + ngx_str_t *args = cf->args->elts; - conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_websocket_main_conf_t)); - if (conf == NULL) { - return NULL; + if (cf->args->nelts != 2) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Wrong argument number"); + return NGX_CONF_ERROR; } - conf->max_ws_connections = -1; - conf->max_ws_age = -1; - return conf; + srvcf->max_ws_connections = atoi((char *)args[1].data); + + return NGX_CONF_OK; } static char * -ngx_http_ws_log_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +ngx_http_websocket_max_conn_age(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { + ngx_http_websocket_srv_conf_t *srvcf = conf; + ngx_str_t *args = cf->args->elts; + + if (cf->args->nelts != 2) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Wrong argument number"); + return NGX_CONF_ERROR; + } + + ngx_int_t timeout; + timeout = ngx_parse_time(&args[1], 1); + if (timeout == NGX_ERROR) { + return NGX_CONF_ERROR; + } + + srvcf->max_ws_age = timeout; + + return NGX_CONF_OK; +} + +static char * +ngx_http_websocket_log_enabled(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_websocket_srv_conf_t *srvcf = conf; + ngx_str_t *args = cf->args->elts; + + if (cf->args->nelts != 2) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Wrong argument number"); + return NGX_CONF_ERROR; + } + + if (strcmp((char *)args[1].data, "on") == 0) { + srvcf->enabled = 1; + } + + return NGX_CONF_OK; +} + +static char * +ngx_http_websocket_log_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_websocket_srv_conf_t *srvcf = conf; ngx_str_t *args = cf->args->elts; if (cf->args->nelts != 2 && cf->args->nelts != 3) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Wrong argument number"); return NGX_CONF_ERROR; } if (cf->args->nelts == 2) { - log_template = + srvcf->log_template = compile_template((char *)args[1].data, variables, cf->pool); return NGX_CONF_OK; } if (strcmp((char *)args[1].data, "close") == 0) { - log_close_template = + srvcf->log_close_template = compile_template((char *)args[2].data, variables, cf->pool); return NGX_CONF_OK; } else if (strcmp((char *)args[1].data, "open") == 0) { - log_open_template = + srvcf->log_open_template = + compile_template((char *)args[2].data, variables, cf->pool); + return NGX_CONF_OK; + } else if (strcmp((char *)args[1].data, "message") == 0) { + srvcf->log_message_template = compile_template((char *)args[2].data, variables, cf->pool); return NGX_CONF_OK; } else { @@ -603,6 +557,85 @@ ngx_http_ws_log_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } } +static void init_ws_log_file(ngx_conf_t *cf, ngx_http_websocket_srv_conf_t *srvcf, ngx_str_t *file_path) { + srvcf->ws_log = ngx_pcalloc(cf->pool, sizeof(ngx_log_t)); + + if (!srvcf->ws_log) + return; + + srvcf->ws_log->log_level = NGX_LOG_NOTICE; + srvcf->ws_log->file = ngx_conf_open_file(cf->cycle, file_path); +} + +static char * +ngx_http_websocket_log_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_websocket_srv_conf_t *srvcf = conf; + ngx_str_t *args = cf->args->elts; + + if (cf->args->nelts != 2) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Wrong argument number"); + return NGX_CONF_ERROR; + } + + init_ws_log_file(cf, srvcf, &args[1]); + + if (!srvcf->ws_log) + return NGX_CONF_ERROR; + + if (!srvcf->ws_log->file) + return NGX_CONF_ERROR; + + return NGX_CONF_OK; +} + +static void * +ngx_http_websocket_stat_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_websocket_srv_conf_t *srvcf; + + srvcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_websocket_srv_conf_t)); + if (!srvcf) { + return NULL; + } + + srvcf->max_ws_connections = NGX_CONF_UNSET; + srvcf->max_ws_age = NGX_CONF_UNSET; + srvcf->enabled = NGX_CONF_UNSET; + srvcf->log_template = NGX_CONF_UNSET_PTR; + srvcf->log_message_template = NGX_CONF_UNSET_PTR; + srvcf->log_close_template = NGX_CONF_UNSET_PTR; + srvcf->log_open_template = NGX_CONF_UNSET_PTR; + srvcf->ws_log = NGX_CONF_UNSET_PTR; + + return srvcf; +} + +static char * +ngx_http_websocket_stat_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_websocket_srv_conf_t *prev = parent; + ngx_http_websocket_srv_conf_t *conf = child; + + ngx_conf_merge_value(conf->max_ws_connections, prev->max_ws_connections, -1); + ngx_conf_merge_value(conf->max_ws_age, prev->max_ws_age, -1); + ngx_conf_merge_value(conf->enabled, prev->enabled, 0); + ngx_conf_merge_ptr_value(conf->log_template, prev->log_template, NULL); + ngx_conf_merge_ptr_value(conf->log_message_template, prev->log_message_template, NULL); + ngx_conf_merge_ptr_value(conf->log_close_template, prev->log_close_template, NULL); + ngx_conf_merge_ptr_value(conf->log_open_template, prev->log_open_template, NULL); + ngx_conf_merge_ptr_value(conf->ws_log, prev->ws_log, NULL); + + if (conf->enabled && !conf->ws_log) { + init_ws_log_file(cf, conf, &cf->cycle->error_log); + + if (!conf->ws_log || !conf->ws_log->file) + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + static void allocate_counters() { @@ -706,14 +739,13 @@ complete_ws_handshake(ngx_connection_t *connection, const char *ws_key) static ngx_int_t ngx_http_websocket_request_handler(ngx_http_request_t *r) { - ngx_http_websocket_main_conf_t *conf; - conf = ngx_http_get_module_main_conf(r, ngx_http_websocket_stat_module); - if (conf == NULL) { + ngx_http_websocket_srv_conf_t *srvcf = ngx_http_get_module_srv_conf(r, ngx_http_websocket_stat_module); + if (!srvcf) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } - if (conf->max_ws_connections > 0 && - conf->max_ws_connections == (int)*ngx_websocket_stat_active) { + if (srvcf->max_ws_connections > 0 && + srvcf->max_ws_connections == (int)*ngx_websocket_stat_active) { ngx_table_elt_t *upgrade_hdr = find_header_in(r, "Upgrade"); if (!upgrade_hdr || strcasecmp((char *)upgrade_hdr->value.data, "websocket") != 0) { @@ -733,7 +765,7 @@ ngx_http_websocket_request_handler(ngx_http_request_t *r) } static ngx_int_t -ngx_http_websocket_stat_init(ngx_conf_t *cf) +ngx_http_websocket_stat_configure(ngx_conf_t *cf) { allocate_counters(); @@ -743,19 +775,6 @@ ngx_http_websocket_stat_init(ngx_conf_t *cf) ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_websocket_stat_body_filter; - if (!log_template) { - log_template = - compile_template(default_log_template_str, variables, cf->pool); - } - if (!log_open_template) { - log_open_template = compile_template(default_open_log_template_str, - variables, cf->pool); - } - if (!log_close_template) { - log_close_template = compile_template(default_close_log_template_str, - variables, cf->pool); - } - ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);