如何在Golang中终止来自子函数的请求

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

How to terminate request from a sub function in golang

问题

我想在checkSomeThing函数中返回并终止请求。但问题是进程会继续执行,直到到达main()的末尾才返回响应。以下是我的代码:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", handler)
	// ...

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

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

	http.Error(w, "Operation completed!", http.StatusOK)
	fmt.Println("End of Handler.")
}

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

	http.Error(w, "Bad Request!", http.StatusBadRequest)
	return
}

运行程序后,输出如下:

// Output:    
< HTTP/1.1 400 Bad Request
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Sun, 12 Sep 2021 12:38:49 GMT
< Content-Length: 34
< 
Bad Request!
Operation completed!

请注意,我只翻译了你提供的代码部分。

英文:

I want to return and terminate request that comes in checkSomeThing function. But the problem is the process continues and doesn't return response unless reaches the end of the main(). here's my code:

package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
)

func main() {
	http.HandleFunc(&quot;/&quot;, handler)
	// ...

	http.ListenAndServe(&quot;:8080&quot;, nil)
}

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

	http.Error(w, &quot;Operation completed!&quot;, http.StatusOK)
	fmt.Println(&quot;End of Handler.&quot;)
}

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

	http.Error(w, &quot;Bad Request!&quot;, http.StatusBadRequest)
	return
}

After run the program here's the output:

// Output:    
&lt; HTTP/1.1 400 Bad Request
&lt; Content-Type: text/plain; charset=utf-8
&lt; X-Content-Type-Options: nosniff
&lt; Date: Sun, 12 Sep 2021 12:38:49 GMT
&lt; Content-Length: 34
&lt; 
Bad Request!
Operation completed!

答案1

得分: 2

始终尝试在API级别处理错误-将错误向上冒泡到调用堆栈。然而,有些情况下这是不可能的-也许你的处理程序是处理程序链中的一个,你不希望其他层完成。所以...

如果你想立即中止处理程序的请求,标准库支持panicrecovery

根据http.Handler文档

如果ServeHTTP发生panic,服务器(ServeHTTP的调用者)假设panic的影响仅限于活动请求。它会恢复panic,将堆栈跟踪记录到服务器错误日志,并根据HTTP协议关闭网络连接或发送HTTP/2 RST_STREAM。为了中止处理程序,使客户端看到中断的响应但服务器不记录错误,请使用值ErrAbortHandler进行panic。

以及http.ErrAbortHandler

...是一个用于中止处理程序的特殊panic值。虽然来自ServeHTTP的任何panic都会中止对客户端的响应,但是使用ErrAbortHandler进行panic还会抑制将堆栈跟踪记录到服务器错误日志中。

所以在你的处理程序中可以这样做:

func checkSomeThing(w http.ResponseWriter, r *http.Request) {
    http.Error(w, "Bad Request!", http.StatusBadRequest)
    panic(http.ErrAbortHandler) // 终止请求(以及链中的其他处理程序)
}

这里有一个注意事项...

对客户端的写入将被缓冲-而panic可能会导致这些缓冲的写入丢失。

强制刷新缓冲区:

if f, ok := w.(http.Flusher); ok {
    f.Flush()
}

panic(http.ErrAbortHandler)

或者你可以添加自己的panic恢复。只需确保在恢复过程中“重新引发”任何panic-如果它们不是由你引起的-这样它们就不会丢失:

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

    defer func() {
        r := recover()

        switch r {

        case nil: // 没有panic

        // (2) 在这里...
        case http.ErrAbortHandler:
            log.Println("Recovered in handler")

        // (2) 或者在这里...
        default:
            panic(r) // 重新引发任何意外的panic
        }
    }()
    checkSomeThing(w, r)  // (1) 在这里发生panic...

    // (3) ... 这部分永远不会运行
}

在上面的panic恢复代码中,http.ServeHTTP将永远不会知道发生了panic-并自然地刷新请求缓冲区。

英文:

