diff --git a/httpretty.go b/httpretty.go index f757b0f..3bedebb 100644 --- a/httpretty.go +++ b/httpretty.go @@ -117,6 +117,9 @@ type Logger struct { // Colors set ANSI escape codes that terminals use to print text in different colors. Colors bool + // Align HTTP headers. + Align bool + // Formatters for the request and response bodies. // No standard formatters are used. You need to add what you want to use explicitly. // We provide a JSONFormatter for convenience (add it manually). diff --git a/httpretty_test.go b/httpretty_test.go index 1354631..5feca64 100644 --- a/httpretty_test.go +++ b/httpretty_test.go @@ -51,6 +51,38 @@ func TestPrintRequest(t *testing.T) { } } +func TestPrintRequestWithAlign(t *testing.T) { + t.Parallel() + var req, err = http.NewRequest(http.MethodPost, "http://wxww.example.com/", nil) + if err != nil { + panic(err) + } + req.Header.Set("Header", "foo") + req.Header.Set("Other-Header", "bar") + + logger := &Logger{ + TLS: true, + RequestHeader: true, + RequestBody: true, + ResponseHeader: true, + ResponseBody: true, + Align: true, + } + var buf bytes.Buffer + logger.SetOutput(&buf) + logger.PrintRequest(req) + + want := `> POST / HTTP/1.1 +> Host: wxww.example.com +> Header: foo +> Other-Header: bar + +` + if got := buf.String(); got != want { + t.Errorf("PrintRequest(req) = %v, wanted %v", got, want) + } +} + func TestPrintRequestWithColors(t *testing.T) { t.Parallel() var req, err = http.NewRequest(http.MethodPost, "http://wxww.example.com/", nil) diff --git a/printer.go b/printer.go index ab6e6a7..b85b28f 100644 --- a/printer.go +++ b/printer.go @@ -9,6 +9,7 @@ import ( "mime" "net" "net/http" + "slices" "sort" "strings" "time" @@ -486,27 +487,42 @@ func (p *printer) printHeaders(prefix rune, h http.Header) { if !p.logger.SkipSanitize { h = header.Sanitize(header.DefaultSanitizers, h) } - skipped := p.logger.cloneSkipHeader() - for _, key := range sortHeaderKeys(h) { + + longest, sorted := sortHeaderKeys(h, p.logger.cloneSkipHeader()) + for _, key := range sorted { for _, v := range h[key] { - if _, skip := skipped[key]; skip { - continue + var pad string + if p.logger.Align { + pad = strings.Repeat(" ", longest-len(key)) } - p.printf("%c %s%s %s\n", prefix, + p.printf("%c %s%s %s%s\n", prefix, p.format(color.FgBlue, color.Bold, key), p.format(color.FgRed, ":"), + pad, p.format(color.FgYellow, v)) } } } -func sortHeaderKeys(h http.Header) []string { - keys := make([]string, 0, len(h)) +func sortHeaderKeys(h http.Header, skipped map[string]struct{}) (int, []string) { + var ( + keys = make([]string, 0, len(h)) + longest int + ) for key := range h { + if _, skip := skipped[key]; skip { + continue + } keys = append(keys, key) + if l := len(key); l > longest { + longest = l + } } sort.Strings(keys) - return keys + if i := slices.Index(keys, "Host"); i > -1 { + keys = append([]string{"Host"}, slices.Delete(keys, i, i+1)...) + } + return longest, keys } func (p *printer) printRequestHeader(req *http.Request) { @@ -514,31 +530,28 @@ func (p *printer) printRequestHeader(req *http.Request) { p.format(color.FgBlue, color.Bold, req.Method), p.format(color.FgYellow, req.URL.RequestURI()), p.format(color.FgBlue, req.Proto)) - host := req.Host - if host == "" { - host = req.URL.Host - } - if host != "" { - p.printf("> %s%s %s\n", - p.format(color.FgBlue, color.Bold, "Host"), - p.format(color.FgRed, ":"), - p.format(color.FgYellow, host), - ) - } - p.printHeaders('>', addHeaderContentLength(req)) + p.printHeaders('>', addRequestHeaders(req)) p.println() } -// addHeaderContentLength returns a copy of the given header with an additional header, Content-Length, if it's known. -func addHeaderContentLength(req *http.Request) http.Header { - if req.ContentLength == 0 && len(req.Header.Values("Content-Length")) == 0 { - return req.Header - } +// addRequestHeaders returns a copy of the given header with an additional headers set, if known. +func addRequestHeaders(req *http.Request) http.Header { cp := http.Header{} for k, v := range req.Header { cp[k] = v } - cp.Set("Content-Length", fmt.Sprintf("%d", req.ContentLength)) + + if len(req.Header.Values("Content-Length")) == 0 && req.ContentLength > 0 { + cp.Set("Content-Length", fmt.Sprintf("%d", req.ContentLength)) + } + + host := req.Host + if host == "" { + host = req.URL.Host + } + if host != "" { + cp.Set("Host", host) + } return cp }