From: Stefan Eissing Date: Mon, 14 Sep 2015 12:38:53 +0000 (+0000) Subject: merge of current 2.4.x and change 1702919 X-Git-Tag: 2.4.17~79^2~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0b5ebcfce8b2cf68d3936ed6169f56b9957a69f7;p=thirdparty%2Fapache%2Fhttpd.git merge of current 2.4.x and change 1702919 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.17-protocols-http2@1702925 13f79535-47bb-0310-9956-ffa450edef68 --- 0b5ebcfce8b2cf68d3936ed6169f56b9957a69f7 diff --cc modules/http2/h2_config.c index 883f273a38f,00000000000..a370bd620c3 mode 100644,000000..100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@@ -1,416 -1,0 +1,386 @@@ +/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include "h2_alt_svc.h" +#include "h2_ctx.h" +#include "h2_conn.h" +#include "h2_config.h" +#include "h2_private.h" + +#define DEF_VAL (-1) + +#define H2_CONFIG_GET(a, b, n) \ + (((a)->n == DEF_VAL)? (b) : (a))->n + +static h2_config defconf = { + "default", + 100, /* max_streams */ + 64 * 1024, /* window_size */ + -1, /* min workers */ + -1, /* max workers */ + 10 * 60, /* max workers idle secs */ + 64 * 1024, /* stream max mem size */ + NULL, /* no alt-svcs */ + -1, /* alt-svc max age */ + 0, /* serialize headers */ + -1, /* h2 direct mode */ + -1, /* # session extra files */ +}; + +static int files_per_session = 0; + +void h2_config_init(apr_pool_t *pool) { + /* Determine a good default for this platform and mpm? + * TODO: not sure how APR wants to hand out this piece of + * information. + */ + int max_files = 256; + int conn_threads = 1; + int tx_files = max_files / 4; + + (void)pool; + ap_mpm_query(AP_MPMQ_MAX_THREADS, &conn_threads); + switch (h2_conn_mpm_type()) { + case H2_MPM_PREFORK: + case H2_MPM_WORKER: + case H2_MPM_EVENT: + /* allow that many transfer open files per mplx */ + files_per_session = (tx_files / conn_threads); + break; + default: + /* don't know anything about it, stay safe */ + break; + } +} + +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_pcalloc(pool, strlen(prefix) + strlen(s) + 20); + strcpy(name, prefix); + strcat(name, "["); + strcat(name, s); + strcat(name, "]"); + + conf->name = name; + conf->h2_max_streams = DEF_VAL; + conf->h2_window_size = DEF_VAL; + conf->min_workers = DEF_VAL; + conf->max_workers = DEF_VAL; + conf->max_worker_idle_secs = DEF_VAL; + conf->stream_max_mem_size = DEF_VAL; + conf->alt_svc_max_age = DEF_VAL; + conf->serialize_headers = DEF_VAL; + conf->h2_direct = DEF_VAL; + conf->session_extra_files = DEF_VAL; + return conf; +} + +void *h2_config_create_svr(apr_pool_t *pool, server_rec *s) +{ + return h2_config_create(pool, "srv", s->defn_name); +} + +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) +{ + 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_pcalloc(pool, 20 + strlen(add->name) + strlen(base->name)); + strcpy(name, "merged["); + strcat(name, add->name); + strcat(name, ", "); + strcat(name, base->name); + strcat(name, "]"); + n->name = name; + + n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams); + n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size); + n->min_workers = H2_CONFIG_GET(add, base, min_workers); + n->max_workers = H2_CONFIG_GET(add, base, max_workers); + n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs); + n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size); + n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs; + n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age); + n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers); + n->h2_direct = H2_CONFIG_GET(add, base, h2_direct); + n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files); + + return n; +} + +int h2_config_geti(h2_config *conf, h2_config_var_t var) +{ + int n; + switch(var) { + case H2_CONF_MAX_STREAMS: + return H2_CONFIG_GET(conf, &defconf, h2_max_streams); + case H2_CONF_WIN_SIZE: + return H2_CONFIG_GET(conf, &defconf, h2_window_size); + case H2_CONF_MIN_WORKERS: + return H2_CONFIG_GET(conf, &defconf, min_workers); + case H2_CONF_MAX_WORKERS: + return H2_CONFIG_GET(conf, &defconf, max_workers); + case H2_CONF_MAX_WORKER_IDLE_SECS: + return H2_CONFIG_GET(conf, &defconf, max_worker_idle_secs); + case H2_CONF_STREAM_MAX_MEM: + return H2_CONFIG_GET(conf, &defconf, stream_max_mem_size); + case H2_CONF_ALT_SVC_MAX_AGE: + return H2_CONFIG_GET(conf, &defconf, alt_svc_max_age); + case H2_CONF_SER_HEADERS: + return H2_CONFIG_GET(conf, &defconf, serialize_headers); + case H2_CONF_DIRECT: + return H2_CONFIG_GET(conf, &defconf, h2_direct); + case H2_CONF_SESSION_FILES: + n = H2_CONFIG_GET(conf, &defconf, session_extra_files); + if (n < 0) { + n = files_per_session; + } + return n; + default: + return DEF_VAL; + } +} + +h2_config *h2_config_sget(server_rec *s) +{ + h2_config *cfg = (h2_config *)ap_get_module_config(s->module_config, + &h2_module); + AP_DEBUG_ASSERT(cfg); + return cfg; +} + + +static const char *h2_conf_set_max_streams(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + cfg->h2_max_streams = (int)apr_atoi64(value); + (void)arg; + if (cfg->h2_max_streams < 1) { + return "value must be > 0"; + } + return NULL; +} + +static const char *h2_conf_set_window_size(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + cfg->h2_window_size = (int)apr_atoi64(value); + (void)arg; + if (cfg->h2_window_size < 1024) { + return "value must be > 1k"; + } + return NULL; +} + +static const char *h2_conf_set_min_workers(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + cfg->min_workers = (int)apr_atoi64(value); + (void)arg; + if (cfg->min_workers < 1) { + return "value must be > 1"; + } + return NULL; +} + +static const char *h2_conf_set_max_workers(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + cfg->max_workers = (int)apr_atoi64(value); + (void)arg; + if (cfg->max_workers < 1) { + return "value must be > 1"; + } + return NULL; +} + +static const char *h2_conf_set_max_worker_idle_secs(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + cfg->max_worker_idle_secs = (int)apr_atoi64(value); + (void)arg; + if (cfg->max_worker_idle_secs < 1) { + return "value must be > 1"; + } + return NULL; +} + +static const char *h2_conf_set_stream_max_mem_size(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + + + cfg->stream_max_mem_size = (int)apr_atoi64(value); + (void)arg; + if (cfg->stream_max_mem_size < 1024) { + return "value must be > 1k"; + } + return NULL; +} + +static const char *h2_add_alt_svc(cmd_parms *parms, + void *arg, const char *value) +{ + if (value && strlen(value)) { + h2_config *cfg = h2_config_sget(parms->server); + h2_alt_svc *as = h2_alt_svc_parse(value, parms->pool); + if (!as) { + return "unable to parse alt-svc specifier"; + } + if (!cfg->alt_svcs) { + cfg->alt_svcs = apr_array_make(parms->pool, 5, sizeof(h2_alt_svc*)); + } + APR_ARRAY_PUSH(cfg->alt_svcs, h2_alt_svc*) = as; + } + (void)arg; + return NULL; +} + +static const char *h2_conf_set_alt_svc_max_age(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + cfg->alt_svc_max_age = (int)apr_atoi64(value); + (void)arg; + return NULL; +} + +static const char *h2_conf_set_session_extra_files(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + apr_int64_t max = (int)apr_atoi64(value); + if (max <= 0) { + return "value must be a positive number"; + } + cfg->session_extra_files = (int)max; + (void)arg; + return NULL; +} + +static const char *h2_conf_set_serialize_headers(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + if (!strcasecmp(value, "On")) { + cfg->serialize_headers = 1; + return NULL; + } + else if (!strcasecmp(value, "Off")) { + cfg->serialize_headers = 0; + return NULL; + } + + (void)arg; + return "value must be On or Off"; +} + +static const char *h2_conf_set_direct(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = h2_config_sget(parms->server); + if (!strcasecmp(value, "On")) { + cfg->h2_direct = 1; + return NULL; + } + else if (!strcasecmp(value, "Off")) { + cfg->h2_direct = 0; + return NULL; + } + + (void)arg; + return "value must be On or Off"; +} + +#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) + + +const command_rec h2_cmds[] = { + AP_INIT_TAKE1("H2MaxSessionStreams", h2_conf_set_max_streams, NULL, + RSRC_CONF, "maximum number of open streams per session"), + AP_INIT_TAKE1("H2WindowSize", h2_conf_set_window_size, NULL, + RSRC_CONF, "window size on client DATA"), + AP_INIT_TAKE1("H2MinWorkers", h2_conf_set_min_workers, NULL, + RSRC_CONF, "minimum number of worker threads per child"), + AP_INIT_TAKE1("H2MaxWorkers", h2_conf_set_max_workers, NULL, + RSRC_CONF, "maximum number of worker threads per child"), + AP_INIT_TAKE1("H2MaxWorkerIdleSeconds", h2_conf_set_max_worker_idle_secs, NULL, + RSRC_CONF, "maximum number of idle seconds before a worker shuts down"), + AP_INIT_TAKE1("H2StreamMaxMemSize", h2_conf_set_stream_max_mem_size, NULL, + RSRC_CONF, "maximum number of bytes buffered in memory for a stream"), + AP_INIT_TAKE1("H2AltSvc", h2_add_alt_svc, NULL, + RSRC_CONF, "adds an Alt-Svc for this server"), + AP_INIT_TAKE1("H2AltSvcMaxAge", h2_conf_set_alt_svc_max_age, NULL, + RSRC_CONF, "set the maximum age (in seconds) that client can rely on alt-svc information"), + AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL, + RSRC_CONF, "on to enable header serialization for compatibility"), + AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL, + RSRC_CONF, "on to enable direct HTTP/2 mode"), + AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL, + RSRC_CONF, "number of extra file a session might keep open"), + AP_END_CMD +}; + + +h2_config *h2_config_rget(request_rec *r) +{ + h2_config *cfg = (h2_config *)ap_get_module_config(r->per_dir_config, + &h2_module); + return cfg? cfg : h2_config_sget(r->server); +} + +h2_config *h2_config_get(conn_rec *c) +{ + h2_ctx *ctx = h2_ctx_get(c); ++ + if (ctx->config) { + return ctx->config; + } - if (!ctx->server && ctx->hostname) { - /* We have a host agreed upon via TLS SNI, but no request yet. - * The sni host was accepted and therefore does match a server record - * (vhost) for it. But we need to know which one. - * Normally, it is enough to be set on the initial request on a - * connection, but we need it earlier. Simulate a request and call - * the vhost matching stuff. - */ - apr_uri_t uri; - request_rec r; - memset(&uri, 0, sizeof(uri)); - uri.scheme = (char*)"https"; - uri.hostinfo = (char*)ctx->hostname; - uri.hostname = (char*)ctx->hostname; - uri.port_str = (char*)""; - uri.port = c->local_addr->port; - uri.path = (char*)"/"; - - memset(&r, 0, sizeof(r)); - r.uri = (char*)"/"; - r.connection = c; - r.pool = c->pool; - r.hostname = ctx->hostname; - r.headers_in = apr_table_make(c->pool, 1); - r.parsed_uri = uri; - r.status = HTTP_OK; - r.server = r.connection->base_server; - ap_update_vhost_from_headers(&r); - ctx->server = r.server; - } - - if (ctx->server) { ++ else if (ctx->server) { + ctx->config = h2_config_sget(ctx->server); + return ctx->config; + } + + return h2_config_sget(c->base_server); +} + diff --cc modules/http2/h2_ctx.c index 0e198456f23,00000000000..f37a1474a1e mode 100644,000000..100644 --- a/modules/http2/h2_ctx.c +++ b/modules/http2/h2_ctx.c @@@ -1,84 -1,0 +1,90 @@@ +/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "h2_private.h" +#include "h2_task.h" +#include "h2_ctx.h" +#include "h2_private.h" + +static h2_ctx *h2_ctx_create(const conn_rec *c) +{ + h2_ctx *ctx = apr_pcalloc(c->pool, sizeof(h2_ctx)); + AP_DEBUG_ASSERT(ctx); + ap_set_module_config(c->conn_config, &h2_module, ctx); + return ctx; +} + +h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task_env *env) +{ + h2_ctx *ctx = h2_ctx_create(c); + if (ctx) { + ctx->task_env = env; + } + return ctx; +} + +h2_ctx *h2_ctx_get(const conn_rec *c) +{ + h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &h2_module); + if (ctx == NULL) { + ctx = h2_ctx_create(c); + } + return ctx; +} + +h2_ctx *h2_ctx_rget(const request_rec *r) +{ + return h2_ctx_get(r->connection); +} + +const char *h2_ctx_protocol_get(const conn_rec *c) +{ + h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &h2_module); + return ctx? ctx->protocol : NULL; +} + +h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto) +{ + ctx->protocol = proto; + ctx->is_h2 = (proto != NULL); + return ctx; +} + ++h2_ctx *h2_ctx_server_set(h2_ctx *ctx, server_rec *s) ++{ ++ ctx->server = s; ++ return ctx; ++} ++ +int h2_ctx_is_task(h2_ctx *ctx) +{ + return ctx && !!ctx->task_env; +} + +int h2_ctx_is_active(h2_ctx *ctx) +{ + return ctx && ctx->is_h2; +} + +struct h2_task_env *h2_ctx_get_task(h2_ctx *ctx) +{ + return ctx->task_env; +} diff --cc modules/http2/h2_ctx.h index f9bc8278296,00000000000..86c59206eda mode 100644,000000..100644 --- a/modules/http2/h2_ctx.h +++ b/modules/http2/h2_ctx.h @@@ -1,59 -1,0 +1,63 @@@ +/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __mod_h2__h2_ctx__ +#define __mod_h2__h2_ctx__ + +struct h2_task_env; +struct h2_config; + +/** + * The h2 module context associated with a connection. + * + * It keeps track of the different types of connections: + * - those from clients that use HTTP/2 protocol + * - those from clients that do not use HTTP/2 + * - those created by ourself to perform work on HTTP/2 streams + */ +typedef struct h2_ctx { + int is_h2; /* h2 engine is used */ + const char *protocol; /* the protocol negotiated */ + struct h2_task_env *task_env; /* the h2_task environment or NULL */ + const char *hostname; /* hostname negotiated via SNI, optional */ + server_rec *server; /* httpd server config selected. */ + struct h2_config *config; /* effective config in this context */ +} h2_ctx; + +h2_ctx *h2_ctx_get(const conn_rec *c); +h2_ctx *h2_ctx_rget(const request_rec *r); +h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task_env *env); + + +/* Set the h2 protocol established on this connection context or + * NULL when other protocols are in place. + */ +h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto); + ++/* Set the server_rec relevant for this context. ++ */ ++h2_ctx *h2_ctx_server_set(h2_ctx *ctx, server_rec *s); ++ +/** + * Get the h2 protocol negotiated for this connection, or NULL. + */ +const char *h2_ctx_protocol_get(const conn_rec *c); + +int h2_ctx_is_task(h2_ctx *ctx); +int h2_ctx_is_active(h2_ctx *ctx); + +struct h2_task_env *h2_ctx_get_task(h2_ctx *ctx); + +#endif /* defined(__mod_h2__h2_ctx__) */ diff --cc modules/http2/h2_switch.c index 4cdc4118b0a,00000000000..23c34490eaa mode 100644,000000..100644 --- a/modules/http2/h2_switch.c +++ b/modules/http2/h2_switch.c @@@ -1,180 -1,0 +1,181 @@@ +/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "h2_private.h" + +#include "h2_config.h" +#include "h2_ctx.h" +#include "h2_conn.h" +#include "h2_h2.h" +#include "h2_switch.h" + +/******************************************************************************* + * SSL var lookup + */ +APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup, + (apr_pool_t *, server_rec *, + conn_rec *, request_rec *, + char *)); +static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *, + conn_rec *, request_rec *, + char *); + +/******************************************************************************* + * Once per lifetime init, retrieve optional functions + */ +apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s) +{ + (void)pool; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_switch init"); + opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); + + return APR_SUCCESS; +} + +static int h2_protocol_propose(conn_rec *c, request_rec *r, + server_rec *s, + const apr_array_header_t *offers, + apr_array_header_t *proposals) +{ + int proposed = 0; + const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos; + + (void)s; + if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) { + /* We do not know how to switch from anything else but http/1.1. + */ + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, + "protocol switch: current proto != http/1.1, declined"); + return DECLINED; + } + + if (r) { + const char *p; + /* So far, this indicates an HTTP/1 Upgrade header initiated + * protocol switch. For that, the HTTP2-Settings header needs + * to be present and valid for the connection. + */ + p = apr_table_get(r->headers_in, "HTTP2-Settings"); + if (!p) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "upgrade without HTTP2-Settings declined"); + return DECLINED; + } + + p = apr_table_get(r->headers_in, "Connection"); + if (!ap_find_token(r->pool, p, "http2-settings")) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "upgrade without HTTP2-Settings declined"); + return DECLINED; + } + + /* We also allow switching only for requests that have no body. + */ + p = apr_table_get(r->headers_in, "Content-Length"); + if (p && strcmp(p, "0")) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "upgrade with content-length: %s, declined", p); + return DECLINED; + } + } + + while (*protos) { + /* Add all protocols we know (tls or clear) and that + * are part of the offerings (if there have been any). + */ + if (!offers || ap_array_str_contains(offers, *protos)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "proposing protocol '%s'", *protos); + APR_ARRAY_PUSH(proposals, const char*) = *protos; + proposed = 1; + } + ++protos; + } + return proposed? DECLINED : OK; +} + +static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, + const char *protocol) +{ + int found = 0; + const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos; + const char **p = protos; + + (void)s; + while (*p) { + if (!strcmp(*p, protocol)) { + found = 1; + break; + } + p++; + } + + if (found) { + h2_ctx *ctx = h2_ctx_get(c); + + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "switching protocol to '%s'", protocol); + h2_ctx_protocol_set(ctx, protocol); ++ h2_ctx_server_set(ctx, s); + + if (r != NULL) { + apr_status_t status; + /* Switching in the middle of a request means that + * we have to send out the response to this one in h2 + * format. So we need to take over the connection + * right away. + */ + ap_remove_input_filter_byhandle(r->input_filters, "http_in"); + ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout"); + + /* Ok, start an h2_conn on this one. */ + status = h2_conn_rprocess(r); + if (status != DONE) { + /* Nothing really to do about this. */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, + "session proessed, unexpected status"); + } + } + return DONE; + } + + return DECLINED; +} + +static const char *h2_protocol_get(const conn_rec *c) +{ + return h2_ctx_protocol_get(c); +} + +void h2_switch_register_hooks(void) +{ + ap_hook_protocol_propose(h2_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_protocol_switch(h2_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_protocol_get(h2_protocol_get, NULL, NULL, APR_HOOK_MIDDLE); +} + diff --cc modules/http2/h2_task.c index 26b8c6e40e8,00000000000..bbea7b20f8a mode 100644,000000..100644 --- a/modules/http2/h2_task.c +++ b/modules/http2/h2_task.c @@@ -1,467 -1,0 +1,467 @@@ +/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "h2_private.h" +#include "h2_conn.h" +#include "h2_config.h" +#include "h2_from_h1.h" +#include "h2_h2.h" +#include "h2_mplx.h" +#include "h2_session.h" +#include "h2_stream.h" +#include "h2_task_input.h" +#include "h2_task_output.h" +#include "h2_task.h" +#include "h2_ctx.h" +#include "h2_worker.h" + + +static apr_status_t h2_filter_stream_input(ap_filter_t* filter, + apr_bucket_brigade* brigade, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) { + h2_task_env *env = filter->ctx; + AP_DEBUG_ASSERT(env); + if (!env->input) { + return APR_ECONNABORTED; + } + return h2_task_input_read(env->input, filter, brigade, + mode, block, readbytes); +} + +static apr_status_t h2_filter_stream_output(ap_filter_t* filter, + apr_bucket_brigade* brigade) { + h2_task_env *env = filter->ctx; + AP_DEBUG_ASSERT(env); + if (!env->output) { + return APR_ECONNABORTED; + } + return h2_task_output_write(env->output, filter, brigade); +} + +static apr_status_t h2_filter_read_response(ap_filter_t* f, + apr_bucket_brigade* bb) { + h2_task_env *env = f->ctx; + AP_DEBUG_ASSERT(env); + if (!env->output || !env->output->from_h1) { + return APR_ECONNABORTED; + } + return h2_from_h1_read_response(env->output->from_h1, f, bb); +} + +/******************************************************************************* + * Register various hooks + */ +static const char *const mod_ssl[] = { "mod_ssl.c", NULL}; +static int h2_task_pre_conn(conn_rec* c, void *arg); +static int h2_task_process_conn(conn_rec* c); + +void h2_task_register_hooks(void) +{ + /* This hook runs on new connections before mod_ssl has a say. + * Its purpose is to prevent mod_ssl from touching our pseudo-connections + * for streams. + */ + ap_hook_pre_connection(h2_task_pre_conn, + NULL, mod_ssl, APR_HOOK_FIRST); + /* When the connection processing actually starts, we might to + * take over, if the connection is for a task. + */ + ap_hook_process_connection(h2_task_process_conn, + NULL, NULL, APR_HOOK_FIRST); + + ap_register_output_filter("H2_RESPONSE", h2_response_output_filter, + NULL, AP_FTYPE_PROTOCOL); + ap_register_input_filter("H2_TO_H1", h2_filter_stream_input, + NULL, AP_FTYPE_NETWORK); + ap_register_output_filter("H1_TO_H2", h2_filter_stream_output, + NULL, AP_FTYPE_NETWORK); + ap_register_output_filter("H1_TO_H2_RESP", h2_filter_read_response, + NULL, AP_FTYPE_PROTOCOL); +} + +static int h2_task_pre_conn(conn_rec* c, void *arg) +{ + + h2_ctx *ctx = h2_ctx_get(c); + + (void)arg; + if (h2_ctx_is_task(ctx)) { + h2_task_env *env = h2_ctx_get_task(ctx); + + /* This connection is a pseudo-connection used for a h2_task. + * Since we read/write directly from it ourselves, we need + * to disable a possible ssl connection filter. + */ + h2_tls_disable(c); + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "h2_h2, pre_connection, found stream task"); + + /* Add our own, network level in- and output filters. + */ + ap_add_input_filter("H2_TO_H1", env, NULL, c); + ap_add_output_filter("H1_TO_H2", env, NULL, c); + } + return OK; +} + +static int h2_task_process_conn(conn_rec* c) +{ + h2_ctx *ctx = h2_ctx_get(c); + + if (h2_ctx_is_task(ctx)) { + if (!ctx->task_env->serialize_headers) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "h2_h2, processing request directly"); + h2_task_process_request(ctx->task_env); + return DONE; + } + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "h2_task(%s), serialized handling", ctx->task_env->id); + } + return DECLINED; +} + + +h2_task *h2_task_create(long session_id, + int stream_id, + apr_pool_t *stream_pool, + h2_mplx *mplx, conn_rec *c) +{ + h2_task *task = apr_pcalloc(stream_pool, sizeof(h2_task)); + if (task == NULL) { + ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, stream_pool, + APLOGNO(02941) "h2_task(%ld-%d): create stream task", + session_id, stream_id); + h2_mplx_out_close(mplx, stream_id); + return NULL; + } + + APR_RING_ELEM_INIT(task, link); + + task->id = apr_psprintf(stream_pool, "%ld-%d", session_id, stream_id); + task->stream_id = stream_id; + task->mplx = mplx; + + task->c = c; + + ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, stream_pool, + "h2_task(%s): created", task->id); + return task; +} + +void h2_task_set_request(h2_task *task, + const char *method, + const char *scheme, + const char *authority, + const char *path, + apr_table_t *headers, int eos) +{ + task->method = method; + task->scheme = scheme; + task->authority = authority; + task->path = path; + task->headers = headers; + task->input_eos = eos; +} + +apr_status_t h2_task_destroy(h2_task *task) +{ + (void)task; + return APR_SUCCESS; +} + +apr_status_t h2_task_do(h2_task *task, h2_worker *worker) +{ + apr_status_t status = APR_SUCCESS; + h2_config *cfg = h2_config_get(task->mplx->c); + h2_task_env env; + + AP_DEBUG_ASSERT(task); + + memset(&env, 0, sizeof(env)); + + env.id = task->id; + env.stream_id = task->stream_id; + env.mplx = task->mplx; + task->mplx = NULL; + + env.input_eos = task->input_eos; - env.serialize_headers = !!h2_config_geti(cfg, H2_CONF_SER_HEADERS); ++ env.serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS); + + /* Create a subpool from the worker one to be used for all things + * with life-time of this task_env execution. + */ + apr_pool_create(&env.pool, h2_worker_get_pool(worker)); + + /* Link the env to the worker which provides useful things such + * as mutex, a socket etc. */ + env.io = h2_worker_get_cond(worker); + + /* Clone fields, so that lifetimes become (more) independent. */ + env.method = apr_pstrdup(env.pool, task->method); + env.scheme = apr_pstrdup(env.pool, task->scheme); + env.authority = apr_pstrdup(env.pool, task->authority); + env.path = apr_pstrdup(env.pool, task->path); + env.headers = apr_table_clone(env.pool, task->headers); + + /* Setup the pseudo connection to use our own pool and bucket_alloc */ + env.c = *task->c; + task->c = NULL; + status = h2_conn_setup(&env, worker); + + /* save in connection that this one is a pseudo connection, prevents + * other hooks from messing with it. */ + h2_ctx_create_for(&env.c, &env); + + if (status == APR_SUCCESS) { + env.input = h2_task_input_create(&env, env.pool, + env.c.bucket_alloc); + env.output = h2_task_output_create(&env, env.pool, + env.c.bucket_alloc); + status = h2_conn_process(&env.c, h2_worker_get_socket(worker)); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, &env.c, + "h2_task(%s): processing done", env.id); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c, + APLOGNO(02957) "h2_task(%s): error setting up h2_task_env", + env.id); + } + + if (env.input) { + h2_task_input_destroy(env.input); + env.input = NULL; + } + + if (env.output) { + h2_task_output_close(env.output); + h2_task_output_destroy(env.output); + env.output = NULL; + } + + h2_task_set_finished(task); + if (env.io) { + apr_thread_cond_signal(env.io); + } + + if (env.pool) { + apr_pool_destroy(env.pool); + env.pool = NULL; + } + + if (env.c.id) { + h2_conn_post(&env.c, worker); + } + + h2_mplx_task_done(env.mplx, env.stream_id); + + return status; +} + +int h2_task_has_started(h2_task *task) +{ + AP_DEBUG_ASSERT(task); + return apr_atomic_read32(&task->has_started); +} + +void h2_task_set_started(h2_task *task) +{ + AP_DEBUG_ASSERT(task); + apr_atomic_set32(&task->has_started, 1); +} + +int h2_task_has_finished(h2_task *task) +{ + return apr_atomic_read32(&task->has_finished); +} + +void h2_task_set_finished(h2_task *task) +{ + apr_atomic_set32(&task->has_finished, 1); +} + +void h2_task_die(h2_task_env *env, int status, request_rec *r) +{ + (void)env; + ap_die(status, r); +} + +static request_rec *h2_task_create_request(h2_task_env *env) +{ + conn_rec *conn = &env->c; + request_rec *r; + apr_pool_t *p; + int access_status = HTTP_OK; + + apr_pool_create(&p, conn->pool); + apr_pool_tag(p, "request"); + r = apr_pcalloc(p, sizeof(request_rec)); + AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn); + r->pool = p; + r->connection = conn; + r->server = conn->base_server; + + r->user = NULL; + r->ap_auth_type = NULL; + + r->allowed_methods = ap_make_method_list(p, 2); + + r->headers_in = apr_table_copy(r->pool, env->headers); + r->trailers_in = apr_table_make(r->pool, 5); + r->subprocess_env = apr_table_make(r->pool, 25); + r->headers_out = apr_table_make(r->pool, 12); + r->err_headers_out = apr_table_make(r->pool, 5); + r->trailers_out = apr_table_make(r->pool, 5); + r->notes = apr_table_make(r->pool, 5); + + r->request_config = ap_create_request_config(r->pool); + /* Must be set before we run create request hook */ + + r->proto_output_filters = conn->output_filters; + r->output_filters = r->proto_output_filters; + r->proto_input_filters = conn->input_filters; + r->input_filters = r->proto_input_filters; + ap_run_create_request(r); + r->per_dir_config = r->server->lookup_defaults; + + r->sent_bodyct = 0; /* bytect isn't for body */ + + r->read_length = 0; + r->read_body = REQUEST_NO_BODY; + + r->status = HTTP_OK; /* Until further notice */ + r->header_only = 0; + r->the_request = NULL; + + /* Begin by presuming any module can make its own path_info assumptions, + * until some module interjects and changes the value. + */ + r->used_path_info = AP_REQ_DEFAULT_PATH_INFO; + + r->useragent_addr = conn->client_addr; + r->useragent_ip = conn->client_ip; + + ap_run_pre_read_request(r, conn); + + /* Time to populate r with the data we have. */ + r->request_time = apr_time_now(); + r->the_request = apr_psprintf(r->pool, "%s %s HTTP/1.1", + env->method, env->path); + r->method = env->method; + /* Provide quick information about the request method as soon as known */ + r->method_number = ap_method_number_of(r->method); + if (r->method_number == M_GET && r->method[0] == 'H') { + r->header_only = 1; + } + + ap_parse_uri(r, env->path); - r->protocol = (char*)"HTTP/1.1"; - r->proto_num = HTTP_VERSION(1, 1); ++ r->protocol = (char*)"HTTP/2"; ++ r->proto_num = HTTP_VERSION(2, 0); + + /* update what we think the virtual host is based on the headers we've + * now read. may update status. + * Leave r->hostname empty, vhost will parse if form our Host: header, + * otherwise we get complains about port numbers. + */ + r->hostname = NULL; + ap_update_vhost_from_headers(r); + + /* we may have switched to another server */ + r->per_dir_config = r->server->lookup_defaults; + + /* + * Add the HTTP_IN filter here to ensure that ap_discard_request_body + * called by ap_die and by ap_send_error_response works correctly on + * status codes that do not cause the connection to be dropped and + * in situations where the connection should be kept alive. + */ + ap_add_input_filter_handle(ap_http_input_filter_handle, + NULL, r, r->connection); + + if (access_status != HTTP_OK + || (access_status = ap_run_post_read_request(r))) { + /* Request check post hooks failed. An example of this would be a + * request for a vhost where h2 is disabled --> 421. + */ + h2_task_die(env, access_status, r); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + r = NULL; + goto traceout; + } + + AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, + (char *)r->uri, (char *)r->server->defn_name, + r->status); + return r; +traceout: + AP_READ_REQUEST_FAILURE((uintptr_t)r); + return r; +} + + +apr_status_t h2_task_process_request(h2_task_env *env) +{ + conn_rec *c = &env->c; + request_rec *r; + conn_state_t *cs = c->cs; + + r = h2_task_create_request(env); + if (r && (r->status == HTTP_OK)) { + ap_update_child_status(c->sbh, SERVER_BUSY_READ, r); + + if (cs) + cs->state = CONN_STATE_HANDLER; + ap_process_request(r); + /* After the call to ap_process_request, the + * request pool will have been deleted. We set + * r=NULL here to ensure that any dereference + * of r that might be added later in this function + * will result in a segfault immediately instead + * of nondeterministic failures later. + */ + r = NULL; + } + ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL); + c->sbh = NULL; + + return APR_SUCCESS; +} + + + + diff --cc modules/http2/h2_task_input.c index cc7d8503fd5,00000000000..487f7e60692 mode 100644,000000..100644 --- a/modules/http2/h2_task_input.c +++ b/modules/http2/h2_task_input.c @@@ -1,219 -1,0 +1,222 @@@ +/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include "h2_private.h" +#include "h2_conn.h" +#include "h2_mplx.h" +#include "h2_session.h" +#include "h2_stream.h" +#include "h2_task_input.h" +#include "h2_task.h" +#include "h2_util.h" + + +static int is_aborted(ap_filter_t *f) +{ + return (f->c->aborted); +} + +static int ser_header(void *ctx, const char *name, const char *value) +{ + h2_task_input *input = (h2_task_input*)ctx; + apr_brigade_printf(input->bb, NULL, NULL, "%s: %s\r\n", name, value); + return 1; +} + +h2_task_input *h2_task_input_create(h2_task_env *env, apr_pool_t *pool, + apr_bucket_alloc_t *bucket_alloc) +{ + h2_task_input *input = apr_pcalloc(pool, sizeof(h2_task_input)); + if (input) { + input->env = env; + input->bb = NULL; + + if (env->serialize_headers) { ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, &env->c, ++ "h2_task_input(%s): serialize request %s %s", ++ env->id, env->method, env->path); + input->bb = apr_brigade_create(pool, bucket_alloc); + apr_brigade_printf(input->bb, NULL, NULL, "%s %s HTTP/1.1\r\n", + env->method, env->path); + apr_table_do(ser_header, input, env->headers, NULL); + apr_brigade_puts(input->bb, NULL, NULL, "\r\n"); + if (input->env->input_eos) { + APR_BRIGADE_INSERT_TAIL(input->bb, apr_bucket_eos_create(bucket_alloc)); + } + } + else if (!input->env->input_eos) { + input->bb = apr_brigade_create(pool, bucket_alloc); + } + else { + /* We do not serialize and have eos already, no need to + * create a bucket brigade. */ + } + + if (APLOGcdebug(&env->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, &env->c, + "h2_task_input(%s): request is: %s", + env->id, buffer); + } + } + return input; +} + +void h2_task_input_destroy(h2_task_input *input) +{ + input->bb = NULL; +} + +apr_status_t h2_task_input_read(h2_task_input *input, + ap_filter_t* f, + apr_bucket_brigade* bb, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + apr_status_t status = APR_SUCCESS; + apr_off_t bblen = 0; + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, + "h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld", + input->env->id, block, mode, (long)readbytes); + + if (is_aborted(f)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, + "h2_task_input(%s): is aborted", + input->env->id); + return APR_ECONNABORTED; + } + + if (mode == AP_MODE_INIT) { + return APR_SUCCESS; + } + + if (input->bb) { + status = apr_brigade_length(input->bb, 1, &bblen); + if (status != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, f->c, + APLOGNO(02958) "h2_task_input(%s): brigade length fail", + input->env->id); + return status; + } + } + + if ((bblen == 0) && input->env->input_eos) { + return APR_EOF; + } + + while ((bblen == 0) || (mode == AP_MODE_READBYTES && bblen < readbytes)) { + /* Get more data for our stream from mplx. + */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, + "h2_task_input(%s): get more data from mplx, block=%d, " + "readbytes=%ld, queued=%ld", + input->env->id, block, + (long)readbytes, (long)bblen); + + /* Although we sometimes get called with APR_NONBLOCK_READs, + we seem to fill our buffer blocking. Otherwise we get EAGAIN, + return that to our caller and everyone throws up their hands, + never calling us again. */ + status = h2_mplx_in_read(input->env->mplx, APR_BLOCK_READ, + input->env->stream_id, input->bb, + input->env->io); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, + "h2_task_input(%s): mplx in read returned", + input->env->id); + if (status != APR_SUCCESS) { + return status; + } + status = apr_brigade_length(input->bb, 1, &bblen); + if (status != APR_SUCCESS) { + return status; + } + if ((bblen == 0) && (block == APR_NONBLOCK_READ)) { + return h2_util_has_eos(input->bb, 0)? APR_EOF : APR_EAGAIN; + } + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, + "h2_task_input(%s): mplx in read, %ld bytes in brigade", + input->env->id, (long)bblen); + } + + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, + "h2_task_input(%s): read, mode=%d, block=%d, " + "readbytes=%ld, queued=%ld", + input->env->id, mode, block, + (long)readbytes, (long)bblen); + + if (!APR_BRIGADE_EMPTY(input->bb)) { + if (mode == AP_MODE_EXHAUSTIVE) { + /* return all we have */ + return h2_util_move(bb, input->bb, readbytes, 0, + "task_input_read(exhaustive)"); + } + else if (mode == AP_MODE_READBYTES) { + return h2_util_move(bb, input->bb, readbytes, 0, + "task_input_read(readbytes)"); + } + else if (mode == AP_MODE_SPECULATIVE) { + /* return not more than was asked for */ + return h2_util_copy(bb, input->bb, readbytes, + "task_input_read(speculative)"); + } + else if (mode == AP_MODE_GETLINE) { + /* we are reading a single LF line, e.g. the HTTP headers */ + status = apr_brigade_split_line(bb, input->bb, block, + HUGE_STRING_LEN); + if (APLOGctrace1(f->c)) { + char buffer[1024]; + apr_size_t len = sizeof(buffer)-1; + apr_brigade_flatten(bb, buffer, &len); + buffer[len] = 0; + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, + "h2_task_input(%s): getline: %s", + input->env->id, buffer); + } + return status; + } + else { + /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not + * to support it. Seems to work. */ + ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c, + APLOGNO(02942) + "h2_task_input, unsupported READ mode %d", mode); + return APR_ENOTIMPL; + } + } + + if (is_aborted(f)) { + return APR_ECONNABORTED; + } + + return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF; +} +