-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathrequest.go
More file actions
207 lines (171 loc) · 5.45 KB
/
request.go
File metadata and controls
207 lines (171 loc) · 5.45 KB
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Package request provides the HTTP request functionality for the PHPIPAM API.
package request
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/paybyphone/phpipam-sdk-go/phpipam/session"
)
// APIResponse represents a PHPIPAM response body. Both successful and
// unsuccessful requests share the same response format.
type APIResponse struct {
// The HTTP result code.
Code int
// The response data. This is further unmarshaled into the data type set by
// Request.Output.
Data json.RawMessage
// The error message, if the request failed.
Message string
// Whether or not the API request was successful.
Success bool
}
// Request represents the API request.
type Request struct {
// The API session.
Session *session.Session
// The request method.
Method string
// The request URI.
URI string
// The request data.
Input interface{}
// The output of the request. This corresponds to the "data" field in a
// response.
Output interface{}
}
// requestResponse is an unexported struct that encompasses status codes
// and request body in a fashion that can be read after the request
// is closed.
type requestResponse struct {
// Status code.
StatusCode int
// Status code with short-form message.
Status string
// Response body.
Body []byte
}
// BodyString converts requestResponse.Body to string.
func (r *requestResponse) BodyString() string {
buf := bytes.NewBuffer(r.Body)
return buf.String()
}
// readResponseJSON reads a "successful" response body as JSON into variable
// pointed to by v.
//
// First the main HTTP response is unmarshalled. If the request at that point
// failed according to the success field, the request is handed off to
// handleError and the resulting error message is returned. Otherwise, the
// request is successful and the response data is unmarshalled.
func (r *requestResponse) ReadResponseJSON(v interface{}) error {
var resp APIResponse
if err := json.Unmarshal(r.Body, &resp); err != nil {
return fmt.Errorf("JSON parsing error: %s - Response body: %s", err, r.Body)
}
if !resp.Success {
return r.handleError()
}
if string(resp.Data) != "" {
if err := json.Unmarshal(resp.Data, v); err != nil {
return fmt.Errorf("JSON parsing error: %s - Response data: %s", err, string(resp.Data))
}
}
return nil
}
// handleError handles a PHPIPAM API error response.
func (r *requestResponse) handleError() error {
var resp APIResponse
if err := json.Unmarshal(r.Body, &resp); err != nil {
// more than likely not JSON, just pull together the body and return it as
// the error message
return fmt.Errorf("Non-API error (%s): %s", r.Status, r.BodyString())
}
// Return a properly formatted error from the appropraite fields.
return fmt.Errorf("Error from API (%d): %s", resp.Code, resp.Message)
}
// newRequestResponse creates a new requestResponse instance off a HTTP
// response. Warning: This also closes the Body.
func newRequestResponse(r *http.Response) *requestResponse {
rr := &requestResponse{
StatusCode: r.StatusCode,
Status: r.Status,
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
rr.Body = body
return rr
}
// Send sends a request to the API endpoint, and parsees the response.
//
// Note that by design, Send does not handle redirects - if you get a 302 error
// or some other sort of 300 error from the SDK, please check your API
// endpoints.
func (r *Request) Send() error {
var req *http.Request
var err error
r.Session.RLock()
client := r.Session.HttpClient
r.Session.RUnlock()
if client == nil {
client = new(http.Client)
}
if client.CheckRedirect == nil {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
// Write the client back to the session object and re-use it next time
r.Session.Lock()
r.Session.HttpClient = client
r.Session.Unlock()
switch r.Method {
case "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE":
bs, err := json.Marshal(r.Input)
if err != nil {
return fmt.Errorf("Error preparing request data: %s", err)
}
buf := bytes.NewBuffer(bs)
req, err = http.NewRequest(r.Method, fmt.Sprintf("%s/%s%s", r.Session.Config.Endpoint, r.Session.Config.AppID, r.URI), buf)
req.Header.Add("Content-Type", "application/json")
default:
return fmt.Errorf("API request method %s not supported by PHPIPAM", r.Method)
}
if err != nil {
panic(err)
}
// Add session token if it exists, otherwise append username/password from the config.
// Note that according to the PHPIPAM docs, Basic Auth does not work on
// anything else other than the user controller. Falling back to basic auth
// should only be used for setting up the session only.
if r.Session.Token.String != "" {
req.Header.Add("phpipam-token", r.Session.Token.String)
} else {
req.SetBasicAuth(r.Session.Config.Username, r.Session.Config.Password)
}
re, err := client.Do(req)
if err != nil {
return fmt.Errorf("HTTP protocol error: %s", err)
}
resp := newRequestResponse(re)
// A response code of 300 or higher is an error. We do not handle redirects.
if resp.StatusCode >= 300 {
return resp.handleError()
}
// Unmarshal response into Output. The service is responsible for
// this being functional past JSON parsing.
if err := resp.ReadResponseJSON(r.Output); err != nil {
return err
}
return nil
}
// NewRequest creates a new request instance with configuration set.
func NewRequest(s *session.Session) *Request {
r := &Request{
Session: s,
}
return r
}