Skip to content

Commit

Permalink
Add Align to align HTTP headers
Browse files Browse the repository at this point in the history
IMHO this makes stuff rather more readable/scannable. Some people may
not like it, so I added it as an option.

Will automatically align to the longest header; I modified the
sortHeaderKeys() so we don't need to do an extra loop for it.

Also pass the Host header to printHeaders() so it's aligned too. Do some
special-fu to make sure it still prints as the first.

Before;

	* Request at 2024-09-19 15:19:15.459694244 +0100 IST m=+1.001660288
	* Request to http://localhost:8081/api/3/ping
	> GET /api/3/ping HTTP/1.1
	> Host: localhost:8081
	> Accept: application/json; charset=utf-8
	> Authorization: ApiKey ████████████████████
	> Content-Type: application/json; charset=utf-8
	> User-Agent: alfred/

	< HTTP/1.1 200 OK
	< Cache-Control: no-store,no-cache
	< Content-Length: 88
	< Content-Security-Policy: default-src 'self'; img-src 'self' blob: data:;
	< Content-Type: application/json; charset=utf-8
	< Date: Thu, 19 Sep 2024 14:19:15 GMT
	< Referrer-Policy: same-origin
	< X-Content-Type-Options: nosniff
	< X-Frame-Options: SAMEORIGIN
	< X-Powered-By: Alfred
	< X-Xss-Protection: 1; mode=block

	{
		"ftm_version": "3.7.0",
		"role": 4,
		"title": "Alfred",
		"version": "d67f396b"
	}

	* Request took 9.251101ms

After:

	* Request at 2024-09-19 15:18:45.255328802 +0100 IST m=+1.001249794
	* Request to http://localhost:8081/api/3/ping
	> GET /api/3/ping HTTP/1.1
	> Host:          localhost:8081
	> Accept:        application/json; charset=utf-8
	> Authorization: ApiKey ████████████████████
	> Content-Type:  application/json; charset=utf-8
	> User-Agent:    alfred/

	< HTTP/1.1 200 OK
	< Cache-Control:           no-store,no-cache
	< Content-Length:          88
	< Content-Security-Policy: default-src 'self'; img-src 'self' blob: data:;
	< Content-Type:            application/json; charset=utf-8
	< Date:                    Thu, 19 Sep 2024 14:18:45 GMT
	< Referrer-Policy:         same-origin
	< X-Content-Type-Options:  nosniff
	< X-Frame-Options:         SAMEORIGIN
	< X-Powered-By:            Alfred
	< X-Xss-Protection:        1; mode=block

	{
		"ftm_version": "3.7.0",
		"role": 4,
		"title": "Alfred",
		"version": "d67f396b"
	}

	* Request took 9.251101ms
  • Loading branch information
arp242 committed Sep 19, 2024
1 parent b2e47bd commit 962ad53
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 26 deletions.
3 changes: 3 additions & 0 deletions httpretty.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
32 changes: 32 additions & 0 deletions httpretty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
65 changes: 39 additions & 26 deletions printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"mime"
"net"
"net/http"
"slices"

Check failure on line 12 in printer.go

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, 1.20.x)

package slices is not in GOROOT (/opt/hostedtoolcache/go/1.20.14/x64/src/slices)

Check failure on line 12 in printer.go

View workflow job for this annotation

GitHub Actions / Test (macos-latest, 1.20.x)

package slices is not in GOROOT (/Users/runner/hostedtoolcache/go/1.20.14/arm64/src/slices)

Check failure on line 12 in printer.go

View workflow job for this annotation

GitHub Actions / Test (windows-latest, 1.20.x)

package slices is not in GOROOT (C:\hostedtoolcache\windows\go\1.20.14\x64\src\slices)

Check failure on line 12 in printer.go

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest, 1.20.x)

package slices is not in GOROOT (/opt/hostedtoolcache/go/1.20.14/x64/src/slices)

Check failure on line 12 in printer.go

View workflow job for this annotation

GitHub Actions / Test (macos-latest, 1.20.x)

package slices is not in GOROOT (/Users/runner/hostedtoolcache/go/1.20.14/arm64/src/slices)

Check failure on line 12 in printer.go

View workflow job for this annotation

GitHub Actions / Test (windows-latest, 1.20.x)

package slices is not in GOROOT (C:\hostedtoolcache\windows\go\1.20.14\x64\src\slices)
"sort"
"strings"
"time"
Expand Down Expand Up @@ -486,59 +487,71 @@ 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) {
p.printf("> %s %s %s\n",
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
}

Expand Down

0 comments on commit 962ad53

Please sign in to comment.