英文:
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.Handler
的WriteHeader
函数,只是存储状态代码并在外部访问,然后调用原始的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 -> override func -> do http stuff -> logs response code -> 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 := &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())
})
}
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{"*"},
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)
}
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("hello there: %v", 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("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)
}
答案2
得分: 0
好的,以下是翻译好的内容:
答案稍微复杂一些,如果你没有完整的包列表可能会难以理解。问题出在go-openapi
和go-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{"*"},
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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论