pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/req/req.go (about)

     1  // Package req simplify working with an HTTP requests
     2  package req
     3  
     4  // ////////////////////////////////////////////////////////////////////////////////// //
     5  //                                                                                    //
     6  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     7  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     8  //                                                                                    //
     9  // ////////////////////////////////////////////////////////////////////////////////// //
    10  
    11  import (
    12  	"bytes"
    13  	"encoding/json"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"mime/multipart"
    18  	"net"
    19  	"net/http"
    20  	"net/url"
    21  	"os"
    22  	"path/filepath"
    23  	"runtime"
    24  	"strings"
    25  	"time"
    26  )
    27  
    28  // ////////////////////////////////////////////////////////////////////////////////// //
    29  
    30  // Error types
    31  const (
    32  	ERROR_BODY_ENCODE    = 1
    33  	ERROR_CREATE_REQUEST = 2
    34  	ERROR_SEND_REQUEST   = 3
    35  )
    36  
    37  // Request method
    38  const (
    39  	POST   = "POST"
    40  	GET    = "GET"
    41  	PUT    = "PUT"
    42  	HEAD   = "HEAD"
    43  	DELETE = "DELETE"
    44  	PATCH  = "PATCH"
    45  )
    46  
    47  // Content types
    48  const (
    49  	CONTENT_TYPE_ATOM         = "application/atom+xml"
    50  	CONTENT_TYPE_EDI          = "application/EDI-X12"
    51  	CONTENT_TYPE_EDIFACT      = "application/EDIFACT"
    52  	CONTENT_TYPE_JSON         = "application/json"
    53  	CONTENT_TYPE_JAVASCRIPT   = "application/javascript"
    54  	CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"
    55  	CONTENT_TYPE_PDF          = "application/pdf"
    56  	CONTENT_TYPE_POSTSCRIPT   = "application/postscript"
    57  	CONTENT_TYPE_SOAP         = "application/soap+xml"
    58  	CONTENT_TYPE_WOFF         = "application/font-woff"
    59  	CONTENT_TYPE_XHTML        = "application/xhtml+xml"
    60  	CONTENT_TYPE_DTD          = "application/xml-dtd"
    61  	CONTENT_TYPE_XOP          = "application/xop+xml"
    62  	CONTENT_TYPE_ZIP          = "application/zip"
    63  	CONTENT_TYPE_GZIP         = "application/gzip"
    64  	CONTENT_TYPE_BITTORRENT   = "application/x-bittorrent"
    65  	CONTENT_TYPE_TEX          = "application/x-tex"
    66  	CONTENT_TYPE_BASIC        = "audio/basic"
    67  	CONTENT_TYPE_L24          = "audio/L24"
    68  	CONTENT_TYPE_MP4_AUDIO    = "audio/mp4"
    69  	CONTENT_TYPE_AAC          = "audio/aac"
    70  	CONTENT_TYPE_MPEG_AUDIO   = "audio/mpeg"
    71  	CONTENT_TYPE_OGG_AUDIO    = "audio/ogg"
    72  	CONTENT_TYPE_VORBIS       = "audio/vorbis"
    73  	CONTENT_TYPE_WMA          = "audio/x-ms-wma"
    74  	CONTENT_TYPE_WAX          = "audio/x-ms-wax"
    75  	CONTENT_TYPE_REALAUDIO    = "audio/vnd.rn-realaudio"
    76  	CONTENT_TYPE_WAV          = "audio/vnd.wave"
    77  	CONTENT_TYPE_WEBM_AUDIO   = "audio/webm"
    78  	CONTENT_TYPE_GIF          = "image/gif"
    79  	CONTENT_TYPE_JPEG         = "image/jpeg"
    80  	CONTENT_TYPE_PJPEG        = "image/pjpeg"
    81  	CONTENT_TYPE_PNG          = "image/png"
    82  	CONTENT_TYPE_SVG          = "image/svg+xml"
    83  	CONTENT_TYPE_TIFF         = "image/tiff"
    84  	CONTENT_TYPE_ICON         = "image/vnd.microsoft.icon"
    85  	CONTENT_TYPE_WBMP         = "image/vnd.wap.wbmp"
    86  	CONTENT_TYPE_HTTP         = "message/http"
    87  	CONTENT_TYPE_IMDN         = "message/imdn+xml"
    88  	CONTENT_TYPE_PARTIAL      = "message/partial"
    89  	CONTENT_TYPE_RFC822       = "message/rfc822"
    90  	CONTENT_TYPE_EXAMPLE      = "model/example"
    91  	CONTENT_TYPE_IGES         = "model/iges"
    92  	CONTENT_TYPE_MESH         = "model/mesh"
    93  	CONTENT_TYPE_VRML         = "model/vrml"
    94  	CONTENT_TYPE_MIXED        = "multipart/mixed"
    95  	CONTENT_TYPE_ALTERNATIVE  = "multipart/alternative"
    96  	CONTENT_TYPE_RELATED      = "multipart/related"
    97  	CONTENT_TYPE_FORM_DATA    = "multipart/form-data"
    98  	CONTENT_TYPE_SIGNED       = "multipart/signed"
    99  	CONTENT_TYPE_ENCRYPTED    = "multipart/encrypted"
   100  	CONTENT_TYPE_CSS          = "text/css"
   101  	CONTENT_TYPE_CSV          = "text/csv"
   102  	CONTENT_TYPE_HTML         = "text/html"
   103  	CONTENT_TYPE_PLAIN        = "text/plain"
   104  	CONTENT_TYPE_PHP          = "text/php"
   105  	CONTENT_TYPE_XML          = "text/xml"
   106  	CONTENT_TYPE_MPEG_VIDEO   = "video/mpeg"
   107  	CONTENT_TYPE_MP4_VIDEO    = "video/mp4"
   108  	CONTENT_TYPE_OGG_VIDEO    = "video/ogg"
   109  	CONTENT_TYPE_QUICKTIME    = "video/quicktime"
   110  	CONTENT_TYPE_WEBM_VIDEO   = "video/webm"
   111  	CONTENT_TYPE_WMV          = "video/x-ms-wmv"
   112  	CONTENT_TYPE_FLV          = "video/x-flv"
   113  	CONTENT_TYPE_3GPP         = "video/3gpp"
   114  	CONTENT_TYPE_3GPP2        = "video/3gpp2"
   115  )
   116  
   117  // Status codes
   118  const (
   119  	STATUS_CONTINUE                        = 100 // RFC 7231, 6.2.1
   120  	STATUS_SWITCHING_PROTOCOLS             = 101 // RFC 7231, 6.2.2
   121  	STATUS_PROCESSING                      = 102 // RFC 2518, 10.1
   122  	STATUS_OK                              = 200 // RFC 7231, 6.3.1
   123  	STATUS_CREATED                         = 201 // RFC 7231, 6.3.2
   124  	STATUS_ACCEPTED                        = 202 // RFC 7231, 6.3.3
   125  	STATUS_NON_AUTHORITATIVE_INFO          = 203 // RFC 7231, 6.3.4
   126  	STATUS_NO_CONTENT                      = 204 // RFC 7231, 6.3.5
   127  	STATUS_RESET_CONTENT                   = 205 // RFC 7231, 6.3.6
   128  	STATUS_PARTIAL_CONTENT                 = 206 // RFC 7233, 4.1
   129  	STATUS_MULTI_STATUS                    = 207 // RFC 4918, 11.1
   130  	STATUS_ALREADY_REPORTED                = 208 // RFC 5842, 7.1
   131  	STATUS_IMUSED                          = 226 // RFC 3229, 10.4.1
   132  	STATUS_MULTIPLE_CHOICES                = 300 // RFC 7231, 6.4.1
   133  	STATUS_MOVED_PERMANENTLY               = 301 // RFC 7231, 6.4.2
   134  	STATUS_FOUND                           = 302 // RFC 7231, 6.4.3
   135  	STATUS_SEE_OTHER                       = 303 // RFC 7231, 6.4.4
   136  	STATUS_NOT_MODIFIED                    = 304 // RFC 7232, 4.1
   137  	STATUS_USE_PROXY                       = 305 // RFC 7231, 6.4.5
   138  	STATUS_TEMPORARY_REDIRECT              = 307 // RFC 7231, 6.4.7
   139  	STATUS_PERMANENT_REDIRECT              = 308 // RFC 7538, 3
   140  	STATUS_BAD_REQUEST                     = 400 // RFC 7231, 6.5.1
   141  	STATUS_UNAUTHORIZED                    = 401 // RFC 7235, 3.1
   142  	STATUS_PAYMENT_REQUIRED                = 402 // RFC 7231, 6.5.2
   143  	STATUS_FORBIDDEN                       = 403 // RFC 7231, 6.5.3
   144  	STATUS_NOT_FOUND                       = 404 // RFC 7231, 6.5.4
   145  	STATUS_METHOD_NOT_ALLOWED              = 405 // RFC 7231, 6.5.5
   146  	STATUS_NOT_ACCEPTABLE                  = 406 // RFC 7231, 6.5.6
   147  	STATUS_PROXY_AUTH_REQUIRED             = 407 // RFC 7235, 3.2
   148  	STATUS_REQUEST_TIMEOUT                 = 408 // RFC 7231, 6.5.7
   149  	STATUS_CONFLICT                        = 409 // RFC 7231, 6.5.8
   150  	STATUS_GONE                            = 410 // RFC 7231, 6.5.9
   151  	STATUS_LENGTH_REQUIRED                 = 411 // RFC 7231, 6.5.10
   152  	STATUS_PRECONDITION_FAILED             = 412 // RFC 7232, 4.2
   153  	STATUS_REQUEST_ENTITY_TOO_LARGE        = 413 // RFC 7231, 6.5.11
   154  	STATUS_REQUEST_URITOO_LONG             = 414 // RFC 7231, 6.5.12
   155  	STATUS_UNSUPPORTED_MEDIA_TYPE          = 415 // RFC 7231, 6.5.13
   156  	STATUS_REQUESTED_RANGE_NOT_SATISFIABLE = 416 // RFC 7233, 4.4
   157  	STATUS_EXPECTATION_FAILED              = 417 // RFC 7231, 6.5.14
   158  	STATUS_TEAPOT                          = 418 // RFC 7168, 2.3.3
   159  	STATUS_UNPROCESSABLE_ENTITY            = 422 // RFC 4918, 11.2
   160  	STATUS_LOCKED                          = 423 // RFC 4918, 11.3
   161  	STATUS_FAILED_DEPENDENCY               = 424 // RFC 4918, 11.4
   162  	STATUS_UPGRADE_REQUIRED                = 426 // RFC 7231, 6.5.15
   163  	STATUS_PRECONDITION_REQUIRED           = 428 // RFC 6585, 3
   164  	STATUS_TOO_MANY_REQUESTS               = 429 // RFC 6585, 4
   165  	STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 // RFC 6585, 5
   166  	STATUS_UNAVAILABLE_FOR_LEGAL_REASONS   = 451 // RFC 7725, 3
   167  	STATUS_INTERNAL_SERVER_ERROR           = 500 // RFC 7231, 6.6.1
   168  	STATUS_NOT_IMPLEMENTED                 = 501 // RFC 7231, 6.6.2
   169  	STATUS_BAD_GATEWAY                     = 502 // RFC 7231, 6.6.3
   170  	STATUS_SERVICE_UNAVAILABLE             = 503 // RFC 7231, 6.6.4
   171  	STATUS_GATEWAY_TIMEOUT                 = 504 // RFC 7231, 6.6.5
   172  	STATUS_HTTPVERSION_NOT_SUPPORTED       = 505 // RFC 7231, 6.6.6
   173  	STATUS_VARIANT_ALSO_NEGOTIATES         = 506 // RFC 2295, 8.1
   174  	STATUS_INSUFFICIENT_STORAGE            = 507 // RFC 4918, 11.5
   175  	STATUS_LOOP_DETECTED                   = 508 // RFC 5842, 7.2
   176  	STATUS_NOT_EXTENDED                    = 510 // RFC 2774, 7
   177  	STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511 // RFC 6585, 6
   178  )
   179  
   180  // USER_AGENT is default user agent
   181  const USER_AGENT = "go-ek-req"
   182  
   183  // ////////////////////////////////////////////////////////////////////////////////// //
   184  
   185  // Query is a map[string]interface{} used for query
   186  type Query map[string]interface{}
   187  
   188  // Headers is a map[string]string used for headers
   189  type Headers map[string]string
   190  
   191  // Request is basic struct
   192  type Request struct {
   193  	Method            string      // Request method
   194  	URL               string      // Request URL
   195  	Query             Query       // Map with query params
   196  	Body              interface{} // Request body
   197  	Headers           Headers     // Map with headers
   198  	ContentType       string      // Content type header
   199  	Accept            string      // Accept header
   200  	BasicAuthUsername string      // Basic auth username
   201  	BasicAuthPassword string      // Basic auth password
   202  	AutoDiscard       bool        // Automatically discard all responses with status code != 200
   203  	FollowRedirect    bool        // Follow redirect
   204  	Close             bool        // Close indicates whether to close the connection after sending request
   205  }
   206  
   207  // Response is struct contains response data and properties
   208  type Response struct {
   209  	*http.Response
   210  	URL string
   211  }
   212  
   213  // RequestError is error struct
   214  type RequestError struct {
   215  	class int
   216  	desc  string
   217  }
   218  
   219  // Engine is request engine
   220  type Engine struct {
   221  	UserAgent string // UserAgent is default user-agent used for all requests
   222  
   223  	Dialer    *net.Dialer     // Dialer is default dialer struct
   224  	Transport *http.Transport // Transport is default transport struct
   225  	Client    *http.Client    // Client is default client struct
   226  
   227  	dialTimeout    float64 // dialTimeout is dial timeout in seconds
   228  	requestTimeout float64 // requestTimeout is request timeout in seconds
   229  
   230  	initialized bool
   231  }
   232  
   233  // ////////////////////////////////////////////////////////////////////////////////// //
   234  
   235  var (
   236  	// ErrEngineIsNil is returned if engine struct is nil
   237  	ErrEngineIsNil = RequestError{ERROR_CREATE_REQUEST, "Engine is nil"}
   238  
   239  	// ErrClientIsNil is returned if client struct is nil
   240  	ErrClientIsNil = RequestError{ERROR_CREATE_REQUEST, "Engine.Client is nil"}
   241  
   242  	// ErrTransportIsNil is returned if transport is nil
   243  	ErrTransportIsNil = RequestError{ERROR_CREATE_REQUEST, "Engine.Transport is nil"}
   244  
   245  	// ErrDialerIsNil is returned if dialer is nil
   246  	ErrDialerIsNil = RequestError{ERROR_CREATE_REQUEST, "Engine.Dialer is nil"}
   247  
   248  	// ErrEmptyURL is returned if given URL is empty
   249  	ErrEmptyURL = RequestError{ERROR_CREATE_REQUEST, "URL property can't be empty and must be set"}
   250  
   251  	// ErrUnsupportedScheme is returned if given URL contains unsupported scheme
   252  	ErrUnsupportedScheme = RequestError{ERROR_CREATE_REQUEST, "Unsupported scheme in URL"}
   253  )
   254  
   255  // Global is global engine used by default for Request.Do, Request.Get, Request.Post,
   256  // Request.Put, Request.Patch, Request.Head and Request.Delete methods
   257  var Global = &Engine{
   258  	dialTimeout: 10.0,
   259  }
   260  
   261  // ////////////////////////////////////////////////////////////////////////////////// //
   262  
   263  var ioCopyFunc = io.Copy
   264  var useFakeFormGenerator = false
   265  
   266  // ////////////////////////////////////////////////////////////////////////////////// //
   267  
   268  // SetUserAgent sets user agent based on app name and version for global engine
   269  func SetUserAgent(app, version string, subs ...string) {
   270  	Global.SetUserAgent(app, version, subs...)
   271  }
   272  
   273  // SetDialTimeout sets dial timeout for global engine
   274  func SetDialTimeout(timeout float64) {
   275  	Global.SetDialTimeout(timeout)
   276  }
   277  
   278  // SetRequestTimeout sets request timeout for global engine
   279  func SetRequestTimeout(timeout float64) {
   280  	Global.SetRequestTimeout(timeout)
   281  }
   282  
   283  // ////////////////////////////////////////////////////////////////////////////////// //
   284  
   285  // Init initializes engine
   286  func (e *Engine) Init() *Engine {
   287  	if e.initialized {
   288  		return e
   289  	}
   290  
   291  	if e.Dialer == nil {
   292  		e.Dialer = &net.Dialer{}
   293  	}
   294  
   295  	if e.Transport == nil {
   296  		e.Transport = &http.Transport{
   297  			Dial:  e.Dialer.Dial,
   298  			Proxy: http.ProxyFromEnvironment,
   299  		}
   300  	} else {
   301  		e.Transport.Dial = e.Dialer.Dial
   302  	}
   303  
   304  	if e.Client == nil {
   305  		e.Client = &http.Client{
   306  			Transport: e.Transport,
   307  		}
   308  	}
   309  
   310  	if e.dialTimeout > 0 {
   311  		e.SetDialTimeout(e.dialTimeout)
   312  	}
   313  
   314  	if e.requestTimeout > 0 {
   315  		e.SetRequestTimeout(e.requestTimeout)
   316  	}
   317  
   318  	if e.UserAgent == "" {
   319  		e.SetUserAgent(USER_AGENT, "10")
   320  	}
   321  
   322  	e.dialTimeout = 0
   323  	e.requestTimeout = 0
   324  
   325  	e.initialized = true
   326  
   327  	return e
   328  }
   329  
   330  // Do sends request and process response
   331  func (e *Engine) Do(r Request) (*Response, error) {
   332  	return e.doRequest(r, "")
   333  }
   334  
   335  // Get sends GET request and process response
   336  func (e *Engine) Get(r Request) (*Response, error) {
   337  	return e.doRequest(r, GET)
   338  }
   339  
   340  // Post sends POST request and process response
   341  func (e *Engine) Post(r Request) (*Response, error) {
   342  	return e.doRequest(r, POST)
   343  }
   344  
   345  // Put sends PUT request and process response
   346  func (e *Engine) Put(r Request) (*Response, error) {
   347  	return e.doRequest(r, PUT)
   348  }
   349  
   350  // Head sends HEAD request and process response
   351  func (e *Engine) Head(r Request) (*Response, error) {
   352  	return e.doRequest(r, HEAD)
   353  }
   354  
   355  // Patch sends PATCH request and process response
   356  func (e *Engine) Patch(r Request) (*Response, error) {
   357  	return e.doRequest(r, PATCH)
   358  }
   359  
   360  // PostFile sends multipart POST request with file data
   361  func (e *Engine) PostFile(r Request, file, fieldName string, extraFields map[string]string) (*Response, error) {
   362  	err := configureMultipartRequest(&r, file, fieldName, extraFields)
   363  
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	return e.doRequest(r, POST)
   369  }
   370  
   371  // Delete sends DELETE request and process response
   372  func (e *Engine) Delete(r Request) (*Response, error) {
   373  	return e.doRequest(r, DELETE)
   374  }
   375  
   376  // SetUserAgent sets user agent based on app name and version
   377  func (e *Engine) SetUserAgent(app, version string, subs ...string) {
   378  	if e != nil {
   379  		e.UserAgent = fmt.Sprintf(
   380  			"%s/%s (go; %s; %s-%s)",
   381  			app, version, runtime.Version(),
   382  			runtime.GOARCH, runtime.GOOS,
   383  		)
   384  
   385  		if len(subs) != 0 {
   386  			e.UserAgent += " " + strings.Join(subs, " ")
   387  		}
   388  	}
   389  }
   390  
   391  // SetDialTimeout sets dial timeout
   392  func (e *Engine) SetDialTimeout(timeout float64) {
   393  	if e != nil && timeout > 0 {
   394  		if e.Dialer == nil {
   395  			e.dialTimeout = timeout
   396  		} else {
   397  			e.Dialer.Timeout = time.Duration(timeout * float64(time.Second))
   398  		}
   399  	}
   400  }
   401  
   402  // SetRequestTimeout sets request timeout
   403  func (e *Engine) SetRequestTimeout(timeout float64) {
   404  	if e != nil && timeout > 0 {
   405  		if e.Dialer == nil {
   406  			e.requestTimeout = timeout
   407  		} else {
   408  			e.Client.Timeout = time.Duration(timeout * float64(time.Second))
   409  		}
   410  	}
   411  }
   412  
   413  // Do sends request and process response
   414  func (r Request) Do() (*Response, error) {
   415  	return Global.doRequest(r, "")
   416  }
   417  
   418  // Get sends GET request and process response
   419  func (r Request) Get() (*Response, error) {
   420  	return Global.Get(r)
   421  }
   422  
   423  // Post sends POST request and process response
   424  func (r Request) Post() (*Response, error) {
   425  	return Global.Post(r)
   426  }
   427  
   428  // Put sends PUT request and process response
   429  func (r Request) Put() (*Response, error) {
   430  	return Global.Put(r)
   431  }
   432  
   433  // Head sends HEAD request and process response
   434  func (r Request) Head() (*Response, error) {
   435  	return Global.Head(r)
   436  }
   437  
   438  // Patch sends PATCH request and process response
   439  func (r Request) Patch() (*Response, error) {
   440  	return Global.Patch(r)
   441  }
   442  
   443  // Delete sends DELETE request and process response
   444  func (r Request) Delete() (*Response, error) {
   445  	return Global.Delete(r)
   446  }
   447  
   448  // PostFile sends multipart POST request with file data
   449  func (r Request) PostFile(file, fieldName string, extraFields map[string]string) (*Response, error) {
   450  	return Global.PostFile(r, file, fieldName, extraFields)
   451  }
   452  
   453  // Discard reads response body for closing connection
   454  func (r *Response) Discard() {
   455  	io.Copy(ioutil.Discard, r.Body)
   456  }
   457  
   458  // JSON decodes json encoded body
   459  func (r *Response) JSON(v interface{}) error {
   460  	defer r.Body.Close()
   461  	return json.NewDecoder(r.Body).Decode(v)
   462  }
   463  
   464  // Bytes reads response body as byte slice
   465  func (r *Response) Bytes() []byte {
   466  	defer r.Body.Close()
   467  	result, _ := ioutil.ReadAll(r.Body)
   468  	return result
   469  }
   470  
   471  // String reads response body as string
   472  func (r *Response) String() string {
   473  	return string(r.Bytes())
   474  }
   475  
   476  // Error shows error message
   477  func (e RequestError) Error() string {
   478  	switch e.class {
   479  	case ERROR_BODY_ENCODE:
   480  		return fmt.Sprintf("Can't encode request body (%s)", e.desc)
   481  	case ERROR_SEND_REQUEST:
   482  		return fmt.Sprintf("Can't send request (%s)", e.desc)
   483  	default:
   484  		return fmt.Sprintf("Can't create request struct (%s)", e.desc)
   485  	}
   486  }
   487  
   488  // ////////////////////////////////////////////////////////////////////////////////// //
   489  
   490  // Encode converts query struct to url-encoded string
   491  func (q Query) Encode() string {
   492  	var result string
   493  
   494  	for k, v := range q {
   495  		switch u := v.(type) {
   496  		case string:
   497  			if v == "" {
   498  				result += k + "&"
   499  			} else {
   500  				result += k + "=" + url.QueryEscape(u) + "&"
   501  			}
   502  		case nil:
   503  			result += k + "&"
   504  		default:
   505  			result += k + "=" + fmt.Sprintf("%v", v) + "&"
   506  		}
   507  	}
   508  
   509  	if result == "" {
   510  		return ""
   511  	}
   512  
   513  	return result[:len(result)-1]
   514  }
   515  
   516  // ////////////////////////////////////////////////////////////////////////////////// //
   517  
   518  // This method has a lot of actions to prepare request for executing, so it is ok to
   519  // have so many conditions
   520  // codebeat:disable[CYCLO,ABC]
   521  
   522  func (e *Engine) doRequest(r Request, method string) (*Response, error) {
   523  	// Lazy engine initialization
   524  	if e != nil && !e.initialized {
   525  		e.Init()
   526  	}
   527  
   528  	err := checkRequest(r)
   529  
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  
   534  	err = checkEngine(e)
   535  
   536  	if err != nil {
   537  		return nil, err
   538  	}
   539  
   540  	if method != "" {
   541  		r.Method = method
   542  	}
   543  
   544  	if r.Method == "" {
   545  		r.Method = GET
   546  	}
   547  
   548  	if r.Query != nil && len(r.Query) != 0 {
   549  		r.URL += "?" + r.Query.Encode()
   550  	}
   551  
   552  	bodyReader, err := getBodyReader(r.Body)
   553  
   554  	if err != nil {
   555  		return nil, RequestError{ERROR_BODY_ENCODE, err.Error()}
   556  	}
   557  
   558  	req, err := createRequest(e, r, bodyReader)
   559  
   560  	if err != nil {
   561  		return nil, err
   562  	}
   563  
   564  	resp, err := e.Client.Do(req)
   565  
   566  	if err != nil {
   567  		return nil, RequestError{ERROR_SEND_REQUEST, err.Error()}
   568  	}
   569  
   570  	result := &Response{resp, r.URL}
   571  
   572  	if resp.StatusCode != STATUS_OK && r.AutoDiscard {
   573  		result.Discard()
   574  	}
   575  
   576  	return result, nil
   577  }
   578  
   579  // codebeat:enable[CYCLO,ABC]
   580  
   581  // ////////////////////////////////////////////////////////////////////////////////// //
   582  
   583  func checkRequest(r Request) error {
   584  	if r.URL == "" {
   585  		return ErrEmptyURL
   586  	}
   587  
   588  	if !isURL(r.URL) {
   589  		return ErrUnsupportedScheme
   590  	}
   591  
   592  	return nil
   593  }
   594  
   595  func checkEngine(e *Engine) error {
   596  	if e == nil {
   597  		return ErrEngineIsNil
   598  	}
   599  
   600  	if e.Dialer == nil {
   601  		return ErrDialerIsNil
   602  	}
   603  
   604  	if e.Transport == nil {
   605  		return ErrTransportIsNil
   606  	}
   607  
   608  	if e.Client == nil {
   609  		return ErrClientIsNil
   610  	}
   611  
   612  	return nil
   613  }
   614  
   615  func createRequest(e *Engine, r Request, bodyReader io.Reader) (*http.Request, error) {
   616  	req, err := http.NewRequest(r.Method, r.URL, bodyReader)
   617  
   618  	if err != nil {
   619  		return nil, RequestError{ERROR_CREATE_REQUEST, err.Error()}
   620  	}
   621  
   622  	if r.Headers != nil && len(r.Headers) != 0 {
   623  		for k, v := range r.Headers {
   624  			req.Header.Add(k, v)
   625  		}
   626  	}
   627  
   628  	if r.ContentType != "" {
   629  		req.Header.Add("Content-Type", r.ContentType)
   630  	}
   631  
   632  	if r.Accept != "" {
   633  		req.Header.Add("Accept", r.Accept)
   634  	}
   635  
   636  	if e.UserAgent != "" {
   637  		req.Header.Add("User-Agent", e.UserAgent)
   638  	}
   639  
   640  	if r.BasicAuthUsername != "" && r.BasicAuthPassword != "" {
   641  		req.SetBasicAuth(r.BasicAuthUsername, r.BasicAuthPassword)
   642  	}
   643  
   644  	if r.Close {
   645  		req.Close = true
   646  	}
   647  
   648  	return req, nil
   649  }
   650  
   651  func configureMultipartRequest(r *Request, file, fieldName string, extraFields map[string]string) error {
   652  	fd, err := os.OpenFile(file, os.O_RDONLY, 0)
   653  
   654  	if err != nil {
   655  		return err
   656  	}
   657  
   658  	buf := &bytes.Buffer{}
   659  	w := multipart.NewWriter(buf)
   660  	part, err := createFormFile(w, fieldName, file)
   661  
   662  	if err != nil {
   663  		fd.Close()
   664  		return err
   665  	}
   666  
   667  	_, err = ioCopyFunc(part, fd)
   668  
   669  	if err != nil {
   670  		fd.Close()
   671  		return err
   672  	}
   673  
   674  	if extraFields != nil {
   675  		for k, v := range extraFields {
   676  			w.WriteField(k, v)
   677  		}
   678  	}
   679  
   680  	w.Close()
   681  
   682  	r.ContentType = w.FormDataContentType()
   683  	r.Body = buf
   684  
   685  	return nil
   686  }
   687  
   688  func createFormFile(w *multipart.Writer, fieldName, file string) (io.Writer, error) {
   689  	if useFakeFormGenerator {
   690  		return nil, fmt.Errorf("")
   691  	}
   692  
   693  	return w.CreateFormFile(fieldName, filepath.Base(file))
   694  }
   695  
   696  func getBodyReader(body interface{}) (io.Reader, error) {
   697  	switch u := body.(type) {
   698  	case nil:
   699  		return nil, nil
   700  	case string:
   701  		return strings.NewReader(u), nil
   702  	case io.Reader:
   703  		return u, nil
   704  	case []byte:
   705  		return bytes.NewReader(u), nil
   706  	default:
   707  		jsonBody, err := json.MarshalIndent(body, "", "  ")
   708  
   709  		if err == nil {
   710  			return bytes.NewReader(jsonBody), nil
   711  		}
   712  
   713  		return nil, err
   714  	}
   715  }
   716  
   717  func isURL(url string) bool {
   718  	switch {
   719  	case len(url) < 10:
   720  		return false
   721  	case url[0:7] == "http://":
   722  		return true
   723  	case url[0:8] == "https://":
   724  		return true
   725  	case url[0:6] == "ftp://":
   726  		return true
   727  	}
   728  
   729  	return false
   730  }