From 5a5f9ddaa95c230e1dee1c9bc9112e952cca9401 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Mon, 14 Nov 2016 11:15:08 +0000 Subject: [PATCH] Merge of r1767936,1768160,1769192,1769550 from trunk: mod_http2: new directive 'H2PushResource' to enable early pushes before processing of the main request starts. Resources are announced to the client in Link headers on a 103 early hint response. All responses with status code <400 are inspected for Link header and trigger pushes accordingly. 304 still does prevent pushes. 'H2PushResource' can mark resources as 'critical' which gives them higher priority than the main resource. This leads to preferred scheduling for processing and, when content is available, will send it first. 'critical' is also recognized on Link headers. mod_proxy_http2: uris in Link headers are now mapped back to a suitable local url when available. Relative uris with an absolute path are mapped as well. This makes reverse proxy mapping available for resources announced in this header. With 103 interim responses being forwarded to the main client connection, this effectively allows early pushing of resources by a reverse proxied backend server. adding support for newly proposed 103 status code. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1769595 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 21 ++ docs/manual/mod/mod_http2.xml | 38 ++++ modules/http/http_filters.c | 3 +- modules/http2/h2.h | 2 - modules/http2/h2_config.c | 80 ++++++- modules/http2/h2_config.h | 12 +- modules/http2/h2_from_h1.c | 14 +- modules/http2/h2_h2.c | 29 ++- modules/http2/h2_mplx.c | 4 - modules/http2/h2_proxy_session.c | 30 ++- modules/http2/h2_proxy_util.c | 352 +++++++++++++++++++++++++++++++ modules/http2/h2_proxy_util.h | 9 +- modules/http2/h2_push.c | 6 +- modules/http2/h2_push.h | 1 + modules/http2/h2_session.c | 23 +- modules/http2/h2_stream.h | 1 + modules/http2/h2_version.h | 4 +- modules/http2/mod_http2.c | 4 +- modules/metadata/mod_headers.c | 11 +- modules/session/mod_session.c | 13 +- server/util_cookies.c | 6 +- 21 files changed, 605 insertions(+), 58 deletions(-) diff --git a/CHANGES b/CHANGES index 042fb0dea7..04d511c1b6 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,27 @@ Changes with Apache 2.4.24 + *) mod_http2: new directive 'H2PushResource' to enable early pushes before + processing of the main request starts. Resources are announced to the + client in Link headers on a 103 early hint response. + All responses with status code <400 are inspected for Link header and + trigger pushes accordingly. 304 still does prevent pushes. + 'H2PushResource' can mark resources as 'critical' which gives them higher + priority than the main resource. This leads to preferred scheduling for + processing and, when content is available, will send it first. 'critical' + is also recognized on Link headers. [Stefan Eissing] + + *) mod_proxy_http2: uris in Link headers are now mapped back to a suitable + local url when available. Relative uris with an absolute path are mapped + as well. This makes reverse proxy mapping available for resources + announced in this header. + With 103 interim responses being forwarded to the main client connection, + this effectively allows early pushing of resources by a reverse proxied + backend server. [Stefan Eissing] + + *) mod_proxy_http2: adding support for newly proposed 103 status code. + [Stefan Eissing] + *) mpm_unix: Apache fails to start if previously crashed then restarted with the same PID (e.g. in container). PR 60261. [Val , Yann Ylavic] diff --git a/docs/manual/mod/mod_http2.xml b/docs/manual/mod/mod_http2.xml index f05dd4dae7..02134bbe9f 100644 --- a/docs/manual/mod/mod_http2.xml +++ b/docs/manual/mod/mod_http2.xml @@ -933,5 +933,43 @@ H2TLSCoolDownSecs 0 + + H2PushResource + Declares resources for early pushing to the client + H2PushResource [add] path [critical] + + server config + virtual host + directory + .htaccess + + Available in version 2.4.24 and later. + + +

+ When added to a directory/location HTTP/2 PUSHes will be attempted + for all paths added via this directive. This directive can be used + several times for the same location. +

+

+ This directive pushes resources much earlier than adding + Link headers via mod_headers. + mod_http2 announces these resources in a + 103 Early Hints interim response to the client. + That means that clients not supporting PUSH will still get + early preload hints. +

+

+ In contrast to setting Link response headers + via mod_headers, this directive will only + take effect on HTTP/2 connections. +

