无法在中间件中跟踪 HTTP 响应代码

Can't track HTTP response code in middleware

我正在使用 go-openapi 从 swagger 配置生成一个 http 服务器并处理所有处理程序。 我的中间件的模式是 request -> override func -> do http stuff -> logs response code -> response.

这是我的中间件:

func (pm *PrometheusMetrics) HTTPMiddleware(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        statusRecoder := &utils.StatusRecorder{ResponseWriter: w, StatusCode: 200}
        logrus.Infof("--> %v %v", r.Method, r.URL.Path)
        // statusRecoder := negroni.NewResponseWriter(w) <-- tried this solution, didn't worked

        h.ServeHTTP(statusRecoder, r)

        logrus.Infof("<-- %v", statusRecoder.Status()) // should display status code
        duration := time.Since(start)
        statusCode := strconv.Itoa(statusRecoder.Status())

        pm.PushHTTPMetrics(r.URL.Path, statusCode, duration.Seconds())
    })
}

下面是我传递中间件的方式:

func setupGlobalMiddleware(handler http.Handler, promMetrics *apihandlers.PrometheusMetrics) http.Handler {
    middle := interpose.New()
    middle.UseHandler(handler)

    recoveryMiddleware := recovr.New()
    middle.Use(recoveryMiddleware)

    logrusMiddleware := interposeMiddleware.NegroniLogrus()
    middle.Use(logrusMiddleware)
    middle.Use(func(h http.Handler) http.Handler {
        return promMetrics.HTTPMiddleware(h)
    })

    corsMiddleware := cors.New(cors.Options{
        AllowedHeaders:     []string{"*"},
        AllowedOrigins:     []string{"*"},
        AllowedMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
        MaxAge:             1000,
        OptionsPassthrough: false,
    })
    if log.GetLevel() >= log.DebugLevel {
        corsMiddleware.Log = log.StandardLogger()
    }

    return corsMiddleware.Handler(middle)
}

最后,我调用 setupGlobalMiddleware 函数的地方:

func configureAPI(api *operations.KubeesAPI) http.Handler { // operations is package generated by go-openapi
    // Some setup...
    return setupGlobalMiddleware(api.Serve(setupMiddlewares), promMetrics)
}

statusRecoder的覆盖:

type StatusRecorder struct {
    http.ResponseWriter
    StatusCode int
}

// WriteHeader is the fake function to record status code
func (rec *StatusRecorder) WriteHeader(statusCode int) {
    logrus.Infof("hello there: %v", statusCode)
    rec.StatusCode = statusCode
    rec.ResponseWriter.WriteHeader(statusCode)
}

如您所见,我重写了 http.Handler 中的 WriteHeader 函数以仅存储状态代码并在外部访问它,然后调用原始 WriteHeader 函数。这里的问题是,我的函数 WriteHeader 从未被调用过。并且无法弄清楚为什么不是。

The problem here, is that my function WriteHeader is never called. And can't figure out why it's not.

因为 Go 使用组合,而不是继承。 WriteHeader 最常被第一次调用 Write() 自动调用。来自 http.ResponseWriter documentation:

// Write writes the data to the connection as part of an HTTP reply.
//
// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType. Additionally, if the total size of all written
// data is under a few KB and there are no Flush calls, the
// Content-Length header is added automatically.

但是由于您还没有定义自己的 Write 方法,因此调用了嵌入式方法(由 http.ResponseWriter 的嵌入式实例提供)。当那个 Write() 方法被调用,然后调用 WriteHeader 时,它调用原始的 WriteHeader 方法,而不是你的版本。

解决方案是也提供你自己的Write方法,它包装了原来的,并在需要时调用你的WriteHeader版本。

type StatusRecorder struct {
    http.ResponseWriter
    StatusCode int
    written bool
}

// WriteHeader is the fake function to record status code
func (rec *StatusRecorder) WriteHeader(statusCode int) {
    rec.written = true
    logrus.Infof("hello there: %v", statusCode)
    rec.StatusCode = statusCode
    rec.ResponseWriter.WriteHeader(statusCode)
}

func (rec *StatusRecorder) Write(p []byte) (int, error) {method
    if !rec.written {
        rec.WriteHeader(http.StatusOK)
    }
    return rec.Write(p)
}

好吧,如果您没有完整的软件包列表,那么答案会稍微复杂一些。
问题是 go-openapigo-swagger 在代码中实现中间件并处理它们的方式。许多其他已安装的软件包都有这个问题。
为了确保我正确检索状态代码,我将中间件放在中间件声明的右下角。

代码:

func setupGlobalMiddleware(handler http.Handler, promMetrics *apihandlers.PrometheusMetrics) http.Handler {
    middle := interpose.New()
    middle.UseHandler(handler)

    recoveryMiddleware := recovr.New()
    middle.Use(recoveryMiddleware)

    logrusMiddleware := interposeMiddleware.NegroniLogrus()
    middle.Use(logrusMiddleware)

    // the old declaration was here

    corsMiddleware := cors.New(cors.Options{
        AllowedHeaders:     []string{"*"},
        AllowedOrigins:     []string{"*"},
        AllowedMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
        MaxAge:             1000,
        OptionsPassthrough: false,
    })
    if log.GetLevel() >= log.DebugLevel {
        corsMiddleware.Log = log.StandardLogger()
    }

    // notice the changement where the promMetrics.HTTPMiddleware is now in the bottom
    return corsMiddleware.Handler(promMetrics.HTTPMiddleware(middle))
    // return corsMiddleware.Handler(middle) <-- old line
}