Skip to content

feat(http2): add an executor getter for server and client - #4250

Open
robertream wants to merge 1 commit into
hyperium:masterfrom
robertream:http2-builder-executor
Open

robertream wants to merge 1 commit into
hyperium:masterfrom
robertream:http2-builder-executor

Conversation

@robertream

@robertream robertream commented Oct 29, 2026 •

Copy link
Copy Markdown

Ran into this writing a WebTransport extension: a session has to be spawned once the response is out, and the executor you'd want is the one already driving the connection, but there's no way to read it back off the builder, so you keep a second copy and hope they match. Adds a getter on both builders.

@robertream
robertream force-pushed the http2-builder-executor branch from 720a9a0 to 8bae438 Compare October 29, 2026 11:46
@robertream robertream changed the title feat(http2): expose the executor a Builder was created with feat(http2): add Builder::executor Oct 29, 2026
`Builder::new(exec)` is the only constructor on either HTTP/2 builder and the
executor cannot be read back, so a caller that needs it for work alongside a
connection keeps a second copy with nothing relating the two.

`impl<E> Builder<E>` is unconstrained, so the accessor needs no bound, and
returning `&E` leaves cloning to the caller.
@robertream
robertream force-pushed the http2-builder-executor branch from 8bae438 to f10811e Compare October 29, 2026 11:53
@robertream robertream changed the title feat(http2): add Builder::executor feat(http2): add an executor getter for server and client Oct 29, 2026

@cratelyn cratelyn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hello, thank you for opening a pull request! my initial reaction is some skepticism that this new interface is truly needed.

the executor you'd want is the one already driving the connection, but there's no way to read it back off the builder, so you keep a second copy and hope they match.

i'm not sure what you mean by "second copy and hope they match". types like TokioExecutor can be freely cloned, since they are essentially an abstraction over functions like tokio::spawn etc.

is there a situation where not having access to the inner E can cause a bug or other incorrectness? is cloning the executor when constructing the server/client and passing it forward not possible?

@robertream

Copy link
Copy Markdown
Author

For some broader context, I'm building an extension to hyper and axum to support WebTransport over HTTP/2, following the IETF standardization work, which is now in Working Group Last Call: https://datatracker.ietf.org/doc/draft-ietf-webtrans-http2/

I'm also building a gateway that terminates HTTP/3 WebTransport connections and proxies them to HTTP/2 WebTransport backends. The idea is that WebTransport servers can use HTTP/2 within a backend data center network without every service needing to implement HTTP/3 or QUIC.

Apple shipped WebTransport in Safari 26.4 in March, on by default and including HTTP/2 fallback when QUIC isn't available, which as far as I know makes it the only shipping browser implementation of the HTTP/2 mapping. The draft itself is edited by Meta, with Apple, Mozilla, and Google co-authoring.

The mapping of WebTransport onto HTTP/2 and hyper is fairly fussy, so I'd like to hide as much of that complexity as possible behind webtransport_service, including wiring up and scheduling the stateful connection task that maintains the server-side WebTransport session.

I'll be following up with a handful of PRs for the adjustments needed in h2 and hyper to support the emerging standard. Those changes are a bit more involved, though, so I wanted to start with this one and break the ice.

As for the getter, you're right, there's no correctness issue. My "hope they match" wording implied one and shouldn't have.

The executor is passed to Builder::new purely for compositional ergonomics. Builder stores it and clones it once, at serve time, into proto::h2::Server::new. It plays no part in configuring the builder. hyper already shows the two forms are equivalent: client::conn::http2::handshake(exec, io) takes the executor as a parameter and is documented as "a shortcut for Builder::new(exec).handshake(io)". Carrying it on the builder is what keeps serve_connection(io, service) clean.

The getter would extend that same affordance to extensions of the builder. hyper spares its own callers from supplying the executor at serve time, but an extension trait on Builder<E> can't, because it has no way to read what the builder already holds. So libraries building on hyper, whether that's axum or mine, have to reimpose the parameter hyper chose to avoid. That's all I'm asking for: not a new capability, just the same ergonomics hyper gives itself.

It's one call site in one crate today.

The difference is:

// Today
Builder::new(TokioExecutor::new()).webtransport_service(config, router)

// Without the getter
let exec = TokioExecutor::new();
Builder::new(exec.clone()).webtransport_service(exec, config, router)

If you'd rather not add the getter, that's completely fair. I'll just add the parameter to my own signature.

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.

2 participants