+

+ By adding critical to such a resource, the server + will give processing it more preference and send its data, once + available, before the data from the main request. +

+
+
diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index 2bc1fc9f1f..b4ca437edd 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -776,8 +776,7 @@ static void fixup_vary(request_rec *r) * its comma-separated fieldname values, and then add them to varies * if not already present in the array. */ - apr_table_do((int (*)(void *, const char *, const char *))uniq_field_values, - (void *) varies, r->headers_out, "Vary", NULL); + apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL); /* If we found any, replace old Vary fields with unique-ified value */ diff --git a/modules/http2/h2.h b/modules/http2/h2.h index 59719ad8c7..2b80e1bfba 100644 --- a/modules/http2/h2.h +++ b/modules/http2/h2.h @@ -55,8 +55,6 @@ extern const char *H2_MAGIC_TOKEN; /* Initial default window size, RFC 7540 ch. 6.5.2 */ #define H2_INITIAL_WINDOW_SIZE ((64*1024)-1) -#define H2_HTTP_2XX(a) ((a) >= 200 && (a) < 300) - #define H2_STREAM_CLIENT_INITIATED(id) (id&0x01) #define H2_ALEN(a) (sizeof(a)/sizeof((a)[0])) diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c index 5613e8a479..fe07637004 100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@ -73,7 +73,6 @@ static void *h2_config_create(apr_pool_t *pool, const char *prefix, const char *x) { h2_config *conf = (h2_config *)apr_pcalloc(pool, sizeof(h2_config)); - const char *s = x? x : "unknown"; char *name = apr_pstrcat(pool, prefix, "[", s, "]", NULL); @@ -96,7 +95,7 @@ static void *h2_config_create(apr_pool_t *pool, conf->priorities = NULL; conf->push_diary_size = DEF_VAL; conf->copy_files = DEF_VAL; - + conf->push_list = NULL; return conf; } @@ -110,12 +109,11 @@ void *h2_config_create_dir(apr_pool_t *pool, char *x) return h2_config_create(pool, "dir", x); } -void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv) +static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv) { h2_config *base = (h2_config *)basev; h2_config *add = (h2_config *)addv; h2_config *n = (h2_config *)apr_pcalloc(pool, sizeof(h2_config)); - char *name = apr_pstrcat(pool, "merged[", add->name, ", ", base->name, "]", NULL); n->name = name; @@ -143,10 +141,25 @@ void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv) } n->push_diary_size = H2_CONFIG_GET(add, base, push_diary_size); n->copy_files = H2_CONFIG_GET(add, base, copy_files); - + if (add->push_list && base->push_list) { + n->push_list = apr_array_append(pool, base->push_list, add->push_list); + } + else { + n->push_list = add->push_list? add->push_list : base->push_list; + } return n; } +void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv) +{ + return h2_config_merge(pool, basev, addv); +} + +void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv) +{ + return h2_config_merge(pool, basev, addv); +} + int h2_config_geti(const h2_config *conf, h2_config_var_t var) { return (int)h2_config_geti64(conf, var); @@ -521,6 +534,59 @@ static const char *h2_conf_set_copy_files(cmd_parms *parms, return "value must be On or Off"; } +static void add_push(apr_pool_t *pool, h2_config *conf, h2_push_res *push) +{ + h2_push_res *new; + if (!conf->push_list) { + conf->push_list = apr_array_make(pool, 10, sizeof(*push)); + } + new = apr_array_push(conf->push_list); + new->uri_ref = push->uri_ref; + new->critical = push->critical; +} + +static const char *h2_conf_add_push_res(cmd_parms *cmd, void *dirconf, + const char *arg1, const char *arg2, + const char *arg3) +{ + h2_config *dconf = (h2_config*)dirconf ; + h2_config *sconf = (h2_config*)h2_config_sget(cmd->server); + h2_push_res push; + const char *last = arg3; + + memset(&push, 0, sizeof(push)); + if (!strcasecmp("add", arg1)) { + push.uri_ref = arg2; + } + else { + push.uri_ref = arg1; + last = arg2; + if (arg3) { + return "too many parameter"; + } + } + + if (last) { + if (!strcasecmp("critical", last)) { + push.critical = 1; + } + else { + return "unknown last parameter"; + } + } + + /* server command? set both */ + if (cmd->path == NULL) { + add_push(cmd->pool, sconf, &push); + add_push(cmd->pool, dconf, &push); + } + else { + add_push(cmd->pool, dconf, &push); + } + + return NULL; +} + #define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) const command_rec h2_cmds[] = { @@ -561,7 +627,9 @@ const command_rec h2_cmds[] = { AP_INIT_TAKE1("H2PushDiarySize", h2_conf_set_push_diary_size, NULL, RSRC_CONF, "size of push diary"), AP_INIT_TAKE1("H2CopyFiles", h2_conf_set_copy_files, NULL, - OR_ALL, "on to perform copy of file data"), + OR_FILEINFO, "on to perform copy of file data"), + AP_INIT_TAKE123("H2PushResource", h2_conf_add_push_res, NULL, + OR_FILEINFO, "add a resource to be pushed in this location/on this server."), AP_END_CMD }; diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h index 4dc5f6c9b9..08bde3b740 100644 --- a/modules/http2/h2_config.h +++ b/modules/http2/h2_config.h @@ -45,6 +45,12 @@ typedef enum { struct apr_hash_t; struct h2_priority; +struct h2_push_res; + +typedef struct h2_push_res { + const char *uri_ref; + int critical; +} h2_push_res; /* Apache httpd module configuration for h2. */ typedef struct h2_config { @@ -70,14 +76,14 @@ typedef struct h2_config { int push_diary_size; /* # of entries in push diary */ int copy_files; /* if files shall be copied vs setaside on output */ + apr_array_header_t *push_list;/* list of h2_push_res configurations */ } h2_config; void *h2_config_create_dir(apr_pool_t *pool, char *x); +void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv); void *h2_config_create_svr(apr_pool_t *pool, server_rec *s); -void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv); - -apr_status_t h2_config_apply_header(const h2_config *config, request_rec *r); +void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv); extern const command_rec h2_cmds[]; diff --git a/modules/http2/h2_from_h1.c b/modules/http2/h2_from_h1.c index cdb444650b..372e0bfc78 100644 --- a/modules/http2/h2_from_h1.c +++ b/modules/http2/h2_from_h1.c @@ -103,8 +103,7 @@ static void fix_vary(request_rec *r) * its comma-separated fieldname values, and then add them to varies * if not already present in the array. */ - apr_table_do((int (*)(void *, const char *, const char *))uniq_field_values, - (void *) varies, r->headers_out, "Vary", NULL); + apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL); /* If we found any, replace old Vary fields with unique-ified value */ @@ -275,8 +274,7 @@ static h2_headers *create_response(h2_task *task, request_rec *r) set_basic_http_header(headers, r, r->pool); if (r->status == HTTP_NOT_MODIFIED) { - apr_table_do((int (*)(void *, const char *, const char *)) copy_header, - (void *) headers, r->headers_out, + apr_table_do(copy_header, headers, r->headers_out, "ETag", "Content-Location", "Expires", @@ -290,8 +288,7 @@ static h2_headers *create_response(h2_task *task, request_rec *r) NULL); } else { - apr_table_do((int (*)(void *, const char *, const char *)) copy_header, - (void *) headers, r->headers_out, NULL); + apr_table_do(copy_header, headers, r->headers_out, NULL); } return h2_headers_rcreate(r, r->status, headers, r->pool); @@ -491,10 +488,13 @@ apr_status_t h2_from_h1_parse_response(h2_task *task, ap_filter_t *f, } else if (line[0] == '\0') { /* end of headers, pass response onward */ - + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, + "h2_task(%s): end of response", task->id); return pass_response(task, f, parser); } else { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, + "h2_task(%s): response header %s", task->id, line); status = parse_header(parser, line); } break; diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c index f810014638..8ca8ccb55f 100644 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@ -681,6 +681,31 @@ static int h2_h2_pre_close_conn(conn_rec *c) return DECLINED; } +static void check_push(request_rec *r, const char *tag) +{ + const h2_config *conf = h2_config_rget(r); + if (conf && conf->push_list && conf->push_list->nelts > 0) { + int i, old_status; + const char *old_line; + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "%s, early announcing %d resources for push", + tag, conf->push_list->nelts); + for (i = 0; i < conf->push_list->nelts; ++i) { + h2_push_res *push = &APR_ARRAY_IDX(conf->push_list, i, h2_push_res); + apr_table_addn(r->headers_out, "Link", + apr_psprintf(r->pool, "<%s>; rel=preload%s", + push->uri_ref, push->critical? "; critical" : "")); + } + old_status = r->status; + old_line = r->status_line; + r->status = 103; + r->status_line = "103 Early Hints"; + ap_send_interim_response(r, 1); + r->status = old_status; + r->status_line = old_line; + } +} + static int h2_h2_post_read_req(request_rec *r) { /* slave connection? */ @@ -691,7 +716,8 @@ static int h2_h2_post_read_req(request_rec *r) * that we manipulate filters only once. */ if (task && !task->filters_set) { ap_filter_t *f; - ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "adding request filters"); + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "h2_task(%s): adding request filters", task->id); /* setup the correct filters to process the request for h2 */ ap_add_input_filter("H2_REQUEST", task, r, r->connection); @@ -729,6 +755,7 @@ static int h2_h2_late_fixups(request_rec *r) "h2_slave_out(%s): copy_files on", task->id); h2_beam_on_file_beam(task->output.beam, h2_beam_no_files, NULL); } + check_push(r, "late_fixup"); } } return DECLINED; diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index 60f4f29adb..7150058558 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -512,10 +512,6 @@ static int task_print(void *ctx, void *val) (stream? 0 : 1), task->worker_started, task->worker_done, task->frozen); } - else if (task) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, /* NO APLOGNO */ - "->03198: h2_stream(%ld-%d): NULL", m->id, task->stream_id); - } else { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, /* NO APLOGNO */ "->03198: h2_stream(%ld-NULL): NULL", m->id); diff --git a/modules/http2/h2_proxy_session.c b/modules/http2/h2_proxy_session.c index 59ae9d48e2..9624bd1f2b 100644 --- a/modules/http2/h2_proxy_session.c +++ b/modules/http2/h2_proxy_session.c @@ -36,6 +36,8 @@ typedef struct h2_proxy_stream { const char *url; request_rec *r; h2_proxy_request *req; + const char *real_server_uri; + const char *p_server_uri; int standalone; h2_stream_state_t state; @@ -161,7 +163,17 @@ static int on_frame_recv(nghttp2_session *ngh2, const nghttp2_frame *frame, ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_proxy_session(%s): got interim HEADERS, status=%d", session->id, r->status); - r->status_line = ap_get_status_line(r->status); + switch(r->status) { + case 103: + /* workaround until we get this into http protocol base + * parts. without this, unknown codes are converted to + * 500... */ + r->status_line = "103 Early Hints"; + break; + default: + r->status_line = ap_get_status_line(r->status); + break; + } ap_send_interim_response(r, 1); } stream->waiting_on_100 = 0; @@ -216,8 +228,9 @@ static int add_header(void *table, const char *n, const char *v) return 1; } -static void process_proxy_header(request_rec *r, const char *n, const char *v) +static void process_proxy_header(h2_proxy_stream *stream, const char *n, const char *v) { + request_rec *r = stream->r; static const struct { const char *name; ap_proxy_header_reverse_map_fn func; @@ -240,6 +253,13 @@ static void process_proxy_header(request_rec *r, const char *n, const char *v) return; } } + if (!ap_cstr_casecmp("Link", n)) { + dconf = ap_get_module_config(r->per_dir_config, &proxy_module); + apr_table_add(r->headers_out, n, + h2_proxy_link_reverse_map(r, dconf, + stream->real_server_uri, stream->p_server_uri, v)); + return; + } apr_table_add(r->headers_out, n, v); } @@ -274,7 +294,7 @@ static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream, ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, "h2_proxy_stream(%s-%d): got header %s: %s", stream->session->id, stream->id, hname, hvalue); - process_proxy_header(stream->r, hname, hvalue); + process_proxy_header(stream, hname, hvalue); } return APR_SUCCESS; } @@ -689,6 +709,10 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url, /* port info missing and port is not default for scheme: append */ authority = apr_psprintf(stream->pool, "%s:%d", authority, puri.port); } + /* we need this for mapping relative uris in headers ("Link") back + * to local uris */ + stream->real_server_uri = apr_psprintf(stream->pool, "%s://%s", scheme, authority); + stream->p_server_uri = apr_psprintf(stream->pool, "%s://%s", puri.scheme, authority); path = apr_uri_unparse(stream->pool, &puri, APR_URI_UNP_OMITSITEPART); h2_proxy_req_make(stream->req, stream->pool, r->method, scheme, authority, path, r->headers_in); diff --git a/modules/http2/h2_proxy_util.c b/modules/http2/h2_proxy_util.c index 8089dde5e5..8e1231c642 100644 --- a/modules/http2/h2_proxy_util.c +++ b/modules/http2/h2_proxy_util.c @@ -14,18 +14,22 @@ */ #include +#include #include #include #include #include #include +#include #include #include "h2.h" #include "h2_proxy_util.h" +APLOG_USE_MODULE(proxy_http2); + /* h2_log2(n) iff n is a power of 2 */ unsigned char h2_proxy_log2(int n) { @@ -701,3 +705,351 @@ int h2_proxy_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t m frame->hd.flags, frame->hd.stream_id); } } + +/******************************************************************************* + * link header handling + ******************************************************************************/ + +typedef struct { + apr_pool_t *pool; + request_rec *r; + proxy_dir_conf *conf; + const char *s; + int slen; + int i; + const char *server_uri; + int su_len; + const char *real_backend_uri; + int rbu_len; + const char *p_server_uri; + int psu_len; + int link_start; + int link_end; +} link_ctx; + +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 (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 int skip_qstring(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, '\"')) { + size_t end; + if (find_chr(ctx, '\"', &end)) { + ctx->i = end + 1; + return 1; + } + } + return 0; +} + +static int skip_ptoken(link_ctx *ctx) +{ + 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) { + ctx->i = i; + return 1; + } + } + return 0; +} + + +static int read_link(link_ctx *ctx) +{ + ctx->link_start = ctx->link_end = 0; + if (skip_ws(ctx) && read_chr(ctx, '<')) { + size_t end; + if (find_chr(ctx, '>', &end)) { + ctx->link_start = ctx->i; + ctx->link_end = end; + ctx->i = end + 1; + return 1; + } + } + return 0; +} + +static int skip_pname(link_ctx *ctx) +{ + if (skip_ws(ctx)) { + int i; + for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) { + /* nop */ + } + if (i > ctx->i) { + ctx->i = i; + return 1; + } + } + return 0; +} + +static int skip_pvalue(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, '=')) { + if (skip_qstring(ctx) || skip_ptoken(ctx)) { + return 1; + } + } + return 0; +} + +static int skip_param(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, ';')) { + if (skip_pname(ctx)) { + skip_pvalue(ctx); /* value is optional */ + return 1; + } + } + return 0; +} + +static int read_sep(link_ctx *ctx) +{ + if (skip_ws(ctx) && read_chr(ctx, ',')) { + return 1; + } + return 0; +} + +static size_t subst_str(link_ctx *ctx, int start, int end, const char *ns) +{ + int olen, nlen, plen; + int delta; + char *p; + + olen = end - start; + nlen = strlen(ns); + delta = nlen - olen; + plen = ctx->slen + delta + 1; + p = apr_pcalloc(ctx->pool, plen); + strncpy(p, ctx->s, start); + strncpy(p + start, ns, nlen); + strcpy(p + start + nlen, ctx->s + end); + ctx->s = p; + ctx->slen = strlen(p); + if (ctx->i >= end) { + ctx->i += delta; + } + return nlen; +} + +static void map_link(link_ctx *ctx) +{ + if (ctx->link_start < ctx->link_end) { + char buffer[HUGE_STRING_LEN]; + int need_len, link_len, buffer_len, prepend_p_server; + const char *mapped; + + buffer[0] = '\0'; + buffer_len = 0; + link_len = ctx->link_end - ctx->link_start; + need_len = link_len + 1; + prepend_p_server = (ctx->s[ctx->link_start] == '/'); + if (prepend_p_server) { + /* common to use relative uris in link header, for mappings + * to work need to prefix the backend server uri */ + need_len += ctx->psu_len; + strncpy(buffer, ctx->p_server_uri, sizeof(buffer)); + buffer_len = ctx->psu_len; + } + if (need_len > sizeof(buffer)) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ctx->r, APLOGNO(03482) + "link_reverse_map uri too long, skipped: %s", ctx->s); + return; + } + strncpy(buffer + buffer_len, ctx->s + ctx->link_start, link_len); + buffer_len += link_len; + buffer[buffer_len] = '\0'; + if (!prepend_p_server + && strcmp(ctx->real_backend_uri, ctx->p_server_uri) + && !strncmp(buffer, ctx->real_backend_uri, ctx->rbu_len)) { + /* the server uri and our local proxy uri we use differ, for mapping + * to work, we need to use the proxy uri */ + int path_start = ctx->link_start + ctx->rbu_len; + link_len -= ctx->rbu_len; + strcpy(buffer, ctx->p_server_uri); + strncpy(buffer + ctx->psu_len, ctx->s + path_start, link_len); + buffer_len = ctx->psu_len + link_len; + buffer[buffer_len] = '\0'; + } + mapped = ap_proxy_location_reverse_map(ctx->r, ctx->conf, buffer); + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, ctx->r, + "reverse_map[%s] %s --> %s", ctx->p_server_uri, buffer, mapped); + if (mapped != buffer) { + if (prepend_p_server) { + if (ctx->server_uri == NULL) { + ctx->server_uri = ap_construct_url(ctx->pool, "", ctx->r); + ctx->su_len = strlen(ctx->server_uri); + } + if (!strncmp(mapped, ctx->server_uri, ctx->su_len)) { + mapped += ctx->su_len; + } + } + subst_str(ctx, ctx->link_start, ctx->link_end, mapped); + } + } +} + +/* RFC 5988 + Link = "Link" ":" #link-value + link-value = "<" URI-Reference ">" *( ";" link-param ) + link-param = ( ( "rel" "=" relation-types ) + | ( "anchor" "=" <"> URI-Reference <"> ) + | ( "rev" "=" relation-types ) + | ( "hreflang" "=" Language-Tag ) + | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) + | ( "title" "=" quoted-string ) + | ( "title*" "=" ext-value ) + | ( "type" "=" ( media-type | quoted-mt ) ) + | ( link-extension ) ) + link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) + | ( ext-name-star "=" ext-value ) + ext-name-star = parmname "*" ; reserved for RFC2231-profiled + ; extensions. Whitespace NOT + ; allowed in between. + ptoken = 1*ptokenchar + ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" + | ")" | "*" | "+" | "-" | "." | "/" | DIGIT + | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA + | "[" | "]" | "^" | "_" | "`" | "{" | "|" + | "}" | "~" + media-type = type-name "/" subtype-name + quoted-mt = <"> media-type <"> + relation-types = relation-type + | <"> relation-type *( 1*SP relation-type ) <"> + 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 + / "!" / "#" / "$" / "&" / "+" / "-" / "." + / "^" / "_" / "`" / "|" / "~" + */ + +const char *h2_proxy_link_reverse_map(request_rec *r, + proxy_dir_conf *conf, + const char *real_backend_uri, + const char *proxy_server_uri, + const char *s) +{ + link_ctx ctx; + + if (r->proxyreq != PROXYREQ_REVERSE) { + return s; + } + memset(&ctx, 0, sizeof(ctx)); + ctx.r = r; + ctx.pool = r->pool; + ctx.conf = conf; + ctx.real_backend_uri = real_backend_uri; + ctx.rbu_len = strlen(ctx.real_backend_uri); + ctx.p_server_uri = proxy_server_uri; + ctx.psu_len = strlen(ctx.p_server_uri); + ctx.s = s; + ctx.slen = strlen(s); + while (read_link(&ctx)) { + while (skip_param(&ctx)) { + /* nop */ + } + map_link(&ctx); + if (!read_sep(&ctx)) { + break; + } + } + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "link_reverse_map %s --> %s", s, ctx.s); + return ctx.s; +} diff --git a/modules/http2/h2_proxy_util.h b/modules/http2/h2_proxy_util.h index 06185e4d7d..f90d14951b 100644 --- a/modules/http2/h2_proxy_util.h +++ b/modules/http2/h2_proxy_util.h @@ -192,6 +192,13 @@ apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool, const char *authority, const char *path, apr_table_t *headers); - +/******************************************************************************* + * reverse mapping for link headers + ******************************************************************************/ +const char *h2_proxy_link_reverse_map(request_rec *r, + proxy_dir_conf *conf, + const char *real_server_uri, + const char *proxy_server_uri, + const char *s); #endif /* defined(__mod_h2__h2_proxy_util__) */ diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c index 455b7f0600..4330d7a4dc 100644 --- a/modules/http2/h2_push.c +++ b/modules/http2/h2_push.c @@ -353,7 +353,11 @@ static int add_push(link_ctx *ctx) /* atm, we do not push on pushes */ h2_request_end_headers(req, ctx->pool, 1); push->req = req; - + if (has_param(ctx, "critical")) { + h2_priority *prio = apr_pcalloc(ctx->pool, sizeof(*prio)); + prio->dependency = H2_DEPENDANT_BEFORE; + push->priority = prio; + } if (!ctx->pushes) { ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*)); } diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h index d4c8a7a012..eb122ebf86 100644 --- a/modules/http2/h2_push.h +++ b/modules/http2/h2_push.h @@ -25,6 +25,7 @@ struct h2_stream; typedef struct h2_push { const struct h2_request *req; + h2_priority *priority; } h2_push; typedef enum { diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index f743f4814c..5cb63d00b4 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -1233,6 +1233,7 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, stream = h2_session_open_stream(session, nid, is->id, push->req); if (stream) { + h2_session_set_prio(session, stream, push->priority); status = stream_schedule(session, stream, 1); if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c, @@ -1271,6 +1272,10 @@ apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream, #ifdef H2_NG2_CHANGE_PRIO nghttp2_stream *s_grandpa, *s_parent, *s; + if (prio == NULL) { + /* we treat this as a NOP */ + return APR_SUCCESS; + } s = nghttp2_session_find_stream(session->ngh2, stream->id); if (!s) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, @@ -1437,7 +1442,6 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, nghttp2_data_provider provider, *pprovider = NULL; h2_ngheader *ngh; apr_table_t *hout; - const h2_priority *prio; const char *note; ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03073) @@ -1454,7 +1458,7 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, /* If 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 response HTTP status is not sth >= 400, * and the remote side has pushing enabled, * -> find and perform any pushes on this stream * *before* we submit the stream response itself. @@ -1462,23 +1466,24 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, * headers that get pushed right afterwards. * * *) 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 + * make pushes on 401 or 403 codes 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 (!stream->initiated_on - && h2_headers_are_response(headers) - && H2_HTTP_2XX(headers->status) + && !stream->has_response + && (headers->status < 400) + && (headers->status != 304) && h2_session_push_enabled(session)) { h2_stream_submit_pushes(stream, headers); } - prio = h2_stream_get_priority(stream, headers); - if (prio) { - h2_session_set_prio(session, stream, prio); + if (!stream->pref_priority) { + stream->pref_priority = h2_stream_get_priority(stream, headers); } + h2_session_set_prio(session, stream, stream->pref_priority); hout = headers->headers; note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE); diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h index 57fdbba04c..adb419e516 100644 --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@ -67,6 +67,7 @@ struct h2_stream { unsigned int push_policy; /* which push policy to use for this request */ unsigned int can_be_cleaned : 1; /* stream pool can be cleaned */ + const h2_priority *pref_priority; /* preferred priority for this stream */ apr_off_t out_data_frames; /* # of DATA frames sent */ apr_off_t out_data_octets; /* # of DATA octets (payload) sent */ apr_off_t in_data_frames; /* # of DATA frames received */ diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h index 1b81038aa7..ef185663a4 100644 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@ -26,7 +26,7 @@ * @macro * Version number of the http2 module as c string */ -#define MOD_HTTP2_VERSION "1.7.9" +#define MOD_HTTP2_VERSION "1.8.0" /** * @macro @@ -34,7 +34,7 @@ * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ -#define MOD_HTTP2_VERSION_NUM 0x010709 +#define MOD_HTTP2_VERSION_NUM 0x010800 #endif /* mod_h2_h2_version_h */ diff --git a/modules/http2/mod_http2.c b/modules/http2/mod_http2.c index 7452cd7c2b..e0a4b90883 100644 --- a/modules/http2/mod_http2.c +++ b/modules/http2/mod_http2.c @@ -48,9 +48,9 @@ static void h2_hooks(apr_pool_t *pool); AP_DECLARE_MODULE(http2) = { STANDARD20_MODULE_STUFF, h2_config_create_dir, /* func to create per dir config */ - h2_config_merge, + h2_config_merge_dir, /* func to merge per dir config */ h2_config_create_svr, /* func to create per server config */ - h2_config_merge, /* func to merge per server config */ + h2_config_merge_svr, /* func to merge per server config */ h2_cmds, /* command handlers */ h2_hooks }; diff --git a/modules/metadata/mod_headers.c b/modules/metadata/mod_headers.c index 210b2bbb57..1ea970d780 100644 --- a/modules/metadata/mod_headers.c +++ b/modules/metadata/mod_headers.c @@ -666,13 +666,15 @@ static const char *process_regexp(header_entry *hdr, const char *value, return ret; } -static int echo_header(echo_do *v, const char *key, const char *val) +static int echo_header(void *v, const char *key, const char *val) { + edit_do *ed = v; + /* If the input header (key) matches the regex, echo it intact to * r->headers_out. */ - if (!ap_regexec(v->hdr->regex, key, 0, NULL, 0)) { - apr_table_add(v->r->headers_out, key, val); + if (!ap_regexec(ed->hdr->regex, key, 0, NULL, 0)) { + apr_table_add(ed->r->headers_out, key, val); } return 1; @@ -808,8 +810,7 @@ static int do_headers_fixup(request_rec *r, apr_table_t *headers, case hdr_echo: v.r = r; v.hdr = hdr; - apr_table_do((int (*) (void *, const char *, const char *)) - echo_header, (void *) &v, r->headers_in, NULL); + apr_table_do(echo_header, &v, r->headers_in, NULL); break; case hdr_edit: case hdr_edit_r: diff --git a/modules/session/mod_session.c b/modules/session/mod_session.c index 0b472a240e..ff4c9a6f2d 100644 --- a/modules/session/mod_session.c +++ b/modules/session/mod_session.c @@ -299,15 +299,16 @@ static apr_status_t ap_session_set(request_rec * r, session_rec * z, return APR_SUCCESS; } -static int identity_count(int *count, const char *key, const char *val) +static int identity_count(void *v, const char *key, const char *val) { + int *count = v; *count += strlen(key) * 3 + strlen(val) * 3 + 1; return 1; } -static int identity_concat(char *buffer, const char *key, const char *val) +static int identity_concat(void *v, const char *key, const char *val) { - char *slider = buffer; + char *slider = v; int length = strlen(slider); slider += length; if (length) { @@ -344,11 +345,9 @@ static apr_status_t session_identity_encode(request_rec * r, session_rec * z) char *expiry = apr_psprintf(z->pool, "%" APR_INT64_T_FMT, z->expiry); apr_table_setn(z->entries, SESSION_EXPIRY, expiry); } - apr_table_do((int (*) (void *, const char *, const char *)) - identity_count, &length, z->entries, NULL); + apr_table_do(identity_count, &length, z->entries, NULL); buffer = apr_pcalloc(r->pool, length + 1); - apr_table_do((int (*) (void *, const char *, const char *)) - identity_concat, buffer, z->entries, NULL); + apr_table_do(identity_concat, buffer, z->entries, NULL); z->encoded = buffer; return OK; diff --git a/server/util_cookies.c b/server/util_cookies.c index 5139aecd4c..82a514fcc6 100644 --- a/server/util_cookies.c +++ b/server/util_cookies.c @@ -174,8 +174,9 @@ AP_DECLARE(apr_status_t) ap_cookie_remove2(request_rec * r, const char *name2, c * $path or other attributes following our cookie if present. If we end * up with an empty cookie, remove the whole header. */ -static int extract_cookie_line(ap_cookie_do * v, const char *key, const char *val) +static int extract_cookie_line(void *varg, const char *key, const char *val) { + ap_cookie_do *v = varg; char *last1, *last2; char *cookie = apr_pstrdup(v->r->pool, val); const char *name = apr_pstrcat(v->r->pool, v->name ? v->name : "", "=", NULL); @@ -252,8 +253,7 @@ AP_DECLARE(apr_status_t) ap_cookie_read(request_rec * r, const char *name, const v.duplicated = 0; v.name = name; - apr_table_do((int (*) (void *, const char *, const char *)) - extract_cookie_line, (void *) &v, r->headers_in, + apr_table_do(extract_cookie_line, &v, r->headers_in, "Cookie", "Cookie2", NULL); if (v.duplicated) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00011) LOG_PREFIX -- 2.40.0