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

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

Can't track HTTP response code in middleware

问题

我正在使用go-openapi来根据Swagger配置生成一个HTTP服务器,并处理所有的处理程序。

我的中间件的架构应该是请求 -> 覆盖函数 -> 执行HTTP操作 -> 记录响应代码 -> 响应

这是我的中间件代码:

func (pm *PrometheusMetrics) HTTPMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		statusRecorder := &utils.StatusRecorder{ResponseWriter: w, StatusCode: 200}
		logrus.Infof("--> %v %v", r.Method, r.URL.Path)
		// statusRecorder := negroni.NewResponseWriter(w) <-- 尝试了这个解决方案,但没有起作用

		h.ServeHTTP(statusRecorder, r)

		logrus.Infof("<-- %v", statusRecorder.Status()) // 应该显示状态代码
		duration := time.Since(start)
		statusCode := strconv.Itoa(statusRecorder.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是由go-openapi生成的包
    // 一些设置...
    return setupGlobalMiddleware(api.Serve(setupMiddlewares), promMetrics)
}

statusRecorder的覆盖:

type StatusRecorder struct {
	http.ResponseWriter
	StatusCode int
}

// WriteHeader是用于记录状态代码的虚拟函数
func (rec *StatusRecorder) WriteHeader(statusCode int) {
	logrus.Infof("hello there: %v", statusCode)
	rec.StatusCode = statusCode
	rec.ResponseWriter.WriteHeader(statusCode)
}

正如你所看到的,我重写了http.HandlerWriteHeader函数,只是存储状态代码并在外部访问,然后调用原始的WriteHeader函数。问题在于,我的WriteHeader函数从未被调用。我无法弄清楚为什么没有调用它。

英文:

I'm using the go-openapi to generate from a swagger config an http server and handle all handlers.
The schema of my middleware would be request -&gt; override func -&gt; do http stuff -&gt; logs response code -&gt; response.

Here's my middleware:

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

		h.ServeHTTP(statusRecoder, r)

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

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

And here's how I pass the middleware:

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{&quot;*&quot;},
		AllowedOrigins:     []string{&quot;*&quot;},
		AllowedMethods:     []string{&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;PATCH&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;},
		MaxAge:             1000,
		OptionsPassthrough: false,
	})
	if log.GetLevel() &gt;= log.DebugLevel {
		corsMiddleware.Log = log.StandardLogger()
	}

	return corsMiddleware.Handler(middle)
}

And finally, where I call the setupGlobalMiddleware function:

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

The override of 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(&quot;hello there: %v&quot;, statusCode)
	rec.StatusCode = statusCode
	rec.ResponseWriter.WriteHeader(statusCode)
}

As you can see, I'm overriding the WriteHeader function from the http.Handler to just store the status code and access it outside, then call the original WriteHeader function. The problem here, is that my function WriteHeader is never called. And can't figure out why it's not.

答案1

得分: 1

问题在于,我的函数WriteHeader从未被调用。我无法弄清楚为什么没有调用它。

因为Go使用组合而不是继承。WriteHeader最常由对Write()的第一次调用自动调用。根据http.ResponseWriter文档

// 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) {
    if !rec.written {
        rec.WriteHeader(http.StatusOK)
    }
    return rec.Write(p)
}
英文:

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

Because Go uses composition, not inheretence. WriteHeader is most frequently called automatically by the first call to Write(). From the 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.

But since you haven't defined your own Write method, the embedded one (provided by the embedded instance of http.ResponseWriter) is called instead. When that Write() method is called, and it then calls WriteHeader, it calls the original WriteHeader method, not your version.

The solution is to also provide your own Write method, which wraps the original, and calls your version of WriteHeader when needed.

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(&quot;hello there: %v&quot;, 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)
}

答案2

得分: 0

好的,以下是翻译好的内容:

答案稍微复杂一些,如果你没有完整的包列表可能会难以理解。问题出在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)

    // 旧的声明在这里

	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()
	}

    // 注意变化,现在promMetrics.HTTPMiddleware在底部
	return corsMiddleware.Handler(promMetrics.HTTPMiddleware(middle))
	// return corsMiddleware.Handler(middle) <-- 旧的行
}
英文:

Well the answer was a little bit more complex to understand if you didn't have the entire package list.<br>
The problem was the way that go-openapi and go-swagger implement middlewares in the code and handle them. Many other installed packages have this problem.<br>
To make sure I retrieve properly the status code, I put the middleware at the rigth bottom of middlewares declaration.<br>

Code:

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{&quot;*&quot;},
		AllowedOrigins:     []string{&quot;*&quot;},
		AllowedMethods:     []string{&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;PATCH&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;},
		MaxAge:             1000,
		OptionsPassthrough: false,
	})
	if log.GetLevel() &gt;= 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) &lt;-- old line
}

huangapple
  • 本文由 发表于 2021年2月25日 19:02:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/66367237.html
匿名

发表评论

匿名网友

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

确定