-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
*
- * 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
*
- * 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 <nghttp2/nghttp2.h>
+#include <ap_mmn.h>
#include <httpd.h>
#include <mod_proxy.h>
#include "mod_http2.h"
#include "mod_proxy_http2.h"
-#include "h2_request.h"
-#include "h2_util.h"
+#include "h2.h"
+#include "h2_proxy_util.h"
#include "h2_version.h"
#include "h2_proxy_session.h"
+#define H2MIN(x,y) ((x) < (y) ? (x) : (y))
+
static void register_hook(apr_pool_t *p);
AP_DECLARE_MODULE(proxy_http2) = {
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
NULL, /* command apr_table_t */
- register_hook /* register hooks */
+ register_hook, /* register hooks */
+#if defined(AP_MODULE_FLAG_NONE)
+ AP_MODULE_FLAG_ALWAYS_MERGE
+#endif
};
/* Optional functions from mod_http2 */
http2_req_engine_init *einit);
static apr_status_t (*req_engine_pull)(h2_req_engine *engine,
apr_read_type_e block,
- apr_uint32_t capacity,
+ int capacity,
request_rec **pr);
-static void (*req_engine_done)(h2_req_engine *engine, conn_rec *r_conn);
+static void (*req_engine_done)(h2_req_engine *engine, conn_rec *r_conn,
+ apr_status_t status);
typedef struct h2_proxy_ctx {
conn_rec *owner;
const char *engine_id;
const char *engine_type;
apr_pool_t *engine_pool;
- apr_uint32_t req_buffer_size;
- request_rec *next;
- apr_size_t capacity;
+ apr_size_t req_buffer_size;
+ h2_proxy_fifo *requests;
+ int capacity;
unsigned standalone : 1;
unsigned is_ssl : 1;
apr_port_t port, def_port;
/* ap_port_of_scheme() */
- if (ap_casecmpstrn(url, "h2c:", 4) == 0) {
+ if (ap_cstr_casecmpn(url, "h2c:", 4) == 0) {
url += 4;
scheme = "h2c";
http_scheme = "http";
}
- else if (ap_casecmpstrn(url, "h2:", 3) == 0) {
+ else if (ap_cstr_casecmpn(url, "h2:", 3) == 0) {
url += 3;
scheme = "h2";
http_scheme = "https";
path = url; /* this is the raw path */
}
else {
- path = ap_proxy_canonenc(r->pool, url, strlen(url),
+ path = ap_proxy_canonenc(r->pool, url, (int)strlen(url),
enc_path, 0, r->proxyreq);
search = r->args;
}
const char *id,
const char *type,
apr_pool_t *pool,
- apr_uint32_t req_buffer_size,
+ apr_size_t req_buffer_size,
request_rec *r,
http2_output_consumed **pconsumed,
void **pctx)
{
h2_proxy_ctx *ctx = ap_get_module_config(r->connection->conn_config,
&proxy_http2_module);
- if (ctx) {
- conn_rec *c = ctx->owner;
- h2_proxy_ctx *nctx;
-
- /* we need another lifetime for this. If we do not host
- * an engine, the context lives in r->pool. Since we expect
- * to server more than r, we need to live longer */
- nctx = apr_pcalloc(pool, sizeof(*nctx));
- if (nctx == NULL) {
- return APR_ENOMEM;
- }
- memcpy(nctx, ctx, sizeof(*nctx));
- ctx = nctx;
- ctx->pool = pool;
- ctx->engine = engine;
- ctx->engine_id = id;
- ctx->engine_type = type;
- ctx->engine_pool = pool;
- ctx->req_buffer_size = req_buffer_size;
- ctx->capacity = 100;
-
- ap_set_module_config(c->conn_config, &proxy_http2_module, ctx);
-
- *pconsumed = out_consumed;
- *pctx = ctx;
- return APR_SUCCESS;
+ if (!ctx) {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03368)
+ "h2_proxy_session, engine init, no ctx found");
+ return APR_ENOTIMPL;
}
- ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03368)
- "h2_proxy_session, engine init, no ctx found");
- return APR_ENOTIMPL;
+
+ ctx->pool = pool;
+ ctx->engine = engine;
+ ctx->engine_id = id;
+ ctx->engine_type = type;
+ ctx->engine_pool = pool;
+ ctx->req_buffer_size = req_buffer_size;
+ ctx->capacity = H2MIN(100, h2_proxy_fifo_capacity(ctx->requests));
+
+ *pconsumed = out_consumed;
+ *pctx = ctx;
+ return APR_SUCCESS;
}
static apr_status_t add_request(h2_proxy_session *session, request_rec *r)
url = apr_table_get(r->notes, H2_PROXY_REQ_URL_NOTE);
apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
ctx->p_conn->connection->local_addr->port));
- status = h2_proxy_session_submit(session, url, r);
- if (status != OK) {
+ status = h2_proxy_session_submit(session, url, r, ctx->standalone);
+ if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, r->connection, APLOGNO(03351)
"pass request body failed to %pI (%s) from %s (%s)",
ctx->p_conn->addr, ctx->p_conn->hostname ?
return status;
}
-static void request_done(h2_proxy_session *session, request_rec *r,
- int complete, int touched)
+static void request_done(h2_proxy_ctx *ctx, request_rec *r,
+ apr_status_t status, int touched)
{
- h2_proxy_ctx *ctx = session->user_data;
const char *task_id = apr_table_get(r->connection->notes, H2_TASK_ID_NOTE);
-
- if (!complete && !touched) {
- /* untouched request, need rescheduling */
- if (req_engine_push && is_h2 && is_h2(ctx->owner)) {
- if (req_engine_push(ctx->engine_type, r, NULL) == APR_SUCCESS) {
- /* push to engine */
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection,
- APLOGNO(03369)
- "h2_proxy_session(%s): rescheduled request %s",
- ctx->engine_id, task_id);
- return;
- }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, r->connection,
+ "h2_proxy_session(%s): request done %s, touched=%d",
+ ctx->engine_id, task_id, touched);
+ if (status != APR_SUCCESS) {
+ if (!touched) {
+ /* untouched request, need rescheduling */
+ status = h2_proxy_fifo_push(ctx->requests, r);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection,
+ APLOGNO(03369)
+ "h2_proxy_session(%s): rescheduled request %s",
+ ctx->engine_id, task_id);
+ return;
+ }
+ else {
+ const char *uri;
+ uri = apr_uri_unparse(r->pool, &r->parsed_uri, 0);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection,
+ APLOGNO(03471) "h2_proxy_session(%s): request %s -> %s "
+ "not complete, cannot repeat",
+ ctx->engine_id, task_id, uri);
}
}
- if (r == ctx->rbase && complete) {
- ctx->r_status = APR_SUCCESS;
+ if (r == ctx->rbase) {
+ ctx->r_status = ((status == APR_SUCCESS)? APR_SUCCESS
+ : HTTP_SERVICE_UNAVAILABLE);
}
- if (complete) {
- if (req_engine_done && ctx->engine) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection,
- APLOGNO(03370)
- "h2_proxy_session(%s): finished request %s",
- ctx->engine_id, task_id);
- req_engine_done(ctx->engine, r->connection);
- }
- }
- else {
- if (req_engine_done && ctx->engine) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection,
- APLOGNO(03371)
- "h2_proxy_session(%s): failed request %s",
- ctx->engine_id, task_id);
- req_engine_done(ctx->engine, r->connection);
- }
+ if (req_engine_done && ctx->engine) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection,
+ APLOGNO(03370)
+ "h2_proxy_session(%s): finished request %s",
+ ctx->engine_id, task_id);
+ req_engine_done(ctx->engine, r->connection, status);
}
}
+static void session_req_done(h2_proxy_session *session, request_rec *r,
+ apr_status_t status, int touched)
+{
+ request_done(session->user_data, r, status, touched);
+}
+
static apr_status_t next_request(h2_proxy_ctx *ctx, int before_leave)
{
- if (ctx->next) {
+ if (h2_proxy_fifo_count(ctx->requests) > 0) {
return APR_SUCCESS;
}
else if (req_engine_pull && ctx->engine) {
apr_status_t status;
+ request_rec *r = NULL;
+
status = req_engine_pull(ctx->engine, before_leave?
APR_BLOCK_READ: APR_NONBLOCK_READ,
- ctx->capacity, &ctx->next);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, ctx->owner,
- "h2_proxy_engine(%s): pulled request %s",
- ctx->engine_id,
- (ctx->next? ctx->next->the_request : "NULL"));
+ ctx->capacity, &r);
+ if (status == APR_SUCCESS && r) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, ctx->owner,
+ "h2_proxy_engine(%s): pulled request (%s) %s",
+ ctx->engine_id,
+ before_leave? "before leave" : "regular",
+ r->the_request);
+ h2_proxy_fifo_push(ctx->requests, r);
+ }
return APR_STATUS_IS_EAGAIN(status)? APR_SUCCESS : status;
}
return APR_EOF;
static apr_status_t proxy_engine_run(h2_proxy_ctx *ctx) {
apr_status_t status = OK;
+ int h2_front;
+ request_rec *r;
/* Step Four: Send the Request in a new HTTP/2 stream and
* loop until we got the response or encounter errors.
*/
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->owner,
"eng(%s): setup session", ctx->engine_id);
- ctx->session = h2_proxy_session_setup(ctx->engine_id, ctx->p_conn, ctx->conf,
- 30, h2_log2(ctx->req_buffer_size),
- request_done);
+ h2_front = is_h2? is_h2(ctx->owner) : 0;
+ ctx->session = h2_proxy_session_setup(ctx->engine_id, ctx->p_conn, ctx->conf,
+ h2_front, 30,
+ h2_proxy_log2((int)ctx->req_buffer_size),
+ session_req_done);
if (!ctx->session) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner,
APLOGNO(03372) "session unavailable");
"eng(%s): run session %s", ctx->engine_id, ctx->session->id);
ctx->session->user_data = ctx;
- while (1) {
- if (ctx->next) {
- add_request(ctx->session, ctx->next);
- ctx->next = NULL;
+ while (!ctx->owner->aborted) {
+ if (APR_SUCCESS == h2_proxy_fifo_try_pull(ctx->requests, (void**)&r)) {
+ add_request(ctx->session, r);
}
status = h2_proxy_session_process(ctx->session);
/* ongoing processing, call again */
if (ctx->session->remote_max_concurrent > 0
&& ctx->session->remote_max_concurrent != ctx->capacity) {
- ctx->capacity = ctx->session->remote_max_concurrent;
+ ctx->capacity = H2MIN((int)ctx->session->remote_max_concurrent,
+ h2_proxy_fifo_capacity(ctx->requests));
}
s2 = next_request(ctx, 0);
if (s2 == APR_ECONNABORTED) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, s2, ctx->owner,
APLOGNO(03374) "eng(%s): pull request",
ctx->engine_id);
- status = s2;
+ /* give notice that we're leaving and cancel all ongoing
+ * streams. */
+ next_request(ctx, 1);
+ h2_proxy_session_cancel_all(ctx->session);
+ h2_proxy_session_process(ctx->session);
+ status = ctx->r_status = APR_SUCCESS;
break;
}
- if (!ctx->next && h2_ihash_is_empty(ctx->session->streams)) {
+ if ((h2_proxy_fifo_count(ctx->requests) == 0)
+ && h2_proxy_ihash_empty(ctx->session->streams)) {
break;
}
}
else {
/* end of processing, maybe error */
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
- APLOGNO(03375) "eng(%s): end of session run",
- ctx->engine_id);
+ APLOGNO(03375) "eng(%s): end of session %s",
+ ctx->engine_id, ctx->session->id);
/*
* Any open stream of that session needs to
* a) be reopened on the new session iff safe to do so
* b) reported as done (failed) otherwise
*/
- h2_proxy_session_cleanup(ctx->session, request_done);
+ h2_proxy_session_cleanup(ctx->session, session_req_done);
break;
}
}
return status;
}
-static h2_proxy_ctx *push_request_somewhere(h2_proxy_ctx *ctx)
+static apr_status_t push_request_somewhere(h2_proxy_ctx *ctx, request_rec *r)
{
conn_rec *c = ctx->owner;
const char *engine_type, *hostname;
engine_type = apr_psprintf(ctx->pool, "proxy_http2 %s%s", hostname,
ctx->server_portstr);
- if (c->master && req_engine_push && ctx->next && is_h2 && is_h2(c)) {
+ if (c->master && req_engine_push && r && is_h2 && is_h2(c)) {
/* If we are have req_engine capabilities, push the handling of this
* request (e.g. slave connection) to a proxy_http2 engine which
* uses the same backend. We may be called to create an engine
* ourself. */
- if (req_engine_push(engine_type, ctx->next, proxy_engine_init)
- == APR_SUCCESS) {
- /* to renew the lifetime, we might have set a new ctx */
- ctx = ap_get_module_config(c->conn_config, &proxy_http2_module);
+ if (req_engine_push(engine_type, r, proxy_engine_init) == APR_SUCCESS) {
if (ctx->engine == NULL) {
- /* Another engine instance has taken over processing of this
- * request. */
- ctx->r_status = SUSPENDED;
- ctx->next = NULL;
- return ctx;
+ /* request has been assigned to an engine in another thread */
+ return SUSPENDED;
}
}
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"H2: hosting engine %s", ctx->engine_id);
}
- return ctx;
+
+ return h2_proxy_fifo_push(ctx->requests, r);
}
static int proxy_http2_handler(request_rec *r,
apr_status_t status;
h2_proxy_ctx *ctx;
apr_uri_t uri;
- int reconnected = 0;
+ int reconnects = 0;
/* find the scheme */
if ((url[0] != 'h' && url[0] != 'H') || url[1] != '2') {
default:
return DECLINED;
}
+
ctx = apr_pcalloc(r->pool, sizeof(*ctx));
ctx->owner = r->connection;
ctx->pool = r->pool;
ctx->conf = conf;
ctx->flushall = apr_table_get(r->subprocess_env, "proxy-flushall")? 1 : 0;
ctx->r_status = HTTP_SERVICE_UNAVAILABLE;
- ctx->next = r;
- r = NULL;
+
+ h2_proxy_fifo_set_create(&ctx->requests, ctx->pool, 100);
+
ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, ctx);
/* scheme says, this is for us. */
}
ctx->p_conn->is_ssl = ctx->is_ssl;
- if (ctx->is_ssl) {
- /* If there is still some data on an existing ssl connection, now
- * would be a good timne to get rid of it. */
- ap_proxy_ssl_connection_cleanup(ctx->p_conn, ctx->rbase);
- }
/* Step One: Determine the URL to connect to (might be a proxy),
* initialize the backend accordingly and determine the server
/* If we are not already hosting an engine, try to push the request
* to an already existing engine or host a new engine here. */
- if (!ctx->engine) {
- ctx = push_request_somewhere(ctx);
+ if (r && !ctx->engine) {
+ ctx->r_status = push_request_somewhere(ctx, r);
+ r = NULL;
if (ctx->r_status == SUSPENDED) {
- /* request was pushed to another engine */
+ /* request was pushed to another thread, leave processing here */
goto cleanup;
}
}
* backend->hostname. */
if (ap_proxy_connect_backend(ctx->proxy_func, ctx->p_conn, ctx->worker,
ctx->server)) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctx->owner, APLOGNO(03352)
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03352)
"H2: failed to make connection to backend: %s",
ctx->p_conn->hostname);
- goto cleanup;
+ goto reconnect;
}
/* Step Three: Create conn_rec for the socket we have open now. */
if (!ctx->p_conn->connection) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
- "setup new connection: is_ssl=%d %s %s %s",
- ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname,
- locurl, ctx->p_conn->hostname);
- if ((status = ap_proxy_connection_create(ctx->proxy_func, ctx->p_conn,
- ctx->owner,
- ctx->server)) != OK) {
- goto cleanup;
- }
-
- /*
- * On SSL connections set a note on the connection what CN is
- * requested, such that mod_ssl can check if it is requested to do
- * so.
- */
- if (ctx->p_conn->ssl_hostname) {
- apr_table_setn(ctx->p_conn->connection->notes,
- "proxy-request-hostname", ctx->p_conn->ssl_hostname);
+ status = ap_proxy_connection_create_ex(ctx->proxy_func,
+ ctx->p_conn, ctx->rbase);
+ if (status != OK) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
+ "setup new connection: is_ssl=%d %s %s %s",
+ ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname,
+ locurl, ctx->p_conn->hostname);
+ goto reconnect;
}
- if (ctx->is_ssl) {
+ if (!ctx->p_conn->data && ctx->is_ssl) {
+ /* New SSL connection: set a note on the connection about what
+ * protocol we want.
+ */
apr_table_setn(ctx->p_conn->connection->notes,
"proxy-request-alpn-protos", "h2");
}
ctx->engine = NULL;
}
-cleanup:
- if (!reconnected && ctx->engine && next_request(ctx, 1) == APR_SUCCESS) {
+reconnect:
+ if (next_request(ctx, 1) == APR_SUCCESS) {
/* Still more to do, tear down old conn and start over */
if (ctx->p_conn) {
ctx->p_conn->close = 1;
+#if AP_MODULE_MAGIC_AT_LEAST(20140207, 2)
proxy_run_detach_backend(r, ctx->p_conn);
+#endif
ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
ctx->p_conn = NULL;
}
- reconnected = 1; /* we do this only once, then fail */
- goto run_connect;
+ ++reconnects;
+ if (reconnects < 5 && !ctx->owner->aborted) {
+ goto run_connect;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(10023)
+ "giving up after %d reconnects, %d requests todo",
+ reconnects, h2_proxy_fifo_count(ctx->requests));
}
+cleanup:
if (ctx->p_conn) {
if (status != APR_SUCCESS) {
/* close socket when errors happened or session shut down (EOF) */
ctx->p_conn->close = 1;
}
+#if AP_MODULE_MAGIC_AT_LEAST(20140207, 2)
proxy_run_detach_backend(ctx->rbase, ctx->p_conn);
+#endif
ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
ctx->p_conn = NULL;
}
+ /* Any requests will still have need to fail */
+ while (APR_SUCCESS == h2_proxy_fifo_try_pull(ctx->requests, (void**)&r)) {
+ request_done(ctx, r, HTTP_SERVICE_UNAVAILABLE, 1);
+ }
+
ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, NULL);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
APLOGNO(03377) "leaving handler");