mod_http3/src/h3_filter.c¶
Functions¶
| Name | |
|---|---|
| apr_status_t | h3_filter_out(ap_filter_t * f, apr_bucket_brigade * bb) |
| apr_status_t | input_filter_eos(ap_filter_t * f, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) |
| apr_status_t | h3_filter_in(ap_filter_t * f, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) |
| apr_status_t | serve_request_body(ap_filter_t * f, h3_stream * h3s, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e , apr_off_t readbytes) |
| apr_status_t | capture_body_bucket(request_rec * r, h3_conn_ctx_t * ctx, apr_bucket * b) |
| apr_status_t | h3_filter_out_proto(ap_filter_t * f, apr_bucket_brigade * bb) |
| apr_status_t | h3_filter_in_proto(ap_filter_t * f, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) |
Attributes¶
| Name | |
|---|---|
| ap_filter_rec_t * | h3_net_in_filter_handle |
| ap_filter_rec_t * | h3_net_out_filter_handle |
| ap_filter_rec_t * | h3_proto_out_filter_handle |
| ap_filter_rec_t * | h3_proto_in_filter_handle |
Functions Documentation¶
function h3_filter_out¶
Parameters:
- f The filter handle (unused).
- bb The outbound brigade to dispose of.
Return: APR_SUCCESS.
Network-layer output filter callback. Discards the brigade (the response is captured by the protocol-layer filter and submitted to nghttp3).
function input_filter_eos¶
static apr_status_t input_filter_eos(
ap_filter_t * f,
apr_bucket_brigade * bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes
)
function h3_filter_in¶
apr_status_t h3_filter_in(
ap_filter_t * f,
apr_bucket_brigade * bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes
)
Return: APR_SUCCESS (with EOS bucket inserted) or APR_EOF.
Network-layer input filter callback. Unconditional EOS stub.
function serve_request_body¶
static apr_status_t serve_request_body(
ap_filter_t * f,
h3_stream * h3s,
apr_bucket_brigade * bb,
ap_input_mode_t mode,
apr_read_type_e ,
apr_off_t readbytes
)
function capture_body_bucket¶
function h3_filter_out_proto¶
Parameters:
- f The filter handle.
- bb The outbound brigade.
Return: APR_SUCCESS, or an APR error if pool/brigade access fails.
Protocol-layer output filter callback. Captures the response into the per-request h3_conn_ctx_t. In the default mode it submits headers early and feeds body bytes through a bounded per-stream queue with backpressure. When H3MaxResponseBodySize is finite, it retains bounded whole-response capture so an over-limit response can be replaced before transmission.
function h3_filter_in_proto¶
apr_status_t h3_filter_in_proto(
ap_filter_t * f,
apr_bucket_brigade * bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes
)
Return: APR_SUCCESS, with data and/or an EOS bucket inserted into bb.
Protocol-layer input filter callback. Serves the request body.
Attributes Documentation¶
variable h3_net_in_filter_handle¶
variable h3_net_out_filter_handle¶
variable h3_proto_out_filter_handle¶
variable h3_proto_in_filter_handle¶
Source code¶
/*
* Copyright (c) 2026 The mod_http3 Project Authors. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <httpd.h>
#include <http_config.h>
#include <http_connection.h>
#include <http_core.h>
#include <http_log.h>
#include <http_protocol.h>
#include <http_request.h>
#include <apr_buckets.h>
#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_tables.h>
#include <string.h>
#include <nghttp3/nghttp3.h>
#include "h3.h"
#include "h3_check.h"
#include "h3_config.h"
#include "h3_filter.h"
#include "h3_request.h"
#include "h3_session.h"
#include "mod_http3.h"
ap_filter_rec_t* h3_net_in_filter_handle;
ap_filter_rec_t* h3_net_out_filter_handle;
ap_filter_rec_t* h3_proto_out_filter_handle;
ap_filter_rec_t* h3_proto_in_filter_handle;
apr_status_t h3_filter_out(ap_filter_t* /*f*/, apr_bucket_brigade* bb)
{
CHECK(bb);
apr_brigade_cleanup(bb);
return APR_SUCCESS;
}
static apr_status_t input_filter_eos(ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
{
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE)
{
if (!f->next)
{
return APR_EOF;
}
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
if (!APR_BRIGADE_EMPTY(bb))
{
return APR_SUCCESS;
}
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc));
return APR_SUCCESS;
}
apr_status_t h3_filter_in(ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
{
return input_filter_eos(f, bb, mode, block, readbytes);
}
/* Serve request body. */
static apr_status_t serve_request_body(ap_filter_t* f, h3_stream* h3s, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e /*block*/, apr_off_t readbytes)
{
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "serve_request_body started, h3s=%p", (void*)h3s);
apr_bucket_alloc_t* ba = f->c->bucket_alloc;
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE && mode != AP_MODE_EXHAUSTIVE && mode != AP_MODE_SPECULATIVE)
{
/* AP_MODE_INIT, AP_MODE_EATCRLF: nothing for us to do. */
return APR_SUCCESS;
}
apr_size_t avail = h3s->request_body_len - h3s->request_body_offset;
if (avail == 0)
{
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(ba));
return APR_SUCCESS;
}
const uint8_t* unread = h3s->request_body + h3s->request_body_offset;
apr_size_t want = avail;
if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE)
{
if (readbytes > 0 && (apr_size_t)readbytes < want)
{
want = (apr_size_t)readbytes;
}
}
else if (mode == AP_MODE_GETLINE)
{
const void* nl = memchr(unread, '\n', avail);
if (nl)
{
want = (apr_size_t)((const uint8_t*)nl - unread) + 1;
}
}
/* AP_MODE_EXHAUSTIVE: take everything remaining, as already set above. */
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "serve_request_body: unread=%p, want=%" APR_SIZE_T_FMT, (void*)unread, want);
apr_bucket* b = apr_bucket_pool_create((const char*)unread, want, h3s->pool, ba);
APR_BRIGADE_INSERT_TAIL(bb, b);
if (mode != AP_MODE_SPECULATIVE)
{
h3s->request_body_offset += want;
if (h3s->request_body_offset >= h3s->request_body_len)
{
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(ba));
}
}
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "serve_request_body done, want=%" APR_SIZE_T_FMT ", offset=%" APR_SIZE_T_FMT, want, h3s->request_body_offset);
return APR_SUCCESS;
}
static apr_status_t capture_body_bucket(request_rec* r, h3_conn_ctx_t* ctx, apr_bucket* b)
{
CHECK(ctx);
CHECK(b);
if (ctx->response_too_large)
{
return APR_SUCCESS;
}
const char* data = NULL;
apr_size_t len = 0;
if (apr_bucket_read(b, &data, &len, APR_BLOCK_READ) != APR_SUCCESS || !data || !len)
{
return APR_SUCCESS;
}
/* Read from the H3-owning vhost (ctx->s), not r->server: only it is defaulted in h3_post_config. */
h3_server_conf* conf = ap_get_module_config(ctx->s->module_config, &http3_module);
apr_size_t limit = conf ? conf->h3_max_response_body_size : H3_MAX_RESPONSE_BODY_SIZE_DEFAULT;
if (ctx->dataheaplen > limit || len > limit - ctx->dataheaplen)
{
ctx->response_too_large = 1;
ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "HTTP/3 response body for %s exceeds H3MaxResponseBodySize (%" APR_SIZE_T_FMT " bytes); aborting response with 500", r->uri, limit);
return APR_SUCCESS;
}
apr_size_t new_len = ctx->dataheaplen + len;
if (new_len > ctx->dataheapcap)
{
apr_size_t new_cap = ctx->dataheapcap ? ctx->dataheapcap * 2 : H3_BODY_BUF_INIT_CAP;
while (new_cap < new_len)
{
new_cap *= 2;
}
char* new_heap = apr_palloc(ctx->c3reqpool, new_cap);
if (ctx->dataheaplen > 0)
{
memcpy(new_heap, ctx->dataheap, ctx->dataheaplen);
}
ctx->dataheap = new_heap;
ctx->dataheapcap = new_cap;
}
memcpy(ctx->dataheap + ctx->dataheaplen, data, len);
ctx->dataheaplen = new_len;
return APR_SUCCESS;
}
apr_status_t h3_filter_out_proto(ap_filter_t* f, apr_bucket_brigade* bb)
{
CHECK(f);
CHECK(bb);
h3_conn_ctx_t* ctx = (h3_conn_ctx_t*)f->ctx;
if (!ctx || f->r->main != NULL)
{
return ap_pass_brigade(f->next, bb);
}
for (apr_bucket* b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb);)
{
apr_bucket* next = APR_BUCKET_NEXT(b);
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "h3_filter_out_proto: received bucket type=%s length=%" APR_SIZE_T_FMT, b->type->name, b->length);
if (AP_BUCKET_IS_ERROR(b))
{
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "h3_filter_out_proto: generating error response");
ap_send_error_response(f->r, 0);
return OK;
}
if (AP_BUCKET_IS_RESPONSE(b))
{
ctx->resp = b->data;
if (ctx->resp->headers)
{
apr_table_t* dup = apr_table_make(ctx->c3reqpool, apr_table_elts(ctx->resp->headers)->nelts);
const apr_array_header_t* src_arr = apr_table_elts(ctx->resp->headers);
const apr_table_entry_t* src = (const apr_table_entry_t*)src_arr->elts;
for (int i = 0; i < src_arr->nelts; i++)
{
if (src[i].key && src[i].val)
{
apr_table_add(dup, apr_pstrdup(ctx->c3reqpool, src[i].key), apr_pstrdup(ctx->c3reqpool, src[i].val));
}
}
ctx->resp->headers = dup;
}
ctx->resp->pool = ctx->c3reqpool;
APR_BUCKET_REMOVE(b);
if (ctx->streaming)
{
apr_status_t rv = h3_response_start(f->r, ctx);
if (rv != APR_SUCCESS)
{
apr_brigade_cleanup(bb);
return rv;
}
}
}
else if (!APR_BUCKET_IS_METADATA(b))
{
apr_status_t rv;
if (ctx->streaming)
{
rv = h3_response_start(f->r, ctx);
if (rv == APR_SUCCESS)
{
const char* data = NULL;
apr_size_t len = 0;
rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
if (rv == APR_SUCCESS && data && len > 0)
{
rv = h3_stream_response_append(ctx->stream, (const uint8_t*)data, len);
}
}
}
else
{
rv = capture_body_bucket(f->r, ctx, b);
}
if (rv != APR_SUCCESS)
{
apr_brigade_cleanup(bb);
return rv;
}
next = APR_BUCKET_NEXT(b);
}
else if (ctx->streaming && (APR_BUCKET_IS_EOS(b) || APR_BUCKET_IS_FLUSH(b)))
{
apr_status_t rv = h3_response_start(f->r, ctx);
if (rv != APR_SUCCESS)
{
apr_brigade_cleanup(bb);
return rv;
}
if (APR_BUCKET_IS_EOS(b))
{
h3_stream_response_complete(ctx->stream);
}
}
b = next;
}
apr_brigade_cleanup(bb);
return OK;
}
apr_status_t h3_filter_in_proto(ap_filter_t* f, apr_bucket_brigade* bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes)
{
ap_log_error(APLOG_MARK, APLOG_INFO, 0, f->c->base_server, "h3_filter_in_proto called, readbytes=%" APR_OFF_T_FMT, readbytes);
h3_conn_ctx_t* ctx = (h3_conn_ctx_t*)ap_get_module_config(f->r->request_config, &http3_module);
h3_stream* h3s = ctx ? ctx->stream : NULL;
if (!h3s)
{
return input_filter_eos(f, bb, mode, block, readbytes);
}
return serve_request_body(f, h3s, bb, mode, block, readbytes);
}