Add Body type and make serve() body-generic for http@1 migration#4564
Draft
Add Body type and make serve() body-generic for http@1 migration#4564
Body type and make serve() body-generic for http@1 migration#4564Conversation
|
A new generated diff is ready to view.
A new doc preview is ready to view. |
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.
Add
Bodytype and makeserve()body-generic forhttp@1migrationBackground:
http@0->http@1body modelIn
hyper0.x (http@0),hyper::Bodywas a concrete type — every handler, middleware, and route used the samehyper::Bodyfor both requests and responses. Simple, but inflexible.In
hyper1.x (http@1),http_body::Bodyis a trait.hyper's own request body is nowhyper::body::Incoming— a type that onlyhypercan construct. You cannot create anIncomingfrom bytes, a stream, or any other source — it is produced exclusively byhyperwhen reading from the wire. This is a fundamental shift: there is no single concrete body type anymore. Instead, the ecosystem expects routers, middleware, andTowerlayers to be generic overB: http_body::Body. This is the right design — middleware like logging, compression, or metrics shouldn't care about the concrete body type. They work through the trait.Our existing
Route<B>is already generic over the body type withB = Incomingas the default. Codegen produces handlers that are body-generic. Theserve()function andBoxBody/BoxBodySyncresponse types were added as part of thehttp@1merge. This PR does not change any of that.The problem this PR solves
While body-generic middleware is the correct long-term design, teams writing application-level middleware (not reusable library middleware) face a practical pain point: threading
B: http_body::Body<Data = Bytes> + Send + 'staticthrough every service wrapper, plugin, and layer is verbose and error-prone. When a team just needs to stash a copy of the request body inhttp::Extensions, or reconstruct a request from buffered bytes, they need a concrete body type they can name and construct.Incomingcan't fill this role — you can't construct one from bytes, and it doesn't implementFrom<Vec<u8>>or similar. In thehttp@0world,hyper::Body::from("hello")just worked. Inhttp@1, there's a gap.What this PR adds
1.
Body— an opt-in, type-erased request bodyhttp_body::Body<Data = Bytes>behind one heap allocationFrom<Incoming>— convertshyper's request body at theserveboundaryFrom<&str>,From<String>,From<Bytes>,From<Vec<u8>>, etc. — constructable from common typesBody::empty(),Default— for empty bodieshttp_body::Bodyitself, so it works anywhere the trait is expectedtry_downcastavoids double-boxing when wrapping aBodyin aBodyTeams that want simpler middleware signatures can use
Route<Body>and letserve()handle theIncoming->Bodyconversion. Teams that want zero-overhead can continue usingRoute<Incoming>— nothing changes for them.2.
serve()made body-generic viaReqBody: From<Incoming>Previously,
serve()hardcodedIncomingas the request body type. Now:A thin
MapRequestBodyadapter sits at thehyperboundary and callsreq.map(ReqBody::from)on each incoming request. WhenReqBody = Incoming,From<Incoming> for Incomingis the identity — zero cost. WhenReqBody = Body, it performs one heap allocation viaFrom<Incoming> for Body.3.
Brenamed toRespBodythroughoutserve/mod.rsfor clarity —Bwas ambiguous now that we have both request and response body generics.4. Test coverage including streaming and error scenarios:
test_serve_with_custom_request_body_type—Incoming->Bodyconversion works for buffered requeststest_serve_with_streaming_request_body— ChunkedTransfer-Encodingstreams throughBodyframe-by-frametest_serve_with_streaming_response_body—wrap_streamresponse body arrives correctly throughhypertest_serve_with_streaming_response_body_error— Stream error mid-response propagates to client (not swallowed)test_serve_with_streaming_request_body_error— Malformed chunked request doesn't panic or hang the serverDesign principles preserved
http_body::Body— this has not changed.Bodyis a convenience for application code, not a replacement for the trait.BoxBody/BoxBodySyncremain the response body types —Bodyis request-side only.wrap_stream/wrap_stream_syncremain the stream-to-body APIs — no redundant alternatives added.serve()withService<Request<Incoming>>continue to work via type inference.Changes
src/body.rs— AddBody,BodyDataStream,Fromimpls, remove unnecessary privateStreamBodyandSyncWrappersrc/serve/mod.rs— AddReqBodygeneric,MapRequestBodyadapter, renameB->RespBodytests/serve_integration_test.rs— +5 new tests, remove flaky sleeps, harden assertionstests/graceful_shutdown_test.rs— Harden assertions, fixunwrap->expectTest plan
cargo test— 121 tests pass, 0 warningsIncoming->Bodyconversion (frame-by-frame)wrap_streamthroughhyperboundary