// Copyright 2015 go-swagger maintainers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client import ( "bytes" "fmt" "io" "io/ioutil" "log" "mime/multipart" "net/http" "net/url" "os" "path" "path/filepath" "strings" "time" "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" ) // NewRequest creates a new swagger http client request func newRequest(method, pathPattern string, writer runtime.ClientRequestWriter) (*request, error) { return &request{ pathPattern: pathPattern, method: method, writer: writer, header: make(http.Header), query: make(url.Values), timeout: DefaultTimeout, }, nil } // Request represents a swagger client request. // // This Request struct converts to a HTTP request. // There might be others that convert to other transports. // There is no error checking here, it is assumed to be used after a spec has been validated. // so impossible combinations should not arise (hopefully). // // The main purpose of this struct is to hide the machinery of adding params to a transport request. // The generated code only implements what is necessary to turn a param into a valid value for these methods. type request struct { pathPattern string method string writer runtime.ClientRequestWriter pathParams map[string]string header http.Header query url.Values formFields url.Values fileFields map[string][]runtime.NamedReadCloser payload interface{} timeout time.Duration buf *bytes.Buffer } var ( // ensure interface compliance _ runtime.ClientRequest = new(request) ) func (r *request) isMultipart(mediaType string) bool { if len(r.fileFields) > 0 { return true } return runtime.MultipartFormMime == mediaType } // BuildHTTP creates a new http request based on the data from the params func (r *request) BuildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry) (*http.Request, error) { return r.buildHTTP(mediaType, basePath, producers, registry, nil) } func (r *request) buildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry, auth runtime.ClientAuthInfoWriter) (*http.Request, error) { // build the data if err := r.writer.WriteToRequest(r, registry); err != nil { return nil, err } if auth != nil { if err := auth.AuthenticateRequest(r, registry); err != nil { return nil, err } } // create http request var reinstateSlash bool if r.pathPattern != "" && r.pathPattern != "/" && r.pathPattern[len(r.pathPattern)-1] == '/' { reinstateSlash = true } urlPath := path.Join(basePath, r.pathPattern) for k, v := range r.pathParams { urlPath = strings.Replace(urlPath, "{"+k+"}", url.PathEscape(v), -1) } if reinstateSlash { urlPath = urlPath + "/" } var body io.ReadCloser var pr *io.PipeReader var pw *io.PipeWriter r.buf = bytes.NewBuffer(nil) if r.payload != nil || len(r.formFields) > 0 || len(r.fileFields) > 0 { body = ioutil.NopCloser(r.buf) if r.isMultipart(mediaType) { pr, pw = io.Pipe() body = pr } } req, err := http.NewRequest(r.method, urlPath, body) if err != nil { return nil, err } req.URL.RawQuery = r.query.Encode() req.Header = r.header // check if this is a form type request if len(r.formFields) > 0 || len(r.fileFields) > 0 { if !r.isMultipart(mediaType) { req.Header.Set(runtime.HeaderContentType, mediaType) formString := r.formFields.Encode() // set content length before writing to the buffer req.ContentLength = int64(len(formString)) // write the form values as the body r.buf.WriteString(formString) return req, nil } mp := multipart.NewWriter(pw) req.Header.Set(runtime.HeaderContentType, mangleContentType(mediaType, mp.Boundary())) go func() { defer func() { mp.Close() pw.Close() }() for fn, v := range r.formFields { for _, vi := range v { if err := mp.WriteField(fn, vi); err != nil { pw.CloseWithError(err) log.Println(err) } } } defer func() { for _, ff := range r.fileFields { for _, ffi := range ff { ffi.Close() } } }() for fn, f := range r.fileFields { for _, fi := range f { wrtr, err := mp.CreateFormFile(fn, filepath.Base(fi.Name())) if err != nil { pw.CloseWithError(err) log.Println(err) } else if _, err := io.Copy(wrtr, fi); err != nil { pw.CloseWithError(err) log.Println(err) } } } }() return req, nil } // if there is payload, use the producer to write the payload, and then // set the header to the content-type appropriate for the payload produced if r.payload != nil { // TODO: infer most appropriate content type based on the producer used, // and the `consumers` section of the spec/operation req.Header.Set(runtime.HeaderContentType, mediaType) if rdr, ok := r.payload.(io.ReadCloser); ok { req.Body = rdr return req, nil } if rdr, ok := r.payload.(io.Reader); ok { req.Body = ioutil.NopCloser(rdr) return req, nil } req.GetBody = func() (io.ReadCloser, error) { var b bytes.Buffer producer := producers[mediaType] if err := producer.Produce(&b, r.payload); err != nil { return nil, err } if _, err := r.buf.Write(b.Bytes()); err != nil { return nil, err } return ioutil.NopCloser(&b), nil } // set the content length of the request or else a chunked transfer is // declared, and this corrupts outgoing JSON payloads. the content's // length must be set prior to the body being written per the spec at // https://golang.org/pkg/net/http // // If Body is present, Content-Length is <= 0 and TransferEncoding // hasn't been set to "identity", Write adds // "Transfer-Encoding: chunked" to the header. Body is closed // after it is sent. // // to that end a temporary buffer, b, is created to produce the payload // body, and then its size is used to set the request's content length var b bytes.Buffer producer := producers[mediaType] if err := producer.Produce(&b, r.payload); err != nil { return nil, err } req.ContentLength = int64(b.Len()) if _, err := r.buf.Write(b.Bytes()); err != nil { return nil, err } } if runtime.CanHaveBody(req.Method) && req.Body == nil && req.Header.Get(runtime.HeaderContentType) == "" { req.Header.Set(runtime.HeaderContentType, mediaType) } return req, nil } func mangleContentType(mediaType, boundary string) string { if strings.ToLower(mediaType) == runtime.URLencodedFormMime { return fmt.Sprintf("%s; boundary=%s", mediaType, boundary) } return "multipart/form-data; boundary=" + boundary } func (r *request) GetMethod() string { return r.method } func (r *request) GetPath() string { path := r.pathPattern for k, v := range r.pathParams { path = strings.Replace(path, "{"+k+"}", v, -1) } return path } func (r *request) GetBody() []byte { if r.buf == nil { return nil } return r.buf.Bytes() } // SetHeaderParam adds a header param to the request // when there is only 1 value provided for the varargs, it will set it. // when there are several values provided for the varargs it will add it (no overriding) func (r *request) SetHeaderParam(name string, values ...string) error { if r.header == nil { r.header = make(http.Header) } r.header[http.CanonicalHeaderKey(name)] = values return nil } // SetQueryParam adds a query param to the request // when there is only 1 value provided for the varargs, it will set it. // when there are several values provided for the varargs it will add it (no overriding) func (r *request) SetQueryParam(name string, values ...string) error { if r.query == nil { r.query = make(url.Values) } r.query[name] = values return nil } // GetQueryParams returns a copy of all query params currently set for the request func (r *request) GetQueryParams() url.Values { var result = make(url.Values) for key, value := range r.query { result[key] = append([]string{}, value...) } return result } // SetFormParam adds a forn param to the request // when there is only 1 value provided for the varargs, it will set it. // when there are several values provided for the varargs it will add it (no overriding) func (r *request) SetFormParam(name string, values ...string) error { if r.formFields == nil { r.formFields = make(url.Values) } r.formFields[name] = values return nil } // SetPathParam adds a path param to the request func (r *request) SetPathParam(name string, value string) error { if r.pathParams == nil { r.pathParams = make(map[string]string) } r.pathParams[name] = value return nil } // SetFileParam adds a file param to the request func (r *request) SetFileParam(name string, files ...runtime.NamedReadCloser) error { for _, file := range files { if actualFile, ok := file.(*os.File); ok { fi, err := os.Stat(actualFile.Name()) if err != nil { return err } if fi.IsDir() { return fmt.Errorf("%q is a directory, only files are supported", file.Name()) } } } if r.fileFields == nil { r.fileFields = make(map[string][]runtime.NamedReadCloser) } if r.formFields == nil { r.formFields = make(url.Values) } r.fileFields[name] = files return nil } func (r *request) GetFileParam() map[string][]runtime.NamedReadCloser { return r.fileFields } // SetBodyParam sets a body parameter on the request. // This does not yet serialze the object, this happens as late as possible. func (r *request) SetBodyParam(payload interface{}) error { r.payload = payload return nil } func (r *request) GetBodyParam() interface{} { return r.payload } // SetTimeout sets the timeout for a request func (r *request) SetTimeout(timeout time.Duration) error { r.timeout = timeout return nil }