【问题标题】:How to terminate request from a sub function in golang如何终止来自golang中子函数的请求
【发布时间】:2021-09-12 12:40:50
【问题描述】:

我想返回并终止 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!

【问题讨论】:

  • test 函数在哪里?你的意思是checkSomeThing

标签: go go-http


【解决方案1】:

始终尝试在 API 级别处理错误 - 将错误冒泡到调用堆栈中。但是,在某些情况下这是不可能的——也许您的处理程序是处理程序中间件链中的众多处理程序之一,而您不希望完成这些其他层。所以...


如果您想立即中止来自处理程序的请求,标准库支持panic 和恢复。

来自http.Handler docs

如果 ServeHTTP 发生恐慌,服务器(ServeHTTP 的调用者)会假设 恐慌的影响被隔离到活动请求中。它 恢复恐慌,将堆栈跟踪记录到服务器错误日志,以及 要么关闭网络连接,要么发送一个 HTTP/2 RST_STREAM, 取决于 HTTP 协议。 中止处理程序以便客户端看到 响应中断,但服务器没有记录错误,恐慌 值为 ErrAbortHandler。

http.ErrAbortHandler:

... 是用于中止处理程序的哨兵恐慌值。尽管 来自 ServeHTTP 的任何恐慌都会中止对客户端的响应,恐慌 使用 ErrAbortHandler 还会抑制将堆栈跟踪记录到 服务器的错误日志。

所以要在您的处理程序中执行此操作:

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

有一个问题...

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

forcibly flush缓冲区:

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

panic(http.ErrAbortHandler)

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

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("Recovered in handler")

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

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

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

【讨论】:

  • 谢谢。这就是我一直在寻找的。恢复方法对我来说效果很好。但是你说这不是一个好的解决方案。 req恢复方法都没有?
  • 像大多数软件决策一样,答案是“视情况而定”。如果您可以从处理程序中完全恢复 - 走那条路。 panic 不应替代错误处理。在某些情况下,通过调用堆栈冒泡错误(可能第三方层不在您的控制范围内)是不可行的,panicing 是您唯一的选择。
  • 恕我直言,这是一个非常合理的解决方案,因为我们无法使用当前标准 API 从处理程序中控制处理程序链(它可能为此目的有一个额外的返回参数)。
  • @mh-cbon 经过反思,我改变了主意。由于标准库对此提供支持,因此这不是一个完全“越界”的解决方案。相应地更新了答案并改进了恐慌恢复代码以仅恢复预期的恐慌,让意外的恐慌通过。
【解决方案2】:

根据 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, "Operation completed!", http.StatusOK)
    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")
}

【讨论】:

  • 它可以工作,但我想这样做的原因是单独的程序逻辑。有没有什么办法不强制我们err签入main()函数?
  • 这有点矫枉过正,但你可以panicnet/http ServeMux 处理程序检测到恐慌并继续为新请求提供服务,从而避免整个服务结束。
  • 你能发表你的答案吗? @colm.anseo
  • 但我不认为恐慌是一个好的解决方案。在使用 golang 编写服务器时,您应该从验证器函数返回错误,然后在用例或处理程序函数中处理它。在 python 中,您可以轻松地从任何地方引发验证错误。
猜你喜欢
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多