Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
211 lines (176 sloc) 5.93 kb
Provides an HTTP Transport that implements the `RoundTripper` interface and
can be used as a built in replacement for the standard library's, providing:
* connection timeouts
* request timeouts
This is a thin wrapper around `http.Transport` that sets dial timeouts and uses
Go's internal timer scheduler to call the Go 1.1+ `CancelRequest()` API.
package httpclient
import (
// returns the current version of the package
func Version() string {
return "0.4.1"
// Transport implements the RoundTripper interface and can be used as a replacement
// for Go's built in http.Transport implementing end-to-end request timeouts.
// transport := &httpclient.Transport{
// ConnectTimeout: 1*time.Second,
// ResponseHeaderTimeout: 5*time.Second,
// RequestTimeout: 10*time.Second,
// }
// defer transport.Close()
// client := &http.Client{Transport: transport}
// req, _ := http.NewRequest("GET", "", nil)
// resp, err := client.Do(req)
// if err != nil {
// return err
// }
// defer resp.Body.Close()
type Transport struct {
// Proxy specifies a function to return a proxy for a given
// *http.Request. If the function returns a non-nil error, the
// request is aborted with the provided error.
// If Proxy is nil or returns a nil *url.URL, no proxy is used.
Proxy func(*http.Request) (*url.URL, error)
// Dial specifies the dial function for creating TCP
// connections. This will override the Transport's ConnectTimeout and
// ReadWriteTimeout settings.
// If Dial is nil, a dialer is generated on demand matching the Transport's
// options.
Dial func(network, addr string) (net.Conn, error)
// TLSClientConfig specifies the TLS configuration to use with
// tls.Client. If nil, the default configuration is used.
TLSClientConfig *tls.Config
// DisableKeepAlives, if true, prevents re-use of TCP connections
// between different HTTP requests.
DisableKeepAlives bool
// DisableCompression, if true, prevents the Transport from
// requesting compression with an "Accept-Encoding: gzip"
// request header when the Request contains no existing
// Accept-Encoding value. If the Transport requests gzip on
// its own and gets a gzipped response, it's transparently
// decoded in the Response.Body. However, if the user
// explicitly requested gzip it is not automatically
// uncompressed.
DisableCompression bool
// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
// (keep-alive) to keep per-host. If zero,
// http.DefaultMaxIdleConnsPerHost is used.
MaxIdleConnsPerHost int
// ConnectTimeout, if non-zero, is the maximum amount of time a dial will wait for
// a connect to complete.
ConnectTimeout time.Duration
// ResponseHeaderTimeout, if non-zero, specifies the amount of
// time to wait for a server's response headers after fully
// writing the request (including its body, if any). This
// time does not include the time to read the response body.
ResponseHeaderTimeout time.Duration
// RequestTimeout, if non-zero, specifies the amount of time for the entire
// request to complete (including all of the above timeouts + entire response body).
// This should never be less than the sum total of the above two timeouts.
RequestTimeout time.Duration
// ReadWriteTimeout, if non-zero, will set a deadline for every Read and
// Write operation on the request connection.
ReadWriteTimeout time.Duration
starter sync.Once
transport *http.Transport
// Close cleans up the Transport, currently a no-op
func (t *Transport) Close() error {
return nil
func (t *Transport) lazyStart() {
if t.Dial == nil {
t.Dial = func(netw, addr string) (net.Conn, error) {
c, err := net.DialTimeout(netw, addr, t.ConnectTimeout)
if err != nil {
return nil, err
if t.ReadWriteTimeout > 0 {
timeoutConn := &rwTimeoutConn{
TCPConn: c.(*net.TCPConn),
rwTimeout: t.ReadWriteTimeout,
return timeoutConn, nil
return c, nil
t.transport = &http.Transport{
Dial: t.Dial,
Proxy: t.Proxy,
TLSClientConfig: t.TLSClientConfig,
DisableKeepAlives: t.DisableKeepAlives,
DisableCompression: t.DisableCompression,
MaxIdleConnsPerHost: t.MaxIdleConnsPerHost,
ResponseHeaderTimeout: t.ResponseHeaderTimeout,
func (t *Transport) CancelRequest(req *http.Request) {
func (t *Transport) CloseIdleConnections() {
func (t *Transport) RegisterProtocol(scheme string, rt http.RoundTripper) {
t.transport.RegisterProtocol(scheme, rt)
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
if t.RequestTimeout > 0 {
timer := time.AfterFunc(t.RequestTimeout, func() {
resp, err = t.transport.RoundTrip(req)
if err != nil {
} else {
resp.Body = &bodyCloseInterceptor{ReadCloser: resp.Body, timer: timer}
} else {
resp, err = t.transport.RoundTrip(req)
type bodyCloseInterceptor struct {
timer *time.Timer
func (bci *bodyCloseInterceptor) Close() error {
return bci.ReadCloser.Close()
// A net.Conn that sets a deadline for every Read or Write operation
type rwTimeoutConn struct {
rwTimeout time.Duration
func (c *rwTimeoutConn) Read(b []byte) (int, error) {
err := c.TCPConn.SetReadDeadline(time.Now().Add(c.rwTimeout))
if err != nil {
return 0, err
return c.TCPConn.Read(b)
func (c *rwTimeoutConn) Write(b []byte) (int, error) {
err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.rwTimeout))
if err != nil {
return 0, err
return c.TCPConn.Write(b)
Jump to Line
Something went wrong with that request. Please try again.