0

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)
    })
}
lofcek
  • 1,165
  • 2
  • 9
  • 18

1 Answers1

0

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)
    })
}
Adrian
  • 42,911
  • 6
  • 107
  • 99
  • My program maybe does not work perfect, but idea was - if w support interface http.Flusher, rec also should support it. If w does not support http.Flusher, we should create rec, that does not support it. It is not an issue, if w could support only one interface. But what if w could implement some of three interfaces? – lofcek Jun 14 '17 at 14:11
  • Yes, that's what I'm saying. If you're embedding the `ResponseWriter` in `statusRecorder`, then `statusRecorder` implements **all** interfaces that were implemented by what you pass in as the `ResponseWriter`, without having to shadow any of the themods. – Adrian Jun 14 '17 at 14:21
  • That sounds much better, than works. See this example - at first I wrote directly to httptest.NewRecorder. That is a flusher, but in the second, I wrapped it - but it does not work at all. https://play.golang.org/p/j-me5z_ggW – lofcek Jun 15 '17 at 07:51
  • I'm sorry, you are absolutely right. The test case I used to prove it before I answered was flawed. There's another option, I'll update my answer shortly. – Adrian Jun 15 '17 at 12:49
  • thank, but this was what I want to avoid. There are also another possibilities, but it would be reasonable to use it for more "emulated" interfaces. One is using reflect.StructOf, that allow us to create such structs in run-time, and also use "go generate" create such code by computer. But each of this possibilities are ugly. – lofcek Jun 15 '17 at 16:16