在Google App Engine上恢复恐慌是可能的吗?

huangapple go评论76阅读模式
英文:

Is it possible to recover from panic on google app engine?

问题

我想知道是否有可能从恐慌中恢复。GAE似乎有自己的恐慌恢复机制,但我找不到任何处理它的钩子来在我的应用程序中处理。

英文:

I'm wondering if it's possible to recover from a panic. It seems GAE has it's own panic recovery mechanism but I can't find any hook to handle it on my app.

答案1

得分: 1

在AppEngine webapp中,处理程序的注册方式与普通的Go应用程序相同。你只需要不显式调用http.ListenAndServe()(因为平台会处理),而是在init()函数中进行处理程序的注册(而不是在main()函数中)。

话虽如此,在AppEngine中也可以使用相同的panic-recover包装方法,不过很遗憾,没有其他更好的方法。

看一下这个示例:它使用一个通过HandleFunc()注册的函数和一个通过Handle()注册的Handler来处理两个URL模式,但它们都故意引发panic(拒绝提供服务):

func myHandleFunc(w http.ResponseWriter, r *http.Request) {
    panic("I'm myHandlerFunc and I refuse to serve!")
}

type MyHandler int

func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    panic("I'm MyHandler and I refuse to serve!")
}

func main() {
    http.HandleFunc("/myfunc", myHandleFunc)
    http.Handle("/myhandler", new(MyHandler))

    panic(http.ListenAndServe(":8080", nil))
}

将浏览器定向到http://localhost:8080/myfunchttp://localhost:8080/myhandler会导致HTTP 500状态:内部服务器错误(或者取决于你在哪里检查它的空响应)。

总体思路是使用recover来“捕获”处理程序引发的panic(规范:处理panic)。我们可以以一种方式“包装”处理函数或处理程序,首先注册一个defer语句,即使函数的其余部分引发panic,也会调用该语句,并在其中从panic状态中恢复。

看一下这两个函数:

func protectFunc(hf func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            r := recover()
            if r != nil {
                // hf()引发了panic,我们刚刚从中恢复过来。
                // 以某种方式处理错误,提供自定义错误页面。
                w.Write([]byte("Something went bad but I recovered and sent this!"))
            }
        }()
        hf(w, r)
    }
}

func protectHandler(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            r := recover()
            if r != nil {
                // h.ServeHTTP()引发了panic,我们刚刚从中恢复过来。
                // 以某种方式处理错误,提供自定义错误页面。
                w.Write([]byte("Something went bad but I recovered and sent this!"))
            }
        }()
        h.ServeHTTP(w, r)
    })
}

第一个函数接受一个函数,并返回一个调用我们传递的函数但在引发panic时从panic状态中恢复的函数。

第二个函数接受一个Handler并返回另一个Handler,类似地调用传递的Handler,但也处理panic并恢复正常执行。

现在,如果我们使用这些方法注册受保护的处理函数和Handler,注册的处理程序将永远不会引发panic(假设在恢复正常执行之后的代码不会引发panic):

http.HandleFunc("/myfunc-protected", protectFunc(myHandleFunc))
http.Handle("/myhandler-protected", protectHandler(new(MyHandler)))

访问http://localhost:8080/myfunc-protectedhttp://localhost:8080/myhandler-protected URL会得到HTTP 200状态(OK),并显示以下消息:

Something went bad but I recovered and sent this!
英文:

Handlers in an AppEngine webapp are registered in the same way as would be in a normal Go application. You just don't have to call http.ListenAndServe() explicitly (because it will be by the platform), and handler registration happens in an init() function (not in main()).

Having said that, the same panic-recover wrapping works on AppEngine too, and unfortunately there is no other, better way.

Take a look at this example: it uses a function registered with HandleFunc() and a Handler registered with Handle() to handle 2 URL patterns, but both intentionally panics (they refuse to serve):

func myHandleFunc(w http.ResponseWriter, r *http.Request) {
	panic("I'm myHandlerFunc and I refuse to serve!")
}

type MyHandler int

func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	panic("I'm MyHandler and I refuse to serve!")
}

func main() {
	http.HandleFunc("/myfunc", myHandleFunc)
	http.Handle("/myhandler", new(MyHandler))

	panic(http.ListenAndServe(":8080", nil))
}

Directing your browser to http://localhost:8080/myfunc and http://localhost:8080/myhandler results in HTTP 500 status: internal server error (or an empty response depending on where you check it).

The general idea is to use recover to "catch" the panics from handlers (spec: Handling panics). We can "wrap" handle functions or Handlers in a way that we first register a defer statement which is called even if the rest of the function panics, and in which we recover from the panicing state.

See these 2 functions:

func protectFunc(hf func(http.ResponseWriter,
	*http.Request)) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			r := recover()
			if r != nil {
				// hf() paniced, we just recovered from it.
				// Handle error somehow, serve custom error page.
				w.Write([]byte("Something went bad but I recovered and sent this!"))
			}
		}()
		hf(w, r)
	}
}

func protectHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			r := recover()
			if r != nil {
				// h.ServeHTTP() paniced, we just recovered from it.
				// Handle error somehow, serve custom error page.
				w.Write([]byte("Something went bad but I recovered and sent this!"))
			}
		}()
		h.ServeHTTP(w, r)
	})
}

The first one takes a function and returns one which calles the one we passed but recovers from panicing state if one was initiated.

The second one takes a Handler and returns another Handler which similarly calls the passed one but also handles panics and restores normal execution.

Now if we register handler functions and Handlers protected by these methods, the registered handlers will never panic (assuming the code after restoring normal execution does not panic):

http.HandleFunc("/myfunc-protected", protectFunc(myHandleFunc))
http.Handle("/myhandler-protected", protectHandler(new(MyHandler)))

Visiting http://localhost:8080/myfunc-protected and http://localhost:8080/myhandler-protected URLs resuls in HTTP 200 status (OK) with the message:

Something went bad but I recovered and sent this!

huangapple
  • 本文由 发表于 2015年3月3日 00:57:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/28815334.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定