blob: a1c19158b5d2ee0bfb0a821f7801511d9f894fc6 [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// 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 "io/ioutil"
22 "log"
23 "mime/multipart"
24 "net/http"
25 "net/url"
26 "os"
27 "path"
28 "path/filepath"
29 "strings"
30 "time"
31
32 "github.com/go-openapi/runtime"
33 "github.com/go-openapi/strfmt"
34)
35
36// NewRequest creates a new swagger http client request
37func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) (*request, error) {
38 return &request{
39 pathPattern: pathPattern,
40 method: method,
41 writer: writer,
42 header: make(http.Header),
43 query: make(url.Values),
44 timeout: DefaultTimeout,
45 }, nil
46}
47
48// Request represents a swagger client request.
49//
50// This Request struct converts to a HTTP request.
51// There might be others that convert to other transports.
52// There is no error checking here, it is assumed to be used after a spec has been validated.
53// so impossible combinations should not arise (hopefully).
54//
55// The main purpose of this struct is to hide the machinery of adding params to a transport request.
56// The generated code only implements what is necessary to turn a param into a valid value for these methods.
57type request struct {
58 pathPattern string
59 method string
60 writer runtime.ClientRequestWriter
61
62 pathParams map[string]string
63 header http.Header
64 query url.Values
65 formFields url.Values
66 fileFields map[string][]runtime.NamedReadCloser
67 payload interface{}
68 timeout time.Duration
69 buf *bytes.Buffer
70}
71
72var (
73 // ensure interface compliance
74 _ runtime.ClientRequest = new(request)
75)
76
77func (r *request) isMultipart(mediaType string) bool {
78 if len(r.fileFields) > 0 {
79 return true
80 }
81
82 return runtime.MultipartFormMime == mediaType
83}
84
85// BuildHTTP creates a new http request based on the data from the params
86func (r *request) BuildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) {
87 return r.buildHTTP(mediaType, basePath, producers, registry, nil)
88}
89
90func (r *request) buildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry, auth runtime.ClientAuthInfoWriter) (*http.Request, error) {
91 // build the data
92 if err := r.writer.WriteToRequest(r, registry); err != nil {
93 return nil, err
94 }
95
96 if auth != nil {
97 if err := auth.AuthenticateRequest(r, registry); err != nil {
98 return nil, err
99 }
100 }
101
102 // create http request
103 var reinstateSlash bool
104 if r.pathPattern != "" && r.pathPattern != "/" && r.pathPattern[len(r.pathPattern)-1] == '/' {
105 reinstateSlash = true
106 }
107 urlPath := path.Join(basePath, r.pathPattern)
108 for k, v := range r.pathParams {
109 urlPath = strings.Replace(urlPath, "{"+k+"}", url.PathEscape(v), -1)
110 }
111 if reinstateSlash {
112 urlPath = urlPath + "/"
113 }
114
115 var body io.ReadCloser
116 var pr *io.PipeReader
117 var pw *io.PipeWriter
118
119 r.buf = bytes.NewBuffer(nil)
120 if r.payload != nil || len(r.formFields) > 0 || len(r.fileFields) > 0 {
121 body = ioutil.NopCloser(r.buf)
122 if r.isMultipart(mediaType) {
123 pr, pw = io.Pipe()
124 body = pr
125 }
126 }
127 req, err := http.NewRequest(r.method, urlPath, body)
128
129 if err != nil {
130 return nil, err
131 }
132
133 req.URL.RawQuery = r.query.Encode()
134 req.Header = r.header
135
136 // check if this is a form type request
137 if len(r.formFields) > 0 || len(r.fileFields) > 0 {
138 if !r.isMultipart(mediaType) {
139 req.Header.Set(runtime.HeaderContentType, mediaType)
140 formString := r.formFields.Encode()
141 // set content length before writing to the buffer
142 req.ContentLength = int64(len(formString))
143 // write the form values as the body
144 r.buf.WriteString(formString)
145 return req, nil
146 }
147
148 mp := multipart.NewWriter(pw)
149 req.Header.Set(runtime.HeaderContentType, mangleContentType(mediaType, mp.Boundary()))
150
151 go func() {
152 defer func() {
153 mp.Close()
154 pw.Close()
155 }()
156
157 for fn, v := range r.formFields {
158 for _, vi := range v {
159 if err := mp.WriteField(fn, vi); err != nil {
160 pw.CloseWithError(err)
161 log.Println(err)
162 }
163 }
164 }
165
166 defer func() {
167 for _, ff := range r.fileFields {
168 for _, ffi := range ff {
169 ffi.Close()
170 }
171 }
172 }()
173 for fn, f := range r.fileFields {
174 for _, fi := range f {
175 wrtr, err := mp.CreateFormFile(fn, filepath.Base(fi.Name()))
176 if err != nil {
177 pw.CloseWithError(err)
178 log.Println(err)
179 } else if _, err := io.Copy(wrtr, fi); err != nil {
180 pw.CloseWithError(err)
181 log.Println(err)
182 }
183 }
184 }
185
186 }()
187 return req, nil
188
189 }
190
191 // if there is payload, use the producer to write the payload, and then
192 // set the header to the content-type appropriate for the payload produced
193 if r.payload != nil {
194 // TODO: infer most appropriate content type based on the producer used,
195 // and the `consumers` section of the spec/operation
196 req.Header.Set(runtime.HeaderContentType, mediaType)
197 if rdr, ok := r.payload.(io.ReadCloser); ok {
198 req.Body = rdr
199
200 return req, nil
201 }
202
203 if rdr, ok := r.payload.(io.Reader); ok {
204 req.Body = ioutil.NopCloser(rdr)
205
206 return req, nil
207 }
208
209 req.GetBody = func() (io.ReadCloser, error) {
210 var b bytes.Buffer
211 producer := producers[mediaType]
212 if err := producer.Produce(&b, r.payload); err != nil {
213 return nil, err
214 }
215
216 if _, err := r.buf.Write(b.Bytes()); err != nil {
217 return nil, err
218 }
219 return ioutil.NopCloser(&b), nil
220 }
221
222 // set the content length of the request or else a chunked transfer is
223 // declared, and this corrupts outgoing JSON payloads. the content's
224 // length must be set prior to the body being written per the spec at
225 // https://golang.org/pkg/net/http
226 //
227 // If Body is present, Content-Length is <= 0 and TransferEncoding
228 // hasn't been set to "identity", Write adds
229 // "Transfer-Encoding: chunked" to the header. Body is closed
230 // after it is sent.
231 //
232 // to that end a temporary buffer, b, is created to produce the payload
233 // body, and then its size is used to set the request's content length
234 var b bytes.Buffer
235 producer := producers[mediaType]
236 if err := producer.Produce(&b, r.payload); err != nil {
237 return nil, err
238 }
239 req.ContentLength = int64(b.Len())
240 if _, err := r.buf.Write(b.Bytes()); err != nil {
241 return nil, err
242 }
243 }
244
245 if runtime.CanHaveBody(req.Method) && req.Body == nil && req.Header.Get(runtime.HeaderContentType) == "" {
246 req.Header.Set(runtime.HeaderContentType, mediaType)
247 }
248
249 return req, nil
250}
251
252func mangleContentType(mediaType, boundary string) string {
253 if strings.ToLower(mediaType) == runtime.URLencodedFormMime {
254 return fmt.Sprintf("%s; boundary=%s", mediaType, boundary)
255 }
256 return "multipart/form-data; boundary=" + boundary
257}
258
259func (r *request) GetMethod() string {
260 return r.method
261}
262
263func (r *request) GetPath() string {
264 path := r.pathPattern
265 for k, v := range r.pathParams {
266 path = strings.Replace(path, "{"+k+"}", v, -1)
267 }
268 return path
269}
270
271func (r *request) GetBody() []byte {
272 if r.buf == nil {
273 return nil
274 }
275 return r.buf.Bytes()
276}
277
278// SetHeaderParam adds a header param to the request
279// when there is only 1 value provided for the varargs, it will set it.
280// when there are several values provided for the varargs it will add it (no overriding)
281func (r *request) SetHeaderParam(name string, values ...string) error {
282 if r.header == nil {
283 r.header = make(http.Header)
284 }
285 r.header[http.CanonicalHeaderKey(name)] = values
286 return nil
287}
288
289// SetQueryParam adds a query param to the request
290// when there is only 1 value provided for the varargs, it will set it.
291// when there are several values provided for the varargs it will add it (no overriding)
292func (r *request) SetQueryParam(name string, values ...string) error {
293 if r.query == nil {
294 r.query = make(url.Values)
295 }
296 r.query[name] = values
297 return nil
298}
299
300// GetQueryParams returns a copy of all query params currently set for the request
301func (r *request) GetQueryParams() url.Values {
302 var result = make(url.Values)
303 for key, value := range r.query {
304 result[key] = append([]string{}, value...)
305 }
306 return result
307}
308
309// SetFormParam adds a forn param to the request
310// when there is only 1 value provided for the varargs, it will set it.
311// when there are several values provided for the varargs it will add it (no overriding)
312func (r *request) SetFormParam(name string, values ...string) error {
313 if r.formFields == nil {
314 r.formFields = make(url.Values)
315 }
316 r.formFields[name] = values
317 return nil
318}
319
320// SetPathParam adds a path param to the request
321func (r *request) SetPathParam(name string, value string) error {
322 if r.pathParams == nil {
323 r.pathParams = make(map[string]string)
324 }
325
326 r.pathParams[name] = value
327 return nil
328}
329
330// SetFileParam adds a file param to the request
331func (r *request) SetFileParam(name string, files ...runtime.NamedReadCloser) error {
332 for _, file := range files {
333 if actualFile, ok := file.(*os.File); ok {
334 fi, err := os.Stat(actualFile.Name())
335 if err != nil {
336 return err
337 }
338 if fi.IsDir() {
339 return fmt.Errorf("%q is a directory, only files are supported", file.Name())
340 }
341 }
342 }
343
344 if r.fileFields == nil {
345 r.fileFields = make(map[string][]runtime.NamedReadCloser)
346 }
347 if r.formFields == nil {
348 r.formFields = make(url.Values)
349 }
350
351 r.fileFields[name] = files
352 return nil
353}
354
355func (r *request) GetFileParam() map[string][]runtime.NamedReadCloser {
356 return r.fileFields
357}
358
359// SetBodyParam sets a body parameter on the request.
360// This does not yet serialze the object, this happens as late as possible.
361func (r *request) SetBodyParam(payload interface{}) error {
362 r.payload = payload
363 return nil
364}
365
366func (r *request) GetBodyParam() interface{} {
367 return r.payload
368}
369
370// SetTimeout sets the timeout for a request
371func (r *request) SetTimeout(timeout time.Duration) error {
372 r.timeout = timeout
373 return nil
374}