package middleware import ( "bufio" "fmt" "io" "net" "net/http" ) const ( maxResponseBodyInLogs = 4096 // At most 4k bytes from response bodies in our logs. ) type badResponseLoggingWriter interface { http.ResponseWriter // Get the status code. StatusCode() int // Get the error while writing. WriteError() error } func newBadResponseLoggingWriter(rw http.ResponseWriter, buffer io.Writer) badResponseLoggingWriter { b := nonFlushingBadResponseLoggingWriter{ rw: rw, buffer: buffer, logBody: false, bodyBytesLeft: maxResponseBodyInLogs, statusCode: http.StatusOK, } if f, ok := rw.(http.Flusher); ok { return &flushingBadResponseLoggingWriter{b, f} } return &b } type nonFlushingBadResponseLoggingWriter struct { rw http.ResponseWriter buffer io.Writer logBody bool bodyBytesLeft int statusCode int writeError error // The error returned when downstream Write() fails. } // Extends nonFlushingBadResponseLoggingWriter that implements http.Flusher type flushingBadResponseLoggingWriter struct { nonFlushingBadResponseLoggingWriter f http.Flusher } // Unwrap method is used by http.ResponseController to get access to original http.ResponseWriter. func (writer *nonFlushingBadResponseLoggingWriter) Unwrap() http.ResponseWriter { return writer.rw } // Header returns the header map that will be sent by WriteHeader. // Implements ResponseWriter. func (writer *nonFlushingBadResponseLoggingWriter) Header() http.Header { return writer.rw.Header() } // WriteHeader writes the HTTP response header. func (writer *nonFlushingBadResponseLoggingWriter) WriteHeader(statusCode int) { writer.statusCode = statusCode if statusCode >= 500 || statusCode == 400 { writer.logBody = true } writer.rw.WriteHeader(statusCode) } // Writes HTTP response data. func (writer *nonFlushingBadResponseLoggingWriter) Write(data []byte) (int, error) { if writer.statusCode == 0 { // WriteHeader has (probably) not been called, so we need to call it with StatusOK to fulfill the interface contract. // https://godoc.org/net/http#ResponseWriter writer.WriteHeader(http.StatusOK) } // 204 No Content is a success response that indicates that the request has been successfully processed and that the response body is intentionally empty. if writer.statusCode == 204 { return 0, nil } n, err := writer.rw.Write(data) if writer.logBody { writer.captureResponseBody(data) } if err != nil { writer.writeError = err } return n, err } // Hijack hijacks the first response writer that is a Hijacker. func (writer *nonFlushingBadResponseLoggingWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { hj, ok := writer.rw.(http.Hijacker) if ok { return hj.Hijack() } return nil, nil, fmt.Errorf("cannot cast underlying response writer to Hijacker") } func (writer *nonFlushingBadResponseLoggingWriter) StatusCode() int { return writer.statusCode } func (writer *nonFlushingBadResponseLoggingWriter) WriteError() error { return writer.writeError } func (writer *flushingBadResponseLoggingWriter) Flush() { writer.f.Flush() } func (writer *nonFlushingBadResponseLoggingWriter) captureResponseBody(data []byte) { if len(data) > writer.bodyBytesLeft { _, _ = writer.buffer.Write(data[:writer.bodyBytesLeft]) _, _ = io.WriteString(writer.buffer, "...") writer.bodyBytesLeft = 0 writer.logBody = false } else { _, _ = writer.buffer.Write(data) writer.bodyBytesLeft -= len(data) } }