gomartini

How to break-out of martini middleware


In short, if I run some middleware for an http.Request and determine that the request deserves an HTTP 422, how can I "break-out" out of the middleware chain, and return early without invoking all of the middleware functions in the chain?

For example, if I have this:

func Routes(m *martini.ClassicMartini) {

    m.Get("/cp/users", mw.AsJson, mw.RequestTimer, ctr.GetMany)

}

if I call return in any of the middleware funds above, to my knowledge it will still invoke all of the middleware funds registered in the chain, so that ctr.GetMany is always invoked.

is there some way to make the request/response as completed and tell martini to stop calling all the funcs in the chain?

if the first return value is an integer, I guess martini assumes it's a status code. My current best guess is according to the docs: https://github.com/go-martini/martini#middleware-handlers

we could use this:

m.Use(func(c martini.Context, w http.ResponseWriter){

    if reqIsMalformed() {
        http.Error(w, "Bad request because xyz", 422)
        return;
    }

    c.Next()

})

and if a condition was not met, we could just never call c.Next()?


Solution

  • I did an experiment with the router/middleware, here are the results (the useful information is it at the end):

    func check0() {
        return
    }
    
    func check01() int {
        return 200
    }
    
    func check02() (int, string) {
        return 200, "boop"
    }
    
    func check03() bool {
        return true
    }
    
    func check04() string {
        return "04"
    }
    
    func check1(res http.ResponseWriter) string {
        return "1"
    }
    
    func check2(c martini.Context, res http.ResponseWriter) string {
        if true {
            return "hiii"
        }
        c.Next()
        return "2"
    }
    
    func check3(c martini.Context, res http.ResponseWriter) string {
        c.Next()
        return "3"
    }
    
    func check4(res http.ResponseWriter) {
        res.Write([]byte("4"))
    }
    
    func check5(c martini.Context, res http.ResponseWriter) (int, string, string) {
        res.Write([]byte("5.0"))
        c.Next()
        return 200, "5.1x", "5.1y"
    }
    
    func finish(res http.ResponseWriter) {
        fmt.Println("in finish")
        res.Write([]byte("all done"))
    }
    
    func Routes(m *martini.ClassicMartini) {
        m.Get("/cp/meta/test/middleware0", check0, finish)
        m.Get("/cp/meta/test/middleware01", check01, finish)
        m.Get("/cp/meta/test/middleware02", check02, finish)
        m.Get("/cp/meta/test/middleware03", check03, finish)
        m.Get("/cp/meta/test/middleware04", check04, finish)
        m.Get("/cp/meta/test/middleware1", check1, finish)
        m.Get("/cp/meta/test/middleware2", check2, finish)
        m.Get("/cp/meta/test/middleware3", check3, finish)
        m.Get("/cp/meta/test/middleware4", check4, finish)
        m.Get("/cp/meta/test/middleware5", check5, finish)
        m.Get("/cp/meta/echo_runtime_config", common.AsJson, common.RequestTimer, mw.BodyToMap, ctr.GetRuntimeConfig)
    }
    

    and here are the results when I hit the api:

    GET /cp/meta/test/middleware0 => 'all done'
    GET /cp/meta/test/middleware01 => ''
    GET /cp/meta/test/middleware03 => '<bool Value>'
    GET /cp/meta/test/middleware02 => 'boop'
    GET /cp/meta/test/middleware1 => '1'
    GET /cp/meta/test/middleware04 => '04'
    GET /cp/meta/test/middleware2 => 'hiii'
    GET /cp/meta/test/middleware3 => 'all done3'
    GET /cp/meta/test/middleware4 => '4'
    GET /cp/meta/test/middleware5 => '5.0all done5.1x'
    

    there was meant to be added to this question. so here are the rules:

    1. if the middleware function returns anything (aka the func has a non-void return signature) then no subsequent middleware will be called.
    2. injecting various params doesn't seem to make a difference as to whether subsequent middleware is called (including martini.Context, etc).
    3. Using martini.Context.Next() appears only useful for running a hook after all other remaining middleware is called.
    4. The remaining middleware will be invoked if nothing is returned, you obviously don't need to call c.Next().
    5. If you return an int as the first argument in the return list, it will be interpreted as a http status code, the second argument, if it's anything will be written to the body. If the first argument is a string, not a int, then that will be written to the body. I am not certain if 3rd arguments are used or ignored, but they seem to be ignored.