375 lines
10 KiB
Go
375 lines
10 KiB
Go
// 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
|
|
}
|