1
// An HTTP Client which sends json and binary requests, handling data marshalling and response processing.
9
gooseerrors "launchpad.net/goose/errors"
16
type GooseHTTPClient struct {
20
type ErrorResponse struct {
21
Message string `json:"message"`
22
Code int `json:"code"`
23
Title string `json:"title"`
26
func (e *ErrorResponse) Error() string {
27
return fmt.Sprintf("Failed: %d %s: %s", e.Code, e.Title, e.Message)
30
type ErrorWrapper struct {
31
Error ErrorResponse `json:"error"`
34
type RequestData struct {
35
ReqHeaders http.Header
43
// JsonRequest JSON encodes and sends the supplied object (if any) to the specified URL.
44
// Optional method arguments are pass using the RequestData object.
45
// Relevant RequestData fields:
46
// ReqHeaders: additional HTTP header values to add to the request.
47
// ExpectedStatus: the allowed HTTP response status values, else an error is returned.
48
// ReqValue: the data object to send.
49
// RespValue: the data object to decode the result into.
50
func (c *GooseHTTPClient) JsonRequest(method, url string, reqData *RequestData) (err error) {
56
if reqData.ReqValue != nil {
57
body, err = json.Marshal(reqData.ReqValue)
59
gooseerrors.AddErrorContext(&err, "failed marshalling the request body")
62
reqBody := strings.NewReader(string(body))
63
req, err = http.NewRequest(method, url, reqBody)
65
req, err = http.NewRequest(method, url, nil)
68
gooseerrors.AddErrorContext(&err, "failed creating the request")
71
req.Header.Add("Content-Type", "application/json")
72
req.Header.Add("Accept", "application/json")
74
respBody, err := c.sendRequest(req, reqData.ReqHeaders, reqData.ExpectedStatus, string(body))
79
if len(respBody) > 0 {
80
if reqData.RespValue != nil {
81
err = json.Unmarshal(respBody, &reqData.RespValue)
83
gooseerrors.AddErrorContext(&err, "failed unmarshaling the response body: %s", respBody)
90
// Sends the supplied byte array (if any) to the specified URL.
91
// Optional method arguments are pass using the RequestData object.
92
// Relevant RequestData fields:
93
// ReqHeaders: additional HTTP header values to add to the request.
94
// ExpectedStatus: the allowed HTTP response status values, else an error is returned.
95
// ReqData: the byte array to send.
96
// RespData: the byte array to decode the result into.
97
func (c *GooseHTTPClient) BinaryRequest(method, url string, reqData *RequestData) (err error) {
100
var req *http.Request
102
if reqData.ReqData != nil {
103
rawReqReader := bytes.NewReader(reqData.ReqData)
104
req, err = http.NewRequest(method, url, rawReqReader)
106
req, err = http.NewRequest(method, url, nil)
109
gooseerrors.AddErrorContext(&err, "failed creating the request")
112
req.Header.Add("Content-Type", "application/octet-stream")
113
req.Header.Add("Accept", "application/octet-stream")
115
respBody, err := c.sendRequest(req, reqData.ReqHeaders, reqData.ExpectedStatus, string(reqData.ReqData))
120
if len(respBody) > 0 {
121
if reqData.RespData != nil {
122
*reqData.RespData = respBody
128
// Sends the specified request and checks that the HTTP response status is as expected.
129
// req: the request to send.
130
// extraHeaders: additional HTTP headers to include with the request.
131
// expectedStatus: a slice of allowed response status codes.
132
// payloadInfo: a string to include with an error message if something goes wrong.
133
func (c *GooseHTTPClient) sendRequest(req *http.Request, extraHeaders http.Header, expectedStatus []int, payloadInfo string) (respBody []byte, err error) {
134
if extraHeaders != nil {
135
for header, values := range extraHeaders {
136
for _, value := range values {
137
req.Header.Add(header, value)
142
rawResp, err := c.Do(req)
144
gooseerrors.AddErrorContext(&err, "failed executing the request")
148
if len(expectedStatus) == 0 {
149
expectedStatus = []int{http.StatusOK}
151
for _, status := range expectedStatus {
152
if rawResp.StatusCode == status {
157
if !foundStatus && len(expectedStatus) > 0 {
158
defer rawResp.Body.Close()
159
var errInfo interface{}
160
errInfo, _ = ioutil.ReadAll(rawResp.Body)
161
// Check if we have a JSON representation of the failure, if so decode it.
162
if rawResp.Header.Get("Content-Type") == "application/json" {
163
var wrappedErr ErrorWrapper
164
if err := json.Unmarshal(errInfo.([]byte), &wrappedErr); err == nil {
165
errInfo = wrappedErr.Error
170
"request (%s) returned unexpected status: %s; error info: %v; request body: %s",
178
respBody, err = ioutil.ReadAll(rawResp.Body)
181
gooseerrors.AddErrorContext(&err, "failed reading the response body")