1// Copyright 2015 go-swagger maintainers 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package client 16 17import ( 18 "bytes" 19 "fmt" 20 "io" 21 "log" 22 "mime/multipart" 23 "net/http" 24 "net/textproto" 25 "net/url" 26 "os" 27 "path" 28 "path/filepath" 29 "strings" 30 "time" 31 32 "github.com/go-openapi/strfmt" 33 34 "github.com/go-openapi/runtime" 35) 36 37// NewRequest creates a new swagger http client request 38func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) (*request, error) { 39 return &request{ 40 pathPattern: pathPattern, 41 method: method, 42 writer: writer, 43 header: make(http.Header), 44 query: make(url.Values), 45 timeout: DefaultTimeout, 46 getBody: getRequestBuffer, 47 }, nil 48} 49 50// Request represents a swagger client request. 51// 52// This Request struct converts to a HTTP request. 53// There might be others that convert to other transports. 54// There is no error checking here, it is assumed to be used after a spec has been validated. 55// so impossible combinations should not arise (hopefully). 56// 57// The main purpose of this struct is to hide the machinery of adding params to a transport request. 58// The generated code only implements what is necessary to turn a param into a valid value for these methods. 59type request struct { 60 pathPattern string 61 method string 62 writer runtime.ClientRequestWriter 63 64 pathParams map[string]string 65 header http.Header 66 query url.Values 67 formFields url.Values 68 fileFields map[string][]runtime.NamedReadCloser 69 payload interface{} 70 timeout time.Duration 71 buf *bytes.Buffer 72 73 getBody func(r *request) []byte 74} 75 76var ( 77 // ensure interface compliance 78 _ runtime.ClientRequest = new(request) 79) 80 81func (r *request) isMultipart(mediaType string) bool { 82 if len(r.fileFields) > 0 { 83 return true 84 } 85 86 return runtime.MultipartFormMime == mediaType 87} 88 89// BuildHTTP creates a new http request based on the data from the params 90func (r *request) BuildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) { 91 return r.buildHTTP(mediaType, basePath, producers, registry, nil) 92} 93func escapeQuotes(s string) string { 94 return strings.NewReplacer("\\", "\\\\", `"`, "\\\"").Replace(s) 95} 96func (r *request) buildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry, auth runtime.ClientAuthInfoWriter) (*http.Request, error) { 97 // build the data 98 if err := r.writer.WriteToRequest(r, registry); err != nil { 99 return nil, err 100 } 101 102 // Our body must be an io.Reader. 103 // When we create the http.Request, if we pass it a 104 // bytes.Buffer then it will wrap it in an io.ReadCloser 105 // and set the content length automatically. 106 var body io.Reader 107 var pr *io.PipeReader 108 var pw *io.PipeWriter 109 110 r.buf = bytes.NewBuffer(nil) 111 if r.payload != nil || len(r.formFields) > 0 || len(r.fileFields) > 0 { 112 body = r.buf 113 if r.isMultipart(mediaType) { 114 pr, pw = io.Pipe() 115 body = pr 116 } 117 } 118 119 // check if this is a form type request 120 if len(r.formFields) > 0 || len(r.fileFields) > 0 { 121 if !r.isMultipart(mediaType) { 122 r.header.Set(runtime.HeaderContentType, mediaType) 123 formString := r.formFields.Encode() 124 r.buf.WriteString(formString) 125 goto DoneChoosingBodySource 126 } 127 128 mp := multipart.NewWriter(pw) 129 r.header.Set(runtime.HeaderContentType, mangleContentType(mediaType, mp.Boundary())) 130 131 go func() { 132 defer func() { 133 mp.Close() 134 pw.Close() 135 }() 136 137 for fn, v := range r.formFields { 138 for _, vi := range v { 139 if err := mp.WriteField(fn, vi); err != nil { 140 pw.CloseWithError(err) 141 log.Println(err) 142 } 143 } 144 } 145 146 defer func() { 147 for _, ff := range r.fileFields { 148 for _, ffi := range ff { 149 ffi.Close() 150 } 151 } 152 }() 153 for fn, f := range r.fileFields { 154 for _, fi := range f { 155 buf := bytes.NewBuffer([]byte{}) 156 157 // Need to read the data so that we can detect the content type 158 _, err := io.Copy(buf, fi) 159 if err != nil { 160 _ = pw.CloseWithError(err) 161 log.Println(err) 162 } 163 fileBytes := buf.Bytes() 164 fileContentType := http.DetectContentType(fileBytes) 165 166 newFi := runtime.NamedReader(fi.Name(), buf) 167 168 // Create the MIME headers for the new part 169 h := make(textproto.MIMEHeader) 170 h.Set("Content-Disposition", 171 fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 172 escapeQuotes(fn), escapeQuotes(filepath.Base(fi.Name())))) 173 h.Set("Content-Type", fileContentType) 174 175 wrtr, err := mp.CreatePart(h) 176 if err != nil { 177 pw.CloseWithError(err) 178 log.Println(err) 179 } else if _, err := io.Copy(wrtr, newFi); err != nil { 180 pw.CloseWithError(err) 181 log.Println(err) 182 } 183 } 184 } 185 }() 186 187 goto DoneChoosingBodySource 188 } 189 190 // if there is payload, use the producer to write the payload, and then 191 // set the header to the content-type appropriate for the payload produced 192 if r.payload != nil { 193 // TODO: infer most appropriate content type based on the producer used, 194 // and the `consumers` section of the spec/operation 195 r.header.Set(runtime.HeaderContentType, mediaType) 196 if rdr, ok := r.payload.(io.ReadCloser); ok { 197 body = rdr 198 goto DoneChoosingBodySource 199 } 200 201 if rdr, ok := r.payload.(io.Reader); ok { 202 body = rdr 203 goto DoneChoosingBodySource 204 } 205 206 producer := producers[mediaType] 207 if err := producer.Produce(r.buf, r.payload); err != nil { 208 return nil, err 209 } 210 } 211 212DoneChoosingBodySource: 213 214 if runtime.CanHaveBody(r.method) && body == nil && r.header.Get(runtime.HeaderContentType) == "" { 215 r.header.Set(runtime.HeaderContentType, mediaType) 216 } 217 218 if auth != nil { 219 // If we're not using r.buf as our http.Request's body, 220 // either the payload is an io.Reader or io.ReadCloser, 221 // or we're doing a multipart form/file. 222 // 223 // In those cases, if the AuthenticateRequest call asks for the body, 224 // we must read it into a buffer and provide that, then use that buffer 225 // as the body of our http.Request. 226 // 227 // This is done in-line with the GetBody() request rather than ahead 228 // of time, because there's no way to know if the AuthenticateRequest 229 // will even ask for the body of the request. 230 // 231 // If for some reason the copy fails, there's no way to return that 232 // error to the GetBody() call, so return it afterwards. 233 // 234 // An error from the copy action is prioritized over any error 235 // from the AuthenticateRequest call, because the mis-read 236 // body may have interfered with the auth. 237 // 238 var copyErr error 239 if buf, ok := body.(*bytes.Buffer); body != nil && (!ok || buf != r.buf) { 240 var copied bool 241 r.getBody = func(r *request) []byte { 242 if copied { 243 return getRequestBuffer(r) 244 } 245 246 defer func() { 247 copied = true 248 }() 249 250 if _, copyErr = io.Copy(r.buf, body); copyErr != nil { 251 return nil 252 } 253 254 if closer, ok := body.(io.ReadCloser); ok { 255 if copyErr = closer.Close(); copyErr != nil { 256 return nil 257 } 258 } 259 260 body = r.buf 261 return getRequestBuffer(r) 262 } 263 } 264 265 authErr := auth.AuthenticateRequest(r, registry) 266 267 if copyErr != nil { 268 return nil, fmt.Errorf("error retrieving the response body: %v", copyErr) 269 } 270 271 if authErr != nil { 272 return nil, authErr 273 } 274 } 275 276 // create http request 277 var reinstateSlash bool 278 if r.pathPattern != "" && r.pathPattern != "/" && r.pathPattern[len(r.pathPattern)-1] == '/' { 279 reinstateSlash = true 280 } 281 urlPath := path.Join(basePath, r.pathPattern) 282 for k, v := range r.pathParams { 283 urlPath = strings.Replace(urlPath, "{"+k+"}", url.PathEscape(v), -1) 284 } 285 if reinstateSlash { 286 urlPath = urlPath + "/" 287 } 288 289 req, err := http.NewRequest(r.method, urlPath, body) 290 if err != nil { 291 return nil, err 292 } 293 294 req.URL.RawQuery = r.query.Encode() 295 req.Header = r.header 296 297 return req, nil 298} 299 300func mangleContentType(mediaType, boundary string) string { 301 if strings.ToLower(mediaType) == runtime.URLencodedFormMime { 302 return fmt.Sprintf("%s; boundary=%s", mediaType, boundary) 303 } 304 return "multipart/form-data; boundary=" + boundary 305} 306 307func (r *request) GetMethod() string { 308 return r.method 309} 310 311func (r *request) GetPath() string { 312 path := r.pathPattern 313 for k, v := range r.pathParams { 314 path = strings.Replace(path, "{"+k+"}", v, -1) 315 } 316 return path 317} 318 319func (r *request) GetBody() []byte { 320 return r.getBody(r) 321} 322 323func getRequestBuffer(r *request) []byte { 324 if r.buf == nil { 325 return nil 326 } 327 return r.buf.Bytes() 328} 329 330// SetHeaderParam adds a header param to the request 331// when there is only 1 value provided for the varargs, it will set it. 332// when there are several values provided for the varargs it will add it (no overriding) 333func (r *request) SetHeaderParam(name string, values ...string) error { 334 if r.header == nil { 335 r.header = make(http.Header) 336 } 337 r.header[http.CanonicalHeaderKey(name)] = values 338 return nil 339} 340 341// GetHeaderParams returns the all headers currently set for the request 342func (r *request) GetHeaderParams() http.Header { 343 return r.header 344} 345 346// SetQueryParam adds a query param to the request 347// when there is only 1 value provided for the varargs, it will set it. 348// when there are several values provided for the varargs it will add it (no overriding) 349func (r *request) SetQueryParam(name string, values ...string) error { 350 if r.query == nil { 351 r.query = make(url.Values) 352 } 353 r.query[name] = values 354 return nil 355} 356 357// GetQueryParams returns a copy of all query params currently set for the request 358func (r *request) GetQueryParams() url.Values { 359 var result = make(url.Values) 360 for key, value := range r.query { 361 result[key] = append([]string{}, value...) 362 } 363 return result 364} 365 366// SetFormParam adds a forn param to the request 367// when there is only 1 value provided for the varargs, it will set it. 368// when there are several values provided for the varargs it will add it (no overriding) 369func (r *request) SetFormParam(name string, values ...string) error { 370 if r.formFields == nil { 371 r.formFields = make(url.Values) 372 } 373 r.formFields[name] = values 374 return nil 375} 376 377// SetPathParam adds a path param to the request 378func (r *request) SetPathParam(name string, value string) error { 379 if r.pathParams == nil { 380 r.pathParams = make(map[string]string) 381 } 382 383 r.pathParams[name] = value 384 return nil 385} 386 387// SetFileParam adds a file param to the request 388func (r *request) SetFileParam(name string, files ...runtime.NamedReadCloser) error { 389 for _, file := range files { 390 if actualFile, ok := file.(*os.File); ok { 391 fi, err := os.Stat(actualFile.Name()) 392 if err != nil { 393 return err 394 } 395 if fi.IsDir() { 396 return fmt.Errorf("%q is a directory, only files are supported", file.Name()) 397 } 398 } 399 } 400 401 if r.fileFields == nil { 402 r.fileFields = make(map[string][]runtime.NamedReadCloser) 403 } 404 if r.formFields == nil { 405 r.formFields = make(url.Values) 406 } 407 408 r.fileFields[name] = files 409 return nil 410} 411 412func (r *request) GetFileParam() map[string][]runtime.NamedReadCloser { 413 return r.fileFields 414} 415 416// SetBodyParam sets a body parameter on the request. 417// This does not yet serialze the object, this happens as late as possible. 418func (r *request) SetBodyParam(payload interface{}) error { 419 r.payload = payload 420 return nil 421} 422 423func (r *request) GetBodyParam() interface{} { 424 return r.payload 425} 426 427// SetTimeout sets the timeout for a request 428func (r *request) SetTimeout(timeout time.Duration) error { 429 r.timeout = timeout 430 return nil 431} 432