terraform/vendor/github.com/cyberdelia/heroku-go/v3/transport.go

153 lines
3.3 KiB
Go
Raw Normal View History

package heroku
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"net/http/httputil"
"os"
"strings"
"github.com/pborman/uuid"
)
var DefaultTransport = &Transport{}
var DefaultClient = &http.Client{
Transport: DefaultTransport,
}
type Transport struct {
// Username is the HTTP basic auth username for API calls made by this Client.
Username string
// Password is the HTTP basic auth password for API calls made by this Client.
Password string
// UserAgent to be provided in API requests. Set to DefaultUserAgent if not
// specified.
UserAgent string
// Debug mode can be used to dump the full request and response to stdout.
Debug bool
// AdditionalHeaders are extra headers to add to each HTTP request sent by
// this Client.
AdditionalHeaders http.Header
// Transport is the HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
Transport http.RoundTripper
}
// Forward CancelRequest to underlying Transport
func (t *Transport) CancelRequest(req *http.Request) {
type canceler interface {
CancelRequest(*http.Request)
}
tr, ok := t.Transport.(canceler)
if !ok {
log.Printf("heroku: Client Transport of type %T doesn't support CancelRequest; Timeout not supported\n", t.Transport)
return
}
tr.CancelRequest(req)
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.Transport == nil {
t.Transport = http.DefaultTransport
}
// Making a copy of the Request so that
// we don't modify the Request we were given.
req = cloneRequest(req)
if t.UserAgent != "" {
req.Header.Set("User-Agent", t.UserAgent)
}
req.Header.Set("Accept", "application/vnd.heroku+json; version=3")
req.Header.Set("Request-Id", uuid.New())
req.SetBasicAuth(t.Username, t.Password)
for k, v := range t.AdditionalHeaders {
req.Header[k] = v
}
if t.Debug {
dump, err := httputil.DumpRequestOut(req, true)
if err != nil {
log.Println(err)
} else {
os.Stderr.Write(dump)
os.Stderr.Write([]byte{'\n', '\n'})
}
}
resp, err := t.Transport.RoundTrip(req)
if err != nil {
if resp != nil {
resp.Body.Close()
}
return nil, err
}
if t.Debug {
dump, err := httputil.DumpResponse(resp, true)
if err != nil {
log.Println(err)
} else {
os.Stderr.Write(dump)
os.Stderr.Write([]byte{'\n'})
}
}
if err = checkResponse(resp); err != nil {
if resp != nil {
resp.Body.Close()
}
return nil, err
}
return resp, nil
}
type Error struct {
error
ID string
URL string
}
func checkResponse(resp *http.Response) error {
if resp.StatusCode/100 != 2 { // 200, 201, 202, etc
var e struct {
Message string
ID string
URL string `json:"url"`
}
err := json.NewDecoder(resp.Body).Decode(&e)
if err != nil {
return fmt.Errorf("encountered an error : %s", resp.Status)
}
return Error{error: errors.New(e.Message), ID: e.ID, URL: e.URL}
}
if msg := resp.Header.Get("X-Heroku-Warning"); msg != "" {
log.Println(strings.TrimSpace(msg))
}
return nil
}
// cloneRequest returns a clone of the provided *http.Request.
func cloneRequest(req *http.Request) *http.Request {
// shallow copy of the struct
clone := new(http.Request)
*clone = *req
// deep copy of the Header
clone.Header = make(http.Header)
for k, s := range req.Header {
clone.Header[k] = s
}
return clone
}