Skip to content

docs(examples): add an example of using tower layers#4137

Open
RAprogramm wants to merge 2 commits into
hyperium:masterfrom
RAprogramm:3154
Open

docs(examples): add an example of using tower layers#4137
RAprogramm wants to merge 2 commits into
hyperium:masterfrom
RAprogramm:3154

Conversation

@RAprogramm

@RAprogramm RAprogramm commented Jul 26, 2026

Copy link
Copy Markdown

Closes #3154.

I know new examples are not free — #3628, #3821 and #3910 were all closed with "I'm trying to now
limit how many examples are in the repository". So this PR is split into two commits that stand on
their own, and merging only the second one is a perfectly good outcome if the first is unwanted.

  1. docs(examples): add an example of using tower layers — the example itself.
  2. docs(service): link the tower middleware guide from the module docs — 12 lines of rustdoc, no
    new target, no new dependency, no compile time.

Since this repo squash-merges, "merge only the second one" means dropping the first commit — say so
and I will either force-push this branch down to the rustdoc commit alone, or split it into its own
PR, whichever you prefer.

Why this one differs from the examples that were turned down

Those PRs added application patterns (a proxy, a router, 100-continue). This is an interop question
about hyper's own Service trait: the reporter asked twice, in 2023, for the hello-world accept
loop with layers on it, and there is still no in-repo answer. It is labelled E-easy + A-docs and
has been open for three years.

What it costs

Files 3 (new example, Cargo.toml, examples/README.md)
Example 109 lines
Dev-graph +4 crates (tower, tower-layer, tower-service, sync_wrapper): cargo tree -e normal,dev goes 71 → 75 against the same lockfile
Published graph +0 — include is ["Cargo.toml", "LICENSE", "src/**/*"]
Cargo.lock churn none, it is gitignored

CI, job by job

test is the only job that compiles examples, and it passes. Everything else is untouched, but
since the dev-dependency is the part worth scrutinising, here is the full accounting:

Job Effect
test compiles the example; clean under #![deny(warnings)]
style rustfmt --check --edition 2021 over every tracked file: clean
lint omits --all-targets, so examples are not linted (CI.yml#L68-L70)
msrv cargo check --features full compiles zero tower crates
features cargo hack --no-dev-deps — dev-deps excluded outright
minimal-versions cargo-minimal-versions removes dev-dependencies unless the invocation names --example(s)/--test(s)/--bench(es)/--all-targets; hyper's does not
udeps All deps seem to have been used.
miri, ffi*, doc, check-external-types, semver lib-only

MSRV

tower 0.5.x declares rust-version = 1.64.0, hyper declares 1.63. I ran the msrv job's exact
sequence locally to be sure it is a non-issue:

$ CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback cargo update
      Adding tower v0.5.3 (requires Rust 1.64.0)
$ cargo +1.63 check --features full
    Finished dev [unoptimized + debuginfo] target(s) in 1m 52s      # zero tower crates compiled

cargo update also writes a v3 lockfile, matching the declared rust-version. If you would still
rather not have a dev-dependency whose declared MSRV is above hyper's, say the word and I will switch
the example to tower-layer + tower-service (neither has ever published a rust-version, and they
are what hyper-util itself depends on) with a hand-written layer.

The 0.5.2 floor rather than 0.5 is deliberate. tower = "0.5" resolves to 0.5.0 under a minimal
lockfile, and 0.5.0 pins tower-layer ^0.3.1, which does not build:

error[E0015]: cannot call non-const associated function `Identity::new` in constant functions
   --> tower-0.5.0/src/builder/mod.rs:120:20

0.5.1 is the first release requiring tower-layer ^0.3.3, and 0.5.2 the first requiring
sync_wrapper ^1 rather than ^0.1.1, so 0.5.2 is the cheapest floor that clears both. This is a
courtesy to anyone running -Z minimal-versions by hand, not a CI fix: the job strips
dev-dependencies before resolving (cargo-minimal-versions, remove_dev_deps in src/main.rs).

Why the adapter is vendored

The same cycle that already forces #[path = "../benches/support/mod.rs"] mod support; in every
example: hyper cannot depend on hyper-util. The comment above the adapter names
hyper_util::service::TowerToHyperService as the thing to use in a real application, so it points
readers at the util crate rather than competing with it. The implementation is the same shape —
Oneshot<S, Req> over a cloned service, bound S: tower::Service<R> + Clone, no boxing.

Delta over the middleware guide

https://hyper.rs/guides/1/server/middleware/ covers the Logger case well. This adds three things
the guide does not: it is compiled by CI on every push, it uses off-the-shelf layers rather than a
hand-written one, and it documents a behaviour that surprised me while writing it — because the
adapter drives the whole stack to readiness before calling it, a timeout layer bounds the handler
but not the time spent waiting for a concurrency_limit permit, in either layer order. Measured
with a 10s handler, concurrency_limit(1), timeout(2s) and two concurrent requests: 2.00s / 4.00s
both ways round.

The guide still pins tower = "0.4"; happy to send a follow-up there, but I did not want to bundle it.

Scope

Server-side only, matching the issue. tower_http::trace::TraceLayer and friends drop into the same
ServiceBuilder slot with no other change, which answers the follow-up question in the thread.
Client-side tower is left out deliberately.

Happy to cut this down further — to concurrency_limit alone, or to just the adapter plus
service_fn — if simpler is better.

Show a tower stack in front of the low-level server API, with the adapter
needed to hand a tower::Service to serve_connection. hyper cannot depend on
hyper-util, so TowerToHyperService is vendored into the example the same way
the examples already vendor TokioIo.

Closes hyperium#3154
The module says nothing about middleware today, so readers reaching for tower
have no in-repo pointer to the adapter they need.
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.

Provide example on how to use hyper 1.0.0 with tower layers

1 participant