git-lfs/lfs/http.go

337 lines
8.2 KiB
Go
Raw Normal View History

2015-03-19 19:30:55 +00:00
package lfs
import (
"bufio"
"bytes"
"crypto/tls"
2015-03-26 17:10:17 +00:00
"errors"
2015-03-05 19:49:15 +00:00
"fmt"
"io"
2015-04-10 18:43:58 +00:00
"net"
"net/http"
"net/http/httputil"
"os"
2015-03-05 19:49:15 +00:00
"strings"
"sync"
2015-04-10 18:43:58 +00:00
"time"
2015-05-13 19:43:41 +00:00
2015-05-25 18:20:50 +00:00
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)
type transferStats struct {
HeaderSize int
BodySize int
Start time.Time
Stop time.Time
}
type transfer struct {
requestStats *transferStats
responseStats *transferStats
}
var (
// TODO should use some locks
transfers = make(map[*http.Response]*transfer)
transferBuckets = make(map[string][]*http.Response)
transfersLock sync.Mutex
transferBucketsLock sync.Mutex
)
func LogTransfer(key string, res *http.Response) {
if Config.isLoggingStats {
transferBucketsLock.Lock()
transferBuckets[key] = append(transferBuckets[key], res)
transferBucketsLock.Unlock()
}
}
type HttpClient struct {
*http.Client
}
func (c *HttpClient) Do(req *http.Request) (*http.Response, error) {
traceHttpRequest(req)
crc := countingRequest(req)
if req.Body != nil {
// Only set the body if we have a body, but create the countingRequest
// anyway to make using zeroed stats easier.
req.Body = crc
}
start := time.Now()
res, err := c.Client.Do(req)
if err != nil {
return res, err
}
traceHttpResponse(res)
cresp := countingResponse(res)
res.Body = cresp
if Config.isLoggingStats {
// Pull request stats
reqHeaderSize := 0
dump, err := httputil.DumpRequest(req, false)
if err == nil {
reqHeaderSize = len(dump)
}
reqstats := &transferStats{HeaderSize: reqHeaderSize, BodySize: crc.Count}
resstats := &transferStats{Start: start}
transfersLock.Lock()
transfers[res] = &transfer{requestStats: reqstats, responseStats: resstats}
transfersLock.Unlock()
}
return res, err
}
func DoHTTP(req *http.Request) (*http.Response, error) {
res, err := Config.HttpClient().Do(req)
2015-03-26 18:46:33 +00:00
if res == nil {
res = &http.Response{StatusCode: 0, Header: make(http.Header), Request: req}
}
2015-03-05 18:39:37 +00:00
return res, err
}
func (c *Configuration) HttpClient() *HttpClient {
2015-05-12 08:47:34 +00:00
if c.httpClient != nil {
return c.httpClient
}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
}
2015-05-12 08:47:34 +00:00
sslVerify, _ := c.GitConfig("http.sslverify")
if sslVerify == "false" || len(os.Getenv("GIT_SSL_NO_VERIFY")) > 0 {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
c.httpClient = &HttpClient{
&http.Client{Transport: tr, CheckRedirect: checkRedirect},
2015-05-12 08:47:34 +00:00
}
return c.httpClient
}
2015-03-05 19:49:15 +00:00
2015-03-26 17:10:17 +00:00
func checkRedirect(req *http.Request, via []*http.Request) error {
if len(via) >= 3 {
return errors.New("stopped after 3 redirects")
}
oldest := via[0]
for key, _ := range oldest.Header {
if key == "Authorization" {
if req.URL.Scheme != oldest.URL.Scheme || req.URL.Host != oldest.URL.Host {
continue
}
}
2015-03-26 17:10:17 +00:00
req.Header.Set(key, oldest.Header.Get(key))
}
2015-03-26 18:46:33 +00:00
tracerx.Printf("api: redirect %s %s to %s", oldest.Method, oldest.URL, req.URL)
2015-03-26 17:10:17 +00:00
return nil
}
2015-03-05 19:49:15 +00:00
var tracedTypes = []string{"json", "text", "xml", "html"}
func traceHttpRequest(req *http.Request) {
2015-03-05 19:49:15 +00:00
tracerx.Printf("HTTP: %s %s", req.Method, req.URL.String())
if Config.isTracingHttp == false {
2015-03-05 19:49:15 +00:00
return
}
dump, err := httputil.DumpRequest(req, false)
if err != nil {
return
}
scanner := bufio.NewScanner(bytes.NewBuffer(dump))
for scanner.Scan() {
fmt.Fprintf(os.Stderr, "> %s\n", scanner.Text())
2015-03-05 19:49:15 +00:00
}
}
func traceHttpResponse(res *http.Response) {
2015-03-26 18:46:33 +00:00
if res == nil {
return
}
2015-03-05 19:49:15 +00:00
tracerx.Printf("HTTP: %d", res.StatusCode)
if Config.isTracingHttp == false {
2015-03-05 19:49:15 +00:00
return
}
dump, err := httputil.DumpResponse(res, false)
if err != nil {
return
2015-03-05 19:49:15 +00:00
}
scanner := bufio.NewScanner(bytes.NewBuffer(dump))
for scanner.Scan() {
fmt.Fprintf(os.Stderr, "< %s\n", scanner.Text())
2015-03-05 19:49:15 +00:00
}
}
func countingRequest(req *http.Request) *countingReadCloser {
return &countingReadCloser{request: req, ReadCloser: req.Body}
}
func countingResponse(res *http.Response) *countingReadCloser {
return &countingReadCloser{response: res, ReadCloser: res.Body}
2015-03-05 21:18:02 +00:00
}
type countingReadCloser struct {
Count int
request *http.Request
response *http.Response
io.ReadCloser
2015-03-05 21:18:02 +00:00
}
func (c *countingReadCloser) Read(b []byte) (int, error) {
n, err := c.ReadCloser.Read(b)
if err != nil && err != io.EOF {
return n, err
}
2015-03-05 21:18:02 +00:00
c.Count += n
if Config.isTracingHttp {
contentType := ""
if c.response != nil { // Response, only print certain kinds of data
contentType = strings.ToLower(strings.SplitN(c.response.Header.Get("Content-Type"), ";", 2)[0])
} else {
contentType = strings.ToLower(strings.SplitN(c.request.Header.Get("Content-Type"), ";", 2)[0])
}
2015-03-05 21:18:02 +00:00
for _, tracedType := range tracedTypes {
if strings.Contains(contentType, tracedType) {
fmt.Fprint(os.Stderr, string(b[0:n]))
}
}
}
2015-03-05 19:49:15 +00:00
if err == io.EOF && Config.isLoggingStats {
// This transfer is done, we're checking it this way so we can also
// catch transfers where the caller forgets to Close() the Body.
if c.response != nil {
transfersLock.Lock()
if transfer, ok := transfers[c.response]; ok {
resHeaderSize := 0
dump, err := httputil.DumpResponse(c.response, false)
if err == nil {
resHeaderSize = len(dump)
}
transfer.responseStats.HeaderSize = resHeaderSize
transfer.responseStats.BodySize = c.Count
transfer.responseStats.Stop = time.Now()
}
transfersLock.Unlock()
}
}
2015-03-05 19:49:15 +00:00
return n, err
}
func LogHttpStats() {
if !Config.isLoggingStats {
return
}
file, err := StatsLogFile()
if err != nil {
fmt.Fprintf(os.Stderr, "Error logging http stats: %s\n", err)
return
}
for key, responses := range transferBuckets {
for _, response := range responses {
stats := transfers[response]
fmt.Fprintf(file, "key=%s reqheader=%d reqbody=%d resheader=%d resbody=%d restime=%d status=%d\n",
key,
stats.requestStats.HeaderSize,
stats.requestStats.BodySize,
stats.responseStats.HeaderSize,
stats.responseStats.BodySize,
stats.responseStats.Stop.Sub(stats.responseStats.Start).Nanoseconds(),
response.StatusCode)
}
}
fmt.Fprintf(os.Stderr, "HTTP Stats logged to file %s\n", file.Name())
}
func DumpHttpStats(o io.Writer) {
if !Config.isLoggingStats {
return
}
fmt.Fprint(o, "HTTP Transfer Stats\n\n")
fmt.Fprintf(o, "Concurrent Transfers: %d, Batch: %v\n\n", Config.ConcurrentTransfers(), Config.BatchTransfer())
totalTransfers := 0
totalTime := int64(0)
for key, vs := range transferBuckets {
reqWireSize := 0
resWireSize := 0
reqHeaderSize := 0
resHeaderSize := 0
reqBodySize := 0
resBodySize := 0
keyTransfers := 0
keyTime := int64(0) // nanoseconds
for _, r := range vs {
s := transfers[r]
reqWireSize += s.requestStats.HeaderSize + s.requestStats.BodySize
resWireSize += s.responseStats.HeaderSize + s.responseStats.BodySize
reqHeaderSize += s.requestStats.HeaderSize
resHeaderSize += s.responseStats.HeaderSize
reqBodySize += s.requestStats.BodySize
resBodySize += s.responseStats.BodySize
totalTime += s.responseStats.Stop.Sub(s.responseStats.Start).Nanoseconds()
totalTransfers++
keyTime += s.responseStats.Stop.Sub(s.responseStats.Start).Nanoseconds()
keyTransfers++
}
wireSize := reqWireSize + resWireSize
fmt.Fprintf(o, "%s:\n", key)
fmt.Fprintf(o, "\tTransfers: %d\n", keyTransfers)
fmt.Fprintf(o, "\tTime: %.2fms\n", float64(keyTime)/1000000.0)
fmt.Fprintf(o, "\tWire Data (Bytes): %d\n", wireSize)
fmt.Fprintf(o, "\t\tRequest Size (Bytes): %d\n", reqWireSize)
fmt.Fprintf(o, "\t\t\tHeaders: %d\n", reqHeaderSize)
fmt.Fprintf(o, "\t\t\tBodies: %d\n", reqBodySize)
fmt.Fprintf(o, "\t\tResponse Size (Bytes): %d\n", resWireSize)
fmt.Fprintf(o, "\t\t\tHeaders: %d\n", resHeaderSize)
fmt.Fprintf(o, "\t\t\tBodies: %d\n", resBodySize)
fmt.Fprintf(o, "\tMean Wire Size: %d\n", wireSize/keyTransfers)
fmt.Fprintf(o, "\t\tRequests: %d\n", reqWireSize/keyTransfers)
fmt.Fprintf(o, "\t\tResponses: %d\n", resWireSize/keyTransfers)
fmt.Fprintf(o, "\tMean Transfer Time: %.2fms\n", float64(keyTime)/float64(keyTransfers)/1000000.0)
fmt.Fprintln(o, "")
}
fmt.Fprintf(o, "\nTotal Transfers: %d\n", totalTransfers)
fmt.Fprintf(o, "Total Time: %.2fms\n", float64(totalTime)/1000000.0)
2015-03-05 19:49:15 +00:00
fmt.Fprintln(o, "")
2015-03-05 19:49:15 +00:00
}