detect CloseNotify capability in accesslog and metrics

This commit is contained in:
mpl 2019-12-10 18:18:04 +01:00 committed by Traefiker Bot
parent 1d4f10bead
commit bdf4c6723f
5 changed files with 59 additions and 4 deletions

View file

@ -13,6 +13,20 @@ var (
_ middlewares.Stateful = &captureResponseWriter{} _ middlewares.Stateful = &captureResponseWriter{}
) )
type capturer interface {
http.ResponseWriter
Size() int64
Status() int
}
func newCaptureResponseWriter(rw http.ResponseWriter) capturer {
capt := &captureResponseWriter{rw: rw}
if _, ok := rw.(http.CloseNotifier); !ok {
return capt
}
return captureResponseWriterWithCloseNotify{capt}
}
// captureResponseWriter is a wrapper of type http.ResponseWriter // captureResponseWriter is a wrapper of type http.ResponseWriter
// that tracks request status and size // that tracks request status and size
type captureResponseWriter struct { type captureResponseWriter struct {
@ -21,6 +35,16 @@ type captureResponseWriter struct {
size int64 size int64
} }
type captureResponseWriterWithCloseNotify struct {
*captureResponseWriter
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone away.
func (r *captureResponseWriterWithCloseNotify) CloseNotify() <-chan bool {
return r.rw.(http.CloseNotifier).CloseNotify()
}
func (crw *captureResponseWriter) Header() http.Header { func (crw *captureResponseWriter) Header() http.Header {
return crw.rw.Header() return crw.rw.Header()
} }

View file

@ -49,7 +49,7 @@ func AddServiceFields(rw http.ResponseWriter, req *http.Request, next http.Handl
// AddOriginFields add origin fields // AddOriginFields add origin fields
func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) { func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
crw := &captureResponseWriter{rw: rw} crw := newCaptureResponseWriter(rw)
start := time.Now().UTC() start := time.Now().UTC()
next.ServeHTTP(crw, req) next.ServeHTTP(crw, req)

View file

@ -200,7 +200,7 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
core[ClientHost] = forwardedFor core[ClientHost] = forwardedFor
} }
crw := &captureResponseWriter{rw: rw} crw := newCaptureResponseWriter(rw)
next.ServeHTTP(crw, reqWithDataTable) next.ServeHTTP(crw, reqWithDataTable)

View file

@ -89,10 +89,10 @@ func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request)
}(labels) }(labels)
start := time.Now() start := time.Now()
recorder := &responseRecorder{rw, http.StatusOK} recorder := newResponseRecorder(rw)
m.next.ServeHTTP(recorder, req) m.next.ServeHTTP(recorder, req)
labels = append(labels, "code", strconv.Itoa(recorder.statusCode)) labels = append(labels, "code", strconv.Itoa(recorder.getCode()))
m.reqsCounter.With(labels...).Add(1) m.reqsCounter.With(labels...).Add(1)
m.reqDurationHistogram.With(labels...).Observe(time.Since(start).Seconds()) m.reqDurationHistogram.With(labels...).Observe(time.Since(start).Seconds())
} }

View file

@ -6,6 +6,23 @@ import (
"net/http" "net/http"
) )
type recorder interface {
http.ResponseWriter
http.Flusher
getCode() int
}
func newResponseRecorder(rw http.ResponseWriter) recorder {
rec := &responseRecorder{
ResponseWriter: rw,
statusCode: http.StatusOK,
}
if _, ok := rw.(http.CloseNotifier); !ok {
return rec
}
return responseRecorderWithCloseNotify{rec}
}
// responseRecorder captures information from the response and preserves it for // responseRecorder captures information from the response and preserves it for
// later analysis. // later analysis.
type responseRecorder struct { type responseRecorder struct {
@ -13,6 +30,20 @@ type responseRecorder struct {
statusCode int statusCode int
} }
type responseRecorderWithCloseNotify struct {
*responseRecorder
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone away.
func (r *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
func (r *responseRecorder) getCode() int {
return r.statusCode
}
// WriteHeader captures the status code for later retrieval. // WriteHeader captures the status code for later retrieval.
func (r *responseRecorder) WriteHeader(status int) { func (r *responseRecorder) WriteHeader(status int) {
r.ResponseWriter.WriteHeader(status) r.ResponseWriter.WriteHeader(status)