feat(on-early-drop): Add middleware for client early drop detection#636
Merged
Conversation
0477f9b to
0828576
Compare
|
cc @tottoto can you review? |
Member
|
cc @seanmonstar if you have any time, no problem if not. I've reviewed it back when it was on the fork, so fairly polished. |
Member
|
Hey @fbergero , like we chatted about offline, I pushed some changes on top of yours (and also dealt with the rebase). Generally I added some more integration with the rest of the
|
Introduces a new middleware that detects when a client disconnects before receiving a full response. This allows for monitoring and logging of early connection terminations. Fixes tower-rs#396
Author
|
@jlizen Loved the IDK if we need to update the PR desc to include the new changes (it's on the rustdocs anyhow so up to you) |
rcoh
approved these changes
May 5, 2026
rcoh
left a comment
There was a problem hiding this comment.
there is one duplicated doc line otherwise LGTM
…ropsAsFailures, other doc cleanups
jlizen
approved these changes
May 5, 2026
eleboucher
pushed a commit
to eleboucher/towonel
that referenced
this pull request
May 5, 2026
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [tower-http](https://github.com/tower-rs/tower-http) | workspace.dependencies | patch | `0.6.8` → `0.6.9` | --- ### Release Notes <details> <summary>tower-rs/tower-http (tower-http)</summary> ### [`v0.6.9`](https://github.com/tower-rs/tower-http/releases/tag/tower-http-0.6.9) [Compare Source](tower-rs/tower-http@tower-http-0.6.8...tower-http-0.6.9) #### Added: - `on-early-drop`: middleware that detects when a response future or response body is dropped before completion ([#​636]) Two events get hooks: the response future being dropped before the inner service produces a response, and the response body being dropped before reaching end-of-stream. Install custom callbacks with `OnEarlyDropLayer::builder()`: ```rust use http::Request; use tower_http::on_early_drop::{OnBodyDropFn, OnEarlyDropLayer}; let layer = OnEarlyDropLayer::builder() .on_future_drop(|req: &Request<()>| { let uri = req.uri().clone(); move || eprintln!("future dropped for {}", uri) }) .on_body_drop(OnBodyDropFn::new(|req: &Request<()>| { let uri = req.uri().clone(); move |parts: &http::response::Parts| { let status = parts.status; move || eprintln!("body dropped for {} status {}", uri, status) } })); ``` Or route both events through a `trace::OnFailure` hook with `EarlyDropsAsFailures`. Place this layer inside a `TraceLayer` so the emitted events inherit the request span: ```rust use tower::ServiceBuilder; use tower_http::on_early_drop::{OnEarlyDropLayer, EarlyDropsAsFailures}; use tower_http::trace::{DefaultOnFailure, TraceLayer}; let stack = ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer(OnEarlyDropLayer::new( EarlyDropsAsFailures::new(DefaultOnFailure::default()), )); ``` - `fs`: make `AsyncReadBody::with_capacity` public ([#​415]) #### Changed: - The implicit `async-compression` feature is removed ([#​642]) - The implicit `tokio` feature is removed ([#​628]) - `fs`: no longer auto-enables the `tracing` crate feature; enable `tracing` explicitly to restore error logging on `ServeDir` IO failures ([#​614]) #### Fixed - `trace`: restore failure classification at end-of-stream ([#​483]) - `follow-redirect`: support unicode URLs (swaps `iri-string` dep for `url`) ([#​646]) - `fs`: reject reserved Windows DOS device names (`CON`, `COM1`, etc.) in `ServeDir` ([#​663]) [#​415]: tower-rs/tower-http#415 [#​483]: tower-rs/tower-http#483 [#​614]: tower-rs/tower-http#614 [#​628]: tower-rs/tower-http#628 [#​636]: tower-rs/tower-http#636 [#​642]: tower-rs/tower-http#642 [#​646]: tower-rs/tower-http#646 [#​663]: tower-rs/tower-http#663 #### All the PRs - ci: update deny action to v2 by [@​seanmonstar](https://github.com/seanmonstar) in [#​627](tower-rs/tower-http#627) - chore: improve code comments clarity by [@​xibeiyoumian](https://github.com/xibeiyoumian) in [#​626](tower-rs/tower-http#626) - ci: Update to actions/checkout v6 by [@​tottoto](https://github.com/tottoto) in [#​629](tower-rs/tower-http#629) - ci: msrv resolver by [@​seanmonstar](https://github.com/seanmonstar) in [#​635](tower-rs/tower-http#635) - chore: Remove resolved cargo-deny config by [@​tottoto](https://github.com/tottoto) in [#​631](tower-rs/tower-http#631) - ci: Update to cargo-check-external-types 0.4.0 by [@​tottoto](https://github.com/tottoto) in [#​633](tower-rs/tower-http#633) - examples: Use typed default value clap config by [@​tottoto](https://github.com/tottoto) in [#​634](tower-rs/tower-http#634) - examples: Disable unused reqwest feature by [@​tottoto](https://github.com/tottoto) in [#​632](tower-rs/tower-http#632) - examples: Update to reqwest 0.13 by [@​tottoto](https://github.com/tottoto) in [#​640](tower-rs/tower-http#640) - Fix clippy warnings in warp-key-value-store example by [@​jplatte](https://github.com/jplatte) in [#​637](tower-rs/tower-http#637) - ci: Use Swatinem/rust-cache\@​v2 to cache by [@​tottoto](https://github.com/tottoto) in [#​644](tower-rs/tower-http#644) - ci: Remove unused working-directory config by [@​tottoto](https://github.com/tottoto) in [#​645](tower-rs/tower-http#645) - Use cargo-deny graph config by [@​tottoto](https://github.com/tottoto) in [#​639](tower-rs/tower-http#639) - Fix: follow redirect unicode in [#​646](tower-rs/tower-http#646) - doc: remove mention of deprecated bearer method in lib.rs comment by [@​VojtaStanek](https://github.com/VojtaStanek) in [#​641](tower-rs/tower-http#641) - Allow Unicode-3.0 license by [@​tottoto](https://github.com/tottoto) in [#​648](tower-rs/tower-http#648) - fix(docs): typo by [@​carlocorradini](https://github.com/carlocorradini) in [#​649](tower-rs/tower-http#649) - fix: remove unused GzEncoder import in decompression in [#​647](tower-rs/tower-http#647) - docs: update Example server in [#​652](tower-rs/tower-http#652) - Don't automatically enable tracing for fs feature by [@​ginnyTheCat](https://github.com/ginnyTheCat) in [#​614](tower-rs/tower-http#614) - examples: Remove unnecessary trait bound by [@​tottoto](https://github.com/tottoto) in [#​651](tower-rs/tower-http#651) - Remove implicit async-compression feature by [@​tottoto](https://github.com/tottoto) in [#​642](tower-rs/tower-http#642) - fix clippy warnings by [@​alexanderkjall](https://github.com/alexanderkjall) in [#​659](tower-rs/tower-http#659) - Check for reserved DOS names by [@​Darksonn](https://github.com/Darksonn) in [#​663](tower-rs/tower-http#663) - enable clippy for tower-http and fix current issues by [@​GlenDC](https://github.com/GlenDC) in [#​407](tower-rs/tower-http#407) - chore: remove implicit tokio feature by [@​WaterWhisperer](https://github.com/WaterWhisperer) in [#​628](tower-rs/tower-http#628) - trace: adds back call to classify\_eos on trailers by [@​markdingram](https://github.com/markdingram) in [#​483](tower-rs/tower-http#483) - Make AsyncReadBody::with\_capacity public by [@​bouk](https://github.com/bouk) in [#​415](tower-rs/tower-http#415) - examples: Use axum::body::to\_bytes by [@​tottoto](https://github.com/tottoto) in [#​650](tower-rs/tower-http#650) - ci: Remove unnecessary protoc setup by [@​tottoto](https://github.com/tottoto) in [#​665](tower-rs/tower-http#665) - feat(on-early-drop): Add middleware for client early drop detection by [@​fbergero](https://github.com/fbergero) in [#​636](tower-rs/tower-http#636) - chore: release tower-http 0.6.9 by [@​jlizen](https://github.com/jlizen) in [#​666](tower-rs/tower-http#666) #### New Contributors - [@​xibeiyoumian](https://github.com/xibeiyoumian) made their first contribution in [#​626](tower-rs/tower-http#626) - [@​VojtaStanek](https://github.com/VojtaStanek) made their first contribution in [#​641](tower-rs/tower-http#641) - [@​carlocorradini](https://github.com/carlocorradini) made their first contribution in [#​649](tower-rs/tower-http#649) - [@​ginnyTheCat](https://github.com/ginnyTheCat) made their first contribution in [#​614](tower-rs/tower-http#614) - [@​alexanderkjall](https://github.com/alexanderkjall) made their first contribution in [#​659](tower-rs/tower-http#659) - [@​Darksonn](https://github.com/Darksonn) made their first contribution in [#​663](tower-rs/tower-http#663) - [@​WaterWhisperer](https://github.com/WaterWhisperer) made their first contribution in [#​628](tower-rs/tower-http#628) - [@​bouk](https://github.com/bouk) made their first contribution in [#​415](tower-rs/tower-http#415) - [@​fbergero](https://github.com/fbergero) made their first contribution in [#​636](tower-rs/tower-http#636) - [@​jlizen](https://github.com/jlizen) made their first contribution in [#​666](tower-rs/tower-http#666) **Full Changelog**: <tower-rs/tower-http@tower-http-0.6.8...tower-http-0.6.9> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/towonel/pulls/33
eleboucher
pushed a commit
to eleboucher/towonel
that referenced
this pull request
May 20, 2026
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [tower-http](https://github.com/tower-rs/tower-http) | workspace.dependencies | patch | `0.6.8` → `0.6.9` | --- ### Release Notes <details> <summary>tower-rs/tower-http (tower-http)</summary> ### [`v0.6.9`](https://github.com/tower-rs/tower-http/releases/tag/tower-http-0.6.9) [Compare Source](tower-rs/tower-http@tower-http-0.6.8...tower-http-0.6.9) #### Added: - `on-early-drop`: middleware that detects when a response future or response body is dropped before completion ([#​636]) Two events get hooks: the response future being dropped before the inner service produces a response, and the response body being dropped before reaching end-of-stream. Install custom callbacks with `OnEarlyDropLayer::builder()`: ```rust use http::Request; use tower_http::on_early_drop::{OnBodyDropFn, OnEarlyDropLayer}; let layer = OnEarlyDropLayer::builder() .on_future_drop(|req: &Request<()>| { let uri = req.uri().clone(); move || eprintln!("future dropped for {}", uri) }) .on_body_drop(OnBodyDropFn::new(|req: &Request<()>| { let uri = req.uri().clone(); move |parts: &http::response::Parts| { let status = parts.status; move || eprintln!("body dropped for {} status {}", uri, status) } })); ``` Or route both events through a `trace::OnFailure` hook with `EarlyDropsAsFailures`. Place this layer inside a `TraceLayer` so the emitted events inherit the request span: ```rust use tower::ServiceBuilder; use tower_http::on_early_drop::{OnEarlyDropLayer, EarlyDropsAsFailures}; use tower_http::trace::{DefaultOnFailure, TraceLayer}; let stack = ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer(OnEarlyDropLayer::new( EarlyDropsAsFailures::new(DefaultOnFailure::default()), )); ``` - `fs`: make `AsyncReadBody::with_capacity` public ([#​415]) #### Changed: - The implicit `async-compression` feature is removed ([#​642]) - The implicit `tokio` feature is removed ([#​628]) - `fs`: no longer auto-enables the `tracing` crate feature; enable `tracing` explicitly to restore error logging on `ServeDir` IO failures ([#​614]) #### Fixed - `trace`: restore failure classification at end-of-stream ([#​483]) - `follow-redirect`: support unicode URLs (swaps `iri-string` dep for `url`) ([#​646]) - `fs`: reject reserved Windows DOS device names (`CON`, `COM1`, etc.) in `ServeDir` ([#​663]) [#​415]: tower-rs/tower-http#415 [#​483]: tower-rs/tower-http#483 [#​614]: tower-rs/tower-http#614 [#​628]: tower-rs/tower-http#628 [#​636]: tower-rs/tower-http#636 [#​642]: tower-rs/tower-http#642 [#​646]: tower-rs/tower-http#646 [#​663]: tower-rs/tower-http#663 #### All the PRs - ci: update deny action to v2 by [@​seanmonstar](https://github.com/seanmonstar) in [#​627](tower-rs/tower-http#627) - chore: improve code comments clarity by [@​xibeiyoumian](https://github.com/xibeiyoumian) in [#​626](tower-rs/tower-http#626) - ci: Update to actions/checkout v6 by [@​tottoto](https://github.com/tottoto) in [#​629](tower-rs/tower-http#629) - ci: msrv resolver by [@​seanmonstar](https://github.com/seanmonstar) in [#​635](tower-rs/tower-http#635) - chore: Remove resolved cargo-deny config by [@​tottoto](https://github.com/tottoto) in [#​631](tower-rs/tower-http#631) - ci: Update to cargo-check-external-types 0.4.0 by [@​tottoto](https://github.com/tottoto) in [#​633](tower-rs/tower-http#633) - examples: Use typed default value clap config by [@​tottoto](https://github.com/tottoto) in [#​634](tower-rs/tower-http#634) - examples: Disable unused reqwest feature by [@​tottoto](https://github.com/tottoto) in [#​632](tower-rs/tower-http#632) - examples: Update to reqwest 0.13 by [@​tottoto](https://github.com/tottoto) in [#​640](tower-rs/tower-http#640) - Fix clippy warnings in warp-key-value-store example by [@​jplatte](https://github.com/jplatte) in [#​637](tower-rs/tower-http#637) - ci: Use Swatinem/rust-cache\@​v2 to cache by [@​tottoto](https://github.com/tottoto) in [#​644](tower-rs/tower-http#644) - ci: Remove unused working-directory config by [@​tottoto](https://github.com/tottoto) in [#​645](tower-rs/tower-http#645) - Use cargo-deny graph config by [@​tottoto](https://github.com/tottoto) in [#​639](tower-rs/tower-http#639) - Fix: follow redirect unicode in [#​646](tower-rs/tower-http#646) - doc: remove mention of deprecated bearer method in lib.rs comment by [@​VojtaStanek](https://github.com/VojtaStanek) in [#​641](tower-rs/tower-http#641) - Allow Unicode-3.0 license by [@​tottoto](https://github.com/tottoto) in [#​648](tower-rs/tower-http#648) - fix(docs): typo by [@​carlocorradini](https://github.com/carlocorradini) in [#​649](tower-rs/tower-http#649) - fix: remove unused GzEncoder import in decompression in [#​647](tower-rs/tower-http#647) - docs: update Example server in [#​652](tower-rs/tower-http#652) - Don't automatically enable tracing for fs feature by [@​ginnyTheCat](https://github.com/ginnyTheCat) in [#​614](tower-rs/tower-http#614) - examples: Remove unnecessary trait bound by [@​tottoto](https://github.com/tottoto) in [#​651](tower-rs/tower-http#651) - Remove implicit async-compression feature by [@​tottoto](https://github.com/tottoto) in [#​642](tower-rs/tower-http#642) - fix clippy warnings by [@​alexanderkjall](https://github.com/alexanderkjall) in [#​659](tower-rs/tower-http#659) - Check for reserved DOS names by [@​Darksonn](https://github.com/Darksonn) in [#​663](tower-rs/tower-http#663) - enable clippy for tower-http and fix current issues by [@​GlenDC](https://github.com/GlenDC) in [#​407](tower-rs/tower-http#407) - chore: remove implicit tokio feature by [@​WaterWhisperer](https://github.com/WaterWhisperer) in [#​628](tower-rs/tower-http#628) - trace: adds back call to classify\_eos on trailers by [@​markdingram](https://github.com/markdingram) in [#​483](tower-rs/tower-http#483) - Make AsyncReadBody::with\_capacity public by [@​bouk](https://github.com/bouk) in [#​415](tower-rs/tower-http#415) - examples: Use axum::body::to\_bytes by [@​tottoto](https://github.com/tottoto) in [#​650](tower-rs/tower-http#650) - ci: Remove unnecessary protoc setup by [@​tottoto](https://github.com/tottoto) in [#​665](tower-rs/tower-http#665) - feat(on-early-drop): Add middleware for client early drop detection by [@​fbergero](https://github.com/fbergero) in [#​636](tower-rs/tower-http#636) - chore: release tower-http 0.6.9 by [@​jlizen](https://github.com/jlizen) in [#​666](tower-rs/tower-http#666) #### New Contributors - [@​xibeiyoumian](https://github.com/xibeiyoumian) made their first contribution in [#​626](tower-rs/tower-http#626) - [@​VojtaStanek](https://github.com/VojtaStanek) made their first contribution in [#​641](tower-rs/tower-http#641) - [@​carlocorradini](https://github.com/carlocorradini) made their first contribution in [#​649](tower-rs/tower-http#649) - [@​ginnyTheCat](https://github.com/ginnyTheCat) made their first contribution in [#​614](tower-rs/tower-http#614) - [@​alexanderkjall](https://github.com/alexanderkjall) made their first contribution in [#​659](tower-rs/tower-http#659) - [@​Darksonn](https://github.com/Darksonn) made their first contribution in [#​663](tower-rs/tower-http#663) - [@​WaterWhisperer](https://github.com/WaterWhisperer) made their first contribution in [#​628](tower-rs/tower-http#628) - [@​bouk](https://github.com/bouk) made their first contribution in [#​415](tower-rs/tower-http#415) - [@​fbergero](https://github.com/fbergero) made their first contribution in [#​636](tower-rs/tower-http#636) - [@​jlizen](https://github.com/jlizen) made their first contribution in [#​666](tower-rs/tower-http#666) **Full Changelog**: <tower-rs/tower-http@tower-http-0.6.8...tower-http-0.6.9> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/towonel/pulls/33
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 on-early-drop middleware for client early drop detection
Introduces a new middleware that detects when a client disconnects before receiving a full response. This allows for monitoring and logging of early connection terminations.
Fixes #396
Motivation
When clients disconnect before receiving a complete response (e.g., due to browser navigation, timeouts, or network issues), the entire future chain is typically dropped with no indication of what happened. This creates "disappearing requests" that never show up in logs or metrics, making it difficult to:
Solution
This PR introduces two main components:
1. OnEarlyDropGuard
A standalone guard that executes a callback function when dropped, unless explicitly marked as completed:
2. OnEarlyDropLayer/Service
A Tower middleware that applies early drop detection to HTTP services:
This implementation enables various use cases: