From 364b3f79c4dd91ef7cc3041a015c6797296a172c Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 13 Nov 2015 14:54:15 +0000 Subject: [PATCH] new directive H2Push on/off to en-/disable HTTP/2 server pushes. Server pushes are recognized by Link: headers in responses that carry the rel=preload parameter git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1714219 13f79535-47bb-0310-9956-ffa450edef68 --- docs/manual/mod/mod_http2.xml | 80 +++++++- modules/http2/h2_config.c | 24 +++ modules/http2/h2_config.h | 2 + modules/http2/h2_h2.c | 2 +- modules/http2/h2_mplx.c | 2 +- modules/http2/h2_push.c | 336 +++++++++++++++++++++++++++++++--- modules/http2/h2_push.h | 1 - modules/http2/h2_request.c | 136 ++++++++------ modules/http2/h2_request.h | 7 +- modules/http2/h2_response.c | 165 +---------------- modules/http2/h2_response.h | 30 +-- modules/http2/h2_session.c | 65 ++++--- modules/http2/h2_session.h | 5 +- modules/http2/h2_stream.c | 19 +- modules/http2/h2_stream.h | 5 +- modules/http2/h2_task_input.c | 28 +-- modules/http2/h2_util.c | 93 ++++++++++ modules/http2/h2_util.h | 14 ++ modules/http2/h2_workers.c | 4 +- 19 files changed, 675 insertions(+), 343 deletions(-) diff --git a/docs/manual/mod/mod_http2.xml b/docs/manual/mod/mod_http2.xml index 88fb8ccf6b..da0a6b693e 100644 --- a/docs/manual/mod/mod_http2.xml +++ b/docs/manual/mod/mod_http2.xml @@ -89,6 +89,74 @@ + + H2Push + H2 Server Push Switch + H2Push on|off + H2Push on + + server config + virtual host + + + +

+ This directive toggles the usage of the HTTP/2 server push + protocol feature. This should be used inside a + VirtualHost + section to enable direct HTTP/2 communication for that virtual host. +

+

+ The HTTP/2 protocol allows the server to push other resources to + a client when it asked for a particular one. This is helpful + if those resources are connected in some way and the client can + be expected to ask for it anyway. The pushing then saves the + time it takes the client to ask for the resources itself. On the + other hand, pushing resources the client never needs or already + has is a waste of bandwidth. +

+

