summaryrefslogtreecommitdiffstats
path: root/modules/http2
diff options
context:
space:
mode:
Diffstat (limited to 'modules/http2')
-rw-r--r--modules/http2/config2.m41
-rw-r--r--modules/http2/h2.h19
-rw-r--r--modules/http2/h2_bucket_beam.c60
-rw-r--r--modules/http2/h2_bucket_beam.h19
-rw-r--r--modules/http2/h2_c1_io.c28
-rw-r--r--modules/http2/h2_c1_io.h3
-rw-r--r--modules/http2/h2_c2.c192
-rw-r--r--modules/http2/h2_c2_filter.c50
-rw-r--r--modules/http2/h2_config.c140
-rw-r--r--modules/http2/h2_config.h4
-rw-r--r--modules/http2/h2_conn_ctx.h4
-rw-r--r--modules/http2/h2_headers.c14
-rw-r--r--modules/http2/h2_mplx.c113
-rw-r--r--modules/http2/h2_mplx.h18
-rw-r--r--modules/http2/h2_proxy_session.c98
-rw-r--r--modules/http2/h2_proxy_session.h5
-rw-r--r--modules/http2/h2_push.c3
-rw-r--r--modules/http2/h2_request.c148
-rw-r--r--modules/http2/h2_session.c75
-rw-r--r--modules/http2/h2_session.h3
-rw-r--r--modules/http2/h2_stream.c136
-rw-r--r--modules/http2/h2_stream.h18
-rw-r--r--modules/http2/h2_switch.c5
-rw-r--r--modules/http2/h2_util.c19
-rw-r--r--modules/http2/h2_version.h4
-rw-r--r--modules/http2/h2_ws.c362
-rw-r--r--modules/http2/h2_ws.h35
-rw-r--r--modules/http2/mod_http2.c2
-rw-r--r--modules/http2/mod_http2.dsp4
-rw-r--r--modules/http2/mod_http2.h23
-rw-r--r--modules/http2/mod_proxy_http2.c110
31 files changed, 1447 insertions, 268 deletions
diff --git a/modules/http2/config2.m4 b/modules/http2/config2.m4
index f89f5ba..c4579c4 100644
--- a/modules/http2/config2.m4
+++ b/modules/http2/config2.m4
@@ -37,6 +37,7 @@ h2_stream.lo dnl
h2_switch.lo dnl
h2_util.lo dnl
h2_workers.lo dnl
+h2_ws.lo dnl
"
dnl
diff --git a/modules/http2/h2.h b/modules/http2/h2.h
index 250e726..f496a6d 100644
--- a/modules/http2/h2.h
+++ b/modules/http2/h2.h
@@ -20,6 +20,8 @@
#include <apr_version.h>
#include <ap_mmn.h>
+#include <nghttp2/nghttp2ver.h>
+
struct h2_session;
struct h2_stream;
@@ -33,6 +35,20 @@ struct h2_stream;
#define H2_USE_PIPES (APR_FILES_AS_SOCKETS && APR_VERSION_AT_LEAST(1,6,0))
#endif
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 129)
+#define H2_USE_POLLFD_FROM_CONN 1
+#else
+#define H2_USE_POLLFD_FROM_CONN 0
+#endif
+
+/* WebSockets support requires apr 1.7.0 for apr_encode.h, plus the
+ * WebSockets features of nghttp2 1.34.0 and later. */
+#if H2_USE_PIPES && defined(NGHTTP2_VERSION_NUM) && NGHTTP2_VERSION_NUM >= 0x012200 && APR_VERSION_AT_LEAST(1,7,0)
+#define H2_USE_WEBSOCKETS 1
+#else
+#define H2_USE_WEBSOCKETS 0
+#endif
+
/**
* The magic PRIamble of RFC 7540 that is always sent when starting
* a h2 communication.
@@ -62,6 +78,8 @@ extern const char *H2_MAGIC_TOKEN;
#define H2_HEADER_AUTH_LEN 10
#define H2_HEADER_PATH ":path"
#define H2_HEADER_PATH_LEN 5
+#define H2_HEADER_PROTO ":protocol"
+#define H2_HEADER_PROTO_LEN 9
#define H2_CRLF "\r\n"
/* Size of the frame header itself in HTTP/2 */
@@ -153,6 +171,7 @@ struct h2_request {
const char *scheme;
const char *authority;
const char *path;
+ const char *protocol;
apr_table_t *headers;
apr_time_t request_time;
diff --git a/modules/http2/h2_bucket_beam.c b/modules/http2/h2_bucket_beam.c
index cbf7f34..6978254 100644
--- a/modules/http2/h2_bucket_beam.c
+++ b/modules/http2/h2_bucket_beam.c
@@ -24,6 +24,7 @@
#include <httpd.h>
#include <http_protocol.h>
+#include <http_request.h>
#include <http_log.h>
#include "h2_private.h"
@@ -156,6 +157,23 @@ static void purge_consumed_buckets(h2_bucket_beam *beam)
* from sender thread only */
while (!H2_BLIST_EMPTY(&beam->buckets_consumed)) {
b = H2_BLIST_FIRST(&beam->buckets_consumed);
+ if(AP_BUCKET_IS_EOR(b)) {
+ APR_BUCKET_REMOVE(b);
+ H2_BLIST_INSERT_TAIL(&beam->buckets_eor, b);
+ }
+ else {
+ apr_bucket_delete(b);
+ }
+ }
+}
+
+static void purge_eor_buckets(h2_bucket_beam *beam)
+{
+ apr_bucket *b;
+ /* delete all sender buckets in purge brigade, needs to be called
+ * from sender thread only */
+ while (!H2_BLIST_EMPTY(&beam->buckets_eor)) {
+ b = H2_BLIST_FIRST(&beam->buckets_eor);
apr_bucket_delete(b);
}
}
@@ -250,12 +268,13 @@ static void beam_shutdown(h2_bucket_beam *beam, apr_shutdown_how_e how)
if (how == APR_SHUTDOWN_READWRITE) {
beam->cons_io_cb = NULL;
beam->recv_cb = NULL;
+ beam->eagain_cb = NULL;
}
/* shutdown sender (or both)? */
if (how != APR_SHUTDOWN_READ) {
- h2_blist_cleanup(&beam->buckets_to_send);
purge_consumed_buckets(beam);
+ h2_blist_cleanup(&beam->buckets_to_send);
}
}
@@ -263,6 +282,7 @@ static apr_status_t beam_cleanup(void *data)
{
h2_bucket_beam *beam = data;
beam_shutdown(beam, APR_SHUTDOWN_READWRITE);
+ purge_eor_buckets(beam);
beam->pool = NULL; /* the pool is clearing now */
return APR_SUCCESS;
}
@@ -295,6 +315,7 @@ apr_status_t h2_beam_create(h2_bucket_beam **pbeam, conn_rec *from,
H2_BLIST_INIT(&beam->buckets_to_send);
H2_BLIST_INIT(&beam->buckets_consumed);
+ H2_BLIST_INIT(&beam->buckets_eor);
beam->tx_mem_limits = 1;
beam->max_buf_size = max_buf_size;
beam->timeout = timeout;
@@ -565,6 +586,9 @@ cleanup:
rv = APR_ECONNABORTED;
}
H2_BEAM_LOG(beam, from, APLOG_TRACE2, rv, "end send", sender_bb);
+ if(rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv) && sender_bb != NULL) {
+ apr_brigade_cleanup(sender_bb);
+ }
apr_thread_mutex_unlock(beam->lock);
return rv;
}
@@ -724,6 +748,9 @@ transfer:
leave:
H2_BEAM_LOG(beam, to, APLOG_TRACE2, rv, "end receive", bb);
+ if (rv == APR_EAGAIN && beam->eagain_cb) {
+ beam->eagain_cb(beam->eagain_ctx, beam);
+ }
apr_thread_mutex_unlock(beam->lock);
return rv;
}
@@ -746,6 +773,15 @@ void h2_beam_on_received(h2_bucket_beam *beam,
apr_thread_mutex_unlock(beam->lock);
}
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+ h2_beam_ev_callback *eagain_cb, void *ctx)
+{
+ apr_thread_mutex_lock(beam->lock);
+ beam->eagain_cb = eagain_cb;
+ beam->eagain_ctx = ctx;
+ apr_thread_mutex_unlock(beam->lock);
+}
+
void h2_beam_on_send(h2_bucket_beam *beam,
h2_beam_ev_callback *send_cb, void *ctx)
{
@@ -823,3 +859,25 @@ int h2_beam_report_consumption(h2_bucket_beam *beam)
apr_thread_mutex_unlock(beam->lock);
return rv;
}
+
+int h2_beam_is_complete(h2_bucket_beam *beam)
+{
+ int rv = 0;
+
+ apr_thread_mutex_lock(beam->lock);
+ if (beam->closed)
+ rv = 1;
+ else {
+ apr_bucket *b;
+ for (b = H2_BLIST_FIRST(&beam->buckets_to_send);
+ b != H2_BLIST_SENTINEL(&beam->buckets_to_send);
+ b = APR_BUCKET_NEXT(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ rv = 1;
+ break;
+ }
+ }
+ }
+ apr_thread_mutex_unlock(beam->lock);
+ return rv;
+}
diff --git a/modules/http2/h2_bucket_beam.h b/modules/http2/h2_bucket_beam.h
index 2a9d5f0..c58ce98 100644
--- a/modules/http2/h2_bucket_beam.h
+++ b/modules/http2/h2_bucket_beam.h
@@ -48,6 +48,7 @@ struct h2_bucket_beam {
apr_pool_t *pool;
h2_blist buckets_to_send;
h2_blist buckets_consumed;
+ h2_blist buckets_eor;
apr_size_t max_buf_size;
apr_interval_time_t timeout;
@@ -66,6 +67,8 @@ struct h2_bucket_beam {
void *recv_ctx;
h2_beam_ev_callback *send_cb; /* event: buckets were added in h2_beam_send() */
void *send_ctx;
+ h2_beam_ev_callback *eagain_cb; /* event: a receive results in ARP_EAGAIN */
+ void *eagain_ctx;
apr_off_t recv_bytes; /* amount of bytes transferred in h2_beam_receive() */
apr_off_t recv_bytes_reported; /* amount of bytes reported as received via callback */
@@ -205,6 +208,16 @@ void h2_beam_on_received(h2_bucket_beam *beam,
h2_beam_ev_callback *recv_cb, void *ctx);
/**
+ * Register a callback to be invoked on the receiver side whenever
+ * APR_EAGAIN is being returned in h2_beam_receive().
+ * @param beam the beam to set the callback on
+ * @param egain_cb the callback or NULL, called before APR_EAGAIN is returned
+ * @param ctx the context to use in callback invocation
+ */
+void h2_beam_on_eagain(h2_bucket_beam *beam,
+ h2_beam_ev_callback *eagain_cb, void *ctx);
+
+/**
* Register a call back from the sender side to be invoked when send
* has added buckets to the beam.
* Unregister by passing a NULL on_send_cb.
@@ -245,4 +258,10 @@ apr_off_t h2_beam_get_buffered(h2_bucket_beam *beam);
*/
apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam);
+/**
+ * @return != 0 iff beam has been closed or has an EOS bucket buffered
+ * waiting to be received.
+ */
+int h2_beam_is_complete(h2_bucket_beam *beam);
+
#endif /* h2_bucket_beam_h */
diff --git a/modules/http2/h2_c1_io.c b/modules/http2/h2_c1_io.c
index ade8836..5ed4ee8 100644
--- a/modules/http2/h2_c1_io.c
+++ b/modules/http2/h2_c1_io.c
@@ -260,9 +260,22 @@ static apr_status_t read_to_scratch(h2_c1_io *io, apr_bucket *b)
static apr_status_t pass_output(h2_c1_io *io, int flush)
{
conn_rec *c = io->session->c1;
- apr_off_t bblen;
+ apr_off_t bblen = 0;
apr_status_t rv;
-
+
+ if (io->is_passing) {
+ /* recursive call, may be triggered by an H2EOS bucket
+ * being destroyed and triggering sending more data? */
+ AP_DEBUG_ASSERT(0);
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(10456)
+ "h2_c1_io(%ld): recursive call of h2_c1_io_pass. "
+ "Denied to prevent output corruption. This "
+ "points to a bug in the HTTP/2 implementation.",
+ c->id);
+ return APR_EGENERAL;
+ }
+ io->is_passing = 1;
+
append_scratch(io);
if (flush) {
if (!APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output))) {
@@ -271,17 +284,16 @@ static apr_status_t pass_output(h2_c1_io *io, int flush)
}
}
if (APR_BRIGADE_EMPTY(io->output)) {
- return APR_SUCCESS;
+ rv = APR_SUCCESS;
+ goto cleanup;
}
-
+
io->unflushed = !APR_BUCKET_IS_FLUSH(APR_BRIGADE_LAST(io->output));
apr_brigade_length(io->output, 0, &bblen);
C1_IO_BB_LOG(c, 0, APLOG_TRACE2, "out", io->output);
-
+
rv = ap_pass_brigade(c->output_filters, io->output);
if (APR_SUCCESS != rv) goto cleanup;
-
- io->buffered_len = 0;
io->bytes_written += (apr_size_t)bblen;
if (io->write_size < WRITE_SIZE_MAX
@@ -309,6 +321,8 @@ cleanup:
c->id, (long)bblen);
}
apr_brigade_cleanup(io->output);
+ io->buffered_len = 0;
+ io->is_passing = 0;
return rv;
}
diff --git a/modules/http2/h2_c1_io.h b/modules/http2/h2_c1_io.h
index d891ffb..c4dac38 100644
--- a/modules/http2/h2_c1_io.h
+++ b/modules/http2/h2_c1_io.h
@@ -44,7 +44,8 @@ typedef struct {
apr_off_t buffered_len;
apr_off_t flush_threshold;
unsigned int is_flushed : 1;
-
+ unsigned int is_passing : 1;
+
char *scratch;
apr_size_t ssize;
apr_size_t slen;
diff --git a/modules/http2/h2_c2.c b/modules/http2/h2_c2.c
index 44a08d0..a955200 100644
--- a/modules/http2/h2_c2.c
+++ b/modules/http2/h2_c2.c
@@ -48,8 +48,10 @@
#include "h2_headers.h"
#include "h2_session.h"
#include "h2_stream.h"
+#include "h2_ws.h"
#include "h2_c2.h"
#include "h2_util.h"
+#include "mod_http2.h"
static module *mpm_module;
@@ -133,10 +135,22 @@ apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s)
APR_PROTO_TCP, pool);
}
+static void h2_c2_log_io(conn_rec *c2, apr_off_t bytes_sent)
+{
+ if (bytes_sent && h2_c_logio_add_bytes_out) {
+ h2_c_logio_add_bytes_out(c2, bytes_sent);
+ }
+}
+
void h2_c2_destroy(conn_rec *c2)
{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2,
"h2_c2(%s): destroy", c2->log_id);
+ if(!c2->aborted && conn_ctx && conn_ctx->bytes_sent) {
+ h2_c2_log_io(c2, conn_ctx->bytes_sent);
+ }
apr_pool_destroy(c2->pool);
}
@@ -146,6 +160,10 @@ void h2_c2_abort(conn_rec *c2, conn_rec *from)
AP_DEBUG_ASSERT(conn_ctx);
AP_DEBUG_ASSERT(conn_ctx->stream_id);
+ if(!c2->aborted && conn_ctx->bytes_sent) {
+ h2_c2_log_io(c2, conn_ctx->bytes_sent);
+ }
+
if (conn_ctx->beam_in) {
h2_beam_abort(conn_ctx->beam_in, from);
}
@@ -157,6 +175,7 @@ void h2_c2_abort(conn_rec *c2, conn_rec *from)
typedef struct {
apr_bucket_brigade *bb; /* c2: data in holding area */
+ unsigned did_upgrade_eos:1; /* for Upgrade, we added an extra EOS */
} h2_c2_fctx_in_t;
static apr_status_t h2_c2_filter_in(ap_filter_t* f,
@@ -200,7 +219,17 @@ static apr_status_t h2_c2_filter_in(ap_filter_t* f,
APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
}
}
-
+
+ /* If this is a HTTP Upgrade, it means the request we process
+ * has not Content, although the stream is not necessarily closed.
+ * On first read, we insert an EOS to signal processing that it
+ * has the complete body. */
+ if (conn_ctx->is_upgrade && !fctx->did_upgrade_eos) {
+ b = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(fctx->bb, b);
+ fctx->did_upgrade_eos = 1;
+ }
+
while (APR_BRIGADE_EMPTY(fctx->bb)) {
/* Get more input data for our request. */
if (APLOGctrace2(f->c)) {
@@ -326,42 +355,13 @@ receive:
static apr_status_t beam_out(conn_rec *c2, h2_conn_ctx_t *conn_ctx, apr_bucket_brigade* bb)
{
- apr_off_t written, header_len = 0;
+ apr_off_t written = 0;
apr_status_t rv;
- if (h2_c_logio_add_bytes_out) {
- /* mod_logio wants to report the number of bytes written in a
- * response, including header and footer fields. Since h2 converts
- * those during c1 processing into the HPACKed h2 HEADER frames,
- * we need to give mod_logio something here and count just the
- * raw lengths of all headers in the buckets. */
- apr_bucket *b;
- for (b = APR_BRIGADE_FIRST(bb);
- b != APR_BRIGADE_SENTINEL(bb);
- b = APR_BUCKET_NEXT(b)) {
-#if AP_HAS_RESPONSE_BUCKETS
- if (AP_BUCKET_IS_RESPONSE(b)) {
- header_len += (apr_off_t)response_length_estimate(b->data);
- }
- if (AP_BUCKET_IS_HEADERS(b)) {
- header_len += (apr_off_t)headers_length_estimate(b->data);
- }
-#else
- if (H2_BUCKET_IS_HEADERS(b)) {
- header_len += (apr_off_t)h2_bucket_headers_headers_length(b);
- }
-#endif /* AP_HAS_RESPONSE_BUCKETS */
- }
- }
-
rv = h2_beam_send(conn_ctx->beam_out, c2, bb, APR_BLOCK_READ, &written);
-
if (APR_STATUS_IS_EAGAIN(rv)) {
rv = APR_SUCCESS;
}
- if (written && h2_c_logio_add_bytes_out) {
- h2_c_logio_add_bytes_out(c2, written + header_len);
- }
return rv;
}
@@ -403,30 +403,56 @@ static apr_status_t h2_c2_filter_out(ap_filter_t* f, apr_bucket_brigade* bb)
return rv;
}
-static void check_push(request_rec *r, const char *tag)
+static int addn_headers(void *udata, const char *name, const char *value)
{
- apr_array_header_t *push_list = h2_config_push_list(r);
+ apr_table_t *dest = udata;
+ apr_table_addn(dest, name, value);
+ return 1;
+}
- if (!r->expecting_100 && push_list && push_list->nelts > 0) {
- int i, old_status;
- const char *old_line;
+static void check_early_hints(request_rec *r, const char *tag)
+{
+ apr_array_header_t *push_list = h2_config_push_list(r);
+ apr_table_t *early_headers = h2_config_early_headers(r);
+
+ if (!r->expecting_100 &&
+ ((push_list && push_list->nelts > 0) ||
+ (early_headers && !apr_is_empty_table(early_headers)))) {
+ int have_hints = 0, i;
+
+ if (push_list && push_list->nelts > 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "%s, early announcing %d resources for push",
+ tag, push_list->nelts);
+ for (i = 0; i < push_list->nelts; ++i) {
+ h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res);
+ apr_table_add(r->headers_out, "Link",
+ apr_psprintf(r->pool, "<%s>; rel=preload%s",
+ push->uri_ref, push->critical? "; critical" : ""));
+ }
+ have_hints = 1;
+ }
+ if (early_headers && !apr_is_empty_table(early_headers)) {
+ apr_table_do(addn_headers, r->headers_out, early_headers, NULL);
+ have_hints = 1;
+ }
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "%s, early announcing %d resources for push",
- tag, push_list->nelts);
- for (i = 0; i < push_list->nelts; ++i) {
- h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res);
- apr_table_add(r->headers_out, "Link",
- apr_psprintf(r->pool, "<%s>; rel=preload%s",
- push->uri_ref, push->critical? "; critical" : ""));
+ if (have_hints) {
+ int old_status;
+ const char *old_line;
+
+ if (h2_config_rgeti(r, H2_CONF_PUSH) == 0 &&
+ h2_config_sgeti(r->server, H2_CONF_PUSH) != 0) {
+ apr_table_setn(r->connection->notes, H2_PUSH_MODE_NOTE, "0");
+ }
+ old_status = r->status;
+ old_line = r->status_line;
+ r->status = 103;
+ r->status_line = "103 Early Hints";
+ ap_send_interim_response(r, 1);
+ r->status = old_status;
+ r->status_line = old_line;
}
- old_status = r->status;
- old_line = r->status_line;
- r->status = 103;
- r->status_line = "103 Early Hints";
- ap_send_interim_response(r, 1);
- r->status = old_status;
- r->status_line = old_line;
}
}
@@ -439,11 +465,42 @@ static int c2_hook_fixups(request_rec *r)
return DECLINED;
}
- check_push(r, "late_fixup");
+ check_early_hints(r, "late_fixup");
return DECLINED;
}
+static apr_status_t http2_get_pollfd_from_conn(conn_rec *c,
+ struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout)
+{
+#if H2_USE_PIPES
+ if (c->master) {
+ h2_conn_ctx_t *ctx = h2_conn_ctx_get(c);
+ if (ctx) {
+ if (ctx->beam_in && ctx->pipe_in[H2_PIPE_OUT]) {
+ pfd->desc_type = APR_POLL_FILE;
+ pfd->desc.f = ctx->pipe_in[H2_PIPE_OUT];
+ if (ptimeout)
+ *ptimeout = h2_beam_timeout_get(ctx->beam_in);
+ }
+ else {
+ /* no input */
+ pfd->desc_type = APR_NO_DESC;
+ if (ptimeout)
+ *ptimeout = -1;
+ }
+ return APR_SUCCESS;
+ }
+ }
+#else
+ (void)c;
+ (void)pfd;
+ (void)ptimeout;
+#endif /* H2_USE_PIPES */
+ return APR_ENOTIMPL;
+}
+
#if AP_HAS_RESPONSE_BUCKETS
static void c2_pre_read_request(request_rec *r, conn_rec *c2)
@@ -545,8 +602,14 @@ void h2_c2_register_hooks(void)
/* We need to manipulate the standard HTTP/1.1 protocol filters and
* install our own. This needs to be done very early. */
ap_hook_pre_read_request(c2_pre_read_request, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_post_read_request(c2_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
+ ap_hook_post_read_request(c2_post_read_request, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+#if H2_USE_POLLFD_FROM_CONN
+ ap_hook_get_pollfd_from_conn(http2_get_pollfd_from_conn, NULL, NULL,
+ APR_HOOK_MIDDLE);
+#endif
+ APR_REGISTER_OPTIONAL_FN(http2_get_pollfd_from_conn);
c2_net_in_filter_handle =
ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
@@ -655,11 +718,21 @@ static apr_status_t c2_process(h2_conn_ctx_t *conn_ctx, conn_rec *c)
{
const h2_request *req = conn_ctx->request;
conn_state_t *cs = c->cs;
- request_rec *r;
+ request_rec *r = NULL;
const char *tenc;
apr_time_t timeout;
+ apr_status_t rv = APR_SUCCESS;
+
+ if (req->protocol && !strcmp("websocket", req->protocol)) {
+ req = h2_ws_rewrite_request(req, c, conn_ctx->beam_in == NULL);
+ if (!req) {
+ rv = APR_EGENERAL;
+ goto cleanup;
+ }
+ }
+
+ r = h2_create_request_rec(req, c, conn_ctx->beam_in == NULL);
- r = h2_create_request_rec(conn_ctx->request, c, conn_ctx->beam_in == NULL);
if (!r) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"h2_c2(%s-%d): create request_rec failed, r=NULL",
@@ -722,7 +795,7 @@ static apr_status_t c2_process(h2_conn_ctx_t *conn_ctx, conn_rec *c)
cs->state = CONN_STATE_WRITE_COMPLETION;
cleanup:
- return APR_SUCCESS;
+ return rv;
}
conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent,
@@ -793,7 +866,7 @@ static int h2_c2_hook_post_read_request(request_rec *r)
{
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
- if (conn_ctx && conn_ctx->stream_id) {
+ if (conn_ctx && conn_ctx->stream_id && ap_is_initial_req(r)) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
"h2_c2(%s-%d): adding request filters",
@@ -845,6 +918,11 @@ void h2_c2_register_hooks(void)
* install our own. This needs to be done very early. */
ap_hook_post_read_request(h2_c2_hook_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST);
+#if H2_USE_POLLFD_FROM_CONN
+ ap_hook_get_pollfd_from_conn(http2_get_pollfd_from_conn, NULL, NULL,
+ APR_HOOK_MIDDLE);
+#endif
+ APR_REGISTER_OPTIONAL_FN(http2_get_pollfd_from_conn);
ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in,
NULL, AP_FTYPE_NETWORK);
diff --git a/modules/http2/h2_c2_filter.c b/modules/http2/h2_c2_filter.c
index 37254fc..554f88b 100644
--- a/modules/http2/h2_c2_filter.c
+++ b/modules/http2/h2_c2_filter.c
@@ -39,6 +39,7 @@
#include "h2_c2.h"
#include "h2_mplx.h"
#include "h2_request.h"
+#include "h2_ws.h"
#include "h2_util.h"
@@ -108,20 +109,39 @@ apr_status_t h2_c2_filter_request_in(ap_filter_t *f,
/* This filter is a one-time wonder */
ap_remove_input_filter(f);
- if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) && conn_ctx->stream_id) {
- if (conn_ctx->request->http_status != H2_HTTP_STATUS_UNSET) {
+ if (f->c->master && (conn_ctx = h2_conn_ctx_get(f->c)) &&
+ conn_ctx->stream_id) {
+ const h2_request *req = conn_ctx->request;
+
+ if (req->http_status == H2_HTTP_STATUS_UNSET &&
+ req->protocol && !strcmp("websocket", req->protocol)) {
+ req = h2_ws_rewrite_request(req, f->c, conn_ctx->beam_in == NULL);
+ if (!req)
+ return APR_EGENERAL;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2_filter_request_in(%s): adding request bucket",
+ conn_ctx->id);
+ b = h2_request_create_bucket(req, f->r);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
/* error was encountered preparing this request */
- b = ap_bucket_error_create(conn_ctx->request->http_status, NULL, f->r->pool,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+ "h2_c2_filter_request_in(%s): adding error bucket %d",
+ conn_ctx->id, req->http_status);
+ b = ap_bucket_error_create(req->http_status, NULL, f->r->pool,
f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
return APR_SUCCESS;
}
- b = h2_request_create_bucket(conn_ctx->request, f->r);
- APR_BRIGADE_INSERT_TAIL(bb, b);
+
if (!conn_ctx->beam_in) {
b = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
}
+
return APR_SUCCESS;
}
@@ -511,10 +531,10 @@ static apr_status_t pass_response(h2_conn_ctx_t *conn_ctx, ap_filter_t *f,
{
apr_bucket *b;
apr_status_t status;
-
h2_headers *response = h2_headers_create(parser->http_status,
make_table(parser),
- NULL, 0, parser->pool);
+ parser->c->notes,
+ 0, parser->pool);
apr_brigade_cleanup(parser->tmp);
b = h2_bucket_headers_create(parser->c->bucket_alloc, response);
APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
@@ -636,9 +656,11 @@ apr_status_t h2_c2_filter_catch_h1_out(ap_filter_t* f, apr_bucket_brigade* bb)
int result = ap_map_http_request_error(conn_ctx->last_err,
HTTP_INTERNAL_SERVER_ERROR);
request_rec *r = h2_create_request_rec(conn_ctx->request, f->c, 1);
- ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
- b = ap_bucket_eor_create(f->c->bucket_alloc, r);
- APR_BRIGADE_INSERT_TAIL(bb, b);
+ if (r) {
+ ap_die((result >= 400)? result : HTTP_INTERNAL_SERVER_ERROR, r);
+ b = ap_bucket_eor_create(f->c->bucket_alloc, r);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ }
}
}
/* There are cases where we need to parse a serialized http/1.1 response.
@@ -892,10 +914,10 @@ static apr_status_t read_and_chunk(ap_filter_t *f, h2_conn_ctx_t *conn_ctx,
}
apr_status_t h2_c2_filter_request_in(ap_filter_t* f,
- apr_bucket_brigade* bb,
- ap_input_mode_t mode,
- apr_read_type_e block,
- apr_off_t readbytes)
+ apr_bucket_brigade* bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
{
h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
h2_chunk_filter_t *fctx = f->ctx;
diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c
index eea4be2..22653d4 100644
--- a/modules/http2/h2_config.c
+++ b/modules/http2/h2_config.c
@@ -70,11 +70,15 @@ typedef struct h2_config {
int push_diary_size; /* # of entries in push diary */
int copy_files; /* if files shall be copied vs setaside on output */
apr_array_header_t *push_list; /* list of h2_push_res configurations */
+ apr_table_t *early_headers; /* HTTP headers for a 103 response */
int early_hints; /* support status code 103 */
int padding_bits;
int padding_always;
int output_buffered;
apr_interval_time_t stream_timeout;/* beam timeout */
+ int max_data_frame_len; /* max # bytes in a single h2 DATA frame */
+ int proxy_requests; /* act as forward proxy */
+ int h2_websockets; /* if mod_h2 negotiating WebSockets */
} h2_config;
typedef struct h2_dir_config {
@@ -82,6 +86,7 @@ typedef struct h2_dir_config {
int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
int h2_push; /* if HTTP/2 server push is enabled */
apr_array_header_t *push_list; /* list of h2_push_res configurations */
+ apr_table_t *early_headers; /* HTTP headers for a 103 response */
int early_hints; /* support status code 103 */
apr_interval_time_t stream_timeout;/* beam timeout */
} h2_dir_config;
@@ -105,11 +110,15 @@ static h2_config defconf = {
256, /* push diary size */
0, /* copy files across threads */
NULL, /* push list */
+ NULL, /* early headers */
0, /* early hints, http status 103 */
0, /* padding bits */
1, /* padding always */
1, /* stream output buffered */
-1, /* beam timeout */
+ 0, /* max DATA frame len, 0 == no extra limit */
+ 0, /* forward proxy */
+ 0, /* WebSockets negotiation, enabled */
};
static h2_dir_config defdconf = {
@@ -117,6 +126,7 @@ static h2_dir_config defdconf = {
-1, /* HTTP/1 Upgrade support */
-1, /* HTTP/2 server push enabled */
NULL, /* push list */
+ NULL, /* early headers */
-1, /* early hints, http status 103 */
-1, /* beam timeout */
};
@@ -148,11 +158,15 @@ void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
conf->push_diary_size = DEF_VAL;
conf->copy_files = DEF_VAL;
conf->push_list = NULL;
+ conf->early_headers = NULL;
conf->early_hints = DEF_VAL;
conf->padding_bits = DEF_VAL;
conf->padding_always = DEF_VAL;
conf->output_buffered = DEF_VAL;
conf->stream_timeout = DEF_VAL;
+ conf->max_data_frame_len = DEF_VAL;
+ conf->proxy_requests = DEF_VAL;
+ conf->h2_websockets = DEF_VAL;
return conf;
}
@@ -191,10 +205,19 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
else {
n->push_list = add->push_list? add->push_list : base->push_list;
}
+ if (add->early_headers && base->early_headers) {
+ n->early_headers = apr_table_overlay(pool, add->early_headers, base->early_headers);
+ }
+ else {
+ n->early_headers = add->early_headers? add->early_headers : base->early_headers;
+ }
n->early_hints = H2_CONFIG_GET(add, base, early_hints);
n->padding_bits = H2_CONFIG_GET(add, base, padding_bits);
n->padding_always = H2_CONFIG_GET(add, base, padding_always);
n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
+ n->max_data_frame_len = H2_CONFIG_GET(add, base, max_data_frame_len);
+ n->proxy_requests = H2_CONFIG_GET(add, base, proxy_requests);
+ n->h2_websockets = H2_CONFIG_GET(add, base, h2_websockets);
return n;
}
@@ -232,6 +255,12 @@ void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
else {
n->push_list = add->push_list? add->push_list : base->push_list;
}
+ if (add->early_headers && base->early_headers) {
+ n->early_headers = apr_table_overlay(pool, add->early_headers, base->early_headers);
+ }
+ else {
+ n->early_headers = add->early_headers? add->early_headers : base->early_headers;
+ }
n->early_hints = H2_CONFIG_GET(add, base, early_hints);
n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
return n;
@@ -278,6 +307,12 @@ static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t v
return H2_CONFIG_GET(conf, &defconf, output_buffered);
case H2_CONF_STREAM_TIMEOUT:
return H2_CONFIG_GET(conf, &defconf, stream_timeout);
+ case H2_CONF_MAX_DATA_FRAME_LEN:
+ return H2_CONFIG_GET(conf, &defconf, max_data_frame_len);
+ case H2_CONF_PROXY_REQUESTS:
+ return H2_CONFIG_GET(conf, &defconf, proxy_requests);
+ case H2_CONF_WEBSOCKETS:
+ return H2_CONFIG_GET(conf, &defconf, h2_websockets);
default:
return DEF_VAL;
}
@@ -337,6 +372,15 @@ static void h2_srv_config_seti(h2_config *conf, h2_config_var_t var, int val)
case H2_CONF_OUTPUT_BUFFER:
H2_CONFIG_SET(conf, output_buffered, val);
break;
+ case H2_CONF_MAX_DATA_FRAME_LEN:
+ H2_CONFIG_SET(conf, max_data_frame_len, val);
+ break;
+ case H2_CONF_PROXY_REQUESTS:
+ H2_CONFIG_SET(conf, proxy_requests, val);
+ break;
+ case H2_CONF_WEBSOCKETS:
+ H2_CONFIG_SET(conf, h2_websockets, val);
+ break;
default:
break;
}
@@ -502,6 +546,18 @@ apr_array_header_t *h2_config_push_list(request_rec *r)
return sconf? sconf->push_list : NULL;
}
+apr_table_t *h2_config_early_headers(request_rec *r)
+{
+ const h2_config *sconf;
+ const h2_dir_config *conf = h2_config_rget(r);
+
+ if (conf && conf->early_headers) {
+ return conf->early_headers;
+ }
+ sconf = h2_config_sget(r->server);
+ return sconf? sconf->early_headers : NULL;
+}
+
const struct h2_priority *h2_cconfig_get_priority(conn_rec *c, const char *content_type)
{
const h2_config *conf = h2_config_get(c);
@@ -583,6 +639,17 @@ static const char *h2_conf_set_stream_max_mem_size(cmd_parms *cmd,
return NULL;
}
+static const char *h2_conf_set_max_data_frame_len(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ int val = (int)apr_atoi64(value);
+ if (val < 0) {
+ return "value must be 0 or larger";
+ }
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MAX_DATA_FRAME_LEN, val);
+ return NULL;
+}
+
static const char *h2_conf_set_session_extra_files(cmd_parms *cmd,
void *dirconf, const char *value)
{
@@ -632,6 +699,26 @@ static const char *h2_conf_set_push(cmd_parms *cmd, void *dirconf, const char *v
return "value must be On or Off";
}
+static const char *h2_conf_set_websockets(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+#if H2_USE_WEBSOCKETS
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 1);
+ return NULL;
+#elif !H2_USE_PIPES
+ return "HTTP/2 WebSockets are not supported on this platform";
+#else
+ return "HTTP/2 WebSockets are not supported in this server version";
+#endif
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_WEBSOCKETS, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
const char *ctype, const char *sdependency,
const char *sweight)
@@ -812,6 +899,37 @@ static const char *h2_conf_add_push_res(cmd_parms *cmd, void *dirconf,
return NULL;
}
+static const char *h2_conf_add_early_hint(cmd_parms *cmd, void *dirconf,
+ const char *name, const char *value)
+{
+ apr_table_t *hds, **phds;
+
+ if(!name || !*name)
+ return "Early Hint header name must not be empty";
+ if(!value)
+ return "Early Hint header value must not be empty";
+ while (apr_isspace(*value))
+ ++value;
+ if(!*value)
+ return "Early Hint header value must not be empty/only space";
+ if (*ap_scan_http_field_content(value))
+ return "Early Hint header value contains invalid characters";
+
+ if (cmd->path) {
+ phds = &((h2_dir_config*)dirconf)->early_headers;
+ }
+ else {
+ phds = &(h2_config_sget(cmd->server))->early_headers;
+ }
+ hds = *phds;
+ if (!hds) {
+ *phds = hds = apr_table_make(cmd->pool, 10);
+ }
+ apr_table_add(hds, name, value);
+
+ return NULL;
+}
+
static const char *h2_conf_set_early_hints(cmd_parms *cmd,
void *dirconf, const char *value)
{
@@ -872,6 +990,20 @@ static const char *h2_conf_set_stream_timeout(cmd_parms *cmd,
return NULL;
}
+static const char *h2_conf_set_proxy_requests(cmd_parms *cmd,
+ void *dirconf, const char *value)
+{
+ if (!strcasecmp(value, "On")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PROXY_REQUESTS, 1);
+ return NULL;
+ }
+ else if (!strcasecmp(value, "Off")) {
+ CONFIG_CMD_SET(cmd, dirconf, H2_CONF_PROXY_REQUESTS, 0);
+ return NULL;
+ }
+ return "value must be On or Off";
+}
+
void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
apr_time_t *pidle_limit)
{
@@ -937,6 +1069,14 @@ const command_rec h2_cmds[] = {
RSRC_CONF, "set stream output buffer on/off"),
AP_INIT_TAKE1("H2StreamTimeout", h2_conf_set_stream_timeout, NULL,
RSRC_CONF, "set stream timeout"),
+ AP_INIT_TAKE1("H2MaxDataFrameLen", h2_conf_set_max_data_frame_len, NULL,
+ RSRC_CONF, "maximum number of bytes in a single HTTP/2 DATA frame"),
+ AP_INIT_TAKE2("H2EarlyHint", h2_conf_add_early_hint, NULL,
+ OR_FILEINFO|OR_AUTHCFG, "add a a 'Link:' header for a 103 Early Hints response."),
+ AP_INIT_TAKE1("H2ProxyRequests", h2_conf_set_proxy_requests, NULL,
+ OR_FILEINFO, "Enables forward proxy requests via HTTP/2"),
+ AP_INIT_TAKE1("H2WebSockets", h2_conf_set_websockets, NULL,
+ RSRC_CONF, "off to disable WebSockets over HTTP/2"),
AP_END_CMD
};
diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h
index 6d2e65f..15242db 100644
--- a/modules/http2/h2_config.h
+++ b/modules/http2/h2_config.h
@@ -43,6 +43,9 @@ typedef enum {
H2_CONF_PADDING_ALWAYS,
H2_CONF_OUTPUT_BUFFER,
H2_CONF_STREAM_TIMEOUT,
+ H2_CONF_MAX_DATA_FRAME_LEN,
+ H2_CONF_PROXY_REQUESTS,
+ H2_CONF_WEBSOCKETS,
} h2_config_var_t;
struct apr_hash_t;
@@ -86,6 +89,7 @@ int h2_config_rgeti(request_rec *r, h2_config_var_t var);
apr_int64_t h2_config_rgeti64(request_rec *r, h2_config_var_t var);
apr_array_header_t *h2_config_push_list(request_rec *r);
+apr_table_t *h2_config_early_headers(request_rec *r);
void h2_get_workers_config(server_rec *s, int *pminw, int *pmaxw,
diff --git a/modules/http2/h2_conn_ctx.h b/modules/http2/h2_conn_ctx.h
index 35987bc..3b44856 100644
--- a/modules/http2/h2_conn_ctx.h
+++ b/modules/http2/h2_conn_ctx.h
@@ -53,7 +53,8 @@ struct h2_conn_ctx_t {
const struct h2_request *request; /* c2: the request to process */
struct h2_bucket_beam *beam_out; /* c2: data out, created from req_pool */
struct h2_bucket_beam *beam_in; /* c2: data in or NULL, borrowed from request stream */
- unsigned int input_chunked; /* c2: if input needs HTTP/1.1 chunking applied */
+ unsigned input_chunked:1; /* c2: if input needs HTTP/1.1 chunking applied */
+ unsigned is_upgrade:1; /* c2: if requst is a HTTP Upgrade */
apr_file_t *pipe_in[2]; /* c2: input produced notification pipe */
apr_pollfd_t pfd; /* c1: poll socket input, c2: NUL */
@@ -61,6 +62,7 @@ struct h2_conn_ctx_t {
int has_final_response; /* final HTTP response passed on out */
apr_status_t last_err; /* APR_SUCCES or last error encountered in filters */
+ apr_off_t bytes_sent; /* c2: bytes acutaly sent via c1 */
/* atomic */ apr_uint32_t started; /* c2: processing was started */
apr_time_t started_at; /* c2: when processing started */
/* atomic */ apr_uint32_t done; /* c2: processing has finished */
diff --git a/modules/http2/h2_headers.c b/modules/http2/h2_headers.c
index cbc7b01..d9b3fd0 100644
--- a/modules/http2/h2_headers.c
+++ b/modules/http2/h2_headers.c
@@ -90,9 +90,18 @@ h2_headers *h2_bucket_headers_get(apr_bucket *b)
return NULL;
}
+static void bucket_destroy(void *data)
+{
+ h2_bucket_headers *h = data;
+
+ if (apr_bucket_shared_destroy(h)) {
+ apr_bucket_free(h);
+ }
+}
+
const apr_bucket_type_t h2_bucket_type_headers = {
"H2HEADERS", 5, APR_BUCKET_METADATA,
- apr_bucket_destroy_noop,
+ bucket_destroy,
bucket_read,
apr_bucket_setaside_noop,
apr_bucket_split_notimpl,
@@ -144,6 +153,9 @@ h2_headers *h2_headers_rcreate(request_rec *r, int status,
const apr_table_t *header, apr_pool_t *pool)
{
h2_headers *headers = h2_headers_create(status, header, r->notes, 0, pool);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, headers->status, r,
+ "h2_headers_rcreate(%ld): status=%d",
+ (long)r->connection->id, status);
if (headers->status == HTTP_FORBIDDEN) {
request_rec *r_prev;
for (r_prev = r; r_prev != NULL; r_prev = r_prev->prev) {
diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c
index 99c47ea..2aeea42 100644
--- a/modules/http2/h2_mplx.c
+++ b/modules/http2/h2_mplx.c
@@ -146,6 +146,7 @@ static void m_stream_cleanup(h2_mplx *m, h2_stream *stream)
if (c2_ctx->beam_in) {
h2_beam_on_send(c2_ctx->beam_in, NULL, NULL);
h2_beam_on_received(c2_ctx->beam_in, NULL, NULL);
+ h2_beam_on_eagain(c2_ctx->beam_in, NULL, NULL);
h2_beam_on_consumed(c2_ctx->beam_in, NULL, NULL);
}
}
@@ -333,7 +334,6 @@ h2_mplx *h2_mplx_c1_create(int child_num, apr_uint32_t id, h2_stream *stream0,
apr_pollset_add(m->pollset, &conn_ctx->pfd);
}
- m->scratch_r = apr_pcalloc(m->pool, sizeof(*m->scratch_r));
m->max_spare_transits = 3;
m->c2_transits = apr_array_make(m->pool, (int)m->max_spare_transits,
sizeof(h2_c2_transit*));
@@ -394,6 +394,31 @@ apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
return APR_SUCCESS;
}
+typedef struct {
+ int stream_count;
+ int stream_want_send;
+} stream_iter_aws_t;
+
+static int m_stream_want_send_data(void *ctx, void *stream)
+{
+ stream_iter_aws_t *x = ctx;
+ ++x->stream_count;
+ if (h2_stream_wants_send_data(stream))
+ ++x->stream_want_send;
+ return 1;
+}
+
+int h2_mplx_c1_all_streams_want_send_data(h2_mplx *m)
+{
+ stream_iter_aws_t x;
+ x.stream_count = 0;
+ x.stream_want_send = 0;
+ H2_MPLX_ENTER(m);
+ h2_ihash_iter(m->streams, m_stream_want_send_data, &x);
+ H2_MPLX_LEAVE(m);
+ return x.stream_count && (x.stream_count == x.stream_want_send);
+}
+
static int m_report_stream_iter(void *ctx, void *val) {
h2_mplx *m = ctx;
h2_stream *stream = val;
@@ -441,6 +466,8 @@ static int m_stream_cancel_iter(void *ctx, void *val) {
return 0;
}
+static void c1_purge_streams(h2_mplx *m);
+
void h2_mplx_c1_destroy(h2_mplx *m)
{
apr_status_t status;
@@ -509,7 +536,9 @@ void h2_mplx_c1_destroy(h2_mplx *m)
h2_ihash_count(m->shold));
h2_ihash_iter(m->shold, m_unexpected_stream_iter, m);
}
-
+
+ c1_purge_streams(m);
+
m->c1->aborted = old_aborted;
H2_MPLX_LEAVE(m);
@@ -542,16 +571,6 @@ const h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id)
}
-static void c1_update_scoreboard(h2_mplx *m, h2_stream *stream)
-{
- if (stream->c2) {
- m->scratch_r->connection = stream->c2;
- m->scratch_r->bytes_sent = stream->out_frame_octets;
- ap_increment_counts(m->c1->sbh, m->scratch_r);
- m->scratch_r->connection = NULL;
- }
-}
-
static void c1_purge_streams(h2_mplx *m)
{
h2_stream *stream;
@@ -561,8 +580,6 @@ static void c1_purge_streams(h2_mplx *m)
stream = APR_ARRAY_IDX(m->spurge, i, h2_stream*);
ap_assert(stream->state == H2_SS_CLEANUP);
- c1_update_scoreboard(m, stream);
-
if (stream->input) {
h2_beam_destroy(stream->input, m->c1);
stream->input = NULL;
@@ -585,6 +602,15 @@ static void c1_purge_streams(h2_mplx *m)
apr_array_clear(m->spurge);
}
+void h2_mplx_c1_going_keepalive(h2_mplx *m)
+{
+ H2_MPLX_ENTER_ALWAYS(m);
+ if (m->spurge->nelts) {
+ c1_purge_streams(m);
+ }
+ H2_MPLX_LEAVE(m);
+}
+
apr_status_t h2_mplx_c1_poll(h2_mplx *m, apr_interval_time_t timeout,
stream_ev_callback *on_stream_input,
stream_ev_callback *on_stream_output,
@@ -653,8 +679,12 @@ static apr_status_t c1_process_stream(h2_mplx *m,
if (APLOGctrace1(m->c1)) {
const h2_request *r = stream->request;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
- H2_STRM_MSG(stream, "process %s %s://%s%s"),
- r->method, r->scheme, r->authority, r->path);
+ H2_STRM_MSG(stream, "process %s%s%s %s%s%s%s"),
+ r->protocol? r->protocol : "",
+ r->protocol? " " : "",
+ r->method, r->scheme? r->scheme : "",
+ r->scheme? "://" : "",
+ r->authority, r->path? r->path: "");
}
stream->scheduled = 1;
@@ -765,6 +795,19 @@ static void c2_beam_input_read_notify(void *ctx, h2_bucket_beam *beam)
}
}
+static void c2_beam_input_read_eagain(void *ctx, h2_bucket_beam *beam)
+{
+ conn_rec *c = ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+ /* installed in the input bucket beams when we use pipes.
+ * Drain the pipe just before the beam returns APR_EAGAIN.
+ * A clean state for allowing polling on the pipe to rest
+ * when the beam is empty */
+ if (conn_ctx && conn_ctx->pipe_in[H2_PIPE_OUT]) {
+ h2_util_drain_pipe(conn_ctx->pipe_in[H2_PIPE_OUT]);
+ }
+}
+
static void c2_beam_output_write_notify(void *ctx, h2_bucket_beam *beam)
{
conn_rec *c = ctx;
@@ -809,6 +852,9 @@ static apr_status_t c2_setup_io(h2_mplx *m, conn_rec *c2, h2_stream *stream, h2_
c2->pool, c2->pool);
if (APR_SUCCESS != rv) goto cleanup;
#endif
+ h2_beam_on_eagain(stream->input, c2_beam_input_read_eagain, c2);
+ if (!h2_beam_empty(stream->input))
+ c2_beam_input_write_notify(c2, stream->input);
}
cleanup:
@@ -915,6 +961,15 @@ static void s_c2_done(h2_mplx *m, conn_rec *c2, h2_conn_ctx_t *conn_ctx)
"h2_c2(%s-%d): processing finished without final response",
conn_ctx->id, conn_ctx->stream_id);
c2->aborted = 1;
+ if (conn_ctx->beam_out)
+ h2_beam_abort(conn_ctx->beam_out, c2);
+ }
+ else if (!conn_ctx->beam_out || !h2_beam_is_complete(conn_ctx->beam_out)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, conn_ctx->last_err, c2,
+ "h2_c2(%s-%d): processing finished with incomplete output",
+ conn_ctx->id, conn_ctx->stream_id);
+ c2->aborted = 1;
+ h2_beam_abort(conn_ctx->beam_out, c2);
}
else if (!c2->aborted) {
s_mplx_be_happy(m, c2, conn_ctx);
@@ -1064,14 +1119,32 @@ static int reset_is_acceptable(h2_stream *stream)
return 1; /* otherwise, be forgiving */
}
-apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id)
+apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id, h2_stream *stream)
{
- h2_stream *stream;
apr_status_t status = APR_SUCCESS;
+ int registered;
H2_MPLX_ENTER_ALWAYS(m);
- stream = h2_ihash_get(m->streams, stream_id);
- if (stream && !reset_is_acceptable(stream)) {
+ registered = (h2_ihash_get(m->streams, stream_id) != NULL);
+ if (!stream) {
+ /* a RST might arrive so late, we have already forgotten
+ * about it. Seems ok. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+ H2_MPLX_MSG(m, "RST on unknown stream %d"), stream_id);
+ AP_DEBUG_ASSERT(!registered);
+ }
+ else if (!registered) {
+ /* a RST on a stream that mplx has not been told about, but
+ * which the session knows. Very early and annoying. */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c1,
+ H2_STRM_MSG(stream, "very early RST, drop"));
+ h2_stream_set_monitor(stream, NULL);
+ h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+ h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
+ m_stream_cleanup(m, stream);
+ m_be_annoyed(m);
+ }
+ else if (!reset_is_acceptable(stream)) {
m_be_annoyed(m);
}
H2_MPLX_LEAVE(m);
diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h
index 1f79aa8..860f916 100644
--- a/modules/http2/h2_mplx.h
+++ b/modules/http2/h2_mplx.h
@@ -99,8 +99,6 @@ struct h2_mplx {
struct h2_workers *workers; /* h2 workers process wide instance */
- request_rec *scratch_r; /* pseudo request_rec for scoreboard reporting */
-
apr_uint32_t max_spare_transits; /* max number of transit pools idling */
apr_array_header_t *c2_transits; /* base pools for running c2 connections */
};
@@ -194,11 +192,17 @@ typedef int h2_mplx_stream_cb(struct h2_stream *s, void *userdata);
apr_status_t h2_mplx_c1_streams_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
/**
+ * Return != 0 iff all open streams want to send data
+ */
+int h2_mplx_c1_all_streams_want_send_data(h2_mplx *m);
+
+/**
* A stream has been RST_STREAM by the client. Abort
* any processing going on and remove from processing
* queue.
*/
-apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id);
+apr_status_t h2_mplx_c1_client_rst(h2_mplx *m, int stream_id,
+ struct h2_stream *stream);
/**
* Get readonly access to a stream for a secondary connection.
@@ -212,6 +216,14 @@ const struct h2_stream *h2_mplx_c2_stream_get(h2_mplx *m, int stream_id);
*/
apr_status_t h2_mplx_worker_pop_c2(h2_mplx *m, conn_rec **out_c2);
+
+/**
+ * Session processing is entering KEEPALIVE, e.g. giving control
+ * to the MPM for monitoring incoming socket events only.
+ * Last chance for maintenance work before losing control.
+ */
+void h2_mplx_c1_going_keepalive(h2_mplx *m);
+
#define H2_MPLX_MSG(m, msg) \
"h2_mplx(%d-%lu): "msg, m->child_num, (unsigned long)m->id
diff --git a/modules/http2/h2_proxy_session.c b/modules/http2/h2_proxy_session.c
index c3f2ff3..db22301 100644
--- a/modules/http2/h2_proxy_session.c
+++ b/modules/http2/h2_proxy_session.c
@@ -37,6 +37,7 @@ typedef struct h2_proxy_stream {
const char *url;
request_rec *r;
+ conn_rec *cfront;
h2_proxy_request *req;
const char *real_server_uri;
const char *p_server_uri;
@@ -401,7 +402,7 @@ static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream,
char *s = apr_pstrndup(stream->r->pool, v, vlen);
apr_table_setn(stream->r->notes, "proxy-status", s);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
"h2_proxy_stream(%s-%d): got status %s",
stream->session->id, stream->id, s);
stream->r->status = (int)apr_atoi64(s);
@@ -413,7 +414,7 @@ static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream,
return APR_SUCCESS;
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
"h2_proxy_stream(%s-%d): on_header %s: %s",
stream->session->id, stream->id, n, v);
if (!h2_proxy_res_ignore_header(n, nlen)) {
@@ -425,7 +426,7 @@ static apr_status_t h2_proxy_stream_add_header_out(h2_proxy_stream *stream,
h2_proxy_util_camel_case_header(hname, nlen);
hvalue = apr_pstrndup(stream->pool, v, vlen);
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
"h2_proxy_stream(%s-%d): got header %s: %s",
stream->session->id, stream->id, hname, hvalue);
process_proxy_header(headers, stream, hname, hvalue);
@@ -532,22 +533,21 @@ static int stream_response_data(nghttp2_session *ngh2, uint8_t flags,
h2_proxy_stream_end_headers_out(stream);
}
stream->data_received += len;
-
- b = apr_bucket_transient_create((const char*)data, len,
- stream->r->connection->bucket_alloc);
+ b = apr_bucket_transient_create((const char*)data, len,
+ stream->cfront->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->output, b);
/* always flush after a DATA frame, as we have no other indication
* of buffer use */
- b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
+ b = apr_bucket_flush_create(stream->cfront->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->output, b);
-
+
status = ap_pass_brigade(stream->r->output_filters, stream->output);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03359)
"h2_proxy_session(%s): stream=%d, response DATA %ld, %ld"
" total", session->id, stream_id, (long)len,
(long)stream->data_received);
if (status != APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03344)
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03344)
"h2_proxy_session(%s): passing output on stream %d",
session->id, stream->id);
nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE,
@@ -818,7 +818,7 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
{
h2_proxy_stream *stream;
apr_uri_t puri;
- const char *authority, *scheme, *path;
+ const char *authority, *scheme, *path, *orig_host;
apr_status_t status;
proxy_dir_conf *dconf;
@@ -827,12 +827,13 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
stream->pool = r->pool;
stream->url = url;
stream->r = r;
+ stream->cfront = r->connection;
stream->standalone = standalone;
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->input = apr_brigade_create(stream->pool, stream->cfront->bucket_alloc);
+ stream->output = apr_brigade_create(stream->pool, stream->cfront->bucket_alloc);
stream->req = h2_proxy_req_create(1, stream->pool);
@@ -841,13 +842,14 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
return status;
scheme = (strcmp(puri.scheme, "h2")? "http" : "https");
-
+ orig_host = apr_table_get(r->headers_in, "Host");
+ if (orig_host == NULL) {
+ orig_host = r->hostname;
+ }
+
dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
if (dconf->preserve_host) {
- authority = apr_table_get(r->headers_in, "Host");
- if (authority == NULL) {
- authority = r->hostname;
- }
+ authority = orig_host;
}
else {
authority = puri.hostname;
@@ -856,7 +858,7 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
/* port info missing and port is not default for scheme: append */
authority = apr_psprintf(stream->pool, "%s:%d", authority, puri.port);
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
"authority=%s from uri.hostname=%s and uri.port=%d",
authority, puri.hostname, puri.port);
}
@@ -877,8 +879,6 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
if (dconf->add_forwarded_headers) {
if (PROXYREQ_REVERSE == r->proxyreq) {
- const char *buf;
-
/* Add X-Forwarded-For: so that the upstream has a chance to
* determine, where the original request came from.
*/
@@ -888,8 +888,9 @@ static apr_status_t open_stream(h2_proxy_session *session, const char *url,
/* Add X-Forwarded-Host: so that upstream knows what the
* original request hostname was.
*/
- if ((buf = apr_table_get(r->headers_in, "Host"))) {
- apr_table_mergen(stream->req->headers, "X-Forwarded-Host", buf);
+ if (orig_host) {
+ apr_table_mergen(stream->req->headers, "X-Forwarded-Host",
+ orig_host);
}
/* Add X-Forwarded-Server: so that upstream knows what the
@@ -943,7 +944,7 @@ static apr_status_t submit_stream(h2_proxy_session *session, h2_proxy_stream *st
rv = nghttp2_submit_request(session->ngh2, NULL,
hd->nv, hd->nvlen, pp, stream);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03363)
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->cfront, APLOGNO(03363)
"h2_proxy_session(%s): submit %s%s -> %d",
session->id, stream->req->authority, stream->req->path,
rv);
@@ -1088,7 +1089,7 @@ apr_status_t h2_proxy_session_submit(h2_proxy_session *session,
static void stream_resume(h2_proxy_stream *stream)
{
h2_proxy_session *session = stream->session;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->cfront,
"h2_proxy_stream(%s-%d): resuming",
session->id, stream->id);
stream->suspended = 0;
@@ -1129,7 +1130,7 @@ static apr_status_t check_suspended(h2_proxy_session *session)
return APR_SUCCESS;
}
else if (status != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(status)) {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, session->c,
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, stream->cfront,
APLOGNO(03382) "h2_proxy_stream(%s-%d): check input",
session->id, stream_id);
stream_resume(stream);
@@ -1366,30 +1367,39 @@ static void ev_stream_done(h2_proxy_session *session, int stream_id,
/* if the stream's connection is aborted, do not send anything
* more on it. */
apr_status_t status = (stream->error_code == 0)? APR_SUCCESS : APR_EINVAL;
- int touched = (stream->data_sent ||
+ int touched = (stream->data_sent || stream->data_received ||
stream_id <= session->last_stream_id);
- if (!session->c->aborted) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03364)
+ if (!stream->cfront->aborted) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->cfront, APLOGNO(03364)
"h2_proxy_sesssion(%s): stream(%d) closed "
"(touched=%d, error=%d)",
session->id, stream_id, touched, stream->error_code);
if (status != APR_SUCCESS) {
- b = ap_bucket_error_create(HTTP_SERVICE_UNAVAILABLE, NULL, stream->r->pool,
- stream->r->connection->bucket_alloc);
+ /* stream failed. If we have received (and forwarded) response
+ * data already, we need to append an error buckt to inform
+ * consumers.
+ * Otherwise, we have an early fail on the connection and may
+ * retry this request on a new one. In that case, keep the
+ * output virgin so that a new attempt can be made. */
+ if (stream->data_received) {
+ int http_status = ap_map_http_request_error(status, HTTP_BAD_REQUEST);
+ b = ap_bucket_error_create(http_status, NULL, stream->r->pool,
+ stream->cfront->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->output, b);
- b = apr_bucket_eos_create(stream->r->connection->bucket_alloc);
+ b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->output, b);
ap_pass_brigade(stream->r->output_filters, stream->output);
+ }
}
else if (!stream->data_received) {
/* if the response had no body, this is the time to flush
* an empty brigade which will also write the response headers */
h2_proxy_stream_end_headers_out(stream);
stream->data_received = 1;
- b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
+ b = apr_bucket_flush_create(stream->cfront->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->output, b);
- b = apr_bucket_eos_create(stream->r->connection->bucket_alloc);
+ b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(stream->output, b);
ap_pass_brigade(stream->r->output_filters, stream->output);
}
@@ -1399,7 +1409,7 @@ static void ev_stream_done(h2_proxy_session *session, int stream_id,
h2_proxy_ihash_remove(session->streams, stream_id);
h2_proxy_iq_remove(session->suspended, stream_id);
if (session->done) {
- session->done(session, stream->r, status, touched);
+ session->done(session, stream->r, status, touched, stream->error_code);
}
}
@@ -1669,9 +1679,19 @@ static int done_iter(void *udata, void *val)
{
cleanup_iter_ctx *ctx = udata;
h2_proxy_stream *stream = val;
- int touched = (stream->data_sent ||
+ int touched = (stream->data_sent || stream->data_received ||
stream->id <= ctx->session->last_stream_id);
- ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched);
+ if (touched && stream->output) {
+ apr_bucket *b = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL,
+ stream->r->pool,
+ stream->cfront->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ b = apr_bucket_eos_create(stream->cfront->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(stream->output, b);
+ ap_pass_brigade(stream->r->output_filters, stream->output);
+ }
+ ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched,
+ stream->error_code);
return 1;
}
@@ -1690,6 +1710,12 @@ void h2_proxy_session_cleanup(h2_proxy_session *session,
}
}
+int h2_proxy_session_is_reusable(h2_proxy_session *session)
+{
+ return (session->state != H2_PROXYS_ST_DONE) &&
+ h2_proxy_ihash_empty(session->streams);
+}
+
static int ping_arrived_iter(void *udata, void *val)
{
h2_proxy_stream *stream = val;
diff --git a/modules/http2/h2_proxy_session.h b/modules/http2/h2_proxy_session.h
index f40e5ee..3bc16d7 100644
--- a/modules/http2/h2_proxy_session.h
+++ b/modules/http2/h2_proxy_session.h
@@ -68,7 +68,8 @@ typedef enum {
typedef struct h2_proxy_session h2_proxy_session;
typedef void h2_proxy_request_done(h2_proxy_session *s, request_rec *r,
- apr_status_t status, int touched);
+ apr_status_t status, int touched,
+ int error_code);
struct h2_proxy_session {
const char *id;
@@ -130,4 +131,6 @@ void h2_proxy_session_cleanup(h2_proxy_session *s, h2_proxy_request_done *done);
#define H2_PROXY_REQ_URL_NOTE "h2-proxy-req-url"
+int h2_proxy_session_is_reusable(h2_proxy_session *s);
+
#endif /* h2_proxy_session_h */
diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c
index 462c470..e6a10c5 100644
--- a/modules/http2/h2_push.c
+++ b/modules/http2/h2_push.c
@@ -426,7 +426,7 @@ static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
static int head_iter(void *ctx, const char *key, const char *value)
{
- if (!apr_strnatcasecmp("link", key)) {
+ if (!ap_cstr_casecmp("link", key)) {
inspect_link(ctx, value, strlen(value));
}
return 1;
@@ -502,6 +502,7 @@ static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push
sha256_update(md, push->req->authority);
sha256_update(md, push->req->path);
EVP_DigestFinal(md, hash, &len);
+ EVP_MD_CTX_destroy(md);
val = 0;
for (i = 0; i != len; ++i)
diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c
index 20e94cd..2713947 100644
--- a/modules/http2/h2_request.c
+++ b/modules/http2/h2_request.c
@@ -38,6 +38,7 @@
#include "h2_private.h"
#include "h2_config.h"
+#include "h2_conn_ctx.h"
#include "h2_push.h"
#include "h2_request.h"
#include "h2_util.h"
@@ -119,6 +120,7 @@ apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool,
req->path = path;
req->headers = apr_table_make(pool, 10);
req->http_status = H2_HTTP_STATUS_UNSET;
+ req->request_time = apr_time_now();
x.pool = pool;
x.headers = req->headers;
@@ -166,6 +168,10 @@ apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
&& !strncmp(H2_HEADER_AUTH, name, nlen)) {
req->authority = apr_pstrndup(pool, value, vlen);
}
+ else if (H2_HEADER_PROTO_LEN == nlen
+ && !strncmp(H2_HEADER_PROTO, name, nlen)) {
+ req->protocol = apr_pstrndup(pool, value, vlen);
+ }
else {
char buffer[32];
memset(buffer, 0, 32);
@@ -214,6 +220,7 @@ h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
dst->scheme = apr_pstrdup(p, src->scheme);
dst->authority = apr_pstrdup(p, src->authority);
dst->path = apr_pstrdup(p, src->path);
+ dst->protocol = apr_pstrdup(p, src->protocol);
dst->headers = apr_table_clone(p, src->headers);
return dst;
}
@@ -282,13 +289,20 @@ apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r)
apr_table_t *headers = apr_table_clone(r->pool, req->headers);
const char *uri = req->path;
+ AP_DEBUG_ASSERT(req->method);
AP_DEBUG_ASSERT(req->authority);
- if (req->scheme && (ap_cstr_casecmp(req->scheme,
- ap_ssl_conn_is_ssl(c->master? c->master : c)? "https" : "http")
- || !ap_cstr_casecmp("CONNECT", req->method))) {
- /* Client sent a non-matching ':scheme' pseudo header or CONNECT.
- * In this case, we use an absolute URI.
- */
+ if (!ap_cstr_casecmp("CONNECT", req->method)) {
+ uri = req->authority;
+ }
+ else if (h2_config_cgeti(c, H2_CONF_PROXY_REQUESTS)) {
+ /* Forward proxying: always absolute uris */
+ uri = apr_psprintf(r->pool, "%s://%s%s",
+ req->scheme, req->authority,
+ req->path ? req->path : "");
+ }
+ else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
+ && ap_cstr_casecmp(req->scheme, "https")) {
+ /* Client sent a non-http ':scheme', use an absolute URI */
uri = apr_psprintf(r->pool, "%s://%s%s",
req->scheme, req->authority, req->path ? req->path : "");
}
@@ -299,12 +313,13 @@ apr_bucket *h2_request_create_bucket(const h2_request *req, request_rec *r)
#endif
static void assign_headers(request_rec *r, const h2_request *req,
- int no_body)
+ int no_body, int is_connect)
{
const char *cl;
r->headers_in = apr_table_clone(r->pool, req->headers);
- if (req->authority) {
+
+ if (req->authority && !is_connect) {
/* for internal handling, we have to simulate that :authority
* came in as Host:, RFC 9113 ch. says that mismatches between
* :authority and Host: SHOULD be rejected as malformed. However,
@@ -323,36 +338,40 @@ static void assign_headers(request_rec *r, const h2_request *req,
"set 'Host: %s' from :authority", req->authority);
}
- cl = apr_table_get(req->headers, "Content-Length");
- if (no_body) {
- if (!cl && apr_table_get(req->headers, "Content-Type")) {
- /* If we have a content-type, but already seen eos, no more
- * data will come. Signal a zero content length explicitly.
- */
- apr_table_setn(req->headers, "Content-Length", "0");
+ /* Unless we open a byte stream via CONNECT, apply content-length guards. */
+ if (!is_connect) {
+ cl = apr_table_get(req->headers, "Content-Length");
+ if (no_body) {
+ if (!cl && apr_table_get(req->headers, "Content-Type")) {
+ /* If we have a content-type, but already seen eos, no more
+ * data will come. Signal a zero content length explicitly.
+ */
+ apr_table_setn(req->headers, "Content-Length", "0");
+ }
}
- }
#if !AP_HAS_RESPONSE_BUCKETS
- else if (!cl) {
- /* there may be a body and we have internal HTTP/1.1 processing.
- * If the Content-Length is unspecified, we MUST simulate
- * chunked Transfer-Encoding.
- *
- * HTTP/2 does not need a Content-Length for framing. Ideally
- * all clients set the EOS flag on the header frame if they
- * do not intent to send a body. However, forwarding proxies
- * might just no know at the time and send an empty DATA
- * frame with EOS much later.
- */
- apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
- }
+ else if (!cl) {
+ /* there may be a body and we have internal HTTP/1.1 processing.
+ * If the Content-Length is unspecified, we MUST simulate
+ * chunked Transfer-Encoding.
+ *
+ * HTTP/2 does not need a Content-Length for framing. Ideally
+ * all clients set the EOS flag on the header frame if they
+ * do not intent to send a body. However, forwarding proxies
+ * might just no know at the time and send an empty DATA
+ * frame with EOS much later.
+ */
+ apr_table_mergen(r->headers_in, "Transfer-Encoding", "chunked");
+ }
#endif /* else AP_HAS_RESPONSE_BUCKETS */
+ }
}
request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
int no_body)
{
int access_status = HTTP_OK;
+ int is_connect = !ap_cstr_casecmp("CONNECT", req->method);
#if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
request_rec *r = ap_create_request(c);
@@ -361,18 +380,63 @@ request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
#endif
#if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
- assign_headers(r, req, no_body);
+ assign_headers(r, req, no_body, is_connect);
ap_run_pre_read_request(r, c);
/* Time to populate r with the data we have. */
r->request_time = req->request_time;
AP_DEBUG_ASSERT(req->authority);
- if (req->scheme && (ap_cstr_casecmp(req->scheme,
- ap_ssl_conn_is_ssl(c->master? c->master : c)? "https" : "http")
- || !ap_cstr_casecmp("CONNECT", req->method))) {
- /* Client sent a non-matching ':scheme' pseudo header. Forward this
- * via an absolute URI in the request line.
- */
+ if (req->http_status != H2_HTTP_STATUS_UNSET) {
+ access_status = req->http_status;
+ goto die;
+ }
+ else if (is_connect) {
+ /* CONNECT MUST NOT have scheme or path */
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ req->method, req->authority);
+ if (req->scheme) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10458)
+ "':scheme: %s' header present in CONNECT request",
+ req->scheme);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ else if (req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10459)
+ "':path: %s' header present in CONNECT request",
+ req->path);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ }
+ else if (req->protocol) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10470)
+ "':protocol: %s' header present in %s request",
+ req->protocol, req->method);
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ else if (h2_config_cgeti(c, H2_CONF_PROXY_REQUESTS)) {
+ if (!req->scheme) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10468)
+ "H2ProxyRequests on, but request misses :scheme");
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ if (!req->authority) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(10469)
+ "H2ProxyRequests on, but request misses :authority");
+ access_status = HTTP_BAD_REQUEST;
+ goto die;
+ }
+ r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
+ req->method, req->scheme, req->authority,
+ req->path ? req->path : "");
+ }
+ else if (req->scheme && ap_cstr_casecmp(req->scheme, "http")
+ && ap_cstr_casecmp(req->scheme, "https")) {
+ /* Client sent a ':scheme' pseudo header for something else
+ * than what we have on this connection. Make an absolute URI. */
r->the_request = apr_psprintf(r->pool, "%s %s://%s%s HTTP/2.0",
req->method, req->scheme, req->authority,
req->path ? req->path : "");
@@ -413,7 +477,7 @@ request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
{
const char *s;
- assign_headers(r, req, no_body);
+ assign_headers(r, req, no_body, is_connect);
ap_run_pre_read_request(r, c);
/* Time to populate r with the data we have. */
@@ -489,6 +553,16 @@ request_rec *h2_create_request_rec(const h2_request *req, conn_rec *c,
return r;
die:
+ if (!r->method) {
+ /* if we fail early, `r` is not properly initialized for error
+ * processing which accesses fields in message generation.
+ * Make a best effort. */
+ if (!r->the_request) {
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ req->method, req->path);
+ }
+ ap_parse_request_line(r);
+ }
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
"ap_die(%d) for %s", access_status, r->the_request);
ap_die(access_status, r);
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c
index 7ba49cf..5724fda 100644
--- a/modules/http2/h2_session.c
+++ b/modules/http2/h2_session.c
@@ -319,9 +319,13 @@ static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
status = h2_stream_add_header(stream, (const char *)name, namelen,
(const char *)value, valuelen);
- if (status != APR_SUCCESS
- && (!stream->rtmp
- || stream->rtmp->http_status == H2_HTTP_STATUS_UNSET)) {
+ if (status != APR_SUCCESS &&
+ (!stream->rtmp ||
+ stream->rtmp->http_status == H2_HTTP_STATUS_UNSET ||
+ /* We accept a certain amount of failures in order to reply
+ * with an informative HTTP error response like 413. But if the
+ * client is too wrong, we fail the request a RESET of the stream */
+ stream->request_headers_failed > 100)) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
return 0;
@@ -402,6 +406,10 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
"RST_STREAM by client, error=%d"),
(int)frame->rst_stream.error_code);
+ if (stream) {
+ rv = h2_stream_recv_frame(stream, NGHTTP2_RST_STREAM, frame->hd.flags,
+ frame->hd.length + H2_FRAME_HDR_LEN);
+ }
if (stream && stream->initiated_on) {
/* A stream reset on a request we sent it. Normal, when the
* client does not want it. */
@@ -410,7 +418,8 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
else {
/* A stream reset on a request it sent us. Could happen in a browser
* when the user navigates away or cancels loading - maybe. */
- h2_mplx_c1_client_rst(session->mplx, frame->hd.stream_id);
+ h2_mplx_c1_client_rst(session->mplx, frame->hd.stream_id,
+ stream);
}
++session->streams_reset;
break;
@@ -621,9 +630,8 @@ static int on_invalid_header_cb(nghttp2_session *ngh2,
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1, APLOGNO(03456)
H2_SSSN_STRM_MSG(session, frame->hd.stream_id,
- "invalid header '%s: %s'"),
- apr_pstrndup(session->pool, (const char *)name, namelen),
- apr_pstrndup(session->pool, (const char *)value, valuelen));
+ "invalid header '%.*s: %.*s'"),
+ (int)namelen, name, (int)valuelen, value);
stream = get_stream(session, frame->hd.stream_id);
if (stream) {
h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
@@ -813,6 +821,17 @@ static apr_status_t session_cleanup(h2_session *session, const char *trigger)
"goodbye, clients will be confused, should not happen"));
}
+ if (!h2_iq_empty(session->ready_to_process)) {
+ int sid;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ H2_SSSN_LOG(APLOGNO(10485), session,
+ "cleanup, resetting %d streams in ready-to-process"),
+ h2_iq_count(session->ready_to_process));
+ while ((sid = h2_iq_shift(session->ready_to_process)) > 0) {
+ h2_mplx_c1_client_rst(session->mplx, sid, get_stream(session, sid));
+ }
+ }
+
transit(session, trigger, H2_SESSION_ST_CLEANUP);
h2_mplx_c1_destroy(session->mplx);
session->mplx = NULL;
@@ -902,7 +921,8 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
session->max_stream_count = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
session->max_stream_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
-
+ session->max_data_frame_len = h2_config_sgeti(s, H2_CONF_MAX_DATA_FRAME_LEN);
+
session->out_c1_blocked = h2_iq_create(session->pool, (int)session->max_stream_count);
session->ready_to_process = h2_iq_create(session->pool, (int)session->max_stream_count);
@@ -983,13 +1003,15 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
H2_SSSN_LOG(APLOGNO(03200), session,
"created, max_streams=%d, stream_mem=%d, "
"workers_limit=%d, workers_max=%d, "
- "push_diary(type=%d,N=%d)"),
+ "push_diary(type=%d,N=%d), "
+ "max_data_frame_len=%d"),
(int)session->max_stream_count,
(int)session->max_stream_mem,
session->mplx->processing_limit,
session->mplx->processing_max,
session->push_diary->dtype,
- (int)session->push_diary->N);
+ (int)session->push_diary->N,
+ (int)session->max_data_frame_len);
}
apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);
@@ -1000,7 +1022,7 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
static apr_status_t h2_session_start(h2_session *session, int *rv)
{
apr_status_t status = APR_SUCCESS;
- nghttp2_settings_entry settings[3];
+ nghttp2_settings_entry settings[4];
size_t slen;
int win_size;
@@ -1067,7 +1089,14 @@ static apr_status_t h2_session_start(h2_session *session, int *rv)
settings[slen].value = win_size;
++slen;
}
-
+#if H2_USE_WEBSOCKETS
+ if (h2_config_sgeti(session->s, H2_CONF_WEBSOCKETS)) {
+ settings[slen].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
+ settings[slen].value = 1;
+ ++slen;
+ }
+#endif
+
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c1,
H2_SSSN_LOG(APLOGNO(03201), session,
"start, INITIAL_WINDOW_SIZE=%ld, MAX_CONCURRENT_STREAMS=%d"),
@@ -1278,8 +1307,11 @@ static apr_status_t h2_session_send(h2_session *session)
goto cleanup;
}
}
- if (h2_c1_io_needs_flush(&session->io)) {
+ if (h2_c1_io_needs_flush(&session->io) ||
+ ngrv == NGHTTP2_ERR_WOULDBLOCK) {
rv = h2_c1_io_assure_flushed(&session->io);
+ if (rv != APR_SUCCESS)
+ goto cleanup;
pending = 0;
}
}
@@ -1636,10 +1668,6 @@ static void on_stream_state_enter(void *ctx, h2_stream *stream)
h2_mplx_c1_stream_cleanup(session->mplx, stream, &session->open_streams);
++session->streams_done;
update_child_status(session, SERVER_BUSY_WRITE, "done", stream);
- if (session->open_streams == 0) {
- h2_session_dispatch_event(session, H2_SESSION_EV_NO_MORE_STREAMS,
- 0, "stream done");
- }
break;
default:
break;
@@ -1915,7 +1943,15 @@ apr_status_t h2_session_process(h2_session *session, int async)
status = h2_mplx_c1_poll(session->mplx, session->s->timeout,
on_stream_input, on_stream_output, session);
if (APR_STATUS_IS_TIMEUP(status)) {
- if (session->open_streams == 0) {
+ /* If we timeout without streams open, no new request from client
+ * arrived.
+ * If we timeout without nghttp2 wanting to write something, but
+ * all open streams have something to send, it means we are
+ * blocked on HTTP/2 flow control and the client did not send
+ * WINDOW_UPDATEs to us. */
+ if (session->open_streams == 0 ||
+ (!h2_session_want_send(session) &&
+ h2_mplx_c1_all_streams_want_send_data(session->mplx))) {
h2_session_dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, status, NULL);
break;
}
@@ -1944,7 +1980,8 @@ leaving:
ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
H2_SSSN_MSG(session, "process returns"));
}
-
+ h2_mplx_c1_going_keepalive(session->mplx);
+
if (session->state == H2_SESSION_ST_DONE) {
if (session->local.error) {
char buffer[128];
diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h
index fbddfdd..3328509 100644
--- a/modules/http2/h2_session.h
+++ b/modules/http2/h2_session.h
@@ -103,7 +103,8 @@ typedef struct h2_session {
apr_size_t max_stream_count; /* max number of open streams */
apr_size_t max_stream_mem; /* max buffer memory for a single stream */
-
+ apr_size_t max_data_frame_len; /* max amount of bytes for a single DATA frame */
+
apr_size_t idle_frames; /* number of rcvd frames that kept session in idle state */
apr_interval_time_t idle_delay; /* Time we delay processing rcvd frames in idle state */
diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c
index cf6f798..ee87555 100644
--- a/modules/http2/h2_stream.c
+++ b/modules/http2/h2_stream.c
@@ -125,7 +125,7 @@ static int trans_on_event[][H2_SS_MAX] = {
{ S_XXX, S_ERR, S_ERR, S_CL_L, S_CLS, S_XXX, S_XXX, S_XXX, },/* EV_CLOSED_L*/
{ S_ERR, S_ERR, S_ERR, S_CL_R, S_ERR, S_CLS, S_NOP, S_NOP, },/* EV_CLOSED_R*/
{ S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_CLS, S_NOP, S_NOP, },/* EV_CANCELLED*/
-{ S_NOP, S_XXX, S_XXX, S_XXX, S_XXX, S_CLS, S_CLN, S_XXX, },/* EV_EOS_SENT*/
+{ S_NOP, S_XXX, S_XXX, S_XXX, S_XXX, S_CLS, S_CLN, S_NOP, },/* EV_EOS_SENT*/
{ S_NOP, S_XXX, S_CLS, S_XXX, S_XXX, S_CLS, S_XXX, S_XXX, },/* EV_IN_ERROR*/
};
@@ -166,6 +166,7 @@ static int on_frame_recv(h2_stream_state_t state, int frame_type)
static int on_event(h2_stream* stream, h2_stream_event_t ev)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (stream->monitor && stream->monitor->on_event) {
stream->monitor->on_event(stream->monitor->ctx, stream, ev);
}
@@ -392,6 +393,7 @@ void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev)
{
int new_state;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c1,
H2_STRM_MSG(stream, "dispatch event %d"), ev);
new_state = on_event(stream, ev);
@@ -425,6 +427,7 @@ apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_
apr_status_t status = APR_SUCCESS;
int new_state, eos = 0;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
new_state = on_frame_send(stream->state, ftype);
if (new_state < 0) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
@@ -435,6 +438,12 @@ apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_
++stream->out_frames;
stream->out_frame_octets += frame_len;
+ if(stream->c2) {
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(stream->c2);
+ if(conn_ctx)
+ conn_ctx->bytes_sent = stream->out_frame_octets;
+ }
+
switch (ftype) {
case NGHTTP2_DATA:
eos = (flags & NGHTTP2_FLAG_END_STREAM);
@@ -468,6 +477,7 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_
apr_status_t status = APR_SUCCESS;
int new_state, eos = 0;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
new_state = on_frame_recv(stream->state, ftype);
if (new_state < 0) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
@@ -522,6 +532,7 @@ apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
h2_session *session = stream->session;
apr_status_t status = APR_SUCCESS;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
stream->in_data_frames++;
if (len > 0) {
if (APLOGctrace3(session->c1)) {
@@ -542,11 +553,38 @@ apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
return status;
}
+#ifdef AP_DEBUG
+static apr_status_t stream_pool_destroy(void *data)
+{
+ h2_stream *stream = data;
+ switch (stream->magic) {
+ case H2_STRM_MAGIC_OK:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "was not destroyed explicitly"));
+ AP_DEBUG_ASSERT(0);
+ break;
+ case H2_STRM_MAGIC_SDEL:
+ /* stream has been explicitly destroyed, as it should */
+ H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_PDEL);
+ break;
+ case H2_STRM_MAGIC_PDEL:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, stream->session->c1,
+ H2_STRM_MSG(stream, "already pool destroyed"));
+ AP_DEBUG_ASSERT(0);
+ break;
+ default:
+ AP_DEBUG_ASSERT(0);
+ }
+ return APR_SUCCESS;
+}
+#endif
+
h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
h2_stream_monitor *monitor, int initiated_on)
{
h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
-
+
+ H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_OK);
stream->id = id;
stream->initiated_on = initiated_on;
stream->created = apr_time_now();
@@ -554,6 +592,12 @@ h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
stream->pool = pool;
stream->session = session;
stream->monitor = monitor;
+#ifdef AP_DEBUG
+ if (id) { /* stream 0 has special lifetime */
+ apr_pool_cleanup_register(pool, stream, stream_pool_destroy,
+ apr_pool_cleanup_null);
+ }
+#endif
#ifdef H2_NG2_LOCAL_WIN_SIZE
if (id) {
@@ -575,6 +619,7 @@ void h2_stream_cleanup(h2_stream *stream)
* end of the in/out notifications get closed.
*/
ap_assert(stream);
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (stream->out_buffer) {
apr_brigade_cleanup(stream->out_buffer);
}
@@ -583,13 +628,16 @@ void h2_stream_cleanup(h2_stream *stream)
void h2_stream_destroy(h2_stream *stream)
{
ap_assert(stream);
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c1,
H2_STRM_MSG(stream, "destroy"));
+ H2_STRM_ASSIGN_MAGIC(stream, H2_STRM_MAGIC_SDEL);
apr_pool_destroy(stream->pool);
}
void h2_stream_rst(h2_stream *stream, int error_code)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
stream->rst_error = error_code;
if (stream->c2) {
h2_c2_abort(stream->c2, stream->session->c1);
@@ -605,6 +653,7 @@ apr_status_t h2_stream_set_request_rec(h2_stream *stream,
h2_request *req;
apr_status_t status;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
ap_assert(stream->request == NULL);
ap_assert(stream->rtmp == NULL);
if (stream->rst_error) {
@@ -626,6 +675,7 @@ apr_status_t h2_stream_set_request_rec(h2_stream *stream,
void h2_stream_set_request(h2_stream *stream, const h2_request *r)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
ap_assert(stream->request == NULL);
ap_assert(stream->rtmp == NULL);
stream->rtmp = h2_request_clone(stream->pool, r);
@@ -685,6 +735,7 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
int error = 0, was_added = 0;
apr_status_t status = APR_SUCCESS;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (stream->response) {
return APR_EINVAL;
}
@@ -716,6 +767,9 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
status = h2_request_add_header(stream->rtmp, stream->pool,
name, nlen, value, vlen,
session->s->limit_req_fieldsize, &was_added);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c1,
+ H2_STRM_MSG(stream, "add_header: '%.*s: %.*s"),
+ (int)nlen, name, (int)vlen, value);
if (was_added) ++stream->request_headers_added;
}
else if (H2_SS_OPEN == stream->state) {
@@ -759,6 +813,7 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
cleanup:
if (error) {
+ ++stream->request_headers_failed;
set_error_response(stream, error);
return APR_EINVAL;
}
@@ -791,6 +846,7 @@ apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes)
int is_http_or_https;
h2_request *req = stream->rtmp;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
status = h2_request_end_headers(req, stream->pool, raw_bytes);
if (APR_SUCCESS != status || req->http_status != H2_HTTP_STATUS_UNSET) {
goto cleanup;
@@ -845,7 +901,26 @@ apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes)
* of CONNECT requests (see [RFC7230], Section 5.3)).
*/
if (!ap_cstr_casecmp(req->method, "CONNECT")) {
- if (req->scheme || req->path) {
+ if (req->protocol) {
+ if (!strcmp("websocket", req->protocol)) {
+ if (!req->scheme || !req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
+ H2_STRM_LOG(APLOGNO(10457), stream, "Request to websocket CONNECT "
+ "without :scheme or :path, sending 400 answer"));
+ set_error_response(stream, HTTP_BAD_REQUEST);
+ goto cleanup;
+ }
+ }
+ else {
+ /* do not know that protocol */
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c1, APLOGNO(10460)
+ "':protocol: %s' header present in %s request",
+ req->protocol, req->method);
+ set_error_response(stream, HTTP_NOT_IMPLEMENTED);
+ goto cleanup;
+ }
+ }
+ else if (req->scheme || req->path) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c1,
H2_STRM_LOG(APLOGNO(10384), stream, "Request to CONNECT "
"with :scheme or :path specified, sending 400 answer"));
@@ -1039,6 +1114,7 @@ apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
{
apr_status_t rv = APR_SUCCESS;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (stream->rst_error) {
return APR_ECONNRESET;
}
@@ -1133,6 +1209,7 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
apr_array_header_t *pushes;
int i;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
pushes = h2_push_collect_update(stream, stream->request, response);
if (pushes && !apr_is_empty_array(pushes)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c1,
@@ -1152,6 +1229,7 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
apr_table_t *h2_stream_get_trailers(h2_stream *stream)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
return NULL;
}
@@ -1163,6 +1241,7 @@ const h2_priority *h2_stream_get_priority(h2_stream *stream,
h2_headers *response)
#endif
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (response && stream->initiated_on) {
const char *ctype = apr_table_get(response->headers, "content-type");
if (ctype) {
@@ -1176,6 +1255,7 @@ const h2_priority *h2_stream_get_priority(h2_stream *stream,
int h2_stream_is_ready(h2_stream *stream)
{
/* Have we sent a response or do we have the response in our buffer? */
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (stream->response) {
return 1;
}
@@ -1185,13 +1265,23 @@ int h2_stream_is_ready(h2_stream *stream)
return 0;
}
+int h2_stream_wants_send_data(h2_stream *stream)
+{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
+ return h2_stream_is_ready(stream) &&
+ ((stream->out_buffer && !APR_BRIGADE_EMPTY(stream->out_buffer)) ||
+ (stream->output && !h2_beam_empty(stream->output)));
+}
+
int h2_stream_is_at(const h2_stream *stream, h2_stream_state_t state)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
return stream->state == state;
}
int h2_stream_is_at_or_past(const h2_stream *stream, h2_stream_state_t state)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
switch (state) {
case H2_SS_IDLE:
return 1; /* by definition */
@@ -1214,6 +1304,7 @@ apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount)
{
h2_session *session = stream->session;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (amount > 0) {
apr_off_t consumed = amount;
@@ -1339,6 +1430,7 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s,
H2_SSSN_STRM_MSG(session, stream_id, "data_cb, stream not found"));
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (!stream->output || !stream->response || !stream->out_buffer) {
return NGHTTP2_ERR_DEFERRED;
}
@@ -1346,10 +1438,17 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s,
return NGHTTP2_ERR_DEFERRED;
}
if (h2_c1_io_needs_flush(&session->io)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
- H2_SSSN_STRM_MSG(session, stream_id, "suspending on c1 out needs flush"));
- h2_stream_dispatch(stream, H2_SEV_OUT_C1_BLOCK);
- return NGHTTP2_ERR_DEFERRED;
+ rv = h2_c1_io_pass(&session->io);
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_SSSN_STRM_MSG(session, stream_id, "suspending on c1 out needs flush"));
+ h2_stream_dispatch(stream, H2_SEV_OUT_C1_BLOCK);
+ return NGHTTP2_ERR_DEFERRED;
+ }
+ else if (rv) {
+ h2_session_dispatch_event(session, H2_SESSION_EV_CONN_ERROR, rv, NULL);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
}
/* determine how much we'd like to send. We cannot send more than
@@ -1361,6 +1460,11 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s,
length = chunk_len;
}
}
+ /* We allow configurable max DATA frame length. */
+ if (stream->session->max_data_frame_len > 0
+ && length > stream->session->max_data_frame_len) {
+ length = stream->session->max_data_frame_len;
+ }
/* How much data do we have in our buffers that we can write?
* if not enough, receive more. */
@@ -1393,8 +1497,8 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s,
* it is all fine. */
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
H2_SSSN_STRM_MSG(session, stream_id, "rst stream"));
- h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
- return NGHTTP2_ERR_CALLBACK_FAILURE;
+ h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+ return NGHTTP2_ERR_DEFERRED;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c1,
H2_SSSN_STRM_MSG(session, stream_id,
@@ -1403,10 +1507,17 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s,
eos = 1;
rv = APR_SUCCESS;
}
+ else if (APR_ECONNRESET == rv || APR_ECONNABORTED == rv) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c1,
+ H2_STRM_LOG(APLOGNO(10471), stream, "data_cb, reading data"));
+ h2_stream_rst(stream, H2_ERR_STREAM_CLOSED);
+ return NGHTTP2_ERR_DEFERRED;
+ }
else {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c1,
H2_STRM_LOG(APLOGNO(02938), stream, "data_cb, reading data"));
- return NGHTTP2_ERR_CALLBACK_FAILURE;
+ h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
+ return NGHTTP2_ERR_DEFERRED;
}
}
@@ -1465,6 +1576,7 @@ static apr_status_t stream_do_response(h2_stream *stream)
#endif
nghttp2_data_provider provider, *pprovider = NULL;
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
ap_assert(!stream->response);
ap_assert(stream->out_buffer);
@@ -1562,6 +1674,8 @@ static apr_status_t stream_do_response(h2_stream *stream)
* denies it, submit resources to push */
const char *s = apr_table_get(resp->notes, H2_PUSH_MODE_NOTE);
if (!s || strcmp(s, "0")) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
+ H2_STRM_MSG(stream, "submit pushes, note=%s"), s);
h2_stream_submit_pushes(stream, resp);
}
}
@@ -1653,6 +1767,7 @@ void h2_stream_on_output_change(h2_stream *stream)
/* stream->pout_recv_write signalled a change. Check what has happend, read
* from it and act on seeing a response/data. */
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
if (!stream->output) {
/* c2 has not assigned the output beam to the stream (yet). */
ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c1,
@@ -1701,6 +1816,7 @@ void h2_stream_on_output_change(h2_stream *stream)
void h2_stream_on_input_change(h2_stream *stream)
{
+ H2_STRM_ASSERT_MAGIC(stream, H2_STRM_MAGIC_OK);
ap_assert(stream->input);
h2_beam_report_consumption(stream->input);
if (h2_stream_is_at(stream, H2_SS_CLOSED_L)
diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h
index 695d56a..405978a 100644
--- a/modules/http2/h2_stream.h
+++ b/modules/http2/h2_stream.h
@@ -63,7 +63,22 @@ typedef struct h2_stream_monitor {
trigger a state change */
} h2_stream_monitor;
+#ifdef AP_DEBUG
+#define H2_STRM_MAGIC_OK 0x5354524d
+#define H2_STRM_MAGIC_SDEL 0x5344454c
+#define H2_STRM_MAGIC_PDEL 0x5044454c
+
+#define H2_STRM_ASSIGN_MAGIC(s,m) ((s)->magic = m)
+#define H2_STRM_ASSERT_MAGIC(s,m) ap_assert((s)->magic == m)
+#else
+#define H2_STRM_ASSIGN_MAGIC(s,m) ((void)0)
+#define H2_STRM_ASSERT_MAGIC(s,m) ((void)0)
+#endif
+
struct h2_stream {
+#ifdef AP_DEBUG
+ uint32_t magic;
+#endif
int id; /* http2 stream identifier */
int initiated_on; /* initiating stream id (PUSH) or 0 */
apr_pool_t *pool; /* the memory pool for this stream */
@@ -76,6 +91,7 @@ struct h2_stream {
struct h2_request *rtmp; /* request being assembled */
apr_table_t *trailers_in; /* optional, incoming trailers */
int request_headers_added; /* number of request headers added */
+ int request_headers_failed; /* number of request headers failed to add */
#if AP_HAS_RESPONSE_BUCKETS
ap_bucket_response *response; /* the final, non-interim response or NULL */
@@ -317,6 +333,8 @@ const char *h2_stream_state_str(const h2_stream *stream);
*/
int h2_stream_is_ready(h2_stream *stream);
+int h2_stream_wants_send_data(h2_stream *stream);
+
#define H2_STRM_MSG(s, msg) \
"h2_stream(%d-%lu-%d,%s): "msg, s->session->child_num, \
(unsigned long)s->session->id, s->id, h2_stream_state_str(s)
diff --git a/modules/http2/h2_switch.c b/modules/http2/h2_switch.c
index a30f27c..3799701 100644
--- a/modules/http2/h2_switch.c
+++ b/modules/http2/h2_switch.c
@@ -104,9 +104,10 @@ static int h2_protocol_propose(conn_rec *c, request_rec *r,
/* We also allow switching only for requests that have no body.
*/
p = apr_table_get(r->headers_in, "Content-Length");
- if (p && strcmp(p, "0")) {
+ if ((p && strcmp(p, "0"))
+ || (!p && apr_table_get(r->headers_in, "Transfer-Encoding"))) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03087)
- "upgrade with content-length: %s, declined", p);
+ "upgrade with body declined");
return DECLINED;
}
}
diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c
index 728cee9..8e53ceb 100644
--- a/modules/http2/h2_util.c
+++ b/modules/http2/h2_util.c
@@ -1281,8 +1281,8 @@ apr_size_t h2_util_bucket_print(char *buffer, apr_size_t bmax,
else if (bmax > off) {
off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]",
b->type->name,
- (long)(b->length == ((apr_size_t)-1)?
- -1 : b->length));
+ (b->length == ((apr_size_t)-1)?
+ -1 : (long)b->length));
}
return off;
}
@@ -1650,7 +1650,7 @@ static int contains_name(const literal *lits, size_t llen, nghttp2_nv *nv)
for (i = 0; i < llen; ++i) {
lit = &lits[i];
if (lit->len == nv->namelen
- && !apr_strnatcasecmp(lit->name, (const char *)nv->name)) {
+ && !ap_cstr_casecmp(lit->name, (const char *)nv->name)) {
return 1;
}
}
@@ -1706,7 +1706,7 @@ static apr_status_t req_add_header(apr_table_t *headers, apr_pool_t *pool,
return APR_SUCCESS;
}
else if (nv->namelen == sizeof("cookie")-1
- && !apr_strnatcasecmp("cookie", (const char *)nv->name)) {
+ && !ap_cstr_casecmp("cookie", (const char *)nv->name)) {
existing = apr_table_get(headers, "cookie");
if (existing) {
/* Cookie header come separately in HTTP/2, but need
@@ -1725,7 +1725,7 @@ static apr_status_t req_add_header(apr_table_t *headers, apr_pool_t *pool,
}
}
else if (nv->namelen == sizeof("host")-1
- && !apr_strnatcasecmp("host", (const char *)nv->name)) {
+ && !ap_cstr_casecmp("host", (const char *)nv->name)) {
if (apr_table_get(headers, "Host")) {
return APR_SUCCESS; /* ignore duplicate */
}
@@ -1883,6 +1883,13 @@ void h2_util_drain_pipe(apr_file_t *pipe)
{
char rb[512];
apr_size_t nr = sizeof(rb);
+ apr_interval_time_t timeout;
+ apr_status_t trv;
+
+ /* Make the pipe non-blocking if we can */
+ trv = apr_file_pipe_timeout_get(pipe, &timeout);
+ if (trv == APR_SUCCESS)
+ apr_file_pipe_timeout_set(pipe, 0);
while (apr_file_read(pipe, rb, &nr) == APR_SUCCESS) {
/* Although we write just one byte to the other end of the pipe
@@ -1893,6 +1900,8 @@ void h2_util_drain_pipe(apr_file_t *pipe)
if (nr != sizeof(rb))
break;
}
+ if (trv == APR_SUCCESS)
+ apr_file_pipe_timeout_set(pipe, timeout);
}
apr_status_t h2_util_wait_on_pipe(apr_file_t *pipe)
diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h
index c961089..7e7da21 100644
--- a/modules/http2/h2_version.h
+++ b/modules/http2/h2_version.h
@@ -27,7 +27,7 @@
* @macro
* Version number of the http2 module as c string
*/
-#define MOD_HTTP2_VERSION "2.0.11"
+#define MOD_HTTP2_VERSION "2.0.22"
/**
* @macro
@@ -35,7 +35,7 @@
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_HTTP2_VERSION_NUM 0x02000b
+#define MOD_HTTP2_VERSION_NUM 0x020016
#endif /* mod_h2_h2_version_h */
diff --git a/modules/http2/h2_ws.c b/modules/http2/h2_ws.c
new file mode 100644
index 0000000..396e6e1
--- /dev/null
+++ b/modules/http2/h2_ws.c
@@ -0,0 +1,362 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 <assert.h>
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_sha1.h"
+#include "apr_strmatch.h"
+
+#include <ap_mmn.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+#include <http_ssl.h>
+#include <http_vhost.h>
+#include <util_filter.h>
+#include <ap_mpm.h>
+
+#include "h2_private.h"
+#include "h2_config.h"
+#include "h2_conn_ctx.h"
+#include "h2_headers.h"
+#include "h2_request.h"
+#include "h2_ws.h"
+
+#if H2_USE_WEBSOCKETS
+
+#include "apr_encode.h" /* H2_USE_WEBSOCKETS is conditional on APR 1.6+ */
+
+static ap_filter_rec_t *c2_ws_out_filter_handle;
+
+struct ws_filter_ctx {
+ const char *ws_accept_base64;
+ int has_final_response;
+ int override_body;
+};
+
+/**
+ * Generate the "Sec-WebSocket-Accept" header field for the given key
+ * (base64 encoded) as defined in RFC 6455 ch. 4.2.2 step 5.3
+ */
+static const char *gen_ws_accept(conn_rec *c, const char *key_base64)
+{
+ apr_byte_t dgst[APR_SHA1_DIGESTSIZE];
+ const char ws_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ apr_sha1_ctx_t sha1_ctx;
+
+ apr_sha1_init(&sha1_ctx);
+ apr_sha1_update(&sha1_ctx, key_base64, (unsigned int)strlen(key_base64));
+ apr_sha1_update(&sha1_ctx, ws_guid, (unsigned int)strlen(ws_guid));
+ apr_sha1_final(dgst, &sha1_ctx);
+
+ return apr_pencode_base64_binary(c->pool, dgst, sizeof(dgst),
+ APR_ENCODE_NONE, NULL);
+}
+
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+ conn_rec *c2, int no_body)
+{
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2);
+ h2_request *wsreq;
+ unsigned char key_raw[16];
+ const char *key_base64, *accept_base64;
+ struct ws_filter_ctx *ws_ctx;
+ apr_status_t rv;
+
+ if (!conn_ctx || !req->protocol || strcmp("websocket", req->protocol))
+ return req;
+
+ if (ap_cstr_casecmp("CONNECT", req->method)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket request with method %s",
+ conn_ctx->id, conn_ctx->stream_id, req->method);
+ return req;
+ }
+ if (!req->scheme) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT without :scheme",
+ conn_ctx->id, conn_ctx->stream_id);
+ return req;
+ }
+ if (!req->path) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT without :path",
+ conn_ctx->id, conn_ctx->stream_id);
+ return req;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT for %s",
+ conn_ctx->id, conn_ctx->stream_id, req->path);
+ /* Transform the HTTP/2 extended CONNECT to an internal GET using
+ * the HTTP/1.1 version of websocket connection setup. */
+ wsreq = h2_request_clone(c2->pool, req);
+ wsreq->method = "GET";
+ wsreq->protocol = NULL;
+ apr_table_set(wsreq->headers, "Upgrade", "websocket");
+ apr_table_add(wsreq->headers, "Connection", "Upgrade");
+ /* add Sec-WebSocket-Key header */
+ rv = apr_generate_random_bytes(key_raw, sizeof(key_raw));
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(10461)
+ "error generating secret");
+ return NULL;
+ }
+ key_base64 = apr_pencode_base64_binary(c2->pool, key_raw, sizeof(key_raw),
+ APR_ENCODE_NONE, NULL);
+ apr_table_set(wsreq->headers, "Sec-WebSocket-Key", key_base64);
+ /* This is now the request to process internally */
+
+ /* When this request gets processed and delivers a 101 response,
+ * we expect it to carry a "Sec-WebSocket-Accept" header with
+ * exactly the following value, as per RFC 6455. */
+ accept_base64 = gen_ws_accept(c2, key_base64);
+ /* Add an output filter that intercepts generated responses:
+ * - if a valid WebSocket negotiation happens, transform the
+ * 101 response to a 200
+ * - if a 2xx response happens, that does not pass the Accept test,
+ * return a 502 indicating that the URI seems not support the websocket
+ * protocol (RFC 8441 does not define this, but it seems the best
+ * choice)
+ * - if a 3xx, 4xx or 5xx response happens, forward this unchanged.
+ */
+ ws_ctx = apr_pcalloc(c2->pool, sizeof(*ws_ctx));
+ ws_ctx->ws_accept_base64 = accept_base64;
+ /* insert our filter just before the C2 core filter */
+ ap_remove_output_filter_byhandle(c2->output_filters, "H2_C2_NET_OUT");
+ ap_add_output_filter("H2_C2_WS_OUT", ws_ctx, NULL, c2);
+ ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2);
+ /* Mark the connection as being an Upgrade, with some special handling
+ * since the request needs an EOS, without the stream being closed */
+ conn_ctx->is_upgrade = 1;
+
+ return wsreq;
+}
+
+static apr_bucket *make_valid_resp(conn_rec *c2, int status,
+ apr_table_t *headers, apr_table_t *notes)
+{
+ apr_table_t *nheaders, *nnotes;
+
+ ap_assert(headers);
+ nheaders = apr_table_clone(c2->pool, headers);
+ apr_table_unset(nheaders, "Connection");
+ apr_table_unset(nheaders, "Upgrade");
+ apr_table_unset(nheaders, "Sec-WebSocket-Accept");
+ nnotes = notes? apr_table_clone(c2->pool, notes) :
+ apr_table_make(c2->pool, 10);
+#if AP_HAS_RESPONSE_BUCKETS
+ return ap_bucket_response_create(status, NULL, nheaders, nnotes,
+ c2->pool, c2->bucket_alloc);
+#else
+ return h2_bucket_headers_create(c2->bucket_alloc,
+ h2_headers_create(status, nheaders,
+ nnotes, 0, c2->pool));
+#endif
+}
+
+static apr_bucket *make_invalid_resp(conn_rec *c2, int status,
+ apr_table_t *notes)
+{
+ apr_table_t *nheaders, *nnotes;
+
+ nheaders = apr_table_make(c2->pool, 10);
+ apr_table_setn(nheaders, "Content-Length", "0");
+ nnotes = notes? apr_table_clone(c2->pool, notes) :
+ apr_table_make(c2->pool, 10);
+#if AP_HAS_RESPONSE_BUCKETS
+ return ap_bucket_response_create(status, NULL, nheaders, nnotes,
+ c2->pool, c2->bucket_alloc);
+#else
+ return h2_bucket_headers_create(c2->bucket_alloc,
+ h2_headers_create(status, nheaders,
+ nnotes, 0, c2->pool));
+#endif
+}
+
+static void ws_handle_resp(conn_rec *c2, h2_conn_ctx_t *conn_ctx,
+ struct ws_filter_ctx *ws_ctx, apr_bucket *b)
+{
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_bucket_response *resp = b->data;
+#else /* AP_HAS_RESPONSE_BUCKETS */
+ h2_headers *resp = h2_bucket_headers_get(b);
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+ apr_bucket *b_override = NULL;
+ int is_final = 0;
+ int override_body = 0;
+
+ if (ws_ctx->has_final_response) {
+ /* already did, nop */
+ return;
+ }
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2,
+ "h2_c2(%s-%d): H2_C2_WS_OUT inspecting response %d",
+ conn_ctx->id, conn_ctx->stream_id, resp->status);
+ if (resp->status == HTTP_SWITCHING_PROTOCOLS) {
+ /* The resource agreed to switch protocol. But this is only valid
+ * if it send back the correct Sec-WebSocket-Accept header value */
+ const char *hd = apr_table_get(resp->headers, "Sec-WebSocket-Accept");
+ if (hd && !strcmp(ws_ctx->ws_accept_base64, hd)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT, valid 101 Upgrade"
+ ", converting to 200 response",
+ conn_ctx->id, conn_ctx->stream_id);
+ b_override = make_valid_resp(c2, HTTP_OK, resp->headers, resp->notes);
+ is_final = 1;
+ }
+ else {
+ if (!hd) {
+ /* This points to someone being confused */
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c2, APLOGNO(10462)
+ "h2_c2(%s-%d): websocket CONNECT, got 101 response "
+ "without Sec-WebSocket-Accept header",
+ conn_ctx->id, conn_ctx->stream_id);
+ }
+ else {
+ /* This points to a bug, either in our WebSockets negotiation
+ * or in the request processings implementation of WebSockets */
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c2, APLOGNO(10463)
+ "h2_c2(%s-%d): websocket CONNECT, 101 response "
+ "with 'Sec-WebSocket-Accept: %s' but expected %s",
+ conn_ctx->id, conn_ctx->stream_id, hd,
+ ws_ctx->ws_accept_base64);
+ }
+ b_override = make_invalid_resp(c2, HTTP_BAD_GATEWAY, resp->notes);
+ override_body = is_final = 1;
+ }
+ }
+ else if (resp->status < 200) {
+ /* other intermediate response, pass through */
+ }
+ else if (resp->status < 300) {
+ /* Failure, we might be talking to a plain http resource */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2,
+ "h2_c2(%s-%d): websocket CONNECT, invalid response %d",
+ conn_ctx->id, conn_ctx->stream_id, resp->status);
+ b_override = make_invalid_resp(c2, HTTP_BAD_GATEWAY, resp->notes);
+ override_body = is_final = 1;
+ }
+ else {
+ /* error response, pass through. */
+ ws_ctx->has_final_response = 1;
+ }
+
+ if (b_override) {
+ APR_BUCKET_INSERT_BEFORE(b, b_override);
+ apr_bucket_delete(b);
+ b = b_override;
+ }
+ if (override_body) {
+ APR_BUCKET_INSERT_AFTER(b, apr_bucket_eos_create(c2->bucket_alloc));
+ ws_ctx->override_body = 1;
+ }
+ if (is_final) {
+ ws_ctx->has_final_response = 1;
+ conn_ctx->has_final_response = 1;
+ }
+}
+
+static apr_status_t h2_c2_ws_filter_out(ap_filter_t* f, apr_bucket_brigade* bb)
+{
+ struct ws_filter_ctx *ws_ctx = f->ctx;
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c);
+ apr_bucket *b, *bnext;
+
+ ap_assert(conn_ctx);
+ if (ws_ctx->override_body) {
+ /* We have overridden the original response and also its body.
+ * If this filter is called again, we signal a hard abort to
+ * allow processing to terminate at the earliest. */
+ f->c->aborted = 1;
+ return APR_ECONNABORTED;
+ }
+
+ /* Inspect the brigade, looking for RESPONSE/HEADER buckets.
+ * Remember, this filter is only active for client websocket CONNECT
+ * requests that we translated to an internal GET with websocket
+ * headers.
+ * We inspect the repsone to see if the internal resource actually
+ * agrees to talk websocket or is "just" a normal HTTP resource that
+ * ignored the websocket request headers. */
+ for (b = APR_BRIGADE_FIRST(bb);
+ b != APR_BRIGADE_SENTINEL(bb);
+ b = bnext)
+ {
+ bnext = APR_BUCKET_NEXT(b);
+ if (APR_BUCKET_IS_METADATA(b)) {
+#if AP_HAS_RESPONSE_BUCKETS
+ if (AP_BUCKET_IS_RESPONSE(b)) {
+#else
+ if (H2_BUCKET_IS_HEADERS(b)) {
+#endif /* !AP_HAS_RESPONSE_BUCKETS */
+ ws_handle_resp(f->c, conn_ctx, ws_ctx, b);
+ continue;
+ }
+ }
+ else if (ws_ctx->override_body) {
+ apr_bucket_delete(b);
+ }
+ }
+ return ap_pass_brigade(f->next, bb);
+}
+
+static int ws_post_read(request_rec *r)
+{
+
+ if (r->connection->master) {
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection);
+ if (conn_ctx && conn_ctx->is_upgrade &&
+ !h2_config_sgeti(r->server, H2_CONF_WEBSOCKETS)) {
+ return HTTP_NOT_IMPLEMENTED;
+ }
+ }
+ return DECLINED;
+}
+
+void h2_ws_register_hooks(void)
+{
+ ap_hook_post_read_request(ws_post_read, NULL, NULL, APR_HOOK_MIDDLE);
+ c2_ws_out_filter_handle =
+ ap_register_output_filter("H2_C2_WS_OUT", h2_c2_ws_filter_out,
+ NULL, AP_FTYPE_NETWORK);
+}
+
+#else /* H2_USE_WEBSOCKETS */
+
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+ conn_rec *c2, int no_body)
+{
+ (void)c2;
+ (void)no_body;
+ /* no rewriting */
+ return req;
+}
+
+void h2_ws_register_hooks(void)
+{
+ /* NOP */
+}
+
+#endif /* H2_USE_WEBSOCKETS (else part) */
diff --git a/modules/http2/h2_ws.h b/modules/http2/h2_ws.h
new file mode 100644
index 0000000..a94d300
--- /dev/null
+++ b/modules/http2/h2_ws.h
@@ -0,0 +1,35 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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_ws__
+#define __mod_h2__h2_ws__
+
+#include "h2.h"
+
+/**
+ * Rewrite a websocket request.
+ *
+ * @param req the h2 request to rewrite
+ * @param c2 the connection to process the request on
+ * @param no_body != 0 iff the request is known to have no body
+ * @return the websocket request for internal submit
+ */
+const h2_request *h2_ws_rewrite_request(const h2_request *req,
+ conn_rec *c2, int no_body);
+
+void h2_ws_register_hooks(void);
+
+#endif /* defined(__mod_h2__h2_ws__) */
diff --git a/modules/http2/mod_http2.c b/modules/http2/mod_http2.c
index 8a1ee3f..1bd34b2 100644
--- a/modules/http2/mod_http2.c
+++ b/modules/http2/mod_http2.c
@@ -42,6 +42,7 @@
#include "h2_switch.h"
#include "h2_version.h"
#include "h2_bucket_beam.h"
+#include "h2_ws.h"
static void h2_hooks(apr_pool_t *pool);
@@ -199,6 +200,7 @@ static void h2_hooks(apr_pool_t *pool)
h2_c1_register_hooks();
h2_switch_register_hooks();
h2_c2_register_hooks();
+ h2_ws_register_hooks();
/* Setup subprocess env for certain variables
*/
diff --git a/modules/http2/mod_http2.dsp b/modules/http2/mod_http2.dsp
index d9ff222..9775534 100644
--- a/modules/http2/mod_http2.dsp
+++ b/modules/http2/mod_http2.dsp
@@ -173,6 +173,10 @@ SOURCE=./h2_workers.c
# End Source File
# Begin Source File
+SOURCE=./h2_ws.c
+# End Source File
+# Begin Source File
+
SOURCE=./mod_http2.c
# End Source File
# Begin Source File
diff --git a/modules/http2/mod_http2.h b/modules/http2/mod_http2.h
index f68edcd..9cb04a6 100644
--- a/modules/http2/mod_http2.h
+++ b/modules/http2/mod_http2.h
@@ -32,6 +32,29 @@ APR_DECLARE_OPTIONAL_FN(void,
http2_get_num_workers, (server_rec *s,
int *minw, int *max));
+#define AP_HTTP2_HAS_GET_POLLFD
+
+/**
+ * Get a apr_pollfd_t populated for a h2 connection where
+ * (c->master != NULL) is true and pipes are supported.
+ * To be used in Apache modules implementing WebSockets in Apache httpd
+ * versions that do not support the corresponding `ap_get_pollfd_from_conn()`
+ * function.
+ * When available, use `ap_get_pollfd_from_conn()` instead of this function.
+ *
+ * How it works: pass in a `apr_pollfd_t` which gets populated for
+ * monitoring the input of connection `c`. If `c` is not a HTTP/2
+ * stream connection, the function will return `APR_ENOTIMPL`.
+ * `ptimeout` is optional and, if passed, will get the timeout in effect
+ *
+ * On platforms without support for pipes (e.g. Windows), this function
+ * will return `APR_ENOTIMPL`.
+ */
+APR_DECLARE_OPTIONAL_FN(apr_status_t,
+ http2_get_pollfd_from_conn,
+ (conn_rec *c, struct apr_pollfd_t *pfd,
+ apr_interval_time_t *ptimeout));
+
/*******************************************************************************
* START HTTP/2 request engines (DEPRECATED)
******************************************************************************/
diff --git a/modules/http2/mod_proxy_http2.c b/modules/http2/mod_proxy_http2.c
index 5abccab..ebf8f61 100644
--- a/modules/http2/mod_proxy_http2.c
+++ b/modules/http2/mod_proxy_http2.c
@@ -50,8 +50,7 @@ static int (*is_h2)(conn_rec *c);
typedef struct h2_proxy_ctx {
const char *id;
- conn_rec *master;
- conn_rec *owner;
+ conn_rec *cfront;
apr_pool_t *pool;
server_rec *server;
const char *proxy_func;
@@ -66,10 +65,10 @@ typedef struct h2_proxy_ctx {
unsigned is_ssl : 1;
request_rec *r; /* the request processed in this ctx */
- apr_status_t r_status; /* status of request work */
+ int r_status; /* status of request work */
int r_done; /* request was processed, not necessarily successfully */
int r_may_retry; /* request may be retried */
- h2_proxy_session *session; /* current http2 session against backend */
+ int has_reusable_session; /* http2 session is live and clean */
} h2_proxy_ctx;
static int h2_proxy_post_config(apr_pool_t *p, apr_pool_t *plog,
@@ -159,11 +158,16 @@ static int proxy_http2_canon(request_rec *r, char *url)
search = r->args;
}
else {
+#ifdef PROXY_CANONENC_NOENCODEDSLASHENCODING
core_dir_config *d = ap_get_core_module_config(r->per_dir_config);
int flags = d->allow_encoded_slashes && !d->decode_encoded_slashes ? PROXY_CANONENC_NOENCODEDSLASHENCODING : 0;
path = ap_proxy_canonenc_ex(r->pool, url, (int)strlen(url),
enc_path, flags, r->proxyreq);
+#else
+ path = ap_proxy_canonenc(r->pool, url, (int)strlen(url),
+ enc_path, 0, r->proxyreq);
+#endif
if (!path) {
return HTTP_BAD_REQUEST;
}
@@ -227,79 +231,81 @@ static apr_status_t add_request(h2_proxy_session *session, request_rec *r)
}
static void request_done(h2_proxy_ctx *ctx, request_rec *r,
- apr_status_t status, int touched)
+ apr_status_t status, int touched, int error_code)
{
if (r == ctx->r) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, r->connection,
- "h2_proxy_session(%s): request done, touched=%d",
- ctx->id, touched);
+ "h2_proxy_session(%s): request done, touched=%d, error=%d",
+ ctx->id, touched, error_code);
ctx->r_done = 1;
if (touched) ctx->r_may_retry = 0;
- ctx->r_status = ((status == APR_SUCCESS)? APR_SUCCESS
- : HTTP_SERVICE_UNAVAILABLE);
+ ctx->r_status = error_code? HTTP_BAD_GATEWAY :
+ ((status == APR_SUCCESS)? OK :
+ ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE));
}
}
static void session_req_done(h2_proxy_session *session, request_rec *r,
- apr_status_t status, int touched)
+ apr_status_t status, int touched, int error_code)
{
- request_done(session->user_data, r, status, touched);
+ request_done(session->user_data, r, status, touched, error_code);
}
static apr_status_t ctx_run(h2_proxy_ctx *ctx) {
apr_status_t status = OK;
+ h2_proxy_session *session;
int h2_front;
/* Step Four: Send the Request in a new HTTP/2 stream and
* loop until we got the response or encounter errors.
*/
- h2_front = is_h2? is_h2(ctx->owner) : 0;
- ctx->session = h2_proxy_session_setup(ctx->id, ctx->p_conn, ctx->conf,
- h2_front, 30,
- h2_proxy_log2((int)ctx->req_buffer_size),
- session_req_done);
- if (!ctx->session) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner,
+ ctx->has_reusable_session = 0; /* don't know yet */
+ h2_front = is_h2? is_h2(ctx->cfront) : 0;
+ session = h2_proxy_session_setup(ctx->id, ctx->p_conn, ctx->conf,
+ h2_front, 30,
+ h2_proxy_log2((int)ctx->req_buffer_size),
+ session_req_done);
+ if (!session) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->cfront,
APLOGNO(03372) "session unavailable");
return HTTP_SERVICE_UNAVAILABLE;
}
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03373)
- "eng(%s): run session %s", ctx->id, ctx->session->id);
- ctx->session->user_data = ctx;
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->cfront, APLOGNO(03373)
+ "eng(%s): run session %s", ctx->id, session->id);
+ session->user_data = ctx;
ctx->r_done = 0;
- add_request(ctx->session, ctx->r);
+ add_request(session, ctx->r);
- while (!ctx->owner->aborted && !ctx->r_done) {
+ while (!ctx->cfront->aborted && !ctx->r_done) {
- status = h2_proxy_session_process(ctx->session);
+ status = h2_proxy_session_process(session);
if (status != APR_SUCCESS) {
/* Encountered an error during session processing */
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront,
APLOGNO(03375) "eng(%s): end of session %s",
- ctx->id, ctx->session->id);
+ ctx->id, session->id);
/* Any open stream of that session needs to
* a) be reopened on the new session iff safe to do so
* b) reported as done (failed) otherwise
*/
- h2_proxy_session_cleanup(ctx->session, session_req_done);
+ h2_proxy_session_cleanup(session, session_req_done);
goto out;
}
}
out:
- if (ctx->owner->aborted) {
+ if (ctx->cfront->aborted) {
/* master connection gone */
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront,
APLOGNO(03374) "eng(%s): master connection gone", ctx->id);
/* cancel all ongoing requests */
- h2_proxy_session_cancel_all(ctx->session);
- h2_proxy_session_process(ctx->session);
+ h2_proxy_session_cancel_all(session);
+ h2_proxy_session_process(session);
}
-
- ctx->session->user_data = NULL;
- ctx->session = NULL;
+ ctx->has_reusable_session = h2_proxy_session_is_reusable(session);
+ session->user_data = NULL;
return status;
}
@@ -344,9 +350,8 @@ static int proxy_http2_handler(request_rec *r,
}
ctx = apr_pcalloc(r->pool, sizeof(*ctx));
- ctx->master = r->connection->master? r->connection->master : r->connection;
- ctx->id = apr_psprintf(r->pool, "%ld", (long)ctx->master->id);
- ctx->owner = r->connection;
+ ctx->id = apr_psprintf(r->pool, "%ld", (long)r->connection->id);
+ ctx->cfront = r->connection;
ctx->pool = r->pool;
ctx->server = r->server;
ctx->proxy_func = proxy_func;
@@ -359,7 +364,7 @@ static int proxy_http2_handler(request_rec *r,
ctx->r_done = 0;
ctx->r_may_retry = 1;
- ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, ctx);
+ ap_set_module_config(ctx->cfront->conn_config, &proxy_http2_module, ctx);
/* scheme says, this is for us. */
apr_table_setn(ctx->r->notes, H2_PROXY_REQ_URL_NOTE, url);
@@ -367,7 +372,7 @@ static int proxy_http2_handler(request_rec *r,
"H2: serving URL %s", url);
run_connect:
- if (ctx->owner->aborted) goto cleanup;
+ if (ctx->cfront->aborted) goto cleanup;
/* 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
@@ -395,7 +400,7 @@ run_connect:
* backend->hostname. */
if (ap_proxy_connect_backend(ctx->proxy_func, ctx->p_conn, ctx->worker,
ctx->server)) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03352)
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->cfront, APLOGNO(03352)
"H2: failed to make connection to backend: %s",
ctx->p_conn->hostname);
goto cleanup;
@@ -404,11 +409,11 @@ run_connect:
/* Step Three: Create conn_rec for the socket we have open now. */
status = ap_proxy_connection_create_ex(ctx->proxy_func, ctx->p_conn, ctx->r);
if (status != OK) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront, APLOGNO(03353)
"setup new connection: is_ssl=%d %s %s %s",
ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname,
locurl, ctx->p_conn->hostname);
- ctx->r_status = status;
+ ctx->r_status = ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE);
goto cleanup;
}
@@ -419,10 +424,10 @@ run_connect:
"proxy-request-alpn-protos", "h2");
}
- if (ctx->owner->aborted) goto cleanup;
+ if (ctx->cfront->aborted) goto cleanup;
status = ctx_run(ctx);
- if (ctx->r_status != APR_SUCCESS && ctx->r_may_retry && !ctx->owner->aborted) {
+ if (ctx->r_status != OK && ctx->r_may_retry && !ctx->cfront->aborted) {
/* Not successfully processed, but may retry, tear down old conn and start over */
if (ctx->p_conn) {
ctx->p_conn->close = 1;
@@ -436,15 +441,16 @@ run_connect:
if (reconnects < 2) {
goto run_connect;
}
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(10023)
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->cfront, APLOGNO(10023)
"giving up after %d reconnects, request-done=%d",
reconnects, ctx->r_done);
}
cleanup:
if (ctx->p_conn) {
- if (status != APR_SUCCESS) {
- /* close socket when errors happened or session shut down (EOF) */
+ if (status != APR_SUCCESS || !ctx->has_reusable_session) {
+ /* close socket when errors happened or session is not "clean",
+ * meaning in a working condition with no open streams */
ctx->p_conn->close = 1;
}
#if AP_MODULE_MAGIC_AT_LEAST(20140207, 2)
@@ -454,9 +460,15 @@ cleanup:
ctx->p_conn = NULL;
}
- ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, NULL);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
+ ap_set_module_config(ctx->cfront->conn_config, &proxy_http2_module, NULL);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront,
APLOGNO(03377) "leaving handler");
+ if (ctx->r_status != OK) {
+ ap_die(ctx->r_status, r);
+ }
+ else if (status != APR_SUCCESS) {
+ ap_die(ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE), r);
+ }
return ctx->r_status;
}