-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathgohttp.go
271 lines (230 loc) · 6.64 KB
/
gohttp.go
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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package gohttp
import (
"bytes"
"encoding/json"
"io"
"net/http"
"path/filepath"
"strings"
"github.com/google/go-querystring/query"
)
const (
contentType = "Content-Type"
jsonContentType = "application/json"
formContentType = "application/x-www-form-urlencoded"
)
// Client is the main struct that wraps net/http
type Client struct {
c *http.Client
// request parameters
query map[string]string
queryStructs []interface{}
headers map[string]string
url string
path string
body io.Reader
}
// DefaultClient provides a simple usable client, it is given for quick usage.
// For more control, please create a client manually.
var DefaultClient = New()
// New returns a new GoClient with default values
func New() *Client {
return &Client{
c: &http.Client{},
query: make(map[string]string),
queryStructs: make([]interface{}, 5),
headers: make(map[string]string),
}
}
func (c *Client) prepareRequest(method string) (*http.Request, error) {
req, err := http.NewRequest(method, c.url, c.body)
// concatenate path to url if exists
if c.path != "" {
p := req.URL.Path
req.URL.Path = filepath.Join(p, c.path)
}
// setup the query string, there can be many query strings,
// and they're connected with `&` symbol. Also rawquery and
// queryStructs data will be merged.
if len(c.query) != 0 {
q := req.URL.Query()
for key, value := range c.query {
q.Add(key, value)
}
req.URL.RawQuery = q.Encode()
}
if len(c.queryStructs) != 0 {
q := req.URL.Query()
for _, queryStruct := range c.queryStructs {
qValues, err := query.Values(queryStruct)
if err != nil {
return req, err
}
for key, values := range qValues {
for _, value := range values {
q.Add(key, value)
}
}
}
req.URL.RawQuery = q.Encode()
}
// setup headers
if len(c.headers) != 0 {
for key, value := range c.headers {
req.Header.Add(key, value)
}
}
if err != nil {
return nil, err
}
return req, nil
}
// Do takes HTTP and url, then makes the request, return the response
func (c *Client) Do(method, url string) (*http.Response, error) {
c.url = url
req, err := c.prepareRequest(method)
if err != nil {
return nil, err
}
return c.c.Do(req)
}
// Get handles HTTP GET request, and return response to user
func (c *Client) Get(url string) (*http.Response, error) {
return c.Do("GET", url)
}
// Post handles HTTP POST request
func (c *Client) Post(url string) (*http.Response, error) {
return c.Do("POST", url)
}
// Head handles HTTP HEAD request
func (c *Client) Head(url string) (*http.Response, error) {
return c.Do("HEAD", url)
}
// Put handles HTTP PUT request
func (c *Client) Put(url string) (*http.Response, error) {
return c.Do("PUT", url)
}
// Delete handles HTTP DELETE request
func (c *Client) Delete(url string) (*http.Response, error) {
return c.Do("DELETE", url)
}
// Patch handles HTTP PATCH request
func (c *Client) Patch(url string) (*http.Response, error) {
return c.Do("PATCH", url)
}
// Options handles HTTP OPTIONS request
func (c *Client) Options(url string) (*http.Response, error) {
return c.Do("OPTIONS", url)
}
// Path concatenates base url with resource path.
// Path can be with or without slash `/` at both end,
// it will be handled properly.
func (c *Client) Path(paths ...string) *Client {
for _, path := range paths {
if path != "" {
c.path = filepath.Join(c.path, path)
}
}
return c
}
// Query set parameter query string
func (c *Client) Query(key, value string) *Client {
c.query[key] = value
return c
}
// QueryStruct parses a struct as query strings
// On how it works, please refer to github.com/google/go-querystring repo
func (c *Client) QueryStruct(queryStruct interface{}) *Client {
c.queryStructs = append(c.queryStructs, queryStruct)
return c
}
// Header sets request header data
func (c *Client) Header(key, value string) *Client {
c.headers[key] = value
return c
}
type jsonBodyData struct {
payload interface{}
}
func (jbd jsonBodyData) Body() (io.Reader, error) {
buf := &bytes.Buffer{}
err := json.NewEncoder(buf).Encode(jbd.payload)
if err != nil {
return nil, err
}
return buf, nil
}
// JSON accepts a struct as data, and sets it as body, and send it as application/json
// If the actual method does not support body or json data, such as `GET`, `HEAD`,
// it will be simply omitted.
func (c *Client) JSON(bodyJSON interface{}) *Client {
if bodyJSON != nil {
c.Header(contentType, jsonContentType)
//TODO: how to handle error
body, _ := jsonBodyData{payload: bodyJSON}.Body()
c.body = body
}
return c
}
type formBodyData struct {
payload interface{}
}
// Body returns io.Reader from form data.
// Form data is a collection of many key-value pairs,
// so we use go-querystring to parse it to string, then
// create a io.Reader interface.
func (fbd formBodyData) Body() (io.Reader, error) {
values, err := query.Values(fbd.payload)
if err != nil {
return nil, err
}
return strings.NewReader(values.Encode()), nil
}
// Form accepts a struct, uses it as body data, and sent it as application/www-x-form-urlencoded
// If the actual method does not support body or form data, such as `GET`, `HEAD`,
// it will be simply omitted.
func (c *Client) Form(bodyForm interface{}) *Client {
if bodyForm != nil {
c.Header(contentType, formContentType)
body, _ := formBodyData{payload: bodyForm}.Body()
c.body = body
}
return c
}
// Body accepts `io.Reader`, will read data from it and use it as request body.
// This doee not set `Content-Type` header, so users should use `Header(key, value)`
// to specify it if necessary.
func (c *Client) Body(body io.Reader) *Client {
if body != nil {
c.body = body
}
return c
}
// Get provides a shortcut to send `GET` request
func Get(url string) (*http.Response, error) {
return DefaultClient.Get(url)
}
// Head provides a shortcut to send `HEAD` request
func Head(url string) (*http.Response, error) {
return DefaultClient.Head(url)
}
// Delete provides a shortcut to send `DELETE` request
func Delete(url string) (*http.Response, error) {
return DefaultClient.Delete(url)
}
// Options provides a shortcut to send `OPTIONS` request
func Options(url string) (*http.Response, error) {
return DefaultClient.Options(url)
}
// Post provides a shortcut to send `POST` request
func Post(url string, data io.Reader) (*http.Response, error) {
return DefaultClient.Body(data).Post(url)
}
// Put provides a shortcut to send `PUT` request
func Put(url string, data io.Reader) (*http.Response, error) {
return DefaultClient.Body(data).Put(url)
}
// Patch provides a shortcut to send `PATCH` request
func Patch(url string, data io.Reader) (*http.Response, error) {
return DefaultClient.Body(data).Patch(url)
}