golang-从任何地方退出http处理程序



我正在使用net/http包,想知道如何从代码中的任何位置退出处理程序。假设我有这个代码:

func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request){
err := checkSomeThing(w, r)
if err != nil {
return
}
fmt.Println("End of Handler.")
return
}
func checkSomeThing(w http.ResponseWriter, r *http.Request) error{
http.Error(w, "Bad Request!", http.StatusBadRequest) 

return errors.New("bad request")
}

理想情况下,我希望从checkSomeThing函数中退出处理程序,而不必返回,然后再返回一个级别,随着应用程序的增长,情况会变得更糟。这纯粹是为了代码的可读性。

惯用方法是检查调用链上的错误返回。

要从任何地方退出处理程序,请按照encoding/json包中的模式使用panic和recover。

定义恐慌的唯一类型:

type httpError struct {
status  int
message string
}

编写一个要在defer语句中使用的函数。该函数检查类型并根据需要处理错误。否则,该函数将继续引发恐慌。

func handleExit(w http.ResponseWriter) {
if r := recover(); r != nil {
if he, ok := r.(httpError); ok {
http.Error(w, he.message, he.status)
} else {
panic(r)
}
}
}

为恐慌调用编写一个助手函数:

func exit(status int, message string) {
panic(httpError{status: status, message: message})
}

使用以下功能:

func example() {
exit(http.StatusBadRequest, "Bad!")
}
func someHandler(w http.ResponseWriter, r *http.Request) {
defer handleExit(w)
example()
}

我的答案:首先,在果朗建立的常见模式是有错误"冒泡";从被调用者返回到调用者作为返回值。它在可读性和可重用性方面有很多优点。副作用是有很多if err != nil {return}检查。

如果你真的想打破常规,我的建议

我将提出一个想法,我认为这个想法在golang编码风格和模式方面并不常见或标准。但我在网上没有看到任何迹象表明这是灾难性的。让我们看看我在评论中得到了什么,说这太可怕了。

您可以使用运行时。Goexit((来实现您想要的。处理程序只是等待另一个goroutine来完成工作。如果go例程中运行的内部代码想要中止处理,它可以调用Goexit((。它的优点是所有defer语句仍将执行。

这似乎只是Golang目前不支持的一个弱版本的异常处理。但我要把它扔出去。

func handler(w http.ResponseWriter, r *http.Request) {
var cleanExit bool = false
var ch = make(chan bool)
// the actual handler implementation in a goroutine
go func() {
defer close(ch)
handlerImpl(w, r)
cleanExit = true // if handlerImpl invokes goExit, this line doesn't execute
}()
// wait for goroutine to exit
<-ch
if cleanExit {
fmt.Println("Handler exited normally")
} else {
fmt.Println("Hanlder was aborted")
}
}
func handlerImpl(w http.ResponseWriter, r *http.Request) {
checkSomeThing(w, r)
}
func checkSomeThing(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad Request!", http.StatusBadRequest)
runtime.Goexit()
}

如果checkSomeThing()特定于该路由,那么您可能应该继续使用粘贴的代码示例。

如果checkSomeThing()是所有路由(或路由的子集(通用的函数,那么在调用特定路由的处理程序之前,可以选择运行中间件的方法。

例如,请参阅这个答案或这个答案,或者这里有一种只使用标准http包中的代码的方法:

func checkSomething(...) error {
...
}
func WrapWithCheck(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
err := checkSomething(w, req)
if err != nil {
return
}
handler.ServeHTTP(w, req)
})
}
func setupRouter() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/foo/", handleFoo)
mux.HandleFunc("/bar/", handleBar)
mux.HandleFunc("/baz/", handleBaz)
// add your common call to 'checkSomething' here :
handler := WrapWithCheck(mux)
return handler
}

操场

注意:我尝试在上面的操场上使用httptest,但由于某种原因,它在操场上陷入僵局。如果您将此代码复制/粘贴到sample.go文件中并使用go run sample.go,则效果良好

最新更新