fix: safe type assertions in CloseNotify() and Hijack()#4639
Conversation
When gin is used with http.TimeoutHandler or any other middleware that wraps http.ResponseWriter with a type that does not implement http.Hijacker or http.CloseNotifier, the direct type assertions in Hijack() and CloseNotify() cause a runtime panic. Replace with checked assertions following the same pattern already used in Flush(). CloseNotify() returns nil when unsupported (http.CloseNotifier is deprecated since Go 1.11). Hijack() returns a descriptive error, consistent with the existing errHijackAlreadyWritten pattern. Fixes gin-gonic#4460
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4639 +/- ##
==========================================
- Coverage 99.21% 98.37% -0.84%
==========================================
Files 42 48 +6
Lines 3182 3148 -34
==========================================
- Hits 3157 3097 -60
- Misses 17 42 +25
- Partials 8 9 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Hit this exact panic today when adding http.TimeoutHandler to our gin setup. Looked at the PR and the fix solves it - safe assertion + graceful fallback, consistent with how Flush() is handled. |
|
Hi, responseWriter.CloseNotify() now returns nil when the underlying ResponseWriter doesn’t implement http.CloseNotifier, but Context.Stream() unconditionally selects on that channel to detect disconnects. A nil channel disables that select case permanently, so Stream will not stop on client disconnect and can loop indefinitely until step() returns false. Severity: action required | Category: correctness How to fix: Fallback to request context Agent prompt to fix - you can give this to your LLM of choice:
Found by Qodo code review. FYI, Qodo is free for open-source. |
.Stream when CloseNotify is nil
|
Valid point. CloseNotify() now returns nil when http.CloseNotifier is unsupported, while Context.Stream() previously selected only on that channel. In wrapped-writer scenarios (e.g. http.TimeoutHandler), this can disable disconnect detection in Stream(). Fixed in this PR by adding a fallback to c.Request.Context().Done() (guarded with c.Request != nil) and keeping the safe CloseNotify() behavior unchanged. Added a test for this path as well. |
|
@appleboy - gentle ping on safe assertions for Hijack/CloseNotify + Stream disconnect fallback. Would appreciate a review when you can. Thanks. |
Problem
Similar to #4460 (fixed for
Flush()in #4479),Hijack()andCloseNotify()perform direct type assertions that panic when the underlying writer doesn't implement the interface.When gin is used with
http.TimeoutHandleror any middleware that wrapshttp.ResponseWriterwith a type that doesn't implementhttp.Hijackerorhttp.CloseNotifier, callingHijack()orCloseNotify()causes a runtime panic:Fix
Replace direct assertions with checked assertions - the same pattern already used in
Flush():CloseNotify()returnsnilwhen unsupported (http.CloseNotifieris deprecated since Go 1.11; callers should useRequest.Context().Done())Hijack()returnserrHijackNotSupported, consistent with the existingerrHijackAlreadyWrittenpatternThe capability check in
Hijack()now runs before mutatingw.size, soWritten()staysfalseon the error path.Tests
Added
TestResponseWriterOptionalInterfaceFallbackswith amockNonHijackerWriter(implements onlyhttp.ResponseWriter) verifying thatFlush(),CloseNotify(), andHijack()all degrade gracefully without panicking.Closes #4638.