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]
<section id="envvars"><title>Environment Variables</title>
<p>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 <code>%{VAR_NAME}e</code>).
</p>
<table border="1">
<th>Value Type:</th>
<th>Description:</th>
</tr>
- <tr><td><code>HTTPe</code></td> <td>flag</td> <td>HTTP/2 is being used.</td></tr>
- <tr><td><code>H2PUSH</code></td> <td>flag</td> <td>HTTP/2 Server Push is enabled for this request and also supported by the client.</td></tr>
+ <tr><td><code>HTTP2</code></td><td>flag</td><td>HTTP/2 is being used.</td></tr>
+ <tr><td><code>H2PUSH</code></td><td>flag</td><td>HTTP/2 Server Push is enabled for this connection and also supported by the client.</td></tr>
+ <tr><td><code>H2_PUSH</code></td><td>flag</td><td>alternate name for <code>H2PUSH</code></td></tr>
+ <tr><td><code>H2_PUSHED</code></td><td>string</td><td>empty or <code>PUSHED</code> for a request being pushed by the server.</td></tr>
+ <tr><td><code>H2_PUSHED_ON</code></td><td>number</td><td>HTTP/2 stream number that triggered the push of this request.</td></tr>
+ <tr><td><code>H2_STREAM_ID</code></td><td>number</td><td>HTTP/2 stream number of this request.</td></tr>
+ <tr><td><code>H2_STREAM_TAG</code></td><td>string</td><td>HTTP/2 process unique stream identifier, consisting of connection id and stream id separated by <code>-</code>.</td></tr>
</table>
</section>
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;
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;
}
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,
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;
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,
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;
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)
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;
}
}
- 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.
*
}
-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;
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 */
* @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
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);
}
}
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;
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);
}
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;
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;
&& !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
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",
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 */
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;
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);
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;
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);
}
-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;
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));
/* 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);
* @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);
/**
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.
* @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);
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);
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)
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,
apr_pool_t *pool)
{
h2_response *response;
- const char *s;
if (!headers) {
return NULL;
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;
}
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) {
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 {
unsigned char padlen;
int eos;
h2_stream *stream;
+ apr_bucket *b;
(void)ngh2;
(void)source;
}
}
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);
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;
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;
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;
}
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,
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 *),
* 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)) {
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);
}
stream->submitted = 1;
- if (stream->initiated_on) {
+ if (stream->request && stream->request->initiated_on) {
++session->pushes_submitted;
}
else {
* 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
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) {
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);
}
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));
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;
}
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,
{
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 */
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) \
} 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;
}
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;
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;
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 */
* 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.
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;
}
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;
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)
{
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,
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,
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;
struct h2_from_h1 *from_h1;
unsigned int response_open : 1;
- unsigned int trailers_passed : 1;
apr_off_t written;
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);
}
}
-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,
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;
/**
* 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
* @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
* 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 */
#include <apr_optional.h>
#include <apr_optional_hooks.h>
+#include <apr_strings.h>
#include <apr_time.h>
#include <apr_want.h>
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;
} 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
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 "";
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));
}
}
}