From d0dafa03039c761bd2da4b04ef6dfebe89994aee Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Mon, 11 Apr 2016 14:45:21 -0700 Subject: [PATCH 1/8] Improvements to reported stats - Add a structure to track internally generated errors - Structure the stats a pluggable modules. New status reporting code can use the `h2o_config_register_status_handler` (for queries that need to access per thread data) or `h2o_config_register_simple_status_handler` (for queries that can be run in the request context) APIs in order to add new statuses - requests statuses and error reporting is moved under `lib/handler/status/` --- .travis.yml | 1 + CMakeLists.txt | 2 + include/h2o.h | 78 +++++++- include/h2o/http2_internal.h | 2 +- lib/core/config.c | 17 ++ lib/core/context.c | 65 +++++++ lib/core/request.c | 2 + lib/handler/status.c | 265 +++++++++++++++----------- lib/handler/status/errors.c | 113 +++++++++++ lib/handler/status/requests.c | 177 +++++++++++++++++ lib/http2/connection.c | 4 + src/main.c | 2 +- srcdoc/configure/status_directives.mt | 3 + t/50status.t | 47 +++++ 14 files changed, 665 insertions(+), 113 deletions(-) create mode 100644 lib/handler/status/errors.c create mode 100644 lib/handler/status/requests.c create mode 100644 t/50status.t diff --git a/.travis.yml b/.travis.yml index af7d3f6285..055ee8654d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ before_script: - misc/install-perl-module.pl Net::EmptyPort - misc/install-perl-module.pl Scope::Guard - misc/install-perl-module.pl Starlet + - misc/install-perl-module.pl JSON # install the `ab` command (a.k.a. ApacheBench; optionally required for running some of the tests) - sudo apt-get install -qq apache2-utils # install nghttp2 with `--enable-app` (optionally required for running HTTP/2 tests) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac9985f61b..7e0cf97d49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,6 +239,8 @@ SET(LIB_SOURCE_FILES lib/handler/redirect.c lib/handler/reproxy.c lib/handler/status.c + lib/handler/status/errors.c + lib/handler/status/requests.c lib/handler/configurator/access_log.c lib/handler/configurator/compress.c lib/handler/configurator/errordoc.c diff --git a/include/h2o.h b/include/h2o.h index e5014ea30c..0c5cbe067d 100644 --- a/include/h2o.h +++ b/include/h2o.h @@ -275,6 +275,28 @@ typedef struct st_h2o_protocol_callbacks_t { int (*foreach_request)(h2o_context_t *ctx, int (*cb)(h2o_req_t *req, void *cbdata), void *cbdata); } h2o_protocol_callbacks_t; +enum h2o_status_handler_type { + H2O_STATUS_HANDLER_SIMPLE, + H2O_STATUS_HANDLER_PER_THREAD, +}; +typedef h2o_iovec_t (*simple_status_handler_cb)(h2o_globalconf_t *gconf, h2o_mem_pool_t *pool); +typedef struct h2o_status_handler { + enum h2o_status_handler_type type; + h2o_iovec_t name; + union { + struct { + simple_status_handler_cb status_cb; + } simple; + struct { + void *(*alloc_context_cb)(h2o_req_t *src_req); + void (*per_thread_cb)(void *priv, h2o_context_t *ctx); + h2o_iovec_t (*assemble_cb)(void *ctx); + void (*done_cb)(void *ctx); + } per_thread; + }; +} h2o_status_handler_t; + +typedef H2O_VECTOR(h2o_status_handler_t) h2o_status_callbacks_t; struct st_h2o_globalconf_t { /** * a NULL-terminated list of host contexts (h2o_hostconf_t) @@ -371,10 +393,7 @@ struct st_h2o_globalconf_t { } filecache; /* status */ - struct { - /* optional callback set by the user of libh2o to feed additional json to the status handler */ - h2o_iovec_t (*extra_status)(h2o_globalconf_t *conf, h2o_mem_pool_t *pool); - } status; + h2o_status_callbacks_t statuses; size_t _num_config_slots; }; @@ -411,6 +430,42 @@ typedef struct st_h2o_mimemap_type_t { } data; } h2o_mimemap_type_t; +/* errors emanating from h2o */ +enum { + /* http */ + E_HTTP_400 = 0, + E_HTTP_403, + E_HTTP_404, + E_HTTP_405, + E_HTTP_416, + E_HTTP_417, + E_HTTP_500, + E_HTTP_502, + E_HTTP_503, + E_HTTP_4XX, + E_HTTP_5XX, + E_HTTP_XXX, + /* http2 */ + E_HTTP2_PROTOCOL, + E_HTTP2_INTERNAL, + E_HTTP2_FLOW_CONTROL, + E_HTTP2_SETTINGS_TIMEOUT, + E_HTTP2_STREAM_CLOSED, + E_HTTP2_FRAME_SIZE, + E_HTTP2_REFUSED_STREAM, + E_HTTP2_CANCEL, + E_HTTP2_COMPRESSION, + E_HTTP2_CONNECT, + E_HTTP2_ENHANCE_YOUR_CALM, + E_HTTP2_INADEQUATE_SECURITY, + E_HTTP2_OTHER, + EMITTED_ERRORS_MAX, +}; + +typedef struct st_h2o_emitted_errors_cnt { + unsigned long emitted_errors_cnt[EMITTED_ERRORS_MAX]; +} h2o_emitted_errors_t; + /** * context of the http server. */ @@ -503,6 +558,11 @@ struct st_h2o_context_t { h2o_timestamp_string_t *value; } _timestamp_cache; + /** + * counter for errors internally emitted by h2o + */ + h2o_emitted_errors_t emitted_errors; + H2O_VECTOR(h2o_pathconf_t *) _pathconfs_inited; }; @@ -1143,6 +1203,11 @@ h2o_hostconf_t *h2o_config_register_host(h2o_globalconf_t *config, h2o_iovec_t h * * configuration path does not end with a `/`, and the request path begins with the configuration path followed by a `/` */ h2o_pathconf_t *h2o_config_register_path(h2o_hostconf_t *hostconf, const char *path, int flags); +/** + * registers an extra status handler + */ +void h2o_config_register_status_handler(h2o_globalconf_t *config, h2o_status_handler_t); +void h2o_config_register_simple_status_handler(h2o_globalconf_t *config, h2o_iovec_t name, simple_status_handler_cb status_handler); /** * disposes of the resources allocated for the global configuration */ @@ -1211,7 +1276,10 @@ static void h2o_context_set_filter_context(h2o_context_t *ctx, h2o_filter_t *fil * returns per-module context set by the on_context_init callback */ static void *h2o_context_get_logger_context(h2o_context_t *ctx, h2o_logger_t *logger); - +/** + * internally reports an error emitted by h2o, so that this can in turn be reported in statuses + */ +void h2o_context_report_emitted_error(h2o_context_t *ctx, int status, int http); /* built-in generators */ enum { diff --git a/include/h2o/http2_internal.h b/include/h2o/http2_internal.h index 2abcaf1156..c17502e716 100644 --- a/include/h2o/http2_internal.h +++ b/include/h2o/http2_internal.h @@ -48,7 +48,7 @@ typedef struct st_h2o_http2_stream_t h2o_http2_stream_t; #define H2O_HTTP2_ERROR_COMPRESSION -9 #define H2O_HTTP2_ERROR_CONNECT -10 #define H2O_HTTP2_ERROR_ENHANCE_YOUR_CALM -11 -#define H2O_HTTP2_ERROR_INADEUATE_SECURITY -12 +#define H2O_HTTP2_ERROR_INADEQUATE_SECURITY -12 #define H2O_HTTP2_ERROR_INCOMPLETE -255 /* an internal value indicating that all data is not ready */ #define H2O_HTTP2_ERROR_PROTOCOL_CLOSE_IMMEDIATELY -256 diff --git a/lib/core/config.c b/lib/core/config.c index 6c28207ce2..6dab5e95f0 100644 --- a/lib/core/config.c +++ b/lib/core/config.c @@ -198,6 +198,23 @@ h2o_pathconf_t *h2o_config_register_path(h2o_hostconf_t *hostconf, const char *p return pathconf; } +void h2o_config_register_status_handler(h2o_globalconf_t *config, h2o_status_handler_t status_handler) +{ + h2o_vector_reserve(NULL, &config->statuses, config->statuses.size + 1); + config->statuses.entries[config->statuses.size++] = status_handler; +} + +void h2o_config_register_simple_status_handler(h2o_globalconf_t *config, h2o_iovec_t name, simple_status_handler_cb status_handler) +{ + h2o_status_handler_t *sh; + + h2o_vector_reserve(NULL, &config->statuses, config->statuses.size + 1); + sh = &config->statuses.entries[config->statuses.size++]; + sh->type = H2O_STATUS_HANDLER_SIMPLE; + sh->name = h2o_strdup(NULL, name.base, name.len); + sh->simple.status_cb = status_handler; +} + h2o_hostconf_t *h2o_config_register_host(h2o_globalconf_t *config, h2o_iovec_t host, uint16_t port) { h2o_hostconf_t *hostconf = NULL; diff --git a/lib/core/context.c b/lib/core/context.c index 7fcc47370f..797f60ea41 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -183,3 +183,68 @@ void h2o_context_update_timestamp_cache(h2o_context_t *ctx) h2o_time2str_log(ctx->_timestamp_cache.value->log, ctx->_timestamp_cache.tv_at.tv_sec); } } + +void h2o_context_report_emitted_error(h2o_context_t *ctx, int status, int http) +{ + int s100; + +#define INC_PREDEF_ERROR(x) \ + do { \ + if (status == x) { \ + ctx->emitted_errors.emitted_errors_cnt[E_HTTP_ ## x]++; \ + return; \ + } \ + } while(0) + + if (http == 1) { + INC_PREDEF_ERROR(400); + INC_PREDEF_ERROR(403); + INC_PREDEF_ERROR(404); + INC_PREDEF_ERROR(405); + INC_PREDEF_ERROR(416); + INC_PREDEF_ERROR(417); + INC_PREDEF_ERROR(500); + INC_PREDEF_ERROR(502); + INC_PREDEF_ERROR(503); + +#undef INC_PREDEF_ERROR + + s100 = status / 100; + switch(s100) { + case 4: + ctx->emitted_errors.emitted_errors_cnt[E_HTTP_4XX]++; + return; + case 5: + ctx->emitted_errors.emitted_errors_cnt[E_HTTP_5XX]++; + return; + default: + ctx->emitted_errors.emitted_errors_cnt[E_HTTP_XXX]++; + return; + } + } else { +#define INC_PREDEF_ERROR2(name, x) \ + do { \ + if (status == x) { \ + ctx->emitted_errors.emitted_errors_cnt[E_HTTP2_ ## name]++; \ + return; \ + } \ + } while(0) + + INC_PREDEF_ERROR2(PROTOCOL, -1); + INC_PREDEF_ERROR2(INTERNAL, -2); + INC_PREDEF_ERROR2(FLOW_CONTROL, -3); + INC_PREDEF_ERROR2(SETTINGS_TIMEOUT, -4); + INC_PREDEF_ERROR2(STREAM_CLOSED, -5); + INC_PREDEF_ERROR2(FRAME_SIZE, -6); + INC_PREDEF_ERROR2(REFUSED_STREAM, -7); + INC_PREDEF_ERROR2(CANCEL, -8); + INC_PREDEF_ERROR2(COMPRESSION, -9); + INC_PREDEF_ERROR2(CONNECT, -10); + INC_PREDEF_ERROR2(ENHANCE_YOUR_CALM, -11); + INC_PREDEF_ERROR2(INADEQUATE_SECURITY, -12); + + ctx->emitted_errors.emitted_errors_cnt[E_HTTP2_OTHER]++; + return; +#undef INC_PREDEF_ERROR + } +} diff --git a/lib/core/request.c b/lib/core/request.c index d3760a6ee6..feb26271aa 100644 --- a/lib/core/request.c +++ b/lib/core/request.c @@ -458,6 +458,8 @@ void h2o_send_inline(h2o_req_t *req, const char *body, size_t len) void h2o_send_error(h2o_req_t *req, int status, const char *reason, const char *body, int flags) { + h2o_context_report_emitted_error(req->conn->ctx, status, 1); + if (req->pathconf == NULL) { h2o_hostconf_t *hostconf = setup_before_processing(req); h2o_req_bind_conf(req, hostconf, &hostconf->fallback_path); diff --git a/lib/handler/status.c b/lib/handler/status.c index ac68cca8a0..f00e8d0280 100644 --- a/lib/handler/status.c +++ b/lib/handler/status.c @@ -21,6 +21,13 @@ */ #include "h2o.h" +extern h2o_status_handler_t errors_status_handler; +extern h2o_status_handler_t requests_status_handler; + +struct st_h2o_status_logger_t { + h2o_logger_t super; +}; + struct st_h2o_status_handler_t { h2o_handler_t super; H2O_VECTOR(h2o_multithread_receiver_t *) receivers; @@ -31,15 +38,19 @@ struct st_h2o_status_context_t { h2o_multithread_receiver_t receiver; }; +struct status_ctx { + int active; + void *ctx; +}; struct st_h2o_status_collector_t { struct { h2o_req_t *req; h2o_multithread_receiver_t *receiver; } src; - h2o_logconf_t *logconf; pthread_mutex_t mutex; - h2o_iovec_t data; size_t num_remaining_threads; + h2o_globalconf_t *gconf; + H2O_VECTOR(struct status_ctx) status_ctx; }; struct st_h2o_status_message_t { @@ -47,55 +58,28 @@ struct st_h2o_status_message_t { struct st_h2o_status_collector_t *collector; }; -struct st_collect_req_status_cbdata_t { - h2o_logconf_t *logconf; - h2o_buffer_t *buffer; -}; - -static int collect_req_status(h2o_req_t *req, void *_cbdata) -{ - struct st_collect_req_status_cbdata_t *cbdata = _cbdata; - - /* collect log */ - char buf[4096]; - size_t len = sizeof(buf); - char *logline = h2o_log_request(cbdata->logconf, req, &len, buf); - - /* append to buffer */ - assert(len != 0); - --len; /* omit trailing LF */ - h2o_buffer_reserve(&cbdata->buffer, len + 4); - memcpy(cbdata->buffer->bytes + cbdata->buffer->size, ",\n ", 4); - cbdata->buffer->size += 4; - memcpy(cbdata->buffer->bytes + cbdata->buffer->size, logline, len); - cbdata->buffer->size += len; - - if (logline != buf) - free(logline); - - return 0; -} - static void collect_reqs_of_context(struct st_h2o_status_collector_t *collector, h2o_context_t *ctx) { - struct st_collect_req_status_cbdata_t cbdata = {collector->logconf}; int was_last_thread; - - h2o_buffer_init(&cbdata.buffer, &h2o_socket_buffer_prototype); - ctx->globalconf->http1.callbacks.foreach_request(ctx, collect_req_status, &cbdata); - ctx->globalconf->http2.callbacks.foreach_request(ctx, collect_req_status, &cbdata); + int i; pthread_mutex_lock(&collector->mutex); - if (cbdata.buffer->size != 0) { - collector->data.base = h2o_mem_realloc(collector->data.base, collector->data.len + cbdata.buffer->size); - memcpy(collector->data.base + collector->data.len, cbdata.buffer->bytes, cbdata.buffer->size); - collector->data.len += cbdata.buffer->size; + + for (i = 0; i < collector->gconf->statuses.size; i++) { + h2o_status_handler_t *sh; + sh = &collector->gconf->statuses.entries[i]; + if (!collector->status_ctx.entries[i].active) { + continue; + } + if (sh->type == H2O_STATUS_HANDLER_PER_THREAD) { + sh->per_thread.per_thread_cb(collector->status_ctx.entries[i].ctx, ctx); + } } + + was_last_thread = --collector->num_remaining_threads == 0; pthread_mutex_unlock(&collector->mutex); - h2o_buffer_dispose(&cbdata.buffer); - if (was_last_thread) { struct st_h2o_status_message_t *message = h2o_mem_alloc(sizeof(*message)); message->super = (h2o_multithread_message_t){}; @@ -108,19 +92,57 @@ static void send_response(struct st_h2o_status_collector_t *collector) { static h2o_generator_t generator = {NULL, NULL}; h2o_req_t *req; + size_t nr_statuses; + int i; + int cur_resp = 0; + + req = collector->src.req; + if (!req) { + h2o_mem_release_shared(collector); + return; + } - if ((req = collector->src.req) != NULL) { - h2o_iovec_t resp[] = {{H2O_STRLIT("{\n \"requests\": ")}, collector->data, {H2O_STRLIT("\n ]")}, {}, {H2O_STRLIT("\n}\n")}}; - resp[1].base[0] = '['; - if (req->conn->ctx->globalconf->status.extra_status != NULL) - resp[3] = req->conn->ctx->globalconf->status.extra_status(req->conn->ctx->globalconf, &req->pool); - req->res.status = 200; - h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, H2O_STRLIT("text/plain; charset=utf-8")); - h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, H2O_STRLIT("no-cache, no-store")); - h2o_start_response(req, &generator); - h2o_send(req, resp, - h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")) ? 0 : sizeof(resp) / sizeof(resp[0]), - 1); + nr_statuses = req->conn->ctx->globalconf->statuses.size; + size_t nr_resp = nr_statuses + 2; // 2 for the footer and header + h2o_iovec_t resp[nr_resp]; + + memset(resp, 0, sizeof(resp[0]) * nr_resp); + resp[cur_resp++] = (h2o_iovec_t){ H2O_STRLIT("{\n") }; + + int coma_removed = 0; + for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) { + h2o_status_handler_t *sh = &req->conn->ctx->globalconf->statuses.entries[i]; + if (!collector->status_ctx.entries[i].active) { + continue; + } + if (sh->type == H2O_STATUS_HANDLER_SIMPLE) { + resp[cur_resp++] = sh->simple.status_cb(req->conn->ctx->globalconf, &req->pool); + } else { + resp[cur_resp++] = sh->per_thread.assemble_cb(collector->status_ctx.entries[i].ctx); + } + if (resp[cur_resp - 1].len > 0 && !coma_removed) { + /* requests come in with a leading coma, replace if with a space */ + resp[cur_resp - 1].base[0] = ' '; + coma_removed = 1; + } + } + resp[cur_resp++] = (h2o_iovec_t){ H2O_STRLIT("\n}\n") }; + + req->res.status = 200; + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, H2O_STRLIT("text/plain; charset=utf-8")); + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, H2O_STRLIT("no-cache, no-store")); + h2o_start_response(req, &generator); + h2o_send(req, resp, + h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")) ? 0 : nr_resp, + 1); + for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) { + h2o_status_handler_t *sh = &req->conn->ctx->globalconf->statuses.entries[i]; + if (!collector->status_ctx.entries[i].active) { + continue; + } + if (sh->type == H2O_STATUS_HANDLER_PER_THREAD) { + sh->per_thread.done_cb(collector->status_ctx.entries[i].ctx); + } } h2o_mem_release_shared(collector); } @@ -154,9 +176,7 @@ static void on_collector_dispose(void *_collector) { struct st_h2o_status_collector_t *collector = _collector; - h2o_logconf_dispose(collector->logconf); pthread_mutex_destroy(&collector->mutex); - free(collector->data.base); } static void on_req_close(void *p) @@ -166,60 +186,44 @@ static void on_req_close(void *p) h2o_mem_release_shared(collector); } -static int on_req_json(struct st_h2o_status_handler_t *self, h2o_req_t *req) +static int on_req_json(struct st_h2o_status_handler_t *self, h2o_req_t *req, h2o_iovec_t *status_list) { - h2o_logconf_t *logconf; - -#define ELEMENT(key, expr) "\"" key "\": \"" expr "\"" -#define X_ELEMENT(id) ELEMENT(id, "%{" id "}x") -#define SEPARATOR ", " - const char *fmt = "{" - /* combined_log */ - ELEMENT("host", "%h") SEPARATOR ELEMENT("user", "%u") SEPARATOR ELEMENT("at", "%{%Y%m%dT%H%M%S}t.%{usec_frac}t%{%z}t") - SEPARATOR ELEMENT("method", "%m") SEPARATOR ELEMENT("path", "%U") SEPARATOR ELEMENT("query", "%q") - SEPARATOR ELEMENT("protocol", "%H") SEPARATOR ELEMENT("referer", "%{Referer}i") - SEPARATOR ELEMENT("user-agent", "%{User-agent}i") SEPARATOR - /* time */ - X_ELEMENT("connect-time") SEPARATOR X_ELEMENT("request-header-time") SEPARATOR X_ELEMENT("request-body-time") - SEPARATOR X_ELEMENT("request-total-time") SEPARATOR X_ELEMENT("process-time") SEPARATOR X_ELEMENT("response-time") - SEPARATOR - /* connection */ - X_ELEMENT("connection-id") SEPARATOR X_ELEMENT("ssl.protocol-version") SEPARATOR X_ELEMENT("ssl.session-reused") - SEPARATOR X_ELEMENT("ssl.cipher") SEPARATOR X_ELEMENT("ssl.cipher-bits") SEPARATOR - /* http1 */ - X_ELEMENT("http1.request-index") SEPARATOR - /* http2 */ - X_ELEMENT("http2.stream-id") SEPARATOR X_ELEMENT("http2.priority.received.exclusive") - SEPARATOR X_ELEMENT("http2.priority.received.parent") SEPARATOR X_ELEMENT("http2.priority.received.weight") - SEPARATOR X_ELEMENT("http2.priority.actual.parent") SEPARATOR X_ELEMENT("http2.priority.actual.weight") SEPARATOR - /* misc */ - ELEMENT("authority", "%V") - /* end */ - "}"; -#undef ELEMENT -#undef X_ELEMENT -#undef SEPARATOR - - { /* compile logconf */ - char errbuf[256]; - if ((logconf = h2o_logconf_compile(fmt, H2O_LOGCONF_ESCAPE_JSON, errbuf)) == NULL) { - h2o_iovec_t resp = h2o_concat(&req->pool, h2o_iovec_init(H2O_STRLIT("failed to compile log format:")), - h2o_iovec_init(errbuf, strlen(errbuf))); - h2o_send_error(req, 400, "Invalid Request", resp.base, 0); - return 0; - } - } - { /* construct collector and send request to every thread */ struct st_h2o_status_context_t *status_ctx = h2o_context_get_handler_context(req->conn->ctx, &self->super); struct st_h2o_status_collector_t *collector = h2o_mem_alloc_shared(NULL, sizeof(*collector), on_collector_dispose); - size_t i; - + size_t i, j; + + memset(collector, 0, sizeof(*collector)); + for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) { + h2o_status_handler_t *sh; + + h2o_vector_reserve(&req->pool, &collector->status_ctx, collector->status_ctx.size + 1); + sh = &req->conn->ctx->globalconf->statuses.entries[i]; + + if (status_list) { + int found = 0; + for (j = 0; status_list[j].base; j++) { + if (h2o_memis(status_list[j].base, status_list[j].len, sh->name.base, sh->name.len)) { + found = 1; + break; + } + } + if (!found) { + collector->status_ctx.entries[collector->status_ctx.size].active = 0; + goto Skip; + } + } + if (sh->type == H2O_STATUS_HANDLER_PER_THREAD && sh->per_thread.alloc_context_cb) { + collector->status_ctx.entries[collector->status_ctx.size].ctx = sh->per_thread.alloc_context_cb(req); + } + collector->status_ctx.entries[collector->status_ctx.size].active = 1; +Skip: + collector->status_ctx.size++; + } + collector->gconf = req->conn->ctx->globalconf; collector->src.req = req; collector->src.receiver = &status_ctx->receiver; - collector->logconf = logconf; pthread_mutex_init(&collector->mutex, NULL); - collector->data = h2o_iovec_init(NULL, 0); collector->num_remaining_threads = self->receivers.size; for (i = 0; i != self->receivers.size; ++i) { @@ -236,6 +240,44 @@ static int on_req_json(struct st_h2o_status_handler_t *self, h2o_req_t *req) return 0; } +static h2o_iovec_t *build_status_list(const char *param, size_t len) +{ + int i, cur, start; + int nr_list = 1; + h2o_iovec_t *ret; + for (i = 0; i < len; i++) { + if (param[i] == '|') { + nr_list++; + } + } + ret = h2o_mem_alloc(sizeof(*ret) * (nr_list + 1)); + cur = 0; + start = 0; + for (i = 0; i < len; i++) { + if (param[i] == '|') { + ret[cur++] = h2o_strdup(NULL, ¶m[start], i - start); + start = i + 1; + } + } + if (start != i) { + ret[cur++] = h2o_strdup(NULL, ¶m[start], i - start); + } + ret[cur] = h2o_iovec_init(NULL, 0); + + return ret; +} + +static void free_status_list(h2o_iovec_t *status_list) +{ + int i; + if (!status_list) + return; + for (i = 0; status_list[i].base; i++) { + free(status_list[i].base); + } + free(status_list); +} + static int on_req(h2o_handler_t *_self, h2o_req_t *req) { struct st_h2o_status_handler_t *self = (void *)_self; @@ -252,8 +294,17 @@ static int on_req(h2o_handler_t *_self, h2o_req_t *req) h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, H2O_STRLIT("no-cache")); return h2o_file_send(req, 200, "OK", fn.base, h2o_iovec_init(H2O_STRLIT("text/html; charset=utf-8")), 0); } else if (h2o_memis(local_path.base, local_path.len, H2O_STRLIT("/json"))) { + int ret; /* "/json" maps to the JSON API */ - return on_req_json(self, req); + h2o_iovec_t *status_list = NULL; /* NULL means all */ + if (req->query_at != SIZE_MAX && (req->path.len - req->query_at > 6)) { + if (h2o_memis(&req->path.base[req->query_at], 6, "?show=", 6)) { + status_list = build_status_list(&req->path.base[req->query_at + 6], req->path.len - req->query_at - 6); + } + } + ret = on_req_json(self, req, status_list); + free_status_list(status_list); + return ret; } return -1; @@ -291,10 +342,12 @@ static void on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx) free(status_ctx); } -void h2o_status_register(h2o_pathconf_t *pathconf) +void h2o_status_register(h2o_pathconf_t *conf) { - struct st_h2o_status_handler_t *self = (void *)h2o_create_handler(pathconf, sizeof(*self)); + struct st_h2o_status_handler_t *self = (void *)h2o_create_handler(conf, sizeof(*self)); self->super.on_context_init = on_context_init; self->super.on_context_dispose = on_context_dispose; self->super.on_req = on_req; + h2o_config_register_status_handler(conf->global, requests_status_handler); + h2o_config_register_status_handler(conf->global, errors_status_handler); } diff --git a/lib/handler/status/errors.c b/lib/handler/status/errors.c new file mode 100644 index 0000000000..92afa3fa25 --- /dev/null +++ b/lib/handler/status/errors.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2016 Fastly + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "h2o.h" + +struct errors_status_ctx { + h2o_mem_pool_t *pool; + unsigned long long agg_errors[EMITTED_ERRORS_MAX]; +}; + +static void errors_status_per_thread(void *priv, h2o_context_t *ctx) +{ + size_t i; + struct errors_status_ctx *esc = priv; + for (i = 0; i < EMITTED_ERRORS_MAX; i++) { + esc->agg_errors[i] += ctx->emitted_errors.emitted_errors_cnt[i]; + } +} + +static void *errors_status_alloc_context(h2o_req_t *req) +{ + struct errors_status_ctx *ret; + + ret = h2o_mem_alloc_pool(&req->pool, sizeof(*ret)); + memset(ret, 0, sizeof(*ret)); + ret->pool = &req->pool; + + return ret; +} + +static h2o_iovec_t errors_status_assemble(void *priv) +{ + struct errors_status_ctx *esc = priv; + h2o_iovec_t ret; + +#define BUFSIZE (2*1024) + ret.base = h2o_mem_alloc_pool(esc->pool, BUFSIZE); + ret.len = snprintf(ret.base, BUFSIZE, ",\n" + " \"http1-errors-400\": %llu,\n" + " \"http1-errors-403\": %llu,\n" + " \"http1-errors-404\": %llu,\n" + " \"http1-errors-405\": %llu,\n" + " \"http1-errors-416\": %llu,\n" + " \"http1-errors-417\": %llu,\n" + " \"http1-errors-500\": %llu,\n" + " \"http1-errors-502\": %llu,\n" + " \"http1-errors-503\": %llu,\n" + " \"http1-errors-4xx-others\": %llu,\n" + " \"http1-errors-5xx-others\": %llu,\n" + " \"http1-errors-others\": %llu,\n" + " \"http2-errors-protocol\": %llu, \n" + " \"http2-errors-internal\": %llu, \n" + " \"http2-errors-flow_control\": %llu, \n" + " \"http2-errors-settings_timeout\": %llu, \n" + " \"http2-errors-stream_closed\": %llu, \n" + " \"http2-errors-frame_size\": %llu, \n" + " \"http2-errors-refused_stream\": %llu, \n" + " \"http2-errors-cancel\": %llu, \n" + " \"http2-errors-compression\": %llu, \n" + " \"http2-errors-connect\": %llu, \n" + " \"http2-errors-enhance_your_calm\": %llu, \n" + " \"http2-errors-inadequate_security\": %llu, \n" + " \"http2-errors-other\": %llu", + esc->agg_errors[E_HTTP_400], esc->agg_errors[E_HTTP_403], esc->agg_errors[E_HTTP_404], + esc->agg_errors[E_HTTP_405], esc->agg_errors[E_HTTP_416], esc->agg_errors[E_HTTP_417], + esc->agg_errors[E_HTTP_500], esc->agg_errors[E_HTTP_502], esc->agg_errors[E_HTTP_503], + esc->agg_errors[E_HTTP_4XX], esc->agg_errors[E_HTTP_5XX], esc->agg_errors[E_HTTP_XXX], + esc->agg_errors[E_HTTP2_PROTOCOL], esc->agg_errors[E_HTTP2_INTERNAL], esc->agg_errors[E_HTTP2_FLOW_CONTROL], + esc->agg_errors[E_HTTP2_SETTINGS_TIMEOUT], esc->agg_errors[E_HTTP2_STREAM_CLOSED], esc->agg_errors[E_HTTP2_FRAME_SIZE], + esc->agg_errors[E_HTTP2_REFUSED_STREAM], esc->agg_errors[E_HTTP2_CANCEL], esc->agg_errors[E_HTTP2_COMPRESSION], + esc->agg_errors[E_HTTP2_CONNECT], esc->agg_errors[E_HTTP2_ENHANCE_YOUR_CALM], esc->agg_errors[E_HTTP2_INADEQUATE_SECURITY], + esc->agg_errors[E_HTTP2_OTHER]); + return ret; +#undef BUFSIZE +} + +static void errors_status_done(void *priv) +{ + struct errors_status_ctx *esc = priv; + if (!esc->pool) { + free(esc); + } +} + +h2o_status_handler_t errors_status_handler = { + .type = H2O_STATUS_HANDLER_PER_THREAD, + .name = { H2O_STRLIT("errors") }, + .per_thread = { + .alloc_context_cb = errors_status_alloc_context, + .per_thread_cb = errors_status_per_thread, + .assemble_cb = errors_status_assemble, + .done_cb = errors_status_done, + } +}; diff --git a/lib/handler/status/requests.c b/lib/handler/status/requests.c new file mode 100644 index 0000000000..697cad338e --- /dev/null +++ b/lib/handler/status/requests.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2016 DeNA Co., Ltd., Kazuho Oku + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "h2o.h" + +struct requests_status_ctx { + h2o_logconf_t *logconf; + h2o_mem_pool_t *pool; + h2o_iovec_t req_data; + int first; /* used to know if a thread is the first one, we skip the leading + * coma in that case. */ +}; + +struct st_collect_req_status_cbdata_t { + h2o_logconf_t *logconf; + h2o_buffer_t *buffer; + int first; /* see requests_status_ctx */ +}; + +static int collect_req_status(h2o_req_t *req, void *_cbdata) +{ + struct st_collect_req_status_cbdata_t *cbdata = _cbdata; + + /* collect log */ + char buf[4096]; + size_t len = sizeof(buf); + char *logline = h2o_log_request(cbdata->logconf, req, &len, buf); + + /* append to buffer */ + assert(len != 0); + --len; /* omit trailing LF */ + if (cbdata->first) { + h2o_buffer_reserve(&cbdata->buffer, len + 3); + memcpy(cbdata->buffer->bytes + cbdata->buffer->size, "\n ", 3); + cbdata->buffer->size += 3; + } else { + h2o_buffer_reserve(&cbdata->buffer, len + 4); + memcpy(cbdata->buffer->bytes + cbdata->buffer->size, ",\n ", 4); + cbdata->buffer->size += 4; + } + memcpy(cbdata->buffer->bytes + cbdata->buffer->size, logline, len); + cbdata->buffer->size += len; + + if (logline != buf) + free(logline); + + return 0; +} + +static void requests_status_per_thread(void *priv, h2o_context_t *ctx) +{ + struct requests_status_ctx *rsc = priv; + struct st_collect_req_status_cbdata_t cbdata = {rsc->logconf}; + + cbdata.first = rsc->first; + if (rsc->first) { + rsc->first = 0; + } + h2o_buffer_init(&cbdata.buffer, &h2o_socket_buffer_prototype); + ctx->globalconf->http1.callbacks.foreach_request(ctx, collect_req_status, &cbdata); + ctx->globalconf->http2.callbacks.foreach_request(ctx, collect_req_status, &cbdata); + + if (cbdata.buffer->size != 0) { + rsc->req_data.base = h2o_mem_realloc(rsc->req_data.base, rsc->req_data.len + cbdata.buffer->size); + memcpy(rsc->req_data.base + rsc->req_data.len, cbdata.buffer->bytes, cbdata.buffer->size); + rsc->req_data.len += cbdata.buffer->size; + } + + h2o_buffer_dispose(&cbdata.buffer); +} + +static void *requests_status_alloc_context(h2o_req_t *req) +{ + struct requests_status_ctx *rsc; + rsc = h2o_mem_alloc_pool(&req->pool, sizeof(*rsc)); + rsc->pool = &req->pool; + rsc->first = 1; + +#define ELEMENT(key, expr) "\"" key "\": \"" expr "\"" +#define X_ELEMENT(id) ELEMENT(id, "%{" id "}x") +#define SEPARATOR ", " + const char *fmt = "{" + /* combined_log */ + ELEMENT("host", "%h") SEPARATOR ELEMENT("user", "%u") SEPARATOR ELEMENT("at", "%{%Y%m%dT%H%M%S}t.%{usec_frac}t%{%z}t") + SEPARATOR ELEMENT("method", "%m") SEPARATOR ELEMENT("path", "%U") SEPARATOR ELEMENT("query", "%q") + SEPARATOR ELEMENT("protocol", "%H") SEPARATOR ELEMENT("referer", "%{Referer}i") + SEPARATOR ELEMENT("user-agent", "%{User-agent}i") SEPARATOR + /* time */ + X_ELEMENT("connect-time") SEPARATOR X_ELEMENT("request-header-time") SEPARATOR X_ELEMENT("request-body-time") + SEPARATOR X_ELEMENT("request-total-time") SEPARATOR X_ELEMENT("process-time") SEPARATOR X_ELEMENT("response-time") + SEPARATOR + /* connection */ + X_ELEMENT("connection-id") SEPARATOR X_ELEMENT("ssl.protocol-version") SEPARATOR X_ELEMENT("ssl.session-reused") + SEPARATOR X_ELEMENT("ssl.cipher") SEPARATOR X_ELEMENT("ssl.cipher-bits") SEPARATOR + /* http1 */ + X_ELEMENT("http1.request-index") SEPARATOR + /* http2 */ + X_ELEMENT("http2.stream-id") SEPARATOR X_ELEMENT("http2.priority.received.exclusive") + SEPARATOR X_ELEMENT("http2.priority.received.parent") SEPARATOR X_ELEMENT("http2.priority.received.weight") + SEPARATOR X_ELEMENT("http2.priority.actual.parent") SEPARATOR X_ELEMENT("http2.priority.actual.weight") SEPARATOR + /* misc */ + ELEMENT("authority", "%V") + /* end */ + "}"; +#undef ELEMENT +#undef X_ELEMENT +#undef SEPARATOR + + { /* compile logconf */ + char errbuf[256]; + if ((rsc->logconf = h2o_logconf_compile(fmt, H2O_LOGCONF_ESCAPE_JSON, errbuf)) == NULL) { + h2o_iovec_t resp = h2o_concat(&req->pool, h2o_iovec_init(H2O_STRLIT("failed to compile log format:")), + h2o_iovec_init(errbuf, strlen(errbuf))); + h2o_send_error(req, 400, "Invalid Request", resp.base, 0); + return NULL; + } + } + + rsc->req_data = h2o_strdup(NULL, ",\n \"requests\": [", SIZE_MAX); + return rsc; +} + +static h2o_iovec_t requests_status_assemble(void *priv) +{ + struct requests_status_ctx *rsc = priv; + +#define JSON_FOOTER "\n ]" +#define JSON_FOOTER_LEN 3 + rsc->req_data.base = h2o_mem_realloc(rsc->req_data.base, rsc->req_data.len + JSON_FOOTER_LEN); + memcpy(rsc->req_data.base + rsc->req_data.len, JSON_FOOTER, JSON_FOOTER_LEN); + rsc->req_data.len += JSON_FOOTER_LEN; +#undef JSON_FOOTER +#undef JSON_FOOTER_LEN + + return rsc->req_data; +} + +static void requests_status_done(void *priv) +{ + struct requests_status_ctx *rsc = priv; + + h2o_logconf_dispose(rsc->logconf); + free(rsc->req_data.base); + + if (!rsc->pool) { + free(rsc); + } +} +h2o_status_handler_t requests_status_handler = { + .type = H2O_STATUS_HANDLER_PER_THREAD, + .name = { H2O_STRLIT("requests") }, + .per_thread = { + .alloc_context_cb = requests_status_alloc_context, + .per_thread_cb = requests_status_per_thread, + .assemble_cb = requests_status_assemble, + .done_cb = requests_status_done, + } +}; diff --git a/lib/http2/connection.c b/lib/http2/connection.c index 2f891740af..9a3b86a592 100644 --- a/lib/http2/connection.c +++ b/lib/http2/connection.c @@ -281,6 +281,8 @@ void send_stream_error(h2o_http2_conn_t *conn, uint32_t stream_id, int errnum) assert(stream_id != 0); assert(conn->state < H2O_HTTP2_CONN_STATE_IS_CLOSING); + h2o_context_report_emitted_error(conn->super.ctx, errnum, 2); + h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream_id, -errnum); h2o_http2_conn_request_write(conn); } @@ -859,6 +861,7 @@ static void on_read(h2o_socket_t *sock, const char *err) h2o_http2_conn_t *conn = sock->data; if (err != NULL) { + h2o_context_report_emitted_error(conn->super.ctx, 0, 2); h2o_socket_read_stop(conn->sock); close_connection(conn); return; @@ -929,6 +932,7 @@ static void on_write_complete(h2o_socket_t *sock, const char *err) /* close by error if necessary */ if (err != NULL) { + h2o_context_report_emitted_error(conn->super.ctx, 0, 2); close_connection_now(conn); return; } diff --git a/src/main.c b/src/main.c index 8f3d9e5fc6..a40576403e 100644 --- a/src/main.c +++ b/src/main.c @@ -1511,7 +1511,7 @@ static void setup_configurators(void) h2o_mruby_register_configurator(&conf.globalconf); #endif - conf.globalconf.status.extra_status = on_extra_status; + h2o_config_register_simple_status_handler(&conf.globalconf, (h2o_iovec_t){H2O_STRLIT("main")}, on_extra_status); } int main(int argc, char **argv) diff --git a/srcdoc/configure/status_directives.mt b/srcdoc/configure/status_directives.mt index b8f7a81f9a..68fb499afb 100644 --- a/srcdoc/configure/status_directives.mt +++ b/srcdoc/configure/status_directives.mt @@ -19,6 +19,9 @@ EOT

