github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/lfsapi/verbose.go (about) 1 package lfsapi 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "net/http" 9 "net/http/httputil" 10 "strings" 11 12 "github.com/rubyist/tracerx" 13 ) 14 15 func (c *Client) traceRequest(req *http.Request) (*tracedRequest, error) { 16 tracerx.Printf("HTTP: %s", traceReq(req)) 17 18 if c.Verbose { 19 if dump, err := httputil.DumpRequest(req, false); err == nil { 20 c.traceHTTPDump(">", dump) 21 } 22 } 23 24 body, ok := req.Body.(ReadSeekCloser) 25 if body != nil && !ok { 26 return nil, fmt.Errorf("Request body must implement io.ReadCloser and io.Seeker. Got: %T", body) 27 } 28 29 if body != nil && ok { 30 body.Seek(0, io.SeekStart) 31 tr := &tracedRequest{ 32 verbose: c.Verbose && isTraceableContent(req.Header), 33 verboseOut: c.VerboseOut, 34 ReadSeekCloser: body, 35 } 36 req.Body = tr 37 return tr, nil 38 } 39 40 return nil, nil 41 } 42 43 type tracedRequest struct { 44 BodySize int64 45 verbose bool 46 verboseOut io.Writer 47 ReadSeekCloser 48 } 49 50 func (r *tracedRequest) Read(b []byte) (int, error) { 51 n, err := tracedRead(r.ReadSeekCloser, b, r.verboseOut, false, r.verbose) 52 r.BodySize += int64(n) 53 return n, err 54 } 55 56 func (c *Client) traceResponse(req *http.Request, tracedReq *tracedRequest, res *http.Response) { 57 if tracedReq != nil { 58 c.httpLogger.LogRequest(req, tracedReq.BodySize) 59 } 60 61 if res == nil { 62 c.httpLogger.LogResponse(req, -1, 0) 63 return 64 } 65 66 tracerx.Printf("HTTP: %d", res.StatusCode) 67 68 verboseBody := isTraceableContent(res.Header) 69 res.Body = &tracedResponse{ 70 httpLogger: c.httpLogger, 71 response: res, 72 gitTrace: verboseBody, 73 verbose: verboseBody && c.Verbose, 74 verboseOut: c.VerboseOut, 75 ReadCloser: res.Body, 76 } 77 78 if !c.Verbose { 79 return 80 } 81 82 if dump, err := httputil.DumpResponse(res, false); err == nil { 83 if verboseBody { 84 fmt.Fprintf(c.VerboseOut, "\n\n") 85 } else { 86 fmt.Fprintf(c.VerboseOut, "\n") 87 } 88 c.traceHTTPDump("<", dump) 89 } 90 } 91 92 type tracedResponse struct { 93 BodySize int64 94 httpLogger *syncLogger 95 response *http.Response 96 verbose bool 97 gitTrace bool 98 verboseOut io.Writer 99 eof bool 100 io.ReadCloser 101 } 102 103 func (r *tracedResponse) Read(b []byte) (int, error) { 104 n, err := tracedRead(r.ReadCloser, b, r.verboseOut, r.gitTrace, r.verbose) 105 r.BodySize += int64(n) 106 107 if err == io.EOF && !r.eof { 108 r.httpLogger.LogResponse(r.response.Request, r.response.StatusCode, r.BodySize) 109 r.eof = true 110 } 111 return n, err 112 } 113 114 func tracedRead(r io.Reader, b []byte, verboseOut io.Writer, gitTrace, verbose bool) (int, error) { 115 n, err := r.Read(b) 116 if err == nil || err == io.EOF { 117 if n > 0 && (gitTrace || verbose) { 118 chunk := string(b[0:n]) 119 if gitTrace { 120 tracerx.Printf("HTTP: %s", chunk) 121 } 122 123 if verbose { 124 fmt.Fprint(verboseOut, chunk) 125 } 126 } 127 } 128 129 return n, err 130 } 131 132 func (c *Client) traceHTTPDump(direction string, dump []byte) { 133 scanner := bufio.NewScanner(bytes.NewBuffer(dump)) 134 135 for scanner.Scan() { 136 line := scanner.Text() 137 if !c.DebuggingVerbose && strings.HasPrefix(strings.ToLower(line), "authorization: basic") { 138 fmt.Fprintf(c.VerboseOut, "%s Authorization: Basic * * * * *\n", direction) 139 } else { 140 fmt.Fprintf(c.VerboseOut, "%s %s\n", direction, line) 141 } 142 } 143 } 144 145 var tracedTypes = []string{"json", "text", "xml", "html"} 146 147 func isTraceableContent(h http.Header) bool { 148 ctype := strings.ToLower(strings.SplitN(h.Get("Content-Type"), ";", 2)[0]) 149 for _, tracedType := range tracedTypes { 150 if strings.Contains(ctype, tracedType) { 151 return true 152 } 153 } 154 return false 155 } 156 157 func traceReq(req *http.Request) string { 158 return fmt.Sprintf("%s %s", req.Method, strings.SplitN(req.URL.String(), "?", 2)[0]) 159 }