Added
-
csrf: add cross-site request forgery (CSRF) protection middleware, porting the cross-origin protection scheme introduced in Go 1.25 (#699)use tower::ServiceBuilder; use tower_http::csrf::CsrfLayer; // Rejects cross-origin state-changing requests using `Sec-Fetch-Site`, // an `Origin` allow-list, and an `Origin`/`Host` fallback. No per-request // token state required. let layer = CsrfLayer::new().add_trusted_origin("https://example.com")?; let service = ServiceBuilder::new().layer(layer).service_fn(handler);
-
timeout: addDeadlineBodyfor non-resetting body timeouts, applied via the newRequestBodyDeadlineLayerandResponseBodyDeadlineLayer(#688)Unlike
TimeoutBody, which resets its deadline on every frame,DeadlineBodycaps the total time of a body transfer. A slow client trickling one byte at a time never trips an idle timeout but will trip a deadline.use std::time::Duration; use tower::ServiceBuilder; use tower_http::timeout::RequestBodyDeadlineLayer; // Abort the request body transfer after 30s total, regardless of how // frequently data arrives. let service = ServiceBuilder::new() .layer(RequestBodyDeadlineLayer::new(Duration::from_secs(30))) .service_fn(handler);
-
fs: add strongETagsupport toServeDir, includingIf-MatchandIf-None-Matchprecondition handling per RFC 9110.304 Not Modifiedresponses now carry theETagandLast-Modifiedvalidators (#691) -
fs: add aBackendtrait to makeServeDirwork with non-filesystem sources (e.g. embedded assets or object storage). The defaultTokioBackendpreserves existing behavior. UseServeDir::with_backend()to plug in custom implementations (#684)use tower_http::services::fs::ServeDir; // `MyBackend` implements `tower_http::services::fs::Backend`. // The default `ServeDir::new()` continues to use `TokioBackend` (local FS). let service = ServeDir::with_backend("assets", MyBackend::new());
-
fs: addhtml_as_default_extensionoption toServeDir, appending.htmlwhen the request path has no extension (#519) -
fs: addredirect_path_prefixoption toServeDir, prepending a prefix on trailing-slash redirects so the service can be mounted under a sub-path (#486) -
validate-request: addValidateRequestHeaderLayer::has_header_value()to reject requests when a header does not have an expected value (#360) -
body:UnsyncBoxBody::new()constructor andFrom<ServeFileSystemResponseBody>conversion to avoid double-boxing when combiningServeDirresponses with other body types (#537) -
limit: implementDefaultforlimit::ResponseBodywhen the wrapped body also implementsDefault(#679)
Changed
-
breaking:
compression: the middleware now handles the*wildcard andidentity;q=0in Accept-Encoding per RFC 9110 §12.5.3. Requests that previously fell back to identity (e.g.*;q=0oridentity;q=0with no other acceptable encoding) now receive a 406 Not Acceptable response. Clients that explicitly reject all encodings without listing an alternative will see different behavior. (#693) -
breaking:
compression: upgrade theSizeAbovepredicate threshold fromu16tou64, allowing minimum sizes above 64 KiB (#704) -
breaking: remove the implicit no-op
tokioandasync-compressionfeatures. These were kept as no-op features in 0.6.x for backwards compatibility after the switch todep:syntax in #642. Downstream crates that activatetower-http/tokioortower http/async-compressionshould remove those feature entries; the underlying dependencies are still pulled in transitively by the features that need them (e.g.compression-gzip,fs,timeout). (#628) -
breaking:
trace/classify: include the gRPC error message in tracing output.GrpcCodeandGrpcFailureClassare now#[non_exhaustive], andGrpcStatusis exported from theclassifymodule (#422) -
breaking:
follow-redirect:FollowRedirectnow forwards requestExtensionsto redirected requests instead of dropping them. TheStandardpolicy drops extensions on cross-origin redirections (same-origin keeps them). Opt out withFollowRedirectLayer::preserve_extensions(false); keep specific types withFilterCredentials::allow_extension::<T>()or all of them withkeep_all_extensions(). (#706)use tower_http::follow_redirect::FollowRedirectLayer; // 0.7.0 forwards request `Extensions` across redirects by default. // Restore the previous behavior (drop all extensions) with: let layer = FollowRedirectLayer::new().preserve_extensions(false);
-
breaking:
follow-redirect: header and extension filtering is now cumulative. A value a policy drops on one hop is no longer replayed on later hops, soFilterCredentialsno longer re-sendsCookie/Authorizationto a same-origin target reached after cross-origin hop. CustomPolicy::on_requestimpls now see the previous hop's filtered request, not the original. (#706) -
trace:DefaultOnRequest,DefaultOnResponse,DefaultOnFailure, andDefaultOnEosnow explicitly parent their tracing events to the request span rather than relying on the ambient span context. This fixes intermittent cases where events could appear without their request span attached (#690) -
cors: relax theVaryheader defaults (#674) -
MSRV bumped from 1.64 to 1.65 (#684)
Fixed
fs:ServeDirandServeFilenow emit aVary: Accept-Encodingresponse
header when precompressed serving is configured, ensuring caches correctly
distinguish between compressed and uncompressed variants (#692)- breaking:
services: reject a trailing slash for file paths. File requests with a trailing slash now return404 Not Foundinstead of serving the file (#678) fs: fixServeDirstripping the file extension when serving with identity encoding (#686)compression: forward trailers from the inner body after compression finishes, fixing dropped gRPC status trailers (#685)trace: fireon_eoswhen the inner body reportsis_end_streamwith a precise content-length (#687)on-early-drop: suppress the early-drop guard whenis_end_streamis reported after a data frame (#687)set-header: makeSetMultipleRequestHeadersandSetMultipleResponseHeadersClonefor non-CloneHTTP bodies (#703)
Thanks
New Contributors
- @muhamadazmy made their first contribution in #679
- @Isvane made their first contribution in #678
- @xiaoyawei made their first contribution in #422
- @dependabot[bot] made their first contribution in #696
- @its-the-shrimp made their first contribution in #519
- @yawn made their first contribution in #699
- @Jesse-Bakker made their first contribution in #703
- @junghwan16 made their first contribution in #705
- @claraphyll made their first contribution in #486