Search code examples
gomartini

martini recover for any panics


I want to wire RecoverWrap to all handlers of martini routes to make any panic be finished by code inside RecoverWrap.

I tried to do it like m.Use(RecoverWrap) but do not know how to do it exactly, it fails on compile.

package main

import (
    "errors"
    "github.com/go-martini/martini"
    "net/http"
)

func main() {
    m := martini.Classic()
    //m.Use(RecoverWrap)
    m.Get("/", func() {
        panic("some panic")
    })

    m.Run()
}

func RecoverWrap(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
        var err error
        defer func() {
            r := recover()
            if r != nil {
                switch t := r.(type) {
                case string:
                    err = errors.New(t)
                case error:
                    err = t
                default:
                    err = errors.New("Unknown error")
                }

                http.Error(w, "Something goes wrong", http.StatusInternalServerError)
            }
        }()
        h.ServeHTTP(w, req)
    })
}

Solution

  • Middleware handlers in Martini do not get to "wrap" other handler calls, so http.Handler is not found by the injector.

    What you can do is use context.Next():

    package main
    
    import (
    "errors"
    "github.com/go-martini/martini"
    "net/http"
    )
    
    func main() {
        m := martini.Classic()
        m.Use(RecoverWrap)
        m.Get("/", func() {
            panic("some panic")
            })
    
        m.Run()
    }
    
    func RecoverWrap(c martini.Context, w http.ResponseWriter) {
        var err error
        defer func(w http.ResponseWriter) {
            r := recover()
            if r != nil {
                switch t := r.(type) {
                case string:
                    err = errors.New(t)
                case error:
                    err = t
                default:
                    err = errors.New("Unknown error")
                }
                http.Error(w, "Something goes wrong", http.StatusInternalServerError)
            }
            }(w)
        c.Next()
    }
    

    You will have to make sure that your error handler is the first middleware registered, or those handlers running before will not be caught.

    Actually, the same method is implemented in martini.Recovery:

    https://github.com/go-martini/martini/blob/6241001738f6e1b1ea7c4a4089195e1b0681609a/recovery.go#L115