943 lines
33 KiB
C
943 lines
33 KiB
C
/* 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_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;
|
|
|
|
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 <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 = 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 */
|