d101bdb605
Extract more basic http-related functionality out of lfsapi and into a new package, lfshttp. Everything is currently functional aside from authorization.
160 lines
3.5 KiB
Go
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])
|
|
}
|