mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-07-13 00:52:01 -04:00
groupware: move plain http functions to pkg/http
* introduce a new toplevel package pkg/http for plain HTTP related functions * move these functions from pkg/jmap to pkg/http, including tests: - PeekResponse - DumpHttpRequest - DumpHttpResponse
This commit is contained in:
169
pkg/http/http.go
Normal file
169
pkg/http/http.go
Normal file
@@ -0,0 +1,169 @@
|
||||
// Provides common utility functions for HTTP.
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type readCloser struct {
|
||||
io.Reader
|
||||
closer func() error
|
||||
}
|
||||
|
||||
func (c readCloser) Close() error {
|
||||
if c.closer != nil {
|
||||
return c.closer()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func combinedReader(peekBuffer *bytes.Buffer, body io.Reader) io.Reader {
|
||||
if body != nil && body != http.NoBody {
|
||||
if peekBuffer != nil {
|
||||
return io.MultiReader(peekBuffer, body)
|
||||
} else {
|
||||
return body
|
||||
}
|
||||
} else {
|
||||
if peekBuffer != nil {
|
||||
return peekBuffer
|
||||
} else {
|
||||
return http.NoBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reads up to <size> amount of bytes from the response, and returns a ReadCloser that concatenates over those
|
||||
// bytes as well as the rest of the bytes streamed in <body>, to ensure that the whole response can still be
|
||||
// read.
|
||||
//
|
||||
// Note that if <size> is 0, then this function does nothing.
|
||||
//
|
||||
// Furthermore, this function never closes the <body> Reader, that is left to the caller.
|
||||
//
|
||||
// # Parameters
|
||||
// - body: the HTTP response body, which may be nil or http.NoBody
|
||||
// - size: the number of bytes that will be read from the beginning of the response body,
|
||||
// which may be 0 (in which case this function won't do anything)
|
||||
//
|
||||
// # Return values
|
||||
// - a Reader that provides the full response, including the first bytes that were peeked
|
||||
// up to <size> bytes of the beginning of the response body
|
||||
// - the first <size> bytes of the response
|
||||
// - a boolean: whether the peeked bytes contain the full response or whether it's truncated
|
||||
// - an error if an error occured while reading the body response
|
||||
func PeekResponse(body io.ReadCloser, size int64) (io.ReadCloser, []byte, bool, error) {
|
||||
var peekBuffer *bytes.Buffer = nil
|
||||
truncated := false
|
||||
peek := []byte{}
|
||||
if body != nil && body != http.NoBody && size > 0 {
|
||||
peekBuffer = new(bytes.Buffer)
|
||||
limitedReader := io.LimitReader(body, size)
|
||||
tee := io.TeeReader(limitedReader, peekBuffer)
|
||||
if _, err := io.Copy(io.Discard, tee); err != nil {
|
||||
return nil, peek, false, err
|
||||
}
|
||||
|
||||
if int64(peekBuffer.Len()) >= size {
|
||||
truncated = true
|
||||
}
|
||||
|
||||
peek = peekBuffer.Bytes()
|
||||
|
||||
return readCloser{
|
||||
closer: body.Close,
|
||||
Reader: combinedReader(peekBuffer, body),
|
||||
}, peek, truncated, nil
|
||||
} else {
|
||||
return body, peek, truncated, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts information from an HTTP request and passes it to a function, typically for logging.
|
||||
//
|
||||
// It also extracts the <maxBodySize> first bytes of the request body, if maxBodySize is > 0.
|
||||
func DumpHttpRequest(req *http.Request, maxBodySize int64, closure func(method string, uri string, content string, truncated bool)) error {
|
||||
var logBuilder bytes.Buffer
|
||||
uri := ""
|
||||
if req.URL != nil {
|
||||
uri = req.URL.String()
|
||||
}
|
||||
fmt.Fprintf(&logBuilder, "%s %s\n", req.Method, uri)
|
||||
for key, values := range req.Header {
|
||||
if key == "Authorization" {
|
||||
continue
|
||||
}
|
||||
for _, value := range values {
|
||||
fmt.Fprintf(&logBuilder, "%s: %s\n", key, value)
|
||||
}
|
||||
}
|
||||
truncated := false
|
||||
if maxBodySize >= 0 && req.Body != nil && req.Body != http.NoBody {
|
||||
peekBuffer := new(bytes.Buffer)
|
||||
limitedReader := io.LimitReader(req.Body, maxBodySize)
|
||||
tee := io.TeeReader(limitedReader, peekBuffer)
|
||||
if _, err := io.Copy(io.Discard, tee); err != nil {
|
||||
return fmt.Errorf("failed to peek at the request body for tracing: %v", err)
|
||||
} else {
|
||||
logBuilder.Write(peekBuffer.Bytes())
|
||||
if int64(peekBuffer.Len()) >= maxBodySize {
|
||||
truncated = true
|
||||
}
|
||||
fullBodyReader := io.MultiReader(peekBuffer, req.Body)
|
||||
req.Body = io.NopCloser(fullBodyReader)
|
||||
}
|
||||
}
|
||||
closure(req.Method, uri, logBuilder.String(), truncated)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extracts information from an HTTP response and passes it to a function, typically for logging.
|
||||
//
|
||||
// It also extracts the <maxBodySize> first bytes of the response body, if maxBodySize is > 0.
|
||||
func DumpHttpResponse(resp *http.Response, body io.ReadCloser, maxBodySize int64,
|
||||
closure func(method string, uri string, content string, truncated bool),
|
||||
) (io.ReadCloser, error) {
|
||||
var logBuilder bytes.Buffer
|
||||
fmt.Fprintf(&logBuilder, "%s\n", resp.Status)
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
fmt.Fprintf(&logBuilder, "%s: %s\n", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
var peekBuffer *bytes.Buffer = nil
|
||||
truncated := false
|
||||
if body != nil && body != http.NoBody && maxBodySize > 0 {
|
||||
peekBuffer = new(bytes.Buffer)
|
||||
limitedReader := io.LimitReader(body, maxBodySize)
|
||||
tee := io.TeeReader(limitedReader, peekBuffer)
|
||||
if _, err := io.Copy(io.Discard, tee); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if int64(peekBuffer.Len()) >= maxBodySize {
|
||||
truncated = true
|
||||
}
|
||||
|
||||
logBuilder.Write(peekBuffer.Bytes())
|
||||
}
|
||||
|
||||
req := resp.Request
|
||||
if req != nil {
|
||||
closure(req.Method, req.URL.String(), logBuilder.String(), truncated)
|
||||
} else {
|
||||
closure("", "", logBuilder.String(), truncated)
|
||||
}
|
||||
|
||||
if peekBuffer != nil {
|
||||
return readCloser{
|
||||
closer: body.Close,
|
||||
Reader: combinedReader(peekBuffer, body),
|
||||
}, nil
|
||||
} else {
|
||||
return body, nil
|
||||
}
|
||||
}
|
||||
92
pkg/http/http_test.go
Normal file
92
pkg/http/http_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPeekResponse(t *testing.T) {
|
||||
require := require.New(t)
|
||||
n := 6
|
||||
text := "hello, world"
|
||||
|
||||
require.Less(n, len(text))
|
||||
body, peek, truncated, err := PeekResponse(io.NopCloser(strings.NewReader(text)), int64(n))
|
||||
require.NoError(err)
|
||||
require.True(truncated)
|
||||
require.Equal(text[:n], string(peek))
|
||||
buf := new(strings.Builder)
|
||||
_, err = io.Copy(buf, body)
|
||||
require.NoError(err)
|
||||
require.Equal(text, buf.String())
|
||||
}
|
||||
|
||||
func TestDumpHttpResponse(t *testing.T) {
|
||||
require := require.New(t)
|
||||
n := 6
|
||||
text := "hello, world"
|
||||
|
||||
h := http.Header{}
|
||||
h.Set("testing", "true")
|
||||
h.Add("aaa", "1")
|
||||
h.Add("aaa", "2")
|
||||
|
||||
resp := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Header: h,
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
body, err := DumpHttpResponse(resp, io.NopCloser(strings.NewReader(text)), int64(n), func(method, uri, content string, truncated bool) {
|
||||
require.True(truncated)
|
||||
require.Equal(fmt.Sprintf(`200 OK
|
||||
Testing: true
|
||||
Aaa: 1
|
||||
Aaa: 2
|
||||
%s`, text[:n]), content)
|
||||
})
|
||||
require.NoError(err)
|
||||
buf := new(strings.Builder)
|
||||
_, err = io.Copy(buf, body)
|
||||
require.NoError(err)
|
||||
require.Equal(text, buf.String())
|
||||
}
|
||||
|
||||
func TestDumpHttpRequest(t *testing.T) {
|
||||
require := require.New(t)
|
||||
n := 6
|
||||
text := "hello, world"
|
||||
|
||||
h := http.Header{}
|
||||
h.Set("testing", "true")
|
||||
h.Add("aaa", "1")
|
||||
h.Add("aaa", "2")
|
||||
|
||||
req := &http.Request{
|
||||
Method: "GET",
|
||||
Header: h,
|
||||
URL: &url.URL{Scheme: "https", Host: "example.com", Path: "/testing"},
|
||||
Body: io.NopCloser(strings.NewReader(text)),
|
||||
}
|
||||
|
||||
err := DumpHttpRequest(req, int64(n), func(method, uri, content string, truncated bool) {
|
||||
require.True(truncated)
|
||||
require.Equal(fmt.Sprintf(`GET https://example.com/testing
|
||||
Testing: true
|
||||
Aaa: 1
|
||||
Aaa: 2
|
||||
%s`, text[:n]), content)
|
||||
})
|
||||
require.NoError(err)
|
||||
buf := new(strings.Builder)
|
||||
_, err = io.Copy(buf, req.Body)
|
||||
require.NoError(err)
|
||||
require.Equal(text, buf.String())
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
ochttp "github.com/opencloud-eu/opencloud/pkg/http"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/version"
|
||||
)
|
||||
@@ -195,7 +196,7 @@ var (
|
||||
func (h *HttpJmapClient) beforeRequest(_ context.Context, logger *log.Logger, _ string, endpoint string, req *http.Request) Error {
|
||||
l := logger.Trace()
|
||||
if h.traceRequests && l.Enabled() {
|
||||
if err := dumpHttpRequest(req, h.traceMaxRequestBodySize, func(method string, uri string, content string, truncated bool) {
|
||||
if err := ochttp.DumpHttpRequest(req, h.traceMaxRequestBodySize, func(method string, uri string, content string, truncated bool) {
|
||||
if truncated {
|
||||
l = l.Bool(logBodyTruncated, true)
|
||||
}
|
||||
@@ -209,39 +210,27 @@ func (h *HttpJmapClient) beforeRequest(_ context.Context, logger *log.Logger, _
|
||||
return nil
|
||||
}
|
||||
|
||||
type readCloser struct {
|
||||
io.Reader
|
||||
closer io.ReadCloser
|
||||
}
|
||||
|
||||
func (c readCloser) Close() error {
|
||||
if c.closer != nil {
|
||||
return c.closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HttpJmapClient) response(peekSize int64, _ context.Context, logger *log.Logger, _ string, endpoint string, duration time.Duration, req *http.Request, resp *http.Response) (io.ReadCloser, []byte, bool, error) {
|
||||
func (h *HttpJmapClient) response(peekSize int64, _ context.Context, logger *log.Logger, _ string, endpoint string, duration time.Duration, resp *http.Response) (io.ReadCloser, []byte, bool, error) {
|
||||
whole := resp.Body
|
||||
peek := []byte{}
|
||||
truncated := false
|
||||
var err error
|
||||
|
||||
if peekSize > 0 {
|
||||
whole, peek, truncated, err = peekResponse(resp.Body, peekSize, func(err error) { logger.Error().Err(err).Msg("failed to close response body") }) //NOSONAR
|
||||
whole, peek, truncated, err = ochttp.PeekResponse(resp.Body, peekSize)
|
||||
if err != nil {
|
||||
return whole, peek, truncated, err
|
||||
}
|
||||
}
|
||||
l := logger.Trace()
|
||||
if h.traceResponses && l.Enabled() {
|
||||
body, err := dumpHttpResponse(req, resp, whole, h.traceMaxResponseBodySize, func(method, uri, content string, truncated bool) {
|
||||
body, err := ochttp.DumpHttpResponse(resp, whole, h.traceMaxResponseBodySize, func(method, uri, content string, truncated bool) {
|
||||
l.Str(logProto, logProtoJmap).Str(logType, logTypeResponse).Str(logEndpoint, endpoint).
|
||||
Str(logMethod, method).Str(logUri, uri).
|
||||
Str(logHttpStatus, log.SafeString(resp.Status)).Int(logHttpStatusCode, resp.StatusCode).
|
||||
Dur(logDuration, duration).
|
||||
Msg(content)
|
||||
}, func(err error) { logger.Error().Err(err).Msg("failed to close response body") }) //NOSONAR
|
||||
})
|
||||
if err != nil {
|
||||
return body, peek, truncated, err
|
||||
} else {
|
||||
@@ -296,7 +285,7 @@ func (h *HttpJmapClient) GetSession(ctx context.Context, sessionUrl *url.URL, us
|
||||
}
|
||||
|
||||
// dump the response regardless of the status code
|
||||
body, _, _, err := h.response(responseBodyPeekSize, ctx, logger, username, endpoint, duration, req, res)
|
||||
body, _, _, err := h.response(responseBodyPeekSize, ctx, logger, username, endpoint, duration, res)
|
||||
|
||||
// since we are not returning a stream from this function, we have to close the response body
|
||||
// before leaving the scope of the function
|
||||
@@ -383,7 +372,7 @@ func (h *HttpJmapClient) Command(operation Operation, request Request, ctx Conte
|
||||
// caller won't be able to read the response; instead, it is up to the caller to close the
|
||||
// ReadCloser we are returning from here
|
||||
|
||||
body, _, _, err := h.response(responseBodyPeekSize, cotx, logger, session.Username, endpoint, duration, req, res)
|
||||
body, _, _, err := h.response(responseBodyPeekSize, cotx, logger, session.Username, endpoint, duration, res)
|
||||
language := Language(res.Header.Get("Content-Language")) //NOSONAR
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to read response body")
|
||||
@@ -442,12 +431,7 @@ func (h *HttpJmapClient) UploadBinary(uploadUrl string, operation Operation, end
|
||||
return UploadedBlob{}, "", jmapError(err, JmapErrorSendingRequest)
|
||||
}
|
||||
|
||||
// note that we are omitting the usual deferred response body closer here since we are returning
|
||||
// an io.ReadCloser to the body and if we close it when leaving the scope of this function, the
|
||||
// caller won't be able to read the response; instead, it is up to the caller to close the
|
||||
// ReadCloser we are returning from here
|
||||
|
||||
responseBody, _, _, err := h.response(responseBodyPeekSize, cotx, logger, session.Username, endpoint, duration, req, res)
|
||||
responseBody, _, _, err := h.response(responseBodyPeekSize, cotx, logger, session.Username, endpoint, duration, res)
|
||||
|
||||
// since we are not returning a stream from this function, we have to close the response body
|
||||
// before leaving the scope of the function
|
||||
@@ -519,7 +503,7 @@ func (h *HttpJmapClient) DownloadBinary(downloadUrl string, operation Operation,
|
||||
// caller won't be able to read the response; instead, it is up to the caller to close the
|
||||
// ReadCloser we are returning from here
|
||||
|
||||
responseBody, _, _, err := h.response(responseBodyPeekSize, cotx, logger, session.Username, endpoint, duration, req, res)
|
||||
responseBody, _, _, err := h.response(responseBodyPeekSize, cotx, logger, session.Username, endpoint, duration, res)
|
||||
|
||||
language := Language(res.Header.Get("Content-Language"))
|
||||
if err != nil {
|
||||
@@ -656,14 +640,12 @@ func (w *HttpWsClientFactory) connect(operation Operation, ctx context.Context,
|
||||
{
|
||||
l := logger.Trace()
|
||||
if w.traceWsResponses && l.Enabled() {
|
||||
body, err = dumpHttpResponse(nil, res, res.Body, w.traceMaxResponseBodySize, func(method, uri, content string, truncated bool) {
|
||||
body, err = ochttp.DumpHttpResponse(res, res.Body, w.traceMaxResponseBodySize, func(method, uri, content string, truncated bool) {
|
||||
l.Str(logProto, logProtoJmap).Str(logType, logTypeResponse).Str(logEndpoint, endpoint).
|
||||
Str(logMethod, method).Str(logUri, uri).
|
||||
Str(logHttpStatus, log.SafeString(res.Status)).Int(logHttpStatusCode, res.StatusCode).
|
||||
Dur(logDuration, duration).
|
||||
Msg(content)
|
||||
}, func(err error) {
|
||||
logger.Error().Err(err).Msg("failed to close response body") //NOSONAR
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,49 +13,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPeekResponse(t *testing.T) {
|
||||
require := require.New(t)
|
||||
n := 6
|
||||
text := "hello, world"
|
||||
|
||||
require.Less(n, len(text))
|
||||
body, peek, truncated, err := peekResponse(io.NopCloser(strings.NewReader(text)), int64(n), func(err error) {
|
||||
require.NoError(err)
|
||||
})
|
||||
require.NoError(err)
|
||||
require.True(truncated)
|
||||
require.Equal(text[:n], string(peek))
|
||||
buf := new(strings.Builder)
|
||||
_, err = io.Copy(buf, body)
|
||||
require.NoError(err)
|
||||
require.Equal(text, buf.String())
|
||||
}
|
||||
|
||||
func TestDumpHttpResponse(t *testing.T) {
|
||||
require := require.New(t)
|
||||
n := 6
|
||||
text := "hello, world"
|
||||
|
||||
resp := &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Header: http.Header{},
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
body, err := dumpHttpResponse(nil, resp, io.NopCloser(strings.NewReader(text)), int64(n), func(method, uri, content string, truncated bool) {
|
||||
require.True(truncated)
|
||||
require.Equal(fmt.Sprintf("200 OK\n%s", text[:n]), content)
|
||||
}, func(err error) {
|
||||
require.NoError(err)
|
||||
})
|
||||
require.NoError(err)
|
||||
buf := new(strings.Builder)
|
||||
_, err = io.Copy(buf, body)
|
||||
require.NoError(err)
|
||||
require.Equal(text, buf.String())
|
||||
}
|
||||
|
||||
type testNopReadCloser struct {
|
||||
io.Reader
|
||||
closed bool
|
||||
@@ -119,7 +76,7 @@ func TestResponse(t *testing.T) {
|
||||
traceResponses: true,
|
||||
traceMaxResponseBodySize: int64(tr.traceSize),
|
||||
}
|
||||
body, peek, truncated, err := client.response(int64(tt.peekSize), context.TODO(), &logger, "username", "https://stalwart", time.Duration(1*time.Second), nil, resp)
|
||||
body, peek, truncated, err := client.response(int64(tt.peekSize), context.TODO(), &logger, "username", "https://stalwart", time.Duration(1*time.Second), resp)
|
||||
require.NoError(err)
|
||||
|
||||
if b.withBody {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package jmap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -450,142 +447,3 @@ func ns(namespaces ...JmapNamespace) []JmapNamespace {
|
||||
func ptr[T any | int | uint | bool | string](t T) *T {
|
||||
return &t
|
||||
}
|
||||
|
||||
type nullReader struct {
|
||||
}
|
||||
|
||||
func (r nullReader) Read(p []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (r nullReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = nullReader{}
|
||||
|
||||
func multiReader(peekBuffer *bytes.Buffer, body io.Reader) io.Reader {
|
||||
readers := []io.Reader{}
|
||||
if peekBuffer != nil {
|
||||
readers = append(readers, peekBuffer)
|
||||
}
|
||||
if body != nil && body != http.NoBody {
|
||||
readers = append(readers, body)
|
||||
}
|
||||
switch len(readers) {
|
||||
case 0:
|
||||
return http.NoBody
|
||||
case 1:
|
||||
return readers[0]
|
||||
default:
|
||||
return io.MultiReader(readers...)
|
||||
}
|
||||
}
|
||||
|
||||
func peekResponse(body io.ReadCloser, size int64,
|
||||
closeErrorClosure func(err error)) (io.ReadCloser, []byte, bool, error) {
|
||||
var peekBuffer *bytes.Buffer = nil
|
||||
truncated := false
|
||||
peek := []byte{}
|
||||
if body != nil && body != http.NoBody && size > 0 {
|
||||
peekBuffer = new(bytes.Buffer)
|
||||
limitedReader := io.LimitReader(body, size)
|
||||
tee := io.TeeReader(limitedReader, peekBuffer)
|
||||
if _, err := io.Copy(io.Discard, tee); err != nil {
|
||||
if err := body.Close(); err != nil {
|
||||
closeErrorClosure(err)
|
||||
}
|
||||
return nil, peek, false, err
|
||||
}
|
||||
|
||||
if int64(peekBuffer.Len()) >= size {
|
||||
truncated = true
|
||||
}
|
||||
|
||||
peek = peekBuffer.Bytes()
|
||||
}
|
||||
|
||||
return readCloser{
|
||||
closer: body,
|
||||
Reader: multiReader(peekBuffer, body),
|
||||
}, peek, truncated, nil
|
||||
}
|
||||
|
||||
func dumpHttpRequest(req *http.Request, maxBodySize int64, closure func(method string, uri string, content string, truncated bool)) error {
|
||||
var logBuilder bytes.Buffer
|
||||
uri := req.URL.String()
|
||||
fmt.Fprintf(&logBuilder, "%s %s\n", req.Method, uri)
|
||||
for key, values := range req.Header {
|
||||
if key == "Authorization" {
|
||||
continue
|
||||
}
|
||||
for _, value := range values {
|
||||
fmt.Fprintf(&logBuilder, "%s: %s\n", key, value)
|
||||
}
|
||||
}
|
||||
truncated := false
|
||||
if maxBodySize >= 0 && req.Body != nil && req.Body != http.NoBody {
|
||||
peekBuffer := new(bytes.Buffer)
|
||||
limitedReader := io.LimitReader(req.Body, maxBodySize)
|
||||
tee := io.TeeReader(limitedReader, peekBuffer)
|
||||
if _, err := io.Copy(io.Discard, tee); err != nil {
|
||||
return fmt.Errorf("failed to peek at the request body for tracing: %v", err)
|
||||
} else {
|
||||
logBuilder.Write(peekBuffer.Bytes())
|
||||
if int64(peekBuffer.Len()) >= maxBodySize {
|
||||
truncated = true
|
||||
}
|
||||
fullBodyReader := io.MultiReader(peekBuffer, req.Body)
|
||||
req.Body = io.NopCloser(fullBodyReader)
|
||||
}
|
||||
}
|
||||
closure(req.Method, uri, logBuilder.String(), truncated)
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpHttpResponse(req *http.Request, resp *http.Response, body io.ReadCloser, maxBodySize int64,
|
||||
closure func(method string, uri string, content string, truncated bool),
|
||||
closeErrorClosure func(err error)) (io.ReadCloser, error) {
|
||||
var logBuilder bytes.Buffer
|
||||
fmt.Fprintf(&logBuilder, "%s\n", resp.Status)
|
||||
for key, values := range resp.Header {
|
||||
for _, value := range values {
|
||||
fmt.Fprintf(&logBuilder, "%s: %s\n", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
var peekBuffer *bytes.Buffer = nil
|
||||
truncated := false
|
||||
if body != nil && body != http.NoBody && maxBodySize > 0 {
|
||||
peekBuffer = new(bytes.Buffer)
|
||||
limitedReader := io.LimitReader(body, maxBodySize)
|
||||
tee := io.TeeReader(limitedReader, peekBuffer)
|
||||
if _, err := io.Copy(io.Discard, tee); err != nil {
|
||||
if err := body.Close(); err != nil {
|
||||
closeErrorClosure(err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if int64(peekBuffer.Len()) >= maxBodySize {
|
||||
truncated = true
|
||||
}
|
||||
|
||||
logBuilder.Write(peekBuffer.Bytes())
|
||||
}
|
||||
|
||||
if req != nil {
|
||||
closure(req.Method, req.URL.String(), logBuilder.String(), truncated)
|
||||
} else {
|
||||
closure("", "", logBuilder.String(), truncated)
|
||||
}
|
||||
|
||||
if peekBuffer != nil {
|
||||
return readCloser{
|
||||
closer: body,
|
||||
Reader: multiReader(peekBuffer, body),
|
||||
}, nil
|
||||
} else {
|
||||
return body, nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user