美文网首页
对于golang中的http server api访问异常(pa

对于golang中的http server api访问异常(pa

作者: 蒙浩 | 来源:发表于2020-07-15 16:54 被阅读0次

对于golang进程出现crash,大家都知道使用recover,但是对于http server这种流程,recover应该怎么加呢?
一般会想到:

package main

import (
    "errors"
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func main() {
   defer func() { // 必须要先声明defer,否则不能捕获到panic异常
        glog.Infoln("d")
        if err := recover(); err != nil {
            glog.Infoln(err) // 这里的err其实就是panic传入的内容
        }
        glog.Infoln("e")
    }()
    rtr := mux.NewRouter()
    rtr.HandleFunc("/", withPanic).Methods("GET")

    http.Handle("/", rtr)
    log.Println("Listening...")

    http.ListenAndServe(":3001", http.DefaultServeMux)
}

func withPanic(w http.ResponseWriter, r *http.Request) {
    panic("crash")
}

但是当你run起来之后,只要访问3001,就会直接崩掉。。。。。。。咋没被recover捕捉到呢?原来是http.ListenAndServe()函数的实现中调用了函数Serve,而Serve函数的逻辑里,对于处理http消息的部分是通过协程操作的。。。

func (srv *Server) Serve(l net.Listener) error {
    if fn := testHookServerServe; fn != nil {
        fn(srv, l) // call hook with unwrapped listener
    }

    l = &onceCloseListener{Listener: l}
    defer l.Close()

    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }

    if !srv.trackListener(&l, true) {
        return ErrServerClosed
    }
    defer srv.trackListener(&l, false)

    var tempDelay time.Duration     // how long to sleep on accept failure
    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, e := l.Accept()
        if e != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)   -------------------->原因所在!
    }
}

也就是说,对于用户访问api的响应,是由协程处理,那么自然协程的panic不会被主进程捕获了。
于是可以想到的正确姿势:

package main

import (
    "errors"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func main() {
    m := mux.NewRouter()
    m.Handle("/", RecoverWrap(http.HandlerFunc(handler))).Methods("GET")

    http.Handle("/", m)
    log.Println("Listening...")

    http.ListenAndServe(":3001", nil)

}

func handler(w http.ResponseWriter, r *http.Request) {
    panic(errors.New("panicing from error"))
}

func RecoverWrap(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *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")
                }
                sendMeMail(err)
                http.Error(w, err.Error(), http.StatusInternalServerError)
            }
        }()
        h.ServeHTTP(w, r)
    })
}

func sendMeMail(err error) {
    // send mail
}

这样,即使程序panic了,也不会导致整个程序崩掉,而是会重新启动。
参考:https://stackoverflow.com/questions/28745648/global-recover-handler-for-golang-http-panic

相关文章

网友评论

      本文标题:对于golang中的http server api访问异常(pa

      本文链接:https://www.haomeiwen.com/subject/rlljhktx.html