在Go语言中,可以从任何地方退出HTTP处理程序。

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

golang - Exit http handler from anywhere

问题

我正在使用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函数内部退出处理程序,而不必返回然后再返回到上一级,这在应用程序增长时会变得更糟。这仅仅是为了提高代码的可读性。

英文:

I'm using the net/http package and wondering how I can exit the handler from anywhere in the code. Say I have this code:

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")
}

Ideally I'd like to exit the handler from within the checkSomeThing function without having to return and then return again up a level, which will get worse as the application grows. This is purely for code readability.

答案1

得分: 5

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

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

为 panic 定义一个唯一的类型:

type httpError struct {
    status  int
    message string
}

编写一个用于 defer 语句的函数。该函数检查类型并根据需要处理错误。否则,函数会继续 panic。

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)
        }
    }
}

为 panic 调用编写一个辅助函数:

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()
}
英文:

The idiomatic approach is to check error returns up the call chain.

To exit the the handler from anywhere, use panic and recover following the pattern in the encoding/json package.

Define a unique type for panic:

type httpError struct {
	status  int
	message string
}

Write a function to be used in a defer statement. The function checks for the type and handles the error as appropriate. Otherwise, the function continues the panic.

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)
		}
	}
}

Write a helper function for the call to panic:

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

Use the functions like this:

func example() {
   exit(http.StatusBadRequest, "Bad!")
}

func someHandler(w http.ResponseWriter, r *http.Request) {
   defer handleExit(w)
   example()
}

答案2

得分: 2

我的回答:
首先,Golang中常见的模式是将错误作为返回值从被调用者传递回调用者。这样做在可读性和重用性方面有很多优势。副作用是需要进行很多if err != nil {return}的检查。

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

我要提出一个想法,我认为这在Golang的编码风格和模式中并不常见或标准。但我没有在网上看到任何关于这个的灾难性建议。让我们看看评论中会有什么反应,是否会说这个方法很糟糕。

你可以使用runtime.Goexit()来实现你想要的效果。处理程序只是等待另一个goroutine来完成工作。如果在goroutine中运行的内部代码想要中止处理过程,它可以调用Goexit()。它的优点是所有的defer语句仍然会执行。

这似乎只是Golang当前不支持的异常处理的一个弱化版本。但我还是提出来看看。

func handler(w http.ResponseWriter, r *http.Request) {

	var cleanExit bool = false
	var ch = make(chan bool)

    // 在一个goroutine中实现实际的处理程序
	go func() {
		defer close(ch)
		handlerImpl(w, r)
		cleanExit = true // 如果handlerImpl调用了goExit,这行不会执行
	}()

	// 等待goroutine退出
	<-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()
}
英文:

My answer:
First, the common pattern established in Golang is to have errors "bubble up" from callee back to caller as a return value. It has a lot of advantages with regards to readability and re-use. The side effect is that there's a lot of if err != nil {return} checks.

My suggestion if you really want to break from the norm

I'm going to pitch an idea, that I don't think is common or standard with respect to golang coding styles and patterns. But I didn't see anything online suggesting this was catastrophic. Let's see what I get in the comments to say this is awful.

You could use runtime.Goexit() to achieve what you want. The handler just waits on another goroutine to do the work. If the inner code running in the go-routine wants to abort processing, it can call Goexit(). It has the advantage that all defer statements will still execute.

This just seems like a weak version of exception handling that Golang currently doesn't support. But I'm throwing it out there.

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&#39;t execute
	}()

	// wait for goroutine to exit
	&lt;-ch

	if cleanExit {
		fmt.Println(&quot;Handler exited normally&quot;)
	} else {
		fmt.Println(&quot;Hanlder was aborted&quot;)
	}
}

func handlerImpl(w http.ResponseWriter, r *http.Request) {
	checkSomeThing(w, r)
}

func checkSomeThing(w http.ResponseWriter, r *http.Request) {
	http.Error(w, &quot;Bad Request!&quot;, http.StatusBadRequest)
	runtime.Goexit()
}

答案3

得分: 0

如果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)

	// 在这里添加对'checkSomething'的通用调用:
	handler := WrapWithCheck(mux)
	return handler
}

playground

注意:我在上面的playground中尝试使用httptest,但由于某种原因在playground中会发生死锁。如果你将这段代码复制/粘贴到一个sample.go文件中,并使用go run sample.go,它将正常工作。

英文:

If checkSomeThing() is specific to that route, you should probably keep going with the code sample you pasted.

If checkSomeThing() is a function common to all your routes (or to a subset of routes), you can choose a way to run a middleware before calling the handler for specific routes.

See for example this answer or this answer, or here is a way to do it using only code from the standard http package :

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(&quot;/foo/&quot;, handleFoo)
	mux.HandleFunc(&quot;/bar/&quot;, handleBar)
	mux.HandleFunc(&quot;/baz/&quot;, handleBaz)

	// add your common call to &#39;checkSomething&#39; here :
	handler := WrapWithCheck(mux)
	return handler
}

playground

note : I tried using httptest in the playground above, and for some reason it deadlocks in the playground. It works fine if you copy/paste this code in a sample.go file and use go run sample.go

huangapple
  • 本文由 发表于 2022年7月25日 02:36:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/73101045.html
匿名

发表评论

匿名网友

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

确定