Skip to content

HTTP/3 impl - #3985

Draft
Rafael-Conde wants to merge 41 commits into
hyperium:http3from
Rafael-Conde:rafael/http3-impl
Draft

Rafael-Conde wants to merge 41 commits into
hyperium:http3from
Rafael-Conde:rafael/http3-impl

Conversation

@Rafael-Conde

Copy link
Copy Markdown

I'm creating this as a draft to make my attempt so far available.

I haven't done much but I already got a lot of questions, I got time in my hands and I would like to keep contributing, but I would appreciate feedback to whether I'm going in the right direction or not and how could I improve my current attempt.

I'm looking to improve myself and the contribution in general!

I intend to keep working on it and make more changes available as I feel like input could greatly benefit the end result.

@Rafael-Conde

Copy link
Copy Markdown
Author

@seanmonstar , I'm making my current implementation available here. Let me know if there is a better way do to this.

I'm making this changes available to get feedback and improve the implementation!

any feedback is welcome!

@Rafael-Conde

Copy link
Copy Markdown
Author

I plan on keep working on this and make more changes available as soon as I can

@indramal

indramal commented Jan 3, 2026

Copy link
Copy Markdown

May be this also will help you - https://crates.io/crates/axum-h3 axum-h3 - crates.io: Rust Package Registry

