...

Source file src/github.com/henvic/httpretty/httpretty.go

Documentation: github.com/henvic/httpretty

     1  // Package httpretty prints your HTTP requests pretty on your terminal screen.
     2  // You can use this package both on the client-side and on the server-side.
     3  //
     4  // This package provides a better way to view HTTP traffic without httputil
     5  // DumpRequest, DumpRequestOut, and DumpResponse heavy debugging functions.
     6  //
     7  // You can use the logger quickly to log requests you are opening. For example:
     8  // 	package main
     9  //
    10  // 	import (
    11  // 		"fmt"
    12  // 		"net/http"
    13  // 		"os"
    14  //
    15  // 		"github.com/henvic/httpretty"
    16  // 	)
    17  //
    18  // 	func main() {
    19  // 		logger := &httpretty.Logger{
    20  // 			Time:           true,
    21  // 			TLS:            true,
    22  // 			RequestHeader:  true,
    23  // 			RequestBody:    true,
    24  // 			ResponseHeader: true,
    25  // 			ResponseBody:   true,
    26  // 			Colors:         true,
    27  // 			Formatters:     []httpretty.Formatter{&httpretty.JSONFormatter{}},
    28  // 		}
    29  //
    30  // 		http.DefaultClient.Transport = logger.RoundTripper(http.DefaultClient.Transport) // tip: you can use it on any *http.Client
    31  //
    32  // 		if _, err := http.Get("https://www.google.com/"); err != nil {
    33  // 			fmt.Fprintf(os.Stderr, "%+v\n", err)
    34  // 			os.Exit(1)
    35  // 		}
    36  // 	}
    37  //
    38  // If you pass nil to the logger.RoundTripper it is going to fallback to http.DefaultTransport.
    39  //
    40  // You can use the logger quickly to log requests on your server. For example:
    41  // 	logger := &httpretty.Logger{
    42  // 		Time:           true,
    43  // 		TLS:            true,
    44  // 		RequestHeader:  true,
    45  // 		RequestBody:    true,
    46  // 		ResponseHeader: true,
    47  // 		ResponseBody:   true,
    48  // 	}
    49  //
    50  // 	logger.Middleware(handler)
    51  //
    52  // Note: server logs don't include response headers set by the server.
    53  // Client logs don't include request headers set by the HTTP client.
    54  package httpretty
    55  
    56  import (
    57  	"bytes"
    58  	"context"
    59  	"crypto/tls"
    60  	"encoding/json"
    61  	"errors"
    62  	"io"
    63  	"net/http"
    64  	"net/textproto"
    65  	"os"
    66  	"sync"
    67  
    68  	"github.com/henvic/httpretty/internal/color"
    69  )
    70  
    71  // Formatter can be used to format body.
    72  //
    73  // If the Format function returns an error, the content is printed in verbatim after a warning.
    74  // Match receives a media type from the Content-Type field. The body is formatted if it returns true.
    75  type Formatter interface {
    76  	Match(mediatype string) bool
    77  	Format(w io.Writer, src []byte) error
    78  }
    79  
    80  // WithHide can be used to protect a request from being exposed.
    81  func WithHide(ctx context.Context) context.Context {
    82  	return context.WithValue(ctx, contextHide{}, struct{}{})
    83  }
    84  
    85  // Logger provides a way for you to print client and server-side information about your HTTP traffic.
    86  type Logger struct {
    87  	// SkipRequestInfo avoids printing a line showing the request URI on all requests plus a line
    88  	// containing the remote address on server-side requests.
    89  	SkipRequestInfo bool
    90  
    91  	// Time the request began and its duration.
    92  	Time bool
    93  
    94  	// TLS information, such as certificates and ciphers.
    95  	// BUG(henvic): Currently, the TLS information prints after the response header, although it
    96  	// should be printed before the request header.
    97  	TLS bool
    98  
    99  	// RequestHeader set by the client or received from the server.
   100  	RequestHeader bool
   101  
   102  	// RequestBody sent by the client or received by the server.
   103  	RequestBody bool
   104  
   105  	// ResponseHeader received by the client or set by the HTTP handlers.
   106  	ResponseHeader bool
   107  
   108  	// ResponseBody received by the client or set by the server.
   109  	ResponseBody bool
   110  
   111  	// SkipSanitize bypasses sanitizing headers containing credentials (such as Authorization).
   112  	SkipSanitize bool
   113  
   114  	// Colors set ANSI escape codes that terminals use to print text in different colors.
   115  	Colors bool
   116  
   117  	// Formatters for the request and response bodies.
   118  	// No standard formatters are used. You need to add what you want to use explicitly.
   119  	// We provide a JSONFormatter for convenience (add it manually).
   120  	Formatters []Formatter
   121  
   122  	// MaxRequestBody the logger can print.
   123  	// If value is not set and Content-Length is not sent, 4096 bytes is considered.
   124  	MaxRequestBody int64
   125  
   126  	// MaxResponseBody the logger can print.
   127  	// If value is not set and Content-Length is not sent, 4096 bytes is considered.
   128  	MaxResponseBody int64
   129  
   130  	mu         sync.Mutex // ensures atomic writes; protects the following fields
   131  	w          io.Writer
   132  	filter     Filter
   133  	skipHeader map[string]struct{}
   134  	bodyFilter BodyFilter
   135  	flusher    Flusher
   136  }
   137  
   138  // Filter allows you to skip requests.
   139  //
   140  // If an error happens and you want to log it, you can pass a not-null error value.
   141  type Filter func(req *http.Request) (skip bool, err error)
   142  
   143  // BodyFilter allows you to skip printing a HTTP body based on its associated Header.
   144  //
   145  // It can be used for omitting HTTP Request and Response bodies.
   146  // You can filter by checking properties such as Content-Type or Content-Length.
   147  //
   148  // On a HTTP server, this function is called even when no body is present due to
   149  // http.Request always carrying a non-nil value.
   150  type BodyFilter func(h http.Header) (skip bool, err error)
   151  
   152  // Flusher defines how logger prints requests.
   153  type Flusher int
   154  
   155  // Logger can print without flushing, when they are available, or when the request is done.
   156  const (
   157  	// NoBuffer strategy prints anything immediately, without buffering.
   158  	// It has the issue of mingling concurrent requests in unpredictable ways.
   159  	NoBuffer Flusher = iota
   160  
   161  	// OnReady buffers and prints each step of the request or response (header, body) whenever they are ready.
   162  	// It reduces mingling caused by mingling but does not give any ordering guarantee, so responses can still be out of order.
   163  	OnReady
   164  
   165  	// OnEnd buffers the whole request and flushes it once, in the end.
   166  	OnEnd
   167  )
   168  
   169  // SetFilter allows you to set a function to skip requests.
   170  // Pass nil to remove the filter. This method is concurrency safe.
   171  func (l *Logger) SetFilter(f Filter) {
   172  	l.mu.Lock()
   173  	defer l.mu.Unlock()
   174  	l.filter = f
   175  }
   176  
   177  // SkipHeader allows you to skip printing specific headers.
   178  // This method is concurrency safe.
   179  func (l *Logger) SkipHeader(headers []string) {
   180  	l.mu.Lock()
   181  	defer l.mu.Unlock()
   182  	m := map[string]struct{}{}
   183  	for _, h := range headers {
   184  		m[textproto.CanonicalMIMEHeaderKey(h)] = struct{}{}
   185  	}
   186  	l.skipHeader = m
   187  }
   188  
   189  // SetBodyFilter allows you to set a function to skip printing a body.
   190  // Pass nil to remove the body filter. This method is concurrency safe.
   191  func (l *Logger) SetBodyFilter(f BodyFilter) {
   192  	l.mu.Lock()
   193  	defer l.mu.Unlock()
   194  	l.bodyFilter = f
   195  }
   196  
   197  // SetOutput sets the output destination for the logger.
   198  func (l *Logger) SetOutput(w io.Writer) {
   199  	l.mu.Lock()
   200  	defer l.mu.Unlock()
   201  	l.w = w
   202  }
   203  
   204  // SetFlusher sets the flush strategy for the logger.
   205  func (l *Logger) SetFlusher(f Flusher) {
   206  	l.mu.Lock()
   207  	defer l.mu.Unlock()
   208  	l.flusher = f
   209  }
   210  
   211  func (l *Logger) getWriter() io.Writer {
   212  	if l.w == nil {
   213  		return os.Stdout
   214  	}
   215  
   216  	return l.w
   217  }
   218  
   219  func (l *Logger) getFilter() Filter {
   220  	l.mu.Lock()
   221  	f := l.filter
   222  	defer l.mu.Unlock()
   223  	return f
   224  }
   225  
   226  func (l *Logger) getBodyFilter() BodyFilter {
   227  	l.mu.Lock()
   228  	f := l.bodyFilter
   229  	defer l.mu.Unlock()
   230  	return f
   231  }
   232  
   233  func (l *Logger) cloneSkipHeader() map[string]struct{} {
   234  	l.mu.Lock()
   235  	skipped := l.skipHeader
   236  	l.mu.Unlock()
   237  
   238  	m := map[string]struct{}{}
   239  	for h := range skipped {
   240  		m[h] = struct{}{}
   241  	}
   242  
   243  	return m
   244  }
   245  
   246  type contextHide struct{}
   247  
   248  type roundTripper struct {
   249  	logger *Logger
   250  	rt     http.RoundTripper
   251  }
   252  
   253  // RoundTripper returns a RoundTripper that uses the logger.
   254  func (l *Logger) RoundTripper(rt http.RoundTripper) http.RoundTripper {
   255  	return roundTripper{
   256  		logger: l,
   257  		rt:     rt,
   258  	}
   259  }
   260  
   261  // RoundTrip implements the http.RoundTrip interface.
   262  func (r roundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
   263  	tripper := r.rt
   264  
   265  	if tripper == nil {
   266  		// BUG(henvic): net/http data race condition when the client
   267  		// does concurrent requests using the very same HTTP transport.
   268  		// See Go standard library issue https://golang.org/issue/30597
   269  		tripper = http.RoundTripper(http.DefaultTransport)
   270  	}
   271  
   272  	l := r.logger
   273  	p := newPrinter(l)
   274  	defer p.flush()
   275  
   276  	if hide := req.Context().Value(contextHide{}); hide != nil || p.checkFilter(req) {
   277  		return tripper.RoundTrip(req)
   278  	}
   279  
   280  	var tlsClientConfig *tls.Config
   281  
   282  	if l.Time {
   283  		defer p.printTimeRequest()()
   284  	}
   285  
   286  	if !l.SkipRequestInfo {
   287  		p.printRequestInfo(req)
   288  	}
   289  
   290  	if transport, ok := tripper.(*http.Transport); ok && transport.TLSClientConfig != nil {
   291  		tlsClientConfig = transport.TLSClientConfig
   292  
   293  		if tlsClientConfig.InsecureSkipVerify {
   294  			p.printf("* Skipping TLS verification: %s\n",
   295  				p.format(color.FgRed, "connection is susceptible to man-in-the-middle attacks."))
   296  		}
   297  	}
   298  
   299  	if l.TLS && tlsClientConfig != nil {
   300  		// please remember http.Request.TLS is ignored by the HTTP client.
   301  		p.printOutgoingClientTLS(tlsClientConfig)
   302  	}
   303  
   304  	p.printRequest(req)
   305  
   306  	defer func() {
   307  		if err != nil {
   308  			p.printf("* %s\n", p.format(color.FgRed, err.Error()))
   309  
   310  			if resp == nil {
   311  				return
   312  			}
   313  		}
   314  
   315  		if l.TLS {
   316  			p.printTLSInfo(resp.TLS, false)
   317  			p.printTLSServer(req.Host, resp.TLS)
   318  		}
   319  
   320  		p.printResponse(resp)
   321  	}()
   322  
   323  	return tripper.RoundTrip(req)
   324  }
   325  
   326  // Middleware for logging incoming requests to a HTTP server.
   327  func (l *Logger) Middleware(next http.Handler) http.Handler {
   328  	return httpHandler{
   329  		logger: l,
   330  		next:   next,
   331  	}
   332  }
   333  
   334  type httpHandler struct {
   335  	logger *Logger
   336  	next   http.Handler
   337  }
   338  
   339  // ServeHTTP is a middleware for logging incoming requests to a HTTP server.
   340  func (h httpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   341  	l := h.logger
   342  	p := newPrinter(l)
   343  	defer p.flush()
   344  
   345  	if hide := req.Context().Value(contextHide{}); hide != nil || p.checkFilter(req) {
   346  		h.next.ServeHTTP(w, req)
   347  		return
   348  	}
   349  
   350  	if p.logger.Time {
   351  		defer p.printTimeRequest()()
   352  	}
   353  
   354  	if !p.logger.SkipRequestInfo {
   355  		p.printRequestInfo(req)
   356  	}
   357  
   358  	if p.logger.TLS {
   359  		p.printTLSInfo(req.TLS, true)
   360  		p.printIncomingClientTLS(req.TLS)
   361  	}
   362  
   363  	p.printRequest(req)
   364  
   365  	rec := &responseRecorder{
   366  		ResponseWriter: w,
   367  
   368  		statusCode: http.StatusOK,
   369  
   370  		maxReadableBody: l.MaxResponseBody,
   371  		buf:             &bytes.Buffer{},
   372  	}
   373  
   374  	defer p.printServerResponse(req, rec)
   375  	h.next.ServeHTTP(rec, req)
   376  }
   377  
   378  // PrintRequest prints a request, even when WithHide is used to hide it.
   379  //
   380  // It doesn't log TLS connection details or request duration.
   381  func (l *Logger) PrintRequest(req *http.Request) {
   382  	var p = printer{logger: l}
   383  
   384  	if skip := p.checkFilter(req); skip {
   385  		return
   386  	}
   387  
   388  	p.printRequest(req)
   389  }
   390  
   391  // PrintResponse prints a response.
   392  func (l *Logger) PrintResponse(resp *http.Response) {
   393  	var p = printer{logger: l}
   394  	p.printResponse(resp)
   395  }
   396  
   397  // JSONFormatter helps you read unreadable JSON documents.
   398  //
   399  // github.com/tidwall/pretty could be used to add colors to it.
   400  // However, it would add an external dependency. If you want, you can define
   401  // your own formatter using it or anything else. See Formatter.
   402  type JSONFormatter struct{}
   403  
   404  // Match JSON media type.
   405  func (j *JSONFormatter) Match(mediatype string) bool {
   406  	return mediatype == "application/json"
   407  }
   408  
   409  // Format JSON content.
   410  func (j *JSONFormatter) Format(w io.Writer, src []byte) error {
   411  	if !json.Valid(src) {
   412  		// We want to get the error of json.checkValid, not unmarshal it.
   413  		// The happy path has been optimized, maybe prematurely.
   414  		if err := json.Unmarshal(src, &json.RawMessage{}); err != nil {
   415  			return err
   416  		}
   417  	}
   418  
   419  	// avoiding allocation as we use *bytes.Buffer to store the formatted body before printing
   420  	dst, ok := w.(*bytes.Buffer)
   421  	if !ok {
   422  		// mitigating panic to avoid upsetting anyone who uses this directly
   423  		return errors.New("underlying writer for JSONFormatter must be *bytes.Buffer")
   424  	}
   425  	return json.Indent(dst, src, "", "    ")
   426  }
   427  

View as plain text