-
Notifications
You must be signed in to change notification settings - Fork 4
/
requests.go
184 lines (161 loc) · 3.9 KB
/
requests.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
package requests
import (
"encoding/json"
"io"
"net/http"
"net/http/cookiejar"
"strings"
"github.com/pkg/errors"
)
// Client is a HTTP Client.
type Client struct {
client *http.Client
}
// Header is a HTTP header.
type Header struct {
Key string
Values []string
}
// Get issues a GET to the specified URL.
func (c *Client) Get(url string, options ...func(*Request) error) (*Response, error) {
req := Request{
Method: "GET",
URL: url,
}
if err := applyOptions(&req, options...); err != nil {
return nil, err
}
return c.do(&req)
}
// Post issues a POST request to the specified URL.
func (c *Client) Post(url string, body io.Reader, options ...func(*Request) error) (*Response, error) {
req := Request{
Method: "POST",
URL: url,
Body: body,
}
if err := applyOptions(&req, options...); err != nil {
return nil, err
}
return c.do(&req)
}
// WithHeader applies the header to the request.
func WithHeader(key, value string) func(*Request) error {
return func(r *Request) error {
r.Headers = append(r.Headers, Header{
Key: key,
Values: []string{value},
})
return nil
}
}
func (c *Client) do(request *Request) (*Response, error) {
req, err := newHttpRequest(request)
if err != nil {
return nil, err
}
if c.client == nil {
c.client = &*http.DefaultClient
jar, _ := cookiejar.New(new(cookiejar.Options))
c.client.Jar = jar
c.client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
resp, err := c.client.Do(req)
if err != nil {
return nil, errors.WithStack(err)
}
r := Response{
Request: request,
Status: Status{
Code: resp.StatusCode,
Reason: resp.Status[4:],
},
Headers: headers(resp.Header),
Body: Body{
ReadCloser: resp.Body,
},
}
return &r, nil
}
func applyOptions(req *Request, options ...func(*Request) error) error {
for _, opt := range options {
if err := opt(req); err != nil {
return err
}
}
return nil
}
// Request is a HTTP request.
type Request struct {
Method string
URL string
Headers []Header
Body io.Reader
}
// newHttpRequest converts a *requests.Request into a *http.Request
func newHttpRequest(request *Request) (*http.Request, error) {
req, err := http.NewRequest(request.Method, request.URL, request.Body)
if err != nil {
return nil, errors.WithStack(err)
}
req.Header = toHeaders(request.Headers)
return req, nil
}
// toHeaders convers from Request's Headers slice to http.Request's map[string][]string
func toHeaders(headers []Header) map[string][]string {
if len(headers) == 0 {
return nil
}
m := make(map[string][]string)
for _, h := range headers {
m[h.Key] = h.Values
}
return m
}
// Response is a HTTP response.
type Response struct {
*Request
Status
Headers []Header
Body
}
// Header returns the canonicalised version of a response header as a string
// If there is no key present in the response the empty string is returned.
// If multiple headers are present, they are canonicalised into as single string
// by joining them with a comma. See RFC 2616 § 4.2.
func (r *Response) Header(key string) string {
var vals []string
for _, h := range r.Headers {
// TODO(dfc) § 4.2 states that not all header values can be combined, but equally those
// that cannot be combined with a comma may not be present more than once in a
// header block.
if h.Key == key {
vals = append(vals, h.Values...)
}
}
return strings.Join(vals, ",")
}
type Body struct {
io.ReadCloser
json *json.Decoder
}
// JSON decodes the next JSON encoded object in the body to v.
func (b *Body) JSON(v interface{}) error {
if b.json == nil {
b.json = json.NewDecoder(b)
}
return b.json.Decode(v)
}
// return the body as a string, or bytes, or something
func headers(h map[string][]string) []Header {
headers := make([]Header, 0, len(h))
for k, v := range h {
headers = append(headers, Header{
Key: k,
Values: v,
})
}
return headers
}