Search code examples
goadaptermux

Why would I get an infinite loop with this Adapter Interface Pattern


I need to replace an existing API interface with one that considers an incoming Auth token and also issues a machine-to-machine token for the outgoing service calls.

In summary, this is an API using gorilla/mux routing framework and I'm just adding endpoints to a mux.NewRouter(). Nothing fancy...yet ;).

I have been trying a few different patterns, but the one that seems most appealing is the Adapter Interface derived by Mat Ryer in https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81 and https://go-talks.appspot.com/github.com/matryer/golanguk/building-apis.slide#30

In summary with no direct logic, I have done the following and get stuck in an infinite loop when I run the endpoint from Postman.

{"level":"info","msg":"New Relic Checkpoint!!! /endpoint","time":"2019-08-19T14:28:27-05:00"} {"level":"info","msg":"Security Checkpoint!!! /endpoint","time":"2019-08-19T14:28:27-05:00"} {"level":"info","msg":"Header Checkpoint!!! /endpoint","time":"2019-08-19T14:28:27-05:00"} {"level":"info","msg":"New Relic Checkpoint!!! /endpoint","time":"2019-08-19T14:28:27-05:00"} {"level":"info","msg":"Security Checkpoint!!! /endpoint","time":"2019-08-19T14:28:27-05:00"} {"level":"info","msg":"Header Checkpoint!!! /endpoint","time":"2019-08-19T14:28:27-05:00"}

r.Handle(endpoint.Path(), Adapt(r, NewRelicAdapter(endpoint), SecurityAdapter(endpoint), WithHeader(endpoint)))

the Adapter Interface is established as follows...

type Adapter func(http.Handler) http.Handler

func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
    for _, adapter := range reverseAdapters(adapters) {
        h = adapter(h)
    }
    return h
}

func NewRelicAdapter(endpoint rest.Endpoint) Adapter {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            logrus.Infof("New Relic Checkpoint!!! %v", endpoint.Path())
            h.ServeHTTP(w, r)
        })
    }
}

func SecurityAdapter(endpoint rest.Endpoint) Adapter {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            logrus.Infof("Security Checkpoint!!! %v", endpoint.Path())
            h.ServeHTTP(w, r)
        })
    }
}

func WithHeader(endpoint rest.Endpoint) Adapter {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            logrus.Infof("Header Checkpoint!!! %v", endpoint.Path())
            h.ServeHTTP(w, r)
        })
    }
}

func reverseAdapters(adapters []Adapter) []Adapter {
    for i := 0; i < len(adapters)/2; i++ {
        j := len(adapters) - i - 1
        adapters[i], adapters[j] = adapters[j], adapters[i]
    }
    return adapters
}

I would really appreciate knowing why this loops before I get into the details on what each adapter would be doing.


Solution

  • I think this is because your chain of handlers ends with r, in effect becoming:

    r.Handle(path, r)
    

    You have to have an actual handler somewhere to handle the call, so your setup should look like:

    r.Handle(path, Adapt(theHandler, adapter1, adapter2, adapter3))
    

    However, since you said you're using gorilla/mux, there is another way you can to this. Take a look at Router.Use

    https://godoc.org/github.com/gorilla/mux#Router.Use