/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_ws.h" #include "h2_c2.h" #include "h2_util.h" #include "mod_http2.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); } static void h2_c2_log_io(conn_rec *c2, apr_off_t bytes_sent) { if (bytes_sent && h2_c_logio_add_bytes_out) { h2_c_logio_add_bytes_out(c2, bytes_sent); } } void h2_c2_destroy(conn_rec *c2) { h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c2); ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, c2, "h2_c2(%s): destroy", c2->log_id); if(!c2->aborted && conn_ctx && conn_ctx->bytes_sent) { h2_c2_log_io(c2, conn_ctx->bytes_sent); } 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(!c2->aborted && conn_ctx->bytes_sent) { h2_c2_log_io(c2, conn_ctx->bytes_sent); } 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 */ unsigned did_upgrade_eos:1; /* for Upgrade, we added an extra EOS */ } 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); } } /* If this is a HTTP Upgrade, it means the request we process * has not Content, although the stream is not necessarily closed. * On first read, we insert an EOS to signal processing that it * has the complete body. */ if (conn_ctx->is_upgrade && !fctx->did_upgrade_eos) { b = apr_bucket_eos_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(fctx->bb, b); fctx->did_upgrade_eos = 1; } 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 = 0; apr_status_t rv; rv = h2_beam_send(conn_ctx->beam_out, c2, bb, APR_BLOCK_READ, &written); if (APR_STATUS_IS_EAGAIN(rv)) { rv = APR_SUCCESS; } 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; if (bb == NULL) { #if !AP_MODULE_MAGIC_AT_LEAST(20180720, 1) f->c->data_in_output_filters = 0; #endif return APR_SUCCESS; } 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 int addn_headers(void *udata, const char *name, const char *value) { apr_table_t *dest = udata; apr_table_addn(dest, name, value); return 1; } static void check_early_hints(request_rec *r, const char *tag) { apr_array_header_t *push_list = h2_config_push_list(r); apr_table_t *early_headers = h2_config_early_headers(r); if (!r->expecting_100 && ((push_list && push_list->nelts > 0) || (early_headers && !apr_is_empty_table(early_headers)))) { int have_hints = 0, i; if (push_list && push_list->nelts > 0) { 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" : "")); } have_hints = 1; } if (early_headers && !apr_is_empty_table(early_headers)) { apr_table_do(addn_headers, r->headers_out, early_headers, NULL); have_hints = 1; } if (have_hints) { int old_status; const char *old_line; if (h2_config_rgeti(r, H2_CONF_PUSH) == 0 && h2_config_sgeti(r->server, H2_CONF_PUSH) != 0) { apr_table_setn(r->connection->notes, H2_PUSH_MODE_NOTE, "0"); } 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_early_hints(r, "late_fixup"); return DECLINED; } static apr_status_t http2_get_pollfd_from_conn(conn_rec *c, struct apr_pollfd_t *pfd, apr_interval_time_t *ptimeout) { #if H2_USE_PIPES if (c->master) { h2_conn_ctx_t *ctx = h2_conn_ctx_get(c); if (ctx) { if (ctx->beam_in && ctx->pipe_in[H2_PIPE_OUT]) { pfd->desc_type = APR_POLL_FILE; pfd->desc.f = ctx->pipe_in[H2_PIPE_OUT]; if (ptimeout) *ptimeout = h2_beam_timeout_get(ctx->beam_in); } else { /* no input */ pfd->desc_type = APR_NO_DESC; if (ptimeout) *ptimeout = -1; } return APR_SUCCESS; } } #else (void)c; (void)pfd; (void)ptimeout; #endif /* H2_USE_PIPES */ return APR_ENOTIMPL; } #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); #if H2_USE_POLLFD_FROM_CONN ap_hook_get_pollfd_from_conn(http2_get_pollfd_from_conn, NULL, NULL, APR_HOOK_MIDDLE); #endif APR_REGISTER_OPTIONAL_FN(http2_get_pollfd_from_conn); 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 * * 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 = NULL; const char *tenc; apr_time_t timeout; apr_status_t rv = APR_SUCCESS; if (req->protocol && !strcmp("websocket", req->protocol)) { req = h2_ws_rewrite_request(req, c, conn_ctx->beam_in == NULL); if (!req) { rv = APR_EGENERAL; goto cleanup; } } r = h2_create_request_rec(req, 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 rv; } 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_is_initial_req(r)) { 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); #if H2_USE_POLLFD_FROM_CONN ap_hook_get_pollfd_from_conn(http2_get_pollfd_from_conn, NULL, NULL, APR_HOOK_MIDDLE); #endif APR_REGISTER_OPTIONAL_FN(http2_get_pollfd_from_conn); 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 */