Changes with Apache 2.4.18
+ *) mod_http2: fixes crash on connection abort for a busy connection.
+ fixes crash on a request that did not produce any response.
+ [Stefan Eissing]
+
+ *) mod_http2: trailers are sent after reponse body if set in request_rec
+ trailers_out before the end-of-request bucket is sent through the
+ output filters. [Stefan Eissing]
+
*) mod_http2: incoming trailers (headers after request body) are properly
forwarded to the processing engine. [Stefan Eissing]
if test "x$liberrors" != "x"; then
AC_MSG_WARN([nghttp2 library is unusable])
fi
+ AC_CHECK_FUNCS([nghttp2_session_change_stream_priority],
+ [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_CHANGE_PRIO"])], [])
else
AC_MSG_WARN([nghttp2 version is too old])
fi
if (apr_bucket_shared_destroy(h)) {
h2_session *session = h->session;
+ apr_bucket_free(h);
if (session) {
h2_session_eoc_callback(session);
+ /* all is gone now */
}
- apr_bucket_free(h);
}
}
H2_INITIAL_WINDOW_SIZE, /* window_size */
-1, /* min workers */
-1, /* max workers */
- 10 * 60, /* max workers idle secs */
+ 10, /* max workers idle secs */
64 * 1024, /* stream max mem size */
NULL, /* no alt-svcs */
-1, /* alt-svc max age */
ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
"h2_session(%ld): done", session->id);
- h2_session_close(session);
- h2_session_flush(session);
- /* hereafter session might be gone */
-
/* Make sure this connection gets closed properly. */
ap_update_child_status_from_conn(c->sbh, SERVER_CLOSING, c);
c->keepalive = AP_CONN_CLOSE;
c->cs->state = CONN_STATE_WRITE_COMPLETION;
}
+ h2_session_close(session);
+ /* hereafter session will be gone */
return status;
}
#include <http_connection.h>
#include "h2_private.h"
+#include "h2_bucket_eoc.h"
#include "h2_config.h"
#include "h2_conn_io.h"
#include "h2_h2.h"
#define WRITE_BUFFER_SIZE (8*WRITE_SIZE_MAX)
-apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c)
+apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, apr_pool_t *pool)
{
h2_config *cfg = h2_config_get(c);
io->connection = c;
- io->input = apr_brigade_create(c->pool, c->bucket_alloc);
- io->output = apr_brigade_create(c->pool, c->bucket_alloc);
+ io->input = apr_brigade_create(pool, c->bucket_alloc);
+ io->output = apr_brigade_create(pool, c->bucket_alloc);
io->buflen = 0;
io->is_tls = h2_h2_is_tls(c);
io->buffer_output = io->is_tls;
if (io->buffer_output) {
io->bufsize = WRITE_BUFFER_SIZE;
- io->buffer = apr_pcalloc(c->pool, io->bufsize);
+ io->buffer = apr_pcalloc(pool, io->bufsize);
}
else {
io->bufsize = 0;
&bucket_length, block);
if (status == APR_SUCCESS && bucket_length > 0) {
+ apr_size_t consumed = 0;
+
if (APLOGctrace2(io->connection)) {
char buffer[32];
h2_util_hex_dump(buffer, sizeof(buffer)/sizeof(buffer[0]),
io->connection->id, (int)bucket_length, buffer);
}
- if (bucket_length > 0) {
- apr_size_t consumed = 0;
- status = on_read_cb(bucket_data, bucket_length,
- &consumed, pdone, puser);
- if (status == APR_SUCCESS && bucket_length > consumed) {
- /* We have data left in the bucket. Split it. */
- status = apr_bucket_split(bucket, consumed);
- }
- readlen += consumed;
+ status = on_read_cb(bucket_data, bucket_length, &consumed,
+ pdone, puser);
+ if (status == APR_SUCCESS && bucket_length > consumed) {
+ /* We have data left in the bucket. Split it. */
+ status = apr_bucket_split(bucket, consumed);
}
+ readlen += consumed;
}
}
apr_bucket_delete(bucket);
}
+
if (readlen == 0 && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
return APR_EAGAIN;
}
/* Seems something is left from a previous read, lets
* satisfy our caller with the data we already have. */
status = h2_conn_io_bucket_read(io, block, on_read_cb, puser, &done);
+ apr_brigade_cleanup(io->input);
if (status != APR_SUCCESS || done) {
return status;
}
- apr_brigade_cleanup(io->input);
}
/* We only do a blocking read when we have no streams to process. So,
ap_update_child_status(io->connection->sbh, SERVER_BUSY_READ, NULL);
}
+ /* TODO: replace this with a connection filter itself, so that we
+ * no longer need to transfer incoming buckets to our own brigade.
+ */
status = ap_get_brigade(io->connection->input_filters,
io->input, AP_MODE_READBYTES,
block, 64 * 4096);
apr_status_t h2_conn_io_pass(h2_conn_io *io)
{
return h2_conn_io_flush_int(io, 0);
+}
+
+apr_status_t h2_conn_io_close(h2_conn_io *io, void *session)
+{
+ apr_bucket *b;
+
+ /* Send out anything in our buffers */
+ h2_conn_io_flush_int(io, 0);
+
+ b = h2_bucket_eoc_create(io->connection->bucket_alloc, session);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ b = apr_bucket_flush_create(io->connection->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(io->output, b);
+ return ap_pass_brigade(io->connection->output_filters, io->output);
+ /* and all is gone */
}
\ No newline at end of file
int unflushed;
} h2_conn_io;
-apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c);
+apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, apr_pool_t *pool);
int h2_conn_io_is_buffered(h2_conn_io *io);
apr_status_t h2_conn_io_pass(h2_conn_io *io);
apr_status_t h2_conn_io_flush(h2_conn_io *io);
+apr_status_t h2_conn_io_close(h2_conn_io *io, void *session);
#endif /* defined(__mod_h2__h2_conn_io__) */
apr_status_t h2_from_h1_destroy(h2_from_h1 *from_h1)
{
- if (from_h1->response) {
- h2_response_destroy(from_h1->response);
- from_h1->response = NULL;
- }
from_h1->bb = NULL;
return APR_SUCCESS;
}
if (eb) {
int st = eb->status;
apr_brigade_cleanup(bb);
- ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c,
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
"h2_from_h1(%d): err bucket status=%d",
from_h1->stream_id, st);
ap_die(st, r);
}
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;
}
#define H2_STREAM_CLIENT_INITIATED(id) (id&0x01)
+typedef enum {
+ H2_DEPENDANT_AFTER,
+ H2_DEPENDANT_INTERLEAVED,
+ H2_DEPENDANT_BEFORE,
+} h2_dependency;
+
+typedef struct h2_priority {
+ h2_dependency dependency;
+ int weight;
+} h2_priority;
+
/**
* Provide a user readable description of the HTTP/2 error code-
* @param h2_error http/2 error code, as in rfc 7540, ch. 7
AP_DEBUG_ASSERT(io->pool);
AP_DEBUG_ASSERT(response);
AP_DEBUG_ASSERT(!io->response);
- io->response = h2_response_copy(io->pool, response);
+ io->response = h2_response_clone(io->pool, response);
if (response->rst_error) {
h2_io_rst(io, response->rst_error);
}
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
return NULL;
}
-void h2_io_set_destroy_all(h2_io_set *sp)
-{
- int i;
- for (i = 0; i < sp->list->nelts; ++i) {
- h2_io *io = h2_io_IDX(sp->list, i);
- h2_io_destroy(io);
- }
- sp->list->nelts = 0;
-}
-
-void h2_io_set_remove_all(h2_io_set *sp)
-{
- sp->list->nelts = 0;
-}
-
int h2_io_set_is_empty(h2_io_set *sp)
{
AP_DEBUG_ASSERT(sp);
return sp->list->nelts == 0;
}
-void h2_io_set_iter(h2_io_set *sp,
+int h2_io_set_iter(h2_io_set *sp,
h2_io_set_iter_fn *iter, void *ctx)
{
int i;
for (i = 0; i < sp->list->nelts; ++i) {
h2_io *s = h2_io_IDX(sp->list, i);
if (!iter(ctx, s)) {
- break;
+ return 0;
}
}
+ return 1;
}
apr_size_t h2_io_set_size(h2_io_set *sp)
h2_io *h2_io_set_get(h2_io_set *set, int stream_id);
h2_io *h2_io_set_remove(h2_io_set *set, struct h2_io *io);
-void h2_io_set_remove_all(h2_io_set *set);
-void h2_io_set_destroy_all(h2_io_set *set);
int h2_io_set_is_empty(h2_io_set *set);
apr_size_t h2_io_set_size(h2_io_set *set);
typedef int h2_io_set_iter_fn(void *ctx, struct h2_io *io);
-void h2_io_set_iter(h2_io_set *set,
- h2_io_set_iter_fn *iter, void *ctx);
+/**
+ * Iterator over all h2_io* in the set or until a
+ * callback returns 0. It is not safe to add or remove
+ * set members during iteration.
+ *
+ * @param set the set of h2_io to iterate over
+ * @param iter the function to call for each io
+ * @param ctx user data for the callback
+ * @return 1 iff iteration completed for all members
+ */
+int h2_io_set_iter(h2_io_set *set,
+ h2_io_set_iter_fn *iter, void *ctx);
h2_io *h2_io_set_pop_highest_prio(h2_io_set *set);
static void h2_mplx_destroy(h2_mplx *m)
{
AP_DEBUG_ASSERT(m);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
+ "h2_mplx(%ld): destroy, refs=%d",
+ m->id, m->refs);
m->aborted = 1;
if (m->ready_ios) {
h2_io_set_destroy(m->ready_ios);
m->stream_ios = NULL;
}
- if (m->lock) {
- apr_thread_mutex_destroy(m->lock);
- m->lock = NULL;
- }
-
- if (m->spare_pool) {
- apr_pool_destroy(m->spare_pool);
- m->spare_pool = NULL;
- }
if (m->pool) {
apr_pool_destroy(m->pool);
}
h2_workers_unregister(m->workers, m);
}
+static void io_destroy(h2_mplx *m, h2_io *io)
+{
+ apr_pool_t *pool = io->pool;
+
+ io->pool = NULL;
+ /* The pool is cleared/destroyed which also closes all
+ * allocated file handles. Give this count back to our
+ * file handle pool. */
+ m->file_handles_allowed += io->files_handles_owned;
+ h2_io_set_remove(m->stream_ios, io);
+ h2_io_set_remove(m->ready_ios, io);
+ h2_io_destroy(io);
+
+ if (pool) {
+ apr_pool_clear(pool);
+ if (m->spare_pool) {
+ apr_pool_destroy(m->spare_pool);
+ }
+ m->spare_pool = pool;
+ }
+}
+
+static int io_stream_done(h2_mplx *m, h2_io *io, int rst_error)
+{
+ /* Remove io from ready set, we will never submit it */
+ h2_io_set_remove(m->ready_ios, io);
+ if (io->task_done || h2_tq_remove(m->q, io->id)) {
+ /* already finished or not even started yet */
+ io_destroy(m, io);
+ return 0;
+ }
+ else {
+ /* cleanup once task is done */
+ io->orphaned = 1;
+ if (rst_error) {
+ h2_io_rst(io, rst_error);
+ }
+ return 1;
+ }
+}
+
+static int stream_done_iter(void *ctx, h2_io *io) {
+ return io_stream_done((h2_mplx*)ctx, io, 0);
+}
+
apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
{
apr_status_t status;
+
workers_unregister(m);
-
status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
+ while (!h2_io_set_iter(m->stream_ios, stream_done_iter, m)) {
+ /* iterator until all h2_io have been orphaned or destroyed */
+ }
+
release(m, 0);
while (m->refs > 0) {
m->join_wait = wait;
apr_thread_cond_wait(wait, m->lock);
}
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
- "h2_mplx(%ld): release_join -> destroy", m->id);
- m->pool = NULL;
- apr_thread_mutex_unlock(m->lock);
+ "h2_mplx(%ld): release_join -> destroy, (#ios=%ld)",
+ m->id, (long)h2_io_set_size(m->stream_ios));
h2_mplx_destroy(m);
+ /* all gone */
+ /*apr_thread_mutex_unlock(m->lock);*/
}
return status;
}
status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
m->aborted = 1;
- h2_io_set_destroy_all(m->stream_ios);
apr_thread_mutex_unlock(m->lock);
}
- workers_unregister(m);
-}
-
-
-static void io_destroy(h2_mplx *m, h2_io *io)
-{
- apr_pool_t *pool = io->pool;
-
- io->pool = NULL;
- /* The pool is cleared/destroyed which also closes all
- * allocated file handles. Give this count back to our
- * file handle pool. */
- m->file_handles_allowed += io->files_handles_owned;
- h2_io_set_remove(m->stream_ios, io);
- h2_io_set_remove(m->ready_ios, io);
- h2_io_destroy(io);
-
- if (pool) {
- apr_pool_clear(pool);
- if (m->spare_pool) {
- apr_pool_destroy(m->spare_pool);
- }
- m->spare_pool = pool;
- }
}
apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error)
apr_status_t status;
AP_DEBUG_ASSERT(m);
- if (m->aborted) {
- return APR_ECONNABORTED;
- }
status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
* for processing, e.g. when we received all HEADERs. But when
* a stream is cancelled very early, it will not exist. */
if (io) {
- /* Remove io from ready set, we will never submit it */
- h2_io_set_remove(m->ready_ios, io);
- if (io->task_done || h2_tq_remove(m->q, io->id)) {
- /* already finished or not even started yet */
- io_destroy(m, io);
- }
- else {
- /* cleanup once task is done */
- io->orphaned = 1;
- if (rst_error) {
- h2_io_rst(io, rst_error);
- }
- }
-
+ io_stream_done(m, io, rst_error);
}
apr_thread_mutex_unlock(m->lock);
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);
* insert an error one so that our streams can properly
* reset.
*/
- h2_response *r = h2_response_create(stream_id, 0,
- 500, NULL, m->pool);
+ h2_response *r = h2_response_die(stream_id, APR_EGENERAL,
+ io->request, m->pool);
status = out_open(m, stream_id, r, NULL, NULL, NULL);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c,
"h2_mplx(%ld-%d): close, no response, no rst",
m->id, io->id);
}
- status = h2_io_out_close(io);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c,
+ "h2_mplx(%ld-%d): close with trailers=%s",
+ m->id, io->id, trailers? "yes" : "no");
+ status = h2_io_out_close(io, trailers);
H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_close");
have_out_data_for(m, stream_id);
*/
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);
h2_request_end_headers(req, ctx->pool, 1);
push->req = req;
+ push->prio.dependency = H2_DEPENDANT_AFTER;
+ push->prio.weight = NGHTTP2_DEFAULT_WEIGHT;
+
if (!ctx->pushes) {
ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*));
}
* 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;
struct h2_ngheader;
typedef struct h2_push {
- int initiating_id;
+ int initiating_id;
const struct h2_request *req;
- const char *as;
+ h2_priority prio;
} h2_push;
#include <httpd.h>
#include <http_core.h>
+#include <http_connection.h>
#include <http_protocol.h>
-#include <http_config.h>
+#include <http_request.h>
#include <http_log.h>
+#include <http_vhost.h>
+#include <util_filter.h>
+#include <ap_mpm.h>
+#include <mod_core.h>
+#include <scoreboard.h>
#include "h2_private.h"
#include "h2_mplx.h"
req->authority = authority;
req->path = path;
req->headers = header? header : apr_table_make(pool, 10);
-
+ req->request_time = apr_time_now();
+
return req;
}
dst->eoh = src->eoh;
}
+request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn)
+{
+ request_rec *r;
+ apr_pool_t *p;
+ int access_status = HTTP_OK;
+
+ apr_pool_create(&p, conn->pool);
+ apr_pool_tag(p, "request");
+ r = apr_pcalloc(p, sizeof(request_rec));
+ AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn);
+ r->pool = p;
+ r->connection = conn;
+ r->server = conn->base_server;
+
+ r->user = NULL;
+ r->ap_auth_type = NULL;
+
+ r->allowed_methods = ap_make_method_list(p, 2);
+
+ r->headers_in = apr_table_copy(r->pool, req->headers);
+ r->trailers_in = apr_table_make(r->pool, 5);
+ r->subprocess_env = apr_table_make(r->pool, 25);
+ r->headers_out = apr_table_make(r->pool, 12);
+ r->err_headers_out = apr_table_make(r->pool, 5);
+ r->trailers_out = apr_table_make(r->pool, 5);
+ r->notes = apr_table_make(r->pool, 5);
+
+ r->request_config = ap_create_request_config(r->pool);
+ /* Must be set before we run create request hook */
+
+ r->proto_output_filters = conn->output_filters;
+ r->output_filters = r->proto_output_filters;
+ r->proto_input_filters = conn->input_filters;
+ r->input_filters = r->proto_input_filters;
+ ap_run_create_request(r);
+ r->per_dir_config = r->server->lookup_defaults;
+
+ r->sent_bodyct = 0; /* bytect isn't for body */
+
+ r->read_length = 0;
+ r->read_body = REQUEST_NO_BODY;
+
+ r->status = HTTP_OK; /* Until further notice */
+ r->header_only = 0;
+ r->the_request = NULL;
+
+ /* Begin by presuming any module can make its own path_info assumptions,
+ * until some module interjects and changes the value.
+ */
+ r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+ r->useragent_addr = conn->client_addr;
+ r->useragent_ip = conn->client_ip;
+
+ ap_run_pre_read_request(r, conn);
+
+ /* Time to populate r with the data we have. */
+ r->request_time = req->request_time;
+ r->method = req->method;
+ /* Provide quick information about the request method as soon as known */
+ r->method_number = ap_method_number_of(r->method);
+ if (r->method_number == M_GET && r->method[0] == 'H') {
+ r->header_only = 1;
+ }
+
+ ap_parse_uri(r, req->path);
+ r->protocol = (char*)"HTTP/2";
+ r->proto_num = HTTP_VERSION(2, 0);
+
+ r->the_request = apr_psprintf(r->pool, "%s %s %s",
+ r->method, req->path, r->protocol);
+
+ /* update what we think the virtual host is based on the headers we've
+ * now read. may update status.
+ * Leave r->hostname empty, vhost will parse if form our Host: header,
+ * otherwise we get complains about port numbers.
+ */
+ r->hostname = NULL;
+ ap_update_vhost_from_headers(r);
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ /*
+ * Add the HTTP_IN filter here to ensure that ap_discard_request_body
+ * called by ap_die and by ap_send_error_response works correctly on
+ * status codes that do not cause the connection to be dropped and
+ * in situations where the connection should be kept alive.
+ */
+ ap_add_input_filter_handle(ap_http_input_filter_handle,
+ NULL, r, r->connection);
+
+ if (access_status != HTTP_OK
+ || (access_status = ap_run_post_read_request(r))) {
+ /* Request check post hooks failed. An example of this would be a
+ * request for a vhost where h2 is disabled --> 421.
+ */
+ ap_die(access_status, r);
+ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+ ap_run_log_transaction(r);
+ r = NULL;
+ goto traceout;
+ }
+
+ AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
+ (char *)r->uri, (char *)r->server->defn_name,
+ r->status);
+ return r;
+traceout:
+ AP_READ_REQUEST_FAILURE((uintptr_t)r);
+ return r;
+}
+
+
apr_table_t *headers;
apr_table_t *trailers;
+ apr_time_t request_time;
apr_off_t content_length;
int chunked;
int eoh;
void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src);
+/**
+ * Create a request_rec representing the h2_request to be
+ * processed on the given connection.
+ *
+ * @param req the h2 request to process
+ * @param conn the connection to process the request on
+ * @return the request_rec representing the request
+ */
+request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn);
#endif /* defined(__mod_h2__h2_request__) */
#include <httpd.h>
#include <http_core.h>
#include <http_log.h>
+#include <util_time.h>
#include <nghttp2/nghttp2.h>
#include "h2_private.h"
#include "h2_h2.h"
#include "h2_util.h"
+#include "h2_request.h"
#include "h2_response.h"
-h2_response *h2_response_create(int stream_id,
- int rst_error,
- int http_status,
- apr_array_header_t *hlines,
- apr_pool_t *pool)
+static apr_table_t *parse_headers(apr_array_header_t *hlines, apr_pool_t *pool)
{
- apr_table_t *header;
- h2_response *response = apr_pcalloc(pool, sizeof(h2_response));
- int i;
- if (response == NULL) {
- return NULL;
- }
-
- response->stream_id = stream_id;
- response->rst_error = rst_error;
- response->http_status = http_status? http_status : 500;
- response->content_length = -1;
-
if (hlines) {
- header = apr_table_make(pool, hlines->nelts);
+ apr_table_t *headers = apr_table_make(pool, hlines->nelts);
+ int i;
+
for (i = 0; i < hlines->nelts; ++i) {
char *hline = ((char **)hlines->elts)[i];
char *sep = ap_strchr(hline, ':');
if (!sep) {
ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
- APLOGNO(02955) "h2_response(%d): invalid header[%d] '%s'",
- response->stream_id, i, (char*)hline);
+ APLOGNO(02955) "h2_response: invalid header[%d] '%s'",
+ i, (char*)hline);
/* not valid format, abort */
return NULL;
}
}
if (!h2_util_ignore_header(hline)) {
- apr_table_merge(header, hline, sep);
- if (*sep && H2_HD_MATCH_LIT_CS("content-length", hline)) {
- char *end;
- response->content_length = apr_strtoi64(sep, &end, 10);
- if (sep == end) {
- ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL,
- pool, APLOGNO(02956)
- "h2_response(%d): content-length"
- " value not parsed: %s",
- response->stream_id, sep);
- response->content_length = -1;
- }
- }
+ apr_table_merge(headers, hline, sep);
}
}
+ return headers;
}
else {
- header = apr_table_make(pool, 0);
+ return apr_table_make(pool, 0);
}
+}
+
+static h2_response *h2_response_create_int(int stream_id,
+ int rst_error,
+ int http_status,
+ apr_table_t *headers,
+ apr_pool_t *pool)
+{
+ h2_response *response;
+ const char *s;
- response->header = header;
+ if (!headers) {
+ return NULL;
+ }
+
+ response = apr_pcalloc(pool, sizeof(h2_response));
+ if (response == NULL) {
+ return NULL;
+ }
+
+ response->stream_id = stream_id;
+ response->rst_error = rst_error;
+ response->http_status = http_status? http_status : 500;
+ response->content_length = -1;
+ response->headers = headers;
+
+ s = apr_table_get(headers, "Content-Length");
+ if (s) {
+ char *end;
+
+ response->content_length = apr_strtoi64(s, &end, 10);
+ if (s == end) {
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL,
+ pool, APLOGNO(02956)
+ "h2_response: content-length"
+ " value not parsed: %s", s);
+ response->content_length = -1;
+ }
+ }
return response;
}
+
+h2_response *h2_response_create(int stream_id,
+ int rst_error,
+ int http_status,
+ apr_array_header_t *hlines,
+ apr_pool_t *pool)
+{
+ return h2_response_create_int(stream_id, rst_error, http_status,
+ parse_headers(hlines, pool), pool);
+}
+
h2_response *h2_response_rcreate(int stream_id, request_rec *r,
apr_table_t *header, apr_pool_t *pool)
{
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");
return response;
}
-void h2_response_destroy(h2_response *response)
+h2_response *h2_response_die(int stream_id, apr_status_t type,
+ const struct h2_request *req, apr_pool_t *pool)
{
- (void)response;
+ apr_table_t *headers = apr_table_make(pool, 5);
+ char *date = NULL;
+
+ date = apr_palloc(pool, APR_RFC822_DATE_LEN);
+ ap_recent_rfc822_date(date, req->request_time);
+ apr_table_setn(headers, "Date", date);
+ apr_table_setn(headers, "Server", ap_get_server_banner());
+
+ return h2_response_create_int(stream_id, 0, 500, headers, pool);
}
-h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from)
+h2_response *h2_response_clone(apr_pool_t *pool, h2_response *from)
{
h2_response *to = apr_pcalloc(pool, sizeof(h2_response));
to->stream_id = from->stream_id;
to->http_status = from->http_status;
to->content_length = from->content_length;
- if (from->header) {
- to->header = apr_table_clone(pool, from->header);
+ if (from->headers) {
+ to->headers = apr_table_clone(pool, from->headers);
+ }
+ if (from->trailers) {
+ to->trailers = apr_table_clone(pool, from->trailers);
}
return to;
}
+void h2_response_set_trailers(h2_response *response, apr_table_t *trailers)
+{
+ response->trailers = trailers;
+}
#ifndef __mod_h2__h2_response__
#define __mod_h2__h2_response__
+struct h2_request;
struct h2_push;
typedef struct h2_response {
int rst_error;
int http_status;
apr_off_t content_length;
- apr_table_t *header;
- apr_table_t *trailer;
+ apr_table_t *headers;
+ apr_table_t *trailers;
} h2_response;
+/**
+ * Create the response from the status and parsed header lines.
+ * @param stream_id id of the stream to create the response for
+ * @param rst_error error for reset or 0
+ * @param http_status http status code of response
+ * @param hlines the text lines of the response header
+ * @param pool the memory pool to use
+ */
h2_response *h2_response_create(int stream_id,
int rst_error,
int http_status,
apr_array_header_t *hlines,
apr_pool_t *pool);
+/**
+ * Create the response from the given request_rec.
+ * @param stream_id id of the stream to create the response for
+ * @param r the request record which was processed
+ * @param header the headers of the response
+ * @param pool the memory pool to use
+ */
h2_response *h2_response_rcreate(int stream_id, request_rec *r,
apr_table_t *header, apr_pool_t *pool);
-void h2_response_destroy(h2_response *response);
+/**
+ * Create the response for the given error.
+ * @param stream_id id of the stream to create the response for
+ * @param type the error code
+ * @param req the original h2_request
+ * @param pool the memory pool to use
+ */
+h2_response *h2_response_die(int stream_id, apr_status_t type,
+ const struct h2_request *req, apr_pool_t *pool);
-h2_response *h2_response_copy(apr_pool_t *pool, h2_response *from);
+/**
+ * Deep copies the response into a new pool.
+ * @param pool the pool to use for the clone
+ * @param from the response to clone
+ * @return the cloned response
+ */
+h2_response *h2_response_clone(apr_pool_t *pool, h2_response *from);
+
+/**
+ * Set the trailers in the reponse. Will replace any existing trailers. Will
+ * *not* clone the table.
+ *
+ * @param response the repsone to set the trailers for
+ * @param trailers the trailers to set
+ */
+void h2_response_set_trailers(h2_response *response, apr_table_t *trailers);
#endif /* defined(__mod_h2__h2_response__) */
*/
#include <assert.h>
+#include <math.h>
#include <apr_thread_cond.h>
#include <apr_base64.h>
#include <apr_strings.h>
#include <http_log.h>
#include "h2_private.h"
-#include "h2_bucket_eoc.h"
#include "h2_bucket_eos.h"
#include "h2_config.h"
#include "h2_h2.h"
return stream;
}
-apr_status_t h2_session_flush(h2_session *session)
-{
- return h2_conn_io_flush(&session->io);
-}
-
/**
* Determine the importance of streams when scheduling tasks.
* - if both stream depend on the same one, compare weights
return APR_SUCCESS;
}
+static void *session_malloc(size_t size, void *ctx)
+{
+ h2_session *session = ctx;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c,
+ "h2_session(%ld): malloc(%ld)",
+ session->id, (long)size);
+ return malloc(size);
+}
+
+static void session_free(void *p, void *ctx)
+{
+ h2_session *session = ctx;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c,
+ "h2_session(%ld): free()",
+ session->id);
+ free(p);
+}
+
+static void *session_calloc(size_t n, size_t size, void *ctx)
+{
+ h2_session *session = ctx;
+
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c,
+ "h2_session(%ld): calloc(%ld, %ld)",
+ session->id, (long)n, (long)size);
+ return calloc(n, size);
+}
+
+static void *session_realloc(void *p, size_t size, void *ctx)
+{
+ h2_session *session = ctx;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c,
+ "h2_session(%ld): realloc(%ld)",
+ session->id, (long)size);
+ return realloc(p, size);
+}
+
static h2_session *h2_session_create_int(conn_rec *c,
request_rec *r,
h2_config *config,
session = apr_pcalloc(pool, sizeof(h2_session));
if (session) {
int rv;
+ nghttp2_mem *mem;
+
session->id = c->id;
session->c = c;
session->r = r;
+ session->pool = pool;
apr_pool_pre_cleanup_register(pool, session, session_pool_cleanup);
session->max_stream_count = h2_config_geti(config, H2_CONF_MAX_STREAMS);
session->max_stream_mem = h2_config_geti(config, H2_CONF_STREAM_MAX_MEM);
- session->pool = pool;
-
status = apr_thread_cond_create(&session->iowait, session->pool);
if (status != APR_SUCCESS) {
return NULL;
session->workers = workers;
session->mplx = h2_mplx_create(c, session->pool, workers);
- h2_conn_io_init(&session->io, c);
+ h2_conn_io_init(&session->io, c, session->pool);
session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
status = init_callbacks(c, &callbacks);
h2_session_destroy(session);
return NULL;
}
-
nghttp2_option_set_peer_max_concurrent_streams(options,
(uint32_t)session->max_stream_count);
-
/* We need to handle window updates ourself, otherwise we
* get flooded by nghttp2. */
nghttp2_option_set_no_auto_window_update(options, 1);
- rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
- session, options);
+ if (APLOGctrace6(c)) {
+ mem = apr_pcalloc(session->pool, sizeof(nghttp2_mem));
+ mem->mem_user_data = session;
+ mem->malloc = session_malloc;
+ mem->free = session_free;
+ mem->calloc = session_calloc;
+ mem->realloc = session_realloc;
+
+ rv = nghttp2_session_server_new3(&session->ngh2, callbacks,
+ session, options, mem);
+ }
+ else {
+ rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
+ session, options);
+ }
nghttp2_session_callbacks_del(callbacks);
nghttp2_option_del(options);
apr_pool_destroy(session->spare);
session->spare = NULL;
}
- if (session->mplx) {
- h2_mplx_release_and_join(session->mplx, session->iowait);
- session->mplx = NULL;
- }
}
void h2_session_destroy(h2_session *session)
AP_DEBUG_ASSERT(session);
h2_session_cleanup(session);
+ if (session->mplx) {
+ h2_mplx_release_and_join(session->mplx, session->iowait);
+ session->mplx = NULL;
+ }
if (session->streams) {
if (!h2_stream_set_is_empty(session->streams)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0,session->c,
"h2_session: closing, writing eoc");
- h2_session_cleanup(session);
- return h2_conn_io_writeb(&session->io,
- h2_bucket_eoc_create(session->c->bucket_alloc,
- session));
+ h2_session_cleanup(session);
+ return h2_conn_io_close(&session->io, session);
}
static ssize_t stream_data_cb(nghttp2_session *ng2s,
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;
+ h2_priority *prio;
memset(&provider, 0, sizeof(provider));
provider.source.fd = stream->id;
"h2_stream(%ld-%d): submit response %d",
session->id, stream->id, response->http_status);
+ prio = h2_stream_get_priority(stream);
+ if (prio) {
+ h2_session_set_prio(session, stream, prio);
+ /* no showstopper if that fails for some reason */
+ }
+
ngh = h2_util_ngheader_make_res(stream->pool, response->http_status,
- response->header);
+ response->headers);
rv = nghttp2_submit_response(session->ngh2, response->stream_id,
ngh->nv, ngh->nvlen, &provider);
stream = h2_session_open_stream(session, nid);
if (stream) {
h2_stream_set_h2_request(stream, is->id, push->req);
+ h2_stream_set_priority(stream, &push->prio);
status = stream_schedule(session, stream, 1);
if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
return stream;
}
+static int valid_weight(float f)
+{
+ int w = floor(f);
+ return (w < NGHTTP2_MIN_WEIGHT? NGHTTP2_MIN_WEIGHT :
+ (w > NGHTTP2_MAX_WEIGHT)? NGHTTP2_MAX_WEIGHT : w);
+}
+
+apr_status_t h2_session_set_prio(h2_session *session, h2_stream *stream,
+ h2_priority *prio)
+{
+ apr_status_t status = APR_SUCCESS;
+#ifdef H2_NG2_CHANGE_PRIO
+ nghttp2_stream *s_grandpa, *s_parent, *s;
+
+ s = nghttp2_session_find_stream(session->ngh2, stream->id);
+ if (!s) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): lookup of nghttp2_stream failed",
+ session->id, stream->id);
+ return APR_EINVAL;
+ }
+
+ s_parent = nghttp2_stream_get_parent(s);
+ if (s_parent) {
+ nghttp2_priority_spec ps;
+ int id_parent, id_grandpa, w_parent, w, rv = 0;
+ char *ptype = "AFTER";
+ h2_dependency dep = prio->dependency;
+
+ id_parent = nghttp2_stream_get_stream_id(s_parent);
+ s_grandpa = nghttp2_stream_get_parent(s_parent);
+ if (s_grandpa) {
+ id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
+ }
+ else {
+ /* parent of parent does not exist,
+ * only possible if parent == root */
+ dep = H2_DEPENDANT_AFTER;
+ }
+
+ switch (dep) {
+ case H2_DEPENDANT_INTERLEAVED:
+ /* PUSHed stream is to be interleaved with initiating stream.
+ * It is made a sibling of the initiating stream and gets a
+ * proportional weight [1, MAX_WEIGHT] of the initiaing
+ * stream weight.
+ */
+ ptype = "INTERLEAVED";
+ w_parent = nghttp2_stream_get_weight(s_parent);
+ w = valid_weight(w_parent * ((float)NGHTTP2_MAX_WEIGHT / prio->weight));
+ nghttp2_priority_spec_init(&ps, id_grandpa, w, 0);
+ break;
+
+ case H2_DEPENDANT_BEFORE:
+ /* PUSHed stream os to be sent BEFORE the initiating stream.
+ * It gets the same weight as the initiating stream, replaces
+ * that stream in the dependency tree and has the initiating
+ * stream as child with MAX_WEIGHT.
+ */
+ ptype = "BEFORE";
+ nghttp2_priority_spec_init(&ps, stream->id, NGHTTP2_MAX_WEIGHT, 0);
+ rv = nghttp2_session_change_stream_priority(session->ngh2, id_parent, &ps);
+ if (rv < 0) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): PUSH BEFORE2, weight=%d, "
+ "depends=%d, returned=%d",
+ session->id, id_parent, ps.weight, ps.stream_id, rv);
+ return APR_EGENERAL;
+ }
+ id_grandpa = nghttp2_stream_get_stream_id(s_grandpa);
+ w_parent = nghttp2_stream_get_weight(s_parent);
+ nghttp2_priority_spec_init(&ps, id_grandpa, valid_weight(w_parent), 0);
+ break;
+
+ case H2_DEPENDANT_AFTER:
+ /* The PUSHed stream is to be sent after the initiating stream.
+ * Give if the specified weight and let it depend on the intiating
+ * stream.
+ */
+ /* fall through, it's the default */
+ default:
+ nghttp2_priority_spec_init(&ps, id_parent, valid_weight(prio->weight), 0);
+ break;
+ }
+
+
+ rv = nghttp2_session_change_stream_priority(session->ngh2, stream->id, &ps);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_stream(%ld-%d): PUSH %s, weight=%d, "
+ "depends=%d, returned=%d",
+ session->id, stream->id, ptype,
+ ps.weight, ps.stream_id, rv);
+ status = (rv < 0)? APR_EGENERAL : APR_SUCCESS;
+ }
+#else
+ (void)session;
+ (void)stream;
+ (void)prio;
+ (void)valid_weight;
+#endif
+ return status;
+}
+
apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream)
{
apr_pool_t *pool = h2_stream_detach_pool(stream);
struct apr_thread_cond_t;
struct h2_config;
struct h2_mplx;
+struct h2_priority;
struct h2_push;
struct h2_response;
struct h2_session;
*/
apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv);
-/**
- * Pass any buffered output data through the connection filters.
- * @param session the session to flush
- */
-apr_status_t h2_session_flush(h2_session *session);
-
/**
* Called before a session gets destroyed, might flush output etc.
*/
struct h2_stream *h2_session_push(h2_session *session,
struct h2_stream *is, struct h2_push *push);
+apr_status_t h2_session_set_prio(h2_session *session,
+ struct h2_stream *stream,
+ struct h2_priority *prio);
+
+
#endif /* defined(__mod_h2__h2_session__) */
* 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;
+}
+
+void h2_stream_set_priority(h2_stream *stream, h2_priority *prio)
+{
+ stream->prio = apr_pcalloc(stream->pool, sizeof(*prio));
+ memcpy(stream->prio, prio, sizeof(*prio));
}
+
+h2_priority *h2_stream_get_priority(h2_stream *stream)
+{
+ return stream->prio;
+}
+
} h2_stream_state_t;
struct h2_mplx;
+struct h2_priority;
struct h2_request;
struct h2_response;
struct h2_session;
apr_bucket_brigade *bbout; /* output DATA */
apr_off_t data_frames_sent; /* # of DATA frames sent out for this stream */
+
+ struct h2_priority *prio; /* priority information to set before submit */
};
*/
apr_table_t *h2_stream_get_trailers(h2_stream *stream);
+/**
+ * Get priority information set for this stream.
+ */
+struct h2_priority *h2_stream_get_priority(h2_stream *stream);
+
+/**
+ * Set the priority information to use on the submit of the stream.
+ * @param stream the stream to set priority on
+ * @param prio the priority information
+ */
+void h2_stream_set_priority(h2_stream *stream, struct h2_priority *prio);
+
#endif /* defined(__mod_h2__h2_stream__) */
return h2_from_h1_read_response(task->output->from_h1, f, bb);
}
-static apr_status_t h2_task_process_request(h2_task *task);
-
/*******************************************************************************
* Register various hooks
*/
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)
return OK;
}
-static int h2_task_process_conn(conn_rec* c)
-{
- h2_ctx *ctx = h2_ctx_get(c);
-
- if (h2_ctx_is_task(ctx)) {
- if (!ctx->task->serialize_headers) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
- "h2_h2, processing request directly");
- h2_task_process_request(ctx->task);
- return DONE;
- }
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
- "h2_task(%s), serialized handling", ctx->task->id);
- }
- return DECLINED;
-}
-
-
h2_task *h2_task_create(long session_id, const h2_request *req,
apr_pool_t *pool, h2_mplx *mplx, int eos)
{
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;
}
apr_thread_cond_signal(task->io);
}
- h2_mplx_task_done(task->mplx, task->stream_id);
h2_worker_release_task(worker, task);
+ h2_mplx_task_done(task->mplx, task->stream_id);
return status;
}
-static request_rec *h2_task_create_request(h2_task *task)
-{
- conn_rec *conn = task->c;
- request_rec *r;
- apr_pool_t *p;
- int access_status = HTTP_OK;
-
- apr_pool_create(&p, conn->pool);
- apr_pool_tag(p, "request");
- r = apr_pcalloc(p, sizeof(request_rec));
- AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn);
- r->pool = p;
- r->connection = conn;
- r->server = conn->base_server;
-
- r->user = NULL;
- r->ap_auth_type = NULL;
-
- r->allowed_methods = ap_make_method_list(p, 2);
-
- r->headers_in = apr_table_copy(r->pool, task->request->headers);
- r->trailers_in = apr_table_make(r->pool, 5);
- r->subprocess_env = apr_table_make(r->pool, 25);
- r->headers_out = apr_table_make(r->pool, 12);
- r->err_headers_out = apr_table_make(r->pool, 5);
- r->trailers_out = apr_table_make(r->pool, 5);
- r->notes = apr_table_make(r->pool, 5);
-
- r->request_config = ap_create_request_config(r->pool);
- /* Must be set before we run create request hook */
-
- r->proto_output_filters = conn->output_filters;
- r->output_filters = r->proto_output_filters;
- r->proto_input_filters = conn->input_filters;
- r->input_filters = r->proto_input_filters;
- ap_run_create_request(r);
- r->per_dir_config = r->server->lookup_defaults;
-
- r->sent_bodyct = 0; /* bytect isn't for body */
-
- r->read_length = 0;
- r->read_body = REQUEST_NO_BODY;
-
- r->status = HTTP_OK; /* Until further notice */
- r->header_only = 0;
- r->the_request = NULL;
-
- /* Begin by presuming any module can make its own path_info assumptions,
- * until some module interjects and changes the value.
- */
- r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
-
- r->useragent_addr = conn->client_addr;
- r->useragent_ip = conn->client_ip;
-
- ap_run_pre_read_request(r, conn);
-
- /* Time to populate r with the data we have. */
- r->request_time = apr_time_now();
- r->method = task->request->method;
- /* Provide quick information about the request method as soon as known */
- r->method_number = ap_method_number_of(r->method);
- if (r->method_number == M_GET && r->method[0] == 'H') {
- r->header_only = 1;
- }
-
- ap_parse_uri(r, task->request->path);
- r->protocol = (char*)"HTTP/2";
- r->proto_num = HTTP_VERSION(2, 0);
-
- r->the_request = apr_psprintf(r->pool, "%s %s %s",
- r->method, task->request->path, r->protocol);
-
- /* update what we think the virtual host is based on the headers we've
- * now read. may update status.
- * Leave r->hostname empty, vhost will parse if form our Host: header,
- * otherwise we get complains about port numbers.
- */
- r->hostname = NULL;
- ap_update_vhost_from_headers(r);
-
- /* we may have switched to another server */
- r->per_dir_config = r->server->lookup_defaults;
-
- /*
- * Add the HTTP_IN filter here to ensure that ap_discard_request_body
- * called by ap_die and by ap_send_error_response works correctly on
- * status codes that do not cause the connection to be dropped and
- * in situations where the connection should be kept alive.
- */
- ap_add_input_filter_handle(ap_http_input_filter_handle,
- NULL, r, r->connection);
-
- if (access_status != HTTP_OK
- || (access_status = ap_run_post_read_request(r))) {
- /* Request check post hooks failed. An example of this would be a
- * request for a vhost where h2 is disabled --> 421.
- */
- ap_die(access_status, r);
- ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
- ap_run_log_transaction(r);
- r = NULL;
- goto traceout;
- }
-
- AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method,
- (char *)r->uri, (char *)r->server->defn_name,
- r->status);
- return r;
-traceout:
- AP_READ_REQUEST_FAILURE((uintptr_t)r);
- return r;
-}
-
-
-static apr_status_t h2_task_process_request(h2_task *task)
+static apr_status_t h2_task_process_request(const h2_request *req, conn_rec *c)
{
- conn_rec *c = task->c;
request_rec *r;
conn_state_t *cs = c->cs;
- r = h2_task_create_request(task);
+ r = h2_request_create_rec(req, c);
if (r && (r->status == HTTP_OK)) {
ap_update_child_status(c->sbh, SERVER_BUSY_READ, r);
return APR_SUCCESS;
}
+static int h2_task_process_conn(conn_rec* c)
+{
+ h2_ctx *ctx = h2_ctx_get(c);
+
+ if (h2_ctx_is_task(ctx)) {
+ if (!ctx->task->serialize_headers) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_h2, processing request directly");
+ h2_task_process_request(ctx->task->request, c);
+ return DONE;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_task(%s), serialized handling", ctx->task->id);
+ }
+ return DECLINED;
+}
+
+
return APR_ECONNABORTED;
}
+ output->trailers_passed = !!response->trailers;
return h2_mplx_out_open(output->task->mplx, output->task->stream_id,
response, f, bb, output->task->io);
}
return APR_EOF;
}
+static apr_table_t *get_trailers(h2_task_output *output)
+{
+ if (!output->trailers_passed) {
+ h2_response *response = h2_from_h1_get_response(output->from_h1);
+ if (response && response->trailers) {
+ output->trailers_passed = 1;
+ return response->trailers;
+ }
+ }
+ return NULL;
+}
+
void h2_task_output_close(h2_task_output *output)
{
open_if_needed(output, NULL, NULL);
if (output->state != H2_TASK_OUT_DONE) {
- h2_mplx_out_close(output->task->mplx, output->task->stream_id);
+ h2_mplx_out_close(output->task->mplx, output->task->stream_id,
+ get_trailers(output));
output->state = H2_TASK_OUT_DONE;
}
}
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);
* @macro
* Version number of the h2 module as c string
*/
-#define MOD_HTTP2_VERSION "1.0.5-DEV"
+#define MOD_HTTP2_VERSION "1.0.7"
/**
* @macro
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_HTTP2_VERSION_NUM 0x010005
+#define MOD_HTTP2_VERSION_NUM 0x010007
#endif /* mod_h2_h2_version_h */
apr_allocator_t *allocator = NULL;
apr_pool_t *pool = NULL;
h2_worker *w;
+ apr_status_t status;
- apr_status_t status = apr_allocator_create(&allocator);
+ status = apr_allocator_create(&allocator);
if (status != APR_SUCCESS) {
return NULL;
}
apr_pool_pre_cleanup_register(w->pool, w, cleanup_join_thread);
apr_thread_create(&w->thread, attr, execute, w, w->pool);
- apr_pool_create(&w->task_pool, w->pool);
}
return w;
}
/* Create a subpool from the worker one to be used for all things
* with life-time of this task execution.
*/
+ if (!worker->task_pool) {
+ apr_pool_create(&worker->task_pool, worker->pool);
+ }
task = h2_task_create(m->id, req, worker->task_pool, m, eos);
+
/* Link the task to the worker which provides useful things such
* as mutex, a socket etc. */
task->io = worker->io;