Search code examples
gomiddleware

How to use middlewares when using julienschmidt/httprouter in goLang?


What's the best way to chain middlewares while using julienschmidt/httprouter?

As far as I have googled, http.HandlerFunc accepts functions only in the form func (w ResponseWriter, r *Request) whereas httprouter.Handle functions are of the form func (w http.ResponseWriter, r *http.Request, ps httprouter.Params).

How to I chain middlewares without converting the httprouter.Handle function into http.HandlerFunc?

For example:
My routes.go is of the form,

router  :=  httprouter.New()
router.POST("/api/user/create", middlewares.EscapeStringsMiddleware(User.CreateUser))
log.Fatal(http.ListenAndServe(":8000",  router))

How do I write middleware functions for the above mentioned route?

Already tried methods:

1.

func EscapeStringsMiddleware(next http.Handler) httprouter.Handle {

    return func (response http.ResponseWriter, request *http.Request, ps httprouter.Params) {
        err := request.ParseForm()
        if err != nil {
            panic(err)
        }

        for key, values := range request.Form {
            for i, value := range values {
                value = template.HTMLEscapeString(value)
                value = template.JSEscapeString(value)
                request.Form[key][i] = value
            }
        }
        next.ServeHTTP(response, request)
    }
}

Error obtained:

cannot use User.CreateUser (type func(http.ResponseWriter, *http.Request, httprouter.Params)) as type http.Handler in argument to middlewares.EscapeStringsMiddleware:
func(http.ResponseWriter, *http.Request, httprouter.Params) does not implement http.Handler (missing ServeHTTP method)

2.

func EscapeStringsMiddleware(next httprouter.Handle) httprouter.Handle {

    return func (response http.ResponseWriter, request *http.Request, ps httprouter.Params) {
        err := request.ParseForm()
        if err != nil {
            panic(err)
        }

        for key, values := range request.Form {
            for i, value := range values {
                value = template.HTMLEscapeString(value)
                value = template.JSEscapeString(value)
                request.Form[key][i] = value
            }
        }
        next.ServeHTTP(response, request)
    }
}

Error obtained:

next.ServeHTTP undefined (type httprouter.Handle has no field or method ServeHTTP)

Also how do I chain multiple middleware?

For example,

router.POST("/api/user/create", middlewares.VerifyCSRF(middlewares.EscapeStringsMiddleware(User.CreateUser)))

Solution

  • This issue is not with your middleware handler. You are getting errs because User.CreateUser is not of type http.Handler.

    Try this pattern :

    The important bit is to return a http.Handler and wrap func(w http.ResponseWriter, r *http.Request) with http.HandlerFunc.

    func Handler() http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // do stuff
        })
    }
    

    go source :

    // The HandlerFunc type is an adapter to allow the use of
    // ordinary functions as HTTP handlers. If f is a function
    // with the appropriate signature, HandlerFunc(f) is a
    // Handler that calls f.
    type HandlerFunc func(ResponseWriter, *Request)
    
    // ServeHTTP calls f(w, r).
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
        f(w, r)
    }
    

    Based on your feedback:

    httprouter.Handle does not implement ServeHTTP. It is called directly. For example : next(w, r, ps)

    Below you will find examples of middleware handlers.

    // Middleware without "github.com/julienschmidt/httprouter"
    func StdToStdMiddleware(next http.Handler) http.Handler {
    
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // do stuff
            next.ServeHTTP(w, r)
        })
    }
    
    // Middleware for a standard handler returning a "github.com/julienschmidt/httprouter" Handle
    func StdToJulienMiddleware(next http.Handler) httprouter.Handle {
    
        return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
            // do stuff
            next.ServeHTTP(w, r)
        }
    }
    
    // Pure "github.com/julienschmidt/httprouter" middleware
    func JulienToJulienMiddleware(next httprouter.Handle) httprouter.Handle {
    
        return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
            // do stuff
            next(w, r, ps)
        }
    }
    
    func JulienHandler() httprouter.Handle {
        return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
            // do stuff
        }
    }
    
    func StdHandler() http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // do stuff
        })
    }
    
    func main() {
        router := httprouter.New()
        router.POST("/api/user/create", StdToJulienMiddleware(StdHandler()))
        router.GET("/api/user/create", JulienToJulienMiddleware(JulienHandler()))
        log.Fatal(http.ListenAndServe(":8000", router))
    }