| /* 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 <stddef.h> |
| |
| #include <apr_atomic.h> |
| #include <apr_strings.h> |
| |
| #include <httpd.h> |
| #include <http_core.h> |
| #include <http_config.h> |
| #include <http_connection.h> |
| #include <http_protocol.h> |
| #include <http_request.h> |
| #include <http_log.h> |
| #include <http_vhost.h> |
| #include <util_filter.h> |
| #include <ap_mmn.h> |
| #include <ap_mpm.h> |
| #include <mpm_common.h> |
| #include <mod_core.h> |
| #include <scoreboard.h> |
| |
| #include "h2_private.h" |
| #include "h2.h" |
| #include "h2_bucket_beam.h" |
| #include "h2_c1.h" |
| #include "h2_config.h" |
| #include "h2_conn_ctx.h" |
| #include "h2_c2_filter.h" |
| #include "h2_protocol.h" |
| #include "h2_mplx.h" |
| #include "h2_request.h" |
| #include "h2_headers.h" |
| #include "h2_session.h" |
| #include "h2_stream.h" |
| #include "h2_c2.h" |
| #include "h2_util.h" |
| |
| |
| static module *mpm_module; |
| static int mpm_supported = 1; |
| static apr_socket_t *dummy_socket; |
| |
| #if AP_HAS_RESPONSE_BUCKETS |
| |
| static ap_filter_rec_t *c2_net_in_filter_handle; |
| static ap_filter_rec_t *c2_net_out_filter_handle; |
| static ap_filter_rec_t *c2_request_in_filter_handle; |
| static ap_filter_rec_t *c2_notes_out_filter_handle; |
| |
| #endif /* AP_HAS_RESPONSE_BUCKETS */ |
| |
| 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_module = m; |
| break; |
| } |
| else if (!strcmp("motorz.c", m->name)) { |
| mpm_module = m; |
| break; |
| } |
| else if (!strcmp("mpm_netware.c", m->name)) { |
| mpm_module = m; |
| break; |
| } |
| else if (!strcmp("prefork.c", m->name)) { |
| mpm_module = m; |
| /* While http2 can work really well on prefork, it collides |
| * today's use case for prefork: running 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_module = m; |
| mpm_supported = 0; |
| break; |
| } |
| else if (!strcmp("mpm_winnt.c", m->name)) { |
| mpm_module = m; |
| break; |
| } |
| else if (!strcmp("worker.c", m->name)) { |
| mpm_module = m; |
| break; |
| } |
| } |
| checked = 1; |
| } |
| } |
| |
| 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; |
| } |
| |
| apr_status_t h2_c2_child_init(apr_pool_t *pool, server_rec *s) |
| { |
| check_modules(1); |
| return apr_socket_create(&dummy_socket, APR_INET, SOCK_STREAM, |
| APR_PROTO_TCP, pool); |
| } |
| |
| void h2_c2_destroy(conn_rec *c2) |
| { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2, |
| "h2_c2(%s): destroy", c2->log_id); |
| apr_pool_destroy(c2->pool); |
| } |
| |
| void h2_c2_abort(conn_rec *c2, conn_rec *from) |
| { |
| h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2); |
| |
| AP_DEBUG_ASSERT(conn_ctx); |
| AP_DEBUG_ASSERT(conn_ctx->stream_id); |
| if (conn_ctx->beam_in) { |
| h2_beam_abort(conn_ctx->beam_in, from); |
| } |
| if (conn_ctx->beam_out) { |
| h2_beam_abort(conn_ctx->beam_out, from); |
| } |
| c2->aborted = 1; |
| } |
| |
| typedef struct { |
| apr_bucket_brigade *bb; /* c2: data in holding area */ |
| } h2_c2_fctx_in_t; |
| |
| static apr_status_t h2_c2_filter_in(ap_filter_t* f, |
| apr_bucket_brigade* bb, |
| ap_input_mode_t mode, |
| apr_read_type_e block, |
| apr_off_t readbytes) |
| { |
| h2_conn_ctx_t *conn_ctx; |
| h2_c2_fctx_in_t *fctx = f->ctx; |
| apr_status_t status = APR_SUCCESS; |
| apr_bucket *b; |
| apr_off_t bblen; |
| apr_size_t rmax = (readbytes < APR_INT32_MAX)? |
| (apr_size_t)readbytes : APR_INT32_MAX; |
| |
| conn_ctx = h2_conn_ctx_get(f->c); |
| AP_DEBUG_ASSERT(conn_ctx); |
| |
| if (mode == AP_MODE_INIT) { |
| return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes); |
| } |
| |
| if (f->c->aborted) { |
| return APR_ECONNABORTED; |
| } |
| |
| if (APLOGctrace3(f->c)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, f->c, |
| "h2_c2_in(%s-%d): read, mode=%d, block=%d, readbytes=%ld", |
| conn_ctx->id, conn_ctx->stream_id, mode, block, |
| (long)readbytes); |
| } |
| |
| if (!fctx) { |
| fctx = apr_pcalloc(f->c->pool, sizeof(*fctx)); |
| f->ctx = fctx; |
| fctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc); |
| if (!conn_ctx->beam_in) { |
| b = apr_bucket_eos_create(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(fctx->bb, b); |
| } |
| } |
| |
| while (APR_BRIGADE_EMPTY(fctx->bb)) { |
| /* Get more input data for our request. */ |
| if (APLOGctrace2(f->c)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c, |
| "h2_c2_in(%s-%d): get more data from mplx, block=%d, " |
| "readbytes=%ld", |
| conn_ctx->id, conn_ctx->stream_id, block, (long)readbytes); |
| } |
| if (conn_ctx->beam_in) { |
| if (conn_ctx->pipe_in[H2_PIPE_OUT]) { |
| receive: |
| status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, APR_NONBLOCK_READ, |
| conn_ctx->mplx->stream_max_mem); |
| if (APR_STATUS_IS_EAGAIN(status) && APR_BLOCK_READ == block) { |
| status = h2_util_wait_on_pipe(conn_ctx->pipe_in[H2_PIPE_OUT]); |
| if (APR_SUCCESS == status) { |
| goto receive; |
| } |
| } |
| } |
| else { |
| status = h2_beam_receive(conn_ctx->beam_in, f->c, fctx->bb, block, |
| conn_ctx->mplx->stream_max_mem); |
| } |
| } |
| else { |
| status = APR_EOF; |
| } |
| |
| if (APLOGctrace3(f->c)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c, |
| "h2_c2_in(%s-%d): read returned", |
| conn_ctx->id, conn_ctx->stream_id); |
| } |
| if (APR_STATUS_IS_EAGAIN(status) |
| && (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) { |
| /* chunked input handling does not seem to like it if we |
| * return with APR_EAGAIN from a GETLINE read... |
| * upload 100k test on test-ser.example.org hangs */ |
| status = APR_SUCCESS; |
| } |
| else if (APR_STATUS_IS_EOF(status)) { |
| break; |
| } |
| else if (status != APR_SUCCESS) { |
| conn_ctx->last_err = status; |
| return status; |
| } |
| |
| if (APLOGctrace3(f->c)) { |
| h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE3, |
| "c2 input recv raw", fctx->bb); |
| } |
| if (h2_c_logio_add_bytes_in) { |
| apr_brigade_length(bb, 0, &bblen); |
| h2_c_logio_add_bytes_in(f->c, bblen); |
| } |
| } |
| |
| /* Nothing there, no more data to get. Return. */ |
| if (status == APR_EOF && APR_BRIGADE_EMPTY(fctx->bb)) { |
| return status; |
| } |
| |
| if (APLOGctrace3(f->c)) { |
| h2_util_bb_log(f->c, conn_ctx->stream_id, APLOG_TRACE3, |
| "c2 input.bb", fctx->bb); |
| } |
| |
| if (APR_BRIGADE_EMPTY(fctx->bb)) { |
| if (APLOGctrace3(f->c)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, f->c, |
| "h2_c2_in(%s-%d): no data", |
| conn_ctx->id, conn_ctx->stream_id); |
| } |
| return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF; |
| } |
| |
| if (mode == AP_MODE_EXHAUSTIVE) { |
| /* return all we have */ |
| APR_BRIGADE_CONCAT(bb, fctx->bb); |
| } |
| else if (mode == AP_MODE_READBYTES) { |
| status = h2_brigade_concat_length(bb, fctx->bb, rmax); |
| } |
| else if (mode == AP_MODE_SPECULATIVE) { |
| status = h2_brigade_copy_length(bb, fctx->bb, rmax); |
| } |
| else if (mode == AP_MODE_GETLINE) { |
| /* we are reading a single LF line, e.g. the HTTP headers. |
| * this has the nasty side effect to split the bucket, even |
| * though it ends with CRLF and creates a 0 length bucket */ |
| status = apr_brigade_split_line(bb, fctx->bb, block, |
| HUGE_STRING_LEN); |
| if (APLOGctrace3(f->c)) { |
| char buffer[1024]; |
| apr_size_t len = sizeof(buffer)-1; |
| apr_brigade_flatten(bb, buffer, &len); |
| buffer[len] = 0; |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c, |
| "h2_c2_in(%s-%d): getline: %s", |
| conn_ctx->id, conn_ctx->stream_id, buffer); |
| } |
| } |
| else { |
| /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not |
| * to support it. Seems to work. */ |
| ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c, |
| APLOGNO(03472) |
| "h2_c2_in(%s-%d), unsupported READ mode %d", |
| conn_ctx->id, conn_ctx->stream_id, mode); |
| status = APR_ENOTIMPL; |
| } |
| |
| if (APLOGctrace3(f->c)) { |
| apr_brigade_length(bb, 0, &bblen); |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, f->c, |
| "h2_c2_in(%s-%d): %ld data bytes", |
| conn_ctx->id, conn_ctx->stream_id, (long)bblen); |
| } |
| return status; |
| } |
| |
| static apr_status_t beam_out(conn_rec *c2, h2_conn_ctx_t *conn_ctx, apr_bucket_brigade* bb) |
| { |
| apr_off_t written, header_len = 0; |
| apr_status_t rv; |
| |
| if (h2_c_logio_add_bytes_out) { |
| /* mod_logio wants to report the number of bytes written in a |
| * response, including header and footer fields. Since h2 converts |
| * those during c1 processing into the HPACKed h2 HEADER frames, |
| * we need to give mod_logio something here and count just the |
| * raw lengths of all headers in the buckets. */ |
| apr_bucket *b; |
| for (b = APR_BRIGADE_FIRST(bb); |
| b != APR_BRIGADE_SENTINEL(bb); |
| b = APR_BUCKET_NEXT(b)) { |
| #if AP_HAS_RESPONSE_BUCKETS |
| if (AP_BUCKET_IS_RESPONSE(b)) { |
| header_len += (apr_off_t)response_length_estimate(b->data); |
| } |
| if (AP_BUCKET_IS_HEADERS(b)) { |
| header_len += (apr_off_t)headers_length_estimate(b->data); |
| } |
| #else |
| if (H2_BUCKET_IS_HEADERS(b)) { |
| header_len += (apr_off_t)h2_bucket_headers_headers_length(b); |
| } |
| #endif /* AP_HAS_RESPONSE_BUCKETS */ |
| } |
| } |
| |
| rv = h2_beam_send(conn_ctx->beam_out, c2, bb, APR_BLOCK_READ, &written); |
| |
| if (APR_STATUS_IS_EAGAIN(rv)) { |
| rv = APR_SUCCESS; |
| } |
| if (written && h2_c_logio_add_bytes_out) { |
| h2_c_logio_add_bytes_out(c2, written + header_len); |
| } |
| return rv; |
| } |
| |
| static apr_status_t h2_c2_filter_out(ap_filter_t* f, apr_bucket_brigade* bb) |
| { |
| h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(f->c); |
| apr_status_t rv; |
| |
| ap_assert(conn_ctx); |
| #if AP_HAS_RESPONSE_BUCKETS |
| if (!conn_ctx->has_final_response) { |
| apr_bucket *e; |
| |
| for (e = APR_BRIGADE_FIRST(bb); |
| e != APR_BRIGADE_SENTINEL(bb); |
| e = APR_BUCKET_NEXT(e)) |
| { |
| if (AP_BUCKET_IS_RESPONSE(e)) { |
| ap_bucket_response *resp = e->data; |
| if (resp->status >= 200) { |
| conn_ctx->has_final_response = 1; |
| break; |
| } |
| } |
| if (APR_BUCKET_IS_EOS(e)) { |
| break; |
| } |
| } |
| } |
| #endif /* AP_HAS_RESPONSE_BUCKETS */ |
| rv = beam_out(f->c, conn_ctx, bb); |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, f->c, |
| "h2_c2(%s-%d): output leave", |
| conn_ctx->id, conn_ctx->stream_id); |
| if (APR_SUCCESS != rv) { |
| h2_c2_abort(f->c, f->c); |
| } |
| return rv; |
| } |
| |
| static void check_push(request_rec *r, const char *tag) |
| { |
| apr_array_header_t *push_list = h2_config_push_list(r); |
| |
| if (!r->expecting_100 && push_list && push_list->nelts > 0) { |
| int i, old_status; |
| const char *old_line; |
| |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, |
| "%s, early announcing %d resources for push", |
| tag, push_list->nelts); |
| for (i = 0; i < push_list->nelts; ++i) { |
| h2_push_res *push = &APR_ARRAY_IDX(push_list, i, h2_push_res); |
| apr_table_add(r->headers_out, "Link", |
| apr_psprintf(r->pool, "<%s>; rel=preload%s", |
| push->uri_ref, push->critical? "; critical" : "")); |
| } |
| old_status = r->status; |
| old_line = r->status_line; |
| r->status = 103; |
| r->status_line = "103 Early Hints"; |
| ap_send_interim_response(r, 1); |
| r->status = old_status; |
| r->status_line = old_line; |
| } |
| } |
| |
| static int c2_hook_fixups(request_rec *r) |
| { |
| conn_rec *c2 = r->connection; |
| h2_conn_ctx_t *conn_ctx; |
| |
| if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) { |
| return DECLINED; |
| } |
| |
| check_push(r, "late_fixup"); |
| |
| return DECLINED; |
| } |
| |
| #if AP_HAS_RESPONSE_BUCKETS |
| |
| static void c2_pre_read_request(request_rec *r, conn_rec *c2) |
| { |
| h2_conn_ctx_t *conn_ctx; |
| |
| if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) { |
| return; |
| } |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, |
| "h2_c2(%s-%d): adding request filters", |
| conn_ctx->id, conn_ctx->stream_id); |
| ap_add_input_filter_handle(c2_request_in_filter_handle, NULL, r, r->connection); |
| ap_add_output_filter_handle(c2_notes_out_filter_handle, NULL, r, r->connection); |
| } |
| |
| static int c2_post_read_request(request_rec *r) |
| { |
| h2_conn_ctx_t *conn_ctx; |
| conn_rec *c2 = r->connection; |
| apr_time_t timeout; |
| |
| if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) { |
| return DECLINED; |
| } |
| /* Now that the request_rec is fully initialized, set relevant params */ |
| conn_ctx->server = r->server; |
| timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT); |
| if (timeout <= 0) { |
| timeout = r->server->timeout; |
| } |
| h2_conn_ctx_set_timeout(conn_ctx, timeout); |
| /* We only handle this one request on the connection and tell everyone |
| * that there is no need to keep it "clean" if something fails. Also, |
| * this prevents mod_reqtimeout from doing funny business with monitoring |
| * keepalive timeouts. |
| */ |
| r->connection->keepalive = AP_CONN_CLOSE; |
| |
| if (conn_ctx->beam_in && !apr_table_get(r->headers_in, "Content-Length")) { |
| r->body_indeterminate = 1; |
| } |
| |
| if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) { |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, |
| "h2_mplx(%s-%d): copy_files in output", |
| conn_ctx->id, conn_ctx->stream_id); |
| h2_beam_set_copy_files(conn_ctx->beam_out, 1); |
| } |
| |
| /* Add the raw bytes of the request (e.g. header frame lengths to |
| * the logio for this request. */ |
| if (conn_ctx->request->raw_bytes && h2_c_logio_add_bytes_in) { |
| h2_c_logio_add_bytes_in(c2, conn_ctx->request->raw_bytes); |
| } |
| return OK; |
| } |
| |
| static int c2_hook_pre_connection(conn_rec *c2, void *csd) |
| { |
| h2_conn_ctx_t *conn_ctx; |
| |
| if (!c2->master || !(conn_ctx = h2_conn_ctx_get(c2)) || !conn_ctx->stream_id) { |
| return DECLINED; |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2, |
| "h2_c2(%s-%d), adding filters", |
| conn_ctx->id, conn_ctx->stream_id); |
| ap_add_input_filter_handle(c2_net_in_filter_handle, NULL, NULL, c2); |
| ap_add_output_filter_handle(c2_net_out_filter_handle, NULL, NULL, c2); |
| if (c2->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.) */ |
| c2->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 c2 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 unnecessary on a h2 stream. |
| */ |
| c2->keepalive = AP_CONN_CLOSE; |
| } |
| return OK; |
| } |
| |
| void h2_c2_register_hooks(void) |
| { |
| /* When the connection processing actually starts, we might |
| * take over, if the connection is for a h2 stream. |
| */ |
| ap_hook_pre_connection(c2_hook_pre_connection, |
| NULL, NULL, APR_HOOK_MIDDLE); |
| |
| /* We need to manipulate the standard HTTP/1.1 protocol filters and |
| * install our own. This needs to be done very early. */ |
| ap_hook_pre_read_request(c2_pre_read_request, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_post_read_request(c2_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST); |
| ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST); |
| |
| c2_net_in_filter_handle = |
| ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in, |
| NULL, AP_FTYPE_NETWORK); |
| c2_net_out_filter_handle = |
| ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out, |
| NULL, AP_FTYPE_NETWORK); |
| c2_request_in_filter_handle = |
| ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in, |
| NULL, AP_FTYPE_PROTOCOL); |
| c2_notes_out_filter_handle = |
| ap_register_output_filter("H2_C2_NOTES_OUT", h2_c2_filter_notes_out, |
| NULL, AP_FTYPE_PROTOCOL); |
| } |
| |
| #else /* AP_HAS_RESPONSE_BUCKETS */ |
| |
| static apr_status_t c2_run_pre_connection(conn_rec *c2, apr_socket_t *csd) |
| { |
| if (c2->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.) */ |
| c2->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 c2 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 unnecessary on a h2 stream. |
| */ |
| c2->keepalive = AP_CONN_CLOSE; |
| return ap_run_pre_connection(c2, csd); |
| } |
| ap_assert(c2->output_filters); |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t h2_c2_process(conn_rec *c2, apr_thread_t *thread, int worker_id) |
| { |
| h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2); |
| |
| ap_assert(conn_ctx); |
| ap_assert(conn_ctx->mplx); |
| |
| /* See the discussion at <https://github.com/icing/mod_h2/issues/195> |
| * |
| * Each conn_rec->id is supposed to be unique at a point in time. Since |
| * some modules (and maybe external code) uses this id as an identifier |
| * for the request_rec they handle, it needs to be unique for secondary |
| * connections also. |
| * |
| * The MPM module assigns the connection ids and mod_unique_id is using |
| * that one to generate identifier for requests. While the implementation |
| * works for HTTP/1.x, the parallel execution of several requests per |
| * connection will generate duplicate identifiers on load. |
| * |
| * The original implementation for secondary connection identifiers used |
| * to shift the master connection id up and assign the stream id to the |
| * lower bits. This was cramped on 32 bit systems, but on 64bit there was |
| * enough space. |
| * |
| * As issue 195 showed, mod_unique_id only uses the lower 32 bit of the |
| * connection id, even on 64bit systems. Therefore collisions in request ids. |
| * |
| * The way master connection ids are generated, there is some space "at the |
| * top" of the lower 32 bits on allmost all systems. If you have a setup |
| * with 64k threads per child and 255 child processes, you live on the edge. |
| * |
| * The new implementation shifts 8 bits and XORs in the worker |
| * id. This will experience collisions with > 256 h2 workers and heavy |
| * load still. There seems to be no way to solve this in all possible |
| * configurations by mod_h2 alone. |
| */ |
| c2->id = (c2->master->id << 8)^worker_id; |
| |
| if (!conn_ctx->pre_conn_done) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c2, |
| "h2_c2(%s-%d), adding filters", |
| conn_ctx->id, conn_ctx->stream_id); |
| ap_add_input_filter("H2_C2_NET_IN", NULL, NULL, c2); |
| ap_add_output_filter("H2_C2_NET_CATCH_H1", NULL, NULL, c2); |
| ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2); |
| |
| c2_run_pre_connection(c2, ap_get_conn_socket(c2)); |
| conn_ctx->pre_conn_done = 1; |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2, |
| "h2_c2(%s-%d): process connection", |
| conn_ctx->id, conn_ctx->stream_id); |
| |
| c2->current_thread = thread; |
| ap_run_process_connection(c2); |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2, |
| "h2_c2(%s-%d): processing done", |
| conn_ctx->id, conn_ctx->stream_id); |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t c2_process(h2_conn_ctx_t *conn_ctx, conn_rec *c) |
| { |
| const h2_request *req = conn_ctx->request; |
| conn_state_t *cs = c->cs; |
| request_rec *r; |
| const char *tenc; |
| apr_time_t timeout; |
| |
| r = h2_create_request_rec(conn_ctx->request, c, conn_ctx->beam_in == NULL); |
| if (!r) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "h2_c2(%s-%d): create request_rec failed, r=NULL", |
| conn_ctx->id, conn_ctx->stream_id); |
| goto cleanup; |
| } |
| if (r->status != HTTP_OK) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "h2_c2(%s-%d): create request_rec failed, r->status=%d", |
| conn_ctx->id, conn_ctx->stream_id, r->status); |
| goto cleanup; |
| } |
| |
| tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); |
| conn_ctx->input_chunked = tenc && ap_is_chunked(r->pool, tenc); |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "h2_c2(%s-%d): created request_rec for %s", |
| conn_ctx->id, conn_ctx->stream_id, r->the_request); |
| conn_ctx->server = r->server; |
| timeout = h2_config_geti64(r, r->server, H2_CONF_STREAM_TIMEOUT); |
| if (timeout <= 0) { |
| timeout = r->server->timeout; |
| } |
| h2_conn_ctx_set_timeout(conn_ctx, timeout); |
| |
| if (h2_config_sgeti(conn_ctx->server, H2_CONF_COPY_FILES)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "h2_mplx(%s-%d): copy_files in output", |
| conn_ctx->id, conn_ctx->stream_id); |
| h2_beam_set_copy_files(conn_ctx->beam_out, 1); |
| } |
| |
| ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r); |
| if (cs) { |
| cs->state = CONN_STATE_HANDLER; |
| } |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "h2_c2(%s-%d): start process_request", |
| conn_ctx->id, conn_ctx->stream_id); |
| |
| /* Add the raw bytes of the request (e.g. header frame lengths to |
| * the logio for this request. */ |
| if (req->raw_bytes && h2_c_logio_add_bytes_in) { |
| h2_c_logio_add_bytes_in(c, req->raw_bytes); |
| } |
| |
| ap_process_request(r); |
| /* After the call to ap_process_request, the |
| * request pool may have been deleted. */ |
| r = NULL; |
| if (conn_ctx->beam_out) { |
| h2_beam_close(conn_ctx->beam_out, c); |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "h2_c2(%s-%d): process_request done", |
| conn_ctx->id, conn_ctx->stream_id); |
| if (cs) |
| cs->state = CONN_STATE_WRITE_COMPLETION; |
| |
| cleanup: |
| return APR_SUCCESS; |
| } |
| |
| conn_rec *h2_c2_create(conn_rec *c1, apr_pool_t *parent, |
| apr_bucket_alloc_t *buckt_alloc) |
| { |
| apr_pool_t *pool; |
| conn_rec *c2; |
| void *cfg; |
| |
| ap_assert(c1); |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c1, |
| "h2_c2: create for c1(%ld)", c1->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 |
| * independent of its parent pool in the sense that it can work in |
| * another thread. |
| */ |
| apr_pool_create(&pool, parent); |
| apr_pool_tag(pool, "h2_c2_conn"); |
| |
| c2 = (conn_rec *) apr_palloc(pool, sizeof(conn_rec)); |
| memcpy(c2, c1, sizeof(conn_rec)); |
| |
| c2->master = c1; |
| c2->pool = pool; |
| c2->conn_config = ap_create_conn_config(pool); |
| c2->notes = apr_table_make(pool, 5); |
| c2->input_filters = NULL; |
| c2->output_filters = NULL; |
| c2->keepalives = 0; |
| #if AP_MODULE_MAGIC_AT_LEAST(20180903, 1) |
| c2->filter_conn_ctx = NULL; |
| #endif |
| c2->bucket_alloc = apr_bucket_alloc_create(pool); |
| #if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1) |
| c2->data_in_input_filters = 0; |
| c2->data_in_output_filters = 0; |
| #endif |
| /* prevent mpm_event from making wrong assumptions about this connection, |
| * like e.g. using its socket for an async read check. */ |
| c2->clogging_input_filters = 1; |
| c2->log = NULL; |
| c2->aborted = 0; |
| /* We cannot install the master connection socket on the secondary, as |
| * modules mess with timeouts/blocking of the socket, with |
| * unwanted side effects to the master connection processing. |
| * Fortunately, since we never use the secondary socket, we can just install |
| * a single, process-wide dummy and everyone is happy. |
| */ |
| ap_set_module_config(c2->conn_config, &core_module, dummy_socket); |
| /* TODO: these should be unique to this thread */ |
| c2->sbh = NULL; /*c1->sbh;*/ |
| /* TODO: not all mpm modules have learned about secondary connections yet. |
| * copy their config from master to secondary. |
| */ |
| if (mpm_module) { |
| cfg = ap_get_module_config(c1->conn_config, mpm_module); |
| ap_set_module_config(c2->conn_config, mpm_module, cfg); |
| } |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2, |
| "h2_c2(%s): created", c2->log_id); |
| return c2; |
| } |
| |
| static int h2_c2_hook_post_read_request(request_rec *r) |
| { |
| h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(r->connection); |
| |
| if (conn_ctx && conn_ctx->stream_id) { |
| |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, |
| "h2_c2(%s-%d): adding request filters", |
| conn_ctx->id, conn_ctx->stream_id); |
| |
| /* setup the correct filters to process the request for h2 */ |
| ap_add_input_filter("H2_C2_REQUEST_IN", NULL, r, r->connection); |
| |
| /* 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_add_output_filter("H2_C2_RESPONSE_OUT", NULL, r, r->connection); |
| ap_add_output_filter("H2_C2_TRAILERS_OUT", NULL, r, r->connection); |
| } |
| return DECLINED; |
| } |
| |
| static int h2_c2_hook_process(conn_rec* c) |
| { |
| h2_conn_ctx_t *ctx; |
| |
| if (!c->master) { |
| return DECLINED; |
| } |
| |
| ctx = h2_conn_ctx_get(c); |
| if (ctx->stream_id) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "h2_h2, processing request directly"); |
| c2_process(ctx, c); |
| return DONE; |
| } |
| else { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "secondary_conn(%ld): no h2 stream assing?", c->id); |
| } |
| return DECLINED; |
| } |
| |
| void h2_c2_register_hooks(void) |
| { |
| /* When the connection processing actually starts, we might |
| * take over, if the connection is for a h2 stream. |
| */ |
| ap_hook_process_connection(h2_c2_hook_process, |
| NULL, NULL, APR_HOOK_FIRST); |
| /* We need to manipulate the standard HTTP/1.1 protocol filters and |
| * install our own. This needs to be done very early. */ |
| ap_hook_post_read_request(h2_c2_hook_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST); |
| ap_hook_fixups(c2_hook_fixups, NULL, NULL, APR_HOOK_LAST); |
| |
| ap_register_input_filter("H2_C2_NET_IN", h2_c2_filter_in, |
| NULL, AP_FTYPE_NETWORK); |
| ap_register_output_filter("H2_C2_NET_OUT", h2_c2_filter_out, |
| NULL, AP_FTYPE_NETWORK); |
| ap_register_output_filter("H2_C2_NET_CATCH_H1", h2_c2_filter_catch_h1_out, |
| NULL, AP_FTYPE_NETWORK); |
| |
| ap_register_input_filter("H2_C2_REQUEST_IN", h2_c2_filter_request_in, |
| NULL, AP_FTYPE_PROTOCOL); |
| ap_register_output_filter("H2_C2_RESPONSE_OUT", h2_c2_filter_response_out, |
| NULL, AP_FTYPE_PROTOCOL); |
| ap_register_output_filter("H2_C2_TRAILERS_OUT", h2_c2_filter_trailers_out, |
| NULL, AP_FTYPE_PROTOCOL); |
| } |
| |
| #endif /* else AP_HAS_RESPONSE_BUCKETS */ |