}
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);
+}
+
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__) */
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;
}
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;
return status;
}
+ process_trailers(io, trailers);
if (!io->bbout) {
io->bbout = apr_brigade_create(io->pool, io->bucket_alloc);
}
}
-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;
}
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
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);
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);
else {
status = APR_ECONNABORTED;
}
+
+ *ptrailers = (*peos && io->response)? io->response->trailers : NULL;
apr_thread_mutex_unlock(m->lock);
}
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_off_t *plen, int *peos,
+ apr_table_t **ptrailers)
{
apr_status_t status;
AP_DEBUG_ASSERT(m);
else {
status = APR_ECONNABORTED;
}
+ *ptrailers = (*peos && io->response)? io->response->trailers : NULL;
apr_thread_mutex_unlock(m->lock);
}
return status;
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;
&& (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)
&& 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,
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);
}
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;
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);
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);
"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);
*/
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
*/
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.
* @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);
* 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;
apr_array_header_t *hlines,
apr_pool_t *pool)
{
- apr_table_t *header;
+ apr_table_t *headers;
h2_response *response = apr_pcalloc(pool, sizeof(h2_response));
int i;
if (response == NULL) {
response->content_length = -1;
if (hlines) {
- header = apr_table_make(pool, hlines->nelts);
+ headers = apr_table_make(pool, hlines->nelts);
for (i = 0; i < hlines->nelts; ++i) {
char *hline = ((char **)hlines->elts)[i];
char *sep = ap_strchr(hline, ':');
}
if (!h2_util_ignore_header(hline)) {
- apr_table_merge(header, hline, sep);
+ apr_table_merge(headers, hline, sep);
if (*sep && H2_HD_MATCH_LIT_CS("content-length", hline)) {
char *end;
response->content_length = apr_strtoi64(sep, &end, 10);
}
}
else {
- header = apr_table_make(pool, 0);
+ headers = apr_table_make(pool, 0);
}
- response->header = header;
+ response->headers = headers;
return response;
}
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");
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;
+}
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;
h2_response *h2_response_create(int stream_id,
h2_response *h2_response_copy(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__) */
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;
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;
session->id, stream->id, response->http_status);
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);
* 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");
}
{
apr_status_t status = APR_SUCCESS;
const char *src;
+ apr_table_t *trailers = NULL;
int test_read = (*plen == 0);
if (stream->rst_error) {
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;
}
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");
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);
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) {
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)) {
*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;
}
apr_table_t *h2_stream_get_trailers(h2_stream *stream)
{
- /* TODO */
- return NULL;
+ return stream->response? stream->response->trailers : NULL;
}
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)
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;
}
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->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;
}
}
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);
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);
}
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);