Ankit Kumar
Ankit Kumar

Reputation: 1775

What is the best way to send back the response from the middle-ware in Golang

I have created middle-wares using Adapter pattern. One of my middle-ware is for authentication. So if the user is not authorized then I have to send back response to the user and the next middle-ware/s should not be called.

// Adapter type

type Adapter func(http.Handler) http.Handler

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

// CheckAuth middleware
func CheckAuth() Adapter {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // Get Authorization token from the header
            // Validate the token
            // if valid token then call h.ServeHTTP(w, r)
            // else send response 401 to the user,
            if(validUser){
                h.ServeHTTP(w, r)
            }else{
                fmt.Fprintf(w, "Unauthorized")
            }
            return h
        }
    }
}

http.Handle("/", Adapt(indexHandler, AddHeader(),
                                 CheckAuth(),
                                 CopyMgoSession(db),
                                 Notify(logger), 
                               )

in the CheckAuth middleware I'm calling h.ServeHTTP(w, r) only if the user is authorized, so for the else condtition we also need to break the for loop of the Adapt function or else it will call next middleware even after sending the response.

let me know if there is any other way to handle such situation.

Upvotes: 4

Views: 2554

Answers (3)

Peter
Peter

Reputation: 31691

The Adapt function is not relevant for serving requests. It is executed once (and only once) before the HTTP server even starts. Note that it returns an http.Handler but it isn't an http.Handler itself.

That handler that Adapt returns in this case behaves like this:

var indexHandler http.Handler

func handlerWithMiddleWare(w http.ResponseWriter, r *http.Request) {
    notify := func(w http.ResponseWriter, r *http.Request) {
            copyMgoSession := func(w http.ResponseWriter, r *http.Request) {
                    checkAuth := func(w http.ResponseWriter, r *http.Request) {
                            addHeader := func(w http.ResponseWriter, r *http.Request) {
                                    indexHandler.ServeHTTP(w, r)
                            }

                            addHeader(w, r)
                    }

                    checkAuth(w, r)
            }

            copyMgoSession(w, r)
    }

    notify(w, r)
}

So if you let CheckAuth return without calling the next middleware, you can send whatever response you like; just as you would in any other handler.

By the way, you way want to let Adapt iterate in reverse order. I'm not sure that you're aware that Notify executes first, then CopyMgoSession, then CheckAuth, and then AddHeader.

Upvotes: 2

Oswin Noetzelmann
Oswin Noetzelmann

Reputation: 9545

Middleware is typically chained. There are frameworks that can do it for you. A sleek example is Alice.

chain := alice.New(th.Throttle, timeoutHandler, nosurf.NewPure).Then(myHandler)

If you want to do it yourself you can use recursion to avoid a for loop. For example (from this link):

// buildChain builds the middlware chain recursively, functions are first class
func buildChain(f http.HandlerFunc, m ...middleware) http.HandlerFunc {
        // if our chain is done, use the original handlerfunc
        if len(m) == 0 {
                return f
        }
        // otherwise nest the handlerfuncs
        return m[0](buildChain(f, m[1:cap(m)]...))
}

Each middleware receives the next as parameter. As such the next has to be manually called by the previous handler otherwise the chain stops. So in your auth middleware you don't have to call the next one if auth fails and the chain stops with your error status being the last thing returned. So in your code you need to accept a parameter of http.Handler and that is the next handler (a middleware function has to have the signature of func(http.Handler) http.Handler). See this blog for more details.

You may want to set the correct http status codes as well. Include something like this:

http.Error(w, "Forbidden: xyz", http.StatusForbidden)

Upvotes: 1

Michael Hampton
Michael Hampton

Reputation: 9980

The next middleware in the chain only runs if you explicitly call it.

That next middleware is passed to your closure as h, and you are calling it by invoking h.ServeHTTP(). If you do not call this, no other middleware runs, so you must supply the complete HTTP response.

Upvotes: 4

Related Questions