对于golang中的http server api访问异常(panic)的recover操作

蒙浩 · · 580 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

对于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访问异常(panic)的recover操作

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

580 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传