From: Stefan Eissing Date: Thu, 17 Dec 2015 16:12:43 +0000 (+0000) Subject: moving session reading into a connection input filter X-Git-Tag: 2.5.0-alpha~2526 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dde6628b7a581cf071f461d8025bed2f1ac97b0b;p=thirdparty%2Fapache%2Fhttpd.git moving session reading into a connection input filter git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1720608 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/http2/config.m4 b/modules/http2/config.m4 index 05cf2ba36d0..1a6f89db2d0 100644 --- a/modules/http2/config.m4 +++ b/modules/http2/config.m4 @@ -26,6 +26,7 @@ h2_config.lo dnl h2_conn.lo dnl h2_conn_io.lo dnl h2_ctx.lo dnl +h2_filter.lo dnl h2_from_h1.lo dnl h2_h2.lo dnl h2_io.lo dnl diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c index 21248489548..54e8e405c6e 100644 --- a/modules/http2/h2_conn.c +++ b/modules/http2/h2_conn.c @@ -28,6 +28,7 @@ #include "h2_private.h" #include "h2_config.h" #include "h2_ctx.h" +#include "h2_filter.h" #include "h2_mplx.h" #include "h2_session.h" #include "h2_stream.h" @@ -72,8 +73,7 @@ apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s) const h2_config *config = h2_config_sget(s); apr_status_t status = APR_SUCCESS; int minw = h2_config_geti(config, H2_CONF_MIN_WORKERS); - int maxw = h2_config_geti(config, H2_CONF_MAX_WORKERS); - + int maxw = h2_config_geti(config, H2_CONF_MAX_WORKERS); int max_threads_per_child = 0; int idle_secs = 0; int i; @@ -112,7 +112,10 @@ apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s) workers = h2_workers_create(s, pool, minw, maxw); idle_secs = h2_config_geti(config, H2_CONF_MAX_WORKER_IDLE_SECS); h2_workers_set_max_idle_secs(workers, idle_secs); - + + ap_register_input_filter("H2_IN", h2_filter_core_input, + NULL, AP_FTYPE_CONNECTION); + return status; } @@ -129,6 +132,7 @@ static module *h2_conn_mpm_module(void) { apr_status_t h2_conn_setup(h2_ctx *ctx, conn_rec *c, request_rec *r) { h2_session *session; + h2_filter_core_in *in; if (!workers) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911) @@ -144,6 +148,11 @@ apr_status_t h2_conn_setup(h2_ctx *ctx, conn_rec *c, request_rec *r) } h2_ctx_session_set(ctx, session); + + in = apr_pcalloc(session->pool, sizeof(*in)); + in->session = session; + ap_add_input_filter("H2_IN", in, r, c); + ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c); return APR_SUCCESS; @@ -155,9 +164,16 @@ apr_status_t h2_conn_process(h2_ctx *ctx, int async) h2_session *session; session = h2_ctx_session_get(ctx); + if (session->c->cs) { + session->c->cs->sense = CONN_SENSE_DEFAULT; + } status = h2_session_process(session, async); + if (session->c->cs) { + session->c->cs->state = CONN_STATE_WRITE_COMPLETION; + } + if (status == APR_EOF) { ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, "h2_session(%ld): done", session->id); @@ -169,14 +185,9 @@ apr_status_t h2_conn_process(h2_ctx *ctx, int async) /* hereafter session will be gone */ } else { - session->c->data_in_input_filters = 0; session->c->keepalive = AP_CONN_KEEPALIVE; } - if (session->c->cs) { - session->c->cs->state = CONN_STATE_WRITE_COMPLETION; - } - return DONE; } @@ -261,11 +272,7 @@ apr_status_t h2_slave_setup(h2_task *task, apr_bucket_alloc_t *bucket_alloc, break; } - /* TODO: we simulate that we had already a request on this connection. - * This keeps the mod_ssl SNI vs. Host name matcher from answering - * 400 Bad Request - * when names do not match. We prefer a predictable 421 status. - */ + /* Simulate that we had already a request on this connection. */ task->c->keepalives = 1; return APR_SUCCESS; diff --git a/modules/http2/h2_conn_io.c b/modules/http2/h2_conn_io.c index 21428da078c..bbc15396b72 100644 --- a/modules/http2/h2_conn_io.c +++ b/modules/http2/h2_conn_io.c @@ -27,6 +27,7 @@ #include "h2_config.h" #include "h2_conn_io.h" #include "h2_h2.h" +#include "h2_session.h" #include "h2_util.h" #define TLS_DATA_MAX (16*1024) @@ -50,7 +51,6 @@ apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, apr_pool_t *pool) { io->connection = c; - 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); @@ -93,112 +93,6 @@ int h2_conn_io_is_buffered(h2_conn_io *io) return io->bufsize > 0; } -static apr_status_t h2_conn_io_bucket_read(h2_conn_io *io, - apr_read_type_e block, - h2_conn_io_on_read_cb on_read_cb, - void *puser, int *pdone) -{ - apr_status_t status = APR_SUCCESS; - apr_size_t readlen = 0; - *pdone = 0; - - while (status == APR_SUCCESS && !*pdone && !APR_BRIGADE_EMPTY(io->input)) { - - apr_bucket* bucket = APR_BRIGADE_FIRST(io->input); - if (APR_BUCKET_IS_METADATA(bucket)) { - /* we do nothing regarding any meta here */ - } - else { - const char *bucket_data = NULL; - apr_size_t bucket_length = 0; - status = apr_bucket_read(bucket, &bucket_data, - &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]), - bucket_data, bucket_length); - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection, - "h2_conn_io(%ld): read %d bytes: %s", - io->connection->id, (int)bucket_length, buffer); - } - - 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; - } - return status; -} - -apr_status_t h2_conn_io_read(h2_conn_io *io, - apr_read_type_e block, - h2_conn_io_on_read_cb on_read_cb, - void *puser) -{ - apr_status_t status; - int done = 0; - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection, - "h2_conn_io: try read, block=%d", block); - - if (!APR_BRIGADE_EMPTY(io->input)) { - /* 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; - } - } - - /* We only do a blocking read when we have no streams to process. So, - * in httpd scoreboard lingo, we are in a KEEPALIVE connection state. - * When reading non-blocking, we do have streams to process and update - * child with NULL request. That way, any current request information - * in the scoreboard is preserved. - */ - if (block == APR_BLOCK_READ) { - ap_update_child_status_from_conn(io->connection->sbh, - SERVER_BUSY_KEEPALIVE, - io->connection); - } - else { - 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); - switch (status) { - case APR_SUCCESS: - return h2_conn_io_bucket_read(io, block, on_read_cb, puser, &done); - case APR_EOF: - case APR_EAGAIN: - break; - default: - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection, - "h2_conn_io: error reading"); - break; - } - return status; -} - static apr_status_t pass_out(apr_bucket_brigade *bb, void *ctx) { h2_conn_io *io = (h2_conn_io*)ctx; diff --git a/modules/http2/h2_conn_io.h b/modules/http2/h2_conn_io.h index f051c6c3d9a..4430b06f4df 100644 --- a/modules/http2/h2_conn_io.h +++ b/modules/http2/h2_conn_io.h @@ -17,6 +17,7 @@ #define __mod_h2__h2_conn_io__ struct h2_config; +struct h2_session; /* h2_io is the basic handler of a httpd connection. It keeps two brigades, * one for input, one for output and works with the installed connection @@ -26,7 +27,6 @@ struct h2_config; */ typedef struct { conn_rec *connection; - apr_bucket_brigade *input; apr_bucket_brigade *output; int is_tls; @@ -50,15 +50,6 @@ apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, int h2_conn_io_is_buffered(h2_conn_io *io); -typedef apr_status_t (*h2_conn_io_on_read_cb)(const char *data, apr_size_t len, - apr_size_t *readlen, int *done, - void *puser); - -apr_status_t h2_conn_io_read(h2_conn_io *io, - apr_read_type_e block, - h2_conn_io_on_read_cb on_read_cb, - void *puser); - apr_status_t h2_conn_io_write(h2_conn_io *io, const char *buf, size_t length); diff --git a/modules/http2/h2_filter.c b/modules/http2/h2_filter.c new file mode 100644 index 00000000000..37a6df3e74a --- /dev/null +++ b/modules/http2/h2_filter.c @@ -0,0 +1,129 @@ +/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include "h2_private.h" +#include "h2_session.h" +#include "h2_conn_io.h" +#include "h2_util.h" + +#include "h2_filter.h" + + +static apr_status_t consume_brigade(h2_filter_core_in *in, + apr_bucket_brigade *bb, + apr_read_type_e block) +{ + apr_status_t status = APR_SUCCESS; + apr_size_t readlen = 0; + + while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) { + + apr_bucket* bucket = APR_BRIGADE_FIRST(bb); + if (APR_BUCKET_IS_METADATA(bucket)) { + /* we do nothing regarding any meta here */ + } + else { + const char *bucket_data = NULL; + apr_size_t bucket_length = 0; + status = apr_bucket_read(bucket, &bucket_data, + &bucket_length, block); + + if (status == APR_SUCCESS && bucket_length > 0) { + apr_size_t consumed = 0; + + status = h2_session_receive(in->session, bucket_data, + bucket_length, &consumed); + 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; + } + return status; +} + + +apr_status_t h2_filter_core_input(ap_filter_t* f, + apr_bucket_brigade* brigade, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + h2_filter_core_in *in = f->ctx; + apr_status_t status = APR_SUCCESS; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c, + "core_input: read, block=%d, mode=%d, readbytes=%ld", + block, mode, (long)readbytes); + + if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) { + return ap_get_brigade(f->next, brigade, mode, block, readbytes); + } + + if (mode != AP_MODE_READBYTES) { + return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN; + } + + if (!f->bb) { + f->bb = apr_brigade_create(in->session->pool, f->c->bucket_alloc); + } + + if (APR_BRIGADE_EMPTY(f->bb)) { + /* We only do a blocking read when we have no streams to process. So, + * in httpd scoreboard lingo, we are in a KEEPALIVE connection state. + * When reading non-blocking, we do have streams to process and update + * child with NULL request. That way, any current request information + * in the scoreboard is preserved. + */ + if (block == APR_BLOCK_READ) { + ap_update_child_status_from_conn(f->c->sbh, + SERVER_BUSY_KEEPALIVE, f->c); + } + else { + ap_update_child_status(f->c->sbh, SERVER_BUSY_READ, NULL); + } + + status = ap_get_brigade(f->next, f->bb, AP_MODE_READBYTES, + block, readbytes); + } + + switch (status) { + case APR_SUCCESS: + return consume_brigade(in, f->bb, block); + case APR_EOF: + case APR_EAGAIN: + break; + default: + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c, + "h2_conn_io: error reading"); + break; + } + return status; +} diff --git a/modules/http2/h2_filter.h b/modules/http2/h2_filter.h new file mode 100644 index 00000000000..a9035b67fe7 --- /dev/null +++ b/modules/http2/h2_filter.h @@ -0,0 +1,32 @@ +/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __mod_h2__h2_filter__ +#define __mod_h2__h2_filter__ + +struct h2_session; + +typedef struct { + struct h2_session *session; +} h2_filter_core_in; + +apr_status_t h2_filter_core_input(ap_filter_t* filter, + apr_bucket_brigade* brigade, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes); + + +#endif /* __mod_h2__h2_filter__ */ diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c index 1a7e284c00a..b175413db8c 100644 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@ -434,7 +434,6 @@ static int cipher_is_blacklisted(const char *cipher, const char **psource) /******************************************************************************* * Hooks for processing incoming connections: - * - pre_conn_before_tls switches SSL off for stream connections * - process_conn take over connection in case of h2 */ static int h2_h2_process_conn(conn_rec* c); @@ -528,19 +527,15 @@ int h2_is_acceptable_connection(conn_rec *c, int require_all) int h2_allows_h2_direct(conn_rec *c) { const h2_config *cfg = h2_config_get(c); + int is_tls = h2_h2_is_tls(c); + const char *needed_protocol = is_tls? "h2" : "h2c"; int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT); if (h2_direct < 0) { - if (h2_h2_is_tls(c)) { - /* disabled by default on TLS */ - h2_direct = 0; - } - else { - /* enabled if "Protocols h2c" is configured */ - h2_direct = ap_is_allowed_protocol(c, NULL, NULL, "h2c"); - } + h2_direct = is_tls? 0 : 1; } - return !!h2_direct; + return (h2_direct + && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol)); } int h2_allows_h2_upgrade(conn_rec *c) @@ -592,45 +587,55 @@ int h2_h2_process_conn(conn_rec* c) return DECLINED; } - if (!ctx && c->keepalives == 0 - && !strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c)) - && h2_allows_h2_direct(c) - && h2_is_acceptable_connection(c, 1)) { - /* Fresh connection still is on http/1.1 and H2Direct is enabled. - * Otherwise connection is in a fully acceptable state. - * -> peek at the first 24 incoming bytes - */ - apr_bucket_brigade *temp; - char *s = NULL; - apr_size_t slen; + if (!ctx && c->keepalives == 0) { + const char *proto = ap_get_protocol(c); - temp = apr_brigade_create(c->pool, c->bucket_alloc); - status = ap_get_brigade(c->input_filters, temp, - AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24); - - if (status != APR_SUCCESS) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, - "h2_h2, error reading 24 bytes speculative"); - apr_brigade_destroy(temp); - return DECLINED; + if (APLOGctrace1(c)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn, " + "new connection using protocol '%s', direct=%d, " + "tls acceptable=%d", proto, h2_allows_h2_direct(c), + h2_is_acceptable_connection(c, 1)); } - apr_brigade_pflatten(temp, &s, &slen, c->pool); - if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, - "h2_h2, direct mode detected"); - if (!ctx) { - ctx = h2_ctx_get(c, 1); + if (!strcmp(AP_PROTOCOL_HTTP1, proto) + && h2_allows_h2_direct(c) + && h2_is_acceptable_connection(c, 1)) { + /* Fresh connection still is on http/1.1 and H2Direct is enabled. + * Otherwise connection is in a fully acceptable state. + * -> peek at the first 24 incoming bytes + */ + apr_bucket_brigade *temp; + char *s = NULL; + apr_size_t slen; + + temp = apr_brigade_create(c->pool, c->bucket_alloc); + status = ap_get_brigade(c->input_filters, temp, + AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24); + + if (status != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, + "h2_h2, error reading 24 bytes speculative"); + apr_brigade_destroy(temp); + return DECLINED; } - h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c"); - } - else { - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, - "h2_h2, not detected in %d bytes: %s", - (int)slen, s); + + apr_brigade_pflatten(temp, &s, &slen, c->pool); + if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "h2_h2, direct mode detected"); + if (!ctx) { + ctx = h2_ctx_get(c, 1); + } + h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c"); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "h2_h2, not detected in %d bytes: %s", + (int)slen, s); + } + + apr_brigade_destroy(temp); } - - apr_brigade_destroy(temp); } if (ctx) { diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index c4d31ab1476..07a22e012d7 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -1065,36 +1065,6 @@ h2_stream *h2_session_get_stream(h2_session *session, int stream_id) return session->last_stream; } -/* h2_io_on_read_cb implementation that offers the data read - * directly to the session for consumption. - */ -static apr_status_t session_receive(const char *data, apr_size_t len, - apr_size_t *readlen, int *done, - void *puser) -{ - h2_session *session = (h2_session *)puser; - AP_DEBUG_ASSERT(session); - if (len > 0) { - ssize_t n = nghttp2_session_mem_recv(session->ngh2, - (const uint8_t *)data, len); - if (n < 0) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, - session->c, - "h2_session: nghttp2_session_mem_recv error %d", - (int)n); - if (nghttp2_is_fatal((int)n)) { - *done = 1; - h2_session_abort_int(session, (int)n); - return APR_EGENERAL; - } - } - else { - *readlen = n; - } - } - return APR_SUCCESS; -} - apr_status_t h2_session_close(h2_session *session) { AP_DEBUG_ASSERT(session); @@ -1127,9 +1097,7 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s, * to find out how much of the requested length we can send without * blocking. * Indicate EOS when we encounter it or DEFERRED if the stream - * should be suspended. - * TODO: for handling of TRAILERS, the EOF indication needs - * to be aware of that. + * should be suspended. Beware of trailers. */ (void)ng2s; @@ -1587,46 +1555,83 @@ static apr_status_t h2_session_send(h2_session *session) return APR_SUCCESS; } +apr_status_t h2_session_receive(h2_session *session, + const char *data, apr_size_t len, + apr_size_t *readlen) +{ + if (len > 0) { + ssize_t n = nghttp2_session_mem_recv(session->ngh2, + (const uint8_t *)data, len); + if (n < 0) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, + session->c, + "h2_session: nghttp2_session_mem_recv error %d", + (int)n); + if (nghttp2_is_fatal((int)n)) { + h2_session_abort(session, 0, (int)n); + return APR_EGENERAL; + } + } + else { + *readlen = n; + } + } + return APR_SUCCESS; +} + + + static apr_status_t h2_session_read(h2_session *session, int block) { - apr_status_t status; - status = h2_conn_io_read(&session->io, - block? APR_BLOCK_READ : APR_NONBLOCK_READ, - session_receive, session); - switch (status) { - case APR_SUCCESS: - /* successful read, reset our idle timers */ - session->wait_micros = 0; - break; - case APR_EAGAIN: - break; - default: - if (APR_STATUS_IS_ETIMEDOUT(status) - || APR_STATUS_IS_ECONNABORTED(status) - || APR_STATUS_IS_ECONNRESET(status) - || APR_STATUS_IS_EOF(status) - || APR_STATUS_IS_EBADF(status)) { - /* common status for a client that has left */ - ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, - "h2_session(%ld): terminating", - session->id); - /* Stolen from mod_reqtimeout to speed up lingering when - * a read timeout happened. - */ - apr_table_setn(session->c->notes, "short-lingering-close", "1"); - } - else { - /* uncommon status, log on INFO so that we see this */ - ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c, - APLOGNO(02950) - "h2_session(%ld): error reading, terminating", - session->id); - } - h2_session_abort(session, status, 0); - break; + while (1) { + apr_status_t status; + + /* H2_IN filter handles all incoming data against the session. + * We just pull at the filter chain to make it happen */ + status = ap_get_brigade(session->c->input_filters, + session->bbtmp, AP_MODE_READBYTES, + block? APR_BLOCK_READ : APR_NONBLOCK_READ, + APR_BUCKET_BUFF_SIZE); + /* get rid of any possible data we do not expect to get */ + apr_brigade_cleanup(session->bbtmp); + + switch (status) { + case APR_SUCCESS: + /* successful read, reset our idle timers */ + session->wait_micros = 0; + if (block) { + return APR_SUCCESS; + } + break; + case APR_EAGAIN: + return status; + default: + if (APR_STATUS_IS_ETIMEDOUT(status) + || APR_STATUS_IS_ECONNABORTED(status) + || APR_STATUS_IS_ECONNRESET(status) + || APR_STATUS_IS_EOF(status) + || APR_STATUS_IS_EBADF(status)) { + /* common status for a client that has left */ + ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, + "h2_session(%ld): terminating", + session->id); + /* Stolen from mod_reqtimeout to speed up lingering when + * a read timeout happened. + */ + apr_table_setn(session->c->notes, "short-lingering-close", "1"); + } + else { + /* uncommon status, log on INFO so that we see this */ + ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c, + APLOGNO(02950) + "h2_session(%ld): error reading, terminating", + session->id); + } + h2_session_abort(session, status, 0); + return status; + } } - return status; } static apr_status_t h2_session_submit(h2_session *session) @@ -1701,6 +1706,58 @@ apr_status_t h2_session_process(h2_session *session, int async) } got_streams = !h2_stream_set_is_empty(session->streams); + + /* If we want client data, see if some is there. */ + if (nghttp2_session_want_read(session->ngh2)) { + int idle = (session->frames_received > 2 && !got_streams); + int may_block = ((session->frames_received <= 1) + || idle + || (!h2_stream_set_has_unsubmitted(session->streams) + && !h2_stream_set_has_suspended(session->streams))); + + status = h2_session_read(session, may_block && !async); + + ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c, + "h2_session(%ld): process -> read", session->id); + if (status == APR_SUCCESS) { + have_read = 1; + got_streams = !h2_stream_set_is_empty(session->streams); + if (session->reprioritize) { + ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, session->c, + "h2_session(%ld): process -> reprioritize", session->id); + h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session); + session->reprioritize = 0; + } + } + else if (status == APR_EAGAIN) { + /* FIXME: disabling this as event currently discards + * connections on load when we return. */ + if (async && may_block && 0) { + /* There is nothing to read and we are in a state where we + * have nothing to write until new input comes. Return to + * our caller so that the MPM may schedule us again when + * read seems possible. + */ + ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c, + "h2_session(%ld): process -> BLOCK_READ, " + "frames_received=%d, got_streams=%d, " + "have_written=%d", + session->id, (int)session->frames_received, + (int)got_streams, (int)have_written); + h2_conn_io_flush(&session->io); + if (session->c->cs) { + session->c->cs->sense = CONN_SENSE_WANT_READ; + } + return APR_SUCCESS; + } + } + else { + ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c, + "h2_session(%ld): failed read", session->id); + return status; + } + } + if (got_streams) { ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, session->c, "h2_session(%ld): process -> check resume", session->id); @@ -1745,52 +1802,6 @@ apr_status_t h2_session_process(h2_session *session, int async) } } - /* If we want client data, see if some is there. */ - if (nghttp2_session_want_read(session->ngh2)) { - int idle = (session->frames_received > 2 && !got_streams); - int may_block = ((session->frames_received <= 1) - || (idle && !async) - || (!have_written && !h2_stream_set_has_unsubmitted(session->streams) - && !h2_stream_set_has_suspended(session->streams))); - status = h2_session_read(session, may_block); - - ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c, - "h2_session(%ld): process -> read", session->id); - if (status == APR_SUCCESS) { - have_read = 1; - got_streams = !h2_stream_set_is_empty(session->streams); - if (session->reprioritize) { - ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, session->c, - "h2_session(%ld): process -> reprioritize", session->id); - h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session); - session->reprioritize = 0; - } - } - else if (status == APR_EAGAIN && idle && async) { - /* There is nothing to read and we are in a state where we - * have nothing to write until new input comes. Return to - * our caller so that the MPM may schedule us again when - * read seems possible. - */ - ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c, - "h2_session(%ld): process -> BLOCK_READ, " - "frames_received=%d, got_streams=%d, " - "have_written=%d", - session->id, (int)session->frames_received, - (int)got_streams, (int)have_written); - if (have_written) { - h2_conn_io_flush(&session->io); - } - if (session->c->cs) { - session->c->cs->state = CONN_STATE_WRITE_COMPLETION; - session->c->cs->sense = (have_written? - CONN_SENSE_DEFAULT - : CONN_SENSE_WANT_READ); - } - return APR_SUCCESS; - } - } - if (!have_read && !have_written) { if (session->wait_micros == 0) { session->wait_micros = 10; diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h index 2b459ea677c..ba6243aa911 100644 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@ -62,6 +62,7 @@ struct h2_session { * of 'h2c', NULL otherwise */ server_rec *s; /* server/vhost we're starting on */ const struct h2_config *config; /* Relevant config for this session */ + int started; int aborted; /* this session is being aborted */ int reprioritize; /* scheduled streams priority needs to @@ -83,6 +84,7 @@ struct h2_session { struct apr_thread_cond_t *iowait; /* our cond when trywaiting for data */ h2_conn_io io; /* io on httpd conn filters */ + struct h2_mplx *mplx; /* multiplexer for stream data */ struct h2_stream *last_stream; /* last stream worked with */ @@ -120,6 +122,14 @@ h2_session *h2_session_create(conn_rec *c, struct h2_ctx *ctx, h2_session *h2_session_rcreate(request_rec *r, struct h2_ctx *ctx, struct h2_workers *workers); +/** + * Recieve len bytes of raw HTTP/2 input data. Return the amount + * consumed and if the session is done. + */ +apr_status_t h2_session_receive(h2_session *session, + const char *data, apr_size_t len, + apr_size_t *readlen); + /** * Process the given HTTP/2 session until it is ended or a fatal * error occured. diff --git a/modules/http2/mod_http2.dsp b/modules/http2/mod_http2.dsp index 12a3abfe98c..ce878491393 100644 --- a/modules/http2/mod_http2.dsp +++ b/modules/http2/mod_http2.dsp @@ -129,6 +129,10 @@ SOURCE=./h2_ctx.c # End Source File # Begin Source File +SOURCE=./h2_filter.c +# End Source File +# Begin Source File + SOURCE=./h2_from_h1.c # End Source File # Begin Source File