diff --git a/cmd/generic-handlers.go b/cmd/generic-handlers.go
index e4bfb0ef8..2a731a95a 100644
--- a/cmd/generic-handlers.go
+++ b/cmd/generic-handlers.go
@@ -549,7 +549,7 @@ func addCustomHeaders(h http.Handler) http.Handler {
// part of the log entry, Error response XML and auditing.
// Set custom headers such as x-amz-request-id for each request.
w.Header().Set(xhttp.AmzRequestID, mustGetRequestID(UTCNow()))
- h.ServeHTTP(logger.NewResponseWriter(w), r)
+ h.ServeHTTP(xhttp.NewResponseRecorder(w), r)
})
}
diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go
index 1cc70b773..63facc700 100644
--- a/cmd/handler-utils.go
+++ b/cmd/handler-utils.go
@@ -350,7 +350,7 @@ func collectAPIStats(api string, f http.HandlerFunc) http.HandlerFunc {
globalHTTPStats.currentS3Requests.Inc(api)
defer globalHTTPStats.currentS3Requests.Dec(api)
- statsWriter := logger.NewResponseWriter(w)
+ statsWriter := xhttp.NewResponseRecorder(w)
f.ServeHTTP(statsWriter, r)
diff --git a/cmd/http-stats.go b/cmd/http-stats.go
index 5e00231fe..07bb1fc71 100644
--- a/cmd/http-stats.go
+++ b/cmd/http-stats.go
@@ -23,7 +23,7 @@ import (
"sync"
"sync/atomic"
- "github.com/minio/minio/internal/logger"
+ xhttp "github.com/minio/minio/internal/http"
"github.com/prometheus/client_golang/prometheus"
)
@@ -295,7 +295,7 @@ func (st *HTTPStats) toServerHTTPStats() ServerHTTPStats {
}
// Update statistics from http request and response data
-func (st *HTTPStats) updateStats(api string, r *http.Request, w *logger.ResponseWriter) {
+func (st *HTTPStats) updateStats(api string, r *http.Request, w *xhttp.ResponseRecorder) {
// Ignore non S3 requests
if strings.HasSuffix(r.URL.Path, minioReservedBucketPathWithSlash) {
return
diff --git a/cmd/http-tracer.go b/cmd/http-tracer.go
index 05fa725c4..3ec33382b 100644
--- a/cmd/http-tracer.go
+++ b/cmd/http-tracer.go
@@ -18,9 +18,7 @@
package cmd
import (
- "bytes"
"context"
- "io"
"net"
"net/http"
"reflect"
@@ -32,54 +30,9 @@ import (
"github.com/minio/madmin-go"
"github.com/minio/minio/internal/handlers"
- "github.com/minio/minio/internal/logger"
+ xhttp "github.com/minio/minio/internal/http"
)
-// recordRequest - records the first recLen bytes
-// of a given io.Reader
-type recordRequest struct {
- // Data source to record
- io.Reader
- // Response body should be logged
- logBody bool
- // Internal recording buffer
- buf bytes.Buffer
- // total bytes read including header size
- bytesRead int
-}
-
-func (r *recordRequest) Close() error {
- // no-op
- return nil
-}
-
-func (r *recordRequest) Read(p []byte) (n int, err error) {
- n, err = r.Reader.Read(p)
- r.bytesRead += n
-
- if r.logBody {
- r.buf.Write(p[:n])
- }
- if err != nil {
- return n, err
- }
- return n, err
-}
-
-func (r *recordRequest) BodySize() int {
- return r.bytesRead
-}
-
-// Return the bytes that were recorded.
-func (r *recordRequest) Data() []byte {
- // If body logging is enabled then we return the actual body
- if r.logBody {
- return r.buf.Bytes()
- }
- // ... otherwise we return
placeholder
- return logger.BodyPlaceHolder
-}
-
var ldapPwdRegex = regexp.MustCompile("(^.*?)LDAPPassword=([^&]*?)(&(.*?))?$")
// redact LDAP password if part of string
@@ -116,8 +69,8 @@ const contextTraceReqKey = contextTraceReqType("request-trace-info")
// Hold related tracing data of a http request, any handler
// can modify this struct to modify the trace information .
type traceCtxt struct {
- requestRecorder *recordRequest
- responseRecorder *logger.ResponseWriter
+ requestRecorder *xhttp.RequestRecorder
+ responseRecorder *xhttp.ResponseRecorder
funcName string
}
@@ -136,8 +89,8 @@ func httpTracer(h http.Handler) http.Handler {
r = r.WithContext(ctx)
// Setup a http request and response body recorder
- reqRecorder := &recordRequest{Reader: r.Body}
- respRecorder := logger.NewResponseWriter(w)
+ reqRecorder := &xhttp.RequestRecorder{Reader: r.Body}
+ respRecorder := xhttp.NewResponseRecorder(w)
tc.requestRecorder = reqRecorder
tc.responseRecorder = respRecorder
@@ -240,7 +193,7 @@ func httpTrace(f http.HandlerFunc, logBody bool) http.HandlerFunc {
}
tc.funcName = getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
- tc.requestRecorder.logBody = logBody
+ tc.requestRecorder.LogBody = logBody
tc.responseRecorder.LogAllBody = logBody
tc.responseRecorder.LogErrBody = true
diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go
index 37ce3c6a3..c3b3d236a 100644
--- a/cmd/object-handlers.go
+++ b/cmd/object-handlers.go
@@ -3542,7 +3542,7 @@ func (api objectAPIHandlers) PostRestoreObjectHandler(w http.ResponseWriter, r *
return
}
nr := httptest.NewRecorder()
- rw := logger.NewResponseWriter(nr)
+ rw := xhttp.NewResponseRecorder(nr)
rw.LogErrBody = true
rw.LogAllBody = true
rreq.SelectParameters.Evaluate(rw)
diff --git a/internal/http/request-recorder.go b/internal/http/request-recorder.go
new file mode 100644
index 000000000..c098624a5
--- /dev/null
+++ b/internal/http/request-recorder.go
@@ -0,0 +1,72 @@
+// Copyright (c) 2015-2022 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package http
+
+import (
+ "bytes"
+ "io"
+)
+
+// RequestRecorder - records the
+// of a given io.Reader
+type RequestRecorder struct {
+ // Data source to record
+ io.Reader
+ // Response body should be logged
+ LogBody bool
+
+ // internal recording buffer
+ buf bytes.Buffer
+ // total bytes read including header size
+ bytesRead int
+}
+
+// Close is a no operation closer
+func (r *RequestRecorder) Close() error {
+ // no-op
+ return nil
+}
+
+// Read reads from the internal reader and counts/save the body in the memory
+func (r *RequestRecorder) Read(p []byte) (n int, err error) {
+ n, err = r.Reader.Read(p)
+ r.bytesRead += n
+
+ if r.LogBody {
+ r.buf.Write(p[:n])
+ }
+ if err != nil {
+ return n, err
+ }
+ return n, err
+}
+
+// BodySize returns the body size if the currently read object
+func (r *RequestRecorder) BodySize() int {
+ return r.bytesRead
+}
+
+// Data returns the bytes that were recorded.
+func (r *RequestRecorder) Data() []byte {
+ // If body logging is enabled then we return the actual body
+ if r.LogBody {
+ return r.buf.Bytes()
+ }
+ // ... otherwise we return placeholder
+ return BodyPlaceHolder
+}
diff --git a/internal/http/response-recorder.go b/internal/http/response-recorder.go
new file mode 100644
index 000000000..c90f86828
--- /dev/null
+++ b/internal/http/response-recorder.go
@@ -0,0 +1,129 @@
+// Copyright (c) 2015-2022 MinIO, Inc.
+//
+// This file is part of MinIO Object Storage stack
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package http
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+)
+
+// ResponseRecorder - is a wrapper to trap the http response
+// status code and to record the response body
+type ResponseRecorder struct {
+ http.ResponseWriter
+ StatusCode int
+ // Log body of 4xx or 5xx responses
+ LogErrBody bool
+ // Log body of all responses
+ LogAllBody bool
+
+ TimeToFirstByte time.Duration
+ StartTime time.Time
+ // number of bytes written
+ bytesWritten int
+ // number of bytes of response headers written
+ headerBytesWritten int
+ // Internal recording buffer
+ headers bytes.Buffer
+ body bytes.Buffer
+ // Indicate if headers are written in the log
+ headersLogged bool
+}
+
+// NewResponseRecorder - returns a wrapped response writer to trap
+// http status codes for auditing purposes.
+func NewResponseRecorder(w http.ResponseWriter) *ResponseRecorder {
+ return &ResponseRecorder{
+ ResponseWriter: w,
+ StatusCode: http.StatusOK,
+ StartTime: time.Now().UTC(),
+ }
+}
+
+func (lrw *ResponseRecorder) Write(p []byte) (int, error) {
+ if !lrw.headersLogged {
+ // We assume the response code to be '200 OK' when WriteHeader() is not called,
+ // that way following Golang HTTP response behavior.
+ lrw.WriteHeader(http.StatusOK)
+ }
+ n, err := lrw.ResponseWriter.Write(p)
+ lrw.bytesWritten += n
+ if lrw.TimeToFirstByte == 0 {
+ lrw.TimeToFirstByte = time.Now().UTC().Sub(lrw.StartTime)
+ }
+ if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody {
+ // Always logging error responses.
+ lrw.body.Write(p)
+ }
+ if err != nil {
+ return n, err
+ }
+ return n, err
+}
+
+// Write the headers into the given buffer
+func (lrw *ResponseRecorder) writeHeaders(w io.Writer, statusCode int, headers http.Header) {
+ n, _ := fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode))
+ lrw.headerBytesWritten += n
+ for k, v := range headers {
+ n, _ := fmt.Fprintf(w, "%s: %s\n", k, v[0])
+ lrw.headerBytesWritten += n
+ }
+}
+
+// BodyPlaceHolder returns a dummy body placeholder
+var BodyPlaceHolder = []byte("")
+
+// Body - Return response body.
+func (lrw *ResponseRecorder) Body() []byte {
+ // If there was an error response or body logging is enabled
+ // then we return the body contents
+ if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody {
+ return lrw.body.Bytes()
+ }
+ // ... otherwise we return the place holder
+ return BodyPlaceHolder
+}
+
+// WriteHeader - writes http status code
+func (lrw *ResponseRecorder) WriteHeader(code int) {
+ if !lrw.headersLogged {
+ lrw.StatusCode = code
+ lrw.writeHeaders(&lrw.headers, code, lrw.ResponseWriter.Header())
+ lrw.headersLogged = true
+ lrw.ResponseWriter.WriteHeader(code)
+ }
+}
+
+// Flush - Calls the underlying Flush.
+func (lrw *ResponseRecorder) Flush() {
+ lrw.ResponseWriter.(http.Flusher).Flush()
+}
+
+// Size - returns the number of bytes written
+func (lrw *ResponseRecorder) Size() int {
+ return lrw.bytesWritten
+}
+
+// HeaderSize - returns the number of bytes of response headers written
+func (lrw *ResponseRecorder) HeaderSize() int {
+ return lrw.headerBytesWritten
+}
diff --git a/internal/logger/audit.go b/internal/logger/audit.go
index f0a8b0c30..e23de5ba1 100644
--- a/internal/logger/audit.go
+++ b/internal/logger/audit.go
@@ -18,10 +18,8 @@
package logger
import (
- "bytes"
"context"
"fmt"
- "io"
"net/http"
"strconv"
"time"
@@ -32,108 +30,6 @@ import (
"github.com/minio/minio/internal/logger/message/audit"
)
-// ResponseWriter - is a wrapper to trap the http response status code.
-type ResponseWriter struct {
- http.ResponseWriter
- StatusCode int
- // Log body of 4xx or 5xx responses
- LogErrBody bool
- // Log body of all responses
- LogAllBody bool
-
- TimeToFirstByte time.Duration
- StartTime time.Time
- // number of bytes written
- bytesWritten int
- // number of bytes of response headers written
- headerBytesWritten int
- // Internal recording buffer
- headers bytes.Buffer
- body bytes.Buffer
- // Indicate if headers are written in the log
- headersLogged bool
-}
-
-// NewResponseWriter - returns a wrapped response writer to trap
-// http status codes for auditing purposes.
-func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
- return &ResponseWriter{
- ResponseWriter: w,
- StatusCode: http.StatusOK,
- StartTime: time.Now().UTC(),
- }
-}
-
-func (lrw *ResponseWriter) Write(p []byte) (int, error) {
- if !lrw.headersLogged {
- // We assume the response code to be '200 OK' when WriteHeader() is not called,
- // that way following Golang HTTP response behavior.
- lrw.WriteHeader(http.StatusOK)
- }
- n, err := lrw.ResponseWriter.Write(p)
- lrw.bytesWritten += n
- if lrw.TimeToFirstByte == 0 {
- lrw.TimeToFirstByte = time.Now().UTC().Sub(lrw.StartTime)
- }
- if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody {
- // Always logging error responses.
- lrw.body.Write(p)
- }
- if err != nil {
- return n, err
- }
- return n, err
-}
-
-// Write the headers into the given buffer
-func (lrw *ResponseWriter) writeHeaders(w io.Writer, statusCode int, headers http.Header) {
- n, _ := fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode))
- lrw.headerBytesWritten += n
- for k, v := range headers {
- n, _ := fmt.Fprintf(w, "%s: %s\n", k, v[0])
- lrw.headerBytesWritten += n
- }
-}
-
-// BodyPlaceHolder returns a dummy body placeholder
-var BodyPlaceHolder = []byte("")
-
-// Body - Return response body.
-func (lrw *ResponseWriter) Body() []byte {
- // If there was an error response or body logging is enabled
- // then we return the body contents
- if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody {
- return lrw.body.Bytes()
- }
- // ... otherwise we return the place holder
- return BodyPlaceHolder
-}
-
-// WriteHeader - writes http status code
-func (lrw *ResponseWriter) WriteHeader(code int) {
- if !lrw.headersLogged {
- lrw.StatusCode = code
- lrw.writeHeaders(&lrw.headers, code, lrw.ResponseWriter.Header())
- lrw.headersLogged = true
- lrw.ResponseWriter.WriteHeader(code)
- }
-}
-
-// Flush - Calls the underlying Flush.
-func (lrw *ResponseWriter) Flush() {
- lrw.ResponseWriter.(http.Flusher).Flush()
-}
-
-// Size - returns the number of bytes written
-func (lrw *ResponseWriter) Size() int {
- return lrw.bytesWritten
-}
-
-// HeaderSize - returns the number of bytes of response headers written
-func (lrw *ResponseWriter) HeaderSize() int {
- return lrw.headerBytesWritten
-}
-
const contextAuditKey = contextKeyType("audit-entry")
// SetAuditEntry sets Audit info in the context.
@@ -197,13 +93,13 @@ func AuditLog(ctx context.Context, w http.ResponseWriter, r *http.Request, reqCl
headerBytes int64
)
- var st *ResponseWriter
+ var st *xhttp.ResponseRecorder
switch v := w.(type) {
- case *ResponseWriter:
+ case *xhttp.ResponseRecorder:
st = v
case *gzhttp.GzipResponseWriter:
// the writer may be obscured by gzip response writer
- if rw, ok := v.ResponseWriter.(*ResponseWriter); ok {
+ if rw, ok := v.ResponseWriter.(*xhttp.ResponseRecorder); ok {
st = rw
}
}