github.com/gofiber/fiber/v2@v2.47.0/client.go (about) 1 package fiber 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "encoding/json" 7 "encoding/xml" 8 "fmt" 9 "io" 10 "mime/multipart" 11 "os" 12 "path/filepath" 13 "strconv" 14 "sync" 15 "time" 16 17 "github.com/gofiber/fiber/v2/utils" 18 19 "github.com/valyala/fasthttp" 20 ) 21 22 // Request represents HTTP request. 23 // 24 // It is forbidden copying Request instances. Create new instances 25 // and use CopyTo instead. 26 // 27 // Request instance MUST NOT be used from concurrently running goroutines. 28 // Copy from fasthttp 29 type Request = fasthttp.Request 30 31 // Response represents HTTP response. 32 // 33 // It is forbidden copying Response instances. Create new instances 34 // and use CopyTo instead. 35 // 36 // Response instance MUST NOT be used from concurrently running goroutines. 37 // Copy from fasthttp 38 type Response = fasthttp.Response 39 40 // Args represents query arguments. 41 // 42 // It is forbidden copying Args instances. Create new instances instead 43 // and use CopyTo(). 44 // 45 // Args instance MUST NOT be used from concurrently running goroutines. 46 // Copy from fasthttp 47 type Args = fasthttp.Args 48 49 // RetryIfFunc signature of retry if function 50 // Request argument passed to RetryIfFunc, if there are any request errors. 51 // Copy from fasthttp 52 type RetryIfFunc = fasthttp.RetryIfFunc 53 54 var defaultClient Client 55 56 // Client implements http client. 57 // 58 // It is safe calling Client methods from concurrently running goroutines. 59 type Client struct { 60 mutex sync.RWMutex 61 // UserAgent is used in User-Agent request header. 62 UserAgent string 63 64 // NoDefaultUserAgentHeader when set to true, causes the default 65 // User-Agent header to be excluded from the Request. 66 NoDefaultUserAgentHeader bool 67 68 // When set by an external client of Fiber it will use the provided implementation of a 69 // JSONMarshal 70 // 71 // Allowing for flexibility in using another json library for encoding 72 JSONEncoder utils.JSONMarshal 73 74 // When set by an external client of Fiber it will use the provided implementation of a 75 // JSONUnmarshal 76 // 77 // Allowing for flexibility in using another json library for decoding 78 JSONDecoder utils.JSONUnmarshal 79 } 80 81 // Get returns a agent with http method GET. 82 func Get(url string) *Agent { return defaultClient.Get(url) } 83 84 // Get returns a agent with http method GET. 85 func (c *Client) Get(url string) *Agent { 86 return c.createAgent(MethodGet, url) 87 } 88 89 // Head returns a agent with http method HEAD. 90 func Head(url string) *Agent { return defaultClient.Head(url) } 91 92 // Head returns a agent with http method GET. 93 func (c *Client) Head(url string) *Agent { 94 return c.createAgent(MethodHead, url) 95 } 96 97 // Post sends POST request to the given url. 98 func Post(url string) *Agent { return defaultClient.Post(url) } 99 100 // Post sends POST request to the given url. 101 func (c *Client) Post(url string) *Agent { 102 return c.createAgent(MethodPost, url) 103 } 104 105 // Put sends PUT request to the given url. 106 func Put(url string) *Agent { return defaultClient.Put(url) } 107 108 // Put sends PUT request to the given url. 109 func (c *Client) Put(url string) *Agent { 110 return c.createAgent(MethodPut, url) 111 } 112 113 // Patch sends PATCH request to the given url. 114 func Patch(url string) *Agent { return defaultClient.Patch(url) } 115 116 // Patch sends PATCH request to the given url. 117 func (c *Client) Patch(url string) *Agent { 118 return c.createAgent(MethodPatch, url) 119 } 120 121 // Delete sends DELETE request to the given url. 122 func Delete(url string) *Agent { return defaultClient.Delete(url) } 123 124 // Delete sends DELETE request to the given url. 125 func (c *Client) Delete(url string) *Agent { 126 return c.createAgent(MethodDelete, url) 127 } 128 129 func (c *Client) createAgent(method, url string) *Agent { 130 a := AcquireAgent() 131 a.req.Header.SetMethod(method) 132 a.req.SetRequestURI(url) 133 134 c.mutex.RLock() 135 a.Name = c.UserAgent 136 a.NoDefaultUserAgentHeader = c.NoDefaultUserAgentHeader 137 a.jsonDecoder = c.JSONDecoder 138 a.jsonEncoder = c.JSONEncoder 139 if a.jsonDecoder == nil { 140 a.jsonDecoder = json.Unmarshal 141 } 142 c.mutex.RUnlock() 143 144 if err := a.Parse(); err != nil { 145 a.errs = append(a.errs, err) 146 } 147 148 return a 149 } 150 151 // Agent is an object storing all request data for client. 152 // Agent instance MUST NOT be used from concurrently running goroutines. 153 type Agent struct { 154 // Name is used in User-Agent request header. 155 Name string 156 157 // NoDefaultUserAgentHeader when set to true, causes the default 158 // User-Agent header to be excluded from the Request. 159 NoDefaultUserAgentHeader bool 160 161 // HostClient is an embedded fasthttp HostClient 162 *fasthttp.HostClient 163 164 req *Request 165 resp *Response 166 dest []byte 167 args *Args 168 timeout time.Duration 169 errs []error 170 formFiles []*FormFile 171 debugWriter io.Writer 172 mw multipartWriter 173 jsonEncoder utils.JSONMarshal 174 jsonDecoder utils.JSONUnmarshal 175 maxRedirectsCount int 176 boundary string 177 reuse bool 178 parsed bool 179 } 180 181 // Parse initializes URI and HostClient. 182 func (a *Agent) Parse() error { 183 if a.parsed { 184 return nil 185 } 186 a.parsed = true 187 188 uri := a.req.URI() 189 190 var isTLS bool 191 scheme := uri.Scheme() 192 if bytes.Equal(scheme, []byte(schemeHTTPS)) { 193 isTLS = true 194 } else if !bytes.Equal(scheme, []byte(schemeHTTP)) { 195 return fmt.Errorf("unsupported protocol %q. http and https are supported", scheme) 196 } 197 198 name := a.Name 199 if name == "" && !a.NoDefaultUserAgentHeader { 200 name = defaultUserAgent 201 } 202 203 a.HostClient = &fasthttp.HostClient{ 204 Addr: fasthttp.AddMissingPort(string(uri.Host()), isTLS), 205 Name: name, 206 NoDefaultUserAgentHeader: a.NoDefaultUserAgentHeader, 207 IsTLS: isTLS, 208 } 209 210 return nil 211 } 212 213 /************************** Header Setting **************************/ 214 215 // Set sets the given 'key: value' header. 216 // 217 // Use Add for setting multiple header values under the same key. 218 func (a *Agent) Set(k, v string) *Agent { 219 a.req.Header.Set(k, v) 220 221 return a 222 } 223 224 // SetBytesK sets the given 'key: value' header. 225 // 226 // Use AddBytesK for setting multiple header values under the same key. 227 func (a *Agent) SetBytesK(k []byte, v string) *Agent { 228 a.req.Header.SetBytesK(k, v) 229 230 return a 231 } 232 233 // SetBytesV sets the given 'key: value' header. 234 // 235 // Use AddBytesV for setting multiple header values under the same key. 236 func (a *Agent) SetBytesV(k string, v []byte) *Agent { 237 a.req.Header.SetBytesV(k, v) 238 239 return a 240 } 241 242 // SetBytesKV sets the given 'key: value' header. 243 // 244 // Use AddBytesKV for setting multiple header values under the same key. 245 func (a *Agent) SetBytesKV(k, v []byte) *Agent { 246 a.req.Header.SetBytesKV(k, v) 247 248 return a 249 } 250 251 // Add adds the given 'key: value' header. 252 // 253 // Multiple headers with the same key may be added with this function. 254 // Use Set for setting a single header for the given key. 255 func (a *Agent) Add(k, v string) *Agent { 256 a.req.Header.Add(k, v) 257 258 return a 259 } 260 261 // AddBytesK adds the given 'key: value' header. 262 // 263 // Multiple headers with the same key may be added with this function. 264 // Use SetBytesK for setting a single header for the given key. 265 func (a *Agent) AddBytesK(k []byte, v string) *Agent { 266 a.req.Header.AddBytesK(k, v) 267 268 return a 269 } 270 271 // AddBytesV adds the given 'key: value' header. 272 // 273 // Multiple headers with the same key may be added with this function. 274 // Use SetBytesV for setting a single header for the given key. 275 func (a *Agent) AddBytesV(k string, v []byte) *Agent { 276 a.req.Header.AddBytesV(k, v) 277 278 return a 279 } 280 281 // AddBytesKV adds the given 'key: value' header. 282 // 283 // Multiple headers with the same key may be added with this function. 284 // Use SetBytesKV for setting a single header for the given key. 285 func (a *Agent) AddBytesKV(k, v []byte) *Agent { 286 a.req.Header.AddBytesKV(k, v) 287 288 return a 289 } 290 291 // ConnectionClose sets 'Connection: close' header. 292 func (a *Agent) ConnectionClose() *Agent { 293 a.req.Header.SetConnectionClose() 294 295 return a 296 } 297 298 // UserAgent sets User-Agent header value. 299 func (a *Agent) UserAgent(userAgent string) *Agent { 300 a.req.Header.SetUserAgent(userAgent) 301 302 return a 303 } 304 305 // UserAgentBytes sets User-Agent header value. 306 func (a *Agent) UserAgentBytes(userAgent []byte) *Agent { 307 a.req.Header.SetUserAgentBytes(userAgent) 308 309 return a 310 } 311 312 // Cookie sets one 'key: value' cookie. 313 func (a *Agent) Cookie(key, value string) *Agent { 314 a.req.Header.SetCookie(key, value) 315 316 return a 317 } 318 319 // CookieBytesK sets one 'key: value' cookie. 320 func (a *Agent) CookieBytesK(key []byte, value string) *Agent { 321 a.req.Header.SetCookieBytesK(key, value) 322 323 return a 324 } 325 326 // CookieBytesKV sets one 'key: value' cookie. 327 func (a *Agent) CookieBytesKV(key, value []byte) *Agent { 328 a.req.Header.SetCookieBytesKV(key, value) 329 330 return a 331 } 332 333 // Cookies sets multiple 'key: value' cookies. 334 func (a *Agent) Cookies(kv ...string) *Agent { 335 for i := 1; i < len(kv); i += 2 { 336 a.req.Header.SetCookie(kv[i-1], kv[i]) 337 } 338 339 return a 340 } 341 342 // CookiesBytesKV sets multiple 'key: value' cookies. 343 func (a *Agent) CookiesBytesKV(kv ...[]byte) *Agent { 344 for i := 1; i < len(kv); i += 2 { 345 a.req.Header.SetCookieBytesKV(kv[i-1], kv[i]) 346 } 347 348 return a 349 } 350 351 // Referer sets Referer header value. 352 func (a *Agent) Referer(referer string) *Agent { 353 a.req.Header.SetReferer(referer) 354 355 return a 356 } 357 358 // RefererBytes sets Referer header value. 359 func (a *Agent) RefererBytes(referer []byte) *Agent { 360 a.req.Header.SetRefererBytes(referer) 361 362 return a 363 } 364 365 // ContentType sets Content-Type header value. 366 func (a *Agent) ContentType(contentType string) *Agent { 367 a.req.Header.SetContentType(contentType) 368 369 return a 370 } 371 372 // ContentTypeBytes sets Content-Type header value. 373 func (a *Agent) ContentTypeBytes(contentType []byte) *Agent { 374 a.req.Header.SetContentTypeBytes(contentType) 375 376 return a 377 } 378 379 /************************** End Header Setting **************************/ 380 381 /************************** URI Setting **************************/ 382 383 // Host sets host for the uri. 384 func (a *Agent) Host(host string) *Agent { 385 a.req.URI().SetHost(host) 386 387 return a 388 } 389 390 // HostBytes sets host for the URI. 391 func (a *Agent) HostBytes(host []byte) *Agent { 392 a.req.URI().SetHostBytes(host) 393 394 return a 395 } 396 397 // QueryString sets URI query string. 398 func (a *Agent) QueryString(queryString string) *Agent { 399 a.req.URI().SetQueryString(queryString) 400 401 return a 402 } 403 404 // QueryStringBytes sets URI query string. 405 func (a *Agent) QueryStringBytes(queryString []byte) *Agent { 406 a.req.URI().SetQueryStringBytes(queryString) 407 408 return a 409 } 410 411 // BasicAuth sets URI username and password. 412 func (a *Agent) BasicAuth(username, password string) *Agent { 413 a.req.URI().SetUsername(username) 414 a.req.URI().SetPassword(password) 415 416 return a 417 } 418 419 // BasicAuthBytes sets URI username and password. 420 func (a *Agent) BasicAuthBytes(username, password []byte) *Agent { 421 a.req.URI().SetUsernameBytes(username) 422 a.req.URI().SetPasswordBytes(password) 423 424 return a 425 } 426 427 /************************** End URI Setting **************************/ 428 429 /************************** Request Setting **************************/ 430 431 // BodyString sets request body. 432 func (a *Agent) BodyString(bodyString string) *Agent { 433 a.req.SetBodyString(bodyString) 434 435 return a 436 } 437 438 // Body sets request body. 439 func (a *Agent) Body(body []byte) *Agent { 440 a.req.SetBody(body) 441 442 return a 443 } 444 445 // BodyStream sets request body stream and, optionally body size. 446 // 447 // If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes 448 // before returning io.EOF. 449 // 450 // If bodySize < 0, then bodyStream is read until io.EOF. 451 // 452 // bodyStream.Close() is called after finishing reading all body data 453 // if it implements io.Closer. 454 // 455 // Note that GET and HEAD requests cannot have body. 456 func (a *Agent) BodyStream(bodyStream io.Reader, bodySize int) *Agent { 457 a.req.SetBodyStream(bodyStream, bodySize) 458 459 return a 460 } 461 462 // JSON sends a JSON request. 463 func (a *Agent) JSON(v interface{}) *Agent { 464 if a.jsonEncoder == nil { 465 a.jsonEncoder = json.Marshal 466 } 467 468 a.req.Header.SetContentType(MIMEApplicationJSON) 469 470 if body, err := a.jsonEncoder(v); err != nil { 471 a.errs = append(a.errs, err) 472 } else { 473 a.req.SetBody(body) 474 } 475 476 return a 477 } 478 479 // XML sends an XML request. 480 func (a *Agent) XML(v interface{}) *Agent { 481 a.req.Header.SetContentType(MIMEApplicationXML) 482 483 if body, err := xml.Marshal(v); err != nil { 484 a.errs = append(a.errs, err) 485 } else { 486 a.req.SetBody(body) 487 } 488 489 return a 490 } 491 492 // Form sends form request with body if args is non-nil. 493 // 494 // It is recommended obtaining args via AcquireArgs and release it 495 // manually in performance-critical code. 496 func (a *Agent) Form(args *Args) *Agent { 497 a.req.Header.SetContentType(MIMEApplicationForm) 498 499 if args != nil { 500 a.req.SetBody(args.QueryString()) 501 } 502 503 return a 504 } 505 506 // FormFile represents multipart form file 507 type FormFile struct { 508 // Fieldname is form file's field name 509 Fieldname string 510 // Name is form file's name 511 Name string 512 // Content is form file's content 513 Content []byte 514 // autoRelease indicates if returns the object 515 // acquired via AcquireFormFile to the pool. 516 autoRelease bool 517 } 518 519 // FileData appends files for multipart form request. 520 // 521 // It is recommended obtaining formFile via AcquireFormFile and release it 522 // manually in performance-critical code. 523 func (a *Agent) FileData(formFiles ...*FormFile) *Agent { 524 a.formFiles = append(a.formFiles, formFiles...) 525 526 return a 527 } 528 529 // SendFile reads file and appends it to multipart form request. 530 func (a *Agent) SendFile(filename string, fieldname ...string) *Agent { 531 content, err := os.ReadFile(filepath.Clean(filename)) 532 if err != nil { 533 a.errs = append(a.errs, err) 534 return a 535 } 536 537 ff := AcquireFormFile() 538 if len(fieldname) > 0 && fieldname[0] != "" { 539 ff.Fieldname = fieldname[0] 540 } else { 541 ff.Fieldname = "file" + strconv.Itoa(len(a.formFiles)+1) 542 } 543 ff.Name = filepath.Base(filename) 544 ff.Content = append(ff.Content, content...) 545 ff.autoRelease = true 546 547 a.formFiles = append(a.formFiles, ff) 548 549 return a 550 } 551 552 // SendFiles reads files and appends them to multipart form request. 553 // 554 // Examples: 555 // 556 // SendFile("/path/to/file1", "fieldname1", "/path/to/file2") 557 func (a *Agent) SendFiles(filenamesAndFieldnames ...string) *Agent { 558 pairs := len(filenamesAndFieldnames) 559 if pairs&1 == 1 { 560 filenamesAndFieldnames = append(filenamesAndFieldnames, "") 561 } 562 563 for i := 0; i < pairs; i += 2 { 564 a.SendFile(filenamesAndFieldnames[i], filenamesAndFieldnames[i+1]) 565 } 566 567 return a 568 } 569 570 // Boundary sets boundary for multipart form request. 571 func (a *Agent) Boundary(boundary string) *Agent { 572 a.boundary = boundary 573 574 return a 575 } 576 577 // MultipartForm sends multipart form request with k-v and files. 578 // 579 // It is recommended obtaining args via AcquireArgs and release it 580 // manually in performance-critical code. 581 func (a *Agent) MultipartForm(args *Args) *Agent { 582 if a.mw == nil { 583 a.mw = multipart.NewWriter(a.req.BodyWriter()) 584 } 585 586 if a.boundary != "" { 587 if err := a.mw.SetBoundary(a.boundary); err != nil { 588 a.errs = append(a.errs, err) 589 return a 590 } 591 } 592 593 a.req.Header.SetMultipartFormBoundary(a.mw.Boundary()) 594 595 if args != nil { 596 args.VisitAll(func(key, value []byte) { 597 if err := a.mw.WriteField(utils.UnsafeString(key), utils.UnsafeString(value)); err != nil { 598 a.errs = append(a.errs, err) 599 } 600 }) 601 } 602 603 for _, ff := range a.formFiles { 604 w, err := a.mw.CreateFormFile(ff.Fieldname, ff.Name) 605 if err != nil { 606 a.errs = append(a.errs, err) 607 continue 608 } 609 if _, err = w.Write(ff.Content); err != nil { 610 a.errs = append(a.errs, err) 611 } 612 } 613 614 if err := a.mw.Close(); err != nil { 615 a.errs = append(a.errs, err) 616 } 617 618 return a 619 } 620 621 /************************** End Request Setting **************************/ 622 623 /************************** Agent Setting **************************/ 624 625 // Debug mode enables logging request and response detail 626 func (a *Agent) Debug(w ...io.Writer) *Agent { 627 a.debugWriter = os.Stdout 628 if len(w) > 0 { 629 a.debugWriter = w[0] 630 } 631 632 return a 633 } 634 635 // Timeout sets request timeout duration. 636 func (a *Agent) Timeout(timeout time.Duration) *Agent { 637 a.timeout = timeout 638 639 return a 640 } 641 642 // Reuse enables the Agent instance to be used again after one request. 643 // 644 // If agent is reusable, then it should be released manually when it is no 645 // longer used. 646 func (a *Agent) Reuse() *Agent { 647 a.reuse = true 648 649 return a 650 } 651 652 // InsecureSkipVerify controls whether the Agent verifies the server 653 // certificate chain and host name. 654 func (a *Agent) InsecureSkipVerify() *Agent { 655 if a.HostClient.TLSConfig == nil { 656 a.HostClient.TLSConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // We explicitly let the user set insecure mode here 657 } else { 658 a.HostClient.TLSConfig.InsecureSkipVerify = true 659 } 660 661 return a 662 } 663 664 // TLSConfig sets tls config. 665 func (a *Agent) TLSConfig(config *tls.Config) *Agent { 666 a.HostClient.TLSConfig = config 667 668 return a 669 } 670 671 // MaxRedirectsCount sets max redirect count for GET and HEAD. 672 func (a *Agent) MaxRedirectsCount(count int) *Agent { 673 a.maxRedirectsCount = count 674 675 return a 676 } 677 678 // JSONEncoder sets custom json encoder. 679 func (a *Agent) JSONEncoder(jsonEncoder utils.JSONMarshal) *Agent { 680 a.jsonEncoder = jsonEncoder 681 682 return a 683 } 684 685 // JSONDecoder sets custom json decoder. 686 func (a *Agent) JSONDecoder(jsonDecoder utils.JSONUnmarshal) *Agent { 687 a.jsonDecoder = jsonDecoder 688 689 return a 690 } 691 692 // Request returns Agent request instance. 693 func (a *Agent) Request() *Request { 694 return a.req 695 } 696 697 // SetResponse sets custom response for the Agent instance. 698 // 699 // It is recommended obtaining custom response via AcquireResponse and release it 700 // manually in performance-critical code. 701 func (a *Agent) SetResponse(customResp *Response) *Agent { 702 a.resp = customResp 703 704 return a 705 } 706 707 // Dest sets custom dest. 708 // 709 // The contents of dest will be replaced by the response body, if the dest 710 // is too small a new slice will be allocated. 711 func (a *Agent) Dest(dest []byte) *Agent { 712 a.dest = dest 713 714 return a 715 } 716 717 // RetryIf controls whether a retry should be attempted after an error. 718 // 719 // By default, will use isIdempotent function from fasthttp 720 func (a *Agent) RetryIf(retryIf RetryIfFunc) *Agent { 721 a.HostClient.RetryIf = retryIf 722 return a 723 } 724 725 /************************** End Agent Setting **************************/ 726 727 // Bytes returns the status code, bytes body and errors of url. 728 // 729 // it's not safe to use Agent after calling [Agent.Bytes] 730 func (a *Agent) Bytes() (int, []byte, []error) { 731 defer a.release() 732 return a.bytes() 733 } 734 735 func (a *Agent) bytes() (code int, body []byte, errs []error) { //nolint:nonamedreturns,revive // We want to overwrite the body in a deferred func. TODO: Check if we really need to do this. We eventually want to get rid of all named returns. 736 if errs = append(errs, a.errs...); len(errs) > 0 { 737 return code, body, errs 738 } 739 740 var ( 741 req = a.req 742 resp *Response 743 nilResp bool 744 ) 745 746 if a.resp == nil { 747 resp = AcquireResponse() 748 nilResp = true 749 } else { 750 resp = a.resp 751 } 752 753 defer func() { 754 if a.debugWriter != nil { 755 printDebugInfo(req, resp, a.debugWriter) 756 } 757 758 if len(errs) == 0 { 759 code = resp.StatusCode() 760 } 761 762 body = append(a.dest, resp.Body()...) //nolint:gocritic // We want to append to the returned slice here 763 764 if nilResp { 765 ReleaseResponse(resp) 766 } 767 }() 768 769 if a.timeout > 0 { 770 if err := a.HostClient.DoTimeout(req, resp, a.timeout); err != nil { 771 errs = append(errs, err) 772 return code, body, errs 773 } 774 } else if a.maxRedirectsCount > 0 && (string(req.Header.Method()) == MethodGet || string(req.Header.Method()) == MethodHead) { 775 if err := a.HostClient.DoRedirects(req, resp, a.maxRedirectsCount); err != nil { 776 errs = append(errs, err) 777 return code, body, errs 778 } 779 } else if err := a.HostClient.Do(req, resp); err != nil { 780 errs = append(errs, err) 781 } 782 783 return code, body, errs 784 } 785 786 func printDebugInfo(req *Request, resp *Response, w io.Writer) { 787 msg := fmt.Sprintf("Connected to %s(%s)\r\n\r\n", req.URI().Host(), resp.RemoteAddr()) 788 _, _ = w.Write(utils.UnsafeBytes(msg)) //nolint:errcheck // This will never fail 789 _, _ = req.WriteTo(w) //nolint:errcheck // This will never fail 790 _, _ = resp.WriteTo(w) //nolint:errcheck // This will never fail 791 } 792 793 // String returns the status code, string body and errors of url. 794 // 795 // it's not safe to use Agent after calling [Agent.String] 796 func (a *Agent) String() (int, string, []error) { 797 defer a.release() 798 code, body, errs := a.bytes() 799 // TODO: There might be a data race here on body. Maybe use utils.CopyBytes on it? 800 801 return code, utils.UnsafeString(body), errs 802 } 803 804 // Struct returns the status code, bytes body and errors of url. 805 // And bytes body will be unmarshalled to given v. 806 // 807 // it's not safe to use Agent after calling [Agent.Struct] 808 func (a *Agent) Struct(v interface{}) (int, []byte, []error) { 809 defer a.release() 810 811 code, body, errs := a.bytes() 812 if len(errs) > 0 { 813 return code, body, errs 814 } 815 816 // TODO: This should only be done once 817 if a.jsonDecoder == nil { 818 a.jsonDecoder = json.Unmarshal 819 } 820 821 if err := a.jsonDecoder(body, v); err != nil { 822 errs = append(errs, err) 823 } 824 825 return code, body, errs 826 } 827 828 func (a *Agent) release() { 829 if !a.reuse { 830 ReleaseAgent(a) 831 } else { 832 a.errs = a.errs[:0] 833 } 834 } 835 836 func (a *Agent) reset() { 837 a.HostClient = nil 838 a.req.Reset() 839 a.resp = nil 840 a.dest = nil 841 a.timeout = 0 842 a.args = nil 843 a.errs = a.errs[:0] 844 a.debugWriter = nil 845 a.mw = nil 846 a.reuse = false 847 a.parsed = false 848 a.maxRedirectsCount = 0 849 a.boundary = "" 850 a.Name = "" 851 a.NoDefaultUserAgentHeader = false 852 for i, ff := range a.formFiles { 853 if ff.autoRelease { 854 ReleaseFormFile(ff) 855 } 856 a.formFiles[i] = nil 857 } 858 a.formFiles = a.formFiles[:0] 859 } 860 861 var ( 862 clientPool sync.Pool 863 agentPool = sync.Pool{ 864 New: func() interface{} { 865 return &Agent{req: &Request{}} 866 }, 867 } 868 responsePool sync.Pool 869 argsPool sync.Pool 870 formFilePool sync.Pool 871 ) 872 873 // AcquireClient returns an empty Client instance from client pool. 874 // 875 // The returned Client instance may be passed to ReleaseClient when it is 876 // no longer needed. This allows Client recycling, reduces GC pressure 877 // and usually improves performance. 878 func AcquireClient() *Client { 879 v := clientPool.Get() 880 if v == nil { 881 return &Client{} 882 } 883 c, ok := v.(*Client) 884 if !ok { 885 panic(fmt.Errorf("failed to type-assert to *Client")) 886 } 887 return c 888 } 889 890 // ReleaseClient returns c acquired via AcquireClient to client pool. 891 // 892 // It is forbidden accessing req and/or its' members after returning 893 // it to client pool. 894 func ReleaseClient(c *Client) { 895 c.UserAgent = "" 896 c.NoDefaultUserAgentHeader = false 897 c.JSONEncoder = nil 898 c.JSONDecoder = nil 899 900 clientPool.Put(c) 901 } 902 903 // AcquireAgent returns an empty Agent instance from Agent pool. 904 // 905 // The returned Agent instance may be passed to ReleaseAgent when it is 906 // no longer needed. This allows Agent recycling, reduces GC pressure 907 // and usually improves performance. 908 func AcquireAgent() *Agent { 909 a, ok := agentPool.Get().(*Agent) 910 if !ok { 911 panic(fmt.Errorf("failed to type-assert to *Agent")) 912 } 913 return a 914 } 915 916 // ReleaseAgent returns a acquired via AcquireAgent to Agent pool. 917 // 918 // It is forbidden accessing req and/or its' members after returning 919 // it to Agent pool. 920 func ReleaseAgent(a *Agent) { 921 a.reset() 922 agentPool.Put(a) 923 } 924 925 // AcquireResponse returns an empty Response instance from response pool. 926 // 927 // The returned Response instance may be passed to ReleaseResponse when it is 928 // no longer needed. This allows Response recycling, reduces GC pressure 929 // and usually improves performance. 930 // Copy from fasthttp 931 func AcquireResponse() *Response { 932 v := responsePool.Get() 933 if v == nil { 934 return &Response{} 935 } 936 r, ok := v.(*Response) 937 if !ok { 938 panic(fmt.Errorf("failed to type-assert to *Response")) 939 } 940 return r 941 } 942 943 // ReleaseResponse return resp acquired via AcquireResponse to response pool. 944 // 945 // It is forbidden accessing resp and/or its' members after returning 946 // it to response pool. 947 // Copy from fasthttp 948 func ReleaseResponse(resp *Response) { 949 resp.Reset() 950 responsePool.Put(resp) 951 } 952 953 // AcquireArgs returns an empty Args object from the pool. 954 // 955 // The returned Args may be returned to the pool with ReleaseArgs 956 // when no longer needed. This allows reducing GC load. 957 // Copy from fasthttp 958 func AcquireArgs() *Args { 959 v := argsPool.Get() 960 if v == nil { 961 return &Args{} 962 } 963 a, ok := v.(*Args) 964 if !ok { 965 panic(fmt.Errorf("failed to type-assert to *Args")) 966 } 967 return a 968 } 969 970 // ReleaseArgs returns the object acquired via AcquireArgs to the pool. 971 // 972 // String not access the released Args object, otherwise data races may occur. 973 // Copy from fasthttp 974 func ReleaseArgs(a *Args) { 975 a.Reset() 976 argsPool.Put(a) 977 } 978 979 // AcquireFormFile returns an empty FormFile object from the pool. 980 // 981 // The returned FormFile may be returned to the pool with ReleaseFormFile 982 // when no longer needed. This allows reducing GC load. 983 func AcquireFormFile() *FormFile { 984 v := formFilePool.Get() 985 if v == nil { 986 return &FormFile{} 987 } 988 ff, ok := v.(*FormFile) 989 if !ok { 990 panic(fmt.Errorf("failed to type-assert to *FormFile")) 991 } 992 return ff 993 } 994 995 // ReleaseFormFile returns the object acquired via AcquireFormFile to the pool. 996 // 997 // String not access the released FormFile object, otherwise data races may occur. 998 func ReleaseFormFile(ff *FormFile) { 999 ff.Fieldname = "" 1000 ff.Name = "" 1001 ff.Content = ff.Content[:0] 1002 ff.autoRelease = false 1003 1004 formFilePool.Put(ff) 1005 } 1006 1007 const ( 1008 defaultUserAgent = "fiber" 1009 ) 1010 1011 type multipartWriter interface { 1012 Boundary() string 1013 SetBoundary(boundary string) error 1014 CreateFormFile(fieldname, filename string) (io.Writer, error) 1015 WriteField(fieldname, value string) error 1016 Close() error 1017 }