+ Server pushes are detected by inspecting the Link headers of + responses (see https://tools.ietf.org/html/rfc5988 for the + specification). When a link thus specified has the rel=preload + attribute, it is treated as a resource to be pushed. +

+

+ Link headers in responses are either set by the application or + can be configured via mod_headers as: +

+ mod_headers example + + + Header add Link ";rel=preload" + Header add Link ";rel=preload" + + + +

+ As the example shows, there can be several link headers added + to a response, resulting in several pushes being triggered. There + are no checks in the module to avoid pushing the same resource + twice or more to one client. Use with care. +

+

+ HTTP/2 server pushes are enabled by default. This directive + allows it to be switch off on all resources of this server/virtual + host. +

+ Example + + H2Push off + + +

+ Last but not least, pushes happen only when the client signals + its willingness to accept those. Most browsers do, some, like Safari 9, + do not. +

+
+
+ H2Upgrade H2 Upgrade Protocol Switch @@ -285,7 +353,7 @@

This directive sets maximum number of extra file handles a HTTP/2 session is allowed to use. A file handle is counted as - extra when it is transfered from a h2 worker thread to + extra when it is transferred from a h2 worker thread to the main HTTP/2 connection handling. This commonly happens when serving static files.

@@ -362,8 +430,8 @@

The name stems from the Security/Server Side TLS - definitions at mozilla where "modern compatiblity" is defined. Mozilla Firefox and - other browsers require modern compatiblity for HTTP/2 connections. As everything + definitions at mozilla where "modern compatibility" is defined. Mozilla Firefox and + other browsers require modern compatibility for HTTP/2 connections. As everything in OpSec, this is a moving target and can be expected to evolve in the future.

@@ -423,7 +491,7 @@

In deployments where servers are reached locally or over reliable connections only, the value might be decreased with 0 disabling - any warmup phase alltogether. + any warmup phase altogether.

The following example sets the size to zero, effectively disabling @@ -458,7 +526,7 @@

See H2TLSWarmUpSize for a description of TLS warmup. H2TLSCoolDownSecs reflects the fact - that connections may detoriate over time (and TCP flow adjusts) + that connections may deteriorate over time (and TCP flow adjusts) for idle connections as well. It is beneficial to overall performance to fall back to the pre-warmup phase after a number of seconds that no data has been sent. @@ -469,7 +537,7 @@

The following example sets the seconds to zero, effectively disabling - any cooldown. Warmed up TLS connections stay on maximum record + any cool down. Warmed up TLS connections stay on maximum record size.

Example diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c index 25fdd29908..1402de33bc 100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@ -53,6 +53,7 @@ static h2_config defconf = { -1, /* HTTP/1 Upgrade support */ 1024*1024, /* TLS warmup size */ 1, /* TLS cooldown secs */ + 1, /* HTTP/2 server push enabled */ }; static int files_per_session = 0; @@ -108,6 +109,7 @@ static void *h2_config_create(apr_pool_t *pool, conf->h2_upgrade = DEF_VAL; conf->tls_warmup_size = DEF_VAL; conf->tls_cooldown_secs = DEF_VAL; + conf->h2_push = DEF_VAL; return conf; } @@ -151,6 +153,7 @@ void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv) n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade); n->tls_warmup_size = H2_CONFIG_GET(add, base, tls_warmup_size); n->tls_cooldown_secs = H2_CONFIG_GET(add, base, tls_cooldown_secs); + n->h2_push = H2_CONFIG_GET(add, base, h2_push); return n; } @@ -196,6 +199,8 @@ apr_int64_t h2_config_geti64(h2_config *conf, h2_config_var_t var) return H2_CONFIG_GET(conf, &defconf, tls_warmup_size); case H2_CONF_TLS_COOLDOWN_SECS: return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs); + case H2_CONF_PUSH: + return H2_CONFIG_GET(conf, &defconf, h2_push); default: return DEF_VAL; } @@ -358,6 +363,23 @@ static const char *h2_conf_set_direct(cmd_parms *parms, return "value must be On or Off"; } +static const char *h2_conf_set_push(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + if (!strcasecmp(value, "On")) { + cfg->h2_push = 1; + return NULL; + } + else if (!strcasecmp(value, "Off")) { + cfg->h2_push = 0; + return NULL; + } + + (void)arg; + return "value must be On or Off"; +} + static const char *h2_conf_set_modern_tls_only(cmd_parms *parms, void *arg, const char *value) { @@ -444,6 +466,8 @@ const command_rec h2_cmds[] = { RSRC_CONF, "number of bytes on TLS connection before doing max writes"), AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL, RSRC_CONF, "seconds of idle time on TLS before shrinking writes"), + AP_INIT_TAKE1("H2Push", h2_conf_set_push, NULL, + RSRC_CONF, "off to disable HTTP/2 server push"), AP_END_CMD }; diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h index 9c59817a99..7d2ed0fa28 100644 --- a/modules/http2/h2_config.h +++ b/modules/http2/h2_config.h @@ -38,6 +38,7 @@ typedef enum { H2_CONF_UPGRADE, H2_CONF_TLS_WARMUP_SIZE, H2_CONF_TLS_COOLDOWN_SECS, + H2_CONF_PUSH, } h2_config_var_t; /* Apache httpd module configuration for h2. */ @@ -59,6 +60,7 @@ typedef struct h2_config { int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */ apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */ int tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */ + int h2_push; /* if HTTP/2 server push is enabled */ } h2_config; diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c index 5c66dbb88d..54fe9e0fa0 100644 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@ -664,7 +664,7 @@ static int h2_h2_post_read_req(request_rec *r) struct h2_task *task = h2_ctx_get_task(ctx); if (task) { /* h2_task connection for a stream, not for h2c */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "adding h1_to_h2_resp output filter"); if (task->serialize_headers) { ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP"); diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index 723ea268f2..d94abaa5b5 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -583,7 +583,7 @@ static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response, h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io) { if (f) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_mplx(%ld-%d): open response: %d, rst=%d", m->id, stream_id, response->http_status, response->rst_error); diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c index cced862dbd..703ea761a0 100644 --- a/modules/http2/h2_push.c +++ b/modules/http2/h2_push.c @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -31,18 +32,283 @@ typedef struct { - apr_array_header_t *pushes; - apr_pool_t *pool; const h2_request *req; + apr_pool_t *pool; + apr_array_header_t *pushes; + const char *s; + size_t slen; + size_t i; + + const char *link; + apr_table_t *params; + char b[4096]; } link_ctx; -static size_t skip_ws(const char *s, size_t i, size_t max) +static int attr_char(char c) +{ + switch (c) { + case '!': + case '#': + case '$': + case '&': + case '+': + case '-': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + return 1; + default: + return apr_isalnum(c); + } +} + +static int ptoken_char(char c) +{ + switch (c) { + case '!': + case '#': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case '-': + case '.': + case '/': + case ':': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case ']': + case '^': + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + return 1; + default: + return apr_isalnum(c); + } +} + +static int skip_ws(link_ctx *ctx) { char c; - while (i < max && (((c = s[i]) == ' ') || (c == '\t'))) { - ++i; + while (ctx->i < ctx->slen + && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) { + ++ctx->i; + } + return (ctx->i < ctx->slen); +} + +static int find_chr(link_ctx *ctx, char c, size_t *pidx) +{ + size_t j; + for (j = ctx->i; j < ctx->slen; ++j) { + if (ctx->s[j] == c) { + *pidx = j; + return 1; + } + } + return 0; +} + +static int read_chr(link_ctx *ctx, char c) +{ + if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) { + ++ctx->i; + return 1; + } + return 0; +} + +static char *mk_str(link_ctx *ctx, size_t end) +{ + if (ctx->i < end) { + return apr_pstrndup(ctx->pool, ctx->s + ctx->i, end - ctx->i); + } + return ""; +} + +static int read_qstring(link_ctx *ctx, char **ps) +{ + if (skip_ws(ctx) && read_chr(ctx, '\"')) { + size_t end; + if (find_chr(ctx, '\"', &end)) { + *ps = mk_str(ctx, end); + ctx->i = end + 1; + return 1; + } + } + return 0; +} + +static int read_ptoken(link_ctx *ctx, char **ps) +{ + if (skip_ws(ctx)) { + size_t i; + for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) { + /* nop */ + } + if (i > ctx->i) { + *ps = mk_str(ctx, i); + ctx->i = i; + return 1; + } + } + return 0; +} + + +static int read_link(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, '<')) { + size_t end; + if (find_chr(ctx, '>', &end)) { + ctx->link = mk_str(ctx, end); + ctx->i = end + 1; + return 1; + } + } + return 0; +} + +static int read_pname(link_ctx *ctx, char **pname) +{ + if (skip_ws(ctx)) { + size_t i; + for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) { + /* nop */ + } + if (i > ctx->i) { + *pname = mk_str(ctx, i); + ctx->i = i; + return 1; + } } - return i; + return 0; +} + +static int read_pvalue(link_ctx *ctx, char **pvalue) +{ + if (skip_ws(ctx) && read_chr(ctx, '=')) { + if (read_qstring(ctx, pvalue) || read_ptoken(ctx, pvalue)) { + return 1; + } + } + return 0; +} + +static int read_param(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, ';')) { + char *name, *value = ""; + if (read_pname(ctx, &name)) { + read_pvalue(ctx, &value); /* value is optional */ + apr_table_setn(ctx->params, name, value); + return 1; + } + } + return 0; +} + +static int read_sep(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, ',')) { + return 1; + } + return 0; +} + +static void init_params(link_ctx *ctx) +{ + if (!ctx->params) { + ctx->params = apr_table_make(ctx->pool, 5); + } + else { + apr_table_clear(ctx->params); + } +} + +static int same_authority(const h2_request *req, const apr_uri_t *uri) +{ + if (uri->scheme != NULL && strcmp(uri->scheme, req->scheme)) { + return 0; + } + if (uri->hostinfo != NULL && strcmp(uri->hostinfo, req->authority)) { + return 0; + } + return 1; +} + +static int set_header(void *ctx, const char *key, const char *value) +{ + apr_table_setn(ctx, key, value); + return 1; +} + + +static int add_push(link_ctx *ctx) +{ + /* so, we have read a Link header and need to decide + * if we transform it into a push. + */ + const char *rel = apr_table_get(ctx->params, "rel"); + if (rel && !strcmp("preload", rel)) { + apr_uri_t uri; + if (apr_uri_parse(ctx->pool, ctx->link, &uri) == APR_SUCCESS) { + if (uri.path && same_authority(ctx->req, &uri)) { + char *path; + apr_table_t *headers; + h2_request *req; + h2_push *push; + + /* We only want to generate pushes for resources in the + * same authority than the original request. + * icing: i think that is wise, otherwise we really need to + * check that the vhost/server is available and uses the same + * TLS (if any) parameters. + */ + path = apr_uri_unparse(ctx->pool, &uri, APR_URI_UNP_OMITSITEPART); + + push = apr_pcalloc(ctx->pool, sizeof(*push)); + push->initiating_id = ctx->req->id; + + headers = apr_table_make(ctx->pool, 5); + apr_table_do(set_header, headers, ctx->req->headers, + "User-Agent", + "Cache-Control", + "Accept-Language", + NULL); + /* TODO: which headers do we add here? + */ + + req = h2_request_createn(0, ctx->pool, + ctx->req->method, + ctx->req->scheme, + ctx->req->authority, + path, headers); + h2_request_end_headers(req, ctx->pool, 1); + push->req = req; + + if (!ctx->pushes) { + ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*)); + } + APR_ARRAY_PUSH(ctx->pushes, h2_push*) = push; + } + } + } + return 0; } static void inspect_link(link_ctx *ctx, const char *s, size_t slen) @@ -77,20 +343,41 @@ static void inspect_link(link_ctx *ctx, const char *s, size_t slen) relation-type = reg-rel-type | ext-rel-type reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) ext-rel-type = URI + + and from + parmname = 1*attr-char + attr-char = ALPHA / DIGIT + / "!" / "#" / "$" / "&" / "+" / "-" / "." + / "^" / "_" / "`" / "|" / "~" */ - /* TODO */ - (void)skip_ws; + + ctx->s = s; + ctx->slen = slen; + ctx->i = 0; + + while (read_link(ctx)) { + init_params(ctx); + while (read_param(ctx)) { + /* nop */ + } + add_push(ctx); + if (!read_sep(ctx)) { + break; + } + } +} + +static int head_iter(void *ctx, const char *key, const char *value) +{ + if (!apr_strnatcasecmp("link", key)) { + inspect_link(ctx, value, strlen(value)); + } + return 1; } apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req, const h2_response *res) { - link_ctx ctx; - - ctx.pushes = NULL; - ctx.pool = p; - ctx.req = req; - /* Collect push candidates from the request/response pair. * * One source for pushes are "rel=preload" link headers @@ -99,16 +386,15 @@ apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req, * TODO: This may be extended in the future by hooks or callbacks * where other modules can provide push information directly. */ - if (res->ngheader) { - int i; - for (i = 0; i < res->ngheader->nvlen; ++i) { - nghttp2_nv *nv = &res->ngheader->nv[i]; - if (nv->namelen == 4 - && apr_strnatcasecmp("link", (const char *)nv->name)) { - inspect_link(&ctx, (const char *)nv->value, nv->valuelen); - } - } - } + if (res->header) { + link_ctx ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.req = req; + ctx.pool = p; - return ctx.pushes; + apr_table_do(head_iter, &ctx, res->header, NULL); + return ctx.pushes; + } + return NULL; } diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h index 7b586d7987..64edee65d1 100644 --- a/modules/http2/h2_push.h +++ b/modules/http2/h2_push.h @@ -22,7 +22,6 @@ struct h2_ngheader; typedef struct h2_push { int initiating_id; const struct h2_request *req; - const struct h2_ngheader *promise; const char *as; } h2_push; diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index 5d301415a8..3737902c5c 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -30,12 +30,23 @@ h2_request *h2_request_create(int id, apr_pool_t *pool) +{ + return h2_request_createn(id, pool, NULL, NULL, NULL, NULL, NULL); +} + +h2_request *h2_request_createn(int id, apr_pool_t *pool, + const char *method, const char *scheme, + const char *authority, const char *path, + apr_table_t *header) { h2_request *req = apr_pcalloc(pool, sizeof(h2_request)); - req->id = id; - req->headers = apr_table_make(pool, 10); - req->content_length = -1; + req->id = id; + req->method = method; + req->scheme = scheme; + req->authority = authority; + req->path = path; + req->headers = header? header : apr_table_make(pool, 10); return req; } @@ -44,45 +55,26 @@ void h2_request_destroy(h2_request *req) { } +static apr_status_t inspect_clen(h2_request *req, const char *s) +{ + char *end; + req->content_length = apr_strtoi64(s, &end, 10); + return (s == end)? APR_EINVAL : APR_SUCCESS; +} + static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool, const char *name, size_t nlen, const char *value, size_t vlen) { char *hname, *hvalue; - if (H2_HD_MATCH_LIT("transfer-encoding", name, nlen)) { - if (!apr_strnatcasecmp("chunked", value)) { - /* This should never arrive here in a HTTP/2 request */ - ap_log_perror(APLOG_MARK, APLOG_ERR, APR_BADARG, pool, - APLOGNO(02945) - "h2_request: 'transfer-encoding: chunked' received"); - return APR_BADARG; - } - } - else if (H2_HD_MATCH_LIT("content-length", name, nlen)) { - char *end; - req->content_length = apr_strtoi64(value, &end, 10); - if (value == end) { - ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool, - APLOGNO(02959) - "h2_request(%d): content-length value not parsed: %s", - req->id, value); - return APR_EINVAL; - } - req->chunked = 0; - } - else if (H2_HD_MATCH_LIT("content-type", name, nlen)) { - /* If we see a content-type and have no length (yet), - * we need to chunk. */ - req->chunked = (req->content_length == -1); - } - else if ((req->seen_host && H2_HD_MATCH_LIT("host", name, nlen)) - || H2_HD_MATCH_LIT("expect", name, nlen) - || H2_HD_MATCH_LIT("upgrade", name, nlen) - || H2_HD_MATCH_LIT("connection", name, nlen) - || H2_HD_MATCH_LIT("proxy-connection", name, nlen) - || H2_HD_MATCH_LIT("keep-alive", name, nlen) - || H2_HD_MATCH_LIT("http2-settings", name, nlen)) { + if (H2_HD_MATCH_LIT("expect", name, nlen) + || H2_HD_MATCH_LIT("upgrade", name, nlen) + || H2_HD_MATCH_LIT("connection", name, nlen) + || H2_HD_MATCH_LIT("proxy-connection", name, nlen) + || H2_HD_MATCH_LIT("transfer-encoding", name, nlen) + || H2_HD_MATCH_LIT("keep-alive", name, nlen) + || H2_HD_MATCH_LIT("http2-settings", name, nlen)) { /* ignore these. */ return APR_SUCCESS; } @@ -91,7 +83,7 @@ static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool, if (existing) { char *nval; - /* Cookie headers come separately in HTTP/2, but need + /* Cookie header come separately in HTTP/2, but need * to be merged by "; " (instead of default ", ") */ hvalue = apr_pstrndup(pool, value, vlen); @@ -101,7 +93,9 @@ static apr_status_t add_h1_header(h2_request *req, apr_pool_t *pool, } } else if (H2_HD_MATCH_LIT("host", name, nlen)) { - req->seen_host = 1; + if (apr_table_get(req->headers, "Host")) { + return APR_SUCCESS; /* ignore duplicate */ + } } hname = apr_pstrndup(pool, name, nlen); @@ -124,13 +118,13 @@ static int set_h1_header(void *ctx, const char *key, const char *value) return 1; } -static apr_status_t add_h1_headers(h2_request *req, apr_pool_t *pool, - apr_table_t *headers) +static apr_status_t add_all_h1_header(h2_request *req, apr_pool_t *pool, + apr_table_t *header) { h1_ctx x; x.req = req; x.pool = pool; - apr_table_do(set_h1_header, &x, headers, NULL); + apr_table_do(set_h1_header, &x, header, NULL); return APR_SUCCESS; } @@ -150,7 +144,7 @@ apr_status_t h2_request_rwrite(h2_request *req, request_rec *r) r->parsed_uri.port_str); } - status = add_h1_headers(req, r->pool, r->headers_in); + status = add_all_h1_header(req, r->pool, r->headers_in); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, "h2_request(%d): rwrite %s host=%s://%s%s", @@ -215,11 +209,22 @@ apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos) { + const char *s; + if (req->eoh) { return APR_EINVAL; } - - if (!req->seen_host) { + + /* be safe, some header we do not accept on h2(c) */ + apr_table_unset(req->headers, "expect"); + apr_table_unset(req->headers, "upgrade"); + apr_table_unset(req->headers, "connection"); + apr_table_unset(req->headers, "proxy-connection"); + apr_table_unset(req->headers, "transfer-encoding"); + apr_table_unset(req->headers, "keep-alive"); + apr_table_unset(req->headers, "http2-settings"); + + if (!apr_table_get(req->headers, "Host")) { /* Need to add a "Host" header if not already there to * make virtual hosts work correctly. */ if (!req->authority) { @@ -228,20 +233,34 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos) apr_table_set(req->headers, "Host", req->authority); } - if (eos && req->chunked) { - /* We had chunking figured out, but the EOS is already there. - * unmark chunking and set a definitive content-length. - */ + s = apr_table_get(req->headers, "Content-Length"); + if (s) { + if (inspect_clen(req, s) != APR_SUCCESS) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool, + APLOGNO(02959) + "h2_request(%d): content-length value not parsed: %s", + req->id, s); + return APR_EINVAL; + } req->chunked = 0; - apr_table_setn(req->headers, "Content-Length", "0"); } - else if (req->chunked) { - /* We have not seen a content-length. We therefore must - * pass any request content in chunked form. - */ - apr_table_mergen(req->headers, "Transfer-Encoding", "chunked"); + else { + /* no content-length given */ + req->content_length = -1; + s = apr_table_get(req->headers, "Content-Type"); + if (eos && s) { + req->chunked = 0; + apr_table_setn(req->headers, "Content-Length", "0"); + } + else if (s) { + /* We have not seen a content-length, but a content-type. + * must pass any request content in chunked form. + */ + req->chunked = 1; + apr_table_mergen(req->headers, "Transfer-Encoding", "chunked"); + } } - + req->eoh = 1; return APR_SUCCESS; @@ -253,13 +272,12 @@ void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src) { /* keep the dst id */ dst->method = OPT_COPY(p, src->method); - dst->scheme = OPT_COPY(p, src->method); - dst->authority = OPT_COPY(p, src->method); - dst->path = OPT_COPY(p, src->method); + dst->scheme = OPT_COPY(p, src->scheme); + dst->authority = OPT_COPY(p, src->authority); + dst->path = OPT_COPY(p, src->path); dst->headers = apr_table_clone(p, src->headers); dst->content_length = src->content_length; dst->chunked = src->chunked; dst->eoh = src->eoh; - dst->seen_host = src->seen_host; } diff --git a/modules/http2/h2_request.h b/modules/http2/h2_request.h index 5386848eec..a3a8f8ead4 100644 --- a/modules/http2/h2_request.h +++ b/modules/http2/h2_request.h @@ -40,12 +40,15 @@ struct h2_request { apr_off_t content_length; int chunked; int eoh; - - int seen_host; }; h2_request *h2_request_create(int id, apr_pool_t *pool); +h2_request *h2_request_createn(int id, apr_pool_t *pool, + const char *method, const char *scheme, + const char *authority, const char *path, + apr_table_t *headers); + void h2_request_destroy(h2_request *req); apr_status_t h2_request_rwrite(h2_request *req, request_rec *r); diff --git a/modules/http2/h2_response.c b/modules/http2/h2_response.c index ab81b187ff..2751f2d377 100644 --- a/modules/http2/h2_response.c +++ b/modules/http2/h2_response.c @@ -27,20 +27,8 @@ #include "h2_private.h" #include "h2_h2.h" #include "h2_util.h" -#include "h2_push.h" #include "h2_response.h" -static void make_ngheader(apr_pool_t *pool, h2_response *to, - apr_table_t *header); - -static int ignore_header(const char *name) -{ - return (H2_HD_MATCH_LIT_CS("connection", name) - || H2_HD_MATCH_LIT_CS("proxy-connection", name) - || H2_HD_MATCH_LIT_CS("upgrade", name) - || H2_HD_MATCH_LIT_CS("keep-alive", name) - || H2_HD_MATCH_LIT_CS("transfer-encoding", name)); -} h2_response *h2_response_create(int stream_id, int rst_error, @@ -76,10 +64,8 @@ h2_response *h2_response_create(int stream_id, while (*sep == ' ' || *sep == '\t') { ++sep; } - if (ignore_header(hline)) { - /* never forward, ch. 8.1.2.2 */ - } - else { + + if (!h2_util_ignore_header(hline)) { apr_table_merge(header, hline, sep); if (*sep && H2_HD_MATCH_LIT_CS("content-length", hline)) { char *end; @@ -100,7 +86,7 @@ h2_response *h2_response_create(int stream_id, header = apr_table_make(pool, 0); } - response->rheader = header; + response->header = header; return response; } @@ -115,7 +101,7 @@ h2_response *h2_response_rcreate(int stream_id, request_rec *r, response->stream_id = stream_id; response->http_status = r->status; response->content_length = -1; - response->rheader = header; + response->header = header; if (response->http_status == HTTP_FORBIDDEN) { const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden"); @@ -144,149 +130,10 @@ h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from) to->stream_id = from->stream_id; to->http_status = from->http_status; to->content_length = from->content_length; - if (from->rheader) { - make_ngheader(pool, to, from->rheader); + if (from->header) { + to->header = apr_table_clone(pool, from->header); } return to; } -typedef struct { - nghttp2_nv *nv; - size_t nvlen; - size_t nvstrlen; - size_t offset; - char *strbuf; - apr_pool_t *pool; - apr_array_header_t *pushes; -} nvctx_t; - -static int count_header(void *ctx, const char *key, const char *value) -{ - if (!ignore_header(key)) { - nvctx_t *nvctx = (nvctx_t*)ctx; - nvctx->nvlen++; - nvctx->nvstrlen += strlen(key) + strlen(value) + 2; - } - return 1; -} - -#define NV_ADD_LIT_CS(nv, k, v) addnv_lit_cs(nv, k, sizeof(k) - 1, v, strlen(v)) -#define NV_ADD_CS_CS(nv, k, v) addnv_cs_cs(nv, k, strlen(k), v, strlen(v)) -#define NV_BUF_ADD(nv, s, len) memcpy(nv->strbuf, s, len); \ - s = nv->strbuf; \ - nv->strbuf += len + 1 - -static void addnv_cs_cs(nvctx_t *ctx, const char *key, size_t key_len, - const char *value, size_t val_len) -{ - nghttp2_nv *nv = &ctx->nv[ctx->offset]; - - NV_BUF_ADD(ctx, key, key_len); - NV_BUF_ADD(ctx, value, val_len); - - nv->name = (uint8_t*)key; - nv->namelen = key_len; - nv->value = (uint8_t*)value; - nv->valuelen = val_len; - - ctx->offset++; -} - -static void addnv_lit_cs(nvctx_t *ctx, const char *key, size_t key_len, - const char *value, size_t val_len) -{ - nghttp2_nv *nv = &ctx->nv[ctx->offset]; - - NV_BUF_ADD(ctx, value, val_len); - - nv->name = (uint8_t*)key; - nv->namelen = key_len; - nv->value = (uint8_t*)value; - nv->valuelen = val_len; - - ctx->offset++; -} - -static h2_push *h2_parse_preload(apr_pool_t *pool, const char *str) -{ - /* TODO: implement this */ - return NULL; -} - -static int add_header(void *ctx, const char *key, const char *value) -{ - if (!ignore_header(key)) { - nvctx_t *nvctx = (nvctx_t*)ctx; - NV_ADD_CS_CS(nvctx, key, value); - - if (apr_strnatcasecmp("link", key) - && ap_strstr_c("preload", value)) { - /* Detect link headers with rel=preload to use as possible - * server push candidates. */ - h2_push *push; - if ((push = h2_parse_preload(nvctx->pool, value))) { - if (!nvctx->pushes) { - nvctx->pushes = apr_array_make(nvctx->pool, 5, - sizeof(h2_push*)); - } - APR_ARRAY_PUSH(nvctx->pushes, h2_push*) = push; - } - } - } - return 1; -} - -static void make_ngheader(apr_pool_t *pool, h2_response *to, - apr_table_t *header) -{ - size_t n; - h2_ngheader *h; - nvctx_t ctx; - char *status; - - to->ngheader = NULL; - to->pushes = NULL; - - status = apr_psprintf(pool, "%d", to->http_status); - ctx.nv = NULL; - ctx.nvlen = 1; - ctx.nvstrlen = strlen(status) + 1; - ctx.offset = 0; - ctx.strbuf = NULL; - ctx.pool = pool; - ctx.pushes = NULL; - - apr_table_do(count_header, &ctx, header, NULL); - - n = (sizeof(h2_ngheader) - + (ctx.nvlen * sizeof(nghttp2_nv)) + ctx.nvstrlen); - h = apr_pcalloc(pool, n); - if (h) { - ctx.nv = (nghttp2_nv*)(h + 1); - ctx.strbuf = (char*)&ctx.nv[ctx.nvlen]; - - NV_ADD_LIT_CS(&ctx, ":status", status); - apr_table_do(add_header, &ctx, header, NULL); - - h->nv = ctx.nv; - h->nvlen = ctx.nvlen; - - to->ngheader = h; - to->pushes = ctx.pushes; - } -} - -int h2_response_push_count(h2_response *response) -{ - return response->pushes? response->pushes->nelts : 0; -} - -h2_push *h2_response_get_push(h2_response *response, size_t i) -{ - if (response->pushes && i < response->pushes->nelts) { - return APR_ARRAY_IDX(response->pushes, i, h2_push*); - } - return NULL; -} - diff --git a/modules/http2/h2_response.h b/modules/http2/h2_response.h index e6d47f4669..59f7b035aa 100644 --- a/modules/http2/h2_response.h +++ b/modules/http2/h2_response.h @@ -18,24 +18,12 @@ struct h2_push; -/* h2_response is just the data belonging the the head of a HTTP response, - * suitable prepared to be fed to nghttp2 for response submit. - */ -typedef struct h2_ngheader { - nghttp2_nv *nv; - apr_size_t nvlen; -} h2_ngheader; - -struct h2_push; - typedef struct h2_response { int stream_id; int rst_error; int http_status; apr_off_t content_length; - apr_table_t *rheader; - h2_ngheader *ngheader; - apr_array_header_t *pushes; + apr_table_t *header; } h2_response; h2_response *h2_response_create(int stream_id, @@ -51,20 +39,4 @@ void h2_response_destroy(h2_response *response); h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from); -/** - * Get the number of push proposals included with the response. - * @return number of push proposals in this response - */ -int h2_response_push_count(h2_response *response); - -/** - * Get the ith h2_push contained in this response. - * - * @param response the response - * @param i the index of the push to get - * @return the ith h2_push or NULL if out of bounds - */ -struct h2_push *h2_response_get_push(h2_response *response, size_t i); - - #endif /* defined(__mod_h2__h2_response__) */ diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index 0d11097859..89a2eb1ffe 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -30,6 +30,7 @@ #include "h2_h2.h" #include "h2_mplx.h" #include "h2_push.h" +#include "h2_request.h" #include "h2_response.h" #include "h2_stream.h" #include "h2_stream_set.h" @@ -80,10 +81,6 @@ h2_stream *h2_session_open_stream(h2_session *session, int stream_id) session->max_stream_received = stream->id; } - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - "h2_session: stream(%ld-%d): opened", - session->id, stream_id); - return stream; } @@ -305,7 +302,7 @@ static int on_frame_not_send_cb(nghttp2_session *ngh2, return 0; } -static apr_status_t stream_destroy(h2_session *session, +static apr_status_t stream_release(h2_session *session, h2_stream *stream, uint32_t error_code) { @@ -342,12 +339,12 @@ static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id, } stream = h2_session_get_stream(session, stream_id); if (stream) { - stream_destroy(session, stream, error_code); + stream_release(session, stream, error_code); } if (error_code) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - "h2_stream(%ld-%d): close error %d", + "h2_stream(%ld-%d): closed, error=%d", session->id, (int)stream_id, error_code); } @@ -1121,24 +1118,40 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) if (stream->submitted) { rv = NGHTTP2_PROTOCOL_ERROR; } - else if (stream->response && stream->response->ngheader) { + else if (stream->response && stream->response->header) { nghttp2_data_provider provider; h2_response *response = stream->response; + h2_ngheader *ngh; memset(&provider, 0, sizeof(provider)); provider.source.fd = stream->id; provider.read_callback = stream_data_cb; - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, - "h2_stream(%ld-%d): submitting response %d", + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + "h2_stream(%ld-%d): submit response %d", session->id, stream->id, response->http_status); + ngh = h2_util_ngheader_make_res(stream->pool, response->http_status, + response->header); rv = nghttp2_submit_response(session->ngh2, response->stream_id, - response->ngheader->nv, - response->ngheader->nvlen, &provider); - + ngh->nv, ngh->nvlen, &provider); + + /* If the submit worked, + * and this stream is not a pushed one itself, + * and HTTP/2 server push is enabled here, + * and the response is in the range 200-299 *), + * and the remote side has pushing enabled, + * -> find and perform any pushes on this stream + * + * *) the response code is relevant, as we do not want to + * make pushes on 401 or 403 codes, neiterh on 301/302 + * and friends. And if we see a 304, we do not push either + * as the client, having this resource in its cache, might + * also have the pushed ones as well. + */ if (!rv - && !stream->promised_on + && !stream->initiated_on + && h2_config_geti(h2_config_get(session->c), H2_CONF_PUSH) && H2_HTTP_2XX(response->http_status) && h2_session_push_enabled(session)) { @@ -1148,7 +1161,7 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) else { int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR); - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): RST_STREAM, err=%d", session->id, stream->id, err); @@ -1169,15 +1182,18 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) return status; } -struct h2_stream *h2_session_push(h2_session *session, h2_push *push) +struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, + h2_push *push) { apr_status_t status; h2_stream *stream; + h2_ngheader *ngh; int nid; + ngh = h2_util_ngheader_make_req(is->pool, push->req); nid = nghttp2_submit_push_promise(session->ngh2, 0, push->initiating_id, - push->promise->nv, push->promise->nvlen, - NULL); + ngh->nv, ngh->nvlen, NULL); + if (nid <= 0) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): submitting push promise fail: %s", @@ -1186,16 +1202,17 @@ struct h2_stream *h2_session_push(h2_session *session, h2_push *push) return NULL; } - ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c, - "h2_stream(%ld-%d): promised new stream %d", - session->id, push->initiating_id, nid); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + "h2_stream(%ld-%d): promised new stream %d for %s %s", + session->id, push->initiating_id, nid, + push->req->method, push->req->path); stream = h2_session_open_stream(session, nid); if (stream) { - h2_stream_set_h2_request(stream, push->req); + h2_stream_set_h2_request(stream, is->id, push->req); status = stream_end_headers(session, stream, 1); if (status != APR_SUCCESS) { - ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, "h2_stream(%ld-%d): scheduling push stream", session->id, stream->id); h2_stream_cleanup(stream); @@ -1203,7 +1220,7 @@ struct h2_stream *h2_session_push(h2_session *session, h2_push *push) } } else { - ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, session->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, "h2_stream(%ld-%d): failed to create stream obj %d", session->id, push->initiating_id, nid); } diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h index c110b8c214..2ae8691c1d 100644 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@ -189,10 +189,11 @@ apr_status_t h2_session_stream_destroy(h2_session *session, * processing.. * * @param session the session to work in - * @param stream the stream on which the push depends + * @param is the stream initiating the push * @param push the push to promise * @return the new promised stream or NULL */ -struct h2_stream *h2_session_push(h2_session *session, struct h2_push *push); +struct h2_stream *h2_session_push(h2_session *session, + struct h2_stream *is, struct h2_push *push); #endif /* defined(__mod_h2__h2_session__) */ diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c index 7ef28255fe..a6be8b10ea 100644 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@ -159,9 +159,6 @@ h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session) apr_status_t h2_stream_destroy(h2_stream *stream) { AP_DEBUG_ASSERT(stream); - - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c, - "h2_stream(%ld-%d): destroy", stream->session->id, stream->id); if (stream->request) { h2_request_destroy(stream->request); stream->request = NULL; @@ -237,9 +234,12 @@ apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r) return status; } -void h2_stream_set_h2_request(h2_stream *stream, const h2_request *req) +void h2_stream_set_h2_request(h2_stream *stream, int initiated_on, + const h2_request *req) { h2_request_copy(stream->pool, stream->request, req); + stream->initiated_on = initiated_on; + stream->request->eoh = 0; } apr_status_t h2_stream_add_header(h2_stream *stream, @@ -286,10 +286,10 @@ apr_status_t h2_stream_schedule(h2_stream *stream, int eos, } ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c, - "h2_mplx(%ld-%d): start stream, task %s %s (%s)", + "h2_stream(%ld-%d): scheduled %s %s://%s%s", stream->session->id, stream->id, - stream->request->method, stream->request->path, - stream->request->authority); + stream->request->method, stream->request->scheme, + stream->request->authority, stream->request->path); return status; } @@ -545,9 +545,12 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream) pushes = h2_push_collect(stream->pool, stream->request, stream->response); if (pushes && !apr_is_empty_array(pushes)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, + "h2_stream(%ld-%d): found %d push candidates", + stream->session->id, stream->id, pushes->nelts); for (i = 0; i < pushes->nelts; ++i) { h2_push *push = APR_ARRAY_IDX(pushes, i, h2_push*); - h2_stream *s = h2_session_push(stream->session, push); + h2_stream *s = h2_session_push(stream->session, stream, push); if (!s) { status = APR_ECONNRESET; break; diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h index 9484f64933..225f86b2a4 100644 --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@ -50,7 +50,7 @@ typedef struct h2_stream h2_stream; struct h2_stream { int id; /* http2 stream id */ - int promised_on; /* http2 stream this was a promise on, or 0 */ + int initiated_on; /* http2 stream id this was initiated on or 0 */ h2_stream_state_t state; /* http/2 state of this stream */ struct h2_session *session; /* the session this stream belongs to */ @@ -131,7 +131,8 @@ apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r); * @param stream the stream to init the request for * @param req the request for initializing, will be copied */ -void h2_stream_set_h2_request(h2_stream *stream, const struct h2_request *req); +void h2_stream_set_h2_request(h2_stream *stream, int initiated_on, + const struct h2_request *req); /* * Add a HTTP/2 header (including pseudo headers) to the given stream. diff --git a/modules/http2/h2_task_input.c b/modules/http2/h2_task_input.c index 86334ea622..09572b9a34 100644 --- a/modules/http2/h2_task_input.c +++ b/modules/http2/h2_task_input.c @@ -71,21 +71,6 @@ h2_task_input *h2_task_input_create(h2_task *task, apr_pool_t *pool, /* We do not serialize and have eos already, no need to * create a bucket brigade. */ } - - if (APLOGcdebug(task->c)) { - char buffer[1024]; - apr_size_t len = sizeof(buffer)-1; - if (input->bb) { - apr_brigade_flatten(input->bb, buffer, &len); - } - else { - len = 0; - } - buffer[len] = 0; - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, - "h2_task_input(%s): request is: %s", - task->id, buffer); - } } return input; } @@ -105,21 +90,20 @@ apr_status_t h2_task_input_read(h2_task_input *input, apr_status_t status = APR_SUCCESS; apr_off_t bblen = 0; - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld", input->task->id, block, mode, (long)readbytes); + if (mode == AP_MODE_INIT) { + return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes); + } + if (is_aborted(f)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, - "h2_task_input(%s): is aborted", - input->task->id); + "h2_task_input(%s): is aborted", input->task->id); return APR_ECONNABORTED; } - if (mode == AP_MODE_INIT) { - return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes); - } - if (input->bb) { status = apr_brigade_length(input->bb, 1, &bblen); if (status != APR_SUCCESS) { diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c index 7b12a3f3d7..acfc9a61f7 100644 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@ -25,6 +25,7 @@ #include #include "h2_private.h" +#include "h2_request.h" #include "h2_util.h" size_t h2_util_hex_dump(char *buffer, size_t maxlen, @@ -205,6 +206,10 @@ const char *h2_util_first_token_match(apr_pool_t *pool, const char *s, return NULL; } +/******************************************************************************* + * h2_util for bucket brigades + ******************************************************************************/ + /* DEEP_COPY==0 crashes under load. I think the setaside is fine, * however buckets moved to another thread will still be * free'd against the old bucket_alloc. *And* if the old @@ -789,3 +794,91 @@ apr_status_t h2_transfer_brigade(apr_bucket_brigade *to, return APR_SUCCESS; } +/******************************************************************************* + * h2_ngheader + ******************************************************************************/ + +int h2_util_ignore_header(const char *name) +{ + /* never forward, ch. 8.1.2.2 */ + return (H2_HD_MATCH_LIT_CS("connection", name) + || H2_HD_MATCH_LIT_CS("proxy-connection", name) + || H2_HD_MATCH_LIT_CS("upgrade", name) + || H2_HD_MATCH_LIT_CS("keep-alive", name) + || H2_HD_MATCH_LIT_CS("transfer-encoding", name)); +} + +static int count_header(void *ctx, const char *key, const char *value) +{ + if (!h2_util_ignore_header(key)) { + (*((size_t*)ctx))++; + } + return 1; +} + +#define NV_ADD_LIT_CS(nv, k, v) add_header(nv, k, sizeof(k) - 1, v, strlen(v)) +#define NV_ADD_CS_CS(nv, k, v) add_header(nv, k, strlen(k), v, strlen(v)) + +static int add_header(h2_ngheader *ngh, + const char *key, size_t key_len, + const char *value, size_t val_len) +{ + nghttp2_nv *nv = &ngh->nv[ngh->nvlen++]; + + nv->name = (uint8_t*)key; + nv->namelen = key_len; + nv->value = (uint8_t*)value; + nv->valuelen = val_len; + return 1; +} + +static int add_table_header(void *ctx, const char *key, const char *value) +{ + if (!h2_util_ignore_header(key)) { + add_header(ctx, key, strlen(key), value, strlen(value)); + } + return 1; +} + + +h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, + int http_status, + apr_table_t *header) +{ + h2_ngheader *ngh; + size_t n; + + n = 1; + apr_table_do(count_header, &n, header, NULL); + + ngh = apr_pcalloc(p, sizeof(h2_ngheader)); + ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); + NV_ADD_LIT_CS(ngh, ":status", apr_psprintf(p, "%d", http_status)); + apr_table_do(add_table_header, ngh, header, NULL); + + return ngh; +} + +h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, + const struct h2_request *req) +{ + + h2_ngheader *ngh; + size_t n; + + AP_DEBUG_ASSERT(req); + + n = 4; + apr_table_do(count_header, &n, req->headers, NULL); + + ngh = apr_pcalloc(p, sizeof(h2_ngheader)); + ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); + NV_ADD_LIT_CS(ngh, ":scheme", req->scheme); + NV_ADD_LIT_CS(ngh, ":authority", req->authority); + NV_ADD_LIT_CS(ngh, ":path", req->path); + NV_ADD_LIT_CS(ngh, ":method", req->method); + apr_table_do(add_table_header, ngh, req->headers, NULL); + + return ngh; +} + diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h index a859335f0c..51efb8cf2d 100644 --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@ -16,6 +16,7 @@ #ifndef __mod_h2__h2_util__ #define __mod_h2__h2_util__ +struct h2_request; struct nghttp2_frame; size_t h2_util_hex_dump(char *buffer, size_t maxlen, @@ -67,6 +68,19 @@ apr_size_t h2_util_base64url_decode(const char **decoded, nv->value = (uint8_t *)VALUE; \ nv->valuelen = strlen(VALUE) +int h2_util_ignore_header(const char *name); + +typedef struct h2_ngheader { + nghttp2_nv *nv; + apr_size_t nvlen; +} h2_ngheader; + +h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, + int http_status, + apr_table_t *header); +h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, + const struct h2_request *req); + /** * Moves data from one brigade into another. If maxlen > 0, it only * moves up to maxlen bytes into the target brigade, making bucket splits diff --git a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c index 18f39d136c..bebbcd2d29 100644 --- a/modules/http2/h2_workers.c +++ b/modules/http2/h2_workers.c @@ -174,7 +174,7 @@ static apr_status_t get_mplx_next(h2_worker *worker, h2_mplx **pm, * needed to give up with more than enough workers. */ if (task) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s, + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s, "h2_worker(%d): start task(%s)", h2_worker_get_id(worker), task->id); /* Since we hand out a reference to the worker, we increase @@ -326,7 +326,7 @@ apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m) { apr_status_t status = apr_thread_mutex_lock(workers->lock); if (status == APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, status, workers->s, + ap_log_error(APLOG_MARK, APLOG_TRACE2, status, workers->s, "h2_workers: register mplx(%ld)", m->id); if (in_list(workers, m)) { status = APR_EAGAIN; -- 2.40.0