From a963052d64c953e15bea42671d366a3eadd5b91c Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Mon, 1 May 2017 19:30:52 +0200 Subject: [PATCH] Add custom HTTP transport --- src/restic/backend/http_transport.go | 28 ++++++++++++++ src/restic/backend/rest/rest.go | 4 +- src/restic/backend/s3/s3.go | 4 +- src/restic/debug/round_tripper_debug.go | 51 ++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 src/restic/backend/http_transport.go diff --git a/src/restic/backend/http_transport.go b/src/restic/backend/http_transport.go new file mode 100644 index 000000000..1f65ac7c3 --- /dev/null +++ b/src/restic/backend/http_transport.go @@ -0,0 +1,28 @@ +package backend + +import ( + "net" + "net/http" + "restic/debug" + "time" +) + +// Transport returns a new http.RoundTripper with default settings applied. +func Transport() http.RoundTripper { + // copied from net/http + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + // wrap in the debug round tripper + return debug.RoundTripper(tr) +} diff --git a/src/restic/backend/rest/rest.go b/src/restic/backend/rest/rest.go index 773fad6bd..5a0e6226b 100644 --- a/src/restic/backend/rest/rest.go +++ b/src/restic/backend/rest/rest.go @@ -35,8 +35,8 @@ func Open(cfg Config) (restic.Backend, error) { for i := 0; i < connLimit; i++ { connChan <- struct{}{} } - tr := &http.Transport{MaxIdleConnsPerHost: connLimit} - client := http.Client{Transport: tr} + + client := http.Client{Transport: backend.Transport()} // use url without trailing slash for layout url := cfg.URL.String() diff --git a/src/restic/backend/s3/s3.go b/src/restic/backend/s3/s3.go index 2f6db84f5..f16b3c594 100644 --- a/src/restic/backend/s3/s3.go +++ b/src/restic/backend/s3/s3.go @@ -3,7 +3,6 @@ package s3 import ( "bytes" "io" - "net/http" "path" "restic" "strings" @@ -48,8 +47,7 @@ func Open(cfg Config) (restic.Backend, error) { Layout: &backend.S3Layout{Path: cfg.Prefix, Join: path.Join}, } - tr := &http.Transport{MaxIdleConnsPerHost: connLimit} - client.SetCustomTransport(tr) + client.SetCustomTransport(backend.Transport()) be.createConnections() diff --git a/src/restic/debug/round_tripper_debug.go b/src/restic/debug/round_tripper_debug.go index 4ca686b38..0ff9343ad 100644 --- a/src/restic/debug/round_tripper_debug.go +++ b/src/restic/debug/round_tripper_debug.go @@ -3,10 +3,59 @@ package debug import ( + "fmt" + "io" + "io/ioutil" "net/http" "net/http/httputil" + "os" + "restic/errors" ) +type eofDetectRoundTripper struct { + http.RoundTripper +} + +type eofDetectReader struct { + eofSeen bool + rd io.ReadCloser +} + +func (rd *eofDetectReader) Read(p []byte) (n int, err error) { + n, err = rd.rd.Read(p) + if err == io.EOF { + rd.eofSeen = true + } + return n, err +} + +func (rd *eofDetectReader) Close() error { + if !rd.eofSeen { + buf, err := ioutil.ReadAll(rd) + msg := fmt.Sprintf("body not drained, %d bytes not read", len(buf)) + if err != nil { + msg += fmt.Sprintf(", error: %v", err) + } + + if len(buf) > 0 { + if len(buf) > 20 { + buf = append(buf[:20], []byte("...")...) + } + msg += fmt.Sprintf(", body: %q", buf) + } + + fmt.Fprintln(os.Stderr, msg) + Log("%s: %+v", msg, errors.New("Close()")) + } + return rd.rd.Close() +} + +func (tr eofDetectRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { + res, err = tr.RoundTripper.RoundTrip(req) + res.Body = &eofDetectReader{rd: res.Body} + return res, err +} + type loggingRoundTripper struct { http.RoundTripper } @@ -14,7 +63,7 @@ type loggingRoundTripper struct { // RoundTripper returns a new http.RoundTripper which logs all requests (if // debug is enabled). When debug is not enabled, upstream is returned. func RoundTripper(upstream http.RoundTripper) http.RoundTripper { - return loggingRoundTripper{upstream} + return loggingRoundTripper{eofDetectRoundTripper{upstream}} } func (tr loggingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) {