Always try to handle errors at the API level - bubbling errors up the call-stack. There are, however, cases where this is not possible - maybe your handler is one of many in a middleware chain of handlers, and you don't want those other layers to complete. So...


If you want to immediately abort a request from your handler, the standard library supports panic & recovery.

From the http.Handler docs:

> If ServeHTTP panics, the server (the caller of ServeHTTP) assumes that
> the effect of the panic was isolated to the active request. It
> recovers the panic, logs a stack trace to the server error log, and
> either closes the network connection or sends an HTTP/2 RST_STREAM,
> depending on the HTTP protocol. To abort a handler so the client sees
> an interrupted response but the server doesn't log an error, panic
> with the value ErrAbortHandler.

>

and http.ErrAbortHandler:

> ... is a sentinel panic value to abort a handler. While
> any panic from ServeHTTP aborts the response to the client, panicking
> with ErrAbortHandler also suppresses logging of a stack trace to the
> server's error log.

so to do this in your handler:

func checkSomeThing(w http.ResponseWriter, r *http.Request) {
	http.Error(w, &quot;Bad Request!&quot;, http.StatusBadRequest)
	panic(http.ErrAbortHandler) // terminate request (and any more handlers in the chain)
}

There is a catch...

Writes to client will be buffered - and panicing will likely cause these buffered writes to be lost.

To forcibly flush the buffer:

if f, ok := w.(http.Flusher); ok {
    f.Flush()
}

panic(http.ErrAbortHandler)

or you can add your own panic recovery. Just ensure, in the recovery, to "re-raise" any panics - if they were not cause by you - so they won't be lost:

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

    defer func() {
	    r := recover()

	    switch r {

	    case nil: // no panic

        // (2) goes here...
	    case http.ErrAbortHandler:
		    log.Println(&quot;Recovered in handler&quot;)

        // (2) or here...
	    default:
		    panic(r) // re-raise any unexpected panic
	    }
    }()
	checkSomeThing(w, r)  // (1) panic here ...

    // (3) ... and this never runs
}

in the above panic-recovery code, http.ServeHTTP will never know there was a panic - and flush the request buffers naturally.

答案2

得分: 1

根据http.Error文档的说明:

Error使用指定的错误消息和HTTP代码回复请求。它不会结束请求;调用者应确保不再对w进行进一步的写入操作。错误消息应为纯文本。

因此,在执行checkSomeThing函数后,它只是在responsewriter中写入错误字符串,并继续处理其他操作。

下面是你代码的一个可工作版本:

package main

import (
	"errors"
	"fmt"
	"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
	}

	http.Error(w, "操作完成!", http.StatusOK)
	fmt.Println("处理程序结束。")
	return
}

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

	http.Error(w, "错误的请求!", http.StatusBadRequest)
	return errors.New("错误的请求")
}
英文:

According to the http.Error documentation
> Error replies to the request with the specified error message and HTTP code.
It does not otherwise end the request; the caller should ensure no further writes are done to w. The error message should be plain text.

So after executing the checkSomeThing function it just writing the error string in responsewriter and continuing processing further operations.

A working version of your code is given below:

package main

import (
	&quot;errors&quot;
	&quot;fmt&quot;
	&quot;net/http&quot;
)

func main() {
	http.HandleFunc(&quot;/&quot;, handler)
	// ...

	http.ListenAndServe(&quot;:8080&quot;, nil)
}

func handler(w http.ResponseWriter, r *http.Request){
	err := checkSomeThing(w, r)
	if err != nil {
		return
	}

	http.Error(w, &quot;Operation completed!&quot;, http.StatusOK)
	fmt.Println(&quot;End of Handler.&quot;)
	return
}

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

	http.Error(w, &quot;Bad Request!&quot;, http.StatusBadRequest)
	return errors.New(&quot;bad request&quot;)
}

huangapple
  • 本文由 发表于 2021年9月12日 20:40:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/69151510.html
匿名

发表评论

匿名网友

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

确定