fix(proxy): invoke response_body_filter for bodyless upstream responses#853
Open
jsulmont wants to merge 2 commits intocloudflare:mainfrom
Open
fix(proxy): invoke response_body_filter for bodyless upstream responses#853jsulmont wants to merge 2 commits intocloudflare:mainfrom
jsulmont wants to merge 2 commits intocloudflare:mainfrom
Conversation
When the upstream returns 204/304, the H1 client emits a single HttpTask::Header(end=true) and no HttpTask::Body; the H2 path produces a header frame with eos=true and no DATA. The existing call to response_body_filter lives in the HttpTask::Body arm, so the filter is unreachable — making the canonical pattern (mutate status to 200 in response_filter, inject body in response_body_filter) impossible. After the upstream task loop in proxy_h1/h2.rs, detect the bodyless case and invoke response_body_filter once with body=None, end_of_stream=true. If the filter returns non-empty bytes, clear the header's end flag, rewrite Content-Length, and append a HttpTask::Body. Behavior is unchanged for responses with a body.
|
Love this way haha. It’s much cleaner than my original approach. Previously, I was thinking of putting the injection logic inside |
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.
Summary
Closes #852. Invoke
response_body_filteronce withbody=None, end_of_stream=truewhen the upstream response has no body (204/304), so users can inject a synthesized body in the canonicalfilter pattern.
Bug
When the upstream returns 204 or 304, the H1 client emits a single
HttpTask::Header(end_of_stream=true)and never produces aHttpTask::Body; the H2 path similarly emits a header frame witheos=trueand no DATA. The existing call toresponse_body_filterinh{1,2}_response_filterlives in theHttpTask::Bodyarm, so the filter is unreachable for these responses.This makes the documented pattern impossible — a filter that mutates the status from 204 to 200 in
response_filterhas no way to inject a body inresponse_body_filter, because the body filter is never called.Fix
After the upstream task processing loop in
proxy_h1.rsandproxy_h2.rs, detect the bodyless case (batch containsHeader(end_of_stream=true)with noBodytask) and invokeresponse_body_filteronce withbody=None, end_of_stream=truevia a newmaybe_synthesize_body_filter_callhelper onHttpProxy.If the user's filter returns non-empty bytes, the in-flight header task is updated:
end_of_streamcleared,Content-Lengthrewritten to the injected length,Transfer-Encodingremoved. AHttpTask::Body(Some(bytes), true)is appended so the downstream writer ships the synthesized body.The helper handles two task-batch shapes:
[Header(end=true)]and[Header(end=true), Done]. Any batch that contains aBodytask (regardless of end flag) is still handled by the existingHttpTask::Bodyarm.Non-regression
response_body_filter.HttpTask::Bodyarm.response_body_filteris now called once atend_of_stream=truewithbody=None. This aligns with the documented contract ("called once at end of stream") which was previously only honored for responses with bodies. Existing filters that checkif let Some(b) = bodyare unaffected.Verification
A regression test is included in this PR at
pingora-proxy/tests/test_body_filter_bodyless.rs. It's a self-contained test binary (no openresty dependency) that spawns:204 No Contenton/no-bodyand200 + "Hello World!\n"on everything else204 -> 200inresponse_filter, inject"<synthesized/>"inresponse_body_filter)Two
#[tokio::test]s:test_body_filter_reaches_204_upstream— asserts the downstream client receives the injected bodytest_body_filter_passthrough_on_200_upstream— asserts the 200 path is byte-for-byte forwarded (non-regression)On this branch (with the fix), both tests pass.
On stock
main(without the fix), the 204 test fails with an empty body:Diff
~77 lines of fix + ~200 lines of regression test:
pingora-proxy/src/proxy_h1.rs— new helper + call sitepingora-proxy/src/proxy_h2.rs— call site (helper is shared viaHttpProxyimpl)pingora-proxy/tests/test_body_filter_bodyless.rs— new self-contained regression test binary