--- /dev/null
- 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) {
+/* 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 <assert.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_vhost.h>
+
+#include <ap_mpm.h>
+
+#include <apr_strings.h>
+
+#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;
+ }
++ else if (ctx->server) {
+ ctx->config = h2_config_sget(ctx->server);
+ return ctx->config;
+ }
+
+ return h2_config_sget(c->base_server);
+}
+
--- /dev/null
+/* 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 <assert.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+
+#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;
+}
--- /dev/null
+/* 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__) */
--- /dev/null
+/* 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 <assert.h>
+
+#include <apr_strings.h>
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_log.h>
+
+#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);
+}
+
--- /dev/null
- env.serialize_headers = !!h2_config_geti(cfg, H2_CONF_SER_HEADERS);
+/* 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 <assert.h>
+#include <stddef.h>
+
+#include <apr_atomic.h>
+#include <apr_thread_cond.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+#include <http_vhost.h>
+#include <util_filter.h>
+#include <ap_mpm.h>
+#include <mod_core.h>
+#include <scoreboard.h>
+
+#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;
- r->protocol = (char*)"HTTP/1.1";
- r->proto_num = HTTP_VERSION(1, 1);
++ 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/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;
+}
+
+
+
+
--- /dev/null
+/* 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 <assert.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_connection.h>
+
+#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;
+}
+