func dumpRespBody(resp *http.Response) ([]byte, error) {
	if resp.Body == nil {
		return nil, nil
	}
	var b bytes.Buffer
	savecl := resp.ContentLength
	var save io.ReadCloser
	var err error
	save, resp.Body, err = drainBody(resp.Body)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(&b, resp.Body)
	if err != nil {
		return nil, err
	}
	resp.Body = save
	resp.ContentLength = savecl
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}
Dump response body, strongly inspired from httputil.DumpResponse
package httpclient
import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"
	"github.com/rightscale/rsc/log"
	"github.com/rightscale/rsc/recording"
)
const (
	NoDump Format = 1 << iota
	Debug
	JSON
	Verbose
	Record
)
const (
	noRedirectError = "no redirect"
	requestIdHeader = "X-Request-Id"
)
var (
	defaultHiddenHeaders = map[string]bool{"Authorization": true, "Cookie": true}
	defaultResponseHeaderTimeout = 300 * time.Second
	DumpFormat Format
	Insecure bool
	NoCertCheck bool
	ResponseHeaderTimeout = defaultResponseHeaderTimeout
	HiddenHeaders map[string]bool
)
var (
	OsStderr io.Writer = os.Stderr
)
type (
	HTTPClient interface {
		Do(req *http.Request) (*http.Response, error)
		DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error)
		DoHidden(req *http.Request) (*http.Response, error)
		DoHiddenWithContext(ctx context.Context, req *http.Request) (*http.Response, error)
	}
	Format int
	ParamBlock struct {
		DumpFormat Format
		HiddenHeaders map[string]bool
		Insecure bool
		NoCertCheck bool
		NoRedirect bool
		DisableKeepAlives bool
		ResponseHeaderTimeout time.Duration
	}
	dumpClient struct {
		Client        *http.Client
		isInsecure    func() bool
		dumpFormat    func() Format
		hiddenHeaders func() map[string]bool
	}
)
func init() {
	DumpFormat = NoDump
	HiddenHeaders = copyHiddenHeaders(defaultHiddenHeaders)
}
func New() HTTPClient {
	return newVariableDumpClient(newRawClient(false, NoCertCheck, false, ResponseHeaderTimeout))
}
func NewNoRedirect() HTTPClient {
	return newVariableDumpClient(newRawClient(true, NoCertCheck, false, ResponseHeaderTimeout))
}
func NewPB(pb *ParamBlock) HTTPClient {
	responseHeaderTimeout := pb.ResponseHeaderTimeout
	if responseHeaderTimeout == 0 {
		responseHeaderTimeout = defaultResponseHeaderTimeout
	}
	dumpFormat := pb.DumpFormat
	if dumpFormat == 0 {
		dumpFormat = NoDump
	}
	hiddenHeaders := pb.HiddenHeaders
	if hiddenHeaders == nil {
		hiddenHeaders = defaultHiddenHeaders  
	} else {
		hiddenHeaders = copyHiddenHeaders(hiddenHeaders)  
	}
	dc := &dumpClient{Client: newRawClient(pb.NoRedirect, pb.NoCertCheck, pb.DisableKeepAlives, responseHeaderTimeout)}
	dc.isInsecure = func() bool {
		return pb.Insecure
	}
	dc.dumpFormat = func() Format {
		return dumpFormat
	}
	dc.hiddenHeaders = func() map[string]bool {
		return hiddenHeaders
	}
	return dc
}
func newVariableDumpClient(c *http.Client) HTTPClient {
	dc := &dumpClient{Client: c}
	dc.isInsecure = func() bool {
		return Insecure
	}
	dc.dumpFormat = func() Format {
		return DumpFormat
	}
	dc.hiddenHeaders = func() map[string]bool {
		return HiddenHeaders
	}
	return dc
}
func ShortToken() string {
	b := make([]byte, 6)
	io.ReadFull(rand.Reader, b)
	return base64.StdEncoding.EncodeToString(b)
}
func newRawClient(
	noRedirect, noCertCheck, disableKeepAlives bool,
	responseHeaderTimeout time.Duration) *http.Client {
	tr := http.Transport{
		DisableKeepAlives:     disableKeepAlives,
		ResponseHeaderTimeout: responseHeaderTimeout,
		Proxy:                 http.ProxyFromEnvironment,
	}
	tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: noCertCheck}
	c := http.Client{Transport: &tr}
	if noRedirect {
		c.CheckRedirect = func(*http.Request, []*http.Request) error {
			return fmt.Errorf(noRedirectError)
		}
	}
	return &c
}
func (f Format) IsDebug() bool {
	return f&Debug != 0
}
func (f Format) IsJSON() bool {
	return f&JSON != 0
}
func (f Format) IsVerbose() bool {
	return f&Verbose != 0
}
func (f Format) IsRecord() bool {
	return f&Record != 0
}
func (d *dumpClient) DoHidden(req *http.Request) (*http.Response, error) {
	return d.doImp(req, true, nil)
}
func (d *dumpClient) Do(req *http.Request) (*http.Response, error) {
	return d.doImp(req, false, nil)
}
func (d *dumpClient) DoWithContext(ctx context.Context, req *http.Request) (*http.Response, error) {
	return d.doImp(req, true, ctx)
}
func (d *dumpClient) DoHiddenWithContext(ctx context.Context, req *http.Request) (*http.Response, error) {
	return d.doImp(req, false, ctx)
}
func (d *dumpClient) doImp(req *http.Request, hidden bool, ctx context.Context) (*http.Response, error) {
	if req.URL.Scheme == "" {
		if d.isInsecure() {
			req.URL.Scheme = "http"
		} else {
			req.URL.Scheme = "https"
		}
	}
	ua := req.Header.Get("User-Agent")
	if ua == "" {
		req.Header.Set("User-Agent", UA)
	}
	var reqBody []byte
	startedAt := time.Now()
	id := req.Header.Get(requestIdHeader)
	if id == "" {
		id = ShortToken()
	}
	log.Info("started", "id", id, req.Method, req.URL.String())
	df := d.dumpFormat()
	hide := (df == NoDump) || (hidden && !df.IsVerbose())
	if !hide {
		startedAt = time.Now()
		reqBody = d.dumpRequest(req)
	}
	var resp *http.Response
	var err error
	if ctx == nil {
		resp, err = d.Client.Do(req)
	} else {
		resp, err = ctxhttpDo(ctx, d.getClientWithoutTimeout(), req)
	}
	if urlError, ok := err.(*url.Error); ok {
		if urlError.Err.Error() == noRedirectError {
			err = nil
		}
	}
	if err != nil {
		return nil, err
	}
	if !hide {
		d.dumpResponse(resp, req, reqBody)
	}
	log.Info("completed", "id", id, "status", resp.Status, "time", time.Since(startedAt).String())
	return resp, nil
}
func (d *dumpClient) getClientWithoutTimeout() *http.Client {
	client := *d.Client
	tr, ok := client.Transport.(*http.Transport)
	if ok {
		trCopy := &http.Transport{
			Proxy:                  tr.Proxy,
			DialContext:            tr.DialContext,
			Dial:                   tr.Dial,
			DialTLS:                tr.DialTLS,
			TLSClientConfig:        tr.TLSClientConfig,
			TLSHandshakeTimeout:    tr.TLSHandshakeTimeout,
			DisableKeepAlives:      tr.DisableKeepAlives,
			DisableCompression:     tr.DisableCompression,
			MaxIdleConns:           tr.MaxIdleConns,
			MaxIdleConnsPerHost:    tr.MaxIdleConnsPerHost,
			IdleConnTimeout:        tr.IdleConnTimeout,
			ResponseHeaderTimeout:  0,  
			ExpectContinueTimeout:  tr.ExpectContinueTimeout,
			TLSNextProto:           tr.TLSNextProto,
			MaxResponseHeaderBytes: tr.MaxResponseHeaderBytes,
		}
		tr = trCopy
	} else {
		tr = &http.Transport{Proxy: http.ProxyFromEnvironment}
		tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: NoCertCheck}
	}
	client.Transport = tr
	return &client
}
func (d *dumpClient) dumpRequest(req *http.Request) []byte {
	df := d.dumpFormat()
	if df == NoDump {
		return nil
	}
	reqBody, err := dumpReqBody(req)
	if err != nil {
		log.Error("Failed to load request body for dump", "error", err.Error())
	}
	if df.IsDebug() {
		var buffer bytes.Buffer
		buffer.WriteString(req.Method + " " + req.URL.String() + "\n")
		d.writeHeaders(&buffer, req.Header)
		if reqBody != nil {
			buffer.WriteString("\n")
			buffer.Write(reqBody)
			buffer.WriteString("\n")
		}
		fmt.Fprint(OsStderr, buffer.String())
	} else if df.IsJSON() {
		return reqBody
	}
	return nil
}
func (d *dumpClient) dumpResponse(resp *http.Response, req *http.Request, reqBody []byte) {
	df := d.dumpFormat()
	if df == NoDump {
		return
	}
	respBody, _ := dumpRespBody(resp)
	if df.IsDebug() {
		var buffer bytes.Buffer
		buffer.WriteString("==> " + resp.Proto + " " + resp.Status + "\n")
		d.writeHeaders(&buffer, resp.Header)
		if respBody != nil {
			buffer.WriteString("\n")
			buffer.Write(respBody)
			buffer.WriteString("\n")
		}
		fmt.Fprint(OsStderr, buffer.String())
	} else if df.IsJSON() {
		reqHeaders := make(http.Header)
		hh := d.hiddenHeaders()
		filterHeaders(df, hh, req.Header, func(name string, value []string) {
			reqHeaders[name] = value
		})
		respHeaders := make(http.Header)
		filterHeaders(df, hh, resp.Header, func(name string, value []string) {
			respHeaders[name] = value
		})
		dumped := recording.RequestResponse{
			Verb:       req.Method,
			URI:        req.URL.String(),
			ReqHeader:  reqHeaders,
			ReqBody:    string(reqBody),
			Status:     resp.StatusCode,
			RespHeader: respHeaders,
			RespBody:   string(respBody),
		}
		b, err := json.MarshalIndent(dumped, "", "    ")
		if err != nil {
			log.Error("Failed to dump request content", "error", err.Error())
			return
		}
		if df.IsRecord() {
			f := os.NewFile(10, "fd10")
			_, err = f.Stat()
			if err == nil {
				fmt.Fprintf(f, "%s\n", string(b))
			}
		}
		fmt.Fprint(OsStderr, string(b))
	}
}
func (d *dumpClient) writeHeaders(buffer *bytes.Buffer, headers http.Header) {
	filterHeaders(
		d.dumpFormat(),
		d.hiddenHeaders(),
		headers,
		func(name string, value []string) {
			buffer.WriteString(name)
			buffer.WriteString(": ")
			buffer.WriteString(strings.Join(value, ", "))
			buffer.WriteString("\n")
		})
}
func dumpReqBody(req *http.Request) ([]byte, error) {
	if req.Body == nil {
		return nil, nil
	}
	var save io.ReadCloser
	var err error
	save, req.Body, err = drainBody(req.Body)
	if err != nil {
		return nil, err
	}
	var b bytes.Buffer
	var dest io.Writer = &b
	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
	if chunked {
		dest = httputil.NewChunkedWriter(dest)
	}
	_, err = io.Copy(dest, req.Body)
	if chunked {
		dest.(io.Closer).Close()
		io.WriteString(&b, "\r\n")
	}
	req.Body = save
	return b.Bytes(), err
}