github.com/coreos/goproxy@v0.0.0-20190513173959-f8dc2d7ba04e/transport/transport.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // HTTP client implementation. See RFC 2616. 6 // 7 // This is the low-level Transport implementation of RoundTripper. 8 // The high-level interface is in client.go. 9 10 // This file is DEPRECATED and keep solely for backward compatibility. 11 12 package transport 13 14 import ( 15 "net/http" 16 "bufio" 17 "compress/gzip" 18 "crypto/tls" 19 "encoding/base64" 20 "errors" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "log" 25 "net" 26 "net/url" 27 "os" 28 "strings" 29 "sync" 30 ) 31 32 // DefaultTransport is the default implementation of Transport and is 33 // used by DefaultClient. It establishes a new network connection for 34 // each call to Do and uses HTTP proxies as directed by the 35 // $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy) 36 // environment variables. 37 var DefaultTransport RoundTripper = &Transport{Proxy: ProxyFromEnvironment} 38 39 // DefaultMaxIdleConnsPerHost is the default value of Transport's 40 // MaxIdleConnsPerHost. 41 const DefaultMaxIdleConnsPerHost = 2 42 43 // Transport is an implementation of RoundTripper that supports http, 44 // https, and http proxies (for either http or https with CONNECT). 45 // Transport can also cache connections for future re-use. 46 type Transport struct { 47 lk sync.Mutex 48 idleConn map[string][]*persistConn 49 altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper 50 51 // TODO: tunable on global max cached connections 52 // TODO: tunable on timeout on cached connections 53 // TODO: optional pipelining 54 55 // Proxy specifies a function to return a proxy for a given 56 // Request. If the function returns a non-nil error, the 57 // request is aborted with the provided error. 58 // If Proxy is nil or returns a nil *URL, no proxy is used. 59 Proxy func(*http.Request) (*url.URL, error) 60 61 // Dial specifies the dial function for creating TCP 62 // connections. 63 // If Dial is nil, net.Dial is used. 64 Dial func(net, addr string) (c net.Conn, err error) 65 66 // TLSClientConfig specifies the TLS configuration to use with 67 // tls.Client. If nil, the default configuration is used. 68 TLSClientConfig *tls.Config 69 70 DisableKeepAlives bool 71 DisableCompression bool 72 73 // MaxIdleConnsPerHost, if non-zero, controls the maximum idle 74 // (keep-alive) to keep to keep per-host. If zero, 75 // DefaultMaxIdleConnsPerHost is used. 76 MaxIdleConnsPerHost int 77 } 78 79 // ProxyFromEnvironment returns the URL of the proxy to use for a 80 // given request, as indicated by the environment variables 81 // $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy). 82 // An error is returned if the proxy environment is invalid. 83 // A nil URL and nil error are returned if no proxy is defined in the 84 // environment, or a proxy should not be used for the given request. 85 func ProxyFromEnvironment(req *http.Request) (*url.URL, error) { 86 proxy := getenvEitherCase("HTTP_PROXY") 87 if proxy == "" { 88 return nil, nil 89 } 90 if !useProxy(canonicalAddr(req.URL)) { 91 return nil, nil 92 } 93 proxyURL, err := url.Parse(proxy) 94 if err != nil || proxyURL.Scheme == "" { 95 if u, err := url.Parse("http://" + proxy); err == nil { 96 proxyURL = u 97 err = nil 98 } 99 } 100 if err != nil { 101 return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) 102 } 103 return proxyURL, nil 104 } 105 106 // ProxyURL returns a proxy function (for use in a Transport) 107 // that always returns the same URL. 108 func ProxyURL(fixedURL *url.URL) func(*http.Request) (*url.URL, error) { 109 return func(*http.Request) (*url.URL, error) { 110 return fixedURL, nil 111 } 112 } 113 114 // transportRequest is a wrapper around a *Request that adds 115 // optional extra headers to write. 116 type transportRequest struct { 117 *http.Request // original request, not to be mutated 118 extra http.Header // extra headers to write, or nil 119 } 120 121 func (tr *transportRequest) extraHeaders() http.Header { 122 if tr.extra == nil { 123 tr.extra = make(http.Header) 124 } 125 return tr.extra 126 } 127 128 type RoundTripDetails struct { 129 Host string 130 TCPAddr *net.TCPAddr 131 IsProxy bool 132 Error error 133 } 134 135 func (t *Transport) DetailedRoundTrip(req *http.Request) (details *RoundTripDetails, resp *http.Response, err error) { 136 if req.URL == nil { 137 return nil, nil, errors.New("http: nil Request.URL") 138 } 139 if req.Header == nil { 140 return nil, nil, errors.New("http: nil Request.Header") 141 } 142 if req.URL.Scheme != "http" && req.URL.Scheme != "https" { 143 t.lk.Lock() 144 var rt RoundTripper 145 if t.altProto != nil { 146 rt = t.altProto[req.URL.Scheme] 147 } 148 t.lk.Unlock() 149 if rt == nil { 150 return nil, nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme} 151 } 152 return rt.DetailedRoundTrip(req) 153 } 154 treq := &transportRequest{Request: req} 155 cm, err := t.connectMethodForRequest(treq) 156 if err != nil { 157 return nil, nil, err 158 } 159 160 // Get the cached or newly-created connection to either the 161 // host (for http or https), the http proxy, or the http proxy 162 // pre-CONNECTed to https server. In any case, we'll be ready 163 // to send it requests. 164 pconn, err := t.getConn(cm) 165 if err != nil { 166 return nil, nil, err 167 } 168 169 resp, err = pconn.roundTrip(treq) 170 return &RoundTripDetails{pconn.host, pconn.ip, pconn.isProxy, err}, resp, err 171 } 172 173 // RoundTrip implements the RoundTripper interface. 174 func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { 175 _, resp, err = t.DetailedRoundTrip(req) 176 return 177 } 178 179 // RegisterProtocol registers a new protocol with scheme. 180 // The Transport will pass requests using the given scheme to rt. 181 // It is rt's responsibility to simulate HTTP request semantics. 182 // 183 // RegisterProtocol can be used by other packages to provide 184 // implementations of protocol schemes like "ftp" or "file". 185 func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) { 186 if scheme == "http" || scheme == "https" { 187 panic("protocol " + scheme + " already registered") 188 } 189 t.lk.Lock() 190 defer t.lk.Unlock() 191 if t.altProto == nil { 192 t.altProto = make(map[string]RoundTripper) 193 } 194 if _, exists := t.altProto[scheme]; exists { 195 panic("protocol " + scheme + " already registered") 196 } 197 t.altProto[scheme] = rt 198 } 199 200 // CloseIdleConnections closes any connections which were previously 201 // connected from previous requests but are now sitting idle in 202 // a "keep-alive" state. It does not interrupt any connections currently 203 // in use. 204 func (t *Transport) CloseIdleConnections() { 205 t.lk.Lock() 206 defer t.lk.Unlock() 207 if t.idleConn == nil { 208 return 209 } 210 for _, conns := range t.idleConn { 211 for _, pconn := range conns { 212 pconn.close() 213 } 214 } 215 t.idleConn = make(map[string][]*persistConn) 216 } 217 218 // 219 // Private implementation past this point. 220 // 221 222 func getenvEitherCase(k string) string { 223 if v := os.Getenv(strings.ToUpper(k)); v != "" { 224 return v 225 } 226 return os.Getenv(strings.ToLower(k)) 227 } 228 229 func (t *Transport) connectMethodForRequest(treq *transportRequest) (*connectMethod, error) { 230 cm := &connectMethod{ 231 targetScheme: treq.URL.Scheme, 232 targetAddr: canonicalAddr(treq.URL), 233 } 234 if t.Proxy != nil { 235 var err error 236 cm.proxyURL, err = t.Proxy(treq.Request) 237 if err != nil { 238 return nil, err 239 } 240 } 241 return cm, nil 242 } 243 244 // proxyAuth returns the Proxy-Authorization header to set 245 // on requests, if applicable. 246 func (cm *connectMethod) proxyAuth() string { 247 if cm.proxyURL == nil { 248 return "" 249 } 250 if u := cm.proxyURL.User; u != nil { 251 return "Basic " + base64.URLEncoding.EncodeToString([]byte(u.String())) 252 } 253 return "" 254 } 255 256 // putIdleConn adds pconn to the list of idle persistent connections awaiting 257 // a new request. 258 // If pconn is no longer needed or not in a good state, putIdleConn 259 // returns false. 260 func (t *Transport) putIdleConn(pconn *persistConn) bool { 261 t.lk.Lock() 262 defer t.lk.Unlock() 263 if t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 { 264 pconn.close() 265 return false 266 } 267 if pconn.isBroken() { 268 return false 269 } 270 key := pconn.cacheKey 271 max := t.MaxIdleConnsPerHost 272 if max == 0 { 273 max = DefaultMaxIdleConnsPerHost 274 } 275 if len(t.idleConn[key]) >= max { 276 pconn.close() 277 return false 278 } 279 t.idleConn[key] = append(t.idleConn[key], pconn) 280 return true 281 } 282 283 func (t *Transport) getIdleConn(cm *connectMethod) (pconn *persistConn) { 284 t.lk.Lock() 285 defer t.lk.Unlock() 286 if t.idleConn == nil { 287 t.idleConn = make(map[string][]*persistConn) 288 } 289 key := cm.String() 290 for { 291 pconns, ok := t.idleConn[key] 292 if !ok { 293 return nil 294 } 295 if len(pconns) == 1 { 296 pconn = pconns[0] 297 delete(t.idleConn, key) 298 } else { 299 // 2 or more cached connections; pop last 300 // TODO: queue? 301 pconn = pconns[len(pconns)-1] 302 t.idleConn[key] = pconns[0 : len(pconns)-1] 303 } 304 if !pconn.isBroken() { 305 return 306 } 307 } 308 return 309 } 310 311 func (t *Transport) dial(network, addr string) (c net.Conn, raddr string, ip *net.TCPAddr, err error) { 312 if t.Dial != nil { 313 ip, err = net.ResolveTCPAddr("tcp", addr) 314 if err!=nil { 315 return 316 } 317 c, err = t.Dial(network, addr) 318 raddr = addr 319 return 320 } 321 addri, err := net.ResolveTCPAddr("tcp", addr) 322 if err!=nil { 323 return 324 } 325 c, err = net.DialTCP("tcp", nil, addri) 326 raddr = addr 327 ip = addri 328 return 329 } 330 331 // getConn dials and creates a new persistConn to the target as 332 // specified in the connectMethod. This includes doing a proxy CONNECT 333 // and/or setting up TLS. If this doesn't return an error, the persistConn 334 // is ready to write requests to. 335 func (t *Transport) getConn(cm *connectMethod) (*persistConn, error) { 336 if pc := t.getIdleConn(cm); pc != nil { 337 return pc, nil 338 } 339 340 conn, raddr, ip, err := t.dial("tcp", cm.addr()) 341 if err != nil { 342 if cm.proxyURL != nil { 343 err = fmt.Errorf("http: error connecting to proxy %s: %v", cm.proxyURL, err) 344 } 345 return nil, err 346 } 347 348 pa := cm.proxyAuth() 349 350 pconn := &persistConn{ 351 t: t, 352 cacheKey: cm.String(), 353 conn: conn, 354 reqch: make(chan requestAndChan, 50), 355 host: raddr, 356 ip: ip, 357 } 358 359 switch { 360 case cm.proxyURL == nil: 361 // Do nothing. 362 case cm.targetScheme == "http": 363 pconn.isProxy = true 364 if pa != "" { 365 pconn.mutateHeaderFunc = func(h http.Header) { 366 h.Set("Proxy-Authorization", pa) 367 } 368 } 369 case cm.targetScheme == "https": 370 connectReq := &http.Request{ 371 Method: "CONNECT", 372 URL: &url.URL{Opaque: cm.targetAddr}, 373 Host: cm.targetAddr, 374 Header: make(http.Header), 375 } 376 if pa != "" { 377 connectReq.Header.Set("Proxy-Authorization", pa) 378 } 379 connectReq.Write(conn) 380 381 // Read response. 382 // Okay to use and discard buffered reader here, because 383 // TLS server will not speak until spoken to. 384 br := bufio.NewReader(conn) 385 resp, err := http.ReadResponse(br, connectReq) 386 if err != nil { 387 conn.Close() 388 return nil, err 389 } 390 if resp.StatusCode != 200 { 391 f := strings.SplitN(resp.Status, " ", 2) 392 conn.Close() 393 return nil, errors.New(f[1]) 394 } 395 } 396 397 if cm.targetScheme == "https" { 398 // Initiate TLS and check remote host name against certificate. 399 conn = tls.Client(conn, t.TLSClientConfig) 400 if err = conn.(*tls.Conn).Handshake(); err != nil { 401 return nil, err 402 } 403 if t.TLSClientConfig == nil || !t.TLSClientConfig.InsecureSkipVerify { 404 if err = conn.(*tls.Conn).VerifyHostname(cm.tlsHost()); err != nil { 405 return nil, err 406 } 407 } 408 pconn.conn = conn 409 } 410 411 pconn.br = bufio.NewReader(pconn.conn) 412 pconn.bw = bufio.NewWriter(pconn.conn) 413 go pconn.readLoop() 414 return pconn, nil 415 } 416 417 // useProxy returns true if requests to addr should use a proxy, 418 // according to the NO_PROXY or no_proxy environment variable. 419 // addr is always a canonicalAddr with a host and port. 420 func useProxy(addr string) bool { 421 if len(addr) == 0 { 422 return true 423 } 424 host, _, err := net.SplitHostPort(addr) 425 if err != nil { 426 return false 427 } 428 if host == "localhost" { 429 return false 430 } 431 if ip := net.ParseIP(host); ip != nil { 432 if ip.IsLoopback() { 433 return false 434 } 435 } 436 437 no_proxy := getenvEitherCase("NO_PROXY") 438 if no_proxy == "*" { 439 return false 440 } 441 442 addr = strings.ToLower(strings.TrimSpace(addr)) 443 if hasPort(addr) { 444 addr = addr[:strings.LastIndex(addr, ":")] 445 } 446 447 for _, p := range strings.Split(no_proxy, ",") { 448 p = strings.ToLower(strings.TrimSpace(p)) 449 if len(p) == 0 { 450 continue 451 } 452 if hasPort(p) { 453 p = p[:strings.LastIndex(p, ":")] 454 } 455 if addr == p || (p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:])) { 456 return false 457 } 458 } 459 return true 460 } 461 462 // connectMethod is the map key (in its String form) for keeping persistent 463 // TCP connections alive for subsequent HTTP requests. 464 // 465 // A connect method may be of the following types: 466 // 467 // Cache key form Description 468 // ----------------- ------------------------- 469 // ||http|foo.com http directly to server, no proxy 470 // ||https|foo.com https directly to server, no proxy 471 // http://proxy.com|https|foo.com http to proxy, then CONNECT to foo.com 472 // http://proxy.com|http http to proxy, http to anywhere after that 473 // 474 // Note: no support to https to the proxy yet. 475 // 476 type connectMethod struct { 477 proxyURL *url.URL // nil for no proxy, else full proxy URL 478 targetScheme string // "http" or "https" 479 targetAddr string // Not used if proxy + http targetScheme (4th example in table) 480 } 481 482 func (ck *connectMethod) String() string { 483 proxyStr := "" 484 if ck.proxyURL != nil { 485 proxyStr = ck.proxyURL.String() 486 } 487 return strings.Join([]string{proxyStr, ck.targetScheme, ck.targetAddr}, "|") 488 } 489 490 // addr returns the first hop "host:port" to which we need to TCP connect. 491 func (cm *connectMethod) addr() string { 492 if cm.proxyURL != nil { 493 return canonicalAddr(cm.proxyURL) 494 } 495 return cm.targetAddr 496 } 497 498 // tlsHost returns the host name to match against the peer's 499 // TLS certificate. 500 func (cm *connectMethod) tlsHost() string { 501 h := cm.targetAddr 502 if hasPort(h) { 503 h = h[:strings.LastIndex(h, ":")] 504 } 505 return h 506 } 507 508 // persistConn wraps a connection, usually a persistent one 509 // (but may be used for non-keep-alive requests as well) 510 type persistConn struct { 511 t *Transport 512 cacheKey string // its connectMethod.String() 513 conn net.Conn 514 br *bufio.Reader // from conn 515 bw *bufio.Writer // to conn 516 reqch chan requestAndChan // written by roundTrip(); read by readLoop() 517 isProxy bool 518 519 // mutateHeaderFunc is an optional func to modify extra 520 // headers on each outbound request before it's written. (the 521 // original Request given to RoundTrip is not modified) 522 mutateHeaderFunc func(http.Header) 523 524 lk sync.Mutex // guards numExpectedResponses and broken 525 numExpectedResponses int 526 broken bool // an error has happened on this connection; marked broken so it's not reused. 527 528 host string 529 ip *net.TCPAddr 530 } 531 532 func (pc *persistConn) isBroken() bool { 533 pc.lk.Lock() 534 defer pc.lk.Unlock() 535 return pc.broken 536 } 537 538 var remoteSideClosedFunc func(error) bool // or nil to use default 539 540 func remoteSideClosed(err error) bool { 541 if err == io.EOF { 542 return true 543 } 544 if remoteSideClosedFunc != nil { 545 return remoteSideClosedFunc(err) 546 } 547 return false 548 } 549 550 func (pc *persistConn) readLoop() { 551 alive := true 552 var lastbody io.ReadCloser // last response body, if any, read on this connection 553 554 for alive { 555 pb, err := pc.br.Peek(1) 556 557 pc.lk.Lock() 558 if pc.numExpectedResponses == 0 { 559 pc.closeLocked() 560 pc.lk.Unlock() 561 if len(pb) > 0 { 562 log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", 563 string(pb), err) 564 } 565 return 566 } 567 pc.lk.Unlock() 568 569 rc := <-pc.reqch 570 571 // Advance past the previous response's body, if the 572 // caller hasn't done so. 573 if lastbody != nil { 574 lastbody.Close() // assumed idempotent 575 lastbody = nil 576 } 577 resp, err := http.ReadResponse(pc.br, rc.req) 578 579 if err != nil { 580 pc.close() 581 } else { 582 hasBody := rc.req.Method != "HEAD" && resp.ContentLength != 0 583 if rc.addedGzip && hasBody && resp.Header.Get("Content-Encoding") == "gzip" { 584 resp.Header.Del("Content-Encoding") 585 resp.Header.Del("Content-Length") 586 resp.ContentLength = -1 587 gzReader, zerr := gzip.NewReader(resp.Body) 588 if zerr != nil { 589 pc.close() 590 err = zerr 591 } else { 592 resp.Body = &readFirstCloseBoth{&discardOnCloseReadCloser{gzReader}, resp.Body} 593 } 594 } 595 resp.Body = &bodyEOFSignal{body: resp.Body} 596 } 597 598 if err != nil || resp.Close || rc.req.Close { 599 alive = false 600 } 601 602 hasBody := resp != nil && resp.ContentLength != 0 603 var waitForBodyRead chan bool 604 if alive { 605 if hasBody { 606 lastbody = resp.Body 607 waitForBodyRead = make(chan bool) 608 resp.Body.(*bodyEOFSignal).fn = func() { 609 if !pc.t.putIdleConn(pc) { 610 alive = false 611 } 612 waitForBodyRead <- true 613 } 614 } else { 615 // When there's no response body, we immediately 616 // reuse the TCP connection (putIdleConn), but 617 // we need to prevent ClientConn.Read from 618 // closing the Response.Body on the next 619 // loop, otherwise it might close the body 620 // before the client code has had a chance to 621 // read it (even though it'll just be 0, EOF). 622 lastbody = nil 623 624 if !pc.t.putIdleConn(pc) { 625 alive = false 626 } 627 } 628 } 629 630 rc.ch <- responseAndError{resp, err} 631 632 // Wait for the just-returned response body to be fully consumed 633 // before we race and peek on the underlying bufio reader. 634 if waitForBodyRead != nil { 635 <-waitForBodyRead 636 } 637 } 638 } 639 640 type responseAndError struct { 641 res *http.Response 642 err error 643 } 644 645 type requestAndChan struct { 646 req *http.Request 647 ch chan responseAndError 648 649 // did the Transport (as opposed to the client code) add an 650 // Accept-Encoding gzip header? only if it we set it do 651 // we transparently decode the gzip. 652 addedGzip bool 653 } 654 655 func (pc *persistConn) roundTrip(req *transportRequest) (resp *http.Response, err error) { 656 if pc.mutateHeaderFunc != nil { 657 panic("mutateHeaderFunc not supported in modified Transport") 658 pc.mutateHeaderFunc(req.extraHeaders()) 659 } 660 661 // Ask for a compressed version if the caller didn't set their 662 // own value for Accept-Encoding. We only attempted to 663 // uncompress the gzip stream if we were the layer that 664 // requested it. 665 requestedGzip := false 666 if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" { 667 // Request gzip only, not deflate. Deflate is ambiguous and 668 // not as universally supported anyway. 669 // See: http://www.gzip.org/zlib/zlib_faq.html#faq38 670 requestedGzip = true 671 req.extraHeaders().Set("Accept-Encoding", "gzip") 672 } 673 674 pc.lk.Lock() 675 pc.numExpectedResponses++ 676 pc.lk.Unlock() 677 678 // orig: err = req.Request.write(pc.bw, pc.isProxy, req.extra) 679 if pc.isProxy { 680 err = req.Request.WriteProxy(pc.bw) 681 } else { 682 err = req.Request.Write(pc.bw) 683 } 684 if err != nil { 685 pc.close() 686 return 687 } 688 pc.bw.Flush() 689 690 ch := make(chan responseAndError, 1) 691 pc.reqch <- requestAndChan{req.Request, ch, requestedGzip} 692 re := <-ch 693 pc.lk.Lock() 694 pc.numExpectedResponses-- 695 pc.lk.Unlock() 696 697 return re.res, re.err 698 } 699 700 func (pc *persistConn) close() { 701 pc.lk.Lock() 702 defer pc.lk.Unlock() 703 pc.closeLocked() 704 } 705 706 func (pc *persistConn) closeLocked() { 707 pc.broken = true 708 pc.conn.Close() 709 pc.mutateHeaderFunc = nil 710 } 711 712 var portMap = map[string]string{ 713 "http": "80", 714 "https": "443", 715 } 716 717 // canonicalAddr returns url.Host but always with a ":port" suffix 718 func canonicalAddr(url *url.URL) string { 719 addr := url.Host 720 if !hasPort(addr) { 721 return addr + ":" + portMap[url.Scheme] 722 } 723 return addr 724 } 725 726 func responseIsKeepAlive(res *http.Response) bool { 727 // TODO: implement. for now just always shutting down the connection. 728 return false 729 } 730 731 // bodyEOFSignal wraps a ReadCloser but runs fn (if non-nil) at most 732 // once, right before the final Read() or Close() call returns, but after 733 // EOF has been seen. 734 type bodyEOFSignal struct { 735 body io.ReadCloser 736 fn func() 737 isClosed bool 738 } 739 740 func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { 741 n, err = es.body.Read(p) 742 if es.isClosed && n > 0 { 743 panic("http: unexpected bodyEOFSignal Read after Close; see issue 1725") 744 } 745 if err == io.EOF && es.fn != nil { 746 es.fn() 747 es.fn = nil 748 } 749 return 750 } 751 752 func (es *bodyEOFSignal) Close() (err error) { 753 if es.isClosed { 754 return nil 755 } 756 es.isClosed = true 757 err = es.body.Close() 758 if err == nil && es.fn != nil { 759 es.fn() 760 es.fn = nil 761 } 762 return 763 } 764 765 type readFirstCloseBoth struct { 766 io.ReadCloser 767 io.Closer 768 } 769 770 func (r *readFirstCloseBoth) Close() error { 771 if err := r.ReadCloser.Close(); err != nil { 772 r.Closer.Close() 773 return err 774 } 775 if err := r.Closer.Close(); err != nil { 776 return err 777 } 778 return nil 779 } 780 781 // discardOnCloseReadCloser consumes all its input on Close. 782 type discardOnCloseReadCloser struct { 783 io.ReadCloser 784 } 785 786 func (d *discardOnCloseReadCloser) Close() error { 787 io.Copy(ioutil.Discard, d.ReadCloser) // ignore errors; likely invalid or already closed 788 return d.ReadCloser.Close() 789 }