summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Eissing <icing@apache.org>2016-02-08 17:53:45 +0100
committerStefan Eissing <icing@apache.org>2016-02-08 17:53:45 +0100
commit3567f9f7c669c7b82e69484dc099a375250eff0d (patch)
tree4b45ef808cdabe2aec0d845ec3b5110dac2a0932
parentlet proxy handler forward ALPN protocol strings for ssl proxy connections (diff)
downloadapache2-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
-rw-r--r--CHANGES3
-rw-r--r--modules/http2/config.m426
-rw-r--r--modules/http2/h2.h142
-rw-r--r--modules/http2/h2_config.c1
-rw-r--r--modules/http2/h2_conn.c2
-rw-r--r--modules/http2/h2_filter.c2
-rw-r--r--modules/http2/h2_h2.c12
-rw-r--r--modules/http2/h2_h2.h41
-rw-r--r--modules/http2/h2_io.h3
-rw-r--r--modules/http2/h2_mplx.c196
-rw-r--r--modules/http2/h2_mplx.h2
-rw-r--r--modules/http2/h2_private.h15
-rw-r--r--modules/http2/h2_proxy_session.c639
-rw-r--r--modules/http2/h2_proxy_session.h69
-rw-r--r--modules/http2/h2_push.c37
-rw-r--r--modules/http2/h2_push.h20
-rw-r--r--modules/http2/h2_request.c67
-rw-r--r--modules/http2/h2_request.h43
-rw-r--r--modules/http2/h2_response.h13
-rw-r--r--modules/http2/h2_session.c126
-rw-r--r--modules/http2/h2_session.h12
-rw-r--r--modules/http2/h2_stream.c6
-rw-r--r--modules/http2/h2_stream.h12
-rw-r--r--modules/http2/h2_task.c9
-rw-r--r--modules/http2/h2_util.c124
-rw-r--r--modules/http2/h2_util.h15
-rw-r--r--modules/http2/mod_http2.c2
-rw-r--r--modules/http2/mod_http2.h4
-rw-r--r--modules/http2/mod_proxy_http2.c395
-rw-r--r--modules/http2/mod_proxy_http2.h20
30 files changed, 1660 insertions, 398 deletions
diff --git a/CHANGES b/CHANGES
index 56e4d7268b..c189177327 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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