httpclient.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. Provides an HTTP Transport that implements the `RoundTripper` interface and
  3. can be used as a built in replacement for the standard library's, providing:
  4. * connection timeouts
  5. * request timeouts
  6. This is a thin wrapper around `http.Transport` that sets dial timeouts and uses
  7. Go's internal timer scheduler to call the Go 1.1+ `CancelRequest()` API.
  8. */
  9. package httpclient
  10. import (
  11. "crypto/tls"
  12. "io"
  13. "net"
  14. "net/http"
  15. "net/url"
  16. "sync"
  17. "time"
  18. )
  19. // returns the current version of the package
  20. func Version() string {
  21. return "0.4.1"
  22. }
  23. // Transport implements the RoundTripper interface and can be used as a replacement
  24. // for Go's built in http.Transport implementing end-to-end request timeouts.
  25. //
  26. // transport := &httpclient.Transport{
  27. // ConnectTimeout: 1*time.Second,
  28. // ResponseHeaderTimeout: 5*time.Second,
  29. // RequestTimeout: 10*time.Second,
  30. // }
  31. // defer transport.Close()
  32. //
  33. // client := &http.Client{Transport: transport}
  34. // req, _ := http.NewRequest("GET", "http://127.0.0.1/test", nil)
  35. // resp, err := client.Do(req)
  36. // if err != nil {
  37. // return err
  38. // }
  39. // defer resp.Body.Close()
  40. //
  41. type Transport struct {
  42. // Proxy specifies a function to return a proxy for a given
  43. // *http.Request. If the function returns a non-nil error, the
  44. // request is aborted with the provided error.
  45. // If Proxy is nil or returns a nil *url.URL, no proxy is used.
  46. Proxy func(*http.Request) (*url.URL, error)
  47. // Dial specifies the dial function for creating TCP
  48. // connections. This will override the Transport's ConnectTimeout and
  49. // ReadWriteTimeout settings.
  50. // If Dial is nil, a dialer is generated on demand matching the Transport's
  51. // options.
  52. Dial func(network, addr string) (net.Conn, error)
  53. // TLSClientConfig specifies the TLS configuration to use with
  54. // tls.Client. If nil, the default configuration is used.
  55. TLSClientConfig *tls.Config
  56. // DisableKeepAlives, if true, prevents re-use of TCP connections
  57. // between different HTTP requests.
  58. DisableKeepAlives bool
  59. // DisableCompression, if true, prevents the Transport from
  60. // requesting compression with an "Accept-Encoding: gzip"
  61. // request header when the Request contains no existing
  62. // Accept-Encoding value. If the Transport requests gzip on
  63. // its own and gets a gzipped response, it's transparently
  64. // decoded in the Response.Body. However, if the user
  65. // explicitly requested gzip it is not automatically
  66. // uncompressed.
  67. DisableCompression bool
  68. // MaxIdleConnsPerHost, if non-zero, controls the maximum idle
  69. // (keep-alive) to keep per-host. If zero,
  70. // http.DefaultMaxIdleConnsPerHost is used.
  71. MaxIdleConnsPerHost int
  72. // ConnectTimeout, if non-zero, is the maximum amount of time a dial will wait for
  73. // a connect to complete.
  74. ConnectTimeout time.Duration
  75. // ResponseHeaderTimeout, if non-zero, specifies the amount of
  76. // time to wait for a server's response headers after fully
  77. // writing the request (including its body, if any). This
  78. // time does not include the time to read the response body.
  79. ResponseHeaderTimeout time.Duration
  80. // RequestTimeout, if non-zero, specifies the amount of time for the entire
  81. // request to complete (including all of the above timeouts + entire response body).
  82. // This should never be less than the sum total of the above two timeouts.
  83. RequestTimeout time.Duration
  84. // ReadWriteTimeout, if non-zero, will set a deadline for every Read and
  85. // Write operation on the request connection.
  86. ReadWriteTimeout time.Duration
  87. starter sync.Once
  88. transport *http.Transport
  89. }
  90. // Close cleans up the Transport, currently a no-op
  91. func (t *Transport) Close() error {
  92. return nil
  93. }
  94. func (t *Transport) lazyStart() {
  95. if t.Dial == nil {
  96. t.Dial = func(netw, addr string) (net.Conn, error) {
  97. c, err := net.DialTimeout(netw, addr, t.ConnectTimeout)
  98. if err != nil {
  99. return nil, err
  100. }
  101. if t.ReadWriteTimeout > 0 {
  102. timeoutConn := &rwTimeoutConn{
  103. TCPConn: c.(*net.TCPConn),
  104. rwTimeout: t.ReadWriteTimeout,
  105. }
  106. return timeoutConn, nil
  107. }
  108. return c, nil
  109. }
  110. }
  111. t.transport = &http.Transport{
  112. Dial: t.Dial,
  113. Proxy: t.Proxy,
  114. TLSClientConfig: t.TLSClientConfig,
  115. DisableKeepAlives: t.DisableKeepAlives,
  116. DisableCompression: t.DisableCompression,
  117. MaxIdleConnsPerHost: t.MaxIdleConnsPerHost,
  118. ResponseHeaderTimeout: t.ResponseHeaderTimeout,
  119. }
  120. }
  121. func (t *Transport) CancelRequest(req *http.Request) {
  122. t.starter.Do(t.lazyStart)
  123. t.transport.CancelRequest(req)
  124. }
  125. func (t *Transport) CloseIdleConnections() {
  126. t.starter.Do(t.lazyStart)
  127. t.transport.CloseIdleConnections()
  128. }
  129. func (t *Transport) RegisterProtocol(scheme string, rt http.RoundTripper) {
  130. t.starter.Do(t.lazyStart)
  131. t.transport.RegisterProtocol(scheme, rt)
  132. }
  133. func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
  134. t.starter.Do(t.lazyStart)
  135. if t.RequestTimeout > 0 {
  136. timer := time.AfterFunc(t.RequestTimeout, func() {
  137. t.transport.CancelRequest(req)
  138. })
  139. resp, err = t.transport.RoundTrip(req)
  140. if err != nil {
  141. timer.Stop()
  142. } else {
  143. resp.Body = &bodyCloseInterceptor{ReadCloser: resp.Body, timer: timer}
  144. }
  145. } else {
  146. resp, err = t.transport.RoundTrip(req)
  147. }
  148. return
  149. }
  150. type bodyCloseInterceptor struct {
  151. io.ReadCloser
  152. timer *time.Timer
  153. }
  154. func (bci *bodyCloseInterceptor) Close() error {
  155. bci.timer.Stop()
  156. return bci.ReadCloser.Close()
  157. }
  158. // A net.Conn that sets a deadline for every Read or Write operation
  159. type rwTimeoutConn struct {
  160. *net.TCPConn
  161. rwTimeout time.Duration
  162. }
  163. func (c *rwTimeoutConn) Read(b []byte) (int, error) {
  164. err := c.TCPConn.SetReadDeadline(time.Now().Add(c.rwTimeout))
  165. if err != nil {
  166. return 0, err
  167. }
  168. return c.TCPConn.Read(b)
  169. }
  170. func (c *rwTimeoutConn) Write(b []byte) (int, error) {
  171. err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.rwTimeout))
  172. if err != nil {
  173. return 0, err
  174. }
  175. return c.TCPConn.Write(b)
  176. }