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

How to terminate request from a sub function in golang

我想 return 并终止 checkSomeThing 函数中的请求。但问题是进程继续并且 return 没有响应,除非到达 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!

根据 http.Error 文档

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.

因此在执行 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")
}

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


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

来自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.

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.

因此在您的处理程序中执行此操作:

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
}

在上面的 panic-recovery 代码中,http.ServeHTTP 永远不会知道有 panic - 并自然地刷新请求缓冲区。