1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include <nghttp2/nghttp2.h>
21 #include <mod_proxy.h>
22 #include "mod_http2.h"
25 #include "mod_proxy_http2.h"
27 #include "h2_proxy_util.h"
28 #include "h2_version.h"
29 #include "h2_proxy_session.h"
31 #define H2MIN(x,y) ((x) < (y) ? (x) : (y))
33 static void register_hook(apr_pool_t *p);
35 AP_DECLARE_MODULE(proxy_http2) = {
36 STANDARD20_MODULE_STUFF,
37 NULL, /* create per-directory config structure */
38 NULL, /* merge per-directory config structures */
39 NULL, /* create per-server config structure */
40 NULL, /* merge per-server config structures */
41 NULL, /* command apr_table_t */
42 register_hook, /* register hooks */
43 #if defined(AP_MODULE_FLAG_NONE)
44 AP_MODULE_FLAG_ALWAYS_MERGE
48 /* Optional functions from mod_http2 */
49 static int (*is_h2)(conn_rec *c);
50 static apr_status_t (*req_engine_push)(const char *name, request_rec *r,
51 http2_req_engine_init *einit);
52 static apr_status_t (*req_engine_pull)(h2_req_engine *engine,
53 apr_read_type_e block,
56 static void (*req_engine_done)(h2_req_engine *engine, conn_rec *r_conn,
59 typedef struct h2_proxy_ctx {
64 const char *proxy_func;
65 char server_portstr[32];
66 proxy_conn_rec *p_conn;
68 proxy_server_conf *conf;
70 h2_req_engine *engine;
71 const char *engine_id;
72 const char *engine_type;
73 apr_pool_t *engine_pool;
74 apr_size_t req_buffer_size;
75 h2_proxy_fifo *requests;
78 unsigned standalone : 1;
80 unsigned flushall : 1;
82 apr_status_t r_status; /* status of our first request work */
83 h2_proxy_session *session; /* current http2 session against backend */
86 static int h2_proxy_post_config(apr_pool_t *p, apr_pool_t *plog,
87 apr_pool_t *ptemp, server_rec *s)
90 const char *init_key = "mod_proxy_http2_init_counter";
92 apr_status_t status = APR_SUCCESS;
93 (void)plog;(void)ptemp;
95 apr_pool_userdata_get(&data, init_key, s->process->pool);
97 apr_pool_userdata_set((const void *)1, init_key,
98 apr_pool_cleanup_null, s->process->pool);
102 ngh2 = nghttp2_version(0);
103 ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03349)
104 "mod_proxy_http2 (v%s, nghttp2 %s), initializing...",
105 MOD_HTTP2_VERSION, ngh2? ngh2->version_str : "unknown");
107 is_h2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2);
108 req_engine_push = APR_RETRIEVE_OPTIONAL_FN(http2_req_engine_push);
109 req_engine_pull = APR_RETRIEVE_OPTIONAL_FN(http2_req_engine_pull);
110 req_engine_done = APR_RETRIEVE_OPTIONAL_FN(http2_req_engine_done);
112 /* we need all of them */
113 if (!req_engine_push || !req_engine_pull || !req_engine_done) {
114 req_engine_push = NULL;
115 req_engine_pull = NULL;
116 req_engine_done = NULL;
123 * canonicalize the url into the request, if it is meant for us.
124 * slightly modified copy from mod_http
126 static int proxy_http2_canon(request_rec *r, char *url)
128 char *host, *path, sport[7];
132 const char *http_scheme;
133 apr_port_t port, def_port;
135 /* ap_port_of_scheme() */
136 if (ap_cstr_casecmpn(url, "h2c:", 4) == 0) {
139 http_scheme = "http";
141 else if (ap_cstr_casecmpn(url, "h2:", 3) == 0) {
144 http_scheme = "https";
149 port = def_port = ap_proxy_port_of_scheme(http_scheme);
151 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
152 "HTTP2: canonicalising URL %s", url);
154 /* do syntatic check.
155 * We break the URL into host, port, path, search
157 err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
159 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03350)
160 "error parsing URL %s: %s", url, err);
161 return HTTP_BAD_REQUEST;
165 * now parse path/search args, according to rfc1738:
168 * In a reverse proxy, our URL has been processed, so canonicalise
169 * unless proxy-nocanon is set to say it's raw
170 * In a forward proxy, we have and MUST NOT MANGLE the original.
172 switch (r->proxyreq) {
173 default: /* wtf are we doing here? */
174 case PROXYREQ_REVERSE:
175 if (apr_table_get(r->notes, "proxy-nocanon")) {
176 path = url; /* this is the raw path */
179 path = ap_proxy_canonenc(r->pool, url, (int)strlen(url),
180 enc_path, 0, r->proxyreq);
190 return HTTP_BAD_REQUEST;
193 if (port != def_port) {
194 apr_snprintf(sport, sizeof(sport), ":%d", port);
200 if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
201 host = apr_pstrcat(r->pool, "[", host, "]", NULL);
203 r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
204 "/", path, (search) ? "?" : "", (search) ? search : "", NULL);
208 static void out_consumed(void *baton, conn_rec *c, apr_off_t bytes)
210 h2_proxy_ctx *ctx = baton;
213 h2_proxy_session_update_window(ctx->session, c, bytes);
217 static apr_status_t proxy_engine_init(h2_req_engine *engine,
221 apr_size_t req_buffer_size,
223 http2_output_consumed **pconsumed,
226 h2_proxy_ctx *ctx = ap_get_module_config(r->connection->conn_config,
227 &proxy_http2_module);
229 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03368)
230 "h2_proxy_session, engine init, no ctx found");
235 ctx->engine = engine;
237 ctx->engine_type = type;
238 ctx->engine_pool = pool;
239 ctx->req_buffer_size = req_buffer_size;
240 ctx->capacity = H2MIN(100, h2_proxy_fifo_capacity(ctx->requests));
242 *pconsumed = out_consumed;
247 static apr_status_t add_request(h2_proxy_session *session, request_rec *r)
249 h2_proxy_ctx *ctx = session->user_data;
253 url = apr_table_get(r->notes, H2_PROXY_REQ_URL_NOTE);
254 apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu",
255 ctx->p_conn->connection->local_addr->port));
256 status = h2_proxy_session_submit(session, url, r, ctx->standalone);
257 if (status != APR_SUCCESS) {
258 ap_log_cerror(APLOG_MARK, APLOG_ERR, status, r->connection, APLOGNO(03351)
259 "pass request body failed to %pI (%s) from %s (%s)",
260 ctx->p_conn->addr, ctx->p_conn->hostname ?
261 ctx->p_conn->hostname: "", session->c->client_ip,
262 session->c->remote_host ? session->c->remote_host: "");
267 static void request_done(h2_proxy_ctx *ctx, request_rec *r,
268 apr_status_t status, int touched)
270 const char *task_id = apr_table_get(r->connection->notes, H2_TASK_ID_NOTE);
272 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, r->connection,
273 "h2_proxy_session(%s): request done %s, touched=%d",
274 ctx->engine_id, task_id, touched);
275 if (status != APR_SUCCESS) {
277 /* untouched request, need rescheduling */
278 status = h2_proxy_fifo_push(ctx->requests, r);
279 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection,
281 "h2_proxy_session(%s): rescheduled request %s",
282 ctx->engine_id, task_id);
287 uri = apr_uri_unparse(r->pool, &r->parsed_uri, 0);
288 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection,
289 APLOGNO(03471) "h2_proxy_session(%s): request %s -> %s "
290 "not complete, cannot repeat",
291 ctx->engine_id, task_id, uri);
295 if (r == ctx->rbase) {
296 ctx->r_status = ((status == APR_SUCCESS)? APR_SUCCESS
297 : HTTP_SERVICE_UNAVAILABLE);
300 if (req_engine_done && ctx->engine) {
301 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection,
303 "h2_proxy_session(%s): finished request %s",
304 ctx->engine_id, task_id);
305 req_engine_done(ctx->engine, r->connection, status);
309 static void session_req_done(h2_proxy_session *session, request_rec *r,
310 apr_status_t status, int touched)
312 request_done(session->user_data, r, status, touched);
315 static apr_status_t next_request(h2_proxy_ctx *ctx, int before_leave)
317 if (h2_proxy_fifo_count(ctx->requests) > 0) {
320 else if (req_engine_pull && ctx->engine) {
322 request_rec *r = NULL;
324 status = req_engine_pull(ctx->engine, before_leave?
325 APR_BLOCK_READ: APR_NONBLOCK_READ,
327 if (status == APR_SUCCESS && r) {
328 ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, ctx->owner,
329 "h2_proxy_engine(%s): pulled request (%s) %s",
331 before_leave? "before leave" : "regular",
333 h2_proxy_fifo_push(ctx->requests, r);
335 return APR_STATUS_IS_EAGAIN(status)? APR_SUCCESS : status;
340 static apr_status_t proxy_engine_run(h2_proxy_ctx *ctx) {
341 apr_status_t status = OK;
345 /* Step Four: Send the Request in a new HTTP/2 stream and
346 * loop until we got the response or encounter errors.
348 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->owner,
349 "eng(%s): setup session", ctx->engine_id);
350 h2_front = is_h2? is_h2(ctx->owner) : 0;
351 ctx->session = h2_proxy_session_setup(ctx->engine_id, ctx->p_conn, ctx->conf,
353 h2_proxy_log2((int)ctx->req_buffer_size),
356 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner,
357 APLOGNO(03372) "session unavailable");
358 return HTTP_SERVICE_UNAVAILABLE;
361 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03373)
362 "eng(%s): run session %s", ctx->engine_id, ctx->session->id);
363 ctx->session->user_data = ctx;
365 while (!ctx->owner->aborted) {
366 if (APR_SUCCESS == h2_proxy_fifo_try_pull(ctx->requests, (void**)&r)) {
367 add_request(ctx->session, r);
370 status = h2_proxy_session_process(ctx->session);
372 if (status == APR_SUCCESS) {
374 /* ongoing processing, call again */
375 if (ctx->session->remote_max_concurrent > 0
376 && ctx->session->remote_max_concurrent != ctx->capacity) {
377 ctx->capacity = H2MIN((int)ctx->session->remote_max_concurrent,
378 h2_proxy_fifo_capacity(ctx->requests));
380 s2 = next_request(ctx, 0);
381 if (s2 == APR_ECONNABORTED) {
382 /* master connection gone */
383 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, s2, ctx->owner,
384 APLOGNO(03374) "eng(%s): pull request",
386 /* give notice that we're leaving and cancel all ongoing
388 next_request(ctx, 1);
389 h2_proxy_session_cancel_all(ctx->session);
390 h2_proxy_session_process(ctx->session);
391 status = ctx->r_status = APR_SUCCESS;
394 if ((h2_proxy_fifo_count(ctx->requests) == 0)
395 && h2_proxy_ihash_empty(ctx->session->streams)) {
400 /* end of processing, maybe error */
401 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
402 APLOGNO(03375) "eng(%s): end of session %s",
403 ctx->engine_id, ctx->session->id);
405 * Any open stream of that session needs to
406 * a) be reopened on the new session iff safe to do so
407 * b) reported as done (failed) otherwise
409 h2_proxy_session_cleanup(ctx->session, session_req_done);
414 ctx->session->user_data = NULL;
420 static apr_status_t push_request_somewhere(h2_proxy_ctx *ctx, request_rec *r)
422 conn_rec *c = ctx->owner;
423 const char *engine_type, *hostname;
425 hostname = (ctx->p_conn->ssl_hostname?
426 ctx->p_conn->ssl_hostname : ctx->p_conn->hostname);
427 engine_type = apr_psprintf(ctx->pool, "proxy_http2 %s%s", hostname,
428 ctx->server_portstr);
430 if (c->master && req_engine_push && r && is_h2 && is_h2(c)) {
431 /* If we are have req_engine capabilities, push the handling of this
432 * request (e.g. slave connection) to a proxy_http2 engine which
433 * uses the same backend. We may be called to create an engine
435 if (req_engine_push(engine_type, r, proxy_engine_init) == APR_SUCCESS) {
436 if (ctx->engine == NULL) {
437 /* request has been assigned to an engine in another thread */
444 /* No engine was available or has been initialized, handle this
445 * request just by ourself. */
446 ctx->engine_id = apr_psprintf(ctx->pool, "eng-proxy-%ld", c->id);
447 ctx->engine_type = engine_type;
448 ctx->engine_pool = ctx->pool;
449 ctx->req_buffer_size = (32*1024);
451 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
452 "h2_proxy_http2(%ld): setup standalone engine for type %s",
456 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
457 "H2: hosting engine %s", ctx->engine_id);
460 return h2_proxy_fifo_push(ctx->requests, r);
463 static int proxy_http2_handler(request_rec *r,
464 proxy_worker *worker,
465 proxy_server_conf *conf,
467 const char *proxyname,
468 apr_port_t proxyport)
470 const char *proxy_func;
471 char *locurl = url, *u;
479 /* find the scheme */
480 if ((url[0] != 'h' && url[0] != 'H') || url[1] != '2') {
483 u = strchr(url, ':');
484 if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0') {
494 if (url[2] != 'c' && url[2] != 'C') {
503 ctx = apr_pcalloc(r->pool, sizeof(*ctx));
504 ctx->owner = r->connection;
507 ctx->server = r->server;
508 ctx->proxy_func = proxy_func;
509 ctx->is_ssl = is_ssl;
510 ctx->worker = worker;
512 ctx->flushall = apr_table_get(r->subprocess_env, "proxy-flushall")? 1 : 0;
513 ctx->r_status = HTTP_SERVICE_UNAVAILABLE;
515 h2_proxy_fifo_set_create(&ctx->requests, ctx->pool, 100);
517 ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, ctx);
519 /* scheme says, this is for us. */
520 apr_table_setn(ctx->rbase->notes, H2_PROXY_REQ_URL_NOTE, url);
521 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->rbase,
522 "H2: serving URL %s", url);
525 /* Get a proxy_conn_rec from the worker, might be a new one, might
526 * be one still open from another request, or it might fail if the
527 * worker is stopped or in error. */
528 if ((status = ap_proxy_acquire_connection(ctx->proxy_func, &ctx->p_conn,
529 ctx->worker, ctx->server)) != OK) {
533 ctx->p_conn->is_ssl = ctx->is_ssl;
535 /* Step One: Determine the URL to connect to (might be a proxy),
536 * initialize the backend accordingly and determine the server
537 * port string we can expect in responses. */
538 if ((status = ap_proxy_determine_connection(ctx->pool, ctx->rbase, conf, worker,
539 ctx->p_conn, &uri, &locurl,
540 proxyname, proxyport,
542 sizeof(ctx->server_portstr))) != OK) {
546 /* If we are not already hosting an engine, try to push the request
547 * to an already existing engine or host a new engine here. */
548 if (r && !ctx->engine) {
549 ctx->r_status = push_request_somewhere(ctx, r);
551 if (ctx->r_status == SUSPENDED) {
552 /* request was pushed to another thread, leave processing here */
557 /* Step Two: Make the Connection (or check that an already existing
558 * socket is still usable). On success, we have a socket connected to
559 * backend->hostname. */
560 if (ap_proxy_connect_backend(ctx->proxy_func, ctx->p_conn, ctx->worker,
562 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03352)
563 "H2: failed to make connection to backend: %s",
564 ctx->p_conn->hostname);
568 /* Step Three: Create conn_rec for the socket we have open now. */
569 if (!ctx->p_conn->connection) {
570 status = ap_proxy_connection_create_ex(ctx->proxy_func,
571 ctx->p_conn, ctx->rbase);
573 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
574 "setup new connection: is_ssl=%d %s %s %s",
575 ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname,
576 locurl, ctx->p_conn->hostname);
580 if (!ctx->p_conn->data && ctx->is_ssl) {
581 /* New SSL connection: set a note on the connection about what
584 apr_table_setn(ctx->p_conn->connection->notes,
585 "proxy-request-alpn-protos", "h2");
590 status = proxy_engine_run(ctx);
591 if (status == APR_SUCCESS) {
592 /* session and connection still ok */
593 if (next_request(ctx, 1) == APR_SUCCESS) {
594 /* more requests, run again */
595 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03376)
596 "run_session, again");
604 if (next_request(ctx, 1) == APR_SUCCESS) {
605 /* Still more to do, tear down old conn and start over */
607 ctx->p_conn->close = 1;
608 #if AP_MODULE_MAGIC_AT_LEAST(20140207, 2)
609 proxy_run_detach_backend(r, ctx->p_conn);
611 ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
615 if (reconnects < 5 && !ctx->owner->aborted) {
618 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(10023)
619 "giving up after %d reconnects, %d requests todo",
620 reconnects, h2_proxy_fifo_count(ctx->requests));
625 if (status != APR_SUCCESS) {
626 /* close socket when errors happened or session shut down (EOF) */
627 ctx->p_conn->close = 1;
629 #if AP_MODULE_MAGIC_AT_LEAST(20140207, 2)
630 proxy_run_detach_backend(ctx->rbase, ctx->p_conn);
632 ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
636 /* Any requests will still have need to fail */
637 while (APR_SUCCESS == h2_proxy_fifo_try_pull(ctx->requests, (void**)&r)) {
638 request_done(ctx, r, HTTP_SERVICE_UNAVAILABLE, 1);
641 ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, NULL);
642 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
643 APLOGNO(03377) "leaving handler");
644 return ctx->r_status;
647 static void register_hook(apr_pool_t *p)
649 ap_hook_post_config(h2_proxy_post_config, NULL, NULL, APR_HOOK_MIDDLE);
651 proxy_hook_scheme_handler(proxy_http2_handler, NULL, NULL, APR_HOOK_FIRST);
652 proxy_hook_canon_handler(proxy_http2_canon, NULL, NULL, APR_HOOK_FIRST);