diff options
Diffstat (limited to '')
-rw-r--r-- | modules/http2/h2_conn.c | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c new file mode 100644 index 0000000..88da2ba --- /dev/null +++ b/modules/http2/h2_conn.c @@ -0,0 +1,370 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 <assert.h> +#include <apr_strings.h> + +#include <ap_mpm.h> + +#include <httpd.h> +#include <http_core.h> +#include <http_config.h> +#include <http_log.h> +#include <http_connection.h> +#include <http_protocol.h> +#include <http_request.h> + +#include <mpm_common.h> + +#include "h2_private.h" +#include "h2.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" +#include "h2_h2.h" +#include "h2_task.h" +#include "h2_workers.h" +#include "h2_conn.h" +#include "h2_version.h" + +static struct h2_workers *workers; + +static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN; +static module *mpm_module; +static int async_mpm; +static int mpm_supported = 1; +static apr_socket_t *dummy_socket; + +static void check_modules(int force) +{ + static int checked = 0; + int i; + + if (force || !checked) { + for (i = 0; ap_loaded_modules[i]; ++i) { + module *m = ap_loaded_modules[i]; + + if (!strcmp("event.c", m->name)) { + mpm_type = H2_MPM_EVENT; + mpm_module = m; + break; + } + else if (!strcmp("motorz.c", m->name)) { + mpm_type = H2_MPM_MOTORZ; + mpm_module = m; + break; + } + else if (!strcmp("mpm_netware.c", m->name)) { + mpm_type = H2_MPM_NETWARE; + mpm_module = m; + break; + } + else if (!strcmp("prefork.c", m->name)) { + mpm_type = H2_MPM_PREFORK; + mpm_module = m; + /* While http2 can work really well on prefork, it collides + * today's use case for prefork: runnning single-thread app engines + * like php. If we restrict h2_workers to 1 per process, php will + * work fine, but browser will be limited to 1 active request at a + * time. */ + mpm_supported = 0; + break; + } + else if (!strcmp("simple_api.c", m->name)) { + mpm_type = H2_MPM_SIMPLE; + mpm_module = m; + mpm_supported = 0; + break; + } + else if (!strcmp("mpm_winnt.c", m->name)) { + mpm_type = H2_MPM_WINNT; + mpm_module = m; + break; + } + else if (!strcmp("worker.c", m->name)) { + mpm_type = H2_MPM_WORKER; + mpm_module = m; + break; + } + } + checked = 1; + } +} + +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, maxw; + int max_threads_per_child = 0; + int idle_secs = 0; + + check_modules(1); + ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child); + + status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm); + if (status != APR_SUCCESS) { + /* some MPMs do not implemnent this */ + async_mpm = 0; + status = APR_SUCCESS; + } + + h2_config_init(pool); + + h2_get_num_workers(s, &minw, &maxw); + + idle_secs = h2_config_geti(config, H2_CONF_MAX_WORKER_IDLE_SECS); + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, + "h2_workers: min=%d max=%d, mthrpchild=%d, idle_secs=%d", + minw, maxw, max_threads_per_child, idle_secs); + workers = h2_workers_create(s, pool, minw, maxw, idle_secs); + + ap_register_input_filter("H2_IN", h2_filter_core_input, + NULL, AP_FTYPE_CONNECTION); + + status = h2_mplx_child_init(pool, s); + + if (status == APR_SUCCESS) { + status = apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM, + APR_PROTO_TCP, pool); + } + + return status; +} + +h2_mpm_type_t h2_conn_mpm_type(void) +{ + check_modules(0); + return mpm_type; +} + +const char *h2_conn_mpm_name(void) +{ + check_modules(0); + return mpm_module? mpm_module->name : "unknown"; +} + +int h2_mpm_supported(void) +{ + check_modules(0); + return mpm_supported; +} + +static module *h2_conn_mpm_module(void) +{ + check_modules(0); + return mpm_module; +} + +apr_status_t h2_conn_setup(h2_ctx *ctx, conn_rec *c, request_rec *r) +{ + h2_session *session; + apr_status_t status; + + if (!workers) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911) + "workers not initialized"); + return APR_EGENERAL; + } + + if (r) { + status = h2_session_rcreate(&session, r, ctx, workers); + } + else { + status = h2_session_create(&session, c, ctx, workers); + } + + if (status == APR_SUCCESS) { + h2_ctx_session_set(ctx, session); + } + return status; +} + +apr_status_t h2_conn_run(struct h2_ctx *ctx, conn_rec *c) +{ + apr_status_t status; + int mpm_state = 0; + h2_session *session = h2_ctx_session_get(ctx); + + ap_assert(session); + do { + if (c->cs) { + c->cs->sense = CONN_SENSE_DEFAULT; + c->cs->state = CONN_STATE_HANDLER; + } + + status = h2_session_process(session, async_mpm); + + if (APR_STATUS_IS_EOF(status)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, + H2_SSSN_LOG(APLOGNO(03045), session, + "process, closing conn")); + c->keepalive = AP_CONN_CLOSE; + } + else { + c->keepalive = AP_CONN_KEEPALIVE; + } + + if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) { + break; + } + } while (!async_mpm + && c->keepalive == AP_CONN_KEEPALIVE + && mpm_state != AP_MPMQ_STOPPING); + + if (c->cs) { + switch (session->state) { + case H2_SESSION_ST_INIT: + case H2_SESSION_ST_IDLE: + case H2_SESSION_ST_BUSY: + case H2_SESSION_ST_WAIT: + c->cs->state = CONN_STATE_WRITE_COMPLETION; + break; + case H2_SESSION_ST_CLEANUP: + case H2_SESSION_ST_DONE: + default: + c->cs->state = CONN_STATE_LINGER; + break; + } + } + + return APR_SUCCESS; +} + +apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c) +{ + h2_session *session = h2_ctx_session_get(ctx); + if (session) { + apr_status_t status = h2_session_pre_close(session, async_mpm); + return (status == APR_SUCCESS)? DONE : status; + } + return DONE; +} + +conn_rec *h2_slave_create(conn_rec *master, int slave_id, apr_pool_t *parent) +{ + apr_allocator_t *allocator; + apr_status_t status; + apr_pool_t *pool; + conn_rec *c; + void *cfg; + module *mpm; + + ap_assert(master); + ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, master, + "h2_stream(%ld-%d): create slave", master->id, slave_id); + + /* We create a pool with its own allocator to be used for + * processing a request. This is the only way to have the processing + * independant of its parent pool in the sense that it can work in + * another thread. Also, the new allocator needs its own mutex to + * synchronize sub-pools. + */ + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + status = apr_pool_create_ex(&pool, parent, NULL, allocator); + if (status != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, status, master, + APLOGNO(10004) "h2_session(%ld-%d): create slave pool", + master->id, slave_id); + return NULL; + } + apr_allocator_owner_set(allocator, pool); + apr_pool_tag(pool, "h2_slave_conn"); + + c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec)); + if (c == NULL) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, master, + APLOGNO(02913) "h2_session(%ld-%d): create slave", + master->id, slave_id); + apr_pool_destroy(pool); + return NULL; + } + + memcpy(c, master, sizeof(conn_rec)); + + c->master = master; + c->pool = pool; + c->conn_config = ap_create_conn_config(pool); + c->notes = apr_table_make(pool, 5); + c->input_filters = NULL; + c->output_filters = NULL; + c->bucket_alloc = apr_bucket_alloc_create(pool); + c->data_in_input_filters = 0; + c->data_in_output_filters = 0; + /* prevent mpm_event from making wrong assumptions about this connection, + * like e.g. using its socket for an async read check. */ + c->clogging_input_filters = 1; + c->log = NULL; + c->log_id = apr_psprintf(pool, "%ld-%d", + master->id, slave_id); + c->aborted = 0; + /* We cannot install the master connection socket on the slaves, as + * modules mess with timeouts/blocking of the socket, with + * unwanted side effects to the master connection processing. + * Fortunately, since we never use the slave socket, we can just install + * a single, process-wide dummy and everyone is happy. + */ + ap_set_module_config(c->conn_config, &core_module, dummy_socket); + /* TODO: these should be unique to this thread */ + c->sbh = master->sbh; + /* TODO: not all mpm modules have learned about slave connections yet. + * copy their config from master to slave. + */ + if ((mpm = h2_conn_mpm_module()) != NULL) { + cfg = ap_get_module_config(master->conn_config, mpm); + ap_set_module_config(c->conn_config, mpm, cfg); + } + + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "h2_stream(%ld-%d): created slave", master->id, slave_id); + return c; +} + +void h2_slave_destroy(conn_rec *slave) +{ + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, slave, + "h2_stream(%s): destroy slave", + apr_table_get(slave->notes, H2_TASK_ID_NOTE)); + slave->sbh = NULL; + apr_pool_destroy(slave->pool); +} + +apr_status_t h2_slave_run_pre_connection(conn_rec *slave, apr_socket_t *csd) +{ + if (slave->keepalives == 0) { + /* Simulate that we had already a request on this connection. Some + * hooks trigger special behaviour when keepalives is 0. + * (Not necessarily in pre_connection, but later. Set it here, so it + * is in place.) */ + slave->keepalives = 1; + /* We signal that this connection will be closed after the request. + * Which is true in that sense that we throw away all traffic data + * on this slave connection after each requests. Although we might + * reuse internal structures like memory pools. + * The wanted effect of this is that httpd does not try to clean up + * any dangling data on this connection when a request is done. Which + * is unneccessary on a h2 stream. + */ + slave->keepalive = AP_CONN_CLOSE; + return ap_run_pre_connection(slave, csd); + } + return APR_SUCCESS; +} + |