Access to the handler should be restricted, considering the fact that the status includes the details of in-flight HTTP requests. The example below uses Basic authentication. +Displaying in-flight HTTP requests in the json query +(/path-to-handler/json) can optionally be disabled by +passing ?noreqs as a parameter to the url.

{example}->("Exposing status with Basic authentication", <<'EOT'); paths: diff --git a/t/50status.t b/t/50status.t new file mode 100644 index 0000000000..65ff190919 --- /dev/null +++ b/t/50status.t @@ -0,0 +1,47 @@ +use strict; +use warnings; +use Test::More; +use t::Util; +use JSON; + +plan skip_all => 'curl not found' + unless prog_exists('curl'); + +subtest "default json handler" => sub { + my $server = spawn_h2o(<< "EOT"); +hosts: + default: + paths: + /: + file.dir: @{[ DOC_ROOT ]} + /s: + status: ON +EOT + + my $resp = `curl --silent -o /dev/stderr http://127.0.0.1:$server->{port}/s/json 2>&1 > /dev/null`; + my $jresp = decode_json("$resp"); + my @requests = @{$jresp->{'requests'}}; + is $jresp->{'connections'}, 1, "One connection"; + is scalar @requests, 1, "One request"; +}; + +subtest "json hander noreqs" => sub { + my $server = spawn_h2o(<< "EOT"); +hosts: + default: + paths: + /: + file.dir: @{[ DOC_ROOT ]} + /s: + status: ON +EOT + + my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/s/json?show=main|errors' 2>&1 > /dev/null`; + my $jresp = decode_json("$resp"); + is $jresp->{'connections'}, 1, "One connection"; + is $jresp->{'requests'}, undef, "Zero request"; + is $jresp->{'http1-errors-404'}, 0, "Internal errors monitoring"; +}; + + +done_testing(); From 99b9acf2609784ff8b4c569736ac90f36f1d4872 Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Mon, 16 May 2016 09:55:56 -0700 Subject: [PATCH 2/8] - Simplify `h2o_status_handler_t` by removing the type, and making the `init` and `per_thread` callbacks optional. Only `final` will be mandatory. - Remove GNUisms - Fix the doc to reflect the current implementation. --- include/h2o.h | 25 ++++---------- lib/core/config.c | 6 ++-- lib/handler/status.c | 42 ++++++++++-------------- lib/handler/status/errors.c | 31 +++++------------- lib/handler/status/requests.c | 47 ++++++++++----------------- src/main.c | 4 +-- srcdoc/configure/status_directives.mt | 14 +++++--- 7 files changed, 66 insertions(+), 103 deletions(-) diff --git a/include/h2o.h b/include/h2o.h index 0c5cbe067d..54d5ad1eee 100644 --- a/include/h2o.h +++ b/include/h2o.h @@ -275,25 +275,12 @@ typedef struct st_h2o_protocol_callbacks_t { int (*foreach_request)(h2o_context_t *ctx, int (*cb)(h2o_req_t *req, void *cbdata), void *cbdata); } h2o_protocol_callbacks_t; -enum h2o_status_handler_type { - H2O_STATUS_HANDLER_SIMPLE, - H2O_STATUS_HANDLER_PER_THREAD, -}; -typedef h2o_iovec_t (*simple_status_handler_cb)(h2o_globalconf_t *gconf, h2o_mem_pool_t *pool); -typedef struct h2o_status_handler { - enum h2o_status_handler_type type; +typedef h2o_iovec_t (*final_status_handler_cb)(void *ctx, h2o_globalconf_t *gconf, h2o_req_t *req); +typedef struct st_h2o_status_handler_t { h2o_iovec_t name; - union { - struct { - simple_status_handler_cb status_cb; - } simple; - struct { - void *(*alloc_context_cb)(h2o_req_t *src_req); - void (*per_thread_cb)(void *priv, h2o_context_t *ctx); - h2o_iovec_t (*assemble_cb)(void *ctx); - void (*done_cb)(void *ctx); - } per_thread; - }; + void *(*init)(h2o_iovec_t *error); /* optional call back, allocates a context that will be passed to per_thread() */ + void (*per_thread)(void *priv, h2o_context_t *ctx); /* optional callback, will be called for each thread */ + h2o_iovec_t (*final)(void *ctx, h2o_globalconf_t *gconf, h2o_req_t *req); /* mandatory, will be passed the optional context */ } h2o_status_handler_t; typedef H2O_VECTOR(h2o_status_handler_t) h2o_status_callbacks_t; @@ -1207,7 +1194,7 @@ h2o_pathconf_t *h2o_config_register_path(h2o_hostconf_t *hostconf, const char *p * registers an extra status handler */ void h2o_config_register_status_handler(h2o_globalconf_t *config, h2o_status_handler_t); -void h2o_config_register_simple_status_handler(h2o_globalconf_t *config, h2o_iovec_t name, simple_status_handler_cb status_handler); +void h2o_config_register_simple_status_handler(h2o_globalconf_t *config, h2o_iovec_t name, final_status_handler_cb status_handler); /** * disposes of the resources allocated for the global configuration */ diff --git a/lib/core/config.c b/lib/core/config.c index 6dab5e95f0..474958b653 100644 --- a/lib/core/config.c +++ b/lib/core/config.c @@ -204,15 +204,15 @@ void h2o_config_register_status_handler(h2o_globalconf_t *config, h2o_status_han config->statuses.entries[config->statuses.size++] = status_handler; } -void h2o_config_register_simple_status_handler(h2o_globalconf_t *config, h2o_iovec_t name, simple_status_handler_cb status_handler) +void h2o_config_register_simple_status_handler(h2o_globalconf_t *config, h2o_iovec_t name, final_status_handler_cb status_handler) { h2o_status_handler_t *sh; h2o_vector_reserve(NULL, &config->statuses, config->statuses.size + 1); sh = &config->statuses.entries[config->statuses.size++]; - sh->type = H2O_STATUS_HANDLER_SIMPLE; + memset(sh, 0, sizeof(*sh)); sh->name = h2o_strdup(NULL, name.base, name.len); - sh->simple.status_cb = status_handler; + sh->final = status_handler; } h2o_hostconf_t *h2o_config_register_host(h2o_globalconf_t *config, h2o_iovec_t host, uint16_t port) diff --git a/lib/handler/status.c b/lib/handler/status.c index f00e8d0280..3fe9035705 100644 --- a/lib/handler/status.c +++ b/lib/handler/status.c @@ -28,7 +28,7 @@ struct st_h2o_status_logger_t { h2o_logger_t super; }; -struct st_h2o_status_handler_t { +struct st_h2o_root_status_handler_t { h2o_handler_t super; H2O_VECTOR(h2o_multithread_receiver_t *) receivers; }; @@ -71,8 +71,8 @@ static void collect_reqs_of_context(struct st_h2o_status_collector_t *collector, if (!collector->status_ctx.entries[i].active) { continue; } - if (sh->type == H2O_STATUS_HANDLER_PER_THREAD) { - sh->per_thread.per_thread_cb(collector->status_ctx.entries[i].ctx, ctx); + if (sh->per_thread) { + sh->per_thread(collector->status_ctx.entries[i].ctx, ctx); } } @@ -115,11 +115,7 @@ static void send_response(struct st_h2o_status_collector_t *collector) if (!collector->status_ctx.entries[i].active) { continue; } - if (sh->type == H2O_STATUS_HANDLER_SIMPLE) { - resp[cur_resp++] = sh->simple.status_cb(req->conn->ctx->globalconf, &req->pool); - } else { - resp[cur_resp++] = sh->per_thread.assemble_cb(collector->status_ctx.entries[i].ctx); - } + resp[cur_resp++] = sh->final(collector->status_ctx.entries[i].ctx, req->conn->ctx->globalconf, req); if (resp[cur_resp - 1].len > 0 && !coma_removed) { /* requests come in with a leading coma, replace if with a space */ resp[cur_resp - 1].base[0] = ' '; @@ -135,15 +131,6 @@ static void send_response(struct st_h2o_status_collector_t *collector) h2o_send(req, resp, h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD")) ? 0 : nr_resp, 1); - for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) { - h2o_status_handler_t *sh = &req->conn->ctx->globalconf->statuses.entries[i]; - if (!collector->status_ctx.entries[i].active) { - continue; - } - if (sh->type == H2O_STATUS_HANDLER_PER_THREAD) { - sh->per_thread.done_cb(collector->status_ctx.entries[i].ctx); - } - } h2o_mem_release_shared(collector); } @@ -186,7 +173,7 @@ static void on_req_close(void *p) h2o_mem_release_shared(collector); } -static int on_req_json(struct st_h2o_status_handler_t *self, h2o_req_t *req, h2o_iovec_t *status_list) +static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req, h2o_iovec_t *status_list) { { /* construct collector and send request to every thread */ struct st_h2o_status_context_t *status_ctx = h2o_context_get_handler_context(req->conn->ctx, &self->super); @@ -213,8 +200,15 @@ static int on_req_json(struct st_h2o_status_handler_t *self, h2o_req_t *req, h2o goto Skip; } } - if (sh->type == H2O_STATUS_HANDLER_PER_THREAD && sh->per_thread.alloc_context_cb) { - collector->status_ctx.entries[collector->status_ctx.size].ctx = sh->per_thread.alloc_context_cb(req); + if (sh->init) { + h2o_iovec_t err = {NULL, 0}; + void *p; + p = sh->init(&err); + if (!p) { + h2o_send_error(req, 400, "Invalid Request", err.base, 0); + return 0; + } + collector->status_ctx.entries[collector->status_ctx.size].ctx = p; } collector->status_ctx.entries[collector->status_ctx.size].active = 1; Skip: @@ -280,7 +274,7 @@ static void free_status_list(h2o_iovec_t *status_list) static int on_req(h2o_handler_t *_self, h2o_req_t *req) { - struct st_h2o_status_handler_t *self = (void *)_self; + struct st_h2o_root_status_handler_t *self = (void *)_self; size_t prefix_len = req->pathconf->path.len - (req->pathconf->path.base[req->pathconf->path.len - 1] == '/'); h2o_iovec_t local_path = h2o_iovec_init(req->path_normalized.base + prefix_len, req->path_normalized.len - prefix_len); @@ -312,7 +306,7 @@ static int on_req(h2o_handler_t *_self, h2o_req_t *req) static void on_context_init(h2o_handler_t *_self, h2o_context_t *ctx) { - struct st_h2o_status_handler_t *self = (void *)_self; + struct st_h2o_root_status_handler_t *self = (void *)_self; struct st_h2o_status_context_t *status_ctx = h2o_mem_alloc(sizeof(*status_ctx)); status_ctx->ctx = ctx; @@ -326,7 +320,7 @@ static void on_context_init(h2o_handler_t *_self, h2o_context_t *ctx) static void on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx) { - struct st_h2o_status_handler_t *self = (void *)_self; + struct st_h2o_root_status_handler_t *self = (void *)_self; struct st_h2o_status_context_t *status_ctx = h2o_context_get_handler_context(ctx, &self->super); size_t i; @@ -344,7 +338,7 @@ static void on_context_dispose(h2o_handler_t *_self, h2o_context_t *ctx) void h2o_status_register(h2o_pathconf_t *conf) { - struct st_h2o_status_handler_t *self = (void *)h2o_create_handler(conf, sizeof(*self)); + struct st_h2o_root_status_handler_t *self = (void *)h2o_create_handler(conf, sizeof(*self)); self->super.on_context_init = on_context_init; self->super.on_context_dispose = on_context_dispose; self->super.on_req = on_req; diff --git a/lib/handler/status/errors.c b/lib/handler/status/errors.c index 92afa3fa25..4f5365d14b 100644 --- a/lib/handler/status/errors.c +++ b/lib/handler/status/errors.c @@ -23,7 +23,6 @@ #include "h2o.h" struct errors_status_ctx { - h2o_mem_pool_t *pool; unsigned long long agg_errors[EMITTED_ERRORS_MAX]; }; @@ -36,24 +35,23 @@ static void errors_status_per_thread(void *priv, h2o_context_t *ctx) } } -static void *errors_status_alloc_context(h2o_req_t *req) +static void *errors_status_init(h2o_iovec_t *err) { struct errors_status_ctx *ret; - ret = h2o_mem_alloc_pool(&req->pool, sizeof(*ret)); + ret = h2o_mem_alloc(sizeof(*ret)); memset(ret, 0, sizeof(*ret)); - ret->pool = &req->pool; return ret; } -static h2o_iovec_t errors_status_assemble(void *priv) +static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) { struct errors_status_ctx *esc = priv; h2o_iovec_t ret; #define BUFSIZE (2*1024) - ret.base = h2o_mem_alloc_pool(esc->pool, BUFSIZE); + ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); ret.len = snprintf(ret.base, BUFSIZE, ",\n" " \"http1-errors-400\": %llu,\n" " \"http1-errors-403\": %llu,\n" @@ -89,25 +87,14 @@ static h2o_iovec_t errors_status_assemble(void *priv) esc->agg_errors[E_HTTP2_REFUSED_STREAM], esc->agg_errors[E_HTTP2_CANCEL], esc->agg_errors[E_HTTP2_COMPRESSION], esc->agg_errors[E_HTTP2_CONNECT], esc->agg_errors[E_HTTP2_ENHANCE_YOUR_CALM], esc->agg_errors[E_HTTP2_INADEQUATE_SECURITY], esc->agg_errors[E_HTTP2_OTHER]); + free(esc); return ret; #undef BUFSIZE } -static void errors_status_done(void *priv) -{ - struct errors_status_ctx *esc = priv; - if (!esc->pool) { - free(esc); - } -} - h2o_status_handler_t errors_status_handler = { - .type = H2O_STATUS_HANDLER_PER_THREAD, - .name = { H2O_STRLIT("errors") }, - .per_thread = { - .alloc_context_cb = errors_status_alloc_context, - .per_thread_cb = errors_status_per_thread, - .assemble_cb = errors_status_assemble, - .done_cb = errors_status_done, - } + { H2O_STRLIT("errors") }, + errors_status_init, + errors_status_per_thread, + errors_status_final, }; diff --git a/lib/handler/status/requests.c b/lib/handler/status/requests.c index 697cad338e..c67e89b1b5 100644 --- a/lib/handler/status/requests.c +++ b/lib/handler/status/requests.c @@ -24,7 +24,6 @@ struct requests_status_ctx { h2o_logconf_t *logconf; - h2o_mem_pool_t *pool; h2o_iovec_t req_data; int first; /* used to know if a thread is the first one, we skip the leading * coma in that case. */ @@ -88,11 +87,10 @@ static void requests_status_per_thread(void *priv, h2o_context_t *ctx) h2o_buffer_dispose(&cbdata.buffer); } -static void *requests_status_alloc_context(h2o_req_t *req) +static void *requests_status_init(h2o_iovec_t *error) { struct requests_status_ctx *rsc; - rsc = h2o_mem_alloc_pool(&req->pool, sizeof(*rsc)); - rsc->pool = &req->pool; + rsc = h2o_mem_alloc(sizeof(*rsc)); rsc->first = 1; #define ELEMENT(key, expr) "\"" key "\": \"" expr "\"" @@ -128,9 +126,10 @@ static void *requests_status_alloc_context(h2o_req_t *req) { /* compile logconf */ char errbuf[256]; if ((rsc->logconf = h2o_logconf_compile(fmt, H2O_LOGCONF_ESCAPE_JSON, errbuf)) == NULL) { - h2o_iovec_t resp = h2o_concat(&req->pool, h2o_iovec_init(H2O_STRLIT("failed to compile log format:")), + h2o_iovec_t resp = h2o_concat(NULL, h2o_iovec_init(H2O_STRLIT("failed to compile log format:")), h2o_iovec_init(errbuf, strlen(errbuf))); - h2o_send_error(req, 400, "Invalid Request", resp.base, 0); + *error = resp; + free(rsc); return NULL; } } @@ -139,39 +138,29 @@ static void *requests_status_alloc_context(h2o_req_t *req) return rsc; } -static h2o_iovec_t requests_status_assemble(void *priv) +static h2o_iovec_t requests_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) { + h2o_iovec_t ret; struct requests_status_ctx *rsc = priv; #define JSON_FOOTER "\n ]" #define JSON_FOOTER_LEN 3 - rsc->req_data.base = h2o_mem_realloc(rsc->req_data.base, rsc->req_data.len + JSON_FOOTER_LEN); - memcpy(rsc->req_data.base + rsc->req_data.len, JSON_FOOTER, JSON_FOOTER_LEN); - rsc->req_data.len += JSON_FOOTER_LEN; + ret.base = h2o_mem_alloc_pool(&req->pool, rsc->req_data.len + JSON_FOOTER_LEN); + memcpy(ret.base, rsc->req_data.base, rsc->req_data.len); + memcpy(ret.base + rsc->req_data.len, JSON_FOOTER, JSON_FOOTER_LEN); + ret.len = rsc->req_data.len + JSON_FOOTER_LEN; #undef JSON_FOOTER #undef JSON_FOOTER_LEN - return rsc->req_data; -} - -static void requests_status_done(void *priv) -{ - struct requests_status_ctx *rsc = priv; - h2o_logconf_dispose(rsc->logconf); free(rsc->req_data.base); - - if (!rsc->pool) { - free(rsc); - } + free(rsc); + return ret; } + h2o_status_handler_t requests_status_handler = { - .type = H2O_STATUS_HANDLER_PER_THREAD, - .name = { H2O_STRLIT("requests") }, - .per_thread = { - .alloc_context_cb = requests_status_alloc_context, - .per_thread_cb = requests_status_per_thread, - .assemble_cb = requests_status_assemble, - .done_cb = requests_status_done, - } + { H2O_STRLIT("requests") }, + requests_status_init, + requests_status_per_thread, + requests_status_final, }; diff --git a/src/main.c b/src/main.c index a40576403e..5238f1c10c 100644 --- a/src/main.c +++ b/src/main.c @@ -1428,7 +1428,7 @@ static int run_using_server_starter(const char *h2o_cmd, const char *config_file return EX_CONFIG; } -static h2o_iovec_t on_extra_status(h2o_globalconf_t *_conf, h2o_mem_pool_t *pool) +static h2o_iovec_t on_extra_status(void *unused, h2o_globalconf_t *_conf, h2o_req_t *req) { #define BUFSIZE 1024 h2o_iovec_t ret; @@ -1441,7 +1441,7 @@ static h2o_iovec_t on_extra_status(h2o_globalconf_t *_conf, h2o_mem_pool_t *pool if ((generation = getenv("SERVER_STARTER_GENERATION")) == NULL) generation = "null"; - ret.base = h2o_mem_alloc_pool(pool, BUFSIZE); + ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); ret.len = snprintf(ret.base, BUFSIZE, ",\n" " \"server-version\": \"" H2O_VERSION "\",\n" " \"openssl-version\": \"%s\",\n" diff --git a/srcdoc/configure/status_directives.mt b/srcdoc/configure/status_directives.mt index 68fb499afb..6e56691190 100644 --- a/srcdoc/configure/status_directives.mt +++ b/srcdoc/configure/status_directives.mt @@ -18,10 +18,16 @@ EOT ?>

Access to the handler should be restricted, considering the fact that the status includes the details of in-flight HTTP requests. -The example below uses Basic authentication. -Displaying in-flight HTTP requests in the json query -(/path-to-handler/json) can optionally be disabled by -passing ?noreqs as a parameter to the url. +The example below uses Basic authentication.

+

The information returned by the /json handler can be filtered out using +the optional show=module1|module2 parameter.

+ +

There are currently three modules defined: +

{example}->("Exposing status with Basic authentication", <<'EOT'); paths: From 7ee8e64787f621e536c5fc450e36bb0d492600c9 Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Mon, 16 May 2016 13:30:57 -0700 Subject: [PATCH 3/8] - Split errors in http1/http2 - Use predefined functions for the callsites where the status is known, avoiding one indirection level --- include/h2o.h | 64 ++++++++++++++++++++++++------------- lib/core/context.c | 37 +++------------------ lib/core/proxy.c | 8 ++--- lib/core/request.c | 13 ++++---- lib/handler/fastcgi.c | 2 +- lib/handler/file.c | 14 ++++---- lib/handler/mruby.c | 4 +-- lib/handler/redirect.c | 2 +- lib/handler/status.c | 2 +- lib/handler/status/errors.c | 32 ++++++++++++------- lib/http1.c | 4 +-- lib/http2/connection.c | 11 +++++-- 12 files changed, 99 insertions(+), 94 deletions(-) diff --git a/include/h2o.h b/include/h2o.h index 54d5ad1eee..f5340dfe79 100644 --- a/include/h2o.h +++ b/include/h2o.h @@ -419,19 +419,6 @@ typedef struct st_h2o_mimemap_type_t { /* errors emanating from h2o */ enum { - /* http */ - E_HTTP_400 = 0, - E_HTTP_403, - E_HTTP_404, - E_HTTP_405, - E_HTTP_416, - E_HTTP_417, - E_HTTP_500, - E_HTTP_502, - E_HTTP_503, - E_HTTP_4XX, - E_HTTP_5XX, - E_HTTP_XXX, /* http2 */ E_HTTP2_PROTOCOL, E_HTTP2_INTERNAL, @@ -446,12 +433,24 @@ enum { E_HTTP2_ENHANCE_YOUR_CALM, E_HTTP2_INADEQUATE_SECURITY, E_HTTP2_OTHER, - EMITTED_ERRORS_MAX, + E_HTTP2_MAX, +}; +enum { + /* http1 */ + E_HTTP_400 = 0, + E_HTTP_403, + E_HTTP_404, + E_HTTP_405, + E_HTTP_416, + E_HTTP_417, + E_HTTP_500, + E_HTTP_502, + E_HTTP_503, + E_HTTP_4XX, + E_HTTP_5XX, + E_HTTP_XXX, + E_HTTP_MAX, }; - -typedef struct st_h2o_emitted_errors_cnt { - unsigned long emitted_errors_cnt[EMITTED_ERRORS_MAX]; -} h2o_emitted_errors_t; /** * context of the http server. @@ -521,6 +520,10 @@ struct st_h2o_context_t { * timeout entry used for graceful shutdown */ h2o_timeout_entry_t _graceful_shutdown_timeout; + /** + * counter for http2 errors internally emitted by h2o + */ + unsigned long emitted_errors[E_HTTP2_MAX]; } http2; struct { @@ -546,9 +549,9 @@ struct st_h2o_context_t { } _timestamp_cache; /** - * counter for errors internally emitted by h2o + * counter for http1 errors internally emitted by h2o */ - h2o_emitted_errors_t emitted_errors; + unsigned long emitted_errors[E_HTTP_MAX]; H2O_VECTOR(h2o_pathconf_t *) _pathconfs_inited; }; @@ -1266,7 +1269,7 @@ static void *h2o_context_get_logger_context(h2o_context_t *ctx, h2o_logger_t *lo /** * internally reports an error emitted by h2o, so that this can in turn be reported in statuses */ -void h2o_context_report_emitted_error(h2o_context_t *ctx, int status, int http); +void h2o_context_report_http1_error(h2o_context_t *ctx, int status); /* built-in generators */ enum { @@ -1287,7 +1290,24 @@ void h2o_send_inline(h2o_req_t *req, const char *body, size_t len); /** * sends the given information as an error response to the client */ -void h2o_send_error(h2o_req_t *req, int status, const char *reason, const char *body, int flags); +void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, const char *body, int flags); +#define H2O_SEND_ERROR_XXX(status) \ + static inline void h2o_send_error_ ## status(h2o_req_t *req, const char *reason, const char *body, int flags) \ + { \ + req->conn->ctx->emitted_errors[E_HTTP_ ## status]++; \ + h2o_send_error_generic(req, status, reason, body, flags); \ + } + +H2O_SEND_ERROR_XXX(400) +H2O_SEND_ERROR_XXX(403) +H2O_SEND_ERROR_XXX(404) +H2O_SEND_ERROR_XXX(405) +H2O_SEND_ERROR_XXX(416) +H2O_SEND_ERROR_XXX(417) +H2O_SEND_ERROR_XXX(500) +H2O_SEND_ERROR_XXX(502) +H2O_SEND_ERROR_XXX(503) + /** * sends error response using zero timeout; can be called by output filters while processing the headers */ diff --git a/lib/core/context.c b/lib/core/context.c index 797f60ea41..b3afc48c1e 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -184,19 +184,18 @@ void h2o_context_update_timestamp_cache(h2o_context_t *ctx) } } -void h2o_context_report_emitted_error(h2o_context_t *ctx, int status, int http) +void h2o_context_report_http1_error(h2o_context_t *ctx, int status) { int s100; #define INC_PREDEF_ERROR(x) \ do { \ if (status == x) { \ - ctx->emitted_errors.emitted_errors_cnt[E_HTTP_ ## x]++; \ + ctx->emitted_errors[E_HTTP_ ## x]++; \ return; \ } \ } while(0) - if (http == 1) { INC_PREDEF_ERROR(400); INC_PREDEF_ERROR(403); INC_PREDEF_ERROR(404); @@ -212,39 +211,13 @@ void h2o_context_report_emitted_error(h2o_context_t *ctx, int status, int http) s100 = status / 100; switch(s100) { case 4: - ctx->emitted_errors.emitted_errors_cnt[E_HTTP_4XX]++; + ctx->emitted_errors[E_HTTP_4XX]++; return; case 5: - ctx->emitted_errors.emitted_errors_cnt[E_HTTP_5XX]++; + ctx->emitted_errors[E_HTTP_5XX]++; return; default: - ctx->emitted_errors.emitted_errors_cnt[E_HTTP_XXX]++; + ctx->emitted_errors[E_HTTP_XXX]++; return; } - } else { -#define INC_PREDEF_ERROR2(name, x) \ - do { \ - if (status == x) { \ - ctx->emitted_errors.emitted_errors_cnt[E_HTTP2_ ## name]++; \ - return; \ - } \ - } while(0) - - INC_PREDEF_ERROR2(PROTOCOL, -1); - INC_PREDEF_ERROR2(INTERNAL, -2); - INC_PREDEF_ERROR2(FLOW_CONTROL, -3); - INC_PREDEF_ERROR2(SETTINGS_TIMEOUT, -4); - INC_PREDEF_ERROR2(STREAM_CLOSED, -5); - INC_PREDEF_ERROR2(FRAME_SIZE, -6); - INC_PREDEF_ERROR2(REFUSED_STREAM, -7); - INC_PREDEF_ERROR2(CANCEL, -8); - INC_PREDEF_ERROR2(COMPRESSION, -9); - INC_PREDEF_ERROR2(CONNECT, -10); - INC_PREDEF_ERROR2(ENHANCE_YOUR_CALM, -11); - INC_PREDEF_ERROR2(INADEQUATE_SECURITY, -12); - - ctx->emitted_errors.emitted_errors_cnt[E_HTTP2_OTHER]++; - return; -#undef INC_PREDEF_ERROR - } } diff --git a/lib/core/proxy.c b/lib/core/proxy.c index 040be5d588..72d096a853 100644 --- a/lib/core/proxy.c +++ b/lib/core/proxy.c @@ -336,7 +336,7 @@ static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *er if (errstr != NULL && errstr != h2o_http1client_error_is_eos) { self->client = NULL; h2o_req_log_error(req, "lib/core/proxy.c", "%s", errstr); - h2o_send_error(req, 502, "Gateway Error", errstr, 0); + h2o_send_error_502(req, "Gateway Error", errstr, 0); return NULL; } @@ -355,7 +355,7 @@ static h2o_http1client_body_cb on_head(h2o_http1client_t *client, const char *er (req->res.content_length = h2o_strtosize(headers[i].value, headers[i].value_len)) == SIZE_MAX) { self->client = NULL; h2o_req_log_error(req, "lib/core/proxy.c", "%s", "invalid response from upstream (malformed content-length)"); - h2o_send_error(req, 502, "Gateway Error", "invalid response from upstream", 0); + h2o_send_error_502(req, "Gateway Error", "invalid response from upstream", 0); return NULL; } goto Skip; @@ -419,7 +419,7 @@ static h2o_http1client_head_cb on_connect(h2o_http1client_t *client, const char if (errstr != NULL) { self->client = NULL; h2o_req_log_error(self->src_req, "lib/core/proxy.c", "%s", errstr); - h2o_send_error(self->src_req, 502, "Gateway Error", errstr, 0); + h2o_send_error_502(self->src_req, "Gateway Error", errstr, 0); return NULL; } @@ -488,7 +488,7 @@ void h2o__proxy_process_request(h2o_req_t *req) h2o_req_log_error(req, "lib/core/proxy.c", "invalid URL supplied for internal redirection:%s://%.*s%.*s", req->scheme->name.base, (int)req->authority.len, req->authority.base, (int)req->path.len, req->path.base); - h2o_send_error(req, 502, "Gateway Error", "internal error", 0); + h2o_send_error_502(req, "Gateway Error", "internal error", 0); return; } if (port == 65535) diff --git a/lib/core/request.c b/lib/core/request.c index feb26271aa..d80d37843d 100644 --- a/lib/core/request.c +++ b/lib/core/request.c @@ -128,7 +128,7 @@ static void call_handlers(h2o_req_t *req, h2o_handler_t **handler) if ((*handler)->on_req(*handler, req) == 0) return; - h2o_send_error(req, 404, "File Not Found", "not found", 0); + h2o_send_error_404(req, "File Not Found", "not found", 0); } static void process_hosted_request(h2o_req_t *req, h2o_hostconf_t *hostconf) @@ -304,14 +304,14 @@ void h2o_reprocess_request(h2o_req_t *req, h2o_iovec_t method, const h2o_url_sch if (req->res_is_delegated) { if (req->num_delegated == req->conn->ctx->globalconf->max_delegations) { /* TODO log */ - h2o_send_error(req, 502, "Gateway Error", "too many internal delegations", 0); + h2o_send_error_502(req, "Gateway Error", "too many internal delegations", 0); return; } ++req->num_delegated; } else { if (req->num_reprocessed >= 5) { /* TODO log */ - h2o_send_error(req, 502, "Gateway Error", "too many internal reprocesses", 0); + h2o_send_error_502(req, "Gateway Error", "too many internal reprocesses", 0); return; } ++req->num_reprocessed; @@ -456,10 +456,8 @@ void h2o_send_inline(h2o_req_t *req, const char *body, size_t len) h2o_send(req, &buf, 1, 1); } -void h2o_send_error(h2o_req_t *req, int status, const char *reason, const char *body, int flags) +void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, const char *body, int flags) { - h2o_context_report_emitted_error(req->conn->ctx, status, 1); - if (req->pathconf == NULL) { h2o_hostconf_t *hostconf = setup_before_processing(req); h2o_req_bind_conf(req, hostconf, &hostconf->fallback_path); @@ -484,7 +482,8 @@ static void send_error_deferred_cb(h2o_timeout_entry_t *entry) { struct st_send_error_deferred_t *args = H2O_STRUCT_FROM_MEMBER(struct st_send_error_deferred_t, _timeout, entry); reset_response(args->req); - h2o_send_error(args->req, args->status, args->reason, args->body, args->flags); + h2o_context_report_http1_error(args->req->conn->ctx, args->status); + h2o_send_error_generic(args->req, args->status, args->reason, args->body, args->flags); } void h2o_send_error_deferred(h2o_req_t *req, int status, const char *reason, const char *body, int flags) diff --git a/lib/handler/fastcgi.c b/lib/handler/fastcgi.c index 56242ca8aa..87011c88c7 100644 --- a/lib/handler/fastcgi.c +++ b/lib/handler/fastcgi.c @@ -461,7 +461,7 @@ static void errorclose(struct st_fcgi_generator_t *generator) } else { h2o_req_t *req = generator->req; close_generator(generator); - h2o_send_error(req, 503, "Internal Server Error", "Internal Server Error", 0); + h2o_send_error_503(req, "Internal Server Error", "Internal Server Error", 0); } } diff --git a/lib/handler/file.c b/lib/handler/file.c index 8e0b35b3d0..aea70a75ce 100644 --- a/lib/handler/file.c +++ b/lib/handler/file.c @@ -562,7 +562,7 @@ static int try_dynamic_request(h2o_file_handler_t *self, h2o_req_t *req, char *r static void send_method_not_allowed(h2o_req_t *req) { h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ALLOW, H2O_STRLIT("GET, HEAD")); - h2o_send_error(req, 405, "Method Not Allowed", "method not allowed", H2O_SEND_ERROR_KEEP_HEADERS); + h2o_send_error_405(req, "Method Not Allowed", "method not allowed", H2O_SEND_ERROR_KEEP_HEADERS); } static int serve_with_generator(struct st_h2o_sendfile_generator_t *generator, h2o_req_t *req, const char *rpath, size_t rpath_len, @@ -622,7 +622,7 @@ static int serve_with_generator(struct st_h2o_sendfile_generator_t *generator, h content_range.base = h2o_mem_alloc_pool(&req->pool, 32); content_range.len = sprintf(content_range.base, "bytes */%zu", generator->bytesleft); h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_RANGE, content_range.base, content_range.len); - h2o_send_error(req, 416, "Request Range Not Satisfiable", "requested range not satisfiable", + h2o_send_error_416(req, "Request Range Not Satisfiable", "requested range not satisfiable", H2O_SEND_ERROR_KEEP_HEADERS); goto Close; } @@ -767,14 +767,14 @@ static int on_req(h2o_handler_t *_self, h2o_req_t *req) /* failed to open */ if (errno == ENFILE || errno == EMFILE) { - h2o_send_error(req, 503, "Service Unavailable", "please try again later", 0); + h2o_send_error_503(req, "Service Unavailable", "please try again later", 0); } else { if (h2o_mimemap_has_dynamic_type(self->mimemap) && try_dynamic_request(self, req, rpath, rpath_len) == 0) return 0; if (errno == ENOENT || errno == ENOTDIR) { return -1; } else { - h2o_send_error(req, 403, "Access Forbidden", "access forbidden", 0); + h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0); } } return 0; @@ -888,13 +888,13 @@ static int specific_handler_on_req(h2o_handler_t *_self, h2o_req_t *req) /* open file (or send error or return -1) */ if ((generator = create_generator(req, self->real_path.base, self->real_path.len, &is_dir, self->flags)) == NULL) { if (is_dir) { - h2o_send_error(req, 403, "Access Forbidden", "access forbidden", 0); + h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0); } else if (errno == ENOENT) { return -1; } else if (errno == ENFILE || errno == EMFILE) { - h2o_send_error(req, 503, "Service Unavailable", "please try again later", 0); + h2o_send_error_503(req, "Service Unavailable", "please try again later", 0); } else { - h2o_send_error(req, 403, "Access Forbidden", "access forbidden", 0); + h2o_send_error_403(req, "Access Forbidden", "access forbidden", 0); } return 0; } diff --git a/lib/handler/mruby.c b/lib/handler/mruby.c index 6814888ac4..a8f1c7643e 100644 --- a/lib/handler/mruby.c +++ b/lib/handler/mruby.c @@ -681,7 +681,7 @@ static void send_response(h2o_mruby_generator_t *generator, mrb_int status, mrb_ GotException: report_exception(generator->req, mrb); - h2o_send_error(generator->req, 500, "Internal Server Error", "Internal Server Error", 0); + h2o_send_error_500(generator->req, "Internal Server Error", "Internal Server Error", 0); } void h2o_mruby_run_fiber(h2o_mruby_generator_t *generator, mrb_value receiver, mrb_value input, int *is_delegate) @@ -778,7 +778,7 @@ void h2o_mruby_run_fiber(h2o_mruby_generator_t *generator, mrb_value receiver, m if (generator->req != NULL) { report_exception(generator->req, mrb); if (generator->req->_generator == NULL) { - h2o_send_error(generator->req, 500, "Internal Server Error", "Internal Server Error", 0); + h2o_send_error_500(generator->req, "Internal Server Error", "Internal Server Error", 0); } else { h2o_mruby_send_chunked_close(generator); } diff --git a/lib/handler/redirect.c b/lib/handler/redirect.c index f61bd0bb9e..38292b8b12 100644 --- a/lib/handler/redirect.c +++ b/lib/handler/redirect.c @@ -76,7 +76,7 @@ static void redirect_internally(h2o_redirect_handler_t *self, h2o_req_t *req, h2 return; SendInternalError: - h2o_send_error(req, 503, "Internal Server Error", "internal server error", 0); + h2o_send_error_503(req, "Internal Server Error", "internal server error", 0); } static int on_req(h2o_handler_t *_self, h2o_req_t *req) diff --git a/lib/handler/status.c b/lib/handler/status.c index 3fe9035705..2e25e8ccb2 100644 --- a/lib/handler/status.c +++ b/lib/handler/status.c @@ -205,7 +205,7 @@ static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req void *p; p = sh->init(&err); if (!p) { - h2o_send_error(req, 400, "Invalid Request", err.base, 0); + h2o_send_error_400(req, "Invalid Request", err.base, 0); return 0; } collector->status_ctx.entries[collector->status_ctx.size].ctx = p; diff --git a/lib/handler/status/errors.c b/lib/handler/status/errors.c index 4f5365d14b..a6f9b5c58d 100644 --- a/lib/handler/status/errors.c +++ b/lib/handler/status/errors.c @@ -23,15 +23,19 @@ #include "h2o.h" struct errors_status_ctx { - unsigned long long agg_errors[EMITTED_ERRORS_MAX]; + unsigned long long agg_errors_http1[E_HTTP_MAX]; + unsigned long long agg_errors_http2[E_HTTP2_MAX]; }; static void errors_status_per_thread(void *priv, h2o_context_t *ctx) { size_t i; struct errors_status_ctx *esc = priv; - for (i = 0; i < EMITTED_ERRORS_MAX; i++) { - esc->agg_errors[i] += ctx->emitted_errors.emitted_errors_cnt[i]; + for (i = 0; i < E_HTTP_MAX; i++) { + esc->agg_errors_http1[i] += ctx->emitted_errors[i]; + } + for (i = 0; i < E_HTTP_MAX; i++) { + esc->agg_errors_http2[i] += ctx->http2.emitted_errors[i]; } } @@ -78,15 +82,19 @@ static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_ " \"http2-errors-enhance_your_calm\": %llu, \n" " \"http2-errors-inadequate_security\": %llu, \n" " \"http2-errors-other\": %llu", - esc->agg_errors[E_HTTP_400], esc->agg_errors[E_HTTP_403], esc->agg_errors[E_HTTP_404], - esc->agg_errors[E_HTTP_405], esc->agg_errors[E_HTTP_416], esc->agg_errors[E_HTTP_417], - esc->agg_errors[E_HTTP_500], esc->agg_errors[E_HTTP_502], esc->agg_errors[E_HTTP_503], - esc->agg_errors[E_HTTP_4XX], esc->agg_errors[E_HTTP_5XX], esc->agg_errors[E_HTTP_XXX], - esc->agg_errors[E_HTTP2_PROTOCOL], esc->agg_errors[E_HTTP2_INTERNAL], esc->agg_errors[E_HTTP2_FLOW_CONTROL], - esc->agg_errors[E_HTTP2_SETTINGS_TIMEOUT], esc->agg_errors[E_HTTP2_STREAM_CLOSED], esc->agg_errors[E_HTTP2_FRAME_SIZE], - esc->agg_errors[E_HTTP2_REFUSED_STREAM], esc->agg_errors[E_HTTP2_CANCEL], esc->agg_errors[E_HTTP2_COMPRESSION], - esc->agg_errors[E_HTTP2_CONNECT], esc->agg_errors[E_HTTP2_ENHANCE_YOUR_CALM], esc->agg_errors[E_HTTP2_INADEQUATE_SECURITY], - esc->agg_errors[E_HTTP2_OTHER]); + esc->agg_errors_http1[E_HTTP_400], esc->agg_errors_http1[E_HTTP_403], + esc->agg_errors_http1[E_HTTP_404], esc->agg_errors_http1[E_HTTP_405], + esc->agg_errors_http1[E_HTTP_416], esc->agg_errors_http1[E_HTTP_417], + esc->agg_errors_http1[E_HTTP_500], esc->agg_errors_http1[E_HTTP_502], + esc->agg_errors_http1[E_HTTP_503], esc->agg_errors_http1[E_HTTP_4XX], + esc->agg_errors_http1[E_HTTP_5XX], esc->agg_errors_http1[E_HTTP_XXX], + esc->agg_errors_http2[E_HTTP2_PROTOCOL], esc->agg_errors_http2[E_HTTP2_INTERNAL], + esc->agg_errors_http2[E_HTTP2_FLOW_CONTROL], esc->agg_errors_http2[E_HTTP2_SETTINGS_TIMEOUT], + esc->agg_errors_http2[E_HTTP2_STREAM_CLOSED], esc->agg_errors_http2[E_HTTP2_FRAME_SIZE], + esc->agg_errors_http2[E_HTTP2_REFUSED_STREAM], esc->agg_errors_http2[E_HTTP2_CANCEL], + esc->agg_errors_http2[E_HTTP2_COMPRESSION], esc->agg_errors_http2[E_HTTP2_CONNECT], + esc->agg_errors_http2[E_HTTP2_ENHANCE_YOUR_CALM], esc->agg_errors_http2[E_HTTP2_INADEQUATE_SECURITY], + esc->agg_errors_http2[E_HTTP2_OTHER]); free(esc); return ret; #undef BUFSIZE diff --git a/lib/http1.c b/lib/http1.c index 56cb2d195d..53f5926f4e 100644 --- a/lib/http1.c +++ b/lib/http1.c @@ -153,7 +153,7 @@ static void entity_read_send_error(struct st_h2o_http1_conn_t *conn, int status, set_timeout(conn, NULL, NULL); h2o_socket_read_stop(conn->sock); conn->req.http1_is_persistent = 0; - h2o_send_error(&conn->req, status, reason, body, H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION); + h2o_send_error_generic(&conn->req, status, reason, body, H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION); } static void on_entity_read_complete(struct st_h2o_http1_conn_t *conn) @@ -409,7 +409,7 @@ static void handle_incoming_request(struct st_h2o_http1_conn_t *conn) if (!h2o_lcstris(expect.base, expect.len, H2O_STRLIT("100-continue"))) { set_timeout(conn, NULL, NULL); h2o_socket_read_stop(conn->sock); - h2o_send_error(&conn->req, 417, "Expectation Failed", "unknown expectation", + h2o_send_error_417(&conn->req, "Expectation Failed", "unknown expectation", H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION); return; } diff --git a/lib/http2/connection.c b/lib/http2/connection.c index 9a3b86a592..35648e1616 100644 --- a/lib/http2/connection.c +++ b/lib/http2/connection.c @@ -276,12 +276,17 @@ void close_connection(h2o_http2_conn_t *conn) } } +static inline void h2o_context_report_http2_error(h2o_context_t *ctx, int errnum) +{ + ctx->http2.emitted_errors[errnum]++; +} + void send_stream_error(h2o_http2_conn_t *conn, uint32_t stream_id, int errnum) { assert(stream_id != 0); assert(conn->state < H2O_HTTP2_CONN_STATE_IS_CLOSING); - h2o_context_report_emitted_error(conn->super.ctx, errnum, 2); + h2o_context_report_http2_error(conn->super.ctx, -errnum); h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream_id, -errnum); h2o_http2_conn_request_write(conn); @@ -861,7 +866,7 @@ static void on_read(h2o_socket_t *sock, const char *err) h2o_http2_conn_t *conn = sock->data; if (err != NULL) { - h2o_context_report_emitted_error(conn->super.ctx, 0, 2); + h2o_context_report_http2_error(conn->super.ctx, E_HTTP2_OTHER); h2o_socket_read_stop(conn->sock); close_connection(conn); return; @@ -932,7 +937,7 @@ static void on_write_complete(h2o_socket_t *sock, const char *err) /* close by error if necessary */ if (err != NULL) { - h2o_context_report_emitted_error(conn->super.ctx, 0, 2); + h2o_context_report_http2_error(conn->super.ctx, E_HTTP2_OTHER); close_connection_now(conn); return; } From ebc247e46444a4c9849271232c39ff3afcd7fc26 Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Wed, 18 May 2016 10:45:37 -0700 Subject: [PATCH 4/8] - Add a specialized `h2o_send_error_deferred` define so that we can update the counters directly from that function, instead of relying on `h2o_context_report_http1_error` - Remove `h2o_context_report_http1_error` and `E_HTTP_{4,5,X}XX` enums: Every call side now uses a well known http return code. - Simplify the `status_list` logic by calling `h2o_contains_token`. We also use `,` as a separator between the modules, rather than the reserved `|` (update docs and the test). - Add a test case verifying the increment of the 404 counter --- include/h2o.h | 47 +++++++++---------- include/h2o/http2_internal.h | 17 ------- lib/core/context.c | 38 --------------- lib/core/request.c | 37 ++++++++------- lib/handler/status.c | 64 ++++--------------------- lib/handler/status/errors.c | 67 +++++++++++++-------------- lib/http2/connection.c | 6 +-- srcdoc/configure/status_directives.mt | 2 +- t/50status.t | 25 ++++++++-- 9 files changed, 107 insertions(+), 196 deletions(-) diff --git a/include/h2o.h b/include/h2o.h index f5340dfe79..7856f34bd8 100644 --- a/include/h2o.h +++ b/include/h2o.h @@ -417,24 +417,26 @@ typedef struct st_h2o_mimemap_type_t { } data; } h2o_mimemap_type_t; -/* errors emanating from h2o */ -enum { - /* http2 */ - E_HTTP2_PROTOCOL, - E_HTTP2_INTERNAL, - E_HTTP2_FLOW_CONTROL, - E_HTTP2_SETTINGS_TIMEOUT, - E_HTTP2_STREAM_CLOSED, - E_HTTP2_FRAME_SIZE, - E_HTTP2_REFUSED_STREAM, - E_HTTP2_CANCEL, - E_HTTP2_COMPRESSION, - E_HTTP2_CONNECT, - E_HTTP2_ENHANCE_YOUR_CALM, - E_HTTP2_INADEQUATE_SECURITY, - E_HTTP2_OTHER, - E_HTTP2_MAX, -}; +/* defined as negated form of the error codes defined in HTTP2-spec section 7 */ +#define H2O_HTTP2_ERROR_NONE 0 +#define H2O_HTTP2_ERROR_PROTOCOL -1 +#define H2O_HTTP2_ERROR_INTERNAL -2 +#define H2O_HTTP2_ERROR_FLOW_CONTROL -3 +#define H2O_HTTP2_ERROR_SETTINGS_TIMEOUT -4 +#define H2O_HTTP2_ERROR_STREAM_CLOSED -5 +#define H2O_HTTP2_ERROR_FRAME_SIZE -6 +#define H2O_HTTP2_ERROR_REFUSED_STREAM -7 +#define H2O_HTTP2_ERROR_CANCEL -8 +#define H2O_HTTP2_ERROR_COMPRESSION -9 +#define H2O_HTTP2_ERROR_CONNECT -10 +#define H2O_HTTP2_ERROR_ENHANCE_YOUR_CALM -11 +#define H2O_HTTP2_ERROR_INADEQUATE_SECURITY -12 +/* end of the HTT2-spec defined errors */ +#define H2O_HTTP2_ERROR_OTHER -13 +#define H2O_HTTP2_ERROR_MAX 14 +#define H2O_HTTP2_ERROR_INCOMPLETE -255 /* an internal value indicating that all data is not ready */ +#define H2O_HTTP2_ERROR_PROTOCOL_CLOSE_IMMEDIATELY -256 + enum { /* http1 */ E_HTTP_400 = 0, @@ -446,9 +448,6 @@ enum { E_HTTP_500, E_HTTP_502, E_HTTP_503, - E_HTTP_4XX, - E_HTTP_5XX, - E_HTTP_XXX, E_HTTP_MAX, }; @@ -523,7 +522,7 @@ struct st_h2o_context_t { /** * counter for http2 errors internally emitted by h2o */ - unsigned long emitted_errors[E_HTTP2_MAX]; + uint64_t emitted_errors[H2O_HTTP2_ERROR_MAX]; } http2; struct { @@ -1266,10 +1265,6 @@ static void h2o_context_set_filter_context(h2o_context_t *ctx, h2o_filter_t *fil * returns per-module context set by the on_context_init callback */ static void *h2o_context_get_logger_context(h2o_context_t *ctx, h2o_logger_t *logger); -/** - * internally reports an error emitted by h2o, so that this can in turn be reported in statuses - */ -void h2o_context_report_http1_error(h2o_context_t *ctx, int status); /* built-in generators */ enum { diff --git a/include/h2o/http2_internal.h b/include/h2o/http2_internal.h index c17502e716..85e0dec0bb 100644 --- a/include/h2o/http2_internal.h +++ b/include/h2o/http2_internal.h @@ -35,23 +35,6 @@ typedef struct st_h2o_http2_stream_t h2o_http2_stream_t; /* connection flow control window + alpha */ #define H2O_HTTP2_DEFAULT_OUTBUF_SIZE 81920 -/* defined as negated form of the error codes defined in HTTP2-spec section 7 */ -#define H2O_HTTP2_ERROR_NONE 0 -#define H2O_HTTP2_ERROR_PROTOCOL -1 -#define H2O_HTTP2_ERROR_INTERNAL -2 -#define H2O_HTTP2_ERROR_FLOW_CONTROL -3 -#define H2O_HTTP2_ERROR_SETTINGS_TIMEOUT -4 -#define H2O_HTTP2_ERROR_STREAM_CLOSED -5 -#define H2O_HTTP2_ERROR_FRAME_SIZE -6 -#define H2O_HTTP2_ERROR_REFUSED_STREAM -7 -#define H2O_HTTP2_ERROR_CANCEL -8 -#define H2O_HTTP2_ERROR_COMPRESSION -9 -#define H2O_HTTP2_ERROR_CONNECT -10 -#define H2O_HTTP2_ERROR_ENHANCE_YOUR_CALM -11 -#define H2O_HTTP2_ERROR_INADEQUATE_SECURITY -12 -#define H2O_HTTP2_ERROR_INCOMPLETE -255 /* an internal value indicating that all data is not ready */ -#define H2O_HTTP2_ERROR_PROTOCOL_CLOSE_IMMEDIATELY -256 - /* hpack */ #define H2O_HTTP2_ENCODE_INT_MAX_LENGTH 5 diff --git a/lib/core/context.c b/lib/core/context.c index b3afc48c1e..7fcc47370f 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -183,41 +183,3 @@ void h2o_context_update_timestamp_cache(h2o_context_t *ctx) h2o_time2str_log(ctx->_timestamp_cache.value->log, ctx->_timestamp_cache.tv_at.tv_sec); } } - -void h2o_context_report_http1_error(h2o_context_t *ctx, int status) -{ - int s100; - -#define INC_PREDEF_ERROR(x) \ - do { \ - if (status == x) { \ - ctx->emitted_errors[E_HTTP_ ## x]++; \ - return; \ - } \ - } while(0) - - INC_PREDEF_ERROR(400); - INC_PREDEF_ERROR(403); - INC_PREDEF_ERROR(404); - INC_PREDEF_ERROR(405); - INC_PREDEF_ERROR(416); - INC_PREDEF_ERROR(417); - INC_PREDEF_ERROR(500); - INC_PREDEF_ERROR(502); - INC_PREDEF_ERROR(503); - -#undef INC_PREDEF_ERROR - - s100 = status / 100; - switch(s100) { - case 4: - ctx->emitted_errors[E_HTTP_4XX]++; - return; - case 5: - ctx->emitted_errors[E_HTTP_5XX]++; - return; - default: - ctx->emitted_errors[E_HTTP_XXX]++; - return; - } -} diff --git a/lib/core/request.c b/lib/core/request.c index d80d37843d..86a9478fdd 100644 --- a/lib/core/request.c +++ b/lib/core/request.c @@ -478,21 +478,26 @@ void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, cons h2o_send_inline(req, body, SIZE_MAX); } -static void send_error_deferred_cb(h2o_timeout_entry_t *entry) -{ - struct st_send_error_deferred_t *args = H2O_STRUCT_FROM_MEMBER(struct st_send_error_deferred_t, _timeout, entry); - reset_response(args->req); - h2o_context_report_http1_error(args->req->conn->ctx, args->status); - h2o_send_error_generic(args->req, args->status, args->reason, args->body, args->flags); -} - -void h2o_send_error_deferred(h2o_req_t *req, int status, const char *reason, const char *body, int flags) -{ - struct st_send_error_deferred_t *args = h2o_mem_alloc_pool(&req->pool, sizeof(*args)); - *args = (struct st_send_error_deferred_t){req, status, reason, body, flags}; - args->_timeout.cb = send_error_deferred_cb; - h2o_timeout_link(req->conn->ctx->loop, &req->conn->ctx->zero_timeout, &args->_timeout); -} +#define SEND_ERROR_DEFERRED(status_) \ + static void send_error_deferred_cb_ ## status_ (h2o_timeout_entry_t *entry) \ + { \ + struct st_send_error_deferred_t *args = H2O_STRUCT_FROM_MEMBER(struct st_send_error_deferred_t, _timeout, entry); \ + reset_response(args->req); \ + args->req->conn->ctx->emitted_errors[E_HTTP_ ## status_]++; \ + h2o_send_error_generic(args->req, args->status, args->reason, args->body, args->flags); \ + } \ + \ + static void h2o_send_error_deferred_ ## status_ (h2o_req_t *req, const char *reason, const char *body, int flags) \ + { \ + struct st_send_error_deferred_t *args = h2o_mem_alloc_pool(&req->pool, sizeof(*args)); \ + *args = (struct st_send_error_deferred_t){req, status_, reason, body, flags}; \ + args->_timeout.cb = send_error_deferred_cb_ ## status_; \ + h2o_timeout_link(req->conn->ctx->loop, &req->conn->ctx->zero_timeout, &args->_timeout); \ + } + +SEND_ERROR_DEFERRED(502) + +#undef SEND_ERROR_DEFERRED void h2o_req_log_error(h2o_req_t *req, const char *module, const char *fmt, ...) { @@ -560,7 +565,7 @@ void h2o_send_redirect_internal(h2o_req_t *req, h2o_iovec_t method, const char * /* parse the location URL */ if (h2o_url_parse_relative(url_str, url_len, &url) != 0) { /* TODO log fprintf(stderr, "[proxy] cannot handle location header: %.*s\n", (int)url_len, url); */ - h2o_send_error_deferred(req, 502, "Gateway Error", "internal error", 0); + h2o_send_error_deferred_502(req, "Gateway Error", "internal error", 0); return; } /* convert the location to absolute (while creating copies of the values passed to the deferred call) */ diff --git a/lib/handler/status.c b/lib/handler/status.c index 2e25e8ccb2..bacd6c59b1 100644 --- a/lib/handler/status.c +++ b/lib/handler/status.c @@ -49,7 +49,6 @@ struct st_h2o_status_collector_t { } src; pthread_mutex_t mutex; size_t num_remaining_threads; - h2o_globalconf_t *gconf; H2O_VECTOR(struct status_ctx) status_ctx; }; @@ -65,9 +64,9 @@ static void collect_reqs_of_context(struct st_h2o_status_collector_t *collector, pthread_mutex_lock(&collector->mutex); - for (i = 0; i < collector->gconf->statuses.size; i++) { + for (i = 0; i < ctx->globalconf->statuses.size; i++) { h2o_status_handler_t *sh; - sh = &collector->gconf->statuses.entries[i]; + sh = &ctx->globalconf->statuses.entries[i]; if (!collector->status_ctx.entries[i].active) { continue; } @@ -173,12 +172,12 @@ static void on_req_close(void *p) h2o_mem_release_shared(collector); } -static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req, h2o_iovec_t *status_list) +static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req, h2o_iovec_t status_list) { { /* construct collector and send request to every thread */ struct st_h2o_status_context_t *status_ctx = h2o_context_get_handler_context(req->conn->ctx, &self->super); struct st_h2o_status_collector_t *collector = h2o_mem_alloc_shared(NULL, sizeof(*collector), on_collector_dispose); - size_t i, j; + size_t i; memset(collector, 0, sizeof(*collector)); for (i = 0; i < req->conn->ctx->globalconf->statuses.size; i++) { @@ -187,15 +186,8 @@ static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req h2o_vector_reserve(&req->pool, &collector->status_ctx, collector->status_ctx.size + 1); sh = &req->conn->ctx->globalconf->statuses.entries[i]; - if (status_list) { - int found = 0; - for (j = 0; status_list[j].base; j++) { - if (h2o_memis(status_list[j].base, status_list[j].len, sh->name.base, sh->name.len)) { - found = 1; - break; - } - } - if (!found) { + if (status_list.base) { + if (!h2o_contains_token(status_list.base, status_list.len, sh->name.base, sh->name.len, ',')) { collector->status_ctx.entries[collector->status_ctx.size].active = 0; goto Skip; } @@ -214,7 +206,6 @@ static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req Skip: collector->status_ctx.size++; } - collector->gconf = req->conn->ctx->globalconf; collector->src.req = req; collector->src.receiver = &status_ctx->receiver; pthread_mutex_init(&collector->mutex, NULL); @@ -234,44 +225,6 @@ static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req return 0; } -static h2o_iovec_t *build_status_list(const char *param, size_t len) -{ - int i, cur, start; - int nr_list = 1; - h2o_iovec_t *ret; - for (i = 0; i < len; i++) { - if (param[i] == '|') { - nr_list++; - } - } - ret = h2o_mem_alloc(sizeof(*ret) * (nr_list + 1)); - cur = 0; - start = 0; - for (i = 0; i < len; i++) { - if (param[i] == '|') { - ret[cur++] = h2o_strdup(NULL, ¶m[start], i - start); - start = i + 1; - } - } - if (start != i) { - ret[cur++] = h2o_strdup(NULL, ¶m[start], i - start); - } - ret[cur] = h2o_iovec_init(NULL, 0); - - return ret; -} - -static void free_status_list(h2o_iovec_t *status_list) -{ - int i; - if (!status_list) - return; - for (i = 0; status_list[i].base; i++) { - free(status_list[i].base); - } - free(status_list); -} - static int on_req(h2o_handler_t *_self, h2o_req_t *req) { struct st_h2o_root_status_handler_t *self = (void *)_self; @@ -290,14 +243,13 @@ static int on_req(h2o_handler_t *_self, h2o_req_t *req) } else if (h2o_memis(local_path.base, local_path.len, H2O_STRLIT("/json"))) { int ret; /* "/json" maps to the JSON API */ - h2o_iovec_t *status_list = NULL; /* NULL means all */ + h2o_iovec_t status_list = {NULL, 0}; /* NULL means we'll show all statuses */ if (req->query_at != SIZE_MAX && (req->path.len - req->query_at > 6)) { if (h2o_memis(&req->path.base[req->query_at], 6, "?show=", 6)) { - status_list = build_status_list(&req->path.base[req->query_at + 6], req->path.len - req->query_at - 6); + status_list = h2o_iovec_init(&req->path.base[req->query_at + 6], req->path.len - req->query_at - 6); } } ret = on_req_json(self, req, status_list); - free_status_list(status_list); return ret; } diff --git a/lib/handler/status/errors.c b/lib/handler/status/errors.c index a6f9b5c58d..daee7cf2a1 100644 --- a/lib/handler/status/errors.c +++ b/lib/handler/status/errors.c @@ -21,10 +21,11 @@ */ #include "h2o.h" +#include struct errors_status_ctx { - unsigned long long agg_errors_http1[E_HTTP_MAX]; - unsigned long long agg_errors_http2[E_HTTP2_MAX]; + uint64_t agg_errors_http1[E_HTTP_MAX]; + uint64_t agg_errors_http2[H2O_HTTP2_ERROR_MAX]; }; static void errors_status_per_thread(void *priv, h2o_context_t *ctx) @@ -57,44 +58,38 @@ static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_ #define BUFSIZE (2*1024) ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); ret.len = snprintf(ret.base, BUFSIZE, ",\n" - " \"http1-errors-400\": %llu,\n" - " \"http1-errors-403\": %llu,\n" - " \"http1-errors-404\": %llu,\n" - " \"http1-errors-405\": %llu,\n" - " \"http1-errors-416\": %llu,\n" - " \"http1-errors-417\": %llu,\n" - " \"http1-errors-500\": %llu,\n" - " \"http1-errors-502\": %llu,\n" - " \"http1-errors-503\": %llu,\n" - " \"http1-errors-4xx-others\": %llu,\n" - " \"http1-errors-5xx-others\": %llu,\n" - " \"http1-errors-others\": %llu,\n" - " \"http2-errors-protocol\": %llu, \n" - " \"http2-errors-internal\": %llu, \n" - " \"http2-errors-flow_control\": %llu, \n" - " \"http2-errors-settings_timeout\": %llu, \n" - " \"http2-errors-stream_closed\": %llu, \n" - " \"http2-errors-frame_size\": %llu, \n" - " \"http2-errors-refused_stream\": %llu, \n" - " \"http2-errors-cancel\": %llu, \n" - " \"http2-errors-compression\": %llu, \n" - " \"http2-errors-connect\": %llu, \n" - " \"http2-errors-enhance_your_calm\": %llu, \n" - " \"http2-errors-inadequate_security\": %llu, \n" - " \"http2-errors-other\": %llu", + " \"http1-errors-400\": %" PRIu64 ",\n" + " \"http1-errors-403\": %" PRIu64 ",\n" + " \"http1-errors-404\": %" PRIu64 ",\n" + " \"http1-errors-405\": %" PRIu64 ",\n" + " \"http1-errors-416\": %" PRIu64 ",\n" + " \"http1-errors-417\": %" PRIu64 ",\n" + " \"http1-errors-500\": %" PRIu64 ",\n" + " \"http1-errors-502\": %" PRIu64 ",\n" + " \"http1-errors-503\": %" PRIu64 ",\n" + " \"http2-errors-protocol\": %" PRIu64 ", \n" + " \"http2-errors-internal\": %" PRIu64 ", \n" + " \"http2-errors-flow_control\": %" PRIu64 ", \n" + " \"http2-errors-settings_timeout\": %" PRIu64 ", \n" + " \"http2-errors-stream_closed\": %" PRIu64 ", \n" + " \"http2-errors-frame_size\": %" PRIu64 ", \n" + " \"http2-errors-refused_stream\": %" PRIu64 ", \n" + " \"http2-errors-cancel\": %" PRIu64 ", \n" + " \"http2-errors-compression\": %" PRIu64 ", \n" + " \"http2-errors-connect\": %" PRIu64 ", \n" + " \"http2-errors-enhance_your_calm\": %" PRIu64 ", \n" + " \"http2-errors-inadequate_security\": %" PRIu64 "\n", esc->agg_errors_http1[E_HTTP_400], esc->agg_errors_http1[E_HTTP_403], esc->agg_errors_http1[E_HTTP_404], esc->agg_errors_http1[E_HTTP_405], esc->agg_errors_http1[E_HTTP_416], esc->agg_errors_http1[E_HTTP_417], esc->agg_errors_http1[E_HTTP_500], esc->agg_errors_http1[E_HTTP_502], - esc->agg_errors_http1[E_HTTP_503], esc->agg_errors_http1[E_HTTP_4XX], - esc->agg_errors_http1[E_HTTP_5XX], esc->agg_errors_http1[E_HTTP_XXX], - esc->agg_errors_http2[E_HTTP2_PROTOCOL], esc->agg_errors_http2[E_HTTP2_INTERNAL], - esc->agg_errors_http2[E_HTTP2_FLOW_CONTROL], esc->agg_errors_http2[E_HTTP2_SETTINGS_TIMEOUT], - esc->agg_errors_http2[E_HTTP2_STREAM_CLOSED], esc->agg_errors_http2[E_HTTP2_FRAME_SIZE], - esc->agg_errors_http2[E_HTTP2_REFUSED_STREAM], esc->agg_errors_http2[E_HTTP2_CANCEL], - esc->agg_errors_http2[E_HTTP2_COMPRESSION], esc->agg_errors_http2[E_HTTP2_CONNECT], - esc->agg_errors_http2[E_HTTP2_ENHANCE_YOUR_CALM], esc->agg_errors_http2[E_HTTP2_INADEQUATE_SECURITY], - esc->agg_errors_http2[E_HTTP2_OTHER]); + esc->agg_errors_http1[E_HTTP_503], + esc->agg_errors_http2[-H2O_HTTP2_ERROR_PROTOCOL], esc->agg_errors_http2[-H2O_HTTP2_ERROR_INTERNAL], + esc->agg_errors_http2[-H2O_HTTP2_ERROR_FLOW_CONTROL], esc->agg_errors_http2[-H2O_HTTP2_ERROR_SETTINGS_TIMEOUT], + esc->agg_errors_http2[-H2O_HTTP2_ERROR_STREAM_CLOSED], esc->agg_errors_http2[-H2O_HTTP2_ERROR_FRAME_SIZE], + esc->agg_errors_http2[-H2O_HTTP2_ERROR_REFUSED_STREAM], esc->agg_errors_http2[-H2O_HTTP2_ERROR_CANCEL], + esc->agg_errors_http2[-H2O_HTTP2_ERROR_COMPRESSION], esc->agg_errors_http2[-H2O_HTTP2_ERROR_CONNECT], + esc->agg_errors_http2[-H2O_HTTP2_ERROR_ENHANCE_YOUR_CALM], esc->agg_errors_http2[-H2O_HTTP2_ERROR_INADEQUATE_SECURITY]); free(esc); return ret; #undef BUFSIZE diff --git a/lib/http2/connection.c b/lib/http2/connection.c index 35648e1616..5d49a73c6d 100644 --- a/lib/http2/connection.c +++ b/lib/http2/connection.c @@ -278,7 +278,7 @@ void close_connection(h2o_http2_conn_t *conn) static inline void h2o_context_report_http2_error(h2o_context_t *ctx, int errnum) { - ctx->http2.emitted_errors[errnum]++; + ctx->http2.emitted_errors[-errnum]++; } void send_stream_error(h2o_http2_conn_t *conn, uint32_t stream_id, int errnum) @@ -866,7 +866,7 @@ static void on_read(h2o_socket_t *sock, const char *err) h2o_http2_conn_t *conn = sock->data; if (err != NULL) { - h2o_context_report_http2_error(conn->super.ctx, E_HTTP2_OTHER); + h2o_context_report_http2_error(conn->super.ctx, H2O_HTTP2_ERROR_OTHER); h2o_socket_read_stop(conn->sock); close_connection(conn); return; @@ -937,7 +937,7 @@ static void on_write_complete(h2o_socket_t *sock, const char *err) /* close by error if necessary */ if (err != NULL) { - h2o_context_report_http2_error(conn->super.ctx, E_HTTP2_OTHER); + h2o_context_report_http2_error(conn->super.ctx, H2O_HTTP2_ERROR_OTHER); close_connection_now(conn); return; } diff --git a/srcdoc/configure/status_directives.mt b/srcdoc/configure/status_directives.mt index 6e56691190..60ef0bcf07 100644 --- a/srcdoc/configure/status_directives.mt +++ b/srcdoc/configure/status_directives.mt @@ -20,7 +20,7 @@ EOT Access to the handler should be restricted, considering the fact that the status includes the details of in-flight HTTP requests. The example below uses Basic authentication.

The information returned by the /json handler can be filtered out using -the optional show=module1|module2 parameter.

+the optional show=module1,module2 parameter.

There are currently three modules defined:

    diff --git a/t/50status.t b/t/50status.t index 65ff190919..61bd0d35cd 100644 --- a/t/50status.t +++ b/t/50status.t @@ -25,7 +25,7 @@ EOT is scalar @requests, 1, "One request"; }; -subtest "json hander noreqs" => sub { +subtest "json hander without requests" => sub { my $server = spawn_h2o(<< "EOT"); hosts: default: @@ -36,12 +36,31 @@ hosts: status: ON EOT - my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/s/json?show=main|errors' 2>&1 > /dev/null`; + my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/s/json?show=main,errors' 2>&1 > /dev/null`; my $jresp = decode_json("$resp"); is $jresp->{'connections'}, 1, "One connection"; - is $jresp->{'requests'}, undef, "Zero request"; + is $jresp->{'requests'}, undef, "Requests not present"; is $jresp->{'http1-errors-404'}, 0, "Internal errors monitoring"; }; +subtest "json hander check 404 error counter" => sub { + my $server = spawn_h2o(<< "EOT"); +hosts: + default: + paths: + /: + file.dir: @{[ DOC_ROOT ]} + /s: + status: ON +EOT + my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/beeb98fcf148317be5fe5d763c658bc9ea9c087a' 2>&1 > /dev/null`; + my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/s/json?show=errors' 2>&1 > /dev/null`; + my $jresp = decode_json("$resp"); + is $jresp->{'connections'}, undef, "Connections not present"; + is $jresp->{'requests'}, undef, "Requests not present"; + is $jresp->{'http1-errors-404'}, 1, "Found the 404 error"; +}; + + done_testing(); From 07f385039f113db4851ffa560b883bc5f8b38250 Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Mon, 23 May 2016 11:20:55 -0700 Subject: [PATCH 5/8] stats: various small fixes - simplify `st_h2o_status_handler_t` error handler in `init` removing error handling that level, and deferring to `final`. - Remove H2O_HTTP2_ERROR_OTHER in favor of dedicated `read_closed` and `write_closed` errors. - rename http1 errors under H2O_STATUS_ERROR_XXX - replace the remaining callers to `h2o_send_error_generic` to use hardcoded errors - append `DECL_` to the names of function declaring macros for clarity --- include/h2o.h | 70 ++++++++++++++++++++--------------- lib/core/request.c | 8 ++-- lib/handler/status.c | 13 ++----- lib/handler/status/errors.c | 45 +++++++++++++--------- lib/handler/status/requests.c | 30 ++++++++++----- lib/http1.c | 31 +++++++++------- lib/http2/connection.c | 10 ++--- 7 files changed, 118 insertions(+), 89 deletions(-) diff --git a/include/h2o.h b/include/h2o.h index 7856f34bd8..221665014f 100644 --- a/include/h2o.h +++ b/include/h2o.h @@ -278,7 +278,7 @@ typedef struct st_h2o_protocol_callbacks_t { typedef h2o_iovec_t (*final_status_handler_cb)(void *ctx, h2o_globalconf_t *gconf, h2o_req_t *req); typedef struct st_h2o_status_handler_t { h2o_iovec_t name; - void *(*init)(h2o_iovec_t *error); /* optional call back, allocates a context that will be passed to per_thread() */ + void *(*init)(void); /* optional callback, allocates a context that will be passed to per_thread() */ void (*per_thread)(void *priv, h2o_context_t *ctx); /* optional callback, will be called for each thread */ h2o_iovec_t (*final)(void *ctx, h2o_globalconf_t *gconf, h2o_req_t *req); /* mandatory, will be passed the optional context */ } h2o_status_handler_t; @@ -431,24 +431,24 @@ typedef struct st_h2o_mimemap_type_t { #define H2O_HTTP2_ERROR_CONNECT -10 #define H2O_HTTP2_ERROR_ENHANCE_YOUR_CALM -11 #define H2O_HTTP2_ERROR_INADEQUATE_SECURITY -12 +#define H2O_HTTP2_ERROR_MAX 13 /* end of the HTT2-spec defined errors */ -#define H2O_HTTP2_ERROR_OTHER -13 -#define H2O_HTTP2_ERROR_MAX 14 #define H2O_HTTP2_ERROR_INCOMPLETE -255 /* an internal value indicating that all data is not ready */ #define H2O_HTTP2_ERROR_PROTOCOL_CLOSE_IMMEDIATELY -256 enum { - /* http1 */ - E_HTTP_400 = 0, - E_HTTP_403, - E_HTTP_404, - E_HTTP_405, - E_HTTP_416, - E_HTTP_417, - E_HTTP_500, - E_HTTP_502, - E_HTTP_503, - E_HTTP_MAX, + /* http1 protocol errors */ + H2O_STATUS_ERROR_400 = 0, + H2O_STATUS_ERROR_403, + H2O_STATUS_ERROR_404, + H2O_STATUS_ERROR_405, + H2O_STATUS_ERROR_413, + H2O_STATUS_ERROR_416, + H2O_STATUS_ERROR_417, + H2O_STATUS_ERROR_500, + H2O_STATUS_ERROR_502, + H2O_STATUS_ERROR_503, + H2O_STATUS_ERROR_MAX, }; /** @@ -519,10 +519,20 @@ struct st_h2o_context_t { * timeout entry used for graceful shutdown */ h2o_timeout_entry_t _graceful_shutdown_timeout; - /** - * counter for http2 errors internally emitted by h2o - */ - uint64_t emitted_errors[H2O_HTTP2_ERROR_MAX]; + struct { + /** + * counter for http2 errors internally emitted by h2o + */ + uint64_t protocol_level_errors[H2O_HTTP2_ERROR_MAX]; + /** + * premature close on read + */ + uint64_t read_closed; + /** + * premature close on write + */ + uint64_t write_closed; + } events; } http2; struct { @@ -550,7 +560,7 @@ struct st_h2o_context_t { /** * counter for http1 errors internally emitted by h2o */ - unsigned long emitted_errors[E_HTTP_MAX]; + uint64_t http1_status_errors[H2O_STATUS_ERROR_MAX]; H2O_VECTOR(h2o_pathconf_t *) _pathconfs_inited; }; @@ -1286,22 +1296,22 @@ void h2o_send_inline(h2o_req_t *req, const char *body, size_t len); * sends the given information as an error response to the client */ void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, const char *body, int flags); -#define H2O_SEND_ERROR_XXX(status) \ +#define DECL_H2O_SEND_ERROR_XXX(status) \ static inline void h2o_send_error_ ## status(h2o_req_t *req, const char *reason, const char *body, int flags) \ { \ - req->conn->ctx->emitted_errors[E_HTTP_ ## status]++; \ + req->conn->ctx->http1_status_errors[H2O_STATUS_ERROR_ ## status]++; \ h2o_send_error_generic(req, status, reason, body, flags); \ } -H2O_SEND_ERROR_XXX(400) -H2O_SEND_ERROR_XXX(403) -H2O_SEND_ERROR_XXX(404) -H2O_SEND_ERROR_XXX(405) -H2O_SEND_ERROR_XXX(416) -H2O_SEND_ERROR_XXX(417) -H2O_SEND_ERROR_XXX(500) -H2O_SEND_ERROR_XXX(502) -H2O_SEND_ERROR_XXX(503) +DECL_H2O_SEND_ERROR_XXX(400) +DECL_H2O_SEND_ERROR_XXX(403) +DECL_H2O_SEND_ERROR_XXX(404) +DECL_H2O_SEND_ERROR_XXX(405) +DECL_H2O_SEND_ERROR_XXX(416) +DECL_H2O_SEND_ERROR_XXX(417) +DECL_H2O_SEND_ERROR_XXX(500) +DECL_H2O_SEND_ERROR_XXX(502) +DECL_H2O_SEND_ERROR_XXX(503) /** * sends error response using zero timeout; can be called by output filters while processing the headers diff --git a/lib/core/request.c b/lib/core/request.c index 86a9478fdd..b86a38d136 100644 --- a/lib/core/request.c +++ b/lib/core/request.c @@ -478,12 +478,12 @@ void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, cons h2o_send_inline(req, body, SIZE_MAX); } -#define SEND_ERROR_DEFERRED(status_) \ +#define DECL_SEND_ERROR_DEFERRED(status_) \ static void send_error_deferred_cb_ ## status_ (h2o_timeout_entry_t *entry) \ { \ struct st_send_error_deferred_t *args = H2O_STRUCT_FROM_MEMBER(struct st_send_error_deferred_t, _timeout, entry); \ reset_response(args->req); \ - args->req->conn->ctx->emitted_errors[E_HTTP_ ## status_]++; \ + args->req->conn->ctx->http1_status_errors[H2O_STATUS_ERROR_ ## status_]++; \ h2o_send_error_generic(args->req, args->status, args->reason, args->body, args->flags); \ } \ \ @@ -495,9 +495,9 @@ void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, cons h2o_timeout_link(req->conn->ctx->loop, &req->conn->ctx->zero_timeout, &args->_timeout); \ } -SEND_ERROR_DEFERRED(502) +DECL_SEND_ERROR_DEFERRED(502) -#undef SEND_ERROR_DEFERRED +#undef DECL_SEND_ERROR_DEFERRED void h2o_req_log_error(h2o_req_t *req, const char *module, const char *fmt, ...) { diff --git a/lib/handler/status.c b/lib/handler/status.c index bacd6c59b1..b40d2bb5f9 100644 --- a/lib/handler/status.c +++ b/lib/handler/status.c @@ -38,7 +38,7 @@ struct st_h2o_status_context_t { h2o_multithread_receiver_t receiver; }; -struct status_ctx { +struct st_status_ctx_t { int active; void *ctx; }; @@ -49,7 +49,7 @@ struct st_h2o_status_collector_t { } src; pthread_mutex_t mutex; size_t num_remaining_threads; - H2O_VECTOR(struct status_ctx) status_ctx; + H2O_VECTOR(struct st_status_ctx_t) status_ctx; }; struct st_h2o_status_message_t { @@ -193,14 +193,7 @@ static int on_req_json(struct st_h2o_root_status_handler_t *self, h2o_req_t *req } } if (sh->init) { - h2o_iovec_t err = {NULL, 0}; - void *p; - p = sh->init(&err); - if (!p) { - h2o_send_error_400(req, "Invalid Request", err.base, 0); - return 0; - } - collector->status_ctx.entries[collector->status_ctx.size].ctx = p; + collector->status_ctx.entries[collector->status_ctx.size].ctx = sh->init(); } collector->status_ctx.entries[collector->status_ctx.size].active = 1; Skip: diff --git a/lib/handler/status/errors.c b/lib/handler/status/errors.c index daee7cf2a1..e840b253a2 100644 --- a/lib/handler/status/errors.c +++ b/lib/handler/status/errors.c @@ -24,23 +24,27 @@ #include struct errors_status_ctx { - uint64_t agg_errors_http1[E_HTTP_MAX]; + uint64_t agg_errors_http1[H2O_STATUS_ERROR_MAX]; uint64_t agg_errors_http2[H2O_HTTP2_ERROR_MAX]; + uint64_t h2_read_closed; + uint64_t h2_write_closed; }; static void errors_status_per_thread(void *priv, h2o_context_t *ctx) { size_t i; struct errors_status_ctx *esc = priv; - for (i = 0; i < E_HTTP_MAX; i++) { - esc->agg_errors_http1[i] += ctx->emitted_errors[i]; + for (i = 0; i < H2O_STATUS_ERROR_MAX; i++) { + esc->agg_errors_http1[i] += ctx->http1_status_errors[i]; } - for (i = 0; i < E_HTTP_MAX; i++) { - esc->agg_errors_http2[i] += ctx->http2.emitted_errors[i]; + for (i = 0; i < H2O_HTTP2_ERROR_MAX; i++) { + esc->agg_errors_http2[i] += ctx->http2.events.protocol_level_errors[i]; } + esc->h2_read_closed += ctx->http2.events.read_closed; + esc->h2_write_closed += ctx->http2.events.write_closed; } -static void *errors_status_init(h2o_iovec_t *err) +static void *errors_status_init(void) { struct errors_status_ctx *ret; @@ -55,6 +59,10 @@ static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_ struct errors_status_ctx *esc = priv; h2o_iovec_t ret; +#define H1_AGG_ERR(status_) \ + esc->agg_errors_http1[H2O_STATUS_ERROR_ ## status_] +#define H2_AGG_ERR(err_) \ + esc->agg_errors_http2[-H2O_HTTP2_ERROR_ ## err_] #define BUFSIZE (2*1024) ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); ret.len = snprintf(ret.base, BUFSIZE, ",\n" @@ -78,21 +86,22 @@ static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_ " \"http2-errors-compression\": %" PRIu64 ", \n" " \"http2-errors-connect\": %" PRIu64 ", \n" " \"http2-errors-enhance_your_calm\": %" PRIu64 ", \n" - " \"http2-errors-inadequate_security\": %" PRIu64 "\n", - esc->agg_errors_http1[E_HTTP_400], esc->agg_errors_http1[E_HTTP_403], - esc->agg_errors_http1[E_HTTP_404], esc->agg_errors_http1[E_HTTP_405], - esc->agg_errors_http1[E_HTTP_416], esc->agg_errors_http1[E_HTTP_417], - esc->agg_errors_http1[E_HTTP_500], esc->agg_errors_http1[E_HTTP_502], - esc->agg_errors_http1[E_HTTP_503], - esc->agg_errors_http2[-H2O_HTTP2_ERROR_PROTOCOL], esc->agg_errors_http2[-H2O_HTTP2_ERROR_INTERNAL], - esc->agg_errors_http2[-H2O_HTTP2_ERROR_FLOW_CONTROL], esc->agg_errors_http2[-H2O_HTTP2_ERROR_SETTINGS_TIMEOUT], - esc->agg_errors_http2[-H2O_HTTP2_ERROR_STREAM_CLOSED], esc->agg_errors_http2[-H2O_HTTP2_ERROR_FRAME_SIZE], - esc->agg_errors_http2[-H2O_HTTP2_ERROR_REFUSED_STREAM], esc->agg_errors_http2[-H2O_HTTP2_ERROR_CANCEL], - esc->agg_errors_http2[-H2O_HTTP2_ERROR_COMPRESSION], esc->agg_errors_http2[-H2O_HTTP2_ERROR_CONNECT], - esc->agg_errors_http2[-H2O_HTTP2_ERROR_ENHANCE_YOUR_CALM], esc->agg_errors_http2[-H2O_HTTP2_ERROR_INADEQUATE_SECURITY]); + " \"http2-errors-inadequate_security\": %" PRIu64 ", \n" + " \"http2-read-closed\": %" PRIu64 ", \n" + " \"http2-write-closed\": %" PRIu64 "\n", + H1_AGG_ERR(400), H1_AGG_ERR(403), H1_AGG_ERR(404), + H1_AGG_ERR(405), H1_AGG_ERR(416), H1_AGG_ERR(417), + H1_AGG_ERR(500), H1_AGG_ERR(502), H1_AGG_ERR(503), + H2_AGG_ERR(PROTOCOL), H2_AGG_ERR(INTERNAL), H2_AGG_ERR(FLOW_CONTROL), + H2_AGG_ERR(SETTINGS_TIMEOUT), H2_AGG_ERR(STREAM_CLOSED), H2_AGG_ERR(FRAME_SIZE), + H2_AGG_ERR(REFUSED_STREAM), H2_AGG_ERR(CANCEL), H2_AGG_ERR(COMPRESSION), + H2_AGG_ERR(CONNECT), H2_AGG_ERR(ENHANCE_YOUR_CALM), H2_AGG_ERR(INADEQUATE_SECURITY), + esc->h2_read_closed, esc->h2_write_closed); free(esc); return ret; #undef BUFSIZE +#undef H1_AGG_ERR +#undef H2_AGG_ERR } h2o_status_handler_t errors_status_handler = { diff --git a/lib/handler/status/requests.c b/lib/handler/status/requests.c index c67e89b1b5..439d2da093 100644 --- a/lib/handler/status/requests.c +++ b/lib/handler/status/requests.c @@ -22,9 +22,10 @@ #include "h2o.h" -struct requests_status_ctx { +struct st_requests_status_ctx_t { h2o_logconf_t *logconf; h2o_iovec_t req_data; + h2o_iovec_t init_error; int first; /* used to know if a thread is the first one, we skip the leading * coma in that case. */ }; @@ -32,7 +33,7 @@ struct requests_status_ctx { struct st_collect_req_status_cbdata_t { h2o_logconf_t *logconf; h2o_buffer_t *buffer; - int first; /* see requests_status_ctx */ + int first; /* see st_requests_status_ctx_t */ }; static int collect_req_status(h2o_req_t *req, void *_cbdata) @@ -67,9 +68,14 @@ static int collect_req_status(h2o_req_t *req, void *_cbdata) static void requests_status_per_thread(void *priv, h2o_context_t *ctx) { - struct requests_status_ctx *rsc = priv; + struct st_requests_status_ctx_t *rsc = priv; struct st_collect_req_status_cbdata_t cbdata = {rsc->logconf}; + /* we encountered an error at init() time, return early */ + if (rsc->init_error.base) { + return; + } + cbdata.first = rsc->first; if (rsc->first) { rsc->first = 0; @@ -87,10 +93,11 @@ static void requests_status_per_thread(void *priv, h2o_context_t *ctx) h2o_buffer_dispose(&cbdata.buffer); } -static void *requests_status_init(h2o_iovec_t *error) +static void *requests_status_init(void) { - struct requests_status_ctx *rsc; + struct st_requests_status_ctx_t *rsc; rsc = h2o_mem_alloc(sizeof(*rsc)); + memset(rsc, 0, sizeof(*rsc)); rsc->first = 1; #define ELEMENT(key, expr) "\"" key "\": \"" expr "\"" @@ -128,9 +135,8 @@ static void *requests_status_init(h2o_iovec_t *error) if ((rsc->logconf = h2o_logconf_compile(fmt, H2O_LOGCONF_ESCAPE_JSON, errbuf)) == NULL) { h2o_iovec_t resp = h2o_concat(NULL, h2o_iovec_init(H2O_STRLIT("failed to compile log format:")), h2o_iovec_init(errbuf, strlen(errbuf))); - *error = resp; - free(rsc); - return NULL; + rsc->init_error = resp; + return rsc; } } @@ -141,8 +147,13 @@ static void *requests_status_init(h2o_iovec_t *error) static h2o_iovec_t requests_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) { h2o_iovec_t ret; - struct requests_status_ctx *rsc = priv; + struct st_requests_status_ctx_t *rsc = priv; + if (rsc->init_error.base) { + ret = h2o_strdup(&req->pool, rsc->init_error.base, rsc->init_error.len); + free(rsc->init_error.base); + goto out; + } #define JSON_FOOTER "\n ]" #define JSON_FOOTER_LEN 3 ret.base = h2o_mem_alloc_pool(&req->pool, rsc->req_data.len + JSON_FOOTER_LEN); @@ -154,6 +165,7 @@ static h2o_iovec_t requests_status_final(void *priv, h2o_globalconf_t *gconf, h2 h2o_logconf_dispose(rsc->logconf); free(rsc->req_data.base); +out: free(rsc); return ret; } diff --git a/lib/http1.c b/lib/http1.c index 53f5926f4e..d797e0978c 100644 --- a/lib/http1.c +++ b/lib/http1.c @@ -147,14 +147,19 @@ static void process_request(struct st_h2o_http1_conn_t *conn) h2o_process_request(&conn->req); } -static void entity_read_send_error(struct st_h2o_http1_conn_t *conn, int status, const char *reason, const char *body) -{ - conn->_req_entity_reader = NULL; - set_timeout(conn, NULL, NULL); - h2o_socket_read_stop(conn->sock); - conn->req.http1_is_persistent = 0; - h2o_send_error_generic(&conn->req, status, reason, body, H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION); -} +#define DECL_ENTITY_READ_SEND_ERROR_XXX(status_) \ + static void entity_read_send_error_ ## status_(struct st_h2o_http1_conn_t *conn, const char *reason, const char *body) \ + { \ + conn->_req_entity_reader = NULL; \ + set_timeout(conn, NULL, NULL); \ + h2o_socket_read_stop(conn->sock); \ + conn->req.http1_is_persistent = 0; \ + conn->super.ctx->http1_status_errors[H2O_STATUS_ERROR_ ## status_]++; \ + h2o_send_error_generic(&conn->req, status_, reason, body, H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION); \ + } + +DECL_ENTITY_READ_SEND_ERROR_XXX(400) +DECL_ENTITY_READ_SEND_ERROR_XXX(413) static void on_entity_read_complete(struct st_h2o_http1_conn_t *conn) { @@ -178,7 +183,7 @@ static void handle_chunked_entity_read(struct st_h2o_http1_conn_t *conn) inbuf->size = reader->prev_input_size + bufsz; reader->prev_input_size = inbuf->size; if (ret != -1 && inbuf->size - conn->_reqsize >= conn->super.ctx->globalconf->max_request_entity_size) { - entity_read_send_error(conn, 413, "Request Entity Too Large", "request entity is too large"); + entity_read_send_error_413(conn, "Request Entity Too Large", "request entity is too large"); return; } if (ret < 0) { @@ -187,7 +192,7 @@ static void handle_chunked_entity_read(struct st_h2o_http1_conn_t *conn) return; } /* error */ - entity_read_send_error(conn, 400, "Invalid Request", "broken chunked-encoding"); + entity_read_send_error_400(conn, "Invalid Request", "broken chunked-encoding"); return; } /* complete */ @@ -242,7 +247,7 @@ static int create_entity_reader(struct st_h2o_http1_conn_t *conn, const struct p if (entity_header->name_len == sizeof("transfer-encoding") - 1) { /* transfer-encoding */ if (!h2o_lcstris(entity_header->value, entity_header->value_len, H2O_STRLIT("chunked"))) { - entity_read_send_error(conn, 400, "Invalid Request", "unknown transfer-encoding"); + entity_read_send_error_400(conn, "Invalid Request", "unknown transfer-encoding"); return -1; } return create_chunked_entity_reader(conn); @@ -250,11 +255,11 @@ static int create_entity_reader(struct st_h2o_http1_conn_t *conn, const struct p /* content-length */ size_t content_length = h2o_strtosize(entity_header->value, entity_header->value_len); if (content_length == SIZE_MAX) { - entity_read_send_error(conn, 400, "Invalid Request", "broken content-length header"); + entity_read_send_error_400(conn, "Invalid Request", "broken content-length header"); return -1; } if (content_length > conn->super.ctx->globalconf->max_request_entity_size) { - entity_read_send_error(conn, 413, "Request Entity Too Large", "request entity is too large"); + entity_read_send_error_413(conn, "Request Entity Too Large", "request entity is too large"); return -1; } return create_content_length_entity_reader(conn, (size_t)content_length); diff --git a/lib/http2/connection.c b/lib/http2/connection.c index 5d49a73c6d..fe2bb6df41 100644 --- a/lib/http2/connection.c +++ b/lib/http2/connection.c @@ -276,9 +276,9 @@ void close_connection(h2o_http2_conn_t *conn) } } -static inline void h2o_context_report_http2_error(h2o_context_t *ctx, int errnum) +static inline void h2o_context_http2_protocol_error_count(h2o_context_t *ctx, int errnum) { - ctx->http2.emitted_errors[-errnum]++; + ctx->http2.events.protocol_level_errors[-errnum]++; } void send_stream_error(h2o_http2_conn_t *conn, uint32_t stream_id, int errnum) @@ -286,7 +286,7 @@ void send_stream_error(h2o_http2_conn_t *conn, uint32_t stream_id, int errnum) assert(stream_id != 0); assert(conn->state < H2O_HTTP2_CONN_STATE_IS_CLOSING); - h2o_context_report_http2_error(conn->super.ctx, -errnum); + h2o_context_http2_protocol_error_count(conn->super.ctx, -errnum); h2o_http2_encode_rst_stream_frame(&conn->_write.buf, stream_id, -errnum); h2o_http2_conn_request_write(conn); @@ -866,7 +866,7 @@ static void on_read(h2o_socket_t *sock, const char *err) h2o_http2_conn_t *conn = sock->data; if (err != NULL) { - h2o_context_report_http2_error(conn->super.ctx, H2O_HTTP2_ERROR_OTHER); + conn->super.ctx->http2.events.read_closed++; h2o_socket_read_stop(conn->sock); close_connection(conn); return; @@ -937,7 +937,7 @@ static void on_write_complete(h2o_socket_t *sock, const char *err) /* close by error if necessary */ if (err != NULL) { - h2o_context_report_http2_error(conn->super.ctx, H2O_HTTP2_ERROR_OTHER); + conn->super.ctx->http2.events.write_closed++; close_connection_now(conn); return; } From a24ec3d13a6006ccffa8802fec3c22f0459d965f Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Wed, 25 May 2016 10:12:02 -0700 Subject: [PATCH 6/8] Various renames s/http1_status_errors/emitted_error_status/ s/DECL_H2O_SEND_ERROR_XXX/H2O_SEND_ERROR_XXX/ s/errors_status_ctx/st_errors_status_ctx_t/ rename aggregated errors to something that matches the data being aggregated --- include/h2o.h | 26 +++++++++++++------------- lib/core/request.c | 2 +- lib/handler/status/errors.c | 20 ++++++++++---------- lib/http1.c | 2 +- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/h2o.h b/include/h2o.h index 221665014f..77c186f38d 100644 --- a/include/h2o.h +++ b/include/h2o.h @@ -558,9 +558,9 @@ struct st_h2o_context_t { } _timestamp_cache; /** - * counter for http1 errors internally emitted by h2o + * counter for http1 error status internally emitted by h2o */ - uint64_t http1_status_errors[H2O_STATUS_ERROR_MAX]; + uint64_t emitted_error_status[H2O_STATUS_ERROR_MAX]; H2O_VECTOR(h2o_pathconf_t *) _pathconfs_inited; }; @@ -1296,22 +1296,22 @@ void h2o_send_inline(h2o_req_t *req, const char *body, size_t len); * sends the given information as an error response to the client */ void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, const char *body, int flags); -#define DECL_H2O_SEND_ERROR_XXX(status) \ +#define H2O_SEND_ERROR_XXX(status) \ static inline void h2o_send_error_ ## status(h2o_req_t *req, const char *reason, const char *body, int flags) \ { \ - req->conn->ctx->http1_status_errors[H2O_STATUS_ERROR_ ## status]++; \ + req->conn->ctx->emitted_error_status[H2O_STATUS_ERROR_ ## status]++; \ h2o_send_error_generic(req, status, reason, body, flags); \ } -DECL_H2O_SEND_ERROR_XXX(400) -DECL_H2O_SEND_ERROR_XXX(403) -DECL_H2O_SEND_ERROR_XXX(404) -DECL_H2O_SEND_ERROR_XXX(405) -DECL_H2O_SEND_ERROR_XXX(416) -DECL_H2O_SEND_ERROR_XXX(417) -DECL_H2O_SEND_ERROR_XXX(500) -DECL_H2O_SEND_ERROR_XXX(502) -DECL_H2O_SEND_ERROR_XXX(503) +H2O_SEND_ERROR_XXX(400) +H2O_SEND_ERROR_XXX(403) +H2O_SEND_ERROR_XXX(404) +H2O_SEND_ERROR_XXX(405) +H2O_SEND_ERROR_XXX(416) +H2O_SEND_ERROR_XXX(417) +H2O_SEND_ERROR_XXX(500) +H2O_SEND_ERROR_XXX(502) +H2O_SEND_ERROR_XXX(503) /** * sends error response using zero timeout; can be called by output filters while processing the headers diff --git a/lib/core/request.c b/lib/core/request.c index b86a38d136..fcdf759b34 100644 --- a/lib/core/request.c +++ b/lib/core/request.c @@ -483,7 +483,7 @@ void h2o_send_error_generic(h2o_req_t *req, int status, const char *reason, cons { \ struct st_send_error_deferred_t *args = H2O_STRUCT_FROM_MEMBER(struct st_send_error_deferred_t, _timeout, entry); \ reset_response(args->req); \ - args->req->conn->ctx->http1_status_errors[H2O_STATUS_ERROR_ ## status_]++; \ + args->req->conn->ctx->emitted_error_status[H2O_STATUS_ERROR_ ## status_]++; \ h2o_send_error_generic(args->req, args->status, args->reason, args->body, args->flags); \ } \ \ diff --git a/lib/handler/status/errors.c b/lib/handler/status/errors.c index e840b253a2..ecbf02f7ed 100644 --- a/lib/handler/status/errors.c +++ b/lib/handler/status/errors.c @@ -23,9 +23,9 @@ #include "h2o.h" #include -struct errors_status_ctx { - uint64_t agg_errors_http1[H2O_STATUS_ERROR_MAX]; - uint64_t agg_errors_http2[H2O_HTTP2_ERROR_MAX]; +struct st_errors_status_ctx_t { + uint64_t emitted_status_errors[H2O_STATUS_ERROR_MAX]; + uint64_t h2_protocol_level_errors[H2O_HTTP2_ERROR_MAX]; uint64_t h2_read_closed; uint64_t h2_write_closed; }; @@ -33,12 +33,12 @@ struct errors_status_ctx { static void errors_status_per_thread(void *priv, h2o_context_t *ctx) { size_t i; - struct errors_status_ctx *esc = priv; + struct st_errors_status_ctx_t *esc = priv; for (i = 0; i < H2O_STATUS_ERROR_MAX; i++) { - esc->agg_errors_http1[i] += ctx->http1_status_errors[i]; + esc->emitted_status_errors[i] += ctx->emitted_error_status[i]; } for (i = 0; i < H2O_HTTP2_ERROR_MAX; i++) { - esc->agg_errors_http2[i] += ctx->http2.events.protocol_level_errors[i]; + esc->h2_protocol_level_errors[i] += ctx->http2.events.protocol_level_errors[i]; } esc->h2_read_closed += ctx->http2.events.read_closed; esc->h2_write_closed += ctx->http2.events.write_closed; @@ -46,7 +46,7 @@ static void errors_status_per_thread(void *priv, h2o_context_t *ctx) static void *errors_status_init(void) { - struct errors_status_ctx *ret; + struct st_errors_status_ctx_t *ret; ret = h2o_mem_alloc(sizeof(*ret)); memset(ret, 0, sizeof(*ret)); @@ -56,13 +56,13 @@ static void *errors_status_init(void) static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) { - struct errors_status_ctx *esc = priv; + struct st_errors_status_ctx_t *esc = priv; h2o_iovec_t ret; #define H1_AGG_ERR(status_) \ - esc->agg_errors_http1[H2O_STATUS_ERROR_ ## status_] + esc->emitted_status_errors[H2O_STATUS_ERROR_ ## status_] #define H2_AGG_ERR(err_) \ - esc->agg_errors_http2[-H2O_HTTP2_ERROR_ ## err_] + esc->h2_protocol_level_errors[-H2O_HTTP2_ERROR_ ## err_] #define BUFSIZE (2*1024) ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); ret.len = snprintf(ret.base, BUFSIZE, ",\n" diff --git a/lib/http1.c b/lib/http1.c index d797e0978c..8373699883 100644 --- a/lib/http1.c +++ b/lib/http1.c @@ -154,7 +154,7 @@ static void process_request(struct st_h2o_http1_conn_t *conn) set_timeout(conn, NULL, NULL); \ h2o_socket_read_stop(conn->sock); \ conn->req.http1_is_persistent = 0; \ - conn->super.ctx->http1_status_errors[H2O_STATUS_ERROR_ ## status_]++; \ + conn->super.ctx->emitted_error_status[H2O_STATUS_ERROR_ ## status_]++; \ h2o_send_error_generic(&conn->req, status_, reason, body, H2O_SEND_ERROR_HTTP1_CLOSE_CONNECTION); \ } From bd158bc124672c6c85f25b5382b58ba8d52df4a9 Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Fri, 27 May 2016 09:04:30 -0700 Subject: [PATCH 7/8] - Rename 'errors' to 'events', since we're really tracking events here - Modify the json key naming: s/_/-/ and add a dot to denote categories --- CMakeLists.txt | 2 +- lib/handler/status.c | 4 +- lib/handler/status/{errors.c => events.c} | 70 +++++++++++------------ t/50status.t | 8 +-- 4 files changed, 42 insertions(+), 42 deletions(-) rename lib/handler/status/{errors.c => events.c} (61%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e0cf97d49..adc7b3c9e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,7 +239,7 @@ SET(LIB_SOURCE_FILES lib/handler/redirect.c lib/handler/reproxy.c lib/handler/status.c - lib/handler/status/errors.c + lib/handler/status/events.c lib/handler/status/requests.c lib/handler/configurator/access_log.c lib/handler/configurator/compress.c diff --git a/lib/handler/status.c b/lib/handler/status.c index b40d2bb5f9..a873fbcbf2 100644 --- a/lib/handler/status.c +++ b/lib/handler/status.c @@ -21,7 +21,7 @@ */ #include "h2o.h" -extern h2o_status_handler_t errors_status_handler; +extern h2o_status_handler_t events_status_handler; extern h2o_status_handler_t requests_status_handler; struct st_h2o_status_logger_t { @@ -288,5 +288,5 @@ void h2o_status_register(h2o_pathconf_t *conf) self->super.on_context_dispose = on_context_dispose; self->super.on_req = on_req; h2o_config_register_status_handler(conf->global, requests_status_handler); - h2o_config_register_status_handler(conf->global, errors_status_handler); + h2o_config_register_status_handler(conf->global, events_status_handler); } diff --git a/lib/handler/status/errors.c b/lib/handler/status/events.c similarity index 61% rename from lib/handler/status/errors.c rename to lib/handler/status/events.c index ecbf02f7ed..3487e45ad9 100644 --- a/lib/handler/status/errors.c +++ b/lib/handler/status/events.c @@ -23,17 +23,17 @@ #include "h2o.h" #include -struct st_errors_status_ctx_t { +struct st_events_status_ctx_t { uint64_t emitted_status_errors[H2O_STATUS_ERROR_MAX]; uint64_t h2_protocol_level_errors[H2O_HTTP2_ERROR_MAX]; uint64_t h2_read_closed; uint64_t h2_write_closed; }; -static void errors_status_per_thread(void *priv, h2o_context_t *ctx) +static void events_status_per_thread(void *priv, h2o_context_t *ctx) { size_t i; - struct st_errors_status_ctx_t *esc = priv; + struct st_events_status_ctx_t *esc = priv; for (i = 0; i < H2O_STATUS_ERROR_MAX; i++) { esc->emitted_status_errors[i] += ctx->emitted_error_status[i]; } @@ -44,9 +44,9 @@ static void errors_status_per_thread(void *priv, h2o_context_t *ctx) esc->h2_write_closed += ctx->http2.events.write_closed; } -static void *errors_status_init(void) +static void *events_status_init(void) { - struct st_errors_status_ctx_t *ret; + struct st_events_status_ctx_t *ret; ret = h2o_mem_alloc(sizeof(*ret)); memset(ret, 0, sizeof(*ret)); @@ -54,9 +54,9 @@ static void *errors_status_init(void) return ret; } -static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) +static h2o_iovec_t events_status_final(void *priv, h2o_globalconf_t *gconf, h2o_req_t *req) { - struct st_errors_status_ctx_t *esc = priv; + struct st_events_status_ctx_t *esc = priv; h2o_iovec_t ret; #define H1_AGG_ERR(status_) \ @@ -66,29 +66,29 @@ static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_ #define BUFSIZE (2*1024) ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); ret.len = snprintf(ret.base, BUFSIZE, ",\n" - " \"http1-errors-400\": %" PRIu64 ",\n" - " \"http1-errors-403\": %" PRIu64 ",\n" - " \"http1-errors-404\": %" PRIu64 ",\n" - " \"http1-errors-405\": %" PRIu64 ",\n" - " \"http1-errors-416\": %" PRIu64 ",\n" - " \"http1-errors-417\": %" PRIu64 ",\n" - " \"http1-errors-500\": %" PRIu64 ",\n" - " \"http1-errors-502\": %" PRIu64 ",\n" - " \"http1-errors-503\": %" PRIu64 ",\n" - " \"http2-errors-protocol\": %" PRIu64 ", \n" - " \"http2-errors-internal\": %" PRIu64 ", \n" - " \"http2-errors-flow_control\": %" PRIu64 ", \n" - " \"http2-errors-settings_timeout\": %" PRIu64 ", \n" - " \"http2-errors-stream_closed\": %" PRIu64 ", \n" - " \"http2-errors-frame_size\": %" PRIu64 ", \n" - " \"http2-errors-refused_stream\": %" PRIu64 ", \n" - " \"http2-errors-cancel\": %" PRIu64 ", \n" - " \"http2-errors-compression\": %" PRIu64 ", \n" - " \"http2-errors-connect\": %" PRIu64 ", \n" - " \"http2-errors-enhance_your_calm\": %" PRIu64 ", \n" - " \"http2-errors-inadequate_security\": %" PRIu64 ", \n" - " \"http2-read-closed\": %" PRIu64 ", \n" - " \"http2-write-closed\": %" PRIu64 "\n", + " \"http1-errors.400\": %" PRIu64 ",\n" + " \"http1-errors.403\": %" PRIu64 ",\n" + " \"http1-errors.404\": %" PRIu64 ",\n" + " \"http1-errors.405\": %" PRIu64 ",\n" + " \"http1-errors.416\": %" PRIu64 ",\n" + " \"http1-errors.417\": %" PRIu64 ",\n" + " \"http1-errors.500\": %" PRIu64 ",\n" + " \"http1-errors.502\": %" PRIu64 ",\n" + " \"http1-errors.503\": %" PRIu64 ",\n" + " \"http2-errors.protocol\": %" PRIu64 ", \n" + " \"http2-errors.internal\": %" PRIu64 ", \n" + " \"http2-errors.flow-control\": %" PRIu64 ", \n" + " \"http2-errors.settings-timeout\": %" PRIu64 ", \n" + " \"http2-errors.stream-closed\": %" PRIu64 ", \n" + " \"http2-errors.frame-size\": %" PRIu64 ", \n" + " \"http2-errors.refused-stream\": %" PRIu64 ", \n" + " \"http2-errors.cancel\": %" PRIu64 ", \n" + " \"http2-errors.compression\": %" PRIu64 ", \n" + " \"http2-errors.connect\": %" PRIu64 ", \n" + " \"http2-errors.enhance-your-calm\": %" PRIu64 ", \n" + " \"http2-errors.inadequate-security\": %" PRIu64 ", \n" + " \"http2.read-closed\": %" PRIu64 ", \n" + " \"http2.write-closed\": %" PRIu64 "\n", H1_AGG_ERR(400), H1_AGG_ERR(403), H1_AGG_ERR(404), H1_AGG_ERR(405), H1_AGG_ERR(416), H1_AGG_ERR(417), H1_AGG_ERR(500), H1_AGG_ERR(502), H1_AGG_ERR(503), @@ -104,9 +104,9 @@ static h2o_iovec_t errors_status_final(void *priv, h2o_globalconf_t *gconf, h2o_ #undef H2_AGG_ERR } -h2o_status_handler_t errors_status_handler = { - { H2O_STRLIT("errors") }, - errors_status_init, - errors_status_per_thread, - errors_status_final, +h2o_status_handler_t events_status_handler = { + { H2O_STRLIT("events") }, + events_status_init, + events_status_per_thread, + events_status_final, }; diff --git a/t/50status.t b/t/50status.t index 61bd0d35cd..5abd18b108 100644 --- a/t/50status.t +++ b/t/50status.t @@ -36,11 +36,11 @@ hosts: status: ON EOT - my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/s/json?show=main,errors' 2>&1 > /dev/null`; + my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/s/json?show=main,events' 2>&1 > /dev/null`; my $jresp = decode_json("$resp"); is $jresp->{'connections'}, 1, "One connection"; is $jresp->{'requests'}, undef, "Requests not present"; - is $jresp->{'http1-errors-404'}, 0, "Internal errors monitoring"; + is $jresp->{'http1-errors.404'}, 0, "Internal errors monitoring"; }; subtest "json hander check 404 error counter" => sub { @@ -54,11 +54,11 @@ hosts: status: ON EOT my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/beeb98fcf148317be5fe5d763c658bc9ea9c087a' 2>&1 > /dev/null`; - my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/s/json?show=errors' 2>&1 > /dev/null`; + my $resp = `curl --silent -o /dev/stderr 'http://127.0.0.1:$server->{port}/s/json?show=events' 2>&1 > /dev/null`; my $jresp = decode_json("$resp"); is $jresp->{'connections'}, undef, "Connections not present"; is $jresp->{'requests'}, undef, "Requests not present"; - is $jresp->{'http1-errors-404'}, 1, "Found the 404 error"; + is $jresp->{'http1-errors.404'}, 1, "Found the 404 error"; }; From a3b35d17c2aa7f98f0db9328828c575f13c993f9 Mon Sep 17 00:00:00 2001 From: Frederik Deweerdt Date: Tue, 31 May 2016 09:49:48 -0700 Subject: [PATCH 8/8] s/http1-errors/status-errors/ in the json output as well --- lib/handler/status/events.c | 18 +++++++++--------- t/50status.t | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/handler/status/events.c b/lib/handler/status/events.c index 3487e45ad9..147fe8e568 100644 --- a/lib/handler/status/events.c +++ b/lib/handler/status/events.c @@ -66,15 +66,15 @@ static h2o_iovec_t events_status_final(void *priv, h2o_globalconf_t *gconf, h2o_ #define BUFSIZE (2*1024) ret.base = h2o_mem_alloc_pool(&req->pool, BUFSIZE); ret.len = snprintf(ret.base, BUFSIZE, ",\n" - " \"http1-errors.400\": %" PRIu64 ",\n" - " \"http1-errors.403\": %" PRIu64 ",\n" - " \"http1-errors.404\": %" PRIu64 ",\n" - " \"http1-errors.405\": %" PRIu64 ",\n" - " \"http1-errors.416\": %" PRIu64 ",\n" - " \"http1-errors.417\": %" PRIu64 ",\n" - " \"http1-errors.500\": %" PRIu64 ",\n" - " \"http1-errors.502\": %" PRIu64 ",\n" - " \"http1-errors.503\": %" PRIu64 ",\n" + " \"status-errors.400\": %" PRIu64 ",\n" + " \"status-errors.403\": %" PRIu64 ",\n" + " \"status-errors.404\": %" PRIu64 ",\n" + " \"status-errors.405\": %" PRIu64 ",\n" + " \"status-errors.416\": %" PRIu64 ",\n" + " \"status-errors.417\": %" PRIu64 ",\n" + " \"status-errors.500\": %" PRIu64 ",\n" + " \"status-errors.502\": %" PRIu64 ",\n" + " \"status-errors.503\": %" PRIu64 ",\n" " \"http2-errors.protocol\": %" PRIu64 ", \n" " \"http2-errors.internal\": %" PRIu64 ", \n" " \"http2-errors.flow-control\": %" PRIu64 ", \n" diff --git a/t/50status.t b/t/50status.t index 5abd18b108..da7987eee4 100644 --- a/t/50status.t +++ b/t/50status.t @@ -40,7 +40,7 @@ EOT my $jresp = decode_json("$resp"); is $jresp->{'connections'}, 1, "One connection"; is $jresp->{'requests'}, undef, "Requests not present"; - is $jresp->{'http1-errors.404'}, 0, "Internal errors monitoring"; + is $jresp->{'status-errors.404'}, 0, "Internal errors monitoring"; }; subtest "json hander check 404 error counter" => sub { @@ -58,7 +58,7 @@ EOT my $jresp = decode_json("$resp"); is $jresp->{'connections'}, undef, "Connections not present"; is $jresp->{'requests'}, undef, "Requests not present"; - is $jresp->{'http1-errors.404'}, 1, "Found the 404 error"; + is $jresp->{'status-errors.404'}, 1, "Found the 404 error"; };