From: Stefan Eissing Date: Thu, 31 Mar 2016 16:29:43 +0000 (+0000) Subject: Merge r1736463,r1737006,r1737021,r1737102,r1737125,r1737254 from trunk: X-Git-Tag: 2.4.20~16 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=22ac0f3d9935207b041614fa5afdf547ff1608cd;p=thirdparty%2Fapache%2Fhttpd.git Merge r1736463,r1737006,r1737021,r1737102,r1737125,r1737254 from trunk: mod_http2: backport of version 1.4.6 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1737255 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 58033bfb7a0..5a71b72e88f 100644 --- a/CHANGES +++ b/CHANGES @@ -2,8 +2,17 @@ Changes with Apache 2.4.20 + *) mod_http2: incrementing keepalives on each request started so that logging + %k gives increasing numbers per master http2 connection. + New documented variables in env, usable in custom log formats: H2_PUSH, + H2_PUSHED, H2_PUSHED_ON, H2_STREAM_ID and H2_STREAM_TAG. + [Stefan Eissing] + + *) mod_http2: more efficient passing of response bodies with less contention + and file bucket forwarding. [Stefan Eissing] + *) mod_http2: fix for missing score board updates on request count, fix for - memory leak on slave connection reuse. + memory leak on slave connection reuse. [Stefan Eissing] *) mod_http2: Fix build on Windows from dsp files. [Stefan Eissing] diff --git a/docs/manual/mod/mod_http2.xml b/docs/manual/mod/mod_http2.xml index e16f158e125..737a88b0dc7 100644 --- a/docs/manual/mod/mod_http2.xml +++ b/docs/manual/mod/mod_http2.xml @@ -56,7 +56,8 @@
Environment Variables

This module can be configured to provide HTTP/2 related information - as additional environment variables to the SSI and CGI namespace. + as additional environment variables to the SSI and CGI namespace, as well + as in custom log configurations (see %{VAR_NAME}e).

