docs(examples): add an example of using tower layers#4137
Open
RAprogramm wants to merge 2 commits into
Open
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
docs(examples): add an example of using tower layers— the example itself.docs(service): link the tower middleware guide from the module docs— 12 lines of rustdoc, nonew 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
Servicetrait: the reporter asked twice, in 2023, for the hello-world acceptloop with layers on it, and there is still no in-repo answer. It is labelled
E-easy+A-docsandhas been open for three years.
What it costs
Cargo.toml,examples/README.md)tower,tower-layer,tower-service,sync_wrapper):cargo tree -e normal,devgoes 71 → 75 against the same lockfileincludeis["Cargo.toml", "LICENSE", "src/**/*"]Cargo.lockchurnCI, job by job
testis the only job that compiles examples, and it passes. Everything else is untouched, butsince the dev-dependency is the part worth scrutinising, here is the full accounting:
test#![deny(warnings)]stylerustfmt --check --edition 2021over every tracked file: cleanlint--all-targets, so examples are not linted (CI.yml#L68-L70)msrvcargo check --features fullcompiles zero tower cratesfeaturescargo hack --no-dev-deps— dev-deps excluded outrightminimal-versionscargo-minimal-versionsremoves dev-dependencies unless the invocation names--example(s)/--test(s)/--bench(es)/--all-targets; hyper's does notudepsAll deps seem to have been used.miri,ffi*,doc,check-external-types,semverMSRV
tower 0.5.x declares
rust-version = 1.64.0, hyper declares 1.63. I ran themsrvjob's exactsequence locally to be sure it is a non-issue:
cargo updatealso writes a v3 lockfile, matching the declaredrust-version. If you would stillrather 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 arust-version, and theyare what hyper-util itself depends on) with a hand-written layer.
The
0.5.2floor rather than0.5is deliberate.tower = "0.5"resolves to 0.5.0 under a minimallockfile, and 0.5.0 pins
tower-layer ^0.3.1, which does not build:0.5.1 is the first release requiring
tower-layer ^0.3.3, and 0.5.2 the first requiringsync_wrapper ^1rather than^0.1.1, so 0.5.2 is the cheapest floor that clears both. This is acourtesy to anyone running
-Z minimal-versionsby hand, not a CI fix: the job stripsdev-dependencies before resolving (
cargo-minimal-versions,remove_dev_depsinsrc/main.rs).Why the adapter is vendored
The same cycle that already forces
#[path = "../benches/support/mod.rs"] mod support;in everyexample: hyper cannot depend on hyper-util. The comment above the adapter names
hyper_util::service::TowerToHyperServiceas the thing to use in a real application, so it pointsreaders at the util crate rather than competing with it. The implementation is the same shape —
Oneshot<S, Req>over a cloned service, boundS: tower::Service<R> + Clone, no boxing.Delta over the middleware guide
https://hyper.rs/guides/1/server/middleware/ covers the
Loggercase well. This adds three thingsthe 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
timeoutlayer bounds the handlerbut not the time spent waiting for a
concurrency_limitpermit, in either layer order. Measuredwith a 10s handler,
concurrency_limit(1),timeout(2s)and two concurrent requests: 2.00s / 4.00sboth 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::TraceLayerand friends drop into the sameServiceBuilderslot 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_limitalone, or to just the adapter plusservice_fn— if simpler is better.