-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[fix] httpbp
middleware doesn't flush chunked responses
#573
[fix] httpbp
middleware doesn't flush chunked responses
#573
Conversation
c13ff31
to
97c1121
Compare
httpbp
middleware breaks chunked reponseshttpbp
middleware doesn't flush chunked responses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
I think the only thing I would request is to make a unit test that makes a dummy server with the default middleware chain and makes sure that the handler can be hijacked and (separately) flushed.
Co-authored-by: Kyle Lemons <[email protected]>
httpbp/middlewares.go
Outdated
type wrappedHijacker struct { | ||
http.ResponseWriter | ||
http.Hijacker | ||
} | ||
|
||
type wrappedFlusher struct { | ||
http.ResponseWriter | ||
http.Flusher | ||
} | ||
|
||
type wrappedFlushHijacker struct { | ||
http.ResponseWriter | ||
http.Flusher | ||
http.Hijacker | ||
} | ||
|
||
func allowFlushHijack(original, rw http.ResponseWriter) http.ResponseWriter { | ||
flusher, isFlusher := original.(http.Flusher) | ||
hijacker, isHijacker := original.(http.Hijacker) | ||
switch { | ||
case isFlusher && isHijacker: | ||
return &wrappedFlushHijacker{rw, flusher, hijacker} | ||
case isFlusher: | ||
return allowFlush(original, rw) | ||
case isHijacker: | ||
return allowHijack(original, rw) | ||
default: | ||
return rw | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔕 it's manage-able as we currently have 2 optional interfaces, it will become a nightmare if there's a 3rd one added in the future. but I also can't find a better way to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this approach is definitely verbose but I can't think of a better one. The only other interface on http
that a http.ResponseWriter
could implement would be http.Pusher
. I don't think it's worth supporting it now as I don't think we have any use case driving it, but that would be a 3rd interface that may come up in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a quietly known issue amongst the ecosystem, and the approach in the generic case seems to be code generation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the article! I updated this PR to use the approach outlined in the article and included http.Pusher
. Because we are dealing with 3 interfaces, I adapted their code generator and then massaged the output to make it more human readable. Basically leveraging the jump table idea, without having to adapt a full code generator.
@kylelemons let me know if you think is approach is better or if we should revert back to my previous one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally prefer the previous approach. It's true that Pusher is missed, but it has the benefit that the type names wouldn't be atrocious :)
That said, I have no issue with keeping this. The approach is sound, and it's more complete.
httpbp/response_wrappers.go
Outdated
@@ -0,0 +1,33 @@ | |||
// DO NOT EDIT. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DO NOT EDIT is incompatible with "this was generated and then edited" :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1.
I would say it's better to just hand write this code:
type optionalResponseWriter int
const (
flusher optionalResponseWriter = 1 << iota
hijacker
pusher
)
func wrapResponseWriter(...) ... {
var f optionalResponseWriter
...
switch f {
case 0:
...
case flusher:
...
case flusher | hijacker:
...
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong preference between this and the previous approach, but if we pick this approach, I'd prefer to hand writing the code over using codegen.
httpbp/response_wrappers.go
Outdated
@@ -0,0 +1,33 @@ | |||
// DO NOT EDIT. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1.
I would say it's better to just hand write this code:
type optionalResponseWriter int
const (
flusher optionalResponseWriter = 1 << iota
hijacker
pusher
)
func wrapResponseWriter(...) ... {
var f optionalResponseWriter
...
switch f {
case 0:
...
case flusher:
...
case flusher | hijacker:
...
...
}
Closes #
💸 TL;DR
httpbp
middleware, in particularRecordStatusCode
andPrometheusServerMetrics
don't work with chunked HTTP responses because they wraphttp.ResponseWriter
and do not implementhttp.Flush
. This updates our response wrapping within in the Middleware to support both thehttp.Flush
andhttp.Hijacker
interface.📜 Details
Design Doc
Jira
🧪 Testing Steps / Validation
✅ Checks