git-lfs/lfshttp/verbose.go
Preben Ingvaldsen d101bdb605 lfsapi: extract new lfshttp package
Extract more basic http-related functionality out of lfsapi and
into a new package, lfshttp. Everything is currently functional
aside from authorization.
2018-09-11 14:51:29 -07:00

160 lines
3.5 KiB
Go

package lfshttp
import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"net/http/httputil"
"strings"
"github.com/rubyist/tracerx"
)
func (c *Client) traceRequest(req *http.Request) (*tracedRequest, error) {
tracerx.Printf("HTTP: %s", traceReq(req))
if c.Verbose {
if dump, err := httputil.DumpRequest(req, false); err == nil {
c.traceHTTPDump(">", dump)
}
}
body, ok := req.Body.(ReadSeekCloser)
if body != nil && !ok {
return nil, fmt.Errorf("Request body must implement io.ReadCloser and io.Seeker. Got: %T", body)
}
if body != nil && ok {
body.Seek(0, io.SeekStart)
tr := &tracedRequest{
verbose: c.Verbose && isTraceableContent(req.Header),
verboseOut: c.VerboseOut,
ReadSeekCloser: body,
}
req.Body = tr
return tr, nil
}
return nil, nil
}
type tracedRequest struct {
BodySize int64
verbose bool
verboseOut io.Writer
ReadSeekCloser
}
func (r *tracedRequest) Read(b []byte) (int, error) {
n, err := tracedRead(r.ReadSeekCloser, b, r.verboseOut, false, r.verbose)
r.BodySize += int64(n)
return n, err
}
func (c *Client) traceResponse(req *http.Request, tracedReq *tracedRequest, res *http.Response) {
if tracedReq != nil {
c.httpLogger.LogRequest(req, tracedReq.BodySize)
}
if res == nil {
c.httpLogger.LogResponse(req, -1, 0)
return
}
tracerx.Printf("HTTP: %d", res.StatusCode)
verboseBody := isTraceableContent(res.Header)
res.Body = &tracedResponse{
httpLogger: c.httpLogger,
response: res,
gitTrace: verboseBody,
verbose: verboseBody && c.Verbose,
verboseOut: c.VerboseOut,
ReadCloser: res.Body,
}
if !c.Verbose {
return
}
if dump, err := httputil.DumpResponse(res, false); err == nil {
if verboseBody {
fmt.Fprintf(c.VerboseOut, "\n\n")
} else {
fmt.Fprintf(c.VerboseOut, "\n")
}
c.traceHTTPDump("<", dump)
}
}
type tracedResponse struct {
BodySize int64
httpLogger *syncLogger
response *http.Response
verbose bool
gitTrace bool
verboseOut io.Writer
eof bool
io.ReadCloser
}
func (r *tracedResponse) Read(b []byte) (int, error) {
n, err := tracedRead(r.ReadCloser, b, r.verboseOut, r.gitTrace, r.verbose)
r.BodySize += int64(n)
if err == io.EOF && !r.eof {
r.httpLogger.LogResponse(r.response.Request, r.response.StatusCode, r.BodySize)
r.eof = true
}
return n, err
}
func tracedRead(r io.Reader, b []byte, verboseOut io.Writer, gitTrace, verbose bool) (int, error) {
n, err := r.Read(b)
if err == nil || err == io.EOF {
if n > 0 && (gitTrace || verbose) {
chunk := string(b[0:n])
if gitTrace {
tracerx.Printf("HTTP: %s", chunk)
}
if verbose {
fmt.Fprint(verboseOut, chunk)
}
}
}
return n, err
}
func (c *Client) traceHTTPDump(direction string, dump []byte) {
scanner := bufio.NewScanner(bytes.NewBuffer(dump))
for scanner.Scan() {
line := scanner.Text()
if !c.DebuggingVerbose && strings.HasPrefix(strings.ToLower(line), "authorization: basic") {
fmt.Fprintf(c.VerboseOut, "%s Authorization: Basic * * * * *\n", direction)
} else {
fmt.Fprintf(c.VerboseOut, "%s %s\n", direction, line)
}
}
}
var tracedTypes = []string{"json", "text", "xml", "html"}
func isTraceableContent(h http.Header) bool {
ctype := strings.ToLower(strings.SplitN(h.Get("Content-Type"), ";", 2)[0])
for _, tracedType := range tracedTypes {
if strings.Contains(ctype, tracedType) {
return true
}
}
return false
}
func traceReq(req *http.Request) string {
return fmt.Sprintf("%s %s", req.Method, strings.SplitN(req.URL.String(), "?", 2)[0])
}