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 "io/ioutil" 22 "log" 23 "mime/multipart" 24 "net/http" 25 "net/url" 26 "os" 27 "path/filepath" 28 "strings" 29 "time" 30 31 "github.com/go-openapi/runtime" 32 "github.com/go-openapi/strfmt" 33) 34 35// NewRequest creates a new swagger http client request 36func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) (*request, error) { 37 return &request{ 38 pathPattern: pathPattern, 39 method: method, 40 writer: writer, 41 header: make(http.Header), 42 query: make(url.Values), 43 timeout: DefaultTimeout, 44 }, nil 45} 46 47// Request represents a swagger client request. 48// 49// This Request struct converts to a HTTP request. 50// There might be others that convert to other transports. 51// There is no error checking here, it is assumed to be used after a spec has been validated. 52// so impossible combinations should not arise (hopefully). 53// 54// The main purpose of this struct is to hide the machinery of adding params to a transport request. 55// The generated code only implements what is necessary to turn a param into a valid value for these methods. 56type request struct { 57 pathPattern string 58 method string 59 writer runtime.ClientRequestWriter 60 61 pathParams map[string]string 62 header http.Header 63 query url.Values 64 formFields url.Values 65 fileFields map[string]runtime.NamedReadCloser 66 payload interface{} 67 timeout time.Duration 68} 69 70var ( 71 // ensure interface compliance 72 _ runtime.ClientRequest = new(request) 73) 74 75// BuildHTTP creates a new http request based on the data from the params 76func (r *request) BuildHTTP(mediaType string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) { 77 // build the data 78 if err := r.writer.WriteToRequest(r, registry); err != nil { 79 return nil, err 80 } 81 82 // create http request 83 path := r.pathPattern 84 for k, v := range r.pathParams { 85 path = strings.Replace(path, "{"+k+"}", v, -1) 86 } 87 88 var body io.ReadCloser 89 var pr *io.PipeReader 90 var pw *io.PipeWriter 91 buf := bytes.NewBuffer(nil) 92 body = ioutil.NopCloser(buf) 93 if r.fileFields != nil { 94 pr, pw = io.Pipe() 95 body = pr 96 } 97 req, err := http.NewRequest(r.method, path, body) 98 if err != nil { 99 return nil, err 100 } 101 req.URL.RawQuery = r.query.Encode() 102 req.Header = r.header 103 104 // check if this is a form type request 105 if len(r.formFields) > 0 || len(r.fileFields) > 0 { 106 // check if this is multipart 107 if len(r.fileFields) > 0 { 108 mp := multipart.NewWriter(pw) 109 req.Header.Set(runtime.HeaderContentType, mp.FormDataContentType()) 110 111 go func() { 112 defer func() { 113 mp.Close() 114 pw.Close() 115 }() 116 117 for fn, v := range r.formFields { 118 if len(v) > 0 { 119 if err := mp.WriteField(fn, v[0]); err != nil { 120 pw.CloseWithError(err) 121 log.Println(err) 122 } 123 } 124 } 125 126 for fn, f := range r.fileFields { 127 wrtr, err := mp.CreateFormFile(fn, filepath.Base(f.Name())) 128 if err != nil { 129 pw.CloseWithError(err) 130 log.Println(err) 131 } 132 defer func() { 133 for _, ff := range r.fileFields { 134 ff.Close() 135 } 136 137 }() 138 if _, err := io.Copy(wrtr, f); err != nil { 139 pw.CloseWithError(err) 140 log.Println(err) 141 } 142 } 143 144 }() 145 return req, nil 146 } 147 148 req.Header.Set(runtime.HeaderContentType, mediaType) 149 // write the form values as the body 150 buf.WriteString(r.formFields.Encode()) 151 return req, nil 152 } 153 154 // if there is payload, use the producer to write the payload, and then 155 // set the header to the content-type appropriate for the payload produced 156 if r.payload != nil { 157 // TODO: infer most appropriate content type based on the producer used, 158 // and the `consumers` section of the spec/operation 159 req.Header.Set(runtime.HeaderContentType, mediaType) 160 if rdr, ok := r.payload.(io.ReadCloser); ok { 161 req.Body = rdr 162 return req, nil 163 } 164 165 if rdr, ok := r.payload.(io.Reader); ok { 166 req.Body = ioutil.NopCloser(rdr) 167 return req, nil 168 } 169 170 // set the content length of the request or else a chunked transfer is 171 // declared, and this corrupts outgoing JSON payloads. the content's 172 // length must be set prior to the body being written per the spec at 173 // https://golang.org/pkg/net/http 174 // 175 // If Body is present, Content-Length is <= 0 and TransferEncoding 176 // hasn't been set to "identity", Write adds 177 // "Transfer-Encoding: chunked" to the header. Body is closed 178 // after it is sent. 179 // 180 // to that end a temporary buffer, b, is created to produce the payload 181 // body, and then its size is used to set the request's content length 182 var b bytes.Buffer 183 producer := producers[mediaType] 184 if err := producer.Produce(&b, r.payload); err != nil { 185 return nil, err 186 } 187 req.ContentLength = int64(b.Len()) 188 if _, err := buf.Write(b.Bytes()); err != nil { 189 return nil, err 190 } 191 } 192 return req, nil 193} 194 195// SetHeaderParam adds a header param to the request 196// when there is only 1 value provided for the varargs, it will set it. 197// when there are several values provided for the varargs it will add it (no overriding) 198func (r *request) SetHeaderParam(name string, values ...string) error { 199 if r.header == nil { 200 r.header = make(http.Header) 201 } 202 r.header[http.CanonicalHeaderKey(name)] = values 203 return nil 204} 205 206// SetQueryParam adds a query param to the request 207// when there is only 1 value provided for the varargs, it will set it. 208// when there are several values provided for the varargs it will add it (no overriding) 209func (r *request) SetQueryParam(name string, values ...string) error { 210 if r.query == nil { 211 r.query = make(url.Values) 212 } 213 r.query[name] = values 214 return nil 215} 216 217// SetFormParam adds a forn param to the request 218// when there is only 1 value provided for the varargs, it will set it. 219// when there are several values provided for the varargs it will add it (no overriding) 220func (r *request) SetFormParam(name string, values ...string) error { 221 if r.formFields == nil { 222 r.formFields = make(url.Values) 223 } 224 r.formFields[name] = values 225 return nil 226} 227 228// SetPathParam adds a path param to the request 229func (r *request) SetPathParam(name string, value string) error { 230 if r.pathParams == nil { 231 r.pathParams = make(map[string]string) 232 } 233 234 r.pathParams[name] = value 235 return nil 236} 237 238// SetFileParam adds a file param to the request 239func (r *request) SetFileParam(name string, file runtime.NamedReadCloser) error { 240 if actualFile, ok := file.(*os.File); ok { 241 fi, err := os.Stat(actualFile.Name()) 242 if err != nil { 243 return err 244 } 245 if fi.IsDir() { 246 return fmt.Errorf("%q is a directory, only files are supported", file.Name()) 247 } 248 } 249 250 if r.fileFields == nil { 251 r.fileFields = make(map[string]runtime.NamedReadCloser) 252 } 253 if r.formFields == nil { 254 r.formFields = make(url.Values) 255 } 256 257 r.fileFields[name] = file 258 return nil 259} 260 261// SetBodyParam sets a body parameter on the request. 262// This does not yet serialze the object, this happens as late as possible. 263func (r *request) SetBodyParam(payload interface{}) error { 264 r.payload = payload 265 return nil 266} 267 268// SetTimeout sets the timeout for a request 269func (r *request) SetTimeout(timeout time.Duration) error { 270 r.timeout = timeout 271 return nil 272} 273