Repository navigation
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4876 +/- ##
=======================================
Coverage 98.65% 98.65%
=======================================
Files 48 48
Lines 3188 3189 +1
=======================================
+ Hits 3145 3146 +1
Misses 43 43
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
wahajahmed010
left a comment
There was a problem hiding this comment.
Clean fix. The order swap (Hijack() first, then transition w.size only on success) is correct and matches Go's own net/http TestResponseControllerHijack semantics for the HTTP/2 fallback case described in the PR.
A few minor observations:
-
Symmetry consideration. The condition
err == nil && w.size < 0handles two distinct cases: (a) successful hijack on an unwritten response (transition to written), (b) successful hijack on an already-written response (no-op). That is correct. The only behavior change is on the previously-broken case (c) failed hijack on an unwritten response, where the writer now stays unwritten. Good. -
Test coverage. The new test covers
failingHijacker+http.Errorfallback path. Consider also asserting the inverse: a successful hijack on an unwritten response still transitionsWritten()totrueandSize()to0(the original happy-path behavior). That guard prevents a future "always skip the size update" regression from sneaking back in. -
Doc comment.
Hijack()'s godoc says nothing about what happens to the writer state on failure. A one-liner along the lines of "On error, the response writer is left in its prior state so the caller can emit a fallback HTTP response" would help future middleware authors avoid the trap this PR fixes.
LGTM once #2 (or equivalent) is added.
|
@wahajahmed010 Thanks for the review. I've added the successful hijack assertion for Written() == true and Size() == 0, and also clarified the failure behavior in the Hijack() doc comment. Ready for another look when you have a chance. |
1bb5105 to
412f843
Compare
|
Hi @wahajahmed010, I've squashed the commits into a single commit to follow the repository's contribution guidelines. The fix, regression tests, and documentation updates are all preserved. Thanks again for your review! I'd appreciate another look when you have a chance. |
Description
responseWriter.Hijack()currently changes the writer state fromnoWritten(-1) to0before the underlyinghttp.Hijacker.Hijack()call succeeds.If the wrapped
ResponseWriterimplementshttp.Hijackerbut the actual hijack operation returns an error, Gin leaves the response marked as written even though the connection was not hijacked and no HTTP response was written.As a result, the handler can no longer send a normal fallback response with the intended status code.
For example:
Before this fix, the failed hijack makes
Written()returntrue, so the subsequent500status is ignored and the client may receive200 OKinstead.Reproduction
This can be reproduced with a real HTTP/2 request when a middleware
ResponseWriterwrapper implementshttp.Hijackerand delegates the operation throughhttp.ResponseController.The underlying HTTP/2 response writer does not support traditional connection hijacking, so the actual
Hijack()call returns an error. The handler should still be able to send a normal HTTP error response after that failure.Before the fix:
After the fix:
Standard library behavior
This behavior is also consistent with Go's
net/httptests.In
TestResponseControllerHijack,the HTTP/2 case expects
ResponseController.Hijack()to return an error andthen continues using the same
ResponseWriterto produce a normal HTTPresponse.
This matches the expected behavior here: a failed hijack should not make an
otherwise unwritten response unusable for an HTTP fallback.
Root cause
The writer is marked as written before delegating to the underlying hijacker:
If the underlying
Hijack()fails, that state change remains even though hijacking never succeeded.Fix
Call the underlying
Hijack()first and only transition an unwritten response to the written state after a successful hijack:This preserves the existing behavior for successful hijacks and for responses that were already committed before the hijack attempt.
Tests
Added a regression test that verifies:
Hijack()leaves an unwritten response in thenoWrittenstate;Written()remainsfalseandSize()remains-1;500 Internal Server Errorfallback response.Locally validated with:
Pull Request Checklist
masterbranch.docs/doc.md. (Not applicable: this is a bug fix and introduces no new feature.)