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 <valentin.bremond gmail.com>, Yann Ylavic]
</usage>
</directivesynopsis>
+ <directivesynopsis>
+ <name>H2PushResource</name>
+ <description>Declares resources for early pushing to the client</description>
+ <syntax>H2PushResource [add] path [critical]</syntax>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ <context>directory</context>
+ <context>.htaccess</context>
+ </contextlist>
+ <compatibility>Available in version 2.4.24 and later.</compatibility>
+
+ <usage>
+ <p>
+ 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.
+ </p>
+ <p>
+ This directive pushes resources much earlier than adding
+ <code>Link</code> headers via <module>mod_headers</module>.
+ <module>mod_http2</module> announces these resources in a
+ <code>103 Early Hints</code> interim response to the client.
+ That means that clients not supporting PUSH will still get
+ early preload hints.
+ </p>
+ <p>
+ In contrast to setting <code>Link</code> response headers
+ via <module>mod_headers</module>, this directive will only
+ take effect on HTTP/2 connections.
+ </p>
+ <p>
+ By adding <code>critical</code> 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.
+ </p>
+ </usage>
+ </directivesynopsis>
</modulesynopsis>
* 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 */
/* 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]))
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);
conf->priorities = NULL;
conf->push_diary_size = DEF_VAL;
conf->copy_files = DEF_VAL;
-
+ conf->push_list = NULL;
return conf;
}
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;
}
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);
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[] = {
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
};
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 {
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[];
* 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 */
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",
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);
}
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;
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? */
* 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);
"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;
(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);
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;
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;
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;
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);
}
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;
}
/* 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);
*/
#include <assert.h>
+#include <apr_lib.h>
#include <apr_strings.h>
#include <httpd.h>
#include <http_core.h>
#include <http_log.h>
#include <http_request.h>
+#include <mod_proxy.h>
#include <nghttp2/nghttp2.h>
#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)
{
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 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
+ 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 <https://tools.ietf.org/html/rfc5987>
+ 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;
+}
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__) */
/* 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*));
}
typedef struct h2_push {
const struct h2_request *req;
+ h2_priority *priority;
} h2_push;
typedef enum {
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,
#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,
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)
/* 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.
* 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);
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 */
* @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
* 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 */
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
};
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;
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:
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) {
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;
* $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);
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