]> granicus.if.org Git - apache/commitdiff
new experimental http2 proxy module for h2: and h2c: proxy urls
authorStefan Eissing <icing@apache.org>
Mon, 8 Feb 2016 16:53:45 +0000 (16:53 +0000)
committerStefan Eissing <icing@apache.org>
Mon, 8 Feb 2016 16:53:45 +0000 (16:53 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1729209 13f79535-47bb-0310-9956-ffa450edef68

30 files changed:
CHANGES
modules/http2/config.m4
modules/http2/h2.h [new file with mode: 0644]
modules/http2/h2_config.c
modules/http2/h2_conn.c
modules/http2/h2_filter.c
modules/http2/h2_h2.c
modules/http2/h2_h2.h
modules/http2/h2_io.h
modules/http2/h2_mplx.c
modules/http2/h2_mplx.h
modules/http2/h2_private.h
modules/http2/h2_proxy_session.c [new file with mode: 0644]
modules/http2/h2_proxy_session.h [new file with mode: 0644]
modules/http2/h2_push.c
modules/http2/h2_push.h
modules/http2/h2_request.c
modules/http2/h2_request.h
modules/http2/h2_response.h
modules/http2/h2_session.c
modules/http2/h2_session.h
modules/http2/h2_stream.c
modules/http2/h2_stream.h
modules/http2/h2_task.c
modules/http2/h2_util.c
modules/http2/h2_util.h
modules/http2/mod_http2.c
modules/http2/mod_http2.h
modules/http2/mod_proxy_http2.c [new file with mode: 0644]
modules/http2/mod_proxy_http2.h [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 56e4d7268b19ea07b3b3f70bad9726f7a32acd2e..c189177327aa91f639ab66e8776790de0e820d25 100644 (file)
--- 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. 
index f0cfb608871a7e36ee93ca8f98fef4a9e8112fc2..ebbd2d2a441703a6294eea6f9561092f4b92c1fb 100644 (file)
@@ -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 (file)
index 0000000..5429444
--- /dev/null
@@ -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__) */
index dfab2d79df7f344c29e617b8666e6cb1f825397a..0c1e6c469b9806c8a72e2fc4354ba096066faa38 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <apr_strings.h>
 
+#include "h2.h"
 #include "h2_alt_svc.h"
 #include "h2_ctx.h"
 #include "h2_conn.h"
index 3162365abef755abdeaeabe163d28364e78caadb..daeeb515008bb7c989f4f9cffd6aaecd472268cf 100644 (file)
@@ -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;
 }
 
index 87ac9df5345eba5a5edfba07226956a840ad9e88..8330ace15048185d2fd0778f069c72cfafba113b 100644 (file)
@@ -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;
index 9fd072bbe3c10948e772b2eb301598e65f20ca4d..719042f9d4a723199d44f9d1d1b3f2c6e30df81b 100644 (file)
@@ -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 *);
index 563abe3fded1654bfc6f8bb6eccfc8ca9644dd78..592001e9928e407b223dc27e0746cb00283b4ea4 100644 (file)
@@ -28,47 +28,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
index acaa56fcb7d37c06860da2d1ca73f14e361ea60f..e557636443364823b12e6a51899ec1f421efeaa3 100644 (file)
@@ -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;
 
index b2374da90792c9c57c1a74dc459d74ce8f2ef2ba..21fb0864e05459ebf5f28e9af0120756979dd90a 100644 (file)
     } 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;
 }
index 4948af920df812d85e92905d7f8f35d810cb724a..024401dbe5195413fba65552777b6b0388a41431 100644 (file)
@@ -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.
index eb24fa1a21034f996e98caa3a485ed30253a5b93..383adb1f0ebf396f0095c7151880cc2fbf346e73 100644 (file)
@@ -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 (file)
index 0000000..a40f825
--- /dev/null
@@ -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 (file)
index 0000000..598a2a0
--- /dev/null
@@ -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 */
index 842382441eaf0740f565c20138ae1f0daca50fa7..a8b7c8591c9fbb4ea823b4abbf288ebf33408557 100644 (file)
@@ -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 
  *
index b9e7219fce10cb6e4783fb1f4a3c0f17264a914b..d3519dcbfe345910ba41d9d81f39f76b76aeb97d 100644 (file)
 #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;
@@ -65,17 +60,6 @@ apr_array_header_t *h2_push_collect(apr_pool_t *p,
                                     const struct h2_request *req, 
                                     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.
  * 
index 1672db33ebd50948e606f83e29920014b31e6d0e..a2c899144d470b89bfacaff2e2272bbe4f09629b 100644 (file)
 #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;
 }
 
index cc01ed1238b36115c928bcf826b5962f83ad885a..946bd34852493cedca35efaf50e43896754a1086 100644 (file)
 #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);
 
index 59140ee300586813da8ea0e784392e9600424ddd..ca57c532e631cbc8719763258ec75ef2b6c02429 100644 (file)
 #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.
index 6076cd170426a2d2468c66cfda5327cc7a2e5e16..e8bce2e21ba6a85dfeb79f4330b061ae05860b0b 100644 (file)
@@ -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;
                 
index 354b837e17bfe3478053338a6cc863eacc0ee37f..5bc1d937bda11eec68d93ee416d055e4006a7b45 100644 (file)
@@ -37,6 +37,8 @@
  *
  */
 
+#include "h2.h"
+
 struct apr_thread_mutext_t;
 struct apr_thread_cond_t;
 struct h2_ctx;
@@ -54,16 +56,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 */
index fa1ebaeacd04542a10649b2bc529283913266a66..fc2d021ed817e8ed24bc8606fa7396ea6c573b3d 100644 (file)
@@ -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;
 }
 
index fa219df2a86afaa831dc8d51b5d7481d43ca6584..e3d71a3f9b75cf4f048a63a4191a226598ffdf0c 100644 (file)
@@ -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.
  * 
  */
 #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;
index 510c399730ab171970e6553c9a9c3f252aedcfec..b1f6bf7f3fb70e9cbc3c461cafe345aa815c3dc7 100644 (file)
@@ -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
index a02e819bd651719a9b2981b16fb3160c18fc8409..db717d9dc5093e2bf6afc649227951f79c904d0c 100644 (file)
@@ -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;
+}
 
index 6d86f76a2a212646c444bf846f1eb8c8f0b1cfbc..218a57fddfed22bf3dc1d115f06efcf4cfd024a1 100644 (file)
@@ -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__) */
index a3b978c879093524c5830b869fe8390cdd668e9f..8d26cc241a949049fa2a245a38cda5db18f4d97e 100644 (file)
@@ -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;
index a8c58f2c6d06395d13c6319f8ee8206ef4c04e8a..0be8170968dccaf0b72aac897eb9d30ae366c6a4 100644 (file)
@@ -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 (file)
index 0000000..1b26a11
--- /dev/null
@@ -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 (file)
index 0000000..7da84f0
--- /dev/null
@@ -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