[Golang] 在中间件中获取HTTP响应状态码的两种方法

在使用 net/http 库时,我们可能会遇到无法直接获取 HTTP 响应状态码的问题。为了优化这一点,我们可以采用以下两种方案:

方案一:自定义 ResponseWriter

我们可以通过实现一个自定义的 ResponseWriter 来获取 HTTP 响应状态码。具体步骤如下:

  1. 创建一个结构体,嵌入 http.ResponseWriter 并添加一个字段用于存储状态码。
  2. 实现 WriteHeader 方法,在调用原始 ResponseWriterWriteHeader 方法之前,记录状态码。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
    "fmt"
    "net/http"
)

type CustomResponseWriter struct {
    http.ResponseWriter
    StatusCode int
}

func (w *CustomResponseWriter) WriteHeader(code int) {
    w.StatusCode = code
    w.ResponseWriter.WriteHeader(code)
}

func handler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusNotFound)
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        crw := &CustomResponseWriter{ResponseWriter: w}
        handler(crw, r)
        fmt.Printf("Response status code: %d\n", crw.StatusCode)
    })
    http.ListenAndServe(":8080", nil)
}

方案二:使用 httpsnoop

httpsnoop 是一个第三方库,可以帮助我们更方便地获取 HTTP 响应状态码。使用方法如下:

  1. 安装 httpsnoop 包:

    1
    
    go get -u github.com/felixge/httpsnoop
    
  2. 使用 httpsnoop.CaptureMetrics 包装处理函数,从返回的 Metrics 中获取状态码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
    "fmt"
    "net/http"

    "github.com/felixge/httpsnoop"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusNotFound)
    fmt.Fprintf(w, "Hello, World!")
}

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        m := httpsnoop.CaptureMetrics(next, w, r)

        fmt.Println("code: ", m.Code, "time: ", m.Duration, "length: ", m.Written)
    })
}

func main() {
    http.Handle("/", middleware(http.HandlerFunc(handler)))
    http.ListenAndServe(":8080", nil)
}

更高阶的使用方法

我们还可以使用 httpsnoop.Wraphttpsnoop.Hooks 来实现更高阶的功能,例如记录更多的请求和响应信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
    "fmt"
    "net/http"

    "github.com/felixge/httpsnoop"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusNotFound)
    fmt.Fprintf(w, "Hello, World!")
}

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        var _code int

        // 通过替换 WriteHeader 的实现,获取到 code
        _w := httpsnoop.Wrap(w, httpsnoop.Hooks{WriteHeader: func(headerFunc httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
            return func(code int) {
                _code = code

                headerFunc(code)
            }
        }})

        next.ServeHTTP(_w, r)

        fmt.Println("code: ", _code)
    })
}

func main() {
    http.Handle("/", middleware(http.HandlerFunc(handler)))
    http.ListenAndServe(":8080", nil)
}

通过以上两种简单方案和一种高阶方案,我们可以有效地获取 HTTP 响应状态码,从而更好地处理和记录请求的应答信息。