-
Notifications
You must be signed in to change notification settings - Fork 8
/
request_config.go
208 lines (188 loc) · 5.77 KB
/
request_config.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
package forest
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// RequestConfig holds additional information to construct a Http request.
type RequestConfig struct {
URI string
BodyReader io.Reader
HeaderMap http.Header
// for Query parameters
Values url.Values
FormData url.Values
User, Password string
Cookies []*http.Cookie
// for logging
logRequestLine bool
}
// Path is an alias for NewConfig
var Path = NewConfig
// NewRequestConfig is an alias for NewConfig
var NewRequestConfig = NewConfig
// NewConfig returns a new RequestConfig with initialized empty headers and query parameters.
// See Path for an explanation of the function parameters.
func NewConfig(pathTemplate string, pathParams ...interface{}) *RequestConfig {
cfg := &RequestConfig{
HeaderMap: http.Header{},
Values: url.Values{},
logRequestLine: true,
}
cfg.Path(pathTemplate, pathParams...)
return cfg
}
// Do calls the one-argument function parameter with the receiver.
// This allows for custom convenience functions without breaking the fluent programming style.
func (r *RequestConfig) Do(block func(config *RequestConfig)) *RequestConfig {
block(r)
return r
}
// Path sets the URL path with optional path parameters.
// format example: /v1/persons/{param}/ + 42 => /v1/persons/42
// format example: /v1/persons/:param/ + 42 => /v1/persons/42
// format example: /v1/assets/*rest + js/some/file.js => /v1/assets/js/some/file.js
func (r *RequestConfig) Path(pathTemplate string, pathParams ...interface{}) *RequestConfig {
var uri bytes.Buffer
p := 0
tokens := strings.Split(pathTemplate, "/")
for i, each := range tokens {
if len(each) == 0 && i == 0 { // skip leading space
continue
}
uri.WriteString("/")
if strings.HasPrefix(each, "*") {
// treat remainder as is
uri.WriteString(fmt.Sprintf("%v", pathParams[p]))
break
}
if strings.HasPrefix(each, ":") ||
(strings.HasPrefix(each, "{") && strings.HasSuffix(each, "}")) {
if p == len(pathParams) {
// abort
r.URI = pathTemplate
return r
}
uri.WriteString(fmt.Sprintf("%v", pathParams[p]))
p++
} else {
uri.WriteString(each)
}
}
// need to do path encoding
r.URI = URLPathEncode(uri.String())
return r
}
// BasicAuth sets the credentials for Basic Authentication (if username is not empty)
func (r *RequestConfig) BasicAuth(username, password string) *RequestConfig {
r.User = username
r.Password = password
return r
}
// Query adds a name=value pair to the list of query parameters.
func (r *RequestConfig) Query(name string, value interface{}) *RequestConfig {
r.Values.Add(name, fmt.Sprintf("%v", value))
return r
}
// Header adds a name=value pair to the list of header parameters.
func (r *RequestConfig) Header(name, value string) *RequestConfig {
r.HeaderMap.Add(name, value)
return r
}
// Cookie adds a Cookie to the list of cookies to include in the request.
func (r *RequestConfig) Cookie(c *http.Cookie) *RequestConfig {
if c == nil {
return r
}
r.Cookies = append(r.Cookies, c)
return r
}
// Body sets the playload as is. No content type is set.
// It sets the BodyReader field of the RequestConfig.
func (r *RequestConfig) Body(body string) *RequestConfig {
r.BodyReader = strings.NewReader(body)
return r
}
func (r *RequestConfig) pathAndQuery() string {
if len(r.Values) == 0 {
return r.URI
}
return fmt.Sprintf("%s?%s", r.URI, r.Values.Encode())
}
// Content encodes (marshals) the payload conform the content type given.
// If the payload is already a string (JSON,XML,plain) then it is used as is.
// Supported Content-Type values for marshalling: application/json, application/xml, text/plain
// Payload can also be a slice of bytes; use application/octet-stream in that case.
// It sets the BodyReader field of the RequestConfig.
func (r *RequestConfig) Content(payload interface{}, contentType string) *RequestConfig {
r.Header("Content-Type", contentType)
if payloadAsIs, ok := payload.(string); ok {
r.BodyReader = strings.NewReader(payloadAsIs)
return r
}
if strings.Index(contentType, "application/json") != -1 {
data, err := json.Marshal(payload)
if err != nil {
r.Body(fmt.Sprintf("json marshal failed:%v", err))
return r
}
r.BodyReader = bytes.NewReader(data)
return r
}
if strings.Index(contentType, "application/xml") != -1 {
data, err := xml.Marshal(payload)
if err != nil {
r.Body(fmt.Sprintf("xml marshal failed:%v", err))
return r
}
r.BodyReader = bytes.NewReader(data)
return r
}
if strings.Index(contentType, "text/plain") != -1 {
content, ok := payload.(string)
if !ok {
r.Body(fmt.Sprintf("content is not a string:%v", payload))
return r
}
r.BodyReader = strings.NewReader(content)
return r
}
bits, ok := payload.([]byte)
if ok {
r.BodyReader = bytes.NewReader(bits)
return r
}
r.Body(fmt.Sprintf("cannot encode payload, unknown content type:%s", contentType))
return r
}
// Read sets the BodyReader for content to send with the request.
func (r *RequestConfig) Read(bodyReader io.Reader) *RequestConfig {
r.BodyReader = bodyReader
return r
}
// Form set the FormData values e.g for POST operation.
func (r *RequestConfig) Form(bodyData url.Values) *RequestConfig {
r.FormData = bodyData
return r
}
// LogRequestLine controls whether each HTTP verb call is logging the request line (method + url)
func (r *RequestConfig) LogRequestLine(b bool) {
r.logRequestLine = b
}
// Build returns a new HTTP Request.
func (r *RequestConfig) Build(method, baseURL string) (*http.Request, error) {
httpReq, err := http.NewRequest(method, baseURL+r.pathAndQuery(), r.BodyReader)
if err != nil {
return nil, err
}
setBasicAuth(r, httpReq)
setCookies(r, httpReq)
copyHeaders(r.HeaderMap, httpReq.Header)
setFormData(r, httpReq)
return httpReq, nil
}