diff options
author | Stefan Eissing <icing@apache.org> | 2016-02-08 17:53:45 +0100 |
---|---|---|
committer | Stefan Eissing <icing@apache.org> | 2016-02-08 17:53:45 +0100 |
commit | 3567f9f7c669c7b82e69484dc099a375250eff0d (patch) | |
tree | 4b45ef808cdabe2aec0d845ec3b5110dac2a0932 | |
parent | let proxy handler forward ALPN protocol strings for ssl proxy connections (diff) | |
download | apache2-3567f9f7c669c7b82e69484dc099a375250eff0d.tar.xz apache2-3567f9f7c669c7b82e69484dc099a375250eff0d.zip |
new experimental http2 proxy module for h2: and h2c: proxy urls
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1729209 13f79535-47bb-0310-9956-ffa450edef68
30 files changed, 1660 insertions, 398 deletions
@@ -1,6 +1,9 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) mod_proxy_http2: new experimental http2 proxy module for h2: and h2c: proxy + urls. Uses, so far, one connection per request, reuses connections. + *) event: use pre_connection hook to properly initialize connection state for slave connections. use protocol_switch hook to initialize server config early based on SNI selected vhost. diff --git a/modules/http2/config.m4 b/modules/http2/config.m4 index f0cfb60887..ebbd2d2a44 100644 --- a/modules/http2/config.m4 +++ b/modules/http2/config.m4 @@ -201,6 +201,32 @@ is usually linked shared and requires loading. ], $http2_objs, , most, [ # Ensure that other modules can pick up mod_http2.h APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current]) + + +dnl # list of module object files +proxy_http2_objs="dnl +mod_proxy_http2.lo dnl +h2_proxy_session.lo dnl +h2_request.lo dnl +h2_util.lo dnl +" + +dnl # hook module into the Autoconf mechanism (--enable-proxy_http2) +APACHE_MODULE(proxy_http2, [HTTP/2 proxy module. This module requires a libnghttp2 installation. +See --with-nghttp2 on how to manage non-standard locations. ], $proxy_http2_objs, , no, [ + APACHE_CHECK_NGHTTP2 + if test "$ac_cv_nghttp2" = "yes" ; then + if test "x$enable_http2" = "xshared"; then + # The only symbol which needs to be exported is the module + # structure, so ask libtool to hide everything else: + APR_ADDTO(MOD_PROXY_HTTP2_LDADD, [-export-symbols-regex proxy_http2_module]) + fi + else + enable_proxy_http2=no + fi +]) + + dnl # end of module specific part APACHE_MODPATH_FINISH diff --git a/modules/http2/h2.h b/modules/http2/h2.h new file mode 100644 index 0000000000..5429444462 --- /dev/null +++ b/modules/http2/h2.h @@ -0,0 +1,142 @@ +/* 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__ +#define __mod_h2__h2__ + +/** + * The magic PRIamble of RFC 7540 that is always sent when starting + * a h2 communication. + */ +extern const char *H2_MAGIC_TOKEN; + +#define H2_ERR_NO_ERROR (0x00) +#define H2_ERR_PROTOCOL_ERROR (0x01) +#define H2_ERR_INTERNAL_ERROR (0x02) +#define H2_ERR_FLOW_CONTROL_ERROR (0x03) +#define H2_ERR_SETTINGS_TIMEOUT (0x04) +#define H2_ERR_STREAM_CLOSED (0x05) +#define H2_ERR_FRAME_SIZE_ERROR (0x06) +#define H2_ERR_REFUSED_STREAM (0x07) +#define H2_ERR_CANCEL (0x08) +#define H2_ERR_COMPRESSION_ERROR (0x09) +#define H2_ERR_CONNECT_ERROR (0x0a) +#define H2_ERR_ENHANCE_YOUR_CALM (0x0b) +#define H2_ERR_INADEQUATE_SECURITY (0x0c) +#define H2_ERR_HTTP_1_1_REQUIRED (0x0d) + +#define H2_HEADER_METHOD ":method" +#define H2_HEADER_METHOD_LEN 7 +#define H2_HEADER_SCHEME ":scheme" +#define H2_HEADER_SCHEME_LEN 7 +#define H2_HEADER_AUTH ":authority" +#define H2_HEADER_AUTH_LEN 10 +#define H2_HEADER_PATH ":path" +#define H2_HEADER_PATH_LEN 5 +#define H2_CRLF "\r\n" + +/* Maximum number of padding bytes in a frame, rfc7540 */ +#define H2_MAX_PADLEN 256 +/* Initial default window size, RFC 7540 ch. 6.5.2 */ +#define H2_INITIAL_WINDOW_SIZE ((64*1024)-1) + +#define H2_HTTP_2XX(a) ((a) >= 200 && (a) < 300) + +#define H2_STREAM_CLIENT_INITIATED(id) (id&0x01) + +#define H2_ALEN(a) (sizeof(a)/sizeof((a)[0])) + +#define H2MAX(x,y) ((x) > (y) ? (x) : (y)) +#define H2MIN(x,y) ((x) < (y) ? (x) : (y)) + +typedef enum { + H2_DEPENDANT_AFTER, + H2_DEPENDANT_INTERLEAVED, + H2_DEPENDANT_BEFORE, +} h2_dependency; + +typedef struct h2_priority { + h2_dependency dependency; + int weight; +} h2_priority; + +typedef enum { + H2_PUSH_NONE, + H2_PUSH_DEFAULT, + H2_PUSH_HEAD, + H2_PUSH_FAST_LOAD, +} h2_push_policy; + +typedef enum { + H2_STREAM_ST_IDLE, + H2_STREAM_ST_OPEN, + H2_STREAM_ST_RESV_LOCAL, + H2_STREAM_ST_RESV_REMOTE, + H2_STREAM_ST_CLOSED_INPUT, + H2_STREAM_ST_CLOSED_OUTPUT, + H2_STREAM_ST_CLOSED, +} h2_stream_state_t; + +typedef enum { + H2_SESSION_ST_INIT, /* send initial SETTINGS, etc. */ + H2_SESSION_ST_DONE, /* finished, connection close */ + H2_SESSION_ST_IDLE, /* nothing to write, expecting data inc */ + H2_SESSION_ST_BUSY, /* read/write without stop */ + H2_SESSION_ST_WAIT, /* waiting for tasks reporting back */ + H2_SESSION_ST_LOCAL_SHUTDOWN, /* we announced GOAWAY */ + H2_SESSION_ST_REMOTE_SHUTDOWN, /* client announced GOAWAY */ +} h2_session_state; + +/* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal + * format that will be fed to various httpd input filters to finally + * become a request_rec to be handled by soemone. + */ +typedef struct h2_request h2_request; + +struct h2_request { + int id; /* stream id */ + + const char *method; /* pseudo header values, see ch. 8.1.2.3 */ + const char *scheme; + const char *authority; + const char *path; + + apr_table_t *headers; + apr_table_t *trailers; + + apr_time_t request_time; + apr_off_t content_length; + + unsigned int chunked : 1; /* iff requst body needs to be forwarded as chunked */ + unsigned int eoh : 1; /* iff end-of-headers has been seen and request is complete */ + unsigned int body : 1; /* iff this request has a body */ + unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */ + unsigned int push_policy; /* which push policy to use for this request */ +}; + +typedef struct h2_response h2_response; + +struct h2_response { + int stream_id; + int rst_error; + int http_status; + apr_off_t content_length; + apr_table_t *headers; + apr_table_t *trailers; + const char *sos_filter; +}; + + +#endif /* defined(__mod_h2__h2__) */ diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c index dfab2d79df..0c1e6c469b 100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@ -28,6 +28,7 @@ #include <apr_strings.h> +#include "h2.h" #include "h2_alt_svc.h" #include "h2_ctx.h" #include "h2_conn.h" diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c index 3162365abe..daeeb51500 100644 --- a/modules/http2/h2_conn.c +++ b/modules/http2/h2_conn.c @@ -134,6 +134,8 @@ apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s) ap_register_input_filter("H2_IN", h2_filter_core_input, NULL, AP_FTYPE_CONNECTION); + status = h2_mplx_child_init(pool, s); + return status; } diff --git a/modules/http2/h2_filter.c b/modules/http2/h2_filter.c index 87ac9df534..8330ace150 100644 --- a/modules/http2/h2_filter.c +++ b/modules/http2/h2_filter.c @@ -51,7 +51,7 @@ static apr_status_t consume_brigade(h2_filter_cin *cin, apr_bucket* bucket = APR_BRIGADE_FIRST(bb); if (APR_BUCKET_IS_METADATA(bucket)) { - /* we do nothing regardih2_filter_cin_timeout_setng any meta here */ + /* we do nothing regarding any meta here */ } else { const char *bucket_data = NULL; diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c index 9fd072bbe3..719042f9d4 100644 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@ -27,6 +27,8 @@ #include <http_request.h> #include <http_log.h> +#include "mod_ssl.h" + #include "mod_http2.h" #include "h2_private.h" @@ -54,18 +56,8 @@ const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; /******************************************************************************* * The optional mod_ssl functions we need. */ -APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec*)); -APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec*)); - static int (*opt_ssl_engine_disable)(conn_rec*); static int (*opt_ssl_is_https)(conn_rec*); -/******************************************************************************* - * 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 *); diff --git a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h index 563abe3fde..592001e992 100644 --- a/modules/http2/h2_h2.h +++ b/modules/http2/h2_h2.h @@ -29,47 +29,6 @@ extern const char *h2_clear_protos[]; extern const char *h2_tls_protos[]; /** - * The magic PRIamble of RFC 7540 that is always sent when starting - * a h2 communication. - */ -extern const char *H2_MAGIC_TOKEN; - -#define H2_ERR_NO_ERROR (0x00) -#define H2_ERR_PROTOCOL_ERROR (0x01) -#define H2_ERR_INTERNAL_ERROR (0x02) -#define H2_ERR_FLOW_CONTROL_ERROR (0x03) -#define H2_ERR_SETTINGS_TIMEOUT (0x04) -#define H2_ERR_STREAM_CLOSED (0x05) -#define H2_ERR_FRAME_SIZE_ERROR (0x06) -#define H2_ERR_REFUSED_STREAM (0x07) -#define H2_ERR_CANCEL (0x08) -#define H2_ERR_COMPRESSION_ERROR (0x09) -#define H2_ERR_CONNECT_ERROR (0x0a) -#define H2_ERR_ENHANCE_YOUR_CALM (0x0b) -#define H2_ERR_INADEQUATE_SECURITY (0x0c) -#define H2_ERR_HTTP_1_1_REQUIRED (0x0d) - -/* Maximum number of padding bytes in a frame, rfc7540 */ -#define H2_MAX_PADLEN 256 -/* Initial default window size, RFC 7540 ch. 6.5.2 */ -#define H2_INITIAL_WINDOW_SIZE ((64*1024)-1) - -#define H2_HTTP_2XX(a) ((a) >= 200 && (a) < 300) - -#define H2_STREAM_CLIENT_INITIATED(id) (id&0x01) - -typedef enum { - H2_DEPENDANT_AFTER, - H2_DEPENDANT_INTERLEAVED, - H2_DEPENDANT_BEFORE, -} h2_dependency; - -typedef struct h2_priority { - h2_dependency dependency; - int weight; -} h2_priority; - -/** * Provide a user readable description of the HTTP/2 error code- * @param h2_error http/2 error code, as in rfc 7540, ch. 7 * @return textual description of code or that it is unknown. diff --git a/modules/http2/h2_io.h b/modules/http2/h2_io.h index acaa56fcb7..e557636443 100644 --- a/modules/http2/h2_io.h +++ b/modules/http2/h2_io.h @@ -30,8 +30,7 @@ typedef enum { H2_IO_READ, H2_IO_WRITE, H2_IO_ANY, -} -h2_io_op; +} h2_io_op; typedef struct h2_io h2_io; diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index b2374da907..21fb0864e0 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -60,6 +60,48 @@ } while(0) +/* NULL or the mutex hold by this thread, used for recursive calls + */ +static apr_threadkey_t *thread_lock; + +apr_status_t h2_mplx_child_init(apr_pool_t *pool, server_rec *s) +{ + return apr_threadkey_private_create(&thread_lock, NULL, pool); +} + +static apr_status_t enter_mutex(h2_mplx *m, int *pacquired) +{ + apr_status_t status; + void *mutex = NULL; + + /* Enter the mutex if this thread already holds the lock or + * if we can acquire it. Only on the later case do we unlock + * onleaving the mutex. + * This allow recursive entering of the mutex from the saem thread, + * which is what we need in certain situations involving callbacks + */ + apr_threadkey_private_get(&mutex, thread_lock); + if (mutex == m->lock) { + *pacquired = 0; + return APR_SUCCESS; + } + + status = apr_thread_mutex_lock(m->lock); + *pacquired = (status == APR_SUCCESS); + if (*pacquired) { + apr_threadkey_private_set(m->lock, thread_lock); + } + return status; +} + +static void leave_mutex(h2_mplx *m, int acquired) +{ + if (acquired) { + apr_threadkey_private_set(NULL, thread_lock); + apr_thread_mutex_unlock(m->lock); + } +} + static int is_aborted(h2_mplx *m, apr_status_t *pstatus) { AP_DEBUG_ASSERT(m); @@ -177,10 +219,11 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, int h2_mplx_get_max_stream_started(h2_mplx *m) { int stream_id = 0; + int acquired; - apr_thread_mutex_lock(m->lock); + enter_mutex(m, &acquired); stream_id = m->max_stream_started; - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); return stream_id; } @@ -269,10 +312,10 @@ static int stream_done_iter(void *ctx, h2_io *io) apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait) { apr_status_t status; - + int acquired; + h2_workers_unregister(m->workers, m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { int i, wait_secs = 5; /* disable WINDOW_UPDATE callbacks */ @@ -309,7 +352,7 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait) } ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, APLOGNO(03056) "h2_mplx(%ld): release_join -> destroy", m->id); - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); h2_mplx_destroy(m); /* all gone */ } @@ -319,24 +362,28 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait) void h2_mplx_abort(h2_mplx *m) { apr_status_t status; + int acquired; AP_DEBUG_ASSERT(m); if (!m->aborted) { - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { m->aborted = 1; - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } } } apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error) { - apr_status_t status; + apr_status_t status = APR_SUCCESS; + int acquired; + /* This maybe called from inside callbacks that already hold the lock. + * E.g. when we are streaming out DATA and the EOF triggers the stream + * release. + */ AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); /* there should be an h2_io, once the stream has been scheduled @@ -345,8 +392,8 @@ apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error) if (io) { io_stream_done(m, io, rst_error); } - - apr_thread_mutex_unlock(m->lock); + + leave_mutex(m, acquired); } return status; } @@ -371,9 +418,9 @@ static const h2_request *pop_request(h2_mplx *m) void h2_mplx_request_done(h2_mplx **pm, int stream_id, const h2_request **preq) { h2_mplx *m = *pm; + int acquired; - apr_status_t status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if (enter_mutex(m, &acquired) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, "h2_mplx(%ld): request(%d) done", m->id, stream_id); @@ -399,7 +446,7 @@ void h2_mplx_request_done(h2_mplx **pm, int stream_id, const h2_request **preq) * and decrement count */ *pm = NULL; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } } @@ -409,9 +456,10 @@ apr_status_t h2_mplx_in_read(h2_mplx *m, apr_read_type_e block, struct apr_thread_cond_t *iowait) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_read_pre"); @@ -435,7 +483,7 @@ apr_status_t h2_mplx_in_read(h2_mplx *m, apr_read_type_e block, else { status = APR_EOF; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -444,9 +492,10 @@ apr_status_t h2_mplx_in_write(h2_mplx *m, int stream_id, apr_bucket_brigade *bb) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_write_pre"); @@ -458,7 +507,7 @@ apr_status_t h2_mplx_in_write(h2_mplx *m, int stream_id, else { status = APR_ECONNABORTED; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -466,9 +515,10 @@ apr_status_t h2_mplx_in_write(h2_mplx *m, int stream_id, apr_status_t h2_mplx_in_close(h2_mplx *m, int stream_id) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { status = h2_io_in_close(io); @@ -479,7 +529,7 @@ apr_status_t h2_mplx_in_close(h2_mplx *m, int stream_id) else { status = APR_ECONNABORTED; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -507,12 +557,13 @@ void h2_mplx_set_consumed_cb(h2_mplx *m, h2_mplx_consumed_cb *cb, void *ctx) apr_status_t h2_mplx_in_update_windows(h2_mplx *m) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); if (m->aborted) { return APR_ECONNABORTED; } - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { update_ctx ctx; ctx.m = m; @@ -524,7 +575,7 @@ apr_status_t h2_mplx_in_update_windows(h2_mplx *m) if (ctx.streams_updated) { status = APR_SUCCESS; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -535,9 +586,10 @@ apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, apr_table_t **ptrailers) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_pre"); @@ -553,7 +605,7 @@ apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, } *ptrailers = (*peos && io->response)? io->response->trailers : NULL; - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -564,9 +616,10 @@ apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id, apr_table_t **ptrailers) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_pre"); @@ -582,7 +635,7 @@ apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id, status = APR_ECONNABORTED; } *ptrailers = (*peos && io->response)? io->response->trailers : NULL; - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -591,10 +644,10 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) { apr_status_t status; h2_stream *stream = NULL; + int acquired; AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_pop_highest_prio(m->ready_ios); if (io && !m->aborted) { stream = h2_stream_set_get(streams, io->id); @@ -633,7 +686,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) h2_io_signal(io, H2_IO_WRITE); } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return stream; } @@ -716,9 +769,10 @@ apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_response *response, struct apr_thread_cond_t *iowait) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { if (m->aborted) { status = APR_ECONNABORTED; } @@ -728,7 +782,7 @@ apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_response *response, h2_util_bb_log(m->c, stream_id, APLOG_TRACE1, "h2_mplx_out_open", bb); } } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -739,9 +793,10 @@ apr_status_t h2_mplx_out_write(h2_mplx *m, int stream_id, struct apr_thread_cond_t *iowait) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { status = out_write(m, io, f, bb, trailers, iowait); @@ -755,7 +810,7 @@ apr_status_t h2_mplx_out_write(h2_mplx *m, int stream_id, else { status = APR_ECONNABORTED; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -763,9 +818,10 @@ apr_status_t h2_mplx_out_write(h2_mplx *m, int stream_id, apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id, apr_table_t *trailers) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { if (!io->response && !io->rst_error) { @@ -791,7 +847,7 @@ apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id, apr_table_t *trailers) else { status = APR_ECONNABORTED; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -799,9 +855,10 @@ apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id, apr_table_t *trailers) apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->rst_error && !io->orphaned) { h2_io_rst(io, error); @@ -816,7 +873,7 @@ apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error) else { status = APR_ECONNABORTED; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -824,10 +881,11 @@ apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error) int h2_mplx_in_has_eos_for(h2_mplx *m, int stream_id) { int has_eos = 0; + int acquired; + apr_status_t status; AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { has_eos = h2_io_in_has_eos_for(io); @@ -835,7 +893,7 @@ int h2_mplx_in_has_eos_for(h2_mplx *m, int stream_id) else { has_eos = 1; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return has_eos; } @@ -844,9 +902,10 @@ int h2_mplx_out_has_data_for(h2_mplx *m, int stream_id) { apr_status_t status; int has_data = 0; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { has_data = h2_io_out_has_data(io); @@ -854,7 +913,7 @@ int h2_mplx_out_has_data_for(h2_mplx *m, int stream_id) else { has_data = 0; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return has_data; } @@ -863,9 +922,10 @@ apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout, apr_thread_cond_t *iowait) { apr_status_t status; + int acquired; + AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { if (m->aborted) { status = APR_ECONNABORTED; } @@ -879,7 +939,7 @@ apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout, } m->added_output = NULL; } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -896,10 +956,10 @@ static void have_out_data_for(h2_mplx *m, int stream_id) apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx) { apr_status_t status; + int acquired; AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { if (m->aborted) { status = APR_ECONNABORTED; } @@ -909,7 +969,7 @@ apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx) ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c, "h2_mplx(%ld): reprioritize tasks", m->id); } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return status; } @@ -938,10 +998,10 @@ apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, const h2_request *req, { apr_status_t status; int was_empty = 0; + int acquired; AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { if (m->aborted) { status = APR_ECONNABORTED; } @@ -960,7 +1020,7 @@ apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, const h2_request *req, "h2_mplx(%ld-%d): process", m->c->id, stream_id); H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_process"); } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } if (status == APR_SUCCESS && was_empty) { workers_register(m); @@ -972,10 +1032,10 @@ const h2_request *h2_mplx_pop_request(h2_mplx *m, int *has_more) { const h2_request *req = NULL; apr_status_t status; + int acquired; AP_DEBUG_ASSERT(m); - status = apr_thread_mutex_lock(m->lock); - if (APR_SUCCESS == status) { + if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { if (m->aborted) { req = NULL; *has_more = 0; @@ -984,7 +1044,7 @@ const h2_request *h2_mplx_pop_request(h2_mplx *m, int *has_more) req = pop_request(m); *has_more = !h2_tq_empty(m->q); } - apr_thread_mutex_unlock(m->lock); + leave_mutex(m, acquired); } return req; } diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h index 4948af920d..024401dbe5 100644 --- a/modules/http2/h2_mplx.h +++ b/modules/http2/h2_mplx.h @@ -95,6 +95,8 @@ struct h2_mplx { * Object lifecycle and information. ******************************************************************************/ +apr_status_t h2_mplx_child_init(apr_pool_t *pool, server_rec *s); + /** * Create the multiplexer for the given HTTP2 session. * Implicitly has reference count 1. diff --git a/modules/http2/h2_private.h b/modules/http2/h2_private.h index eb24fa1a21..383adb1f0e 100644 --- a/modules/http2/h2_private.h +++ b/modules/http2/h2_private.h @@ -25,19 +25,4 @@ extern module AP_MODULE_DECLARE_DATA http2_module; APLOG_USE_MODULE(http2); -#define H2_HEADER_METHOD ":method" -#define H2_HEADER_METHOD_LEN 7 -#define H2_HEADER_SCHEME ":scheme" -#define H2_HEADER_SCHEME_LEN 7 -#define H2_HEADER_AUTH ":authority" -#define H2_HEADER_AUTH_LEN 10 -#define H2_HEADER_PATH ":path" -#define H2_HEADER_PATH_LEN 5 -#define H2_CRLF "\r\n" - -#define H2_ALEN(a) (sizeof(a)/sizeof((a)[0])) - -#define H2MAX(x,y) ((x) > (y) ? (x) : (y)) -#define H2MIN(x,y) ((x) < (y) ? (x) : (y)) - #endif diff --git a/modules/http2/h2_proxy_session.c b/modules/http2/h2_proxy_session.c new file mode 100644 index 0000000000..a40f825f3f --- /dev/null +++ b/modules/http2/h2_proxy_session.c @@ -0,0 +1,639 @@ +/* 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 <apr_strings.h> +#include <nghttp2/nghttp2.h> + +#include <httpd.h> +#include <mod_proxy.h> + +#include "h2.h" +#include "h2_request.h" +#include "h2_util.h" +#include "h2_proxy_session.h" + +APLOG_USE_MODULE(proxy_http2); + +static int ngstatus_from_apr_status(apr_status_t rv) +{ + if (rv == APR_SUCCESS) { + return NGHTTP2_NO_ERROR; + } + else if (APR_STATUS_IS_EAGAIN(rv)) { + return NGHTTP2_ERR_WOULDBLOCK; + } + else if (APR_STATUS_IS_EOF(rv)) { + return NGHTTP2_ERR_EOF; + } + return NGHTTP2_ERR_PROTO; +} + + +static apr_status_t proxy_session_shutdown(void *theconn) +{ + proxy_conn_rec *p_conn = (proxy_conn_rec *)theconn; + h2_proxy_session *session = p_conn->data; + + if (session && session->ngh2) { + if (session->c && !session->c->aborted && !session->goaway_sent) { + nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, + session->max_stream_recv, 0, NULL, 0); + nghttp2_session_send(session->ngh2); + } + + nghttp2_session_del(session->ngh2); + session->ngh2 = NULL; + p_conn->data = NULL; + } + return APR_SUCCESS; +} + +static ssize_t raw_send(nghttp2_session *ngh2, const uint8_t *data, + size_t length, int flags, void *user_data) +{ + h2_proxy_session *session = user_data; + apr_bucket *b; + apr_status_t status; + int flush = 1; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, + "h2_proxy_sesssion(%ld): raw_send %d bytes, flush=%d", + session->c->id, (int)length, flush); + b = apr_bucket_transient_create((const char*)data, length, + session->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(session->output, b); + + status = ap_proxy_pass_brigade(session->c->bucket_alloc, session->r, + session->p_conn, session->c, + session->output, flush); + if (status != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + "h2_proxy_sesssion(%ld): sending", session->c->id); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return length; +} + +static int on_frame_recv(nghttp2_session *ngh2, const nghttp2_frame *frame, + void *user_data) +{ + h2_proxy_session *session = user_data; + h2_proxy_stream *stream; + int eos; + + if (APLOGcdebug(session->c)) { + char buffer[256]; + + h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO() + "h2_session(%ld): recv FRAME[%s]", + session->c->id, buffer); + } + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + stream = nghttp2_session_get_stream_user_data(ngh2, frame->hd.stream_id); + eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM); + + break; + case NGHTTP2_PUSH_PROMISE: + break; + case NGHTTP2_GOAWAY: + session->goaway_recvd = 1; + /* TODO: close handling */ + break; + default: + break; + } + return 0; +} + +static int before_frame_send(nghttp2_session *ngh2, + const nghttp2_frame *frame, void *user_data) +{ + h2_proxy_session *session = user_data; + if (APLOGcdebug(session->c)) { + char buffer[256]; + + h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03068) + "h2_session(%ld): sent FRAME[%s]", + session->c->id, buffer); + } + return 0; +} + +static int add_header(void *table, const char *n, const char *v) +{ + apr_table_addn(table, n, v); + return 1; +} + +static void process_proxy_header(request_rec *r, const char *n, const char *v) +{ + static const struct { + const char *name; + ap_proxy_header_reverse_map_fn func; + } transform_hdrs[] = { + { "Location", ap_proxy_location_reverse_map }, + { "Content-Location", ap_proxy_location_reverse_map }, + { "URI", ap_proxy_location_reverse_map }, + { "Destination", ap_proxy_location_reverse_map }, + { "Set-Cookie", ap_proxy_cookie_reverse_map }, + { NULL, NULL } + }; + proxy_dir_conf *dconf; + int i; + + for (i = 0; transform_hdrs[i].name; ++i) { + if (!ap_casecmpstr(transform_hdrs[i].name, n)) { + dconf = ap_get_module_config(r->per_dir_config, &proxy_module); + apr_table_add(r->headers_out, n, + (*transform_hdrs[i].func)(r, dconf, v)); + return; + } + } + apr_table_add(r->headers_out, n, v); +} + +static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream, + const char *n, apr_size_t nlen, + const char *v, apr_size_t vlen) +{ + if (n[0] == ':') { + if (!stream->data_received && !strncmp(":status", n, nlen)) { + char *s = apr_pstrndup(stream->pool, v, vlen); + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, + "h2_proxy_stream(%ld-%d): got status %s", + stream->session->c->id, stream->id, s); + stream->r->status = (int)apr_atoi64(s); + if (stream->r->status <= 0) { + stream->r->status = 500; + return APR_EGENERAL; + } + } + return APR_SUCCESS; + } + + if (!h2_proxy_res_ignore_header(n, nlen)) { + char *hname, *hvalue; + + hname = apr_pstrndup(stream->pool, n, nlen); + h2_util_camel_case_header(hname, nlen); + hvalue = apr_pstrndup(stream->pool, v, vlen); + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, + "h2_proxy_stream(%ld-%d): got header %s: %s", + stream->session->c->id, stream->id, hname, hvalue); + process_proxy_header(stream->r, hname, hvalue); + } + return APR_SUCCESS; +} + +static void h2_proxy_stream_end_headers_out(h2_proxy_stream *stream) +{ + h2_proxy_session *session = stream->session; + request_rec *r = stream->r; + apr_pool_t *p = r->pool; + + /* Now, add in the cookies from the response to the ones already saved */ + apr_table_do(add_header, stream->saves, r->headers_out, "Set-Cookie", NULL); + + /* and now load 'em all in */ + if (!apr_is_empty_table(stream->saves)) { + apr_table_unset(r->headers_out, "Set-Cookie"); + r->headers_out = apr_table_overlay(p, r->headers_out, stream->saves); + } + + /* handle Via header in response */ + if (session->conf->viaopt != via_off + && session->conf->viaopt != via_block) { + const char *server_name = ap_get_server_name(stream->r); + apr_port_t port = ap_get_server_port(stream->r); + char portstr[32]; + + /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host, + * then the server name returned by ap_get_server_name() is the + * origin server name (which does make too much sense with Via: headers) + * so we use the proxy vhost's name instead. + */ + if (server_name == stream->r->hostname) { + server_name = stream->r->server->server_hostname; + } + if (ap_is_default_port(port, stream->r)) { + portstr[0] = '\0'; + } + else { + apr_snprintf(portstr, sizeof(portstr), ":%d", port); + } + + /* create a "Via:" response header entry and merge it */ + apr_table_addn(r->headers_out, "Via", + (session->conf->viaopt == via_full) + ? apr_psprintf(p, "%d.%d %s%s (%s)", + HTTP_VERSION_MAJOR(r->proto_num), + HTTP_VERSION_MINOR(r->proto_num), + server_name, portstr, + AP_SERVER_BASEVERSION) + : apr_psprintf(p, "%d.%d %s%s", + HTTP_VERSION_MAJOR(r->proto_num), + HTTP_VERSION_MINOR(r->proto_num), + server_name, portstr) + ); + } +} + +static int on_data_chunk_recv(nghttp2_session *ngh2, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) +{ + h2_proxy_session *session = user_data; + h2_proxy_stream *stream; + apr_bucket *b; + apr_status_t status; + + nghttp2_session_consume(ngh2, stream_id, len); + stream = nghttp2_session_get_stream_user_data(ngh2, stream_id); + if (!stream) { + return 0; + } + + if (!stream->data_received) { + /* last chance to manipulate response headers. + * after this, only trailers */ + h2_proxy_stream_end_headers_out(stream); + stream->data_received = 1; + } + + b = apr_bucket_transient_create((const char*)data, len, session->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(stream->output, b); + status = ap_pass_brigade(stream->r->output_filters, stream->output); + if (status != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO() + "h2_session(%ld-%d): passing output", + session->c->id, stream->id); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +static int on_stream_close(nghttp2_session *ngh2, int32_t stream_id, + uint32_t error_code, void *user_data) +{ + h2_proxy_session *session = user_data; + h2_proxy_stream *stream; + + stream = nghttp2_session_get_stream_user_data(ngh2, stream_id); + if (!stream) { + return 0; + } + + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, + "h2_proxy_sesssion(%ld): closing stream(%d)", + session->c->id, stream_id); + + if (!stream->data_received) { + /* last chance to manipulate response headers. + * after this, only trailers */ + stream->data_received = 1; + } + stream->state = H2_STREAM_ST_CLOSED; + return 0; +} + +static int on_header(nghttp2_session *ngh2, const nghttp2_frame *frame, + const uint8_t *namearg, size_t nlen, + const uint8_t *valuearg, size_t vlen, uint8_t flags, + void *user_data) +{ + h2_proxy_session *session = user_data; + h2_proxy_stream *stream; + const char *n = (const char*)namearg; + const char *v = (const char*)valuearg; + + (void)session; + if (frame->hd.type == NGHTTP2_HEADERS && nlen) { + stream = nghttp2_session_get_stream_user_data(ngh2, frame->hd.stream_id); + if (stream) { + if (h2_proxy_stream_add_header_out(stream, n, nlen, v, vlen)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + } + else if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + } + + return 0; +} + +static ssize_t stream_data_read(nghttp2_session *ngh2, int32_t stream_id, + uint8_t *buf, size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) +{ + h2_proxy_session *session = user_data; + h2_proxy_stream *stream; + apr_status_t status = APR_SUCCESS; + + *data_flags = 0; + stream = nghttp2_session_get_stream_user_data(ngh2, stream_id); + if (!stream) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if (APR_BRIGADE_EMPTY(stream->input)) { + status = ap_get_brigade(stream->r->input_filters, stream->input, + AP_MODE_READBYTES, APR_BLOCK_READ, + H2MIN(APR_BUCKET_BUFF_SIZE, length)); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, + "h2_proxy_stream(%ld-%d): request body read", + session->c->id, stream->id); + } + + if (status == APR_SUCCESS) { + ssize_t readlen = 0; + while (status == APR_SUCCESS + && (readlen < length) + && !APR_BRIGADE_EMPTY(stream->input)) { + apr_bucket* b = APR_BRIGADE_FIRST(stream->input); + if (APR_BUCKET_IS_METADATA(b)) { + if (APR_BUCKET_IS_EOS(b)) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + else { + /* we do nothing more regarding any meta here */ + } + } + else { + const char *bdata = NULL; + apr_size_t blen = 0; + status = apr_bucket_read(b, &bdata, &blen, APR_BLOCK_READ); + + if (status == APR_SUCCESS && blen > 0) { + ssize_t copylen = H2MIN(length - readlen, blen); + memcpy(buf, bdata, copylen); + buf += copylen; + readlen += copylen; + if (copylen < blen) { + /* We have data left in the bucket. Split it. */ + status = apr_bucket_split(b, copylen); + } + } + } + apr_bucket_delete(b); + } + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, + "h2_proxy_stream(%ld-%d): request body read %ld bytes, flags=%d", + session->c->id, stream->id, (long)readlen, (int)*data_flags); + return readlen; + } + else if (APR_STATUS_IS_EAGAIN(status)) { + return NGHTTP2_ERR_DEFERRED; + } + return ngstatus_from_apr_status(status); +} + +h2_proxy_session *h2_proxy_session_setup(request_rec *r, proxy_conn_rec *p_conn, + proxy_server_conf *conf) +{ + if (!p_conn->data) { + h2_proxy_session *session; + nghttp2_settings_entry settings[2]; + nghttp2_session_callbacks *cbs; + int add_conn_window; + int rv; + + session = apr_pcalloc(p_conn->scpool, sizeof(*session)); + apr_pool_pre_cleanup_register(p_conn->scpool, p_conn, proxy_session_shutdown); + p_conn->data = session; + + session->c = p_conn->connection; + session->p_conn = p_conn; + session->conf = conf; + session->r = r; + session->pool = p_conn->scpool; + session->window_bits_default = 30; + session->window_bits_connection = 30; + + session->input = apr_brigade_create(session->pool, session->c->bucket_alloc); + session->output = apr_brigade_create(session->pool, session->c->bucket_alloc); + + nghttp2_session_callbacks_new(&cbs); + nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, on_data_chunk_recv); + nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close); + nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); + nghttp2_session_callbacks_set_before_frame_send_callback(cbs, before_frame_send); + nghttp2_session_callbacks_set_send_callback(cbs, raw_send); + + nghttp2_session_client_new(&session->ngh2, cbs, session); + nghttp2_session_callbacks_del(cbs); + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + "setup session for %s", p_conn->hostname); + + settings[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + settings[0].value = 0; + settings[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + settings[1].value = (1 << session->window_bits_default) - 1; + + rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, settings, + H2_ALEN(settings)); + + /* If the connection window is larger than our default, trigger a WINDOW_UPDATE */ + add_conn_window = ((1 << session->window_bits_connection) - 1 - + NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE); + if (!rv && add_conn_window != 0) { + rv = nghttp2_submit_window_update(session->ngh2, NGHTTP2_FLAG_NONE, 0, add_conn_window); + } + } + return p_conn->data; +} + + +apr_status_t h2_proxy_session_open_stream(h2_proxy_session *session, const char *url, + request_rec *r, h2_proxy_stream **pstream) +{ + h2_proxy_stream *stream; + apr_uri_t puri; + const char *authority, *scheme, *path; + + stream = apr_pcalloc(r->pool, sizeof(*stream)); + + stream->pool = r->pool; + stream->url = url; + stream->r = r; + stream->session = session; + stream->state = H2_STREAM_ST_IDLE; + + stream->input = apr_brigade_create(stream->pool, session->c->bucket_alloc); + stream->output = apr_brigade_create(stream->pool, session->c->bucket_alloc); + + stream->req = h2_request_create(1, stream->pool, 0); + + apr_uri_parse(stream->pool, url, &puri); + scheme = (strcmp(puri.scheme, "h2")? "http" : "https"); + authority = puri.hostname; + if (!ap_strchr_c(authority, ':') && puri.port + && apr_uri_port_of_scheme(scheme) != puri.port) { + /* port info missing and port is not default for scheme: append */ + authority = apr_psprintf(stream->pool, "%s:%d", authority, puri.port); + } + path = apr_uri_unparse(stream->pool, &puri, APR_URI_UNP_OMITSITEPART); + h2_request_make(stream->req, stream->pool, r->method, scheme, + authority, path, r->headers_in); + + /* Tuck away all already existing cookies */ + stream->saves = apr_table_make(r->pool, 2); + apr_table_do(add_header, stream->saves, r->headers_out,"Set-Cookie", NULL); + + *pstream = stream; + + return APR_SUCCESS; +} + +static apr_status_t feed_brigade(h2_proxy_session *session, apr_bucket_brigade *bb) +{ + apr_status_t status = APR_SUCCESS; + apr_size_t readlen = 0; + ssize_t n; + + while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) { + apr_bucket* b = APR_BRIGADE_FIRST(bb); + + if (!APR_BUCKET_IS_METADATA(b)) { + const char *bdata = NULL; + apr_size_t blen = 0; + + status = apr_bucket_read(b, &bdata, &blen, APR_NONBLOCK_READ); + if (status == APR_SUCCESS && blen > 0) { + n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)bdata, blen); + if (n < 0) { + if (nghttp2_is_fatal((int)n)) { + return APR_EGENERAL; + } + } + else { + readlen += n; + if (n < blen) { + apr_bucket_split(b, n); + } + } + } + } + apr_bucket_delete(b); + } + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, + "h2_session(%ld): fed %ld bytes of input", session->c->id, (long)readlen); + if (readlen == 0 && status == APR_SUCCESS) { + return APR_EAGAIN; + } + return status; +} + + +static apr_status_t stream_loop(h2_proxy_stream *stream) +{ + h2_proxy_session *session = stream->session; + apr_status_t status = APR_SUCCESS; + int want_read, want_write; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, + "h2_session(%ld): start loop for stream %d", + session->c->id, stream->id); + while ((status == APR_SUCCESS || APR_STATUS_IS_EAGAIN(status)) + && stream->state != H2_STREAM_ST_CLOSED) { + + want_read = nghttp2_session_want_read(session->ngh2); + want_write = nghttp2_session_want_write(session->ngh2); + + if (want_write) { + int rv = nghttp2_session_send(session->ngh2); + if (rv < 0 && nghttp2_is_fatal(rv)) { + status = APR_EGENERAL; + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, + "h2_session(%ld): write, rv=%d", session->c->id, rv); + break; + } + } + + if (want_read) { + status = ap_get_brigade(session->c->input_filters, session->input, + AP_MODE_READBYTES, + (want_write? APR_NONBLOCK_READ : APR_BLOCK_READ), + APR_BUCKET_BUFF_SIZE); + if (status == APR_SUCCESS) { + status = feed_brigade(session, session->input); + } + else if (!APR_STATUS_IS_EAGAIN(status)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, + "h2_session(%ld): read", session->c->id); + break; + } + } + + if (!want_read && !want_write) { + status = APR_EGENERAL; + break; + } + } + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, + "h2_session(%ld): end loop for stream %d", + session->c->id, stream->id); + return status; +} + +apr_status_t h2_proxy_stream_process(h2_proxy_stream *stream) +{ + h2_proxy_session *session = stream->session; + h2_ngheader *hd; + nghttp2_data_provider *pp = NULL; + nghttp2_data_provider provider; + int rv; + apr_status_t status; + + hd = h2_util_ngheader_make_req(stream->pool, stream->req); + + status = ap_get_brigade(stream->r->input_filters, stream->input, + AP_MODE_READBYTES, APR_NONBLOCK_READ, + APR_BUCKET_BUFF_SIZE); + if ((status == APR_SUCCESS && !APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(stream->input))) + || APR_STATUS_IS_EAGAIN(status)) { + /* there might be data coming */ + provider.source.fd = 0; + provider.source.ptr = NULL; + provider.read_callback = stream_data_read; + pp = &provider; + } + + rv = nghttp2_submit_request(session->ngh2, NULL, + hd->nv, hd->nvlen, pp, stream); + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, + "h2_session(%ld): submit request -> %d", + session->c->id, rv); + if (rv > 0) { + stream->id = rv; + stream->state = H2_STREAM_ST_OPEN; + + return stream_loop(stream); + } + return APR_EGENERAL; +} + diff --git a/modules/http2/h2_proxy_session.h b/modules/http2/h2_proxy_session.h new file mode 100644 index 0000000000..598a2a037a --- /dev/null +++ b/modules/http2/h2_proxy_session.h @@ -0,0 +1,69 @@ +/* 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 h2_proxy_session_h +#define h2_proxy_session_h + +#define H2_ALEN(a) (sizeof(a)/sizeof((a)[0])) + +#include <nghttp2/nghttp2.h> + +typedef struct h2_proxy_session { + conn_rec *c; + proxy_conn_rec *p_conn; + proxy_server_conf *conf; + request_rec *r; + apr_pool_t *pool; + nghttp2_session *ngh2; /* the nghttp2 session itself */ + + int window_bits_default; + int window_bits_connection; + + unsigned int goaway_recvd : 1; + unsigned int goaway_sent : 1; + + int max_stream_recv; + + apr_bucket_brigade *input; + apr_bucket_brigade *output; +} h2_proxy_session; + +typedef struct h2_proxy_stream { + int id; + apr_pool_t *pool; + h2_proxy_session *session; + + const char *url; + request_rec *r; + h2_request *req; + + h2_stream_state_t state; + unsigned int data_received : 1; + + apr_bucket_brigade *input; + apr_bucket_brigade *output; + + apr_table_t *saves; +} h2_proxy_stream; + + +h2_proxy_session *h2_proxy_session_setup(request_rec *r, proxy_conn_rec *p_connm, + proxy_server_conf *conf); + +apr_status_t h2_proxy_session_open_stream(h2_proxy_session *s, const char *url, + request_rec *r, h2_proxy_stream **pstream); +apr_status_t h2_proxy_stream_process(h2_proxy_stream *stream); + +#endif /* h2_proxy_session_h */ diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c index 842382441e..a8b7c8591c 100644 --- a/modules/http2/h2_push.c +++ b/modules/http2/h2_push.c @@ -345,10 +345,9 @@ static int add_push(link_ctx *ctx) "Cache-Control", "Accept-Language", NULL); - req = h2_request_createn(0, ctx->pool, ctx->req->config, - method, ctx->req->scheme, - ctx->req->authority, - path, headers); + req = h2_request_createn(0, ctx->pool, method, ctx->req->scheme, + ctx->req->authority, path, headers, + ctx->req->serialize); /* atm, we do not push on pushes */ h2_request_end_headers(req, ctx->pool, 1, 0); push->req = req; @@ -456,36 +455,6 @@ apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req, return NULL; } -void h2_push_policy_determine(struct h2_request *req, apr_pool_t *p, int push_enabled) -{ - h2_push_policy policy = H2_PUSH_NONE; - if (push_enabled) { - const char *val = apr_table_get(req->headers, "accept-push-policy"); - if (val) { - if (ap_find_token(p, val, "fast-load")) { - policy = H2_PUSH_FAST_LOAD; - } - else if (ap_find_token(p, val, "head")) { - policy = H2_PUSH_HEAD; - } - else if (ap_find_token(p, val, "default")) { - policy = H2_PUSH_DEFAULT; - } - else if (ap_find_token(p, val, "none")) { - policy = H2_PUSH_NONE; - } - else { - /* nothing known found in this header, go by default */ - policy = H2_PUSH_DEFAULT; - } - } - else { - policy = H2_PUSH_DEFAULT; - } - } - req->push_policy = policy; -} - /******************************************************************************* * push diary * diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h index b9e7219fce..d3519dcbfe 100644 --- a/modules/http2/h2_push.h +++ b/modules/http2/h2_push.h @@ -15,19 +15,14 @@ #ifndef __mod_h2__h2_push__ #define __mod_h2__h2_push__ +#include "h2.h" + struct h2_request; struct h2_response; struct h2_ngheader; struct h2_session; struct h2_stream; -typedef enum { - H2_PUSH_NONE, - H2_PUSH_DEFAULT, - H2_PUSH_HEAD, - H2_PUSH_FAST_LOAD, -} h2_push_policy; - typedef struct h2_push { const struct h2_request *req; } h2_push; @@ -66,17 +61,6 @@ apr_array_header_t *h2_push_collect(apr_pool_t *p, const struct h2_response *res); /** - * Set the push policy for the given request. Takes request headers into - * account, see draft https://tools.ietf.org/html/draft-ruellan-http-accept-push-policy-00 - * for details. - * - * @param req the request to determine the policy for - * @param p the pool to use - * @param push_enabled if HTTP/2 server push is generally enabled for this request - */ -void h2_push_policy_determine(struct h2_request *req, apr_pool_t *p, int push_enabled); - -/** * Create a new push diary for the given maximum number of entries. * * @oaram p the pool to use diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index 1672db33eb..a2c899144d 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -30,38 +30,33 @@ #include <scoreboard.h> #include "h2_private.h" -#include "h2_config.h" -#include "h2_mplx.h" #include "h2_push.h" #include "h2_request.h" -#include "h2_task.h" #include "h2_util.h" -h2_request *h2_request_create(int id, apr_pool_t *pool, - const struct h2_config *config) +h2_request *h2_request_create(int id, apr_pool_t *pool, int serialize) { - return h2_request_createn(id, pool, config, - NULL, NULL, NULL, NULL, NULL); + return h2_request_createn(id, pool, NULL, NULL, NULL, NULL, NULL, + serialize); } h2_request *h2_request_createn(int id, apr_pool_t *pool, - const struct h2_config *config, const char *method, const char *scheme, const char *authority, const char *path, - apr_table_t *header) + apr_table_t *header, int serialize) { h2_request *req = apr_pcalloc(pool, sizeof(h2_request)); req->id = id; - req->config = config; req->method = method; req->scheme = scheme; req->authority = authority; req->path = path; req->headers = header? header : apr_table_make(pool, 10); req->request_time = apr_time_now(); - + req->serialize = serialize; + return req; } @@ -139,38 +134,48 @@ static apr_status_t add_all_h1_header(h2_request *req, apr_pool_t *pool, } +apr_status_t h2_request_make(h2_request *req, apr_pool_t *pool, + const char *method, const char *scheme, + const char *authority, const char *path, + apr_table_t *headers) +{ + req->method = method; + req->scheme = scheme; + req->authority = authority; + req->path = path; + + AP_DEBUG_ASSERT(req->scheme); + AP_DEBUG_ASSERT(req->authority); + AP_DEBUG_ASSERT(req->path); + AP_DEBUG_ASSERT(req->method); + + return add_all_h1_header(req, pool, headers); +} + apr_status_t h2_request_rwrite(h2_request *req, request_rec *r) { apr_status_t status; + const char *scheme, *authority; - req->config = h2_config_rget(r); - req->method = r->method; - req->scheme = (r->parsed_uri.scheme? r->parsed_uri.scheme - : ap_http_scheme(r)); - req->authority = r->hostname; - req->path = apr_uri_unparse(r->pool, &r->parsed_uri, - APR_URI_UNP_OMITSITEPART); - - if (!ap_strchr_c(req->authority, ':') && r->server && r->server->port) { - apr_port_t defport = apr_uri_port_of_scheme(req->scheme); + scheme = (r->parsed_uri.scheme? r->parsed_uri.scheme + : ap_http_scheme(r)); + authority = r->hostname; + if (!ap_strchr_c(authority, ':') && r->server && r->server->port) { + apr_port_t defport = apr_uri_port_of_scheme(scheme); if (defport != r->server->port) { /* port info missing and port is not default for scheme: append */ - req->authority = apr_psprintf(r->pool, "%s:%d", req->authority, - (int)r->server->port); + authority = apr_psprintf(r->pool, "%s:%d", authority, + (int)r->server->port); } } - AP_DEBUG_ASSERT(req->scheme); - AP_DEBUG_ASSERT(req->authority); - AP_DEBUG_ASSERT(req->path); - AP_DEBUG_ASSERT(req->method); - - status = add_all_h1_header(req, r->pool, r->headers_in); - + status = h2_request_make(req, r->pool, r->method, scheme, authority, + apr_uri_unparse(r->pool, &r->parsed_uri, + APR_URI_UNP_OMITSITEPART), + r->headers_in); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03058) "h2_request(%d): rwrite %s host=%s://%s%s", req->id, req->method, req->scheme, req->authority, req->path); - return status; } diff --git a/modules/http2/h2_request.h b/modules/http2/h2_request.h index cc01ed1238..946bd34852 100644 --- a/modules/http2/h2_request.h +++ b/modules/http2/h2_request.h @@ -16,46 +16,19 @@ #ifndef __mod_h2__h2_request__ #define __mod_h2__h2_request__ -/* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal - * format that will be fed to various httpd input filters to finally - * become a request_rec to be handled by soemone. - */ -struct h2_config; -struct h2_to_h1; -struct h2_mplx; -struct h2_task; - -typedef struct h2_request h2_request; - -struct h2_request { - int id; /* stream id */ +#include "h2.h" - const char *method; /* pseudo header values, see ch. 8.1.2.3 */ - const char *scheme; - const char *authority; - const char *path; - - apr_table_t *headers; - apr_table_t *trailers; - - apr_time_t request_time; - apr_off_t content_length; - - unsigned int chunked : 1; /* iff requst body needs to be forwarded as chunked */ - unsigned int eoh : 1; /* iff end-of-headers has been seen and request is complete */ - unsigned int body : 1; /* iff this request has a body */ - unsigned int push_policy; /* which push policy to use for this request */ - const struct h2_config *config; -}; - -h2_request *h2_request_create(int id, apr_pool_t *pool, - const struct h2_config *config); +h2_request *h2_request_create(int id, apr_pool_t *pool, int serialize); h2_request *h2_request_createn(int id, apr_pool_t *pool, - const struct h2_config *config, const char *method, const char *scheme, const char *authority, const char *path, - apr_table_t *headers); + apr_table_t *headers, int serialize); + +apr_status_t h2_request_make(h2_request *req, apr_pool_t *pool, + const char *method, const char *scheme, + const char *authority, const char *path, + apr_table_t *headers); void h2_request_destroy(h2_request *req); diff --git a/modules/http2/h2_response.h b/modules/http2/h2_response.h index 59140ee300..ca57c532e6 100644 --- a/modules/http2/h2_response.h +++ b/modules/http2/h2_response.h @@ -16,18 +16,7 @@ #ifndef __mod_h2__h2_response__ #define __mod_h2__h2_response__ -struct h2_request; -struct h2_push; - -typedef struct h2_response { - int stream_id; - int rst_error; - int http_status; - apr_off_t content_length; - apr_table_t *headers; - apr_table_t *trailers; - const char *sos_filter; -} h2_response; +#include "h2.h" /** * Create the response from the status and parsed header lines. diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index 6076cd1704..e8bce2e21b 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -47,8 +47,6 @@ #include "h2_workers.h" -static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen); - static int h2_session_status_from_apr_status(apr_status_t rv) { if (rv == APR_SUCCESS) { @@ -216,7 +214,7 @@ static int on_invalid_frame_recv_cb(nghttp2_session *ngh2, if (APLOGcdebug(session->c)) { char buffer[256]; - frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); + h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03063) "h2_session(%ld): recv unknown FRAME[%s], frames=%ld/%ld (r/s)", session->id, buffer, (long)session->frames_received, @@ -377,7 +375,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, if (APLOGcdebug(session->c)) { char buffer[256]; - frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); + h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03066) "h2_session(%ld): recv FRAME[%s], frames=%ld/%ld (r/s)", session->id, buffer, (long)session->frames_received, @@ -466,8 +464,8 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, if (APLOGctrace2(session->c)) { char buffer[256]; - frame_print(frame, buffer, - sizeof(buffer)/sizeof(buffer[0])); + h2_util_frame_print(frame, buffer, + sizeof(buffer)/sizeof(buffer[0])); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, "h2_session: on_frame_rcv %s", buffer); } @@ -607,7 +605,7 @@ static int on_frame_send_cb(nghttp2_session *ngh2, if (APLOGcdebug(session->c)) { char buffer[256]; - frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); + h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0])); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03068) "h2_session(%ld): sent FRAME[%s], frames=%ld/%ld (r/s)", session->id, buffer, (long)session->frames_received, @@ -690,7 +688,8 @@ static void h2_session_destroy(h2_session *session) } } -static apr_status_t h2_session_shutdown(h2_session *session, int reason, const char *msg) +static apr_status_t h2_session_shutdown(h2_session *session, int reason, + const char *msg, int force_close) { apr_status_t status = APR_SUCCESS; const char *err = msg; @@ -708,6 +707,11 @@ static apr_status_t h2_session_shutdown(h2_session *session, int reason, const c "session(%ld): sent GOAWAY, err=%d, msg=%s", session->id, reason, err? err : ""); dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, reason, err); + + if (force_close) { + h2_mplx_abort(session->mplx); + } + return status; } @@ -1437,14 +1441,14 @@ apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream) apr_pool_t *pool = h2_stream_detach_pool(stream); /* this may be called while the session has already freed - * some internal structures. */ + * some internal structures or even when the mplx is locked. */ if (session->mplx) { h2_mplx_stream_done(session->mplx, stream->id, stream->rst_error); - if (session->last_stream == stream) { - session->last_stream = NULL; - } } + if (session->last_stream == stream) { + session->last_stream = NULL; + } if (session->streams) { h2_stream_set_remove(session->streams, stream->id); } @@ -1460,84 +1464,6 @@ apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream) return APR_SUCCESS; } -static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen) -{ - char scratch[128]; - size_t s_len = sizeof(scratch)/sizeof(scratch[0]); - - switch (frame->hd.type) { - case NGHTTP2_DATA: { - return apr_snprintf(buffer, maxlen, - "DATA[length=%d, flags=%d, stream=%d, padlen=%d]", - (int)frame->hd.length, frame->hd.flags, - frame->hd.stream_id, (int)frame->data.padlen); - } - case NGHTTP2_HEADERS: { - return apr_snprintf(buffer, maxlen, - "HEADERS[length=%d, hend=%d, stream=%d, eos=%d]", - (int)frame->hd.length, - !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), - frame->hd.stream_id, - !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)); - } - case NGHTTP2_PRIORITY: { - return apr_snprintf(buffer, maxlen, - "PRIORITY[length=%d, flags=%d, stream=%d]", - (int)frame->hd.length, - frame->hd.flags, frame->hd.stream_id); - } - case NGHTTP2_RST_STREAM: { - return apr_snprintf(buffer, maxlen, - "RST_STREAM[length=%d, flags=%d, stream=%d]", - (int)frame->hd.length, - frame->hd.flags, frame->hd.stream_id); - } - case NGHTTP2_SETTINGS: { - if (frame->hd.flags & NGHTTP2_FLAG_ACK) { - return apr_snprintf(buffer, maxlen, - "SETTINGS[ack=1, stream=%d]", - frame->hd.stream_id); - } - return apr_snprintf(buffer, maxlen, - "SETTINGS[length=%d, stream=%d]", - (int)frame->hd.length, frame->hd.stream_id); - } - case NGHTTP2_PUSH_PROMISE: { - return apr_snprintf(buffer, maxlen, - "PUSH_PROMISE[length=%d, hend=%d, stream=%d]", - (int)frame->hd.length, - !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), - frame->hd.stream_id); - } - case NGHTTP2_PING: { - return apr_snprintf(buffer, maxlen, - "PING[length=%d, ack=%d, stream=%d]", - (int)frame->hd.length, - frame->hd.flags&NGHTTP2_FLAG_ACK, - frame->hd.stream_id); - } - case NGHTTP2_GOAWAY: { - size_t len = (frame->goaway.opaque_data_len < s_len)? - frame->goaway.opaque_data_len : s_len-1; - memcpy(scratch, frame->goaway.opaque_data, len); - scratch[len+1] = '\0'; - return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s']", - frame->goaway.error_code, scratch); - } - case NGHTTP2_WINDOW_UPDATE: { - return apr_snprintf(buffer, maxlen, - "WINDOW_UPDATE[stream=%d, incr=%d]", - frame->hd.stream_id, - frame->window_update.window_size_increment); - } - default: - return apr_snprintf(buffer, maxlen, - "type=%d[length=%d, flags=%d, stream=%d]", - frame->hd.type, (int)frame->hd.length, - frame->hd.flags, frame->hd.stream_id); - } -} - int h2_session_push_enabled(h2_session *session) { /* iff we can and they can */ @@ -1791,7 +1717,7 @@ static void h2_session_ev_conn_error(h2_session *session, int arg, const char *m default: ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_session(%ld): conn error -> shutdown", session->id); - h2_session_shutdown(session, arg, msg); + h2_session_shutdown(session, arg, msg, 0); break; } } @@ -1808,7 +1734,7 @@ static void h2_session_ev_proto_error(h2_session *session, int arg, const char * default: ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, "h2_session(%ld): proto error -> shutdown", session->id); - h2_session_shutdown(session, arg, msg); + h2_session_shutdown(session, arg, msg, 0); break; } } @@ -1820,7 +1746,7 @@ static void h2_session_ev_conn_timeout(h2_session *session, int arg, const char transit(session, "conn timeout", H2_SESSION_ST_DONE); break; default: - h2_session_shutdown(session, arg, msg); + h2_session_shutdown(session, arg, msg, 1); transit(session, "conn timeout", H2_SESSION_ST_DONE); break; } @@ -1841,7 +1767,7 @@ static void h2_session_ev_no_io(h2_session *session, int arg, const char *msg) if (!is_accepting_streams(session)) { /* We are no longer accepting new streams and have * finished processing existing ones. Time to leave. */ - h2_session_shutdown(session, arg, msg); + h2_session_shutdown(session, arg, msg, 0); transit(session, "no io", H2_SESSION_ST_DONE); } else { @@ -1919,7 +1845,7 @@ static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char /* nop */ break; default: - h2_session_shutdown(session, arg, msg); + h2_session_shutdown(session, arg, msg, 0); break; } } @@ -1932,7 +1858,7 @@ static void h2_session_ev_pre_close(h2_session *session, int arg, const char *ms /* nop */ break; default: - h2_session_shutdown(session, arg, msg); + h2_session_shutdown(session, arg, msg, 1); h2_conn_io_flush(&session->io); break; } @@ -2035,7 +1961,7 @@ apr_status_t h2_session_process(h2_session *session, int async) ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c); if (!h2_is_acceptable_connection(c, 1)) { update_child_status(session, SERVER_BUSY_READ, "inadequate security"); - h2_session_shutdown(session, NGHTTP2_INADEQUATE_SECURITY, NULL); + h2_session_shutdown(session, NGHTTP2_INADEQUATE_SECURITY, NULL, 1); } else { update_child_status(session, SERVER_BUSY_READ, "init"); @@ -2079,7 +2005,7 @@ apr_status_t h2_session_process(h2_session *session, int async) ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, c, "h2_session(%ld): idle, no data, error", session->id); - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); + dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "timeout"); } } else { @@ -2101,7 +2027,7 @@ apr_status_t h2_session_process(h2_session *session, int async) /* continue reading handling */ } else { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); + dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "error"); } } @@ -2208,7 +2134,7 @@ apr_status_t h2_session_process(h2_session *session, int async) transit(session, "wait cycle", H2_SESSION_ST_BUSY); } else { - h2_session_shutdown(session, H2_ERR_INTERNAL_ERROR, "cond wait error"); + h2_session_shutdown(session, H2_ERR_INTERNAL_ERROR, "cond wait error", 0); } break; diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h index 354b837e17..5bc1d937bd 100644 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@ -37,6 +37,8 @@ * */ +#include "h2.h" + struct apr_thread_mutext_t; struct apr_thread_cond_t; struct h2_ctx; @@ -55,16 +57,6 @@ struct h2_workers; struct nghttp2_session; typedef enum { - H2_SESSION_ST_INIT, /* send initial SETTINGS, etc. */ - H2_SESSION_ST_DONE, /* finished, connection close */ - H2_SESSION_ST_IDLE, /* nothing to write, expecting data inc */ - H2_SESSION_ST_BUSY, /* read/write without stop */ - H2_SESSION_ST_WAIT, /* waiting for tasks reporting back */ - H2_SESSION_ST_LOCAL_SHUTDOWN, /* we announced GOAWAY */ - H2_SESSION_ST_REMOTE_SHUTDOWN, /* client announced GOAWAY */ -} h2_session_state; - -typedef enum { H2_SESSION_EV_INIT, /* session was initialized */ H2_SESSION_EV_LOCAL_GOAWAY, /* we send a GOAWAY */ H2_SESSION_EV_REMOTE_GOAWAY, /* remote send us a GOAWAY */ diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c index fa1ebaeacd..fc2d021ed8 100644 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@ -158,7 +158,8 @@ h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session) { h2_stream *stream = h2_stream_create(id, pool, session); set_state(stream, H2_STREAM_ST_OPEN); - stream->request = h2_request_create(id, pool, session->config); + stream->request = h2_request_create(id, pool, + h2_config_geti(session->config, H2_CONF_SER_HEADERS)); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03082) "h2_stream(%ld-%d): opened", session->id, stream->id); @@ -242,6 +243,9 @@ apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r) } set_state(stream, H2_STREAM_ST_OPEN); status = h2_request_rwrite(stream->request, r); + stream->request->serialize = h2_config_geti(h2_config_rget(r), + H2_CONF_SER_HEADERS); + return status; } diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h index fa219df2a8..e3d71a3f9b 100644 --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@ -16,6 +16,8 @@ #ifndef __mod_h2__h2_stream__ #define __mod_h2__h2_stream__ +#include "h2.h" + /** * A HTTP/2 stream, e.g. a client request+response in HTTP/1.1 terms. * @@ -30,16 +32,6 @@ */ #include "h2_io.h" -typedef enum { - H2_STREAM_ST_IDLE, - H2_STREAM_ST_OPEN, - H2_STREAM_ST_RESV_LOCAL, - H2_STREAM_ST_RESV_REMOTE, - H2_STREAM_ST_CLOSED_INPUT, - H2_STREAM_ST_CLOSED_OUTPUT, - H2_STREAM_ST_CLOSED, -} h2_stream_state_t; - struct h2_mplx; struct h2_priority; struct h2_request; diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c index 510c399730..b1f6bf7f3f 100644 --- a/modules/http2/h2_task.c +++ b/modules/http2/h2_task.c @@ -171,7 +171,7 @@ h2_task *h2_task_create(long session_id, const h2_request *req, task->mplx = mplx; task->request = req; task->input_eos = !req->body; - task->ser_headers = h2_config_geti(req->config, H2_CONF_SER_HEADERS); + task->ser_headers = req->serialize; return task; } @@ -206,9 +206,14 @@ static apr_status_t h2_task_process_request(const h2_request *req, conn_rec *c) if (r && (r->status == HTTP_OK)) { ap_update_child_status(c->sbh, SERVER_BUSY_READ, r); - if (cs) + if (cs) { cs->state = CONN_STATE_HANDLER; + } + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "h2_task(%ld-%d): start process_request", c->id, req->id); ap_process_request(r); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "h2_task(%ld-%d): process_request done", c->id, req->id); /* After the call to ap_process_request, the * request pool will have been deleted. We set * r=NULL here to ensure that any dereference diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c index a02e819bd6..db717d9dc5 100644 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@ -1004,6 +1004,9 @@ static literal IgnoredResponseTrailers[] = { H2_DEF_LITERAL("www-authenticate"), H2_DEF_LITERAL("proxy-authenticate"), }; +static literal IgnoredProxyRespHds[] = { + H2_DEF_LITERAL("alt-svc"), +}; static int ignore_header(const literal *lits, size_t llen, const char *name, size_t nlen) @@ -1036,12 +1039,125 @@ int h2_res_ignore_trailer(const char *name, size_t len) return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len); } -void h2_req_strip_ignored_header(apr_table_t *headers) +int h2_proxy_res_ignore_header(const char *name, size_t len) { - int i; - for (i = 0; i < H2_ALEN(IgnoredRequestHeaders); ++i) { - apr_table_unset(headers, IgnoredRequestHeaders[i].name); + return (h2_req_ignore_header(name, len) + || ignore_header(H2_LIT_ARGS(IgnoredProxyRespHds), name, len)); +} + + +/******************************************************************************* + * frame logging + ******************************************************************************/ + +int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen) +{ + char scratch[128]; + size_t s_len = sizeof(scratch)/sizeof(scratch[0]); + + switch (frame->hd.type) { + case NGHTTP2_DATA: { + return apr_snprintf(buffer, maxlen, + "DATA[length=%d, flags=%d, stream=%d, padlen=%d]", + (int)frame->hd.length, frame->hd.flags, + frame->hd.stream_id, (int)frame->data.padlen); + } + case NGHTTP2_HEADERS: { + return apr_snprintf(buffer, maxlen, + "HEADERS[length=%d, hend=%d, stream=%d, eos=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), + frame->hd.stream_id, + !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)); + } + case NGHTTP2_PRIORITY: { + return apr_snprintf(buffer, maxlen, + "PRIORITY[length=%d, flags=%d, stream=%d]", + (int)frame->hd.length, + frame->hd.flags, frame->hd.stream_id); + } + case NGHTTP2_RST_STREAM: { + return apr_snprintf(buffer, maxlen, + "RST_STREAM[length=%d, flags=%d, stream=%d]", + (int)frame->hd.length, + frame->hd.flags, frame->hd.stream_id); + } + case NGHTTP2_SETTINGS: { + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + return apr_snprintf(buffer, maxlen, + "SETTINGS[ack=1, stream=%d]", + frame->hd.stream_id); + } + return apr_snprintf(buffer, maxlen, + "SETTINGS[length=%d, stream=%d]", + (int)frame->hd.length, frame->hd.stream_id); + } + case NGHTTP2_PUSH_PROMISE: { + return apr_snprintf(buffer, maxlen, + "PUSH_PROMISE[length=%d, hend=%d, stream=%d]", + (int)frame->hd.length, + !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), + frame->hd.stream_id); + } + case NGHTTP2_PING: { + return apr_snprintf(buffer, maxlen, + "PING[length=%d, ack=%d, stream=%d]", + (int)frame->hd.length, + frame->hd.flags&NGHTTP2_FLAG_ACK, + frame->hd.stream_id); + } + case NGHTTP2_GOAWAY: { + size_t len = (frame->goaway.opaque_data_len < s_len)? + frame->goaway.opaque_data_len : s_len-1; + memcpy(scratch, frame->goaway.opaque_data, len); + scratch[len+1] = '\0'; + return apr_snprintf(buffer, maxlen, "GOAWAY[error=%d, reason='%s']", + frame->goaway.error_code, scratch); + } + case NGHTTP2_WINDOW_UPDATE: { + return apr_snprintf(buffer, maxlen, + "WINDOW_UPDATE[stream=%d, incr=%d]", + frame->hd.stream_id, + frame->window_update.window_size_increment); + } + default: + return apr_snprintf(buffer, maxlen, + "type=%d[length=%d, flags=%d, stream=%d]", + frame->hd.type, (int)frame->hd.length, + frame->hd.flags, frame->hd.stream_id); } } +/******************************************************************************* + * push policy + ******************************************************************************/ +void h2_push_policy_determine(struct h2_request *req, apr_pool_t *p, int push_enabled) +{ + h2_push_policy policy = H2_PUSH_NONE; + if (push_enabled) { + const char *val = apr_table_get(req->headers, "accept-push-policy"); + if (val) { + if (ap_find_token(p, val, "fast-load")) { + policy = H2_PUSH_FAST_LOAD; + } + else if (ap_find_token(p, val, "head")) { + policy = H2_PUSH_HEAD; + } + else if (ap_find_token(p, val, "default")) { + policy = H2_PUSH_DEFAULT; + } + else if (ap_find_token(p, val, "none")) { + policy = H2_PUSH_NONE; + } + else { + /* nothing known found in this header, go by default */ + policy = H2_PUSH_DEFAULT; + } + } + else { + policy = H2_PUSH_DEFAULT; + } + } + req->push_policy = policy; +} diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h index 6d86f76a2a..218a57fddf 100644 --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@ -28,6 +28,8 @@ size_t h2_util_header_print(char *buffer, size_t maxlen, void h2_util_camel_case_header(char *s, size_t len); +int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen); + /** * Count the bytes that all key/value pairs in a table have * in length (exlucding terminating 0s), plus additional extra per pair. @@ -40,8 +42,8 @@ apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra); int h2_req_ignore_header(const char *name, size_t len); int h2_req_ignore_trailer(const char *name, size_t len); -void h2_req_strip_ignored_header(apr_table_t *headers); int h2_res_ignore_trailer(const char *name, size_t len); +int h2_proxy_res_ignore_header(const char *name, size_t len); /** * Return != 0 iff the string s contains the token, as specified in @@ -190,4 +192,15 @@ apr_status_t h2_transfer_brigade(apr_bucket_brigade *to, apr_off_t *plen, int *peos); +/** + * Set the push policy for the given request. Takes request headers into + * account, see draft https://tools.ietf.org/html/draft-ruellan-http-accept-push-policy-00 + * for details. + * + * @param req the request to determine the policy for + * @param p the pool to use + * @param push_enabled if HTTP/2 server push is generally enabled for this request + */ +void h2_push_policy_determine(struct h2_request *req, apr_pool_t *p, int push_enabled); + #endif /* defined(__mod_h2__h2_util__) */ diff --git a/modules/http2/mod_http2.c b/modules/http2/mod_http2.c index a3b978c879..8d26cc241a 100644 --- a/modules/http2/mod_http2.c +++ b/modules/http2/mod_http2.c @@ -72,7 +72,7 @@ static int h2_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { void *data = NULL; - const char *mod_h2_init_key = "mod_h2_init_counter"; + const char *mod_h2_init_key = "mod_http2_init_counter"; nghttp2_info *ngh2; apr_status_t status; (void)plog;(void)ptemp; diff --git a/modules/http2/mod_http2.h b/modules/http2/mod_http2.h index a8c58f2c6d..0be8170968 100644 --- a/modules/http2/mod_http2.h +++ b/modules/http2/mod_http2.h @@ -13,8 +13,8 @@ * limitations under the License. */ -#ifndef mod_http2_mod_http2_h -#define mod_http2_mod_http2_h +#ifndef __MOD_HTTP2_H__ +#define __MOD_HTTP2_H__ /** The http2_var_lookup() optional function retrieves HTTP2 environment * variables. */ diff --git a/modules/http2/mod_proxy_http2.c b/modules/http2/mod_proxy_http2.c new file mode 100644 index 0000000000..1b26a11542 --- /dev/null +++ b/modules/http2/mod_proxy_http2.c @@ -0,0 +1,395 @@ +/* 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 <nghttp2/nghttp2.h> + +#include <httpd.h> +#include <mod_proxy.h> + + +#include "mod_proxy_http2.h" +#include "h2_request.h" +#include "h2_util.h" +#include "h2_version.h" +#include "h2_proxy_session.h" + +static void register_hook(apr_pool_t *p); + +AP_DECLARE_MODULE(proxy_http2) = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + NULL, /* command apr_table_t */ + register_hook /* register hooks */ +}; + +static int h2_proxy_post_config(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + void *data = NULL; + const char *init_key = "mod_proxy_http2_init_counter"; + nghttp2_info *ngh2; + apr_status_t status = APR_SUCCESS; + (void)plog;(void)ptemp; + + apr_pool_userdata_get(&data, init_key, s->process->pool); + if ( data == NULL ) { + apr_pool_userdata_set((const void *)1, init_key, + apr_pool_cleanup_null, s->process->pool); + return APR_SUCCESS; + } + + ngh2 = nghttp2_version(0); + ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO() + "mod_proxy_http2 (v%s, nghttp2 %s), initializing...", + MOD_HTTP2_VERSION, ngh2? ngh2->version_str : "unknown"); + + return status; +} + +/** + * canonicalize the url into the request, if it is meant for us. + * slightly modified copy from mod_http + */ +static int proxy_http2_canon(request_rec *r, char *url) +{ + char *host, *path, sport[7]; + char *search = NULL; + const char *err; + const char *scheme; + const char *http_scheme; + apr_port_t port, def_port; + + /* ap_port_of_scheme() */ + if (ap_casecmpstrn(url, "h2c:", 4) == 0) { + url += 4; + scheme = "h2c"; + http_scheme = "http"; + } + else if (ap_casecmpstrn(url, "h2:", 3) == 0) { + url += 3; + scheme = "h2"; + http_scheme = "https"; + } + else { + return DECLINED; + } + port = def_port = ap_proxy_port_of_scheme(http_scheme); + + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "HTTP2: canonicalising URL %s", url); + + /* do syntatic check. + * We break the URL into host, port, path, search + */ + err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port); + if (err) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO() + "error parsing URL %s: %s", url, err); + return HTTP_BAD_REQUEST; + } + + /* + * now parse path/search args, according to rfc1738: + * process the path. + * + * In a reverse proxy, our URL has been processed, so canonicalise + * unless proxy-nocanon is set to say it's raw + * In a forward proxy, we have and MUST NOT MANGLE the original. + */ + switch (r->proxyreq) { + default: /* wtf are we doing here? */ + case PROXYREQ_REVERSE: + if (apr_table_get(r->notes, "proxy-nocanon")) { + path = url; /* this is the raw path */ + } + else { + path = ap_proxy_canonenc(r->pool, url, strlen(url), + enc_path, 0, r->proxyreq); + search = r->args; + } + break; + case PROXYREQ_PROXY: + path = url; + break; + } + + if (path == NULL) { + return HTTP_BAD_REQUEST; + } + + if (port != def_port) { + apr_snprintf(sport, sizeof(sport), ":%d", port); + } + else { + sport[0] = '\0'; + } + + if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */ + host = apr_pstrcat(r->pool, "[", host, "]", NULL); + } + r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport, + "/", path, (search) ? "?" : "", (search) ? search : "", NULL); + return OK; +} + +static apr_status_t proxy_http2_cleanup(const char *scheme, request_rec *r, + proxy_conn_rec *backend) +{ + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "cleanup, releasing connection"); + ap_proxy_release_connection(scheme, backend, r->server); + return OK; +} + +static +int proxy_http2_process_stream(apr_pool_t *p, const char *url, request_rec *r, + proxy_conn_rec **pp_conn, proxy_worker *worker, + proxy_server_conf *conf, char *server_portstr, + int flushall) +{ + int rv = APR_ENOTIMPL; + proxy_conn_rec *p_conn = *pp_conn; + h2_proxy_session *session; + h2_proxy_stream *stream; + + session = h2_proxy_session_setup(r, *pp_conn, conf); + if (!session) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, p_conn->connection, + "session unavailable"); + return HTTP_SERVICE_UNAVAILABLE; + } + + /* TODO + * - enter http2 client processing loop: + * - send any input in datasource callback from r->input_filters + * - await response HEADERs + * - send any DATA to r->output_filters + * - on stream close, check for missing response + * - on certain errors, mark connection for close + */ + rv = h2_proxy_session_open_stream(session, url, r, &stream); + if (rv == OK) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + "process stream(%d): %s %s%s, original: %s", + stream->id, stream->req->method, + stream->req->authority, stream->req->path, + r->the_request); + rv = h2_proxy_stream_process(stream); + } + + if (rv != OK) { + conn_rec *c = r->connection; + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO() + "pass request body failed to %pI (%s) from %s (%s)", + p_conn->addr, p_conn->hostname ? p_conn->hostname: "", + c->client_ip, c->remote_host ? c->remote_host: ""); + } + + return rv; +} + +static int proxy_http2_handler(request_rec *r, + proxy_worker *worker, + proxy_server_conf *conf, + char *url, + const char *proxyname, + apr_port_t proxyport) +{ + const char *proxy_function; + proxy_conn_rec *backend; + char *locurl = url, *u, *firsturl; + apr_size_t slen; + int is_ssl = 0, retry = 0; + int flushall = 0; + int status; + char server_portstr[32]; + conn_rec *c = r->connection; + apr_pool_t *p = r->pool; + apr_uri_t *uri = apr_palloc(p, sizeof(*uri)); + const char *ssl_hostname; + + /* find the scheme */ + if ((url[0] != 'h' && url[0] != 'H') || url[1] != '2') { + return DECLINED; + } + u = strchr(url, ':'); + if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0') { + return DECLINED; + } + slen = (u - url); + switch(slen) { + case 2: + proxy_function = "H2"; + is_ssl = 1; + break; + case 3: + if (url[2] != 'c' && url[2] != 'C') { + return DECLINED; + } + proxy_function = "H2C"; + break; + default: + return DECLINED; + } + + if (apr_table_get(r->subprocess_env, "proxy-flushall")) { + flushall = 1; + } + + /* scheme says, this is for us. */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "H2: serving URL %s", url); + + /* Get a proxy_conn_rec from the worker, might be a new one, might + * be one still open from another request, or it might fail if the + * worker is stopped or in error. */ + if ((status = ap_proxy_acquire_connection(proxy_function, &backend, + worker, r->server)) != OK) { + goto cleanup; + } + + backend->is_ssl = is_ssl; + if (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(backend, r); + } + + while (retry < 2) { + conn_rec *backconn; + + /* Step One: Determine the URL to connect to (might be a proxy), + * initialize the backend accordingly and determine the server + * port string we can expect in responses. */ + if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend, + uri, &locurl, proxyname, + proxyport, server_portstr, + sizeof(server_portstr))) != OK) { + break; + } + + if (!ssl_hostname && backend->ssl_hostname) { + /* When reusing connections and finding sockets closed, the proxy + * framework loses the ssl_hostname setting. This is vital for us, + * so we save it once it is known. */ + ssl_hostname = apr_pstrdup(r->pool, backend->ssl_hostname); + } + + if (!retry) { + firsturl = locurl; + /* http does a prefetch here, so that it immediately can start sending + * when the backend connection comes online. This minimizes the risk of + * reusing a connection only to experience a keepalive close. + */ + } + else { + /* On a retry, we'd expect to see the same url again */ + AP_DEBUG_ASSERT(strcmp(firsturl, locurl) == 0); + } + + /* Step Two: Make the Connection (or check that an already existing + * socket is still usable). On success, we have a socket connected to + * backend->hostname. */ + if (ap_proxy_connect_backend(proxy_function, backend, worker, r->server)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO() + "H2: failed to make connection to backend: %s", + backend->hostname); + status = HTTP_SERVICE_UNAVAILABLE; + break; + } + + /* Step Three: Create conn_rec for the socket we have open now. */ + backconn = backend->connection; + if (!backconn) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO() + "setup new connection: is_ssl=%d %s %s %s, was %s", + backend->is_ssl, + backend->ssl_hostname, r->hostname, backend->hostname, + ssl_hostname); + if ((status = ap_proxy_connection_create(proxy_function, backend, + c, r->server)) != OK) { + break; + } + backconn = backend->connection; + + /* + * 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 (ssl_hostname) { + apr_table_setn(backend->connection->notes, + "proxy-request-hostname", ssl_hostname); + } + + if (backend->is_ssl) { + apr_table_setn(backend->connection->notes, + "proxy-request-alpn-protos", "h2"); + } + + /* Step Three-and-a-Half: See if the socket is still connected (if + * desired). Note: Since ap_proxy_connect_backend just above does + * the same check (unconditionally), this step is not required when + * backend's socket/connection is reused (ie. no Step Three). + */ + if (worker->s->ping_timeout_set && worker->s->ping_timeout < 0 && + !ap_proxy_is_socket_connected(backend->sock)) { + backend->close = 1; + ap_log_rerror(APLOG_MARK, APLOG_INFO, status, r, APLOGNO() + "socket check failed to %pI (%s)", + worker->cp->addr, worker->s->hostname); + retry++; + continue; + } + } + + /* Step Four: Send the Request in a new HTTP/2 stream and + * loop until we got the response or encounter errors. + */ + if ((status = proxy_http2_process_stream(p, url, r, &backend, worker, + conf, server_portstr, + flushall)) != OK) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO() + "H2: failed to process request: %s", + r->the_request); + backend->close = 1; + if (backend) { + proxy_run_detach_backend(r, backend); + } + } + break; + } + + /* clean up before return */ +cleanup: + if (backend) { + if (status != OK) { + backend->close = 1; + } + proxy_http2_cleanup(proxy_function, r, backend); + } + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, status, r, "leaving handler"); + return status; +} + +static void register_hook(apr_pool_t *p) +{ + ap_hook_post_config(h2_proxy_post_config, NULL, NULL, APR_HOOK_MIDDLE); + + proxy_hook_scheme_handler(proxy_http2_handler, NULL, NULL, APR_HOOK_FIRST); + proxy_hook_canon_handler(proxy_http2_canon, NULL, NULL, APR_HOOK_FIRST); +} + diff --git a/modules/http2/mod_proxy_http2.h b/modules/http2/mod_proxy_http2.h new file mode 100644 index 0000000000..7da84f0fce --- /dev/null +++ b/modules/http2/mod_proxy_http2.h @@ -0,0 +1,20 @@ +/* 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_PROXY_HTTP2_H__ +#define __MOD_PROXY_HTTP2_H__ + + +#endif |