When I read article https://upgear.io/blog/golang-tip-wrapping-http-response-writer-for-middleware/?utm_source=golangweekly&utm_medium=email, I realize that is easy to make a wrapper (design pattern Proxy), that wrap some methods.
Situation is bit complicated, when you don't want loose an interface, when wrapped object had it.
In example example I've written how to optionally implement http.Flusher. But how to solve a situation from article, when w could implement some of 3 interfaces (http.Flusher, http.Hijacker, http.Pusher). Is it better solution, that write 8 different types, where each implements combination of previous?
// add type, that do what statusRecorder, but keeps ability to be Flusher
type statusRecordFlusher statusRecorder
func (w *statusRecordFlusher) Flush() {
w.ResponseWriter.(http.Flusher).Flush()
}
// and decision, how to wrap
func logware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Initialize the status to 200 in case WriteHeader is not called
var rec http.ResponseWriter
if _, ok := w.(http.Flusher); ok {
rec = &statusRecordFlusher{w, 200}
} else {
rec = &statusRecorder{w, 200}
}
next.ServeHTTP(rec, r)
})
}
You're embedding ResponseWriter
, and shadowing Flush
without adding any behavior; remove the shadow method. You'll still need to do some type wrangling, but you don't need to do any method implementations unless you're trying to add or alter behavior (presumably just WriteHeader
based on the question).
Because you're just trying to expose methods of embedded types here, you don't even need to define all the structs, you can use anonymous structs (playground example here):
type statusRecorder struct {
// Not sure what all is in here but let's assume at least:
http.ResponseWriter
status int
}
func logware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Per the docs:
// The default ResponseWriter for HTTP/1.x connections supports Hijacker, but HTTP/2 connections intentionally do not.
// Pusher is the interface implemented by ResponseWriters that support HTTP/2 server push.
// Therefor w will only ever be a Hijacker or a Pusher, never both.
sr := &statusRecorder{w, 200}
if h, ok := w.(http.Hijacker); ok {
if f, ok := w.(http.Flusher); ok {
w = &struct {
http.ResponseWriter
http.Hijacker
http.Flusher
}{sr, h, f}
} else {
w = &struct {
http.ResponseWriter
http.Hijacker
}{sr, h}
}
} else if p, ok := w.(http.Pusher); ok {
if f, ok := w.(http.Flusher); ok {
w = &struct {
http.ResponseWriter
http.Pusher
http.Flusher
}{sr, p, f}
} else {
w = &struct {
*statusRecorder
http.Pusher
}{sr, p}
}
} else {
w = sr
}
next.ServeHTTP(w, r)
})
}