dswij and others added 10 commits August 10, 2026 11:19
…ad fast path (hyperium#4147)

`is_complete_fast` recognizes `\r\n\r\n` and `\n\n` as head terminators but not `\n\r\n`, while the full parser (httparse) accepts all three. So a request whose head ends with `\n\r\n` parses fine when it arrives in a single read, but stalls when it arrives split across reads: the fast path never reports the head complete and the connection keeps waiting for more bytes.

This extends the `\n` branch to also accept a following `\r\n`, using the same panic-safe slicing idiom as the `\r` branch. Added the `\n\r\n` witness and the `\n\r` negative to `test_is_complete_fast`, plus a parse-level test documenting that the full parser accepts this terminator.

Closes hyperium#4145
Show how to process data and trailer frames while preserving back-pressure, and contrast that with intentionally collecting a bounded body in memory.

Closes hyperium#2201
…rium#4167)

The following lints no longer exist in recent clippy versions and cause
`unknown lint` errors (promoted to errors by `-D warnings` / `-W unknown-lints`):

- clippy::unnecessary_rest_pattern
- clippy::rest_pattern_accessible_field
- clippy::definition_in_module_root

Remove them from the allowed lints list in Cargo.toml.
seanmonstar and others added 9 commits August 28, 2026 08:21
The previous channel variant for `Incoming` used several individual channels to
handle the various mechanisms: sending data, signaling error/abort, sending
trailers, waiting for optional expect-101 polling. Each individual channel would
allocate its own internal shared state in an Arc.

Now the `Incoming` channel variant uses a single combined implementation,
putting all the required shared pieces in a single Arc. This resulted in less
allocations, and less places to potentially store wakers.

The following performance improvements were noticed with microbenchmarks:

| Benchmark       | Before | After      | Improvement |
| --------------- | ------ | ---------- | ----------- |
| Create/drop     | 400 ns | 130–139 ns | ~66%        |
| Data handoff    | 229 ns | 147–155 ns | ~34%        |
| Expect-101 wait | 492 ns | 267–298 ns | ~40–46%     |
i stumbled across this while investigating a solution to hyperium#3914.

in hyperium#2798, a documentation test was introduced that appears to have been broken
since its inception. after spending some time figuring out how to run this
documentation test, i found the following errors:

```
; RUSTDOCFLAGS='--cfg hyper_unstable_ffi' RUSTFLAGS='--cfg hyper_unstable_ffi' cargo test --doc --features client,server,http1,http2,ffi get_in_order
   Compiling hyper v1.11.1 (/hyper)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.39s
   Doc-tests hyper

running 1 test
test src/ext/mod.rs - ext::OriginalHeaderOrder::get_in_order (line 262) - compile ... FAILED

failures:

---- src/ext/mod.rs - ext::OriginalHeaderOrder::get_in_order (line 262) stdout ----
error: expected item, found keyword `let`
   --> src/ext/mod.rs:266:1
    |
266 | let mut h_order = OriginalHeaderOrder::default();
    | ^^^ `let` cannot be used for global variables
    |
    = help: consider using `static` and a `Mutex` instead of `let mut`
    = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>

error: aborting due to 1 previous error

Couldn't compile the test.

failures:
    src/ext/mod.rs - ext::OriginalHeaderOrder::get_in_order (line 262)
```

after wrapping the code in the example in a `fn main() {}` block:

```
; RUSTDOCFLAGS='--cfg hyper_unstable_ffi' RUSTFLAGS='--cfg hyper_unstable_ffi' cargo test --doc --features client,server,http1,http2,ffi get_in_order
   Compiling hyper v1.11.1 (/hyper)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.37s
   Doc-tests hyper

running 1 test
test src/ext/mod.rs - ext::OriginalHeaderOrder::get_in_order (line 262) - compile ... FAILED

failures:

---- src/ext/mod.rs - ext::OriginalHeaderOrder::get_in_order (line 262) stdout ----
error: expected `;`, found keyword `let`
   --> src/ext/mod.rs:284:22
    |
284 | h_order.append(name3)
    |                      ^ help: add `;` here
285 |
286 | let mut iter = h_order.get_in_order()
    | --- unexpected token

error: expected `;`, found keyword `let`
   --> src/ext/mod.rs:286:38
    |
286 | let mut iter = h_order.get_in_order()
    |                                      ^ help: add `;` here
287 |
288 | let (name, idx) = iter.next();
    | --- unexpected token

error[E0603]: struct `OriginalHeaderOrder` is private
   --> src/ext/mod.rs:264:17
    |
264 | use hyper::ext::OriginalHeaderOrder;
    |                 ^^^^^^^^^^^^^^^^^^^ private struct
    |
note: the struct `OriginalHeaderOrder` is defined here
   --> src/ext/mod.rs:204:1
    |
204 | pub(crate) struct OriginalHeaderOrder {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0433]: cannot find type `Headermap` in this scope
   --> src/ext/mod.rs:269:17
    |
269 | let mut h_map = Headermap::new();
    |                 ^^^^^^^^^ use of undeclared type `Headermap`
    |
help: a struct with a similar name exists
    |
269 - let mut h_map = Headermap::new();
269 + let mut h_map = HeaderMap::new();
    |

error: aborting due to 4 previous errors

Some errors have detailed explanations: E0433, E0603.
For more information about an error, try `rustc --explain E0433`.
Couldn't compile the test.

failures:
    src/ext/mod.rs - ext::OriginalHeaderOrder::get_in_order (line 262)
```

after addressing these errors, i observed:

```
error[E0277]: the trait bound `&[u8; 10]: IntoHeaderName` is not satisfied
    --> src/ext/mod.rs:273:14
     |
 273 | h_map.append(name1);
     |       ------ ^^^^^ the trait `IntoHeaderName` is not implemented for `&[u8; 10]`
     |       |
     |       required by a bound introduced by this call
     |
help: the following other types implement trait `IntoHeaderName`
    --> /.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/http-1.3.1/src/header/map.rs:3690:5
     |
3690 |     impl IntoHeaderName for HeaderName {}
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `HeaderName`
...
3712 |     impl<'a> IntoHeaderName for &'a HeaderName {}
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `&'a HeaderName`
...
3734 |     impl IntoHeaderName for &'static str {}
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `&'static str`
note: required by a bound in `HeaderMap::<T>::append`
    --> /.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/http-1.3.1/src/header/map.rs:1373:12
     |
1371 |     pub fn append<K>(&mut self, key: K, value: T) -> bool
     |            ------ required by a bound in this associated function
1372 |     where
1373 |         K: IntoHeaderName,
     |            ^^^^^^^^^^^^^^ required by this bound in `HeaderMap::<T>::append`

error[E0061]: this method takes 2 arguments but 1 argument was supplied
    --> src/ext/mod.rs:273:7
     |
 273 | h_map.append(name1);
     |       ^^^^^^------- argument hyperium#2 of type `HeaderValue` is missing
     |
note: method defined here
    --> /.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/http-1.3.1/src/header/map.rs:1371:12
     |
1371 |     pub fn append<K>(&mut self, key: K, value: T) -> bool
     |            ^^^^^^
help: provide the argument
     |
 273 | h_map.append(name1, /* HeaderValue */);
     |                   +++++++++++++++++++
```

because these errors point to missing arguments and incorrect types, this
commit removes this example altogether. i'm not convinced it was ever run.

Signed-off-by: katelyn martin <git@katelyn.world>
tottoto and others added 22 commits September 11, 2026 16:27
…erium#4196)

This adjusts hyperium#4110 to use `append` instead of `insert` when a nonzero number of "connection" headers are already present, and only if none of the existing headers already include the value being added.

Fixes hyperium#4195
…um#4198)

The send task of an HTTP/2 `Upgraded` stream reserved one byte of capacity at the top of
every loop, before any write was queued. As with `PipeToSendStream` before hyperium#4061, an idle
tunnel then pins the last byte of the connection-level window, and a second stream can
deadlock against peers that only send WINDOW_UPDATE once their window is exhausted.

Pull the next write from the channel first, hold it until h2 has capacity, and only then
reserve. The next write is still not pulled before the held one is handed to h2, so the
writer keeps seeing backpressure. The task now always polls the channel when it holds
nothing, which makes the separate close notification unnecessary, so remove it.

Refs hyperium#4003
- capi/README.md: "staring with" -> "starting with" in the build instructions
- docs/MSRV.md: "Minimum Support Rust Version" -> "Minimum Supported Rust Version"
- docs/GOVERNANCE.md: "elsehwere" -> "elsewhere"
…wlist (hyperium#4220)

Fix the two remaining violations in src/proto/h1/decode.rs by inlining
the header variable into the format string, which also satisfies the
uninlined_format_args lint that the old &header borrow was dodging.

Refs: hyperium#4071
…m#4183)

Adds max_header_size(val: usize) on both server and client HTTP/1 builders.
This allows configuring an explicit byte limit for request/response headers
(including the start line and chunked trailers).

When the limit is exceeded:
- On server: responds with 431 Request Header Fields Too Large and closes the connection.
- On client: returns a Parse::TooLarge error (is_parse_too_large).
- On chunked decoder: replaces existing TODO in proto/h1/conn.rs with the configured h1_max_header_size.

Closes hyperium#3832.
…yperium#4201)

When parsing a message with more than one `Connection` header line, the
per-line logic keyed the branch off the current value of `keep_alive`, so
a line that set `keep_alive` to false made the next line take the HTTP/1.0
arm and set it back to true. `Connection: close` followed by
`Connection: keep-alive` therefore parsed as keep-alive, while the reverse
order parsed correctly.

Track whether any `Connection` line carried a `close` token and let that
win for the rest of the loop, in both `Server::parse` and `Client::parse`.
HTTP/1.0 messages that opt in with `Connection: keep-alive` are unchanged.
…request (hyperium#4208)

The Receiver signals want when its queue reports Pending, and
SendRequest::is_ready reports that want. A want signaled after finding the
queue empty can land after the Sender's give() for the request that follows,
and tokio's coop budget can make the queue report Pending with a request
already queued. Either way the request is taken with want set, and the
connection reports ready until the response completes. hyper-util's legacy
pool returns connections on is_ready, so it hands a busy connection to the
next request, which then waits behind the whole response.

Withdraw the want with want::Taker::unwant when a request is taken. Want is
signaled again once the dispatcher is idle.

Requires want with Taker::unwant (seanmonstar/want#6).
- Make sure the bare links render as clickable.
- Add a note to refrain from using AI/LLMs when writing a HIP.
…rium#4222)

Keep the closed dispatch receiver alive until all in-progress sends have published and canceled their callbacks

Preserve the connection result across drain polls and keep upgrade handoffs unchanged

Closes hyperium#4202
…hyperium#4229)

This should be able to run automatically even without our approving of new
contributors PRs, so they will hopefully be alerted before we have to read
some generated PR description that is overly long.

Organization members are skipped, since there is already trust established.
see hyperium#4071.

this commit addresses the `let_unit_value` clippy lint. only two sites
are affected, relating to tracing spans.

this commit introduces a `hyper::trace::Disabled` type to act as a ZST
marker in places that can sometimes conditionally compile to a
`tracing::span::Entered` guard.

this is based upon experimenting in
<https://github.com/cratelyn/hyper-span-macro-example>.

to restate some of those learnings here:

 ### background

hyper contains a `trace` submodule, introduced in hyperium#3326,
that contains macros like the following:

```rust
macro_rules! trace {
    ($($arg:tt)*) => {
        #[cfg(feature = "tracing")]
        {
            tracing::trace!($($arg)+);
        }
    }
}

macro_rules! trace_span {
    ($($arg:tt)*) => {
        {
            #[cfg(feature = "tracing")]
            {
                let _span = tracing::trace_span!($($arg)+);
                _span.entered()
            }
        }
    }
}
```

these thinly wrap `tracing`'s macros, to account for the fact that tracing
support in hyper is opt-in. it is gated behind a feature flag, and
additionally mandates that the `hyper_unstable_tracing` flag be enabled.

however, this leads to violations of clippy's `let_unit_value` rule when
compiled without the `tracing` feature flag is enabled. this is because
`trace_span!` only returns a `tracing::span::Entered` when that flag is active,
otherwise it returns `()`.

thus, we *must* bind these calls to a variable, so that we properly hold
on to the span guard when the tracing flag is active. however, we must
contend with the fact that when this flag is not active, strict clippy
linting yields us warnings like this:

```text
cargo clippy --no-default-features
    Checking hyper-span-macro-example v0.1.0
error: this let-binding has unit value
  --> src/main.rs:52:5
   |
52 |     let _entered = trace_span!("instrumented");
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#let_unit_value
```

 ### why not use `#[allow(..)]`

one might try to selectively apply `#[allow(clippy::let_unit_value)]`
attributes to stifle this.

however, applying attributes to expressions is still a nightly feature
we cannot rely on yet, so call-sites for our macros must be updated
rather than modifying the code generated by the macro. see
rust-lang/rust#15701.

this would require us to do something like this throughout the codebase,
wherever we create tracing spans:

```rust
 #[cfg_attr(not(feature = "tracing"), allow(clippy::let_unit_value))]
 let _entered = trace_span!("instrumented");
```

`#[cfg_attr]` is a bit esoteric, and if we require that instrumenting code in
hyper require this song and dance every time we create a span, it will
generally discourage instrumentation.

 ### changes

this commit introduce a `Disabled` type to replace the empty `()` when
tracing instrumentation is disabled.

this satisfies clippy's `let_unit_value` rule because macros like
`trace_span!` will never return `()` no matter which feature flags are
active. this _also_ prevents accidentally writing code that would drop
span guards once the tracing flag is enabled, because an inert
`#[must_use]` guard acts as a stand-in when the flag isn't present.

Signed-off-by: katelyn martin <git@katelyn.world>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.