github.com/blend/go-sdk@v1.20220411.3/webutil/request_option.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package webutil 9 10 import ( 11 "bytes" 12 "context" 13 "encoding/json" 14 "encoding/xml" 15 "fmt" 16 "io" 17 "mime/multipart" 18 "net/http" 19 "net/textproto" 20 "net/url" 21 "strings" 22 ) 23 24 // RequestOption is an option for http.Request. 25 type RequestOption func(*http.Request) error 26 27 // RequestOptions are an array of RequestOption. 28 type RequestOptions []RequestOption 29 30 // Apply applies the options to a request. 31 func (ro RequestOptions) Apply(req *http.Request) (err error) { 32 for _, option := range ro { 33 if err = option(req); err != nil { 34 return 35 } 36 } 37 return 38 } 39 40 // OptMethod sets the request method. 41 func OptMethod(method string) RequestOption { 42 return func(r *http.Request) error { 43 r.Method = method 44 return nil 45 } 46 } 47 48 // OptGet sets the request method. 49 func OptGet() RequestOption { 50 return func(r *http.Request) error { 51 r.Method = "GET" 52 return nil 53 } 54 } 55 56 // OptPost sets the request method. 57 func OptPost() RequestOption { 58 return func(r *http.Request) error { 59 r.Method = "POST" 60 return nil 61 } 62 } 63 64 // OptPut sets the request method. 65 func OptPut() RequestOption { 66 return func(r *http.Request) error { 67 r.Method = "PUT" 68 return nil 69 } 70 } 71 72 // OptPatch sets the request method. 73 func OptPatch() RequestOption { 74 return func(r *http.Request) error { 75 r.Method = "PATCH" 76 return nil 77 } 78 } 79 80 // OptDelete sets the request method. 81 func OptDelete() RequestOption { 82 return func(r *http.Request) error { 83 r.Method = "DELETE" 84 return nil 85 } 86 } 87 88 // OptContext sets the request context. 89 func OptContext(ctx context.Context) RequestOption { 90 return func(r *http.Request) error { 91 *r = *r.WithContext(ctx) 92 return nil 93 } 94 } 95 96 // OptBasicAuth is an option that sets the http basic auth. 97 func OptBasicAuth(username, password string) RequestOption { 98 return func(r *http.Request) error { 99 if r.Header == nil { 100 r.Header = http.Header{} 101 } 102 r.SetBasicAuth(username, password) 103 return nil 104 } 105 } 106 107 // OptQuery sets the full querystring. 108 func OptQuery(query url.Values) RequestOption { 109 return func(r *http.Request) error { 110 if r.URL == nil { 111 r.URL = &url.URL{} 112 } 113 r.URL.RawQuery = query.Encode() 114 return nil 115 } 116 } 117 118 // OptQueryValue sets a query value on a request. 119 func OptQueryValue(key, value string) RequestOption { 120 return func(r *http.Request) error { 121 if r.URL == nil { 122 r.URL = &url.URL{} 123 } 124 existing := r.URL.Query() 125 existing.Set(key, value) 126 r.URL.RawQuery = existing.Encode() 127 return nil 128 } 129 } 130 131 // OptQueryValueAdd adds a query value on a request. 132 func OptQueryValueAdd(key, value string) RequestOption { 133 return func(r *http.Request) error { 134 if r.URL == nil { 135 r.URL = &url.URL{} 136 } 137 existing := r.URL.Query() 138 existing.Add(key, value) 139 r.URL.RawQuery = existing.Encode() 140 return nil 141 } 142 } 143 144 // OptHeader sets the request headers. 145 func OptHeader(headers http.Header) RequestOption { 146 return func(r *http.Request) error { 147 r.Header = headers 148 return nil 149 } 150 } 151 152 // OptHeaderValue sets a header value on a request. 153 func OptHeaderValue(key, value string) RequestOption { 154 return func(r *http.Request) error { 155 if r.Header == nil { 156 r.Header = make(http.Header) 157 } 158 r.Header.Set(key, value) 159 return nil 160 } 161 } 162 163 // OptPostForm sets the request post form and the content type. 164 func OptPostForm(postForm url.Values) RequestOption { 165 return func(r *http.Request) error { 166 if r.Header == nil { 167 r.Header = http.Header{} 168 } 169 r.Header.Set(HeaderContentType, ContentTypeApplicationFormEncoded) 170 r.PostForm = postForm 171 return nil 172 } 173 } 174 175 // OptPostFormValue sets a request post form value. 176 func OptPostFormValue(key, value string) RequestOption { 177 return func(r *http.Request) error { 178 if r.Header == nil { 179 r.Header = http.Header{} 180 } 181 r.Header.Set(HeaderContentType, ContentTypeApplicationFormEncoded) 182 if r.PostForm == nil { 183 r.PostForm = url.Values{} 184 } 185 r.PostForm.Set(key, value) 186 return nil 187 } 188 } 189 190 // OptCookie adds a cookie to a context. 191 func OptCookie(cookie *http.Cookie) RequestOption { 192 return func(r *http.Request) error { 193 if r.Header == nil { 194 r.Header = make(http.Header) 195 } 196 r.AddCookie(cookie) 197 return nil 198 } 199 } 200 201 // OptCookieValue adds a cookie value to a context. 202 func OptCookieValue(key, value string) RequestOption { 203 return OptCookie(&http.Cookie{Name: key, Value: value}) 204 } 205 206 // OptBody sets the post body on the request. 207 func OptBody(contents io.ReadCloser) RequestOption { 208 return func(r *http.Request) error { 209 r.Body = contents 210 return nil 211 } 212 } 213 214 // OptBodyBytes sets a body on a context from bytes. 215 func OptBodyBytes(body []byte) RequestOption { 216 return func(r *http.Request) error { 217 r.ContentLength = int64(len(body)) 218 r.Body = io.NopCloser(bytes.NewReader(body)) 219 r.GetBody = func() (io.ReadCloser, error) { 220 return io.NopCloser(bytes.NewReader(body)), nil 221 } 222 r.ContentLength = int64(len(body)) 223 return nil 224 } 225 } 226 227 // OptPostedFiles sets a body from posted files. 228 func OptPostedFiles(files ...PostedFile) RequestOption { 229 return func(r *http.Request) error { 230 if r.Header == nil { 231 r.Header = make(http.Header) 232 } 233 234 b := new(bytes.Buffer) 235 w := multipart.NewWriter(b) 236 237 if len(r.PostForm) > 0 { 238 for key, values := range r.PostForm { 239 for _, value := range values { 240 if err := w.WriteField(key, value); err != nil { 241 return err 242 } 243 } 244 } 245 } 246 247 for _, file := range files { 248 // custom header since CreateFormFile uses application/octet-stream by default 249 var fw io.Writer 250 var err error 251 if file.ContentType != "" { 252 h := make(textproto.MIMEHeader) 253 h.Set("Content-Disposition", 254 fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 255 escapeQuotes(file.Key), escapeQuotes(file.FileName))) 256 h.Set("Content-Type", file.ContentType) 257 fw, err = w.CreatePart(h) 258 if err != nil { 259 return err 260 } 261 } else { 262 fw, err = w.CreateFormFile(file.Key, file.FileName) 263 if err != nil { 264 return err 265 } 266 } 267 _, err = io.Copy(fw, bytes.NewBuffer(file.Contents)) 268 if err != nil { 269 return err 270 } 271 } 272 r.Header.Set(HeaderContentType, w.FormDataContentType()) 273 if err := w.Close(); err != nil { 274 return err 275 } 276 277 bb := b.Bytes() 278 r.Body = io.NopCloser(bytes.NewReader(bb)) 279 r.GetBody = func() (io.ReadCloser, error) { 280 return io.NopCloser(bytes.NewReader(bb)), nil 281 } 282 r.ContentLength = int64(len(bb)) 283 return nil 284 } 285 } 286 287 // OptJSONBody sets the post body on the request. 288 func OptJSONBody(obj interface{}) RequestOption { 289 return func(r *http.Request) error { 290 contents, err := json.Marshal(obj) 291 if err != nil { 292 return err 293 } 294 r.Body = io.NopCloser(bytes.NewReader(contents)) 295 r.GetBody = func() (io.ReadCloser, error) { 296 r := bytes.NewReader(contents) 297 return io.NopCloser(r), nil 298 } 299 r.ContentLength = int64(len(contents)) 300 if r.Header == nil { 301 r.Header = make(http.Header) 302 } 303 r.Header.Set(HeaderContentType, ContentTypeApplicationJSON) 304 return nil 305 } 306 } 307 308 // OptXMLBody sets the post body on the request. 309 func OptXMLBody(obj interface{}) RequestOption { 310 return func(r *http.Request) error { 311 contents, err := xml.Marshal(obj) 312 if err != nil { 313 return err 314 } 315 r.Body = io.NopCloser(bytes.NewBuffer(contents)) 316 r.GetBody = func() (io.ReadCloser, error) { 317 r := bytes.NewReader(contents) 318 return io.NopCloser(r), nil 319 } 320 r.ContentLength = int64(len(contents)) 321 if r.Header == nil { 322 r.Header = make(http.Header) 323 } 324 r.Header.Set(HeaderContentType, ContentTypeApplicationXML) 325 return nil 326 } 327 } 328 329 // OptHTTPClientTrace sets the http trace on the outgoing request. 330 func OptHTTPClientTrace(ht *HTTPTrace) RequestOption { 331 return func(r *http.Request) error { 332 *r = *WithClientHTTPTrace(r, ht) 333 return nil 334 } 335 } 336 337 var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") 338 339 func escapeQuotes(s string) string { 340 return quoteEscaper.Replace(s) 341 }