podman
481 строка · 12.9 Кб
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 client16
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) {39return &request{40pathPattern: pathPattern,41method: method,42writer: writer,43header: make(http.Header),44query: make(url.Values),45timeout: DefaultTimeout,46getBody: getRequestBuffer,47}, nil48}
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 {60pathPattern string61method string62writer runtime.ClientRequestWriter63
64pathParams map[string]string65header http.Header66query url.Values67formFields url.Values68fileFields map[string][]runtime.NamedReadCloser69payload interface{}70timeout time.Duration71buf *bytes.Buffer72
73getBody func(r *request) []byte74}
75
76var (77// ensure interface compliance78_ runtime.ClientRequest = new(request)79)
80
81func (r *request) isMultipart(mediaType string) bool {82if len(r.fileFields) > 0 {83return true84}85
86return runtime.MultipartFormMime == mediaType87}
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) {91return r.buildHTTP(mediaType, basePath, producers, registry, nil)92}
93func escapeQuotes(s string) string {94return strings.NewReplacer("\\", "\\\\", `"`, "\\\"").Replace(s)95}
96
97func logClose(err error, pw *io.PipeWriter) {98log.Println(err)99closeErr := pw.CloseWithError(err)100if closeErr != nil {101log.Println(closeErr)102}103}
104
105func (r *request) buildHTTP(mediaType, basePath string, producers map[string]runtime.Producer, registry strfmt.Registry, auth runtime.ClientAuthInfoWriter) (*http.Request, error) {106// build the data107if err := r.writer.WriteToRequest(r, registry); err != nil {108return nil, err109}110
111// Our body must be an io.Reader.112// When we create the http.Request, if we pass it a113// bytes.Buffer then it will wrap it in an io.ReadCloser114// and set the content length automatically.115var body io.Reader116var pr *io.PipeReader117var pw *io.PipeWriter118
119r.buf = bytes.NewBuffer(nil)120if r.payload != nil || len(r.formFields) > 0 || len(r.fileFields) > 0 {121body = r.buf122if r.isMultipart(mediaType) {123pr, pw = io.Pipe()124body = pr125}126}127
128// check if this is a form type request129if len(r.formFields) > 0 || len(r.fileFields) > 0 {130if !r.isMultipart(mediaType) {131r.header.Set(runtime.HeaderContentType, mediaType)132formString := r.formFields.Encode()133r.buf.WriteString(formString)134goto DoneChoosingBodySource135}136
137mp := multipart.NewWriter(pw)138r.header.Set(runtime.HeaderContentType, mangleContentType(mediaType, mp.Boundary()))139
140go func() {141defer func() {142mp.Close()143pw.Close()144}()145
146for fn, v := range r.formFields {147for _, vi := range v {148if err := mp.WriteField(fn, vi); err != nil {149logClose(err, pw)150return151}152}153}154
155defer func() {156for _, ff := range r.fileFields {157for _, ffi := range ff {158ffi.Close()159}160}161}()162for fn, f := range r.fileFields {163for _, fi := range f {164var fileContentType string165if p, ok := fi.(interface {166ContentType() string167}); ok {168fileContentType = p.ContentType()169} else {170// Need to read the data so that we can detect the content type171buf := make([]byte, 512)172size, err := fi.Read(buf)173if err != nil {174logClose(err, pw)175return176}177fileContentType = http.DetectContentType(buf)178fi = runtime.NamedReader(fi.Name(), io.MultiReader(bytes.NewReader(buf[:size]), fi))179}180
181// Create the MIME headers for the new part182h := make(textproto.MIMEHeader)183h.Set("Content-Disposition",184fmt.Sprintf(`form-data; name="%s"; filename="%s"`,185escapeQuotes(fn), escapeQuotes(filepath.Base(fi.Name()))))186h.Set("Content-Type", fileContentType)187
188wrtr, err := mp.CreatePart(h)189if err != nil {190logClose(err, pw)191return192}193if _, err := io.Copy(wrtr, fi); err != nil {194logClose(err, pw)195}196}197}198}()199
200goto DoneChoosingBodySource201}202
203// if there is payload, use the producer to write the payload, and then204// set the header to the content-type appropriate for the payload produced205if r.payload != nil {206// TODO: infer most appropriate content type based on the producer used,207// and the `consumers` section of the spec/operation208r.header.Set(runtime.HeaderContentType, mediaType)209if rdr, ok := r.payload.(io.ReadCloser); ok {210body = rdr211goto DoneChoosingBodySource212}213
214if rdr, ok := r.payload.(io.Reader); ok {215body = rdr216goto DoneChoosingBodySource217}218
219producer := producers[mediaType]220if err := producer.Produce(r.buf, r.payload); err != nil {221return nil, err222}223}224
225DoneChoosingBodySource:226
227if runtime.CanHaveBody(r.method) && body != nil && r.header.Get(runtime.HeaderContentType) == "" {228r.header.Set(runtime.HeaderContentType, mediaType)229}230
231if auth != nil {232// If we're not using r.buf as our http.Request's body,233// either the payload is an io.Reader or io.ReadCloser,234// or we're doing a multipart form/file.235//236// In those cases, if the AuthenticateRequest call asks for the body,237// we must read it into a buffer and provide that, then use that buffer238// as the body of our http.Request.239//240// This is done in-line with the GetBody() request rather than ahead241// of time, because there's no way to know if the AuthenticateRequest242// will even ask for the body of the request.243//244// If for some reason the copy fails, there's no way to return that245// error to the GetBody() call, so return it afterwards.246//247// An error from the copy action is prioritized over any error248// from the AuthenticateRequest call, because the mis-read249// body may have interfered with the auth.250//251var copyErr error252if buf, ok := body.(*bytes.Buffer); body != nil && (!ok || buf != r.buf) {253var copied bool254r.getBody = func(r *request) []byte {255if copied {256return getRequestBuffer(r)257}258
259defer func() {260copied = true261}()262
263if _, copyErr = io.Copy(r.buf, body); copyErr != nil {264return nil265}266
267if closer, ok := body.(io.ReadCloser); ok {268if copyErr = closer.Close(); copyErr != nil {269return nil270}271}272
273body = r.buf274return getRequestBuffer(r)275}276}277
278authErr := auth.AuthenticateRequest(r, registry)279
280if copyErr != nil {281return nil, fmt.Errorf("error retrieving the response body: %v", copyErr)282}283
284if authErr != nil {285return nil, authErr286}287}288
289// In case the basePath or the request pathPattern include static query parameters,290// parse those out before constructing the final path. The parameters themselves291// will be merged with the ones set by the client, with the priority given first to292// the ones set by the client, then the path pattern, and lastly the base path.293basePathURL, err := url.Parse(basePath)294if err != nil {295return nil, err296}297staticQueryParams := basePathURL.Query()298
299pathPatternURL, err := url.Parse(r.pathPattern)300if err != nil {301return nil, err302}303for name, values := range pathPatternURL.Query() {304if _, present := staticQueryParams[name]; present {305staticQueryParams.Del(name)306}307for _, value := range values {308staticQueryParams.Add(name, value)309}310}311
312// create http request313var reinstateSlash bool314if pathPatternURL.Path != "" && pathPatternURL.Path != "/" && pathPatternURL.Path[len(pathPatternURL.Path)-1] == '/' {315reinstateSlash = true316}317
318urlPath := path.Join(basePathURL.Path, pathPatternURL.Path)319for k, v := range r.pathParams {320urlPath = strings.Replace(urlPath, "{"+k+"}", url.PathEscape(v), -1)321}322if reinstateSlash {323urlPath = urlPath + "/"324}325
326req, err := http.NewRequest(r.method, urlPath, body)327if err != nil {328return nil, err329}330
331originalParams := r.GetQueryParams()332
333// Merge the query parameters extracted from the basePath with the ones set by334// the client in this struct. In case of conflict, the client wins.335for k, v := range staticQueryParams {336_, present := originalParams[k]337if !present {338if err = r.SetQueryParam(k, v...); err != nil {339return nil, err340}341}342}343
344req.URL.RawQuery = r.query.Encode()345req.Header = r.header346
347return req, nil348}
349
350func mangleContentType(mediaType, boundary string) string {351if strings.ToLower(mediaType) == runtime.URLencodedFormMime {352return fmt.Sprintf("%s; boundary=%s", mediaType, boundary)353}354return "multipart/form-data; boundary=" + boundary355}
356
357func (r *request) GetMethod() string {358return r.method359}
360
361func (r *request) GetPath() string {362path := r.pathPattern363for k, v := range r.pathParams {364path = strings.Replace(path, "{"+k+"}", v, -1)365}366return path367}
368
369func (r *request) GetBody() []byte {370return r.getBody(r)371}
372
373func getRequestBuffer(r *request) []byte {374if r.buf == nil {375return nil376}377return r.buf.Bytes()378}
379
380// SetHeaderParam adds a header param to the request
381// when there is only 1 value provided for the varargs, it will set it.
382// when there are several values provided for the varargs it will add it (no overriding)
383func (r *request) SetHeaderParam(name string, values ...string) error {384if r.header == nil {385r.header = make(http.Header)386}387r.header[http.CanonicalHeaderKey(name)] = values388return nil389}
390
391// GetHeaderParams returns the all headers currently set for the request
392func (r *request) GetHeaderParams() http.Header {393return r.header394}
395
396// SetQueryParam adds a query param to the request
397// when there is only 1 value provided for the varargs, it will set it.
398// when there are several values provided for the varargs it will add it (no overriding)
399func (r *request) SetQueryParam(name string, values ...string) error {400if r.query == nil {401r.query = make(url.Values)402}403r.query[name] = values404return nil405}
406
407// GetQueryParams returns a copy of all query params currently set for the request
408func (r *request) GetQueryParams() url.Values {409var result = make(url.Values)410for key, value := range r.query {411result[key] = append([]string{}, value...)412}413return result414}
415
416// SetFormParam adds a forn param to the request
417// when there is only 1 value provided for the varargs, it will set it.
418// when there are several values provided for the varargs it will add it (no overriding)
419func (r *request) SetFormParam(name string, values ...string) error {420if r.formFields == nil {421r.formFields = make(url.Values)422}423r.formFields[name] = values424return nil425}
426
427// SetPathParam adds a path param to the request
428func (r *request) SetPathParam(name string, value string) error {429if r.pathParams == nil {430r.pathParams = make(map[string]string)431}432
433r.pathParams[name] = value434return nil435}
436
437// SetFileParam adds a file param to the request
438func (r *request) SetFileParam(name string, files ...runtime.NamedReadCloser) error {439for _, file := range files {440if actualFile, ok := file.(*os.File); ok {441fi, err := os.Stat(actualFile.Name())442if err != nil {443return err444}445if fi.IsDir() {446return fmt.Errorf("%q is a directory, only files are supported", file.Name())447}448}449}450
451if r.fileFields == nil {452r.fileFields = make(map[string][]runtime.NamedReadCloser)453}454if r.formFields == nil {455r.formFields = make(url.Values)456}457
458r.fileFields[name] = files459return nil460}
461
462func (r *request) GetFileParam() map[string][]runtime.NamedReadCloser {463return r.fileFields464}
465
466// SetBodyParam sets a body parameter on the request.
467// This does not yet serialze the object, this happens as late as possible.
468func (r *request) SetBodyParam(payload interface{}) error {469r.payload = payload470return nil471}
472
473func (r *request) GetBodyParam() interface{} {474return r.payload475}
476
477// SetTimeout sets the timeout for a request
478func (r *request) SetTimeout(timeout time.Duration) error {479r.timeout = timeout480return nil481}
482