@@ -67,8 +68,13 @@ - - + + + + + + +
Value Type: Description:
HTTPe flag HTTP/2 is being used.
H2PUSH flag HTTP/2 Server Push is enabled for this request and also supported by the client.
HTTP2flagHTTP/2 is being used.
H2PUSHflagHTTP/2 Server Push is enabled for this connection and also supported by the client.
H2_PUSHflagalternate name for H2PUSH
H2_PUSHEDstringempty or PUSHED for a request being pushed by the server.
H2_PUSHED_ONnumberHTTP/2 stream number that triggered the push of this request.
H2_STREAM_IDnumberHTTP/2 stream number of this request.
H2_STREAM_TAGstringHTTP/2 process unique stream identifier, consisting of connection id and stream id separated by -.
diff --git a/modules/http2/h2.h b/modules/http2/h2.h index 335d368899e..acb79cd2e23 100644 --- a/modules/http2/h2.h +++ b/modules/http2/h2.h @@ -117,7 +117,8 @@ typedef struct h2_request h2_request; struct h2_request { int id; /* stream id */ - + int initiated_on; /* initiating stream id (PUSH) or 0 */ + const char *method; /* pseudo header values, see ch. 8.1.2.3 */ const char *scheme; const char *authority; diff --git a/modules/http2/h2_ctx.c b/modules/http2/h2_ctx.c index e8294fcb2b3..8b786b94d94 100644 --- a/modules/http2/h2_ctx.c +++ b/modules/http2/h2_ctx.c @@ -65,7 +65,11 @@ h2_ctx *h2_ctx_rget(const request_rec *r) const char *h2_ctx_protocol_get(const conn_rec *c) { - h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &http2_module); + h2_ctx *ctx; + if (c->master) { + c = c->master; + } + ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &http2_module); return ctx? ctx->protocol : NULL; } diff --git a/modules/http2/h2_filter.c b/modules/http2/h2_filter.c index c036b02fd46..8bf7fbcb40c 100644 --- a/modules/http2/h2_filter.c +++ b/modules/http2/h2_filter.c @@ -275,9 +275,9 @@ static apr_status_t h2_sos_h2_status_read_to(h2_sos *sos, apr_bucket_brigade *bb return sos->prev->read_to(sos->prev, bb, plen, peos); } -static apr_status_t h2_sos_h2_status_prep_read(h2_sos *sos, apr_off_t *plen, int *peos) +static apr_status_t h2_sos_h2_status_prepare(h2_sos *sos, apr_off_t *plen, int *peos) { - return sos->prev->prep_read(sos->prev, plen, peos); + return sos->prev->prepare(sos->prev, plen, peos); } static apr_status_t h2_sos_h2_status_readx(h2_sos *sos, h2_io_data_cb *cb, void *ctx, @@ -304,7 +304,7 @@ static h2_sos *h2_sos_h2_status_create(h2_sos *prev) sos->response = response; sos->stream = prev->stream; sos->buffer = h2_sos_h2_status_buffer; - sos->prep_read = h2_sos_h2_status_prep_read; + sos->prepare = h2_sos_h2_status_prepare; sos->readx = h2_sos_h2_status_readx; sos->read_to = h2_sos_h2_status_read_to; sos->get_trailers = h2_sos_h2_status_get_trailers; diff --git a/modules/http2/h2_filter.h b/modules/http2/h2_filter.h index 9a38a9b9cbc..2f281f8be1b 100644 --- a/modules/http2/h2_filter.h +++ b/modules/http2/h2_filter.h @@ -47,7 +47,7 @@ typedef struct h2_sos h2_sos; typedef apr_status_t h2_sos_data_cb(void *ctx, const char *data, apr_off_t len); typedef apr_status_t h2_sos_buffer(h2_sos *sos, apr_bucket_brigade *bb); -typedef apr_status_t h2_sos_prep_read(h2_sos *sos, apr_off_t *plen, int *peos); +typedef apr_status_t h2_sos_prepare(h2_sos *sos, apr_off_t *plen, int *peos); typedef apr_status_t h2_sos_readx(h2_sos *sos, h2_sos_data_cb *cb, void *ctx, apr_off_t *plen, int *peos); typedef apr_status_t h2_sos_read_to(h2_sos *sos, apr_bucket_brigade *bb, @@ -63,7 +63,7 @@ struct h2_sos { struct h2_response *response; void *ctx; h2_sos_buffer *buffer; - h2_sos_prep_read *prep_read; + h2_sos_prepare *prepare; h2_sos_readx *readx; h2_sos_read_to *read_to; h2_sos_get_trailers *get_trailers; diff --git a/modules/http2/h2_io.c b/modules/http2/h2_io.c index 3b36ca181ea..5bbf09e99b7 100644 --- a/modules/http2/h2_io.c +++ b/modules/http2/h2_io.c @@ -108,10 +108,16 @@ 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_clone(io->pool, response); + /* we used to clone the response into the io->pool. But + * we have much tighter control over the EOR bucket nowadays, + * so just use the instance given */ + io->response = response; if (response->rst_error) { h2_io_rst(io, response->rst_error); } + else if (response->content_length == 0) { + io->eos_out = 1; + } } void h2_io_rst(h2_io *io, int error) @@ -346,75 +352,40 @@ apr_status_t h2_io_in_close(h2_io *io) return APR_SUCCESS; } -static int is_out_readable(h2_io *io, apr_off_t *plen, int *peos, - apr_status_t *ps) +apr_status_t h2_io_out_get_brigade(h2_io *io, apr_bucket_brigade *bb, + apr_off_t len) { if (io->rst_error) { - *ps = APR_ECONNABORTED; - return 0; + return APR_ECONNABORTED; } if (io->eos_out_read) { - *plen = 0; - *peos = 1; - *ps = APR_SUCCESS; - return 0; - } - else if (!io->bbout) { - *plen = 0; - *peos = 0; - *ps = APR_EAGAIN; - return 0; - } - return 1; -} - -apr_status_t h2_io_out_readx(h2_io *io, - h2_io_data_cb *cb, void *ctx, - apr_off_t *plen, int *peos) -{ - apr_status_t status; - if (!is_out_readable(io, plen, peos, &status)) { - return status; + return APR_EOF; } - if (cb == NULL) { - /* just checking length available */ - status = h2_util_bb_avail(io->bbout, plen, peos); + else if (!io->bbout || APR_BRIGADE_EMPTY(io->bbout)) { + return APR_EAGAIN; } else { - status = h2_util_bb_readx(io->bbout, cb, ctx, plen, peos); - if (status == APR_SUCCESS) { - io->eos_out_read = *peos; - io->output_consumed += *plen; + apr_status_t status; + apr_off_t pre_len, post_len; + /* Allow file handles pass through without limits. If they + * already have the lifetime of this stream, we might as well + * pass them on to the master connection */ + apr_size_t files = INT_MAX; + + apr_brigade_length(bb, 0, &pre_len); + status = h2_util_move(bb, io->bbout, len, &files, "h2_io_read_to"); + if (status == APR_SUCCESS && io->eos_out + && APR_BRIGADE_EMPTY(io->bbout)) { + io->eos_out_read = 1; } - } - return status; -} - -apr_status_t h2_io_out_read_to(h2_io *io, apr_bucket_brigade *bb, - apr_off_t *plen, int *peos) -{ - apr_status_t status; - if (!is_out_readable(io, plen, peos, &status)) { + apr_brigade_length(bb, 0, &post_len); + io->output_consumed += (post_len - pre_len); return status; } - status = h2_util_move(bb, io->bbout, *plen, NULL, "h2_io_read_to"); - if (status == APR_SUCCESS && io->eos_out && APR_BRIGADE_EMPTY(io->bbout)) { - io->eos_out_read = *peos = 1; - } - io->output_consumed += *plen; - return status; -} - -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, apr_table_t *trailers, + apr_size_t maxlen, apr_size_t *pfile_buckets_allowed) { apr_status_t status; @@ -442,8 +413,6 @@ apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, } } - process_trailers(io, trailers); - /* Let's move the buckets from the request processing in here, so * that the main thread can read them when it has time/capacity. * @@ -466,13 +435,12 @@ 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_table_t *trailers) +apr_status_t h2_io_out_close(h2_io *io) { if (io->rst_error) { return APR_ECONNABORTED; } if (!io->eos_out_read) { /* EOS has not been read yet */ - process_trailers(io, trailers); if (!io->eos_out) { check_bbout(io); io->eos_out = 1; diff --git a/modules/http2/h2_io.h b/modules/http2/h2_io.h index 90d0cde8f2e..d700f6f3220 100644 --- a/modules/http2/h2_io.h +++ b/modules/http2/h2_io.h @@ -37,7 +37,7 @@ typedef struct h2_io h2_io; struct h2_io { int id; /* stream identifier */ - apr_pool_t *pool; /* stream pool */ + apr_pool_t *pool; /* stream pool */ apr_bucket_alloc_t *bucket_alloc; const struct h2_request *request;/* request on this io */ @@ -151,23 +151,19 @@ apr_status_t h2_io_in_shutdown(h2_io *io); * @param plen the requested max len, set to amount of data on return * @param peos != 0 iff the end of stream has been reached */ -apr_status_t h2_io_out_readx(h2_io *io, - h2_io_data_cb *cb, void *ctx, - apr_off_t *plen, int *peos); - -apr_status_t h2_io_out_read_to(h2_io *io, - apr_bucket_brigade *bb, - apr_off_t *plen, int *peos); +apr_status_t h2_io_out_get_brigade(h2_io *io, + apr_bucket_brigade *bb, + apr_off_t len); apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb, - apr_size_t maxlen, apr_table_t *trailers, + apr_size_t maxlen, apr_size_t *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_table_t *trailers); +apr_status_t h2_io_out_close(h2_io *io); /** * Gives the overall length of the data that is currently queued for diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index 00a64ecf2a8..a4dbf1f40f6 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -306,12 +306,13 @@ static void io_destroy(h2_mplx *m, h2_io *io, int events) h2_task_destroy(io->task); io->task = NULL; - if (reuse_slave) { + if (reuse_slave && slave->keepalive == AP_CONN_KEEPALIVE) { apr_bucket_delete(io->eor); io->eor = NULL; APR_ARRAY_PUSH(m->spare_slaves, conn_rec*) = slave; } else { + slave->sbh = NULL; h2_slave_destroy(slave, NULL); } } @@ -613,40 +614,9 @@ apr_status_t h2_mplx_in_update_windows(h2_mplx *m) return status; } -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_table_t **ptrailers) -{ - apr_status_t status; - int acquired; - - AP_DEBUG_ASSERT(m); - if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { - h2_io *io = h2_io_set_get(m->stream_ios, stream_id); - if (io && !io->orphaned) { - H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_pre"); - - 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) { - h2_io_signal(io, H2_IO_WRITE); - } - } - else { - status = APR_ECONNABORTED; - } - - *ptrailers = (*peos && io->response)? io->response->trailers : NULL; - leave_mutex(m, acquired); - } - return status; -} - -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_table_t **ptrailers) +apr_status_t h2_mplx_out_get_brigade(h2_mplx *m, int stream_id, + apr_bucket_brigade *bb, + apr_off_t len, apr_table_t **ptrailers) { apr_status_t status; int acquired; @@ -655,11 +625,11 @@ apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id, if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { - H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_pre"); + H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_get_brigade_pre"); - status = h2_io_out_read_to(io, bb, plen, peos); + status = h2_io_out_get_brigade(io, bb, len); - H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_post"); + H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_get_brigade_post"); if (status == APR_SUCCESS) { h2_io_signal(io, H2_IO_WRITE); } @@ -667,7 +637,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; + *ptrailers = io->response? io->response->trailers : NULL; leave_mutex(m, acquired); } return status; @@ -728,7 +698,6 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_ihash_t *streams) static apr_status_t out_write(h2_mplx *m, h2_io *io, ap_filter_t* f, int blocking, apr_bucket_brigade *bb, - apr_table_t *trailers, struct apr_thread_cond_t *iowait) { apr_status_t status = APR_SUCCESS; @@ -742,7 +711,7 @@ static apr_status_t out_write(h2_mplx *m, h2_io *io, && !is_aborted(m, &status)) { status = h2_io_out_write(io, bb, blocking? m->stream_max_mem : INT_MAX, - trailers, &m->tx_handles_reserved); + &m->tx_handles_reserved); io_out_consumed_signal(m, io); /* Wait for data to drain until there is room again or @@ -759,7 +728,6 @@ static apr_status_t out_write(h2_mplx *m, h2_io *io, m->id, io->id); return APR_INCOMPLETE; } - trailers = NULL; if (f) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_mplx(%ld-%d): waiting for out drain", @@ -797,7 +765,7 @@ static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response, check_tx_reservation(m); } if (bb) { - status = out_write(m, io, f, 0, bb, response->trailers, iowait); + status = out_write(m, io, f, 0, bb, iowait); if (status == APR_INCOMPLETE) { /* write will have transferred as much data as possible. caller has to deal with non-empty brigade */ @@ -838,7 +806,6 @@ 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, int blocking, apr_bucket_brigade *bb, - apr_table_t *trailers, struct apr_thread_cond_t *iowait) { apr_status_t status; @@ -848,10 +815,9 @@ apr_status_t h2_mplx_out_write(h2_mplx *m, int stream_id, if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { - status = out_write(m, io, f, blocking, bb, trailers, iowait); + status = out_write(m, io, f, blocking, bb, iowait); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, m->c, - "h2_mplx(%ld-%d): write with trailers=%s", - m->id, io->id, trailers? "yes" : "no"); + "h2_mplx(%ld-%d): write", m->id, io->id); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_write"); have_out_data_for(m, stream_id); @@ -864,7 +830,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_table_t *trailers) +apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id) { apr_status_t status; int acquired; @@ -886,10 +852,9 @@ apr_status_t h2_mplx_out_close(h2_mplx *m, int stream_id, apr_table_t *trailers) m->id, io->id); } ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, m->c, - "h2_mplx(%ld-%d): close with eor=%s, trailers=%s", - m->id, io->id, io->eor? "yes" : "no", - trailers? "yes" : "no"); - status = h2_io_out_close(io, trailers); + "h2_mplx(%ld-%d): close with eor=%s", + m->id, io->id, io->eor? "yes" : "no"); + status = h2_io_out_close(io); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_close"); io_out_consumed_signal(m, io); @@ -1018,7 +983,8 @@ static h2_io *open_io(h2_mplx *m, int stream_id, const h2_request *request) } -apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, const h2_request *req, +apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, + const h2_request *req, h2_stream_pri_cmp *cmp, void *ctx) { apr_status_t status; @@ -1080,7 +1046,9 @@ static h2_task *pop_task(h2_mplx *m) new_conn = 1; } + slave->sbh = m->c->sbh; io->task = task = h2_task_create(m->id, io->request, slave, m); + m->c->keepalives++; apr_table_setn(slave->notes, H2_TASK_ID_NOTE, task->id); if (new_conn) { h2_slave_run_pre_connection(slave, ap_get_conn_socket(slave)); @@ -1144,7 +1112,7 @@ static void task_done(h2_mplx *m, h2_task *task, h2_req_engine *ngn) /* TODO: this will keep a worker attached to this h2_mplx as * long as it has requests to handle. Might no be fair to * other mplx's. Perhaps leave after n requests? */ - h2_mplx_out_close(m, task->stream_id, NULL); + h2_mplx_out_close(m, task->stream_id); if (ngn && io) { apr_off_t bytes = io->output_consumed + h2_io_out_length(io); diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h index dac5aac4c0a..40298476789 100644 --- a/modules/http2/h2_mplx.h +++ b/modules/http2/h2_mplx.h @@ -190,7 +190,8 @@ apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout, * @param cmp the stream priority compare function * @param ctx context data for the compare function */ -apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, const struct h2_request *r, +apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, + const struct h2_request *r, h2_stream_pri_cmp *cmp, void *ctx); /** @@ -265,23 +266,13 @@ apr_status_t h2_mplx_in_update_windows(h2_mplx *m); struct h2_stream *h2_mplx_next_submit(h2_mplx *m, struct h2_ihash_t *streams); -/** - * Reads output data from the given stream. Will never block, but - * return APR_EAGAIN until data arrives or the stream is closed. - */ -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_table_t **ptrailers); - /** * Reads output data into the given brigade. Will never block, but * return APR_EAGAIN until data arrives or the stream is closed. */ -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_table_t **ptrailers); +apr_status_t h2_mplx_out_get_brigade(h2_mplx *mplx, int stream_id, + apr_bucket_brigade *bb, + apr_off_t len, apr_table_t **ptrailers); /** * Opens the output for the given stream with the specified response. @@ -299,21 +290,18 @@ apr_status_t h2_mplx_out_open(h2_mplx *mplx, int stream_id, * @param blocking == 0 iff call should return with APR_INCOMPLETE if * the full brigade cannot be written at once * @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, int blocking, apr_bucket_brigade *bb, - apr_table_t *trailers, struct apr_thread_cond_t *iowait); /** - * Closes the output for stream stream_id. Optionally forwards trailers - * fromt the processed stream. + * Closes the output for stream 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_close(h2_mplx *m, int stream_id); apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error); diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index 251c0c01fb1..2652661e78a 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -342,6 +342,7 @@ apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool, void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src) { /* keep the dst id */ + dst->initiated_on = src->initiated_on; dst->method = OPT_COPY(p, src->method); dst->scheme = OPT_COPY(p, src->scheme); dst->authority = OPT_COPY(p, src->authority); @@ -350,9 +351,15 @@ void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src) if (src->trailers) { dst->trailers = apr_table_clone(p, src->trailers); } + else { + dst->trailers = NULL; + } dst->content_length = src->content_length; dst->chunked = src->chunked; dst->eoh = src->eoh; + dst->body = src->body; + dst->serialize = src->serialize; + dst->push_policy = src->push_policy; } h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src) diff --git a/modules/http2/h2_response.c b/modules/http2/h2_response.c index 01bb20b9909..eb9043d0dba 100644 --- a/modules/http2/h2_response.c +++ b/modules/http2/h2_response.c @@ -70,6 +70,28 @@ static const char *get_sos_filter(apr_table_t *notes) return notes? apr_table_get(notes, H2_RESP_SOS_NOTE) : NULL; } +static void check_clen(h2_response *response, request_rec *r, apr_pool_t *pool) +{ + + if (r && r->header_only) { + response->content_length = 0; + } + else if (response->headers) { + const char *s = apr_table_get(response->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; + } + } + } +} + static h2_response *h2_response_create_int(int stream_id, int rst_error, int http_status, @@ -78,7 +100,6 @@ static h2_response *h2_response_create_int(int stream_id, apr_pool_t *pool) { h2_response *response; - const char *s; if (!headers) { return NULL; @@ -96,19 +117,7 @@ static h2_response *h2_response_create_int(int stream_id, response->headers = headers; response->sos_filter = get_sos_filter(notes); - 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; - } - } + check_clen(response, NULL, pool); return response; } @@ -138,6 +147,8 @@ h2_response *h2_response_rcreate(int stream_id, request_rec *r, response->headers = header; response->sos_filter = get_sos_filter(r->notes); + check_clen(response, r, pool); + if (response->http_status == HTTP_FORBIDDEN) { const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden"); if (cause) { diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index cbdae8cbbf7..928bb4a673b 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -491,7 +491,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, session->id, (int)frame->hd.stream_id, (int)frame->rst_stream.error_code); stream = h2_session_get_stream(session, frame->hd.stream_id); - if (stream && stream->initiated_on) { + if (stream && stream->request && stream->request->initiated_on) { ++session->pushes_reset; } else { @@ -555,6 +555,7 @@ static int on_send_data_cb(nghttp2_session *ngh2, unsigned char padlen; int eos; h2_stream *stream; + apr_bucket *b; (void)ngh2; (void)source; @@ -599,16 +600,10 @@ static int on_send_data_cb(nghttp2_session *ngh2, } } else { - apr_bucket *b; - char *header = apr_pcalloc(stream->pool, 10); - memcpy(header, (const char *)framehd, 9); - if (padlen) { - header[9] = (char)padlen; + status = h2_conn_io_write(&session->io, (const char *)framehd, 9); + if (padlen && status == APR_SUCCESS) { + status = h2_conn_io_write(&session->io, (const char *)&padlen, 1); } - b = apr_bucket_pool_create(header, padlen? 10 : 9, - stream->pool, session->c->bucket_alloc); - status = h2_conn_io_writeb(&session->io, b); - if (status == APR_SUCCESS) { apr_off_t len = length; status = h2_stream_read_to(stream, session->io.output, &len, &eos); @@ -1114,7 +1109,12 @@ static int resume_on_data(void *ctx, void *val) AP_DEBUG_ASSERT(stream); if (h2_stream_is_suspended(stream)) { - if (h2_mplx_out_has_data_for(stream->session->mplx, stream->id)) { + apr_status_t status; + apr_off_t len = -1; + int eos; + + status = h2_stream_out_prepare(stream, &len, &eos); + if (status == APR_SUCCESS) { int rv; h2_stream_set_suspended(stream, 0); ++rctx->resume_count; @@ -1123,8 +1123,9 @@ static int resume_on_data(void *ctx, void *val) ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)? APLOG_ERR : APLOG_DEBUG, 0, session->c, APLOGNO(02936) - "h2_stream(%ld-%d): resuming %s", - session->id, stream->id, rv? nghttp2_strerror(rv) : ""); + "h2_stream(%ld-%d): resuming %s, len=%ld, eos=%d", + session->id, stream->id, + rv? nghttp2_strerror(rv) : "", (long)len, eos); } } return 1; @@ -1189,7 +1190,7 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s, AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream)); - status = h2_stream_prep_read(stream, &nread, &eos); + status = h2_stream_out_prepare(stream, &nread, &eos); if (nread) { *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; } @@ -1214,11 +1215,6 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s, session->id, (int)stream_id); return NGHTTP2_ERR_DEFERRED; - case APR_EOF: - nread = 0; - eos = 1; - break; - default: nread = 0; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, @@ -1274,18 +1270,21 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) rv = NGHTTP2_PROTOCOL_ERROR; } else if (response && response->headers) { - nghttp2_data_provider provider; + nghttp2_data_provider provider, *pprovider = NULL; h2_ngheader *ngh; const h2_priority *prio; - memset(&provider, 0, sizeof(provider)); - provider.source.fd = stream->id; - provider.read_callback = stream_data_cb; - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03073) "h2_stream(%ld-%d): submit response %d", session->id, stream->id, response->http_status); + if (response->content_length != 0) { + memset(&provider, 0, sizeof(provider)); + provider.source.fd = stream->id; + provider.read_callback = stream_data_cb; + pprovider = &provider; + } + /* If this stream is not a pushed one itself, * and HTTP/2 server push is enabled here, * and the response is in the range 200-299 *), @@ -1301,7 +1300,7 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) * as the client, having this resource in its cache, might * also have the pushed ones as well. */ - if (!stream->initiated_on + if (stream->request && !stream->request->initiated_on && H2_HTTP_2XX(response->http_status) && h2_session_push_enabled(session)) { @@ -1317,7 +1316,7 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) ngh = h2_util_ngheader_make_res(stream->pool, response->http_status, response->headers); rv = nghttp2_submit_response(session->ngh2, response->stream_id, - ngh->nv, ngh->nvlen, &provider); + ngh->nv, ngh->nvlen, pprovider); } else { int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR); @@ -1331,7 +1330,7 @@ static apr_status_t submit_response(h2_session *session, h2_stream *stream) } stream->submitted = 1; - if (stream->initiated_on) { + if (stream->request && stream->request->initiated_on) { ++session->pushes_submitted; } else { @@ -1876,6 +1875,9 @@ static void h2_session_ev_no_io(h2_session *session, int arg, const char *msg) * CPU cycles. Ideally, we'd like to do a blocking read, but that * is not possible if we have scheduled tasks and wait * for them to produce something. */ + if (h2_conn_io_flush(&session->io) != APR_SUCCESS) { + dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); + } if (h2_ihash_is_empty(session->streams)) { if (!is_accepting_streams(session)) { /* We are no longer accepting new streams and have @@ -2249,6 +2251,9 @@ apr_status_t h2_session_process(h2_session *session, int async) if (session->wait_us <= 0) { session->wait_us = 10; session->start_wait = apr_time_now(); + if (h2_conn_io_flush(&session->io) != APR_SUCCESS) { + dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); + } update_child_status(session, SERVER_BUSY_READ, "wait"); } else if ((apr_time_now() - session->start_wait) >= session->s->timeout) { @@ -2301,10 +2306,6 @@ apr_status_t h2_session_process(h2_session *session, int async) break; } - status = h2_conn_io_flush(&session->io); - if (status != APR_SUCCESS) { - dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL); - } if (!nghttp2_session_want_read(session->ngh2) && !nghttp2_session_want_write(session->ngh2)) { dispatch_event(session, H2_SESSION_EV_NGH2_DONE, 0, NULL); @@ -2316,8 +2317,6 @@ apr_status_t h2_session_process(h2_session *session, int async) } out: - h2_conn_io_flush(&session->io); - ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c, "h2_session(%ld): [%s] process returns", session->id, state_name(session->state)); diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c index 2b368b67cf0..0a1dadf9ddf 100644 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@ -235,7 +235,7 @@ void h2_stream_set_h2_request(h2_stream *stream, int initiated_on, const h2_request *req) { h2_request_copy(stream->pool, stream->request, req); - stream->initiated_on = initiated_on; + stream->request->initiated_on = initiated_on; stream->request->eoh = 0; } @@ -387,17 +387,17 @@ int h2_stream_is_suspended(const h2_stream *stream) return stream->suspended; } -apr_status_t h2_stream_prep_read(h2_stream *stream, - apr_off_t *plen, int *peos) +apr_status_t h2_stream_out_prepare(h2_stream *stream, + apr_off_t *plen, int *peos) { if (stream->rst_error) { + *plen = 0; + *peos = 1; return APR_ECONNRESET; } - if (!stream->sos) { - return APR_EGENERAL; - } - return stream->sos->prep_read(stream->sos, plen, peos); + AP_DEBUG_ASSERT(stream->sos); + return stream->sos->prepare(stream->sos, plen, peos); } apr_status_t h2_stream_readx(h2_stream *stream, @@ -476,7 +476,7 @@ const h2_priority *h2_stream_get_priority(h2_stream *stream) { h2_response *response = h2_stream_get_response(stream); - if (stream->initiated_on && response) { + if (response && stream->request && stream->request->initiated_on) { const char *ctype = apr_table_get(response->headers, "content-type"); if (ctype) { /* FIXME: Not good enough, config needs to come from request->server */ @@ -493,7 +493,9 @@ const h2_priority *h2_stream_get_priority(h2_stream *stream) typedef struct h2_sos_mplx { h2_mplx *m; apr_bucket_brigade *bb; + apr_bucket_brigade *tmp; apr_table_t *trailers; + apr_off_t buffer_size; } h2_sos_mplx; #define H2_SOS_MPLX_OUT(lvl,msos,msg) \ @@ -503,129 +505,82 @@ typedef struct h2_sos_mplx { } while(0) -static apr_status_t h2_sos_mplx_read_to(h2_sos *sos, apr_bucket_brigade *bb, - apr_off_t *plen, int *peos) +static apr_status_t mplx_transfer(h2_sos_mplx *msos, int stream_id, + apr_pool_t *pool) { - h2_sos_mplx *msos = sos->ctx; - apr_status_t status = APR_SUCCESS; + apr_status_t status; apr_table_t *trailers = NULL; - - H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx read_to_pre"); - if (APR_BRIGADE_EMPTY(msos->bb)) { - apr_off_t tlen = *plen; - int eos; - status = h2_mplx_out_read_to(msos->m, sos->stream->id, - msos->bb, &tlen, &eos, &trailers); + if (!msos->tmp) { + msos->tmp = apr_brigade_create(msos->bb->p, msos->bb->bucket_alloc); } - - if (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(msos->bb)) { - status = h2_transfer_brigade(bb, msos->bb, sos->stream->pool, - plen, peos); - } - else { - *plen = 0; - *peos = 0; + status = h2_mplx_out_get_brigade(msos->m, stream_id, msos->tmp, + msos->buffer_size-1, &trailers); + if (!APR_BRIGADE_EMPTY(msos->tmp)) { + h2_transfer_brigade(msos->bb, msos->tmp, pool); } - if (trailers) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c, - "h2_stream(%ld-%d): read_to, saving trailers", - msos->m->id, sos->stream->id); msos->trailers = trailers; } - + return status; +} + +static apr_status_t h2_sos_mplx_read_to(h2_sos *sos, apr_bucket_brigade *bb, + apr_off_t *plen, int *peos) +{ + h2_sos_mplx *msos = sos->ctx; + apr_status_t status; + + status = h2_append_brigade(bb, msos->bb, plen, peos); if (status == APR_SUCCESS && !*peos && !*plen) { status = APR_EAGAIN; + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, msos->m->c, + "h2_stream(%ld-%d): read_to, len=%ld eos=%d", + msos->m->id, sos->stream->id, (long)*plen, *peos); } - H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx read_to_post"); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c, "h2_stream(%ld-%d): read_to, len=%ld eos=%d", msos->m->id, sos->stream->id, (long)*plen, *peos); return status; } -static apr_status_t h2_sos_mplx_prep_read(h2_sos *sos, apr_off_t *plen, int *peos) +static apr_status_t h2_sos_mplx_readx(h2_sos *sos, h2_io_data_cb *cb, void *ctx, + apr_off_t *plen, int *peos) { h2_sos_mplx *msos = sos->ctx; apr_status_t status = APR_SUCCESS; - const char *src; - apr_table_t *trailers = NULL; - int test_read = (*plen == 0); - H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx prep_read_pre"); - if (!APR_BRIGADE_EMPTY(msos->bb)) { - src = "stream"; - status = h2_util_bb_avail(msos->bb, plen, peos); - if (!test_read && status == APR_SUCCESS && !*peos && !*plen) { - apr_brigade_cleanup(msos->bb); - return h2_sos_mplx_prep_read(sos, plen, peos); - } - } - else { - src = "mplx"; - status = h2_mplx_out_readx(msos->m, sos->stream->id, - NULL, NULL, plen, peos, &trailers); - if (trailers) { - msos->trailers = trailers; - } - } - - if (!test_read && status == APR_SUCCESS && !*peos && !*plen) { + status = h2_util_bb_readx(msos->bb, cb, ctx, plen, peos); + if (status == APR_SUCCESS && !*peos && !*plen) { status = APR_EAGAIN; } - - H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx prep_read_post"); - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c, - "h2_stream(%ld-%d): prep_read %s, len=%ld eos=%d, trailers=%s", - msos->m->id, sos->stream->id, src, (long)*plen, *peos, - msos->trailers? "yes" : "no"); + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, msos->m->c, + "h2_stream(%ld-%d): readx, len=%ld eos=%d", + msos->m->id, sos->stream->id, (long)*plen, *peos); return status; } -static apr_status_t h2_sos_mplx_readx(h2_sos *sos, h2_io_data_cb *cb, void *ctx, - apr_off_t *plen, int *peos) +static apr_status_t h2_sos_mplx_prepare(h2_sos *sos, apr_off_t *plen, int *peos) { h2_sos_mplx *msos = sos->ctx; apr_status_t status = APR_SUCCESS; - apr_table_t *trailers = NULL; - const char *src; - H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx readx_pre"); - *peos = 0; - if (!APR_BRIGADE_EMPTY(msos->bb)) { - apr_off_t origlen = *plen; - - src = "stream"; - status = h2_util_bb_readx(msos->bb, cb, ctx, plen, peos); - if (status == APR_SUCCESS && !*peos && !*plen) { - apr_brigade_cleanup(msos->bb); - *plen = origlen; - return h2_sos_mplx_readx(sos, cb, ctx, plen, peos); - } - } - else { - src = "mplx"; - status = h2_mplx_out_readx(msos->m, sos->stream->id, - cb, ctx, plen, peos, &trailers); - } + H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx prepare_pre"); - if (trailers) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c, - "h2_stream(%ld-%d): readx, saving trailers", - msos->m->id, sos->stream->id); - msos->trailers = trailers; + if (APR_BRIGADE_EMPTY(msos->bb)) { + status = mplx_transfer(msos, sos->stream->id, sos->stream->pool); } + h2_util_bb_avail(msos->bb, plen, peos); - if (status == APR_SUCCESS && !*peos && !*plen) { + H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx prepare_post"); + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c, + "h2_stream(%ld-%d): prepare, len=%ld eos=%d, trailers=%s", + msos->m->id, sos->stream->id, (long)*plen, *peos, + msos->trailers? "yes" : "no"); + if (!*peos && !*plen) { status = APR_EAGAIN; } - H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_stream readx_post"); - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c, - "h2_stream(%ld-%d): readx %s, len=%ld eos=%d", - msos->m->id, sos->stream->id, src, (long)*plen, *peos); - return status; } @@ -642,13 +597,8 @@ static apr_status_t h2_sos_mplx_buffer(h2_sos *sos, apr_bucket_brigade *bb) apr_status_t status = APR_SUCCESS; if (bb && !APR_BRIGADE_EMPTY(bb)) { - apr_size_t move_all = INT_MAX; - /* we can move file handles from h2_mplx into this h2_stream as many - * 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_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx set_response_pre"); - status = h2_util_move(msos->bb, bb, 16 * 1024, &move_all, - "h2_stream_set_response"); + status = mplx_transfer(msos, sos->stream->id, sos->stream->pool); H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx set_response_post"); } return status; @@ -662,14 +612,15 @@ static h2_sos *h2_sos_mplx_create(h2_stream *stream, h2_response *response) msos = apr_pcalloc(stream->pool, sizeof(*msos)); msos->m = stream->session->mplx; msos->bb = apr_brigade_create(stream->pool, msos->m->c->bucket_alloc); - + msos->buffer_size = 32 * 1024; + sos = apr_pcalloc(stream->pool, sizeof(*sos)); sos->stream = stream; sos->response = response; sos->ctx = msos; sos->buffer = h2_sos_mplx_buffer; - sos->prep_read = h2_sos_mplx_prep_read; + sos->prepare = h2_sos_mplx_prepare; sos->readx = h2_sos_mplx_readx; sos->read_to = h2_sos_mplx_read_to; sos->get_trailers = h2_sos_mplx_get_trailers; diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h index b7df632502c..f0cd2167a37 100644 --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@ -43,7 +43,6 @@ typedef struct h2_stream h2_stream; struct h2_stream { int id; /* http2 stream id */ - int initiated_on; /* http2 stream id this was initiated on or 0 */ h2_stream_state_t state; /* http/2 state of this stream */ struct h2_session *session; /* the session this stream belongs to */ @@ -203,8 +202,8 @@ apr_status_t h2_stream_set_response(h2_stream *stream, * APR_EAGAIN if not data is available and end of stream has not been * reached yet. */ -apr_status_t h2_stream_prep_read(h2_stream *stream, - apr_off_t *plen, int *peos); +apr_status_t h2_stream_out_prepare(h2_stream *stream, + apr_off_t *plen, int *peos); /** * Read data from the stream output. diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c index 9fa69b2365a..dff1bcdd573 100644 --- a/modules/http2/h2_task.c +++ b/modules/http2/h2_task.c @@ -165,7 +165,7 @@ h2_task *h2_task_create(long session_id, const h2_request *req, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, c, APLOGNO(02941) "h2_task(%ld-%d): create stream task", session_id, req->id); - h2_mplx_out_close(mplx, req->id, NULL); + h2_mplx_out_close(mplx, req->id); return NULL; } @@ -173,6 +173,7 @@ h2_task *h2_task_create(long session_id, const h2_request *req, task->stream_id = req->id; task->c = c; task->mplx = mplx; + task->c->keepalives = mplx->c->keepalives; task->pool = pool; task->request = req; task->input_eos = !req->body; diff --git a/modules/http2/h2_task_output.c b/modules/http2/h2_task_output.c index 959398d1d10..80938d1fcca 100644 --- a/modules/http2/h2_task_output.c +++ b/modules/http2/h2_task_output.c @@ -45,23 +45,6 @@ h2_task_output *h2_task_output_create(h2_task *task, conn_rec *c) return output; } -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; - if (h2_task_logio_add_bytes_out) { - /* counter trailers as if we'd do a HTTP/1.1 serialization */ - h2_task_logio_add_bytes_out(output->task->c, - h2_util_table_bytes(response->trailers, 3)+1); - } - return response->trailers; - } - } - return NULL; -} - static apr_status_t open_response(h2_task_output *output, ap_filter_t *f, apr_bucket_brigade *bb, const char *caller) { @@ -71,7 +54,7 @@ static apr_status_t open_response(h2_task_output *output, ap_filter_t *f, if (f) { /* This happens currently when ap_die(status, r) is invoked * by a read request filter. */ - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, output->task->c, APLOGNO(03204) + ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, output->task->c, APLOGNO(03204) "h2_task_output(%s): write without response by %s " "for %s %s %s", output->task->id, caller, @@ -91,7 +74,6 @@ static apr_status_t open_response(h2_task_output *output, ap_filter_t *f, output->written = h2_util_table_bytes(response->headers, 3)+1; h2_task_logio_add_bytes_out(output->task->c, output->written); } - get_trailers(output); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, output->task->c, APLOGNO(03348) "h2_task(%s): open response to %s %s %s", output->task->id, output->task->request->method, @@ -113,8 +95,7 @@ static apr_status_t write_brigade_raw(h2_task_output *output, output->task->id, (long)written); status = h2_mplx_out_write(output->task->mplx, output->task->stream_id, - f, output->task->blocking, bb, - get_trailers(output), output->task->io); + f, output->task->blocking, bb, output->task->io); if (status == APR_INCOMPLETE) { apr_brigade_length(bb, 0, &left); written -= left; diff --git a/modules/http2/h2_task_output.h b/modules/http2/h2_task_output.h index ba396f07ebd..3135bc459e1 100644 --- a/modules/http2/h2_task_output.h +++ b/modules/http2/h2_task_output.h @@ -33,7 +33,6 @@ struct h2_task_output { struct h2_from_h1 *from_h1; unsigned int response_open : 1; - unsigned int trailers_passed : 1; apr_off_t written; apr_bucket_brigade *bb; diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c index 94a2987ca03..06472425f22 100644 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@ -697,7 +697,7 @@ apr_status_t h2_util_bb_avail(apr_bucket_brigade *bb, return status; } else if (blen == 0) { - /* empty brigade, does it have an EOS bucket somwhere? */ + /* brigade without data, does it have an EOS bucket somwhere? */ *plen = 0; *peos = h2_util_has_eos(bb, -1); } @@ -861,7 +861,7 @@ void h2_util_bb_log(conn_rec *c, int stream_id, int level, } -apr_status_t h2_transfer_brigade(apr_bucket_brigade *to, +apr_status_t h2_ltransfer_brigade(apr_bucket_brigade *to, apr_bucket_brigade *from, apr_pool_t *p, apr_off_t *plen, @@ -932,6 +932,92 @@ apr_status_t h2_transfer_brigade(apr_bucket_brigade *to, return APR_SUCCESS; } +apr_status_t h2_transfer_brigade(apr_bucket_brigade *to, + apr_bucket_brigade *from, + apr_pool_t *p) +{ + apr_bucket *e; + apr_status_t rv; + + while (!APR_BRIGADE_EMPTY(from)) { + e = APR_BRIGADE_FIRST(from); + + rv = apr_bucket_setaside(e, p); + + /* If the bucket type does not implement setaside, then + * (hopefully) morph it into a bucket type which does, and set + * *that* aside... */ + if (rv == APR_ENOTIMPL) { + const char *s; + apr_size_t n; + + rv = apr_bucket_read(e, &s, &n, APR_BLOCK_READ); + if (rv == APR_SUCCESS) { + rv = apr_bucket_setaside(e, p); + } + } + + if (rv != APR_SUCCESS) { + /* Return an error but still save the brigade if + * ->setaside() is really not implemented. */ + if (rv != APR_ENOTIMPL) { + return rv; + } + } + + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(to, e); + } + return APR_SUCCESS; +} + +apr_status_t h2_append_brigade(apr_bucket_brigade *to, + apr_bucket_brigade *from, + apr_off_t *plen, + int *peos) +{ + apr_bucket *e; + apr_off_t len = 0, remain = *plen; + apr_status_t rv; + + *peos = 0; + + while (!APR_BRIGADE_EMPTY(from)) { + e = APR_BRIGADE_FIRST(from); + + if (APR_BUCKET_IS_METADATA(e)) { + if (APR_BUCKET_IS_EOS(e)) { + *peos = 1; + } + } + else { + if (remain > 0 && e->length == ((apr_size_t)-1)) { + const char *ign; + apr_size_t ilen; + rv = apr_bucket_read(e, &ign, &ilen, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + return rv; + } + } + + if (remain < e->length) { + if (remain <= 0) { + return APR_SUCCESS; + } + apr_bucket_split(e, remain); + } + } + + APR_BUCKET_REMOVE(e); + APR_BRIGADE_INSERT_TAIL(to, e); + len += e->length; + remain -= e->length; + } + + *plen = len; + return APR_SUCCESS; +} + apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb) { apr_bucket *b; diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h index a83f362ffcc..4ca2f9b65b6 100644 --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@ -243,18 +243,44 @@ void h2_util_bb_log(conn_rec *c, int stream_id, int level, /** * Transfer buckets from one brigade to another with a limit on the - * maximum amount of bytes transfered. + * maximum amount of bytes transfered. Sets aside the buckets to + * pool p. * @param to brigade to transfer buckets to * @param from brigades to remove buckets from * @param p pool that buckets should be setaside to * @param plen maximum bytes to transfer, actual bytes transferred * @param peos if an EOS bucket was transferred */ +apr_status_t h2_ltransfer_brigade(apr_bucket_brigade *to, + apr_bucket_brigade *from, + apr_pool_t *p, + apr_off_t *plen, + int *peos); + +/** + * Transfer all buckets from one brigade to another. Sets aside the buckets to + * pool p. + * @param to brigade to transfer buckets to + * @param from brigades to remove buckets from + * @param p pool that buckets should be setaside to + */ apr_status_t h2_transfer_brigade(apr_bucket_brigade *to, apr_bucket_brigade *from, - apr_pool_t *p, - apr_off_t *plen, - int *peos); + apr_pool_t *p); + +/** + * Transfer buckets from one brigade to another with a limit on the + * maximum amount of bytes transfered. Does no setaside magic, lifetime + * of brigades must fit. + * @param to brigade to transfer buckets to + * @param from brigades to remove buckets from + * @param plen maximum bytes to transfer, actual bytes transferred + * @param peos if an EOS bucket was transferred + */ +apr_status_t h2_append_brigade(apr_bucket_brigade *to, + apr_bucket_brigade *from, + apr_off_t *plen, + int *peos); /** * Get an approximnation of the memory footprint of the given diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h index 1b961c51e7c..d68130db71a 100644 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@ -26,7 +26,7 @@ * @macro * Version number of the http2 module as c string */ -#define MOD_HTTP2_VERSION "1.4.5" +#define MOD_HTTP2_VERSION "1.4.6" /** * @macro @@ -34,7 +34,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 0x010405 +#define MOD_HTTP2_VERSION_NUM 0x010406 #endif /* mod_h2_h2_version_h */ diff --git a/modules/http2/mod_http2.c b/modules/http2/mod_http2.c index 6450eb9ea01..0d33969161d 100644 --- a/modules/http2/mod_http2.c +++ b/modules/http2/mod_http2.c @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -198,36 +199,83 @@ static void h2_hooks(apr_pool_t *pool) ap_hook_handler(h2_filter_h2_status_handler, NULL, NULL, APR_HOOK_MIDDLE); } -static char *value_of_HTTP2(apr_pool_t *p, server_rec *s, - conn_rec *c, request_rec *r) +static const char *val_HTTP2(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, h2_ctx *ctx) { - return c && http2_is_h2(c)? "on" : "off"; + return ctx? "on" : "off"; } -static char *value_of_H2PUSH(apr_pool_t *p, server_rec *s, - conn_rec *c, request_rec *r) +static const char *val_H2_PUSH(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, h2_ctx *ctx) { - h2_ctx *ctx; - if (r) { - ctx = h2_ctx_rget(r); - if (ctx) { + if (ctx) { + if (r) { h2_task *task = h2_ctx_get_task(ctx); - return (task && task->request->push_policy != H2_PUSH_NONE)? "on" : "off"; + if (task && task->request->push_policy != H2_PUSH_NONE) { + return "on"; + } + } + else if (c && h2_session_push_enabled(ctx->session)) { + return "on"; } - } - else if (c) { - ctx = h2_ctx_get(c, 0); - return ctx && h2_session_push_enabled(ctx->session)? "on" : "off"; } else if (s) { const h2_config *cfg = h2_config_sget(s); - return cfg && h2_config_geti(cfg, H2_CONF_PUSH)? "on" : "off"; + if (cfg && h2_config_geti(cfg, H2_CONF_PUSH)) { + return "on"; + } } return "off"; } -typedef char *h2_var_lookup(apr_pool_t *p, server_rec *s, - conn_rec *c, request_rec *r); +static const char *val_H2_PUSHED(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, h2_ctx *ctx) +{ + if (ctx) { + h2_task *task = h2_ctx_get_task(ctx); + if (task && !H2_STREAM_CLIENT_INITIATED(task->stream_id)) { + return "PUSHED"; + } + } + return ""; +} + +static const char *val_H2_PUSHED_ON(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, h2_ctx *ctx) +{ + if (ctx) { + h2_task *task = h2_ctx_get_task(ctx); + if (task && !H2_STREAM_CLIENT_INITIATED(task->stream_id)) { + return apr_itoa(p, task->request->initiated_on); + } + } + return ""; +} + +static const char *val_H2_STREAM_TAG(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, h2_ctx *ctx) +{ + if (ctx) { + h2_task *task = h2_ctx_get_task(ctx); + if (task) { + return task->id; + } + } + return ""; +} + +static const char *val_H2_STREAM_ID(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, h2_ctx *ctx) +{ + const char *cp = val_H2_STREAM_TAG(p, s, c, r, ctx); + if (cp && (cp = ap_strchr_c(cp, '-'))) { + return ++cp; + } + return NULL; +} + +typedef const char *h2_var_lookup(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, h2_ctx *ctx); typedef struct h2_var_def { const char *name; h2_var_lookup *lookup; @@ -235,8 +283,13 @@ typedef struct h2_var_def { } h2_var_def; static h2_var_def H2_VARS[] = { - { "HTTP2", value_of_HTTP2, 1 }, - { "H2PUSH", value_of_H2PUSH, 1 }, + { "HTTP2", val_HTTP2, 1 }, + { "H2PUSH", val_H2_PUSH, 1 }, + { "H2_PUSH", val_H2_PUSH, 1 }, + { "H2_PUSHED", val_H2_PUSHED, 1 }, + { "H2_PUSHED_ON", val_H2_PUSHED_ON, 1 }, + { "H2_STREAM_ID", val_H2_STREAM_ID, 1 }, + { "H2_STREAM_TAG", val_H2_STREAM_TAG, 1 }, }; #ifndef H2_ALEN @@ -257,7 +310,9 @@ static char *http2_var_lookup(apr_pool_t *p, server_rec *s, for (i = 0; i < H2_ALEN(H2_VARS); ++i) { h2_var_def *vdef = &H2_VARS[i]; if (!strcmp(vdef->name, name)) { - return vdef->lookup(p, s, c, r); + h2_ctx *ctx = (r? h2_ctx_rget(r) : + h2_ctx_get(c->master? c->master : c, 0)); + return (char *)vdef->lookup(p, s, c, r, ctx); } } return ""; @@ -273,7 +328,8 @@ static int h2_h2_fixups(request_rec *r) h2_var_def *vdef = &H2_VARS[i]; if (vdef->subprocess) { apr_table_setn(r->subprocess_env, vdef->name, - vdef->lookup(r->pool, r->server, r->connection, r)); + vdef->lookup(r->pool, r->server, r->connection, + r, ctx)); } } }