1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
package v5
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"net/http/httputil"
"regexp"
"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
// BearerToken is a bearer token to authorize the request with. If this is
// set, the basic auth credentials will be ignored.
BearerToken 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())
if t.BearerToken != "" {
req.Header.Add("Authorization", "Bearer "+t.BearerToken)
} else if t.Username != "" || t.Password != "" {
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 {
scanner := bufio.NewScanner(bytes.NewReader(dump))
matcher := regexp.MustCompile(`(?i)authorization:`)
for scanner.Scan() {
line := scanner.Text()
if matcher.MatchString(line) {
matches := strings.Split(line, `:`)
line = fmt.Sprintf("%s: [redacted]", matches[0])
}
log.Println(line)
}
if err := scanner.Err(); err != nil {
log.Printf("Error scanning HTTP debug output for secret tokens: %s\n", err)
}
}
}
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 {
log.Printf("%s", dump)
}
}
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
// StatusCode is the HTTP status code returned from the remote server.
StatusCode int
}
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, StatusCode: resp.StatusCode}
}
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
}
|