synapsecns/sanguine

View on GitHub
core/ginhelper/gindump.go

Summary

Maintainability
F
3 days
Test Coverage
package ginhelper

import (
    "bytes"
    "fmt"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/trace"
    "io"
    "mime"
    "net/http"
    "net/url"
    "strings"

    "github.com/gin-gonic/gin"
)

const traceName = "ginDump"
const dumpName = "http.dump"

// TraceMiddleware is a middleware that traces the request/response.
func TraceMiddleware(tracer trace.Tracer, printStr bool) gin.HandlerFunc {
    return func(ctx *gin.Context) {
        _, span := tracer.Start(ctx, traceName)
        defer span.End()

        // DumpWithOptions will call ctx.Next() to continue the request/response flow.
        DumpWithOptions(true, true, true, true, true, func(dumpStr string) {
            span.SetAttributes(attribute.String(dumpName, dumpStr))
            if printStr {
                fmt.Println(dumpStr)
            }
        })(ctx)
    }
}

// Dump dumps the request and response.
func Dump() gin.HandlerFunc {
    return DumpWithOptions(true, true, true, true, true, nil)
}

// DumpWithOptions dumps the request and response with options.
// nolint: gocognit, nestif, cyclop
func DumpWithOptions(showReq bool, showResp bool, showBody bool, showHeaders bool, showCookies bool, cb func(dumpStr string)) gin.HandlerFunc {
    headerHiddenFields := make([]string, 0)
    bodyHiddenFields := make([]string, 0)

    if !showCookies {
        headerHiddenFields = append(headerHiddenFields, "cookie")
    }

    return func(ctx *gin.Context) {
        var strB strings.Builder

        if showReq && showHeaders {
            // dump req header
            s, err := FormatToBeautifulJSON(ctx.Request.Header, headerHiddenFields)

            if err != nil {
                strB.WriteString(fmt.Sprintf("\nparse req header err \n" + err.Error()))
            } else {
                strB.WriteString("Request-Header:\n")
                strB.Write(s)
            }
        }

        if showReq && showBody {
            // dump req body
            // nolint: nestif
            if ctx.Request.ContentLength > 0 {
                buf, err := io.ReadAll(ctx.Request.Body)
                if err != nil {
                    strB.WriteString(fmt.Sprintf("\nread bodyCache err \n %s", err.Error()))
                    goto DumpRes
                }
                rdr := io.NopCloser(bytes.NewBuffer(buf))
                ctx.Request.Body = io.NopCloser(bytes.NewBuffer(buf))
                ctGet := ctx.Request.Header.Get("Content-Type")
                ct, _, err := mime.ParseMediaType(ctGet)
                if err != nil {
                    strB.WriteString(fmt.Sprintf("\ncontent_type: %s parse err \n %s", ctGet, err.Error()))
                    goto DumpRes
                }

                switch ct {
                case gin.MIMEJSON:
                    bts, err := io.ReadAll(rdr)
                    if err != nil {
                        strB.WriteString(fmt.Sprintf("\nread rdr err \n %s", err.Error()))
                        goto DumpRes
                    }

                    s, err := BeautifyJSONBytes(bts, bodyHiddenFields)
                    if err != nil {
                        strB.WriteString(fmt.Sprintf("\nparse req body err \n" + err.Error()))
                        goto DumpRes
                    }

                    strB.WriteString("\nRequest-Body:\n")
                    strB.Write(s)
                case gin.MIMEPOSTForm:
                    bts, err := io.ReadAll(rdr)
                    if err != nil {
                        strB.WriteString(fmt.Sprintf("\nread rdr err \n %s", err.Error()))
                        goto DumpRes
                    }
                    val, err := url.ParseQuery(string(bts))
                    if err != nil {
                        strB.WriteString(fmt.Sprintf("\nparse query err \n" + err.Error()))
                        goto DumpRes
                    }

                    s, err := FormatToBeautifulJSON(val, bodyHiddenFields)
                    if err != nil {
                        strB.WriteString(fmt.Sprintf("\nparse req body err \n" + err.Error()))
                        goto DumpRes
                    }
                    strB.WriteString("\nRequest-Body:\n")
                    strB.Write(s)

                case gin.MIMEMultipartPOSTForm:
                default:
                }
            }

        DumpRes:
            ctx.Writer = &bodyWriter{bodyCache: bytes.NewBufferString(""), ResponseWriter: ctx.Writer}
            ctx.Next()
        }

        if showResp && showHeaders {
            // dump res header
            sHeader, err := FormatToBeautifulJSON(ctx.Writer.Header(), headerHiddenFields)
            if err != nil {
                strB.WriteString(fmt.Sprintf("\nparse res header err \n" + err.Error()))
            } else {
                strB.WriteString("\nResponse-Header:\n")
                strB.Write(sHeader)
            }
        }

        if showResp && showBody {
            bw, ok := ctx.Writer.(*bodyWriter)
            if !ok {
                strB.WriteString("\nbodyWriter was override , can not read bodyCache")
                goto End
            }

            // dump res body
            if bodyAllowedForStatus(ctx.Writer.Status()) && bw.bodyCache.Len() > 0 {
                ctGet := ctx.Writer.Header().Get("Content-Type")
                ct, _, err := mime.ParseMediaType(ctGet)
                if err != nil {
                    strB.WriteString(fmt.Sprintf("\ncontent-type: %s parse  err \n %s", ctGet, err.Error()))
                    goto End
                }
                switch ct {
                case gin.MIMEJSON:

                    s, err := BeautifyJSONBytes(bw.bodyCache.Bytes(), bodyHiddenFields)
                    if err != nil {
                        strB.WriteString(fmt.Sprintf("\nparse bodyCache err \n" + err.Error()))
                        goto End
                    }
                    strB.WriteString("\nResponse-Body:\n")

                    strB.Write(s)
                case gin.MIMEHTML:
                default:
                }
            }
        }

    End:
        if cb != nil {
            cb(strB.String())
        } else {
            fmt.Println(strB.String())
        }
    }
}

type bodyWriter struct {
    gin.ResponseWriter
    bodyCache *bytes.Buffer
}

// rewrite Write().
func (w bodyWriter) Write(b []byte) (int, error) {
    w.bodyCache.Write(b)
    //nolint: wrapcheck
    return w.ResponseWriter.Write(b)
}

// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.
func bodyAllowedForStatus(status int) bool {
    switch {
    case status >= 100 && status <= 199:
        return false
    case status == http.StatusNoContent:
        return false
    case status == http.StatusNotModified:
        return false
    }
    return true
}