From: Stefan Eissing Date: Tue, 24 Nov 2015 16:58:31 +0000 (+0000) Subject: mod_http2, version 1.0.7 X-Git-Tag: 2.4.18~49 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=de648ac7749fe94c38cb85088feb069a11c48e3c;p=thirdparty%2Fapache%2Fhttpd.git mod_http2, version 1.0.7 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1716210 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index f3f5c1bb035..eb50dcbf3c3 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,14 @@ Changes with Apache 2.4.18 + *) mod_http2: fixes crash on connection abort for a busy connection. + fixes crash on a request that did not produce any response. + [Stefan Eissing] + + *) mod_http2: trailers are sent after reponse body if set in request_rec + trailers_out before the end-of-request bucket is sent through the + output filters. [Stefan Eissing] + *) mod_http2: incoming trailers (headers after request body) are properly forwarded to the processing engine. [Stefan Eissing] diff --git a/modules/http2/config.m4 b/modules/http2/config.m4 index f383b3cd838..e10bb8158f7 100644 --- a/modules/http2/config.m4 +++ b/modules/http2/config.m4 @@ -154,6 +154,8 @@ AC_DEFUN([APACHE_CHECK_NGHTTP2],[ if test "x$liberrors" != "x"; then AC_MSG_WARN([nghttp2 library is unusable]) fi + AC_CHECK_FUNCS([nghttp2_session_change_stream_priority], + [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_CHANGE_PRIO"])], []) else AC_MSG_WARN([nghttp2 version is too old]) fi diff --git a/modules/http2/h2_bucket_eoc.c b/modules/http2/h2_bucket_eoc.c index 8b145cf29ed..3ddb54d68a9 100644 --- a/modules/http2/h2_bucket_eoc.c +++ b/modules/http2/h2_bucket_eoc.c @@ -90,10 +90,11 @@ static void bucket_destroy(void *data) if (apr_bucket_shared_destroy(h)) { h2_session *session = h->session; + apr_bucket_free(h); if (session) { h2_session_eoc_callback(session); + /* all is gone now */ } - apr_bucket_free(h); } } diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c index 7ac4297b328..7dc0b20d20d 100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@ -43,7 +43,7 @@ static h2_config defconf = { H2_INITIAL_WINDOW_SIZE, /* window_size */ -1, /* min workers */ -1, /* max workers */ - 10 * 60, /* max workers idle secs */ + 10, /* max workers idle secs */ 64 * 1024, /* stream max mem size */ NULL, /* no alt-svcs */ -1, /* alt-svc max age */ diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c index d4f56c66306..6fec75ea9a0 100644 --- a/modules/http2/h2_conn.c +++ b/modules/http2/h2_conn.c @@ -177,10 +177,6 @@ apr_status_t h2_conn_process(conn_rec *c, request_rec *r) ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, "h2_session(%ld): done", session->id); - h2_session_close(session); - h2_session_flush(session); - /* hereafter session might be gone */ - /* Make sure this connection gets closed properly. */ ap_update_child_status_from_conn(c->sbh, SERVER_CLOSING, c); c->keepalive = AP_CONN_CLOSE; @@ -188,6 +184,8 @@ apr_status_t h2_conn_process(conn_rec *c, request_rec *r) c->cs->state = CONN_STATE_WRITE_COMPLETION; } + h2_session_close(session); + /* hereafter session will be gone */ return status; } diff --git a/modules/http2/h2_conn_io.c b/modules/http2/h2_conn_io.c index aa8d4d58028..485a8bd47ea 100644 --- a/modules/http2/h2_conn_io.c +++ b/modules/http2/h2_conn_io.c @@ -23,6 +23,7 @@ #include #include "h2_private.h" +#include "h2_bucket_eoc.h" #include "h2_config.h" #include "h2_conn_io.h" #include "h2_h2.h" @@ -44,20 +45,20 @@ #define WRITE_BUFFER_SIZE (8*WRITE_SIZE_MAX) -apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c) +apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, apr_pool_t *pool) { h2_config *cfg = h2_config_get(c); io->connection = c; - io->input = apr_brigade_create(c->pool, c->bucket_alloc); - io->output = apr_brigade_create(c->pool, c->bucket_alloc); + io->input = apr_brigade_create(pool, c->bucket_alloc); + io->output = apr_brigade_create(pool, c->bucket_alloc); io->buflen = 0; io->is_tls = h2_h2_is_tls(c); io->buffer_output = io->is_tls; if (io->buffer_output) { io->bufsize = WRITE_BUFFER_SIZE; - io->buffer = apr_pcalloc(c->pool, io->bufsize); + io->buffer = apr_pcalloc(pool, io->bufsize); } else { io->bufsize = 0; @@ -115,6 +116,8 @@ static apr_status_t h2_conn_io_bucket_read(h2_conn_io *io, &bucket_length, block); if (status == APR_SUCCESS && bucket_length > 0) { + apr_size_t consumed = 0; + if (APLOGctrace2(io->connection)) { char buffer[32]; h2_util_hex_dump(buffer, sizeof(buffer)/sizeof(buffer[0]), @@ -124,20 +127,18 @@ static apr_status_t h2_conn_io_bucket_read(h2_conn_io *io, io->connection->id, (int)bucket_length, buffer); } - if (bucket_length > 0) { - apr_size_t consumed = 0; - status = on_read_cb(bucket_data, bucket_length, - &consumed, pdone, puser); - if (status == APR_SUCCESS && bucket_length > consumed) { - /* We have data left in the bucket. Split it. */ - status = apr_bucket_split(bucket, consumed); - } - readlen += consumed; + status = on_read_cb(bucket_data, bucket_length, &consumed, + pdone, puser); + if (status == APR_SUCCESS && bucket_length > consumed) { + /* We have data left in the bucket. Split it. */ + status = apr_bucket_split(bucket, consumed); } + readlen += consumed; } } apr_bucket_delete(bucket); } + if (readlen == 0 && status == APR_SUCCESS && block == APR_NONBLOCK_READ) { return APR_EAGAIN; } @@ -158,10 +159,10 @@ apr_status_t h2_conn_io_read(h2_conn_io *io, /* Seems something is left from a previous read, lets * satisfy our caller with the data we already have. */ status = h2_conn_io_bucket_read(io, block, on_read_cb, puser, &done); + apr_brigade_cleanup(io->input); if (status != APR_SUCCESS || done) { return status; } - apr_brigade_cleanup(io->input); } /* We only do a blocking read when we have no streams to process. So, @@ -179,6 +180,9 @@ apr_status_t h2_conn_io_read(h2_conn_io *io, ap_update_child_status(io->connection->sbh, SERVER_BUSY_READ, NULL); } + /* TODO: replace this with a connection filter itself, so that we + * no longer need to transfer incoming buckets to our own brigade. + */ status = ap_get_brigade(io->connection->input_filters, io->input, AP_MODE_READBYTES, block, 64 * 4096); @@ -379,4 +383,19 @@ apr_status_t h2_conn_io_flush(h2_conn_io *io) apr_status_t h2_conn_io_pass(h2_conn_io *io) { return h2_conn_io_flush_int(io, 0); +} + +apr_status_t h2_conn_io_close(h2_conn_io *io, void *session) +{ + apr_bucket *b; + + /* Send out anything in our buffers */ + h2_conn_io_flush_int(io, 0); + + b = h2_bucket_eoc_create(io->connection->bucket_alloc, session); + APR_BRIGADE_INSERT_TAIL(io->output, b); + b = apr_bucket_flush_create(io->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(io->output, b); + return ap_pass_brigade(io->connection->output_filters, io->output); + /* and all is gone */ } \ No newline at end of file diff --git a/modules/http2/h2_conn_io.h b/modules/http2/h2_conn_io.h index 4406261a33b..a0dd0d0e5ca 100644 --- a/modules/http2/h2_conn_io.h +++ b/modules/http2/h2_conn_io.h @@ -42,7 +42,7 @@ typedef struct { int unflushed; } h2_conn_io; -apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c); +apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, apr_pool_t *pool); int h2_conn_io_is_buffered(h2_conn_io *io); @@ -65,5 +65,6 @@ apr_status_t h2_conn_io_consider_flush(h2_conn_io *io); apr_status_t h2_conn_io_pass(h2_conn_io *io); apr_status_t h2_conn_io_flush(h2_conn_io *io); +apr_status_t h2_conn_io_close(h2_conn_io *io, void *session); #endif /* defined(__mod_h2__h2_conn_io__) */ diff --git a/modules/http2/h2_from_h1.c b/modules/http2/h2_from_h1.c index 43a4f0822b3..755b7d6815b 100644 --- a/modules/http2/h2_from_h1.c +++ b/modules/http2/h2_from_h1.c @@ -51,10 +51,6 @@ h2_from_h1 *h2_from_h1_create(int stream_id, apr_pool_t *pool) apr_status_t h2_from_h1_destroy(h2_from_h1 *from_h1) { - if (from_h1->response) { - h2_response_destroy(from_h1->response); - from_h1->response = NULL; - } from_h1->bb = NULL; return APR_SUCCESS; } @@ -520,7 +516,7 @@ apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb) if (eb) { int st = eb->status; apr_brigade_cleanup(bb); - ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, "h2_from_h1(%d): err bucket status=%d", from_h1->stream_id, st); ap_die(st, r); @@ -555,3 +551,38 @@ apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb) } return ap_pass_brigade(f->next, bb); } + +apr_status_t h2_response_trailers_filter(ap_filter_t *f, apr_bucket_brigade *bb) +{ + h2_task *task = f->ctx; + h2_from_h1 *from_h1 = task->output? task->output->from_h1 : NULL; + request_rec *r = f->r; + apr_bucket *b; + + if (from_h1 && from_h1->response) { + /* Detect the EOR bucket and forward any trailers that may have + * been set to our h2_response. + */ + for (b = APR_BRIGADE_FIRST(bb); + b != APR_BRIGADE_SENTINEL(bb); + b = APR_BUCKET_NEXT(b)) + { + if (AP_BUCKET_IS_EOR(b)) { + /* FIXME: need a better test case than this. + apr_table_setn(r->trailers_out, "X", "1"); */ + if (r->trailers_out && !apr_is_empty_table(r->trailers_out)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, + "h2_from_h1(%d): trailers filter, saving trailers", + from_h1->stream_id); + h2_response_set_trailers(from_h1->response, + apr_table_clone(from_h1->pool, + r->trailers_out)); + } + break; + } + } + } + + return ap_pass_brigade(f->next, bb); +} + diff --git a/modules/http2/h2_from_h1.h b/modules/http2/h2_from_h1.h index 4f5ebad618b..cdd38ca605f 100644 --- a/modules/http2/h2_from_h1.h +++ b/modules/http2/h2_from_h1.h @@ -69,4 +69,6 @@ struct h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1); apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb); +apr_status_t h2_response_trailers_filter(ap_filter_t *f, apr_bucket_brigade *bb); + #endif /* defined(__mod_h2__h2_from_h1__) */ diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c index 54fe9e0fa0a..e48e64e8a46 100644 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@ -667,16 +667,17 @@ static int h2_h2_post_read_req(request_rec *r) ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "adding h1_to_h2_resp output filter"); if (task->serialize_headers) { - ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP"); +/* ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP");*/ ap_add_output_filter("H1_TO_H2_RESP", task, r, r->connection); } else { /* replace the core http filter that formats response headers * in HTTP/1 with our own that collects status and headers */ ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER"); - ap_remove_output_filter_byhandle(r->output_filters, "H2_RESPONSE"); +/* ap_remove_output_filter_byhandle(r->output_filters, "H2_RESPONSE");*/ ap_add_output_filter("H2_RESPONSE", task, r, r->connection); } + ap_add_output_filter("H2_TRAILERS", task, r, r->connection); } return DECLINED; } diff --git a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h index 4974d866115..563abe3fded 100644 --- a/modules/http2/h2_h2.h +++ b/modules/http2/h2_h2.h @@ -58,6 +58,17 @@ extern const char *H2_MAGIC_TOKEN; #define H2_STREAM_CLIENT_INITIATED(id) (id&0x01) +typedef enum { + H2_DEPENDANT_AFTER, + H2_DEPENDANT_INTERLEAVED, + H2_DEPENDANT_BEFORE, +} h2_dependency; + +typedef struct h2_priority { + h2_dependency dependency; + int weight; +} h2_priority; + /** * Provide a user readable description of the HTTP/2 error code- * @param h2_error http/2 error code, as in rfc 7540, ch. 7 diff --git a/modules/http2/h2_io.c b/modules/http2/h2_io.c index b33faee1f3d..6bd2b83739e 100644 --- a/modules/http2/h2_io.c +++ b/modules/http2/h2_io.c @@ -52,7 +52,7 @@ void h2_io_set_response(h2_io *io, h2_response *response) AP_DEBUG_ASSERT(io->pool); AP_DEBUG_ASSERT(response); AP_DEBUG_ASSERT(!io->response); - io->response = h2_response_copy(io->pool, response); + io->response = h2_response_clone(io->pool, response); if (response->rst_error) { h2_io_rst(io, response->rst_error); } @@ -205,8 +205,17 @@ apr_status_t h2_io_out_read_to(h2_io *io, apr_bucket_brigade *bb, return h2_util_move(bb, io->bbout, *plen, NULL, "h2_io_read_to"); } +static void process_trailers(h2_io *io, apr_table_t *trailers) +{ + if (trailers && io->response) { + h2_response_set_trailers(io->response, + apr_table_clone(io->pool, trailers)); + } +} + apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, - apr_size_t maxlen, int *pfile_handles_allowed) + apr_size_t maxlen, apr_table_t *trailers, + int *pfile_handles_allowed) { apr_status_t status; int start_allowed; @@ -233,6 +242,7 @@ apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, return status; } + process_trailers(io, trailers); if (!io->bbout) { io->bbout = apr_brigade_create(io->pool, io->bucket_alloc); } @@ -258,17 +268,20 @@ apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, } -apr_status_t h2_io_out_close(h2_io *io) +apr_status_t h2_io_out_close(h2_io *io, apr_table_t *trailers) { if (io->rst_error) { return APR_ECONNABORTED; } - if (!io->bbout) { - io->bbout = apr_brigade_create(io->pool, io->bucket_alloc); - } - if (!io->eos_out && !h2_util_has_eos(io->bbout, -1)) { - APR_BRIGADE_INSERT_TAIL(io->bbout, - apr_bucket_eos_create(io->bbout->bucket_alloc)); + if (!io->eos_out) { /* EOS has not been read yet */ + process_trailers(io, trailers); + if (!io->bbout) { + io->bbout = apr_brigade_create(io->pool, io->bucket_alloc); + } + if (!h2_util_has_eos(io->bbout, -1)) { + APR_BRIGADE_INSERT_TAIL(io->bbout, + apr_bucket_eos_create(io->bbout->bucket_alloc)); + } } return APR_SUCCESS; } diff --git a/modules/http2/h2_io.h b/modules/http2/h2_io.h index 1fd1167747a..dcf493539bb 100644 --- a/modules/http2/h2_io.h +++ b/modules/http2/h2_io.h @@ -130,13 +130,14 @@ apr_status_t h2_io_out_read_to(h2_io *io, apr_off_t *plen, int *peos); apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, - apr_size_t maxlen, int *pfile_buckets_allowed); + apr_size_t maxlen, apr_table_t *trailers, + int *pfile_buckets_allowed); /** * Closes the input. After existing data has been read, APR_EOF will * be returned. */ -apr_status_t h2_io_out_close(h2_io *io); +apr_status_t h2_io_out_close(h2_io *io, apr_table_t *trailers); /** * Gives the overall length of the data that is currently queued for diff --git a/modules/http2/h2_io_set.c b/modules/http2/h2_io_set.c index 74ab508fefe..2bb6e694691 100644 --- a/modules/http2/h2_io_set.c +++ b/modules/http2/h2_io_set.c @@ -145,37 +145,23 @@ h2_io *h2_io_set_pop_highest_prio(h2_io_set *set) return NULL; } -void h2_io_set_destroy_all(h2_io_set *sp) -{ - int i; - for (i = 0; i < sp->list->nelts; ++i) { - h2_io *io = h2_io_IDX(sp->list, i); - h2_io_destroy(io); - } - sp->list->nelts = 0; -} - -void h2_io_set_remove_all(h2_io_set *sp) -{ - sp->list->nelts = 0; -} - int h2_io_set_is_empty(h2_io_set *sp) { AP_DEBUG_ASSERT(sp); return sp->list->nelts == 0; } -void h2_io_set_iter(h2_io_set *sp, +int h2_io_set_iter(h2_io_set *sp, h2_io_set_iter_fn *iter, void *ctx) { int i; for (i = 0; i < sp->list->nelts; ++i) { h2_io *s = h2_io_IDX(sp->list, i); if (!iter(ctx, s)) { - break; + return 0; } } + return 1; } apr_size_t h2_io_set_size(h2_io_set *sp) diff --git a/modules/http2/h2_io_set.h b/modules/http2/h2_io_set.h index 5e7555af92e..04ff8702ed2 100644 --- a/modules/http2/h2_io_set.h +++ b/modules/http2/h2_io_set.h @@ -32,16 +32,24 @@ apr_status_t h2_io_set_add(h2_io_set *set, struct h2_io *io); h2_io *h2_io_set_get(h2_io_set *set, int stream_id); h2_io *h2_io_set_remove(h2_io_set *set, struct h2_io *io); -void h2_io_set_remove_all(h2_io_set *set); -void h2_io_set_destroy_all(h2_io_set *set); int h2_io_set_is_empty(h2_io_set *set); apr_size_t h2_io_set_size(h2_io_set *set); typedef int h2_io_set_iter_fn(void *ctx, struct h2_io *io); -void h2_io_set_iter(h2_io_set *set, - h2_io_set_iter_fn *iter, void *ctx); +/** + * Iterator over all h2_io* in the set or until a + * callback returns 0. It is not safe to add or remove + * set members during iteration. + * + * @param set the set of h2_io to iterate over + * @param iter the function to call for each io + * @param ctx user data for the callback + * @return 1 iff iteration completed for all members + */ +int h2_io_set_iter(h2_io_set *set, + h2_io_set_iter_fn *iter, void *ctx); h2_io *h2_io_set_pop_highest_prio(h2_io_set *set); diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index 3908590985a..6f2a5124651 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -73,6 +73,9 @@ static void have_out_data_for(h2_mplx *m, int stream_id); static void h2_mplx_destroy(h2_mplx *m) { AP_DEBUG_ASSERT(m); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, + "h2_mplx(%ld): destroy, refs=%d", + m->id, m->refs); m->aborted = 1; if (m->ready_ios) { h2_io_set_destroy(m->ready_ios); @@ -83,15 +86,6 @@ static void h2_mplx_destroy(h2_mplx *m) m->stream_ios = NULL; } - if (m->lock) { - apr_thread_mutex_destroy(m->lock); - m->lock = NULL; - } - - if (m->spare_pool) { - apr_pool_destroy(m->spare_pool); - m->spare_pool = NULL; - } if (m->pool) { apr_pool_destroy(m->pool); } @@ -199,13 +193,62 @@ static void workers_unregister(h2_mplx *m) { h2_workers_unregister(m->workers, m); } +static void io_destroy(h2_mplx *m, h2_io *io) +{ + apr_pool_t *pool = io->pool; + + io->pool = NULL; + /* The pool is cleared/destroyed which also closes all + * allocated file handles. Give this count back to our + * file handle pool. */ + m->file_handles_allowed += io->files_handles_owned; + h2_io_set_remove(m->stream_ios, io); + h2_io_set_remove(m->ready_ios, io); + h2_io_destroy(io); + + if (pool) { + apr_pool_clear(pool); + if (m->spare_pool) { + apr_pool_destroy(m->spare_pool); + } + m->spare_pool = pool; + } +} + +static int io_stream_done(h2_mplx *m, h2_io *io, int rst_error) +{ + /* Remove io from ready set, we will never submit it */ + h2_io_set_remove(m->ready_ios, io); + if (io->task_done || h2_tq_remove(m->q, io->id)) { + /* already finished or not even started yet */ + io_destroy(m, io); + return 0; + } + else { + /* cleanup once task is done */ + io->orphaned = 1; + if (rst_error) { + h2_io_rst(io, rst_error); + } + return 1; + } +} + +static int stream_done_iter(void *ctx, h2_io *io) { + return io_stream_done((h2_mplx*)ctx, io, 0); +} + apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait) { apr_status_t status; + workers_unregister(m); - status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { + while (!h2_io_set_iter(m->stream_ios, stream_done_iter, m)) { + /* iterator until all h2_io have been orphaned or destroyed */ + } + release(m, 0); while (m->refs > 0) { m->join_wait = wait; @@ -215,10 +258,11 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait) apr_thread_cond_wait(wait, m->lock); } ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, - "h2_mplx(%ld): release_join -> destroy", m->id); - m->pool = NULL; - apr_thread_mutex_unlock(m->lock); + "h2_mplx(%ld): release_join -> destroy, (#ios=%ld)", + m->id, (long)h2_io_set_size(m->stream_ios)); h2_mplx_destroy(m); + /* all gone */ + /*apr_thread_mutex_unlock(m->lock);*/ } return status; } @@ -230,33 +274,8 @@ void h2_mplx_abort(h2_mplx *m) status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { m->aborted = 1; - h2_io_set_destroy_all(m->stream_ios); apr_thread_mutex_unlock(m->lock); } - workers_unregister(m); -} - - -static void io_destroy(h2_mplx *m, h2_io *io) -{ - apr_pool_t *pool = io->pool; - - io->pool = NULL; - /* The pool is cleared/destroyed which also closes all - * allocated file handles. Give this count back to our - * file handle pool. */ - m->file_handles_allowed += io->files_handles_owned; - h2_io_set_remove(m->stream_ios, io); - h2_io_set_remove(m->ready_ios, io); - h2_io_destroy(io); - - if (pool) { - apr_pool_clear(pool); - if (m->spare_pool) { - apr_pool_destroy(m->spare_pool); - } - m->spare_pool = pool; - } } apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error) @@ -264,9 +283,6 @@ apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error) apr_status_t status; AP_DEBUG_ASSERT(m); - if (m->aborted) { - return APR_ECONNABORTED; - } status = apr_thread_mutex_lock(m->lock); if (APR_SUCCESS == status) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); @@ -275,20 +291,7 @@ apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error) * for processing, e.g. when we received all HEADERs. But when * a stream is cancelled very early, it will not exist. */ if (io) { - /* Remove io from ready set, we will never submit it */ - h2_io_set_remove(m->ready_ios, io); - if (io->task_done || h2_tq_remove(m->q, io->id)) { - /* already finished or not even started yet */ - io_destroy(m, io); - } - else { - /* cleanup once task is done */ - io->orphaned = 1; - if (rst_error) { - h2_io_rst(io, rst_error); - } - } - + io_stream_done(m, io, rst_error); } apr_thread_mutex_unlock(m->lock); @@ -447,7 +450,8 @@ apr_status_t h2_mplx_in_update_windows(h2_mplx *m, apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, h2_io_data_cb *cb, void *ctx, - apr_off_t *plen, int *peos) + apr_off_t *plen, int *peos, + apr_table_t **ptrailers) { apr_status_t status; AP_DEBUG_ASSERT(m); @@ -461,7 +465,6 @@ apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_pre"); status = h2_io_out_readx(io, cb, ctx, plen, peos); - H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_post"); if (status == APR_SUCCESS && cb && io->output_drained) { apr_thread_cond_signal(io->output_drained); @@ -470,6 +473,8 @@ apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, else { status = APR_ECONNABORTED; } + + *ptrailers = (*peos && io->response)? io->response->trailers : NULL; apr_thread_mutex_unlock(m->lock); } return status; @@ -477,7 +482,8 @@ apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id, apr_bucket_brigade *bb, - apr_off_t *plen, int *peos) + apr_off_t *plen, int *peos, + apr_table_t **ptrailers) { apr_status_t status; AP_DEBUG_ASSERT(m); @@ -500,6 +506,7 @@ apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id, else { status = APR_ECONNABORTED; } + *ptrailers = (*peos && io->response)? io->response->trailers : NULL; apr_thread_mutex_unlock(m->lock); } return status; @@ -560,6 +567,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) static apr_status_t out_write(h2_mplx *m, h2_io *io, ap_filter_t* f, apr_bucket_brigade *bb, + apr_table_t *trailers, struct apr_thread_cond_t *iowait) { apr_status_t status = APR_SUCCESS; @@ -572,7 +580,7 @@ static apr_status_t out_write(h2_mplx *m, h2_io *io, && (status == APR_SUCCESS) && !is_aborted(m, &status)) { - status = h2_io_out_write(io, bb, m->stream_max_mem, + status = h2_io_out_write(io, bb, m->stream_max_mem, trailers, &m->file_handles_allowed); /* Wait for data to drain until there is room again */ while (!APR_BRIGADE_EMPTY(bb) @@ -580,6 +588,7 @@ static apr_status_t out_write(h2_mplx *m, h2_io *io, && status == APR_SUCCESS && (m->stream_max_mem <= h2_io_out_length(io)) && !is_aborted(m, &status)) { + trailers = NULL; io->output_drained = iowait; if (f) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, @@ -613,7 +622,7 @@ static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response, h2_io_set_response(io, response); h2_io_set_add(m->ready_ios, io); if (bb) { - status = out_write(m, io, f, bb, iowait); + status = out_write(m, io, f, bb, response->trailers, iowait); } have_out_data_for(m, stream_id); } @@ -649,6 +658,7 @@ apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_response *response, apr_status_t h2_mplx_out_write(h2_mplx *m, int stream_id, ap_filter_t* f, apr_bucket_brigade *bb, + apr_table_t *trailers, struct apr_thread_cond_t *iowait) { apr_status_t status; @@ -661,7 +671,10 @@ apr_status_t h2_mplx_out_write(h2_mplx *m, int stream_id, if (!m->aborted) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { - status = out_write(m, io, f, bb, iowait); + status = out_write(m, io, f, bb, trailers, iowait); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c, + "h2_mplx(%ld-%d): write with trailers=%s", + m->id, io->id, trailers? "yes" : "no"); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_write"); have_out_data_for(m, stream_id); @@ -681,7 +694,7 @@ apr_status_t h2_mplx_out_write(h2_mplx *m, int stream_id, return status; } -apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id) +apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id, apr_table_t *trailers) { apr_status_t status; AP_DEBUG_ASSERT(m); @@ -698,14 +711,17 @@ apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id) * insert an error one so that our streams can properly * reset. */ - h2_response *r = h2_response_create(stream_id, 0, - 500, NULL, m->pool); + h2_response *r = h2_response_die(stream_id, APR_EGENERAL, + io->request, m->pool); status = out_open(m, stream_id, r, NULL, NULL, NULL); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c, "h2_mplx(%ld-%d): close, no response, no rst", m->id, io->id); } - status = h2_io_out_close(io); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c, + "h2_mplx(%ld-%d): close with trailers=%s", + m->id, io->id, trailers? "yes" : "no"); + status = h2_io_out_close(io, trailers); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_close"); have_out_data_for(m, stream_id); diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h index 5c950b9c271..c570e91fd43 100644 --- a/modules/http2/h2_mplx.h +++ b/modules/http2/h2_mplx.h @@ -240,7 +240,8 @@ struct h2_stream *h2_mplx_next_submit(h2_mplx *m, */ apr_status_t h2_mplx_out_readx(h2_mplx *mplx, int stream_id, h2_io_data_cb *cb, void *ctx, - apr_off_t *plen, int *peos); + apr_off_t *plen, int *peos, + apr_table_t **ptrailers); /** * Reads output data into the given brigade. Will never block, but @@ -248,7 +249,8 @@ apr_status_t h2_mplx_out_readx(h2_mplx *mplx, int stream_id, */ apr_status_t h2_mplx_out_read_to(h2_mplx *mplx, int stream_id, apr_bucket_brigade *bb, - apr_off_t *plen, int *peos); + apr_off_t *plen, int *peos, + apr_table_t **ptrailers); /** * Opens the output for the given stream with the specified response. @@ -264,17 +266,19 @@ apr_status_t h2_mplx_out_open(h2_mplx *mplx, int stream_id, * @param stream_id the stream identifier * @param filter the apache filter context of the data * @param bb the bucket brigade to append + * @param trailers optional trailers for response, maybe NULL * @param iowait a conditional used for block/signalling in h2_mplx */ apr_status_t h2_mplx_out_write(h2_mplx *mplx, int stream_id, ap_filter_t* filter, apr_bucket_brigade *bb, + apr_table_t *trailers, struct apr_thread_cond_t *iowait); /** - * Closes the output stream. Readers of this stream will get all pending - * data and then only APR_EOF as result. + * Closes the output for stream stream_id. Optionally forwards trailers + * fromt the processed stream. */ -apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id); +apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id, apr_table_t *trailers); apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error); diff --git a/modules/http2/h2_push.c b/modules/http2/h2_push.c index 703ea761a04..65b2b7b6251 100644 --- a/modules/http2/h2_push.c +++ b/modules/http2/h2_push.c @@ -301,6 +301,9 @@ static int add_push(link_ctx *ctx) h2_request_end_headers(req, ctx->pool, 1); push->req = req; + push->prio.dependency = H2_DEPENDANT_AFTER; + push->prio.weight = NGHTTP2_DEFAULT_WEIGHT; + if (!ctx->pushes) { ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*)); } @@ -386,14 +389,14 @@ apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req, * TODO: This may be extended in the future by hooks or callbacks * where other modules can provide push information directly. */ - if (res->header) { + if (res->headers) { link_ctx ctx; memset(&ctx, 0, sizeof(ctx)); ctx.req = req; ctx.pool = p; - apr_table_do(head_iter, &ctx, res->header, NULL); + apr_table_do(head_iter, &ctx, res->headers, NULL); return ctx.pushes; } return NULL; diff --git a/modules/http2/h2_push.h b/modules/http2/h2_push.h index 64edee65d19..b98a2f73e17 100644 --- a/modules/http2/h2_push.h +++ b/modules/http2/h2_push.h @@ -20,9 +20,9 @@ struct h2_response; struct h2_ngheader; typedef struct h2_push { - int initiating_id; + int initiating_id; const struct h2_request *req; - const char *as; + h2_priority prio; } h2_push; diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index a5f7d9d4fb4..e1a371f6a41 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -19,9 +19,15 @@ #include #include +#include #include -#include +#include #include +#include +#include +#include +#include +#include #include "h2_private.h" #include "h2_mplx.h" @@ -48,7 +54,8 @@ h2_request *h2_request_createn(int id, apr_pool_t *pool, req->authority = authority; req->path = path; req->headers = header? header : apr_table_make(pool, 10); - + req->request_time = apr_time_now(); + return req; } @@ -322,3 +329,117 @@ void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src) dst->eoh = src->eoh; } +request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn) +{ + request_rec *r; + apr_pool_t *p; + int access_status = HTTP_OK; + + apr_pool_create(&p, conn->pool); + apr_pool_tag(p, "request"); + r = apr_pcalloc(p, sizeof(request_rec)); + AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn); + r->pool = p; + r->connection = conn; + r->server = conn->base_server; + + r->user = NULL; + r->ap_auth_type = NULL; + + r->allowed_methods = ap_make_method_list(p, 2); + + r->headers_in = apr_table_copy(r->pool, req->headers); + r->trailers_in = apr_table_make(r->pool, 5); + r->subprocess_env = apr_table_make(r->pool, 25); + r->headers_out = apr_table_make(r->pool, 12); + r->err_headers_out = apr_table_make(r->pool, 5); + r->trailers_out = apr_table_make(r->pool, 5); + r->notes = apr_table_make(r->pool, 5); + + r->request_config = ap_create_request_config(r->pool); + /* Must be set before we run create request hook */ + + r->proto_output_filters = conn->output_filters; + r->output_filters = r->proto_output_filters; + r->proto_input_filters = conn->input_filters; + r->input_filters = r->proto_input_filters; + ap_run_create_request(r); + r->per_dir_config = r->server->lookup_defaults; + + r->sent_bodyct = 0; /* bytect isn't for body */ + + r->read_length = 0; + r->read_body = REQUEST_NO_BODY; + + r->status = HTTP_OK; /* Until further notice */ + r->header_only = 0; + r->the_request = NULL; + + /* Begin by presuming any module can make its own path_info assumptions, + * until some module interjects and changes the value. + */ + r->used_path_info = AP_REQ_DEFAULT_PATH_INFO; + + r->useragent_addr = conn->client_addr; + r->useragent_ip = conn->client_ip; + + ap_run_pre_read_request(r, conn); + + /* Time to populate r with the data we have. */ + r->request_time = req->request_time; + r->method = req->method; + /* Provide quick information about the request method as soon as known */ + r->method_number = ap_method_number_of(r->method); + if (r->method_number == M_GET && r->method[0] == 'H') { + r->header_only = 1; + } + + ap_parse_uri(r, req->path); + r->protocol = (char*)"HTTP/2"; + r->proto_num = HTTP_VERSION(2, 0); + + r->the_request = apr_psprintf(r->pool, "%s %s %s", + r->method, req->path, r->protocol); + + /* update what we think the virtual host is based on the headers we've + * now read. may update status. + * Leave r->hostname empty, vhost will parse if form our Host: header, + * otherwise we get complains about port numbers. + */ + r->hostname = NULL; + ap_update_vhost_from_headers(r); + + /* we may have switched to another server */ + r->per_dir_config = r->server->lookup_defaults; + + /* + * Add the HTTP_IN filter here to ensure that ap_discard_request_body + * called by ap_die and by ap_send_error_response works correctly on + * status codes that do not cause the connection to be dropped and + * in situations where the connection should be kept alive. + */ + ap_add_input_filter_handle(ap_http_input_filter_handle, + NULL, r, r->connection); + + if (access_status != HTTP_OK + || (access_status = ap_run_post_read_request(r))) { + /* Request check post hooks failed. An example of this would be a + * request for a vhost where h2 is disabled --> 421. + */ + ap_die(access_status, r); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); + r = NULL; + goto traceout; + } + + AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, + (char *)r->uri, (char *)r->server->defn_name, + r->status); + return r; +traceout: + AP_READ_REQUEST_FAILURE((uintptr_t)r); + return r; +} + + diff --git a/modules/http2/h2_request.h b/modules/http2/h2_request.h index 19005a88e6d..69d24f38a2e 100644 --- a/modules/http2/h2_request.h +++ b/modules/http2/h2_request.h @@ -38,6 +38,7 @@ struct h2_request { apr_table_t *headers; apr_table_t *trailers; + apr_time_t request_time; apr_off_t content_length; int chunked; int eoh; @@ -66,6 +67,15 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos); void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src); +/** + * Create a request_rec representing the h2_request to be + * processed on the given connection. + * + * @param req the h2 request to process + * @param conn the connection to process the request on + * @return the request_rec representing the request + */ +request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn); #endif /* defined(__mod_h2__h2_request__) */ diff --git a/modules/http2/h2_response.c b/modules/http2/h2_response.c index 2751f2d377c..d16fee29bac 100644 --- a/modules/http2/h2_response.c +++ b/modules/http2/h2_response.c @@ -21,42 +21,30 @@ #include #include #include +#include #include #include "h2_private.h" #include "h2_h2.h" #include "h2_util.h" +#include "h2_request.h" #include "h2_response.h" -h2_response *h2_response_create(int stream_id, - int rst_error, - int http_status, - apr_array_header_t *hlines, - apr_pool_t *pool) +static apr_table_t *parse_headers(apr_array_header_t *hlines, apr_pool_t *pool) { - apr_table_t *header; - h2_response *response = apr_pcalloc(pool, sizeof(h2_response)); - int i; - if (response == NULL) { - return NULL; - } - - response->stream_id = stream_id; - response->rst_error = rst_error; - response->http_status = http_status? http_status : 500; - response->content_length = -1; - if (hlines) { - header = apr_table_make(pool, hlines->nelts); + apr_table_t *headers = apr_table_make(pool, hlines->nelts); + int i; + for (i = 0; i < hlines->nelts; ++i) { char *hline = ((char **)hlines->elts)[i]; char *sep = ap_strchr(hline, ':'); if (!sep) { ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool, - APLOGNO(02955) "h2_response(%d): invalid header[%d] '%s'", - response->stream_id, i, (char*)hline); + APLOGNO(02955) "h2_response: invalid header[%d] '%s'", + i, (char*)hline); /* not valid format, abort */ return NULL; } @@ -66,30 +54,67 @@ h2_response *h2_response_create(int stream_id, } if (!h2_util_ignore_header(hline)) { - apr_table_merge(header, hline, sep); - if (*sep && H2_HD_MATCH_LIT_CS("content-length", hline)) { - char *end; - response->content_length = apr_strtoi64(sep, &end, 10); - if (sep == end) { - ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, - pool, APLOGNO(02956) - "h2_response(%d): content-length" - " value not parsed: %s", - response->stream_id, sep); - response->content_length = -1; - } - } + apr_table_merge(headers, hline, sep); } } + return headers; } else { - header = apr_table_make(pool, 0); + return apr_table_make(pool, 0); } +} + +static h2_response *h2_response_create_int(int stream_id, + int rst_error, + int http_status, + apr_table_t *headers, + apr_pool_t *pool) +{ + h2_response *response; + const char *s; - response->header = header; + if (!headers) { + return NULL; + } + + response = apr_pcalloc(pool, sizeof(h2_response)); + if (response == NULL) { + return NULL; + } + + response->stream_id = stream_id; + response->rst_error = rst_error; + response->http_status = http_status? http_status : 500; + response->content_length = -1; + response->headers = headers; + + s = apr_table_get(headers, "Content-Length"); + if (s) { + char *end; + + response->content_length = apr_strtoi64(s, &end, 10); + if (s == end) { + ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, + pool, APLOGNO(02956) + "h2_response: content-length" + " value not parsed: %s", s); + response->content_length = -1; + } + } return response; } + +h2_response *h2_response_create(int stream_id, + int rst_error, + int http_status, + apr_array_header_t *hlines, + apr_pool_t *pool) +{ + return h2_response_create_int(stream_id, rst_error, http_status, + parse_headers(hlines, pool), pool); +} + h2_response *h2_response_rcreate(int stream_id, request_rec *r, apr_table_t *header, apr_pool_t *pool) { @@ -101,7 +126,7 @@ h2_response *h2_response_rcreate(int stream_id, request_rec *r, response->stream_id = stream_id; response->http_status = r->status; response->content_length = -1; - response->header = header; + response->headers = header; if (response->http_status == HTTP_FORBIDDEN) { const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden"); @@ -119,21 +144,37 @@ h2_response *h2_response_rcreate(int stream_id, request_rec *r, return response; } -void h2_response_destroy(h2_response *response) +h2_response *h2_response_die(int stream_id, apr_status_t type, + const struct h2_request *req, apr_pool_t *pool) { - (void)response; + apr_table_t *headers = apr_table_make(pool, 5); + char *date = NULL; + + date = apr_palloc(pool, APR_RFC822_DATE_LEN); + ap_recent_rfc822_date(date, req->request_time); + apr_table_setn(headers, "Date", date); + apr_table_setn(headers, "Server", ap_get_server_banner()); + + return h2_response_create_int(stream_id, 0, 500, headers, pool); } -h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from) +h2_response *h2_response_clone(apr_pool_t *pool, h2_response *from) { h2_response *to = apr_pcalloc(pool, sizeof(h2_response)); to->stream_id = from->stream_id; to->http_status = from->http_status; to->content_length = from->content_length; - if (from->header) { - to->header = apr_table_clone(pool, from->header); + if (from->headers) { + to->headers = apr_table_clone(pool, from->headers); + } + if (from->trailers) { + to->trailers = apr_table_clone(pool, from->trailers); } return to; } +void h2_response_set_trailers(h2_response *response, apr_table_t *trailers) +{ + response->trailers = trailers; +} diff --git a/modules/http2/h2_response.h b/modules/http2/h2_response.h index 4085a41bdfa..426eeead72f 100644 --- a/modules/http2/h2_response.h +++ b/modules/http2/h2_response.h @@ -16,6 +16,7 @@ #ifndef __mod_h2__h2_response__ #define __mod_h2__h2_response__ +struct h2_request; struct h2_push; typedef struct h2_response { @@ -23,21 +24,59 @@ typedef struct h2_response { int rst_error; int http_status; apr_off_t content_length; - apr_table_t *header; - apr_table_t *trailer; + apr_table_t *headers; + apr_table_t *trailers; } h2_response; +/** + * Create the response from the status and parsed header lines. + * @param stream_id id of the stream to create the response for + * @param rst_error error for reset or 0 + * @param http_status http status code of response + * @param hlines the text lines of the response header + * @param pool the memory pool to use + */ h2_response *h2_response_create(int stream_id, int rst_error, int http_status, apr_array_header_t *hlines, apr_pool_t *pool); +/** + * Create the response from the given request_rec. + * @param stream_id id of the stream to create the response for + * @param r the request record which was processed + * @param header the headers of the response + * @param pool the memory pool to use + */ h2_response *h2_response_rcreate(int stream_id, request_rec *r, apr_table_t *header, apr_pool_t *pool); -void h2_response_destroy(h2_response *response); +/** + * Create the response for the given error. + * @param stream_id id of the stream to create the response for + * @param type the error code + * @param req the original h2_request + * @param pool the memory pool to use + */ +h2_response *h2_response_die(int stream_id, apr_status_t type, + const struct h2_request *req, apr_pool_t *pool); -h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from); +/** + * Deep copies the response into a new pool. + * @param pool the pool to use for the clone + * @param from the response to clone + * @return the cloned response + */ +h2_response *h2_response_clone(apr_pool_t *pool, h2_response *from); + +/** + * Set the trailers in the reponse. Will replace any existing trailers. Will + * *not* clone the table. + * + * @param response the repsone to set the trailers for + * @param trailers the trailers to set + */ +void h2_response_set_trailers(h2_response *response, apr_table_t *trailers); #endif /* defined(__mod_h2__h2_response__) */ diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index d70eefd2965..91a3d9465cd 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -14,6 +14,7 @@ */ #include +#include #include #include #include @@ -24,7 +25,6 @@ #include #include "h2_private.h" -#include "h2_bucket_eoc.h" #include "h2_bucket_eos.h" #include "h2_config.h" #include "h2_h2.h" @@ -84,11 +84,6 @@ h2_stream *h2_session_open_stream(h2_session *session, int stream_id) return stream; } -apr_status_t h2_session_flush(h2_session *session) -{ - return h2_conn_io_flush(&session->io); -} - /** * Determine the importance of streams when scheduling tasks. * - if both stream depend on the same one, compare weights @@ -590,6 +585,44 @@ static apr_status_t session_pool_cleanup(void *data) return APR_SUCCESS; } +static void *session_malloc(size_t size, void *ctx) +{ + h2_session *session = ctx; + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c, + "h2_session(%ld): malloc(%ld)", + session->id, (long)size); + return malloc(size); +} + +static void session_free(void *p, void *ctx) +{ + h2_session *session = ctx; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c, + "h2_session(%ld): free()", + session->id); + free(p); +} + +static void *session_calloc(size_t n, size_t size, void *ctx) +{ + h2_session *session = ctx; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c, + "h2_session(%ld): calloc(%ld, %ld)", + session->id, (long)n, (long)size); + return calloc(n, size); +} + +static void *session_realloc(void *p, size_t size, void *ctx) +{ + h2_session *session = ctx; + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c, + "h2_session(%ld): realloc(%ld)", + session->id, (long)size); + return realloc(p, size); +} + static h2_session *h2_session_create_int(conn_rec *c, request_rec *r, h2_config *config, @@ -608,17 +641,18 @@ static h2_session *h2_session_create_int(conn_rec *c, session = apr_pcalloc(pool, sizeof(h2_session)); if (session) { int rv; + nghttp2_mem *mem; + session->id = c->id; session->c = c; session->r = r; + session->pool = pool; apr_pool_pre_cleanup_register(pool, session, session_pool_cleanup); session->max_stream_count = h2_config_geti(config, H2_CONF_MAX_STREAMS); session->max_stream_mem = h2_config_geti(config, H2_CONF_STREAM_MAX_MEM); - session->pool = pool; - status = apr_thread_cond_create(&session->iowait, session->pool); if (status != APR_SUCCESS) { return NULL; @@ -629,7 +663,7 @@ static h2_session *h2_session_create_int(conn_rec *c, session->workers = workers; session->mplx = h2_mplx_create(c, session->pool, workers); - h2_conn_io_init(&session->io, c); + h2_conn_io_init(&session->io, c, session->pool); session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc); status = init_callbacks(c, &callbacks); @@ -648,16 +682,27 @@ static h2_session *h2_session_create_int(conn_rec *c, h2_session_destroy(session); return NULL; } - nghttp2_option_set_peer_max_concurrent_streams(options, (uint32_t)session->max_stream_count); - /* We need to handle window updates ourself, otherwise we * get flooded by nghttp2. */ nghttp2_option_set_no_auto_window_update(options, 1); - rv = nghttp2_session_server_new2(&session->ngh2, callbacks, - session, options); + if (APLOGctrace6(c)) { + mem = apr_pcalloc(session->pool, sizeof(nghttp2_mem)); + mem->mem_user_data = session; + mem->malloc = session_malloc; + mem->free = session_free; + mem->calloc = session_calloc; + mem->realloc = session_realloc; + + rv = nghttp2_session_server_new3(&session->ngh2, callbacks, + session, options, mem); + } + else { + rv = nghttp2_session_server_new2(&session->ngh2, callbacks, + session, options); + } nghttp2_session_callbacks_del(callbacks); nghttp2_option_del(options); @@ -703,10 +748,6 @@ static void h2_session_cleanup(h2_session *session) apr_pool_destroy(session->spare); session->spare = NULL; } - if (session->mplx) { - h2_mplx_release_and_join(session->mplx, session->iowait); - session->mplx = NULL; - } } void h2_session_destroy(h2_session *session) @@ -714,6 +755,10 @@ void h2_session_destroy(h2_session *session) AP_DEBUG_ASSERT(session); h2_session_cleanup(session); + if (session->mplx) { + h2_mplx_release_and_join(session->mplx, session->iowait); + session->mplx = NULL; + } if (session->streams) { if (!h2_stream_set_is_empty(session->streams)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, @@ -993,10 +1038,8 @@ apr_status_t h2_session_close(h2_session *session) ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0,session->c, "h2_session: closing, writing eoc"); - h2_session_cleanup(session); - return h2_conn_io_writeb(&session->io, - h2_bucket_eoc_create(session->c->bucket_alloc, - session)); + h2_session_cleanup(session); + return h2_conn_io_close(&session->io, session); } static ssize_t stream_data_cb(nghttp2_session *ng2s, @@ -1082,10 +1125,14 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s, int rv; nh = h2_util_ngheader_make(stream->pool, trailers); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + "h2_stream(%ld-%d): submit %d trailers", + session->id, (int)stream_id,(int) nh->nvlen); rv = nghttp2_submit_trailer(ng2s, stream->id, nh->nv, nh->nvlen); if (rv < 0) { nread = rv; } + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; } *data_flags |= NGHTTP2_DATA_FLAG_EOF; @@ -1116,10 +1163,11 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) if (stream->submitted) { rv = NGHTTP2_PROTOCOL_ERROR; } - else if (stream->response && stream->response->header) { + else if (stream->response && stream->response->headers) { nghttp2_data_provider provider; h2_response *response = stream->response; h2_ngheader *ngh; + h2_priority *prio; memset(&provider, 0, sizeof(provider)); provider.source.fd = stream->id; @@ -1129,8 +1177,14 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) "h2_stream(%ld-%d): submit response %d", session->id, stream->id, response->http_status); + prio = h2_stream_get_priority(stream); + if (prio) { + h2_session_set_prio(session, stream, prio); + /* no showstopper if that fails for some reason */ + } + ngh = h2_util_ngheader_make_res(stream->pool, response->http_status, - response->header); + response->headers); rv = nghttp2_submit_response(session->ngh2, response->stream_id, ngh->nv, ngh->nvlen, &provider); @@ -1208,6 +1262,7 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, stream = h2_session_open_stream(session, nid); if (stream) { h2_stream_set_h2_request(stream, is->id, push->req); + h2_stream_set_priority(stream, &push->prio); status = stream_schedule(session, stream, 1); if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, @@ -1232,6 +1287,109 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, return stream; } +static int valid_weight(float f) +{ + int w = floor(f); + return (w < NGHTTP2_MIN_WEIGHT? NGHTTP2_MIN_WEIGHT : + (w > NGHTTP2_MAX_WEIGHT)? NGHTTP2_MAX_WEIGHT : w); +} + +apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream, + h2_priority *prio) +{ + apr_status_t status = APR_SUCCESS; +#ifdef H2_NG2_CHANGE_PRIO + nghttp2_stream *s_grandpa, *s_parent, *s; + + s = nghttp2_session_find_stream(session->ngh2, stream->id); + if (!s) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + "h2_stream(%ld-%d): lookup of nghttp2_stream failed", + session->id, stream->id); + return APR_EINVAL; + } + + s_parent = nghttp2_stream_get_parent(s); + if (s_parent) { + nghttp2_priority_spec ps; + int id_parent, id_grandpa, w_parent, w, rv = 0; + char *ptype = "AFTER"; + h2_dependency dep = prio->dependency; + + id_parent = nghttp2_stream_get_stream_id(s_parent); + s_grandpa = nghttp2_stream_get_parent(s_parent); + if (s_grandpa) { + id_grandpa = nghttp2_stream_get_stream_id(s_grandpa); + } + else { + /* parent of parent does not exist, + * only possible if parent == root */ + dep = H2_DEPENDANT_AFTER; + } + + switch (dep) { + case H2_DEPENDANT_INTERLEAVED: + /* PUSHed stream is to be interleaved with initiating stream. + * It is made a sibling of the initiating stream and gets a + * proportional weight [1, MAX_WEIGHT] of the initiaing + * stream weight. + */ + ptype = "INTERLEAVED"; + w_parent = nghttp2_stream_get_weight(s_parent); + w = valid_weight(w_parent * ((float)NGHTTP2_MAX_WEIGHT / prio->weight)); + nghttp2_priority_spec_init(&ps, id_grandpa, w, 0); + break; + + case H2_DEPENDANT_BEFORE: + /* PUSHed stream os to be sent BEFORE the initiating stream. + * It gets the same weight as the initiating stream, replaces + * that stream in the dependency tree and has the initiating + * stream as child with MAX_WEIGHT. + */ + ptype = "BEFORE"; + nghttp2_priority_spec_init(&ps, stream->id, NGHTTP2_MAX_WEIGHT, 0); + rv = nghttp2_session_change_stream_priority(session->ngh2, id_parent, &ps); + if (rv < 0) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + "h2_stream(%ld-%d): PUSH BEFORE2, weight=%d, " + "depends=%d, returned=%d", + session->id, id_parent, ps.weight, ps.stream_id, rv); + return APR_EGENERAL; + } + id_grandpa = nghttp2_stream_get_stream_id(s_grandpa); + w_parent = nghttp2_stream_get_weight(s_parent); + nghttp2_priority_spec_init(&ps, id_grandpa, valid_weight(w_parent), 0); + break; + + case H2_DEPENDANT_AFTER: + /* The PUSHed stream is to be sent after the initiating stream. + * Give if the specified weight and let it depend on the intiating + * stream. + */ + /* fall through, it's the default */ + default: + nghttp2_priority_spec_init(&ps, id_parent, valid_weight(prio->weight), 0); + break; + } + + + rv = nghttp2_session_change_stream_priority(session->ngh2, stream->id, &ps); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + "h2_stream(%ld-%d): PUSH %s, weight=%d, " + "depends=%d, returned=%d", + session->id, stream->id, ptype, + ps.weight, ps.stream_id, rv); + status = (rv < 0)? APR_EGENERAL : APR_SUCCESS; + } +#else + (void)session; + (void)stream; + (void)prio; + (void)valid_weight; +#endif + return status; +} + apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream) { apr_pool_t *pool = h2_stream_detach_pool(stream); diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h index 90052fc9e7f..16767fb7859 100644 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@ -41,6 +41,7 @@ struct apr_thread_mutext_t; struct apr_thread_cond_t; struct h2_config; struct h2_mplx; +struct h2_priority; struct h2_push; struct h2_response; struct h2_session; @@ -147,12 +148,6 @@ apr_status_t h2_session_start(h2_session *session, int *rv); */ apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv); -/** - * Pass any buffered output data through the connection filters. - * @param session the session to flush - */ -apr_status_t h2_session_flush(h2_session *session); - /** * Called before a session gets destroyed, might flush output etc. */ @@ -201,4 +196,9 @@ apr_status_t h2_session_stream_destroy(h2_session *session, struct h2_stream *h2_session_push(h2_session *session, struct h2_stream *is, struct h2_push *push); +apr_status_t h2_session_set_prio(h2_session *session, + struct h2_stream *stream, + struct h2_priority *prio); + + #endif /* defined(__mod_h2__h2_session__) */ diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c index ad7f5df102d..c9f88a27b6a 100644 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@ -223,7 +223,7 @@ apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response, * as we want, since the lifetimes are the same and we are not freeing * the ones in h2_mplx->io before this stream is done. */ H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream set_response_pre"); - status = h2_util_move(stream->bbout, bb, -1, &move_all, + status = h2_util_move(stream->bbout, bb, 16 * 1024, &move_all, "h2_stream_set_response"); H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream set_response_post"); } @@ -476,6 +476,7 @@ apr_status_t h2_stream_prep_read(h2_stream *stream, { apr_status_t status = APR_SUCCESS; const char *src; + apr_table_t *trailers = NULL; int test_read = (*plen == 0); if (stream->rst_error) { @@ -490,19 +491,26 @@ apr_status_t h2_stream_prep_read(h2_stream *stream, apr_brigade_cleanup(stream->bbout); return h2_stream_prep_read(stream, plen, peos); } + trailers = stream->response? stream->response->trailers : NULL; } else { src = "mplx"; status = h2_mplx_out_readx(stream->session->mplx, stream->id, - NULL, NULL, plen, peos); + NULL, NULL, plen, peos, &trailers); + if (trailers && stream->response) { + h2_response_set_trailers(stream->response, trailers); + } } + if (!test_read && status == APR_SUCCESS && !*peos && !*plen) { status = APR_EAGAIN; } + H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream prep_read_post"); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c, - "h2_stream(%ld-%d): prep_read %s, len=%ld eos=%d", - stream->session->id, stream->id, src, (long)*plen, *peos); + "h2_stream(%ld-%d): prep_read %s, len=%ld eos=%d, trailers=%s", + stream->session->id, stream->id, src, (long)*plen, *peos, + trailers? "yes" : "no"); return status; } @@ -511,6 +519,7 @@ apr_status_t h2_stream_readx(h2_stream *stream, apr_off_t *plen, int *peos) { apr_status_t status = APR_SUCCESS; + apr_table_t *trailers = NULL; const char *src; H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream readx_pre"); @@ -532,14 +541,21 @@ apr_status_t h2_stream_readx(h2_stream *stream, else { src = "mplx"; status = h2_mplx_out_readx(stream->session->mplx, stream->id, - cb, ctx, plen, peos); + cb, ctx, plen, peos, &trailers); + } + + if (trailers && stream->response) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c, + "h2_stream(%ld-%d): readx, saving trailers", + stream->session->id, stream->id); + h2_response_set_trailers(stream->response, trailers); } if (status == APR_SUCCESS && !*peos && !*plen) { status = APR_EAGAIN; } - H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream prep_readx_post"); + H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream readx_post"); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c, "h2_stream(%ld-%d): readx %s, len=%ld eos=%d", stream->session->id, stream->id, src, (long)*plen, *peos); @@ -552,6 +568,7 @@ apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, apr_off_t *plen, int *peos) { apr_status_t status = APR_SUCCESS; + apr_table_t *trailers = NULL; H2_STREAM_OUT(APLOG_TRACE2, stream, "h2_stream read_to_pre"); if (stream->rst_error) { @@ -562,7 +579,7 @@ apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, apr_off_t tlen = *plen; int eos; status = h2_mplx_out_read_to(stream->session->mplx, stream->id, - stream->bbout, &tlen, &eos); + stream->bbout, &tlen, &eos, &trailers); } if (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(stream->bbout)) { @@ -574,6 +591,13 @@ apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, *peos = 0; } + if (trailers && stream->response) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c, + "h2_stream(%ld-%d): read_to, saving trailers", + stream->session->id, stream->id); + h2_response_set_trailers(stream->response, trailers); + } + if (status == APR_SUCCESS && !*peos && !*plen) { status = APR_EAGAIN; } @@ -642,6 +666,17 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream) apr_table_t *h2_stream_get_trailers(h2_stream *stream) { - /* TODO */ - return NULL; + return stream->response? stream->response->trailers : NULL; +} + +void h2_stream_set_priority(h2_stream *stream, h2_priority *prio) +{ + stream->prio = apr_pcalloc(stream->pool, sizeof(*prio)); + memcpy(stream->prio, prio, sizeof(*prio)); } + +h2_priority *h2_stream_get_priority(h2_stream *stream) +{ + return stream->prio; +} + diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h index 79801722f4a..8de3ecbacfc 100644 --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@ -41,6 +41,7 @@ typedef enum { } h2_stream_state_t; struct h2_mplx; +struct h2_priority; struct h2_request; struct h2_response; struct h2_session; @@ -69,6 +70,8 @@ struct h2_stream { apr_bucket_brigade *bbout; /* output DATA */ apr_off_t data_frames_sent; /* # of DATA frames sent out for this stream */ + + struct h2_priority *prio; /* priority information to set before submit */ }; @@ -300,4 +303,16 @@ apr_status_t h2_stream_submit_pushes(h2_stream *stream); */ apr_table_t *h2_stream_get_trailers(h2_stream *stream); +/** + * Get priority information set for this stream. + */ +struct h2_priority *h2_stream_get_priority(h2_stream *stream); + +/** + * Set the priority information to use on the submit of the stream. + * @param stream the stream to set priority on + * @param prio the priority information + */ +void h2_stream_set_priority(h2_stream *stream, struct h2_priority *prio); + #endif /* defined(__mod_h2__h2_stream__) */ diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c index b7d48a1bf6f..fee406e1bbe 100644 --- a/modules/http2/h2_task.c +++ b/modules/http2/h2_task.c @@ -82,8 +82,6 @@ static apr_status_t h2_filter_read_response(ap_filter_t* f, return h2_from_h1_read_response(task->output->from_h1, f, bb); } -static apr_status_t h2_task_process_request(h2_task *task); - /******************************************************************************* * Register various hooks */ @@ -113,6 +111,8 @@ void h2_task_register_hooks(void) NULL, AP_FTYPE_NETWORK); ap_register_output_filter("H1_TO_H2_RESP", h2_filter_read_response, NULL, AP_FTYPE_PROTOCOL); + ap_register_output_filter("H2_TRAILERS", h2_response_trailers_filter, + NULL, AP_FTYPE_PROTOCOL); } static int h2_task_pre_conn(conn_rec* c, void *arg) @@ -135,24 +135,6 @@ static int h2_task_pre_conn(conn_rec* c, void *arg) return OK; } -static int h2_task_process_conn(conn_rec* c) -{ - h2_ctx *ctx = h2_ctx_get(c); - - if (h2_ctx_is_task(ctx)) { - if (!ctx->task->serialize_headers) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, - "h2_h2, processing request directly"); - h2_task_process_request(ctx->task); - return DONE; - } - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, - "h2_task(%s), serialized handling", ctx->task->id); - } - return DECLINED; -} - - h2_task *h2_task_create(long session_id, const h2_request *req, apr_pool_t *pool, h2_mplx *mplx, int eos) { @@ -161,7 +143,7 @@ h2_task *h2_task_create(long session_id, const h2_request *req, ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool, APLOGNO(02941) "h2_task(%ld-%d): create stream task", session_id, req->id); - h2_mplx_out_close(mplx, req->id); + h2_mplx_out_close(mplx, req->id, NULL); return NULL; } @@ -228,134 +210,18 @@ apr_status_t h2_task_do(h2_task *task, h2_worker *worker) apr_thread_cond_signal(task->io); } - h2_mplx_task_done(task->mplx, task->stream_id); h2_worker_release_task(worker, task); + h2_mplx_task_done(task->mplx, task->stream_id); return status; } -static request_rec *h2_task_create_request(h2_task *task) -{ - conn_rec *conn = task->c; - request_rec *r; - apr_pool_t *p; - int access_status = HTTP_OK; - - apr_pool_create(&p, conn->pool); - apr_pool_tag(p, "request"); - r = apr_pcalloc(p, sizeof(request_rec)); - AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn); - r->pool = p; - r->connection = conn; - r->server = conn->base_server; - - r->user = NULL; - r->ap_auth_type = NULL; - - r->allowed_methods = ap_make_method_list(p, 2); - - r->headers_in = apr_table_copy(r->pool, task->request->headers); - r->trailers_in = apr_table_make(r->pool, 5); - r->subprocess_env = apr_table_make(r->pool, 25); - r->headers_out = apr_table_make(r->pool, 12); - r->err_headers_out = apr_table_make(r->pool, 5); - r->trailers_out = apr_table_make(r->pool, 5); - r->notes = apr_table_make(r->pool, 5); - - r->request_config = ap_create_request_config(r->pool); - /* Must be set before we run create request hook */ - - r->proto_output_filters = conn->output_filters; - r->output_filters = r->proto_output_filters; - r->proto_input_filters = conn->input_filters; - r->input_filters = r->proto_input_filters; - ap_run_create_request(r); - r->per_dir_config = r->server->lookup_defaults; - - r->sent_bodyct = 0; /* bytect isn't for body */ - - r->read_length = 0; - r->read_body = REQUEST_NO_BODY; - - r->status = HTTP_OK; /* Until further notice */ - r->header_only = 0; - r->the_request = NULL; - - /* Begin by presuming any module can make its own path_info assumptions, - * until some module interjects and changes the value. - */ - r->used_path_info = AP_REQ_DEFAULT_PATH_INFO; - - r->useragent_addr = conn->client_addr; - r->useragent_ip = conn->client_ip; - - ap_run_pre_read_request(r, conn); - - /* Time to populate r with the data we have. */ - r->request_time = apr_time_now(); - r->method = task->request->method; - /* Provide quick information about the request method as soon as known */ - r->method_number = ap_method_number_of(r->method); - if (r->method_number == M_GET && r->method[0] == 'H') { - r->header_only = 1; - } - - ap_parse_uri(r, task->request->path); - r->protocol = (char*)"HTTP/2"; - r->proto_num = HTTP_VERSION(2, 0); - - r->the_request = apr_psprintf(r->pool, "%s %s %s", - r->method, task->request->path, r->protocol); - - /* update what we think the virtual host is based on the headers we've - * now read. may update status. - * Leave r->hostname empty, vhost will parse if form our Host: header, - * otherwise we get complains about port numbers. - */ - r->hostname = NULL; - ap_update_vhost_from_headers(r); - - /* we may have switched to another server */ - r->per_dir_config = r->server->lookup_defaults; - - /* - * Add the HTTP_IN filter here to ensure that ap_discard_request_body - * called by ap_die and by ap_send_error_response works correctly on - * status codes that do not cause the connection to be dropped and - * in situations where the connection should be kept alive. - */ - ap_add_input_filter_handle(ap_http_input_filter_handle, - NULL, r, r->connection); - - if (access_status != HTTP_OK - || (access_status = ap_run_post_read_request(r))) { - /* Request check post hooks failed. An example of this would be a - * request for a vhost where h2 is disabled --> 421. - */ - ap_die(access_status, r); - ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); - ap_run_log_transaction(r); - r = NULL; - goto traceout; - } - - AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, - (char *)r->uri, (char *)r->server->defn_name, - r->status); - return r; -traceout: - AP_READ_REQUEST_FAILURE((uintptr_t)r); - return r; -} - - -static apr_status_t h2_task_process_request(h2_task *task) +static apr_status_t h2_task_process_request(const h2_request *req, conn_rec *c) { - conn_rec *c = task->c; request_rec *r; conn_state_t *cs = c->cs; - r = h2_task_create_request(task); + r = h2_request_create_rec(req, c); if (r && (r->status == HTTP_OK)) { ap_update_child_status(c->sbh, SERVER_BUSY_READ, r); @@ -379,6 +245,24 @@ static apr_status_t h2_task_process_request(h2_task *task) return APR_SUCCESS; } +static int h2_task_process_conn(conn_rec* c) +{ + h2_ctx *ctx = h2_ctx_get(c); + + if (h2_ctx_is_task(ctx)) { + if (!ctx->task->serialize_headers) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "h2_h2, processing request directly"); + h2_task_process_request(ctx->task->request, c); + return DONE; + } + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "h2_task(%s), serialized handling", ctx->task->id); + } + return DECLINED; +} + + diff --git a/modules/http2/h2_task_output.c b/modules/http2/h2_task_output.c index 06a5d7aafbb..1d097ab3590 100644 --- a/modules/http2/h2_task_output.c +++ b/modules/http2/h2_task_output.c @@ -83,17 +83,31 @@ static apr_status_t open_if_needed(h2_task_output *output, ap_filter_t *f, return APR_ECONNABORTED; } + output->trailers_passed = !!response->trailers; return h2_mplx_out_open(output->task->mplx, output->task->stream_id, response, f, bb, output->task->io); } return APR_EOF; } +static apr_table_t *get_trailers(h2_task_output *output) +{ + if (!output->trailers_passed) { + h2_response *response = h2_from_h1_get_response(output->from_h1); + if (response && response->trailers) { + output->trailers_passed = 1; + return response->trailers; + } + } + return NULL; +} + void h2_task_output_close(h2_task_output *output) { open_if_needed(output, NULL, NULL); if (output->state != H2_TASK_OUT_DONE) { - h2_mplx_out_close(output->task->mplx, output->task->stream_id); + h2_mplx_out_close(output->task->mplx, output->task->stream_id, + get_trailers(output)); output->state = H2_TASK_OUT_DONE; } } @@ -111,6 +125,7 @@ apr_status_t h2_task_output_write(h2_task_output *output, ap_filter_t* f, apr_bucket_brigade* bb) { apr_status_t status; + if (APR_BRIGADE_EMPTY(bb)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_task_output(%s): empty write", output->task->id); @@ -124,9 +139,10 @@ apr_status_t h2_task_output_write(h2_task_output *output, output->task->id); return status; } + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "h2_task_output(%s): write brigade", output->task->id); return h2_mplx_out_write(output->task->mplx, output->task->stream_id, - f, bb, output->task->io); + f, bb, get_trailers(output), output->task->io); } diff --git a/modules/http2/h2_task_output.h b/modules/http2/h2_task_output.h index a326c49096b..de03890ed69 100644 --- a/modules/http2/h2_task_output.h +++ b/modules/http2/h2_task_output.h @@ -38,6 +38,7 @@ struct h2_task_output { struct h2_task *task; h2_task_output_state_t state; struct h2_from_h1 *from_h1; + int trailers_passed; }; h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool); diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h index 98a431b3bf1..950e43ff4a5 100644 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@ -20,7 +20,7 @@ * @macro * Version number of the h2 module as c string */ -#define MOD_HTTP2_VERSION "1.0.5-DEV" +#define MOD_HTTP2_VERSION "1.0.7" /** * @macro @@ -28,7 +28,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 0x010005 +#define MOD_HTTP2_VERSION_NUM 0x010007 #endif /* mod_h2_h2_version_h */ diff --git a/modules/http2/h2_worker.c b/modules/http2/h2_worker.c index b11e8549fff..3119cb081ee 100644 --- a/modules/http2/h2_worker.c +++ b/modules/http2/h2_worker.c @@ -96,8 +96,9 @@ h2_worker *h2_worker_create(int id, apr_allocator_t *allocator = NULL; apr_pool_t *pool = NULL; h2_worker *w; + apr_status_t status; - apr_status_t status = apr_allocator_create(&allocator); + status = apr_allocator_create(&allocator); if (status != APR_SUCCESS) { return NULL; } @@ -126,7 +127,6 @@ h2_worker *h2_worker_create(int id, apr_pool_pre_cleanup_register(w->pool, w, cleanup_join_thread); apr_thread_create(&w->thread, attr, execute, w, w->pool); - apr_pool_create(&w->task_pool, w->pool); } return w; } @@ -167,7 +167,11 @@ h2_task *h2_worker_create_task(h2_worker *worker, h2_mplx *m, /* Create a subpool from the worker one to be used for all things * with life-time of this task execution. */ + if (!worker->task_pool) { + apr_pool_create(&worker->task_pool, worker->pool); + } task = h2_task_create(m->id, req, worker->task_pool, m, eos); + /* Link the task to the worker which provides useful things such * as mutex, a socket etc. */ task->io = worker->io;