github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/fshttp/http.go (about) 1 // Package fshttp contains the common http parts of the config, Transport and Client 2 package fshttp 3 4 import ( 5 "bytes" 6 "context" 7 "crypto/tls" 8 "crypto/x509" 9 "io/ioutil" 10 "log" 11 "net" 12 "net/http" 13 "net/http/cookiejar" 14 "net/http/httputil" 15 "sync" 16 "time" 17 18 "github.com/rclone/rclone/fs" 19 "github.com/rclone/rclone/lib/structs" 20 "golang.org/x/net/publicsuffix" 21 "golang.org/x/time/rate" 22 ) 23 24 const ( 25 separatorReq = ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" 26 separatorResp = "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" 27 ) 28 29 var ( 30 transport http.RoundTripper 31 noTransport = new(sync.Once) 32 tpsBucket *rate.Limiter // for limiting number of http transactions per second 33 cookieJar, _ = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) 34 ) 35 36 // StartHTTPTokenBucket starts the token bucket if necessary 37 func StartHTTPTokenBucket() { 38 if fs.Config.TPSLimit > 0 { 39 tpsBurst := fs.Config.TPSLimitBurst 40 if tpsBurst < 1 { 41 tpsBurst = 1 42 } 43 tpsBucket = rate.NewLimiter(rate.Limit(fs.Config.TPSLimit), tpsBurst) 44 fs.Infof(nil, "Starting HTTP transaction limiter: max %g transactions/s with burst %d", fs.Config.TPSLimit, tpsBurst) 45 } 46 } 47 48 // A net.Conn that sets a deadline for every Read or Write operation 49 type timeoutConn struct { 50 net.Conn 51 timeout time.Duration 52 } 53 54 // create a timeoutConn using the timeout 55 func newTimeoutConn(conn net.Conn, timeout time.Duration) (c *timeoutConn, err error) { 56 c = &timeoutConn{ 57 Conn: conn, 58 timeout: timeout, 59 } 60 err = c.nudgeDeadline() 61 return 62 } 63 64 // Nudge the deadline for an idle timeout on by c.timeout if non-zero 65 func (c *timeoutConn) nudgeDeadline() (err error) { 66 if c.timeout == 0 { 67 return nil 68 } 69 when := time.Now().Add(c.timeout) 70 return c.Conn.SetDeadline(when) 71 } 72 73 // readOrWrite bytes doing idle timeouts 74 func (c *timeoutConn) readOrWrite(f func([]byte) (int, error), b []byte) (n int, err error) { 75 n, err = f(b) 76 // Don't nudge if no bytes or an error 77 if n == 0 || err != nil { 78 return 79 } 80 // Nudge the deadline on successful Read or Write 81 err = c.nudgeDeadline() 82 return 83 } 84 85 // Read bytes doing idle timeouts 86 func (c *timeoutConn) Read(b []byte) (n int, err error) { 87 return c.readOrWrite(c.Conn.Read, b) 88 } 89 90 // Write bytes doing idle timeouts 91 func (c *timeoutConn) Write(b []byte) (n int, err error) { 92 return c.readOrWrite(c.Conn.Write, b) 93 } 94 95 // dial with context and timeouts 96 func dialContextTimeout(ctx context.Context, network, address string, ci *fs.ConfigInfo) (net.Conn, error) { 97 dialer := NewDialer(ci) 98 c, err := dialer.DialContext(ctx, network, address) 99 if err != nil { 100 return c, err 101 } 102 return newTimeoutConn(c, ci.Timeout) 103 } 104 105 // ResetTransport resets the existing transport, allowing it to take new settings. 106 // Should only be used for testing. 107 func ResetTransport() { 108 noTransport = new(sync.Once) 109 } 110 111 // NewTransportCustom returns an http.RoundTripper with the correct timeouts. 112 // The customize function is called if set to give the caller an opportunity to 113 // customize any defaults in the Transport. 114 func NewTransportCustom(ci *fs.ConfigInfo, customize func(*http.Transport)) http.RoundTripper { 115 // Start with a sensible set of defaults then override. 116 // This also means we get new stuff when it gets added to go 117 t := new(http.Transport) 118 structs.SetDefaults(t, http.DefaultTransport.(*http.Transport)) 119 t.Proxy = http.ProxyFromEnvironment 120 t.MaxIdleConnsPerHost = 2 * (ci.Checkers + ci.Transfers + 1) 121 t.MaxIdleConns = 2 * t.MaxIdleConnsPerHost 122 t.TLSHandshakeTimeout = ci.ConnectTimeout 123 t.ResponseHeaderTimeout = ci.Timeout 124 125 // TLS Config 126 t.TLSClientConfig = &tls.Config{ 127 InsecureSkipVerify: ci.InsecureSkipVerify, 128 } 129 130 // Load client certs 131 if ci.ClientCert != "" || ci.ClientKey != "" { 132 if ci.ClientCert == "" || ci.ClientKey == "" { 133 log.Fatalf("Both --client-cert and --client-key must be set") 134 } 135 cert, err := tls.LoadX509KeyPair(ci.ClientCert, ci.ClientKey) 136 if err != nil { 137 log.Fatalf("Failed to load --client-cert/--client-key pair: %v", err) 138 } 139 t.TLSClientConfig.Certificates = []tls.Certificate{cert} 140 t.TLSClientConfig.BuildNameToCertificate() 141 } 142 143 // Load CA cert 144 if ci.CaCert != "" { 145 caCert, err := ioutil.ReadFile(ci.CaCert) 146 if err != nil { 147 log.Fatalf("Failed to read --ca-cert: %v", err) 148 } 149 caCertPool := x509.NewCertPool() 150 ok := caCertPool.AppendCertsFromPEM(caCert) 151 if !ok { 152 log.Fatalf("Failed to add certificates from --ca-cert") 153 } 154 t.TLSClientConfig.RootCAs = caCertPool 155 } 156 157 t.DisableCompression = ci.NoGzip 158 t.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { 159 return dialContextTimeout(ctx, network, addr, ci) 160 } 161 t.IdleConnTimeout = 60 * time.Second 162 t.ExpectContinueTimeout = ci.ExpectContinueTimeout 163 164 if ci.Dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 { 165 fs.Debugf(nil, "You have specified to dump information. Please be noted that the "+ 166 "Accept-Encoding as shown may not be correct in the request and the response may not show "+ 167 "Content-Encoding if the go standard libraries auto gzip encoding was in effect. In this case"+ 168 " the body of the request will be gunzipped before showing it.") 169 } 170 171 // customize the transport if required 172 if customize != nil { 173 customize(t) 174 } 175 176 // Wrap that http.Transport in our own transport 177 return newTransport(ci, t) 178 } 179 180 // NewTransport returns an http.RoundTripper with the correct timeouts 181 func NewTransport(ci *fs.ConfigInfo) http.RoundTripper { 182 (*noTransport).Do(func() { 183 transport = NewTransportCustom(ci, nil) 184 }) 185 return transport 186 } 187 188 // NewClient returns an http.Client with the correct timeouts 189 func NewClient(ci *fs.ConfigInfo) *http.Client { 190 client := &http.Client{ 191 Transport: NewTransport(ci), 192 } 193 if ci.Cookie { 194 client.Jar = cookieJar 195 } 196 return client 197 } 198 199 // Transport is our http Transport which wraps an http.Transport 200 // * Sets the User Agent 201 // * Does logging 202 type Transport struct { 203 *http.Transport 204 dump fs.DumpFlags 205 filterRequest func(req *http.Request) 206 userAgent string 207 headers []*fs.HTTPOption 208 } 209 210 // newTransport wraps the http.Transport passed in and logs all 211 // roundtrips including the body if logBody is set. 212 func newTransport(ci *fs.ConfigInfo, transport *http.Transport) *Transport { 213 return &Transport{ 214 Transport: transport, 215 dump: ci.Dump, 216 userAgent: ci.UserAgent, 217 headers: ci.Headers, 218 } 219 } 220 221 // SetRequestFilter sets a filter to be used on each request 222 func (t *Transport) SetRequestFilter(f func(req *http.Request)) { 223 t.filterRequest = f 224 } 225 226 // A mutex to protect this map 227 var checkedHostMu sync.RWMutex 228 229 // A map of servers we have checked for time 230 var checkedHost = make(map[string]struct{}, 1) 231 232 // Check the server time is the same as ours, once for each server 233 func checkServerTime(req *http.Request, resp *http.Response) { 234 host := req.URL.Host 235 if req.Host != "" { 236 host = req.Host 237 } 238 checkedHostMu.RLock() 239 _, ok := checkedHost[host] 240 checkedHostMu.RUnlock() 241 if ok { 242 return 243 } 244 dateString := resp.Header.Get("Date") 245 if dateString == "" { 246 return 247 } 248 date, err := http.ParseTime(dateString) 249 if err != nil { 250 fs.Debugf(nil, "Couldn't parse Date: from server %s: %q: %v", host, dateString, err) 251 return 252 } 253 dt := time.Since(date) 254 const window = 5 * 60 * time.Second 255 if dt > window || dt < -window { 256 fs.Logf(nil, "Time may be set wrong - time from %q is %v different from this computer", host, dt) 257 } 258 checkedHostMu.Lock() 259 checkedHost[host] = struct{}{} 260 checkedHostMu.Unlock() 261 } 262 263 // cleanAuth gets rid of one authBuf header within the first 4k 264 func cleanAuth(buf, authBuf []byte) []byte { 265 // Find how much buffer to check 266 n := 4096 267 if len(buf) < n { 268 n = len(buf) 269 } 270 // See if there is an Authorization: header 271 i := bytes.Index(buf[:n], authBuf) 272 if i < 0 { 273 return buf 274 } 275 i += len(authBuf) 276 // Overwrite the next 4 chars with 'X' 277 for j := 0; i < len(buf) && j < 4; j++ { 278 if buf[i] == '\n' { 279 break 280 } 281 buf[i] = 'X' 282 i++ 283 } 284 // Snip out to the next '\n' 285 j := bytes.IndexByte(buf[i:], '\n') 286 if j < 0 { 287 return buf[:i] 288 } 289 n = copy(buf[i:], buf[i+j:]) 290 return buf[:i+n] 291 } 292 293 var authBufs = [][]byte{ 294 []byte("Authorization: "), 295 []byte("X-Auth-Token: "), 296 } 297 298 // cleanAuths gets rid of all the possible Auth headers 299 func cleanAuths(buf []byte) []byte { 300 for _, authBuf := range authBufs { 301 buf = cleanAuth(buf, authBuf) 302 } 303 return buf 304 } 305 306 // RoundTrip implements the RoundTripper interface. 307 func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 308 // Get transactions per second token first if limiting 309 if tpsBucket != nil { 310 tbErr := tpsBucket.Wait(req.Context()) 311 if tbErr != nil && tbErr != context.Canceled { 312 fs.Errorf(nil, "HTTP token bucket error: %v", tbErr) 313 } 314 } 315 // Force user agent 316 req.Header.Set("User-Agent", t.userAgent) 317 // Set user defined headers 318 for _, option := range t.headers { 319 req.Header.Set(option.Key, option.Value) 320 } 321 // Filter the request if required 322 if t.filterRequest != nil { 323 t.filterRequest(req) 324 } 325 // Logf request 326 if t.dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 { 327 buf, _ := httputil.DumpRequestOut(req, t.dump&(fs.DumpBodies|fs.DumpRequests) != 0) 328 if t.dump&fs.DumpAuth == 0 { 329 buf = cleanAuths(buf) 330 } 331 fs.Debugf(nil, "%s", separatorReq) 332 fs.Debugf(nil, "%s (req %p)", "HTTP REQUEST", req) 333 fs.Debugf(nil, "%s", string(buf)) 334 fs.Debugf(nil, "%s", separatorReq) 335 } 336 // Do round trip 337 resp, err = t.Transport.RoundTrip(req) 338 // Logf response 339 if t.dump&(fs.DumpHeaders|fs.DumpBodies|fs.DumpAuth|fs.DumpRequests|fs.DumpResponses) != 0 { 340 fs.Debugf(nil, "%s", separatorResp) 341 fs.Debugf(nil, "%s (req %p)", "HTTP RESPONSE", req) 342 if err != nil { 343 fs.Debugf(nil, "Error: %v", err) 344 } else { 345 buf, _ := httputil.DumpResponse(resp, t.dump&(fs.DumpBodies|fs.DumpResponses) != 0) 346 fs.Debugf(nil, "%s", string(buf)) 347 } 348 fs.Debugf(nil, "%s", separatorResp) 349 } 350 if err == nil { 351 checkServerTime(req, resp) 352 } 353 return resp, err 354 } 355 356 // NewDialer creates a net.Dialer structure with Timeout, Keepalive 357 // and LocalAddr set from rclone flags. 358 func NewDialer(ci *fs.ConfigInfo) *net.Dialer { 359 dialer := &net.Dialer{ 360 Timeout: ci.ConnectTimeout, 361 KeepAlive: 30 * time.Second, 362 } 363 if ci.BindAddr != nil { 364 dialer.LocalAddr = &net.TCPAddr{IP: ci.BindAddr} 365 } 366 return dialer 367 }