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 }