Merge branch 'main' into main

pull/3105/head
Juan Calderon-Perez 2024-12-16 08:29:04 -05:00 committed by GitHub
commit 8b85d1947d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1842 additions and 1309 deletions

View File

@ -27,7 +27,7 @@ coverage:
format: format:
go run mvdan.cc/gofumpt@latest -w -l . go run mvdan.cc/gofumpt@latest -w -l .
## markdown: 🎨 Find markdown format issues (Requires markdownlint-cli) ## markdown: 🎨 Find markdown format issues (Requires markdownlint-cli2)
.PHONY: markdown .PHONY: markdown
markdown: markdown:
markdownlint-cli2 "**/*.md" "#vendor" markdownlint-cli2 "**/*.md" "#vendor"

View File

@ -24,16 +24,13 @@ import (
var ErrFailedToAppendCert = errors.New("failed to append certificate") var ErrFailedToAppendCert = errors.New("failed to append certificate")
// The Client is used to create a Fiber Client with // Client is used to create a Fiber client with client-level settings that
// client-level settings that apply to all requests // apply to all requests made by the client.
// raise from the client.
// //
// Fiber Client also provides an option to override // The Fiber client also provides an option to override or merge most of the
// or merge most of the client settings at the request. // client settings at the request level.
type Client struct { type Client struct {
// logger
logger log.CommonLogger logger log.CommonLogger
fasthttp *fasthttp.Client fasthttp *fasthttp.Client
header *Header header *Header
@ -49,44 +46,31 @@ type Client struct {
cborUnmarshal utils.CBORUnmarshal cborUnmarshal utils.CBORUnmarshal
cookieJar *CookieJar cookieJar *CookieJar
// retry
retryConfig *RetryConfig retryConfig *RetryConfig
baseURL string baseURL string
userAgent string userAgent string
referer string referer string
// user defined request hooks
userRequestHooks []RequestHook userRequestHooks []RequestHook
// client package defined request hooks
builtinRequestHooks []RequestHook builtinRequestHooks []RequestHook
// user defined response hooks
userResponseHooks []ResponseHook userResponseHooks []ResponseHook
// client package defined response hooks
builtinResponseHooks []ResponseHook builtinResponseHooks []ResponseHook
timeout time.Duration timeout time.Duration
mu sync.RWMutex mu sync.RWMutex
debug bool debug bool
} }
// R raise a request from the client. // R creates a new Request associated with the client.
func (c *Client) R() *Request { func (c *Client) R() *Request {
return AcquireRequest().SetClient(c) return AcquireRequest().SetClient(c)
} }
// RequestHook Request returns user-defined request hooks. // RequestHook returns the user-defined request hooks.
func (c *Client) RequestHook() []RequestHook { func (c *Client) RequestHook() []RequestHook {
return c.userRequestHooks return c.userRequestHooks
} }
// AddRequestHook Add user-defined request hooks. // AddRequestHook adds user-defined request hooks.
func (c *Client) AddRequestHook(h ...RequestHook) *Client { func (c *Client) AddRequestHook(h ...RequestHook) *Client {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -95,12 +79,12 @@ func (c *Client) AddRequestHook(h ...RequestHook) *Client {
return c return c
} }
// ResponseHook return user-define response hooks. // ResponseHook returns the user-defined response hooks.
func (c *Client) ResponseHook() []ResponseHook { func (c *Client) ResponseHook() []ResponseHook {
return c.userResponseHooks return c.userResponseHooks
} }
// AddResponseHook Add user-defined response hooks. // AddResponseHook adds user-defined response hooks.
func (c *Client) AddResponseHook(h ...ResponseHook) *Client { func (c *Client) AddResponseHook(h ...ResponseHook) *Client {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -109,74 +93,74 @@ func (c *Client) AddResponseHook(h ...ResponseHook) *Client {
return c return c
} }
// JSONMarshal returns json marshal function in Core. // JSONMarshal returns the JSON marshal function used by the client.
func (c *Client) JSONMarshal() utils.JSONMarshal { func (c *Client) JSONMarshal() utils.JSONMarshal {
return c.jsonMarshal return c.jsonMarshal
} }
// SetJSONMarshal sets the JSON encoder. // SetJSONMarshal sets the JSON marshal function to use.
func (c *Client) SetJSONMarshal(f utils.JSONMarshal) *Client { func (c *Client) SetJSONMarshal(f utils.JSONMarshal) *Client {
c.jsonMarshal = f c.jsonMarshal = f
return c return c
} }
// JSONUnmarshal returns json unmarshal function in Core. // JSONUnmarshal returns the JSON unmarshal function used by the client.
func (c *Client) JSONUnmarshal() utils.JSONUnmarshal { func (c *Client) JSONUnmarshal() utils.JSONUnmarshal {
return c.jsonUnmarshal return c.jsonUnmarshal
} }
// Set json decoder. // SetJSONUnmarshal sets the JSON unmarshal function to use.
func (c *Client) SetJSONUnmarshal(f utils.JSONUnmarshal) *Client { func (c *Client) SetJSONUnmarshal(f utils.JSONUnmarshal) *Client {
c.jsonUnmarshal = f c.jsonUnmarshal = f
return c return c
} }
// XMLMarshal returns xml marshal function in Core. // XMLMarshal returns the XML marshal function used by the client.
func (c *Client) XMLMarshal() utils.XMLMarshal { func (c *Client) XMLMarshal() utils.XMLMarshal {
return c.xmlMarshal return c.xmlMarshal
} }
// SetXMLMarshal Set xml encoder. // SetXMLMarshal sets the XML marshal function to use.
func (c *Client) SetXMLMarshal(f utils.XMLMarshal) *Client { func (c *Client) SetXMLMarshal(f utils.XMLMarshal) *Client {
c.xmlMarshal = f c.xmlMarshal = f
return c return c
} }
// XMLUnmarshal returns xml unmarshal function in Core. // XMLUnmarshal returns the XML unmarshal function used by the client.
func (c *Client) XMLUnmarshal() utils.XMLUnmarshal { func (c *Client) XMLUnmarshal() utils.XMLUnmarshal {
return c.xmlUnmarshal return c.xmlUnmarshal
} }
// SetXMLUnmarshal Set xml decoder. // SetXMLUnmarshal sets the XML unmarshal function to use.
func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client { func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client {
c.xmlUnmarshal = f c.xmlUnmarshal = f
return c return c
} }
// CBORMarshal returns CBOR marshal function in Core. // CBORMarshal returns the CBOR marshal function used by the client.
func (c *Client) CBORMarshal() utils.CBORMarshal { func (c *Client) CBORMarshal() utils.CBORMarshal {
return c.cborMarshal return c.cborMarshal
} }
// SetCBORMarshal sets CBOR encoder. // SetCBORMarshal sets the CBOR marshal function to use.
func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client { func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client {
c.cborMarshal = f c.cborMarshal = f
return c return c
} }
// CBORUnmarshal returns CBOR unmarshal function in Core. // CBORUnmarshal returns the CBOR unmarshal function used by the client.
func (c *Client) CBORUnmarshal() utils.CBORUnmarshal { func (c *Client) CBORUnmarshal() utils.CBORUnmarshal {
return c.cborUnmarshal return c.cborUnmarshal
} }
// SetCBORUnmarshal sets CBOR decoder. // SetCBORUnmarshal sets the CBOR unmarshal function to use.
func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client { func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client {
c.cborUnmarshal = f c.cborUnmarshal = f
return c return c
} }
// TLSConfig returns tlsConfig in client. // TLSConfig returns the client's TLS configuration.
// If client don't have tlsConfig, this function will init it. // If none is set, it initializes a new one.
func (c *Client) TLSConfig() *tls.Config { func (c *Client) TLSConfig() *tls.Config {
if c.fasthttp.TLSConfig == nil { if c.fasthttp.TLSConfig == nil {
c.fasthttp.TLSConfig = &tls.Config{ c.fasthttp.TLSConfig = &tls.Config{
@ -187,20 +171,20 @@ func (c *Client) TLSConfig() *tls.Config {
return c.fasthttp.TLSConfig return c.fasthttp.TLSConfig
} }
// SetTLSConfig sets tlsConfig in client. // SetTLSConfig sets the TLS configuration for the client.
func (c *Client) SetTLSConfig(config *tls.Config) *Client { func (c *Client) SetTLSConfig(config *tls.Config) *Client {
c.fasthttp.TLSConfig = config c.fasthttp.TLSConfig = config
return c return c
} }
// SetCertificates method sets client certificates into client. // SetCertificates adds certificates to the client's TLS configuration.
func (c *Client) SetCertificates(certs ...tls.Certificate) *Client { func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
config := c.TLSConfig() config := c.TLSConfig()
config.Certificates = append(config.Certificates, certs...) config.Certificates = append(config.Certificates, certs...)
return c return c
} }
// SetRootCertificate adds one or more root certificates into client. // SetRootCertificate adds one or more root certificates to the client's TLS configuration.
func (c *Client) SetRootCertificate(path string) *Client { func (c *Client) SetRootCertificate(path string) *Client {
cleanPath := filepath.Clean(path) cleanPath := filepath.Clean(path)
file, err := os.Open(cleanPath) file, err := os.Open(cleanPath)
@ -230,7 +214,7 @@ func (c *Client) SetRootCertificate(path string) *Client {
return c return c
} }
// SetRootCertificateFromString method adds one or more root certificates into client. // SetRootCertificateFromString adds one or more root certificates from a string to the client's TLS configuration.
func (c *Client) SetRootCertificateFromString(pem string) *Client { func (c *Client) SetRootCertificateFromString(pem string) *Client {
config := c.TLSConfig() config := c.TLSConfig()
@ -245,19 +229,18 @@ func (c *Client) SetRootCertificateFromString(pem string) *Client {
return c return c
} }
// SetProxyURL sets proxy url in client. It will apply via core to hostclient. // SetProxyURL sets the proxy URL for the client. This affects all subsequent requests.
func (c *Client) SetProxyURL(proxyURL string) error { func (c *Client) SetProxyURL(proxyURL string) error {
c.fasthttp.Dial = fasthttpproxy.FasthttpHTTPDialer(proxyURL) c.fasthttp.Dial = fasthttpproxy.FasthttpHTTPDialer(proxyURL)
return nil return nil
} }
// RetryConfig returns retry config in client. // RetryConfig returns the current retry configuration.
func (c *Client) RetryConfig() *RetryConfig { func (c *Client) RetryConfig() *RetryConfig {
return c.retryConfig return c.retryConfig
} }
// SetRetryConfig sets retry config in client which is impl by addon/retry package. // SetRetryConfig sets the retry configuration for the client.
func (c *Client) SetRetryConfig(config *RetryConfig) *Client { func (c *Client) SetRetryConfig(config *RetryConfig) *Client {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -266,58 +249,49 @@ func (c *Client) SetRetryConfig(config *RetryConfig) *Client {
return c return c
} }
// BaseURL returns baseurl in Client instance. // BaseURL returns the client's base URL.
func (c *Client) BaseURL() string { func (c *Client) BaseURL() string {
return c.baseURL return c.baseURL
} }
// SetBaseURL Set baseUrl which is prefix of real url. // SetBaseURL sets the base URL prefix for all requests made by the client.
func (c *Client) SetBaseURL(url string) *Client { func (c *Client) SetBaseURL(url string) *Client {
c.baseURL = url c.baseURL = url
return c return c
} }
// Header method returns header value via key, // Header returns all header values associated with the provided key.
// this method will visit all field in the header,
// then sort them.
func (c *Client) Header(key string) []string { func (c *Client) Header(key string) []string {
return c.header.PeekMultiple(key) return c.header.PeekMultiple(key)
} }
// AddHeader method adds a single header field and its value in the client instance. // AddHeader adds a single header field and its value to the client. These headers apply to all requests.
// These headers will be applied to all requests raised from this client instance.
// Also, it can be overridden at request level header options.
func (c *Client) AddHeader(key, val string) *Client { func (c *Client) AddHeader(key, val string) *Client {
c.header.Add(key, val) c.header.Add(key, val)
return c return c
} }
// SetHeader method sets a single header field and its value in the client instance. // SetHeader sets a single header field and its value in the client.
// These headers will be applied to all requests raised from this client instance.
// Also, it can be overridden at request level header options.
func (c *Client) SetHeader(key, val string) *Client { func (c *Client) SetHeader(key, val string) *Client {
c.header.Set(key, val) c.header.Set(key, val)
return c return c
} }
// AddHeaders method adds multiple headers field and its values at one go in the client instance. // AddHeaders adds multiple header fields and their values to the client.
// These headers will be applied to all requests raised from this client instance. Also it can be
// overridden at request level headers options.
func (c *Client) AddHeaders(h map[string][]string) *Client { func (c *Client) AddHeaders(h map[string][]string) *Client {
c.header.AddHeaders(h) c.header.AddHeaders(h)
return c return c
} }
// SetHeaders method sets multiple headers field and its values at one go in the client instance. // SetHeaders method sets multiple headers field and its values at one go in the client instance.
// These headers will be applied to all requests raised from this client instance. Also it can be // These headers will be applied to all requests created from this client instance. Also it can be
// overridden at request level headers options. // overridden at request level headers options.
func (c *Client) SetHeaders(h map[string]string) *Client { func (c *Client) SetHeaders(h map[string]string) *Client {
c.header.SetHeaders(h) c.header.SetHeaders(h)
return c return c
} }
// Param method returns params value via key, // Param returns all values of the specified query parameter.
// this method will visit all field in the query param.
func (c *Client) Param(key string) []string { func (c *Client) Param(key string) []string {
res := []string{} res := []string{}
tmp := c.params.PeekMulti(key) tmp := c.params.PeekMulti(key)
@ -328,47 +302,38 @@ func (c *Client) Param(key string) []string {
return res return res
} }
// AddParam method adds a single query param field and its value in the client instance. // AddParam adds a single query parameter and its value to the client.
// These params will be applied to all requests raised from this client instance. // These params will be applied to all requests created from this client instance.
// Also, it can be overridden at request level param options.
func (c *Client) AddParam(key, val string) *Client { func (c *Client) AddParam(key, val string) *Client {
c.params.Add(key, val) c.params.Add(key, val)
return c return c
} }
// SetParam method sets a single query param field and its value in the client instance. // SetParam sets a single query parameter and its value in the client.
// These params will be applied to all requests raised from this client instance.
// Also, it can be overridden at request level param options.
func (c *Client) SetParam(key, val string) *Client { func (c *Client) SetParam(key, val string) *Client {
c.params.Set(key, val) c.params.Set(key, val)
return c return c
} }
// AddParams method adds multiple query params field and its values at one go in the client instance. // AddParams adds multiple query parameters and their values to the client.
// These params will be applied to all requests raised from this client instance. Also it can be
// overridden at request level params options.
func (c *Client) AddParams(m map[string][]string) *Client { func (c *Client) AddParams(m map[string][]string) *Client {
c.params.AddParams(m) c.params.AddParams(m)
return c return c
} }
// SetParams method sets multiple params field and its values at one go in the client instance. // SetParams sets multiple query parameters and their values in the client.
// These params will be applied to all requests raised from this client instance. Also it can be
// overridden at request level params options.
func (c *Client) SetParams(m map[string]string) *Client { func (c *Client) SetParams(m map[string]string) *Client {
c.params.SetParams(m) c.params.SetParams(m)
return c return c
} }
// SetParamsWithStruct method sets multiple params field and its values at one go in the client instance. // SetParamsWithStruct sets multiple query parameters and their values using a struct.
// These params will be applied to all requests raised from this client instance. Also it can be
// overridden at request level params options.
func (c *Client) SetParamsWithStruct(v any) *Client { func (c *Client) SetParamsWithStruct(v any) *Client {
c.params.SetParamsWithStruct(v) c.params.SetParamsWithStruct(v)
return c return c
} }
// DelParams method deletes single or multiple params field and its values in client. // DelParams deletes one or more query parameters and their values from the client.
func (c *Client) DelParams(key ...string) *Client { func (c *Client) DelParams(key ...string) *Client {
for _, v := range key { for _, v := range key {
c.params.Del(v) c.params.Del(v)
@ -376,64 +341,51 @@ func (c *Client) DelParams(key ...string) *Client {
return c return c
} }
// SetUserAgent method sets userAgent field and its value in the client instance. // SetUserAgent sets the User-Agent header for the client.
// This ua will be applied to all requests raised from this client instance.
// Also it can be overridden at request level ua options.
func (c *Client) SetUserAgent(ua string) *Client { func (c *Client) SetUserAgent(ua string) *Client {
c.userAgent = ua c.userAgent = ua
return c return c
} }
// SetReferer method sets referer field and its value in the client instance. // SetReferer sets the Referer header for the client.
// This referer will be applied to all requests raised from this client instance.
// Also it can be overridden at request level referer options.
func (c *Client) SetReferer(r string) *Client { func (c *Client) SetReferer(r string) *Client {
c.referer = r c.referer = r
return c return c
} }
// PathParam returns the path param be set in request instance. // PathParam returns the value of the specified path parameter. Returns an empty string if it does not exist.
// if path param doesn't exist, return empty string.
func (c *Client) PathParam(key string) string { func (c *Client) PathParam(key string) string {
if val, ok := (*c.path)[key]; ok { if val, ok := (*c.path)[key]; ok {
return val return val
} }
return "" return ""
} }
// SetPathParam method sets a single path param field and its value in the client instance. // SetPathParam sets a single path parameter and its value in the client.
// These path params will be applied to all requests raised from this client instance.
// Also it can be overridden at request level path params options.
func (c *Client) SetPathParam(key, val string) *Client { func (c *Client) SetPathParam(key, val string) *Client {
c.path.SetParam(key, val) c.path.SetParam(key, val)
return c return c
} }
// SetPathParams method sets multiple path params field and its values at one go in the client instance. // SetPathParams sets multiple path parameters and their values in the client.
// These path params will be applied to all requests raised from this client instance. Also it can be
// overridden at request level path params options.
func (c *Client) SetPathParams(m map[string]string) *Client { func (c *Client) SetPathParams(m map[string]string) *Client {
c.path.SetParams(m) c.path.SetParams(m)
return c return c
} }
// SetPathParamsWithStruct method sets multiple path params field and its values at one go in the client instance. // SetPathParamsWithStruct sets multiple path parameters and their values using a struct.
// These path params will be applied to all requests raised from this client instance. Also it can be
// overridden at request level path params options.
func (c *Client) SetPathParamsWithStruct(v any) *Client { func (c *Client) SetPathParamsWithStruct(v any) *Client {
c.path.SetParamsWithStruct(v) c.path.SetParamsWithStruct(v)
return c return c
} }
// DelPathParams method deletes single or multiple path params field and its values in client. // DelPathParams deletes one or more path parameters and their values from the client.
func (c *Client) DelPathParams(key ...string) *Client { func (c *Client) DelPathParams(key ...string) *Client {
c.path.DelParams(key...) c.path.DelParams(key...)
return c return c
} }
// Cookie returns the cookie be set in request instance. // Cookie returns the value of the specified cookie. Returns an empty string if it does not exist.
// if cookie doesn't exist, return empty string.
func (c *Client) Cookie(key string) string { func (c *Client) Cookie(key string) string {
if val, ok := (*c.cookies)[key]; ok { if val, ok := (*c.cookies)[key]; ok {
return val return val
@ -441,127 +393,111 @@ func (c *Client) Cookie(key string) string {
return "" return ""
} }
// SetCookie method sets a single cookie field and its value in the client instance. // SetCookie sets a single cookie and its value in the client.
// These cookies will be applied to all requests raised from this client instance.
// Also it can be overridden at request level cookie options.
func (c *Client) SetCookie(key, val string) *Client { func (c *Client) SetCookie(key, val string) *Client {
c.cookies.SetCookie(key, val) c.cookies.SetCookie(key, val)
return c return c
} }
// SetCookies method sets multiple cookies field and its values at one go in the client instance. // SetCookies sets multiple cookies and their values in the client.
// These cookies will be applied to all requests raised from this client instance. Also it can be
// overridden at request level cookie options.
func (c *Client) SetCookies(m map[string]string) *Client { func (c *Client) SetCookies(m map[string]string) *Client {
c.cookies.SetCookies(m) c.cookies.SetCookies(m)
return c return c
} }
// SetCookiesWithStruct method sets multiple cookies field and its values at one go in the client instance. // SetCookiesWithStruct sets multiple cookies and their values using a struct.
// These cookies will be applied to all requests raised from this client instance. Also it can be
// overridden at request level cookies options.
func (c *Client) SetCookiesWithStruct(v any) *Client { func (c *Client) SetCookiesWithStruct(v any) *Client {
c.cookies.SetCookiesWithStruct(v) c.cookies.SetCookiesWithStruct(v)
return c return c
} }
// DelCookies method deletes single or multiple cookies field and its values in client. // DelCookies deletes one or more cookies and their values from the client.
func (c *Client) DelCookies(key ...string) *Client { func (c *Client) DelCookies(key ...string) *Client {
c.cookies.DelCookies(key...) c.cookies.DelCookies(key...)
return c return c
} }
// SetTimeout method sets timeout val in client instance. // SetTimeout sets the timeout value for the client. This applies to all requests unless overridden at the request level.
// This value will be applied to all requests raised from this client instance.
// Also, it can be overridden at request level timeout options.
func (c *Client) SetTimeout(t time.Duration) *Client { func (c *Client) SetTimeout(t time.Duration) *Client {
c.timeout = t c.timeout = t
return c return c
} }
// Debug enable log debug level output. // Debug enables debug-level logging output.
func (c *Client) Debug() *Client { func (c *Client) Debug() *Client {
c.debug = true c.debug = true
return c return c
} }
// DisableDebug disables log debug level output. // DisableDebug disables debug-level logging output.
func (c *Client) DisableDebug() *Client { func (c *Client) DisableDebug() *Client {
c.debug = false c.debug = false
return c return c
} }
// SetCookieJar sets cookie jar in client instance. // SetCookieJar sets the cookie jar for the client.
func (c *Client) SetCookieJar(cookieJar *CookieJar) *Client { func (c *Client) SetCookieJar(cookieJar *CookieJar) *Client {
c.cookieJar = cookieJar c.cookieJar = cookieJar
return c return c
} }
// Get provide an API like axios which send get request. // Get sends a GET request to the specified URL, similar to axios.
func (c *Client) Get(url string, cfg ...Config) (*Response, error) { func (c *Client) Get(url string, cfg ...Config) (*Response, error) {
req := AcquireRequest().SetClient(c) req := AcquireRequest().SetClient(c)
setConfigToRequest(req, cfg...) setConfigToRequest(req, cfg...)
return req.Get(url) return req.Get(url)
} }
// Post provide an API like axios which send post request. // Post sends a POST request to the specified URL, similar to axios.
func (c *Client) Post(url string, cfg ...Config) (*Response, error) { func (c *Client) Post(url string, cfg ...Config) (*Response, error) {
req := AcquireRequest().SetClient(c) req := AcquireRequest().SetClient(c)
setConfigToRequest(req, cfg...) setConfigToRequest(req, cfg...)
return req.Post(url) return req.Post(url)
} }
// Head provide a API like axios which send head request. // Head sends a HEAD request to the specified URL, similar to axios.
func (c *Client) Head(url string, cfg ...Config) (*Response, error) { func (c *Client) Head(url string, cfg ...Config) (*Response, error) {
req := AcquireRequest().SetClient(c) req := AcquireRequest().SetClient(c)
setConfigToRequest(req, cfg...) setConfigToRequest(req, cfg...)
return req.Head(url) return req.Head(url)
} }
// Put provide an API like axios which send put request. // Put sends a PUT request to the specified URL, similar to axios.
func (c *Client) Put(url string, cfg ...Config) (*Response, error) { func (c *Client) Put(url string, cfg ...Config) (*Response, error) {
req := AcquireRequest().SetClient(c) req := AcquireRequest().SetClient(c)
setConfigToRequest(req, cfg...) setConfigToRequest(req, cfg...)
return req.Put(url) return req.Put(url)
} }
// Delete provide an API like axios which send delete request. // Delete sends a DELETE request to the specified URL, similar to axios.
func (c *Client) Delete(url string, cfg ...Config) (*Response, error) { func (c *Client) Delete(url string, cfg ...Config) (*Response, error) {
req := AcquireRequest().SetClient(c) req := AcquireRequest().SetClient(c)
setConfigToRequest(req, cfg...) setConfigToRequest(req, cfg...)
return req.Delete(url) return req.Delete(url)
} }
// Options provide an API like axios which send options request. // Options sends an OPTIONS request to the specified URL, similar to axios.
func (c *Client) Options(url string, cfg ...Config) (*Response, error) { func (c *Client) Options(url string, cfg ...Config) (*Response, error) {
req := AcquireRequest().SetClient(c) req := AcquireRequest().SetClient(c)
setConfigToRequest(req, cfg...) setConfigToRequest(req, cfg...)
return req.Options(url) return req.Options(url)
} }
// Patch provide an API like axios which send patch request. // Patch sends a PATCH request to the specified URL, similar to axios.
func (c *Client) Patch(url string, cfg ...Config) (*Response, error) { func (c *Client) Patch(url string, cfg ...Config) (*Response, error) {
req := AcquireRequest().SetClient(c) req := AcquireRequest().SetClient(c)
setConfigToRequest(req, cfg...) setConfigToRequest(req, cfg...)
return req.Patch(url) return req.Patch(url)
} }
// Custom provide an API like axios which send custom request. // Custom sends a request with a custom method to the specified URL, similar to axios.
func (c *Client) Custom(url, method string, cfg ...Config) (*Response, error) { func (c *Client) Custom(url, method string, cfg ...Config) (*Response, error) {
req := AcquireRequest().SetClient(c) req := AcquireRequest().SetClient(c)
setConfigToRequest(req, cfg...) setConfigToRequest(req, cfg...)
return req.Custom(url, method) return req.Custom(url, method)
} }
// SetDial sets dial function in client. // SetDial sets the custom dial function for the client.
func (c *Client) SetDial(dial fasthttp.DialFunc) *Client { func (c *Client) SetDial(dial fasthttp.DialFunc) *Client {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -570,7 +506,7 @@ func (c *Client) SetDial(dial fasthttp.DialFunc) *Client {
return c return c
} }
// SetLogger sets logger instance in client. // SetLogger sets the logger instance used by the client.
func (c *Client) SetLogger(logger log.CommonLogger) *Client { func (c *Client) SetLogger(logger log.CommonLogger) *Client {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -579,12 +515,12 @@ func (c *Client) SetLogger(logger log.CommonLogger) *Client {
return c return c
} }
// Logger returns logger instance of client. // Logger returns the logger instance used by the client.
func (c *Client) Logger() log.CommonLogger { func (c *Client) Logger() log.CommonLogger {
return c.logger return c.logger
} }
// Reset clears the Client object // Reset resets the client to its default state, clearing most configurations.
func (c *Client) Reset() { func (c *Client) Reset() {
c.fasthttp = &fasthttp.Client{} c.fasthttp = &fasthttp.Client{}
c.baseURL = "" c.baseURL = ""
@ -605,31 +541,25 @@ func (c *Client) Reset() {
c.params.Reset() c.params.Reset()
} }
// Config for easy to set the request parameters, it should be // Config is used to easily set request parameters. Note that when setting a request body,
// noted that when setting the request body will use JSON as // JSON is used as the default serialization mechanism. The priority is:
// the default serialization mechanism, while the priority of // Body > FormData > File.
// Body is higher than FormData, and the priority of FormData
// is higher than File.
type Config struct { type Config struct {
Ctx context.Context //nolint:containedctx // It's needed to be stored in the config. Ctx context.Context //nolint:containedctx // It's needed to be stored in the config.
Body any Body any
Header map[string]string Header map[string]string
Param map[string]string Param map[string]string
Cookie map[string]string Cookie map[string]string
PathParam map[string]string PathParam map[string]string
FormData map[string]string FormData map[string]string
UserAgent string UserAgent string
Referer string Referer string
File []*File File []*File
Timeout time.Duration Timeout time.Duration
MaxRedirects int MaxRedirects int
} }
// setConfigToRequest Set the parameters passed via Config to Request. // setConfigToRequest sets the parameters passed via Config to the Request.
func setConfigToRequest(req *Request, config ...Config) { func setConfigToRequest(req *Request, config ...Config) {
if len(config) == 0 { if len(config) == 0 {
return return
@ -694,21 +624,19 @@ var (
defaultUserAgent = "fiber" defaultUserAgent = "fiber"
) )
// init acquire a default client.
func init() { func init() {
defaultClient = New() defaultClient = New()
} }
// New creates and returns a new Client object. // New creates and returns a new Client object.
func New() *Client { func New() *Client {
// FOllOW-UP performance optimization // Follow-up performance optimizations:
// trie to use a pool to reduce the cost of memory allocation // Try to use a pool to reduce the memory allocation cost for the Fiber client and the fasthttp client.
// for the fiber client and the fasthttp client // If possible, also consider pooling other structs (e.g., request headers, cookies, query parameters, path parameters).
// if possible also for other structs -> request header, cookie, query param, path param...
return NewWithClient(&fasthttp.Client{}) return NewWithClient(&fasthttp.Client{})
} }
// NewWithClient creates and returns a new Client object from an existing client. // NewWithClient creates and returns a new Client object from an existing fasthttp.Client.
func NewWithClient(c *fasthttp.Client) *Client { func NewWithClient(c *fasthttp.Client) *Client {
if c == nil { if c == nil {
panic("fasthttp.Client must not be nil") panic("fasthttp.Client must not be nil")
@ -738,12 +666,12 @@ func NewWithClient(c *fasthttp.Client) *Client {
} }
} }
// C get default client. // C returns the default client.
func C() *Client { func C() *Client {
return defaultClient return defaultClient
} }
// Replace the defaultClient, the returned function can undo. // Replace replaces the defaultClient with a new one, returning a function to restore the old client.
func Replace(c *Client) func() { func Replace(c *Client) func() {
replaceMu.Lock() replaceMu.Lock()
defer replaceMu.Unlock() defer replaceMu.Unlock()
@ -759,37 +687,37 @@ func Replace(c *Client) func() {
} }
} }
// Get send a get request use defaultClient, a convenient method. // Get sends a GET request using the default client.
func Get(url string, cfg ...Config) (*Response, error) { func Get(url string, cfg ...Config) (*Response, error) {
return C().Get(url, cfg...) return C().Get(url, cfg...)
} }
// Post send a post request use defaultClient, a convenient method. // Post sends a POST request using the default client.
func Post(url string, cfg ...Config) (*Response, error) { func Post(url string, cfg ...Config) (*Response, error) {
return C().Post(url, cfg...) return C().Post(url, cfg...)
} }
// Head send a head request use defaultClient, a convenient method. // Head sends a HEAD request using the default client.
func Head(url string, cfg ...Config) (*Response, error) { func Head(url string, cfg ...Config) (*Response, error) {
return C().Head(url, cfg...) return C().Head(url, cfg...)
} }
// Put send a put request use defaultClient, a convenient method. // Put sends a PUT request using the default client.
func Put(url string, cfg ...Config) (*Response, error) { func Put(url string, cfg ...Config) (*Response, error) {
return C().Put(url, cfg...) return C().Put(url, cfg...)
} }
// Delete send a delete request use defaultClient, a convenient method. // Delete sends a DELETE request using the default client.
func Delete(url string, cfg ...Config) (*Response, error) { func Delete(url string, cfg ...Config) (*Response, error) {
return C().Delete(url, cfg...) return C().Delete(url, cfg...)
} }
// Options send a options request use defaultClient, a convenient method. // Options sends an OPTIONS request using the default client.
func Options(url string, cfg ...Config) (*Response, error) { func Options(url string, cfg ...Config) (*Response, error) {
return C().Options(url, cfg...) return C().Options(url, cfg...)
} }
// Patch send a patch request use defaultClient, a convenient method. // Patch sends a PATCH request using the default client.
func Patch(url string, cfg ...Config) (*Response, error) { func Patch(url string, cfg ...Config) (*Response, error) {
return C().Patch(url, cfg...) return C().Patch(url, cfg...)
} }

View File

@ -1,4 +1,4 @@
// The code has been taken from https://github.com/valyala/fasthttp/pull/526 originally. // The code was originally taken from https://github.com/valyala/fasthttp/pull/526.
package client package client
import ( import (
@ -18,7 +18,7 @@ var cookieJarPool = sync.Pool{
}, },
} }
// AcquireCookieJar returns an empty CookieJar object from pool. // AcquireCookieJar returns an empty CookieJar object from the pool.
func AcquireCookieJar() *CookieJar { func AcquireCookieJar() *CookieJar {
jar, ok := cookieJarPool.Get().(*CookieJar) jar, ok := cookieJarPool.Get().(*CookieJar)
if !ok { if !ok {
@ -28,22 +28,23 @@ func AcquireCookieJar() *CookieJar {
return jar return jar
} }
// ReleaseCookieJar returns CookieJar to the pool. // ReleaseCookieJar returns a CookieJar object to the pool.
func ReleaseCookieJar(c *CookieJar) { func ReleaseCookieJar(c *CookieJar) {
c.Release() c.Release()
cookieJarPool.Put(c) cookieJarPool.Put(c)
} }
// CookieJar manages cookie storage. It is used by the client to store cookies. // CookieJar manages cookie storage for the client. It stores cookies keyed by host.
type CookieJar struct { type CookieJar struct {
hostCookies map[string][]*fasthttp.Cookie hostCookies map[string][]*fasthttp.Cookie
mu sync.Mutex mu sync.Mutex
} }
// Get returns the cookies stored from a specific domain. // Get returns all cookies stored for a given URI. If there are no cookies for the
// If there were no cookies related with host returned slice will be nil. // provided host, the returned slice will be nil.
// //
// CookieJar keeps a copy of the cookies, so the returned cookies can be released safely. // The CookieJar keeps its own copies of cookies, so it is safe to release the returned
// cookies after use.
func (cj *CookieJar) Get(uri *fasthttp.URI) []*fasthttp.Cookie { func (cj *CookieJar) Get(uri *fasthttp.URI) []*fasthttp.Cookie {
if uri == nil { if uri == nil {
return nil return nil
@ -52,7 +53,7 @@ func (cj *CookieJar) Get(uri *fasthttp.URI) []*fasthttp.Cookie {
return cj.getByHostAndPath(uri.Host(), uri.Path()) return cj.getByHostAndPath(uri.Host(), uri.Path())
} }
// get returns the cookies stored from a specific host and path. // getByHostAndPath returns cookies stored for a specific host and path.
func (cj *CookieJar) getByHostAndPath(host, path []byte) []*fasthttp.Cookie { func (cj *CookieJar) getByHostAndPath(host, path []byte) []*fasthttp.Cookie {
if cj.hostCookies == nil { if cj.hostCookies == nil {
return nil return nil
@ -84,8 +85,7 @@ func (cj *CookieJar) getByHostAndPath(host, path []byte) []*fasthttp.Cookie {
return newCookies return newCookies
} }
// getCookiesByHost returns the cookies stored from a specific host. // getCookiesByHost returns cookies stored for a specific host, removing any that have expired.
// If cookies are expired they will be deleted.
func (cj *CookieJar) getCookiesByHost(host string) []*fasthttp.Cookie { func (cj *CookieJar) getCookiesByHost(host string) []*fasthttp.Cookie {
cj.mu.Lock() cj.mu.Lock()
defer cj.mu.Unlock() defer cj.mu.Unlock()
@ -95,7 +95,8 @@ func (cj *CookieJar) getCookiesByHost(host string) []*fasthttp.Cookie {
for i := 0; i < len(cookies); i++ { for i := 0; i < len(cookies); i++ {
c := cookies[i] c := cookies[i]
if !c.Expire().Equal(fasthttp.CookieExpireUnlimited) && c.Expire().Before(now) { // release cookie if expired // Remove expired cookies.
if !c.Expire().Equal(fasthttp.CookieExpireUnlimited) && c.Expire().Before(now) {
cookies = append(cookies[:i], cookies[i+1:]...) cookies = append(cookies[:i], cookies[i+1:]...)
fasthttp.ReleaseCookie(c) fasthttp.ReleaseCookie(c)
i-- i--
@ -105,23 +106,21 @@ func (cj *CookieJar) getCookiesByHost(host string) []*fasthttp.Cookie {
return cookies return cookies
} }
// Set sets cookies for a specific host. // Set stores the given cookies for the specified URI host. If a cookie key already exists,
// The host is get from uri.Host(). // it will be replaced by the new cookie value.
// If the cookie key already exists it will be replaced by the new cookie value.
// //
// CookieJar keeps a copy of the cookies, so the parsed cookies can be released safely. // CookieJar stores copies of the provided cookies, so they may be safely released after use.
func (cj *CookieJar) Set(uri *fasthttp.URI, cookies ...*fasthttp.Cookie) { func (cj *CookieJar) Set(uri *fasthttp.URI, cookies ...*fasthttp.Cookie) {
if uri == nil { if uri == nil {
return return
} }
cj.SetByHost(uri.Host(), cookies...) cj.SetByHost(uri.Host(), cookies...)
} }
// SetByHost sets cookies for a specific host. // SetByHost stores the given cookies for the specified host. If a cookie key already exists,
// If the cookie key already exists it will be replaced by the new cookie value. // it will be replaced by the new cookie value.
// //
// CookieJar keeps a copy of the cookies, so the parsed cookies can be released safely. // CookieJar stores copies of the provided cookies, so they may be safely released after use.
func (cj *CookieJar) SetByHost(host []byte, cookies ...*fasthttp.Cookie) { func (cj *CookieJar) SetByHost(host []byte, cookies ...*fasthttp.Cookie) {
hostStr := utils.UnsafeString(host) hostStr := utils.UnsafeString(host)
@ -134,26 +133,25 @@ func (cj *CookieJar) SetByHost(host []byte, cookies ...*fasthttp.Cookie) {
hostCookies, ok := cj.hostCookies[hostStr] hostCookies, ok := cj.hostCookies[hostStr]
if !ok { if !ok {
// If the key does not exist in the map, then we must make a copy for the key to avoid unsafe usage. // If the key does not exist in the map, make a copy to avoid unsafe usage.
hostStr = string(host) hostStr = string(host)
} }
for _, cookie := range cookies { for _, cookie := range cookies {
c := searchCookieByKeyAndPath(cookie.Key(), cookie.Path(), hostCookies) existing := searchCookieByKeyAndPath(cookie.Key(), cookie.Path(), hostCookies)
if c == nil { if existing == nil {
// If the cookie does not exist in the slice, let's acquire new cookie and store it. // If the cookie does not exist, acquire a new one.
c = fasthttp.AcquireCookie() existing = fasthttp.AcquireCookie()
hostCookies = append(hostCookies, c) hostCookies = append(hostCookies, existing)
} }
c.CopyTo(cookie) // override cookie properties existing.CopyTo(cookie) // Override cookie properties.
} }
cj.hostCookies[hostStr] = hostCookies cj.hostCookies[hostStr] = hostCookies
} }
// SetKeyValue sets a cookie by key and value for a specific host. // SetKeyValue sets a cookie for the specified host with the given key and value.
// //
// This function prevents extra allocations by making repeated cookies // This function helps prevent extra allocations by avoiding duplication of repeated cookies.
// not being duplicated.
func (cj *CookieJar) SetKeyValue(host, key, value string) { func (cj *CookieJar) SetKeyValue(host, key, value string) {
c := fasthttp.AcquireCookie() c := fasthttp.AcquireCookie()
c.SetKey(key) c.SetKey(key)
@ -162,10 +160,9 @@ func (cj *CookieJar) SetKeyValue(host, key, value string) {
cj.SetByHost(utils.UnsafeBytes(host), c) cj.SetByHost(utils.UnsafeBytes(host), c)
} }
// SetKeyValueBytes sets a cookie by key and value for a specific host. // SetKeyValueBytes sets a cookie for the specified host using byte slices for the key and value.
// //
// This function prevents extra allocations by making repeated cookies // This function helps prevent extra allocations by avoiding duplication of repeated cookies.
// not being duplicated.
func (cj *CookieJar) SetKeyValueBytes(host string, key, value []byte) { func (cj *CookieJar) SetKeyValueBytes(host string, key, value []byte) {
c := fasthttp.AcquireCookie() c := fasthttp.AcquireCookie()
c.SetKeyBytes(key) c.SetKeyBytes(key)
@ -174,17 +171,16 @@ func (cj *CookieJar) SetKeyValueBytes(host string, key, value []byte) {
cj.SetByHost(utils.UnsafeBytes(host), c) cj.SetByHost(utils.UnsafeBytes(host), c)
} }
// dumpCookiesToReq dumps the stored cookies to the request. // dumpCookiesToReq writes the stored cookies to the given request.
func (cj *CookieJar) dumpCookiesToReq(req *fasthttp.Request) { func (cj *CookieJar) dumpCookiesToReq(req *fasthttp.Request) {
uri := req.URI() uri := req.URI()
cookies := cj.getByHostAndPath(uri.Host(), uri.Path()) cookies := cj.getByHostAndPath(uri.Host(), uri.Path())
for _, cookie := range cookies { for _, cookie := range cookies {
req.Header.SetCookieBytesKV(cookie.Key(), cookie.Value()) req.Header.SetCookieBytesKV(cookie.Key(), cookie.Value())
} }
} }
// parseCookiesFromResp parses the response cookies and stores them. // parseCookiesFromResp parses the cookies from the response and stores them for the specified host and path.
func (cj *CookieJar) parseCookiesFromResp(host, path []byte, resp *fasthttp.Response) { func (cj *CookieJar) parseCookiesFromResp(host, path []byte, resp *fasthttp.Response) {
hostStr := utils.UnsafeString(host) hostStr := utils.UnsafeString(host)
@ -194,35 +190,36 @@ func (cj *CookieJar) parseCookiesFromResp(host, path []byte, resp *fasthttp.Resp
if cj.hostCookies == nil { if cj.hostCookies == nil {
cj.hostCookies = make(map[string][]*fasthttp.Cookie) cj.hostCookies = make(map[string][]*fasthttp.Cookie)
} }
cookies, ok := cj.hostCookies[hostStr] cookies, ok := cj.hostCookies[hostStr]
if !ok { if !ok {
// If the key does not exist in the map then // If the key does not exist in the map, make a copy to avoid unsafe usage.
// we must make a copy for the key to avoid unsafe usage.
hostStr = string(host) hostStr = string(host)
} }
now := time.Now() now := time.Now()
resp.Header.VisitAllCookie(func(key, value []byte) { resp.Header.VisitAllCookie(func(key, value []byte) {
isCreated := false created := false
c := searchCookieByKeyAndPath(key, path, cookies) c := searchCookieByKeyAndPath(key, path, cookies)
if c == nil { if c == nil {
c, isCreated = fasthttp.AcquireCookie(), true c, created = fasthttp.AcquireCookie(), true
} }
_ = c.ParseBytes(value) //nolint:errcheck // ignore error _ = c.ParseBytes(value) //nolint:errcheck // ignore error
if c.Expire().Equal(fasthttp.CookieExpireUnlimited) || c.Expire().After(now) { if c.Expire().Equal(fasthttp.CookieExpireUnlimited) || c.Expire().After(now) {
cookies = append(cookies, c) cookies = append(cookies, c)
} else if isCreated { } else if created {
fasthttp.ReleaseCookie(c) fasthttp.ReleaseCookie(c)
} }
}) })
cj.hostCookies[hostStr] = cookies cj.hostCookies[hostStr] = cookies
} }
// Release releases all cookie values. // Release releases all stored cookies. After this, the CookieJar is empty.
func (cj *CookieJar) Release() { func (cj *CookieJar) Release() {
// FOllOW-UP performance optimization // FOLLOW-UP performance optimization:
// currently a race condition is found because the reset method modifies a value which is not a copy but a reference -> solution should be to make a copy // Currently, a race condition is found because the reset method modifies a value
// that is not a copy but a reference. A solution would be to make a copy.
// for _, v := range cj.hostCookies { // for _, v := range cj.hostCookies {
// for _, c := range v { // for _, c := range v {
// fasthttp.ReleaseCookie(c) // fasthttp.ReleaseCookie(c)
@ -231,7 +228,7 @@ func (cj *CookieJar) Release() {
cj.hostCookies = nil cj.hostCookies = nil
} }
// searchCookieByKeyAndPath searches for a cookie by key and path. // searchCookieByKeyAndPath looks up a cookie by its key and path from the provided slice of cookies.
func searchCookieByKeyAndPath(key, path []byte, cookies []*fasthttp.Cookie) *fasthttp.Cookie { func searchCookieByKeyAndPath(key, path []byte, cookies []*fasthttp.Cookie) *fasthttp.Cookie {
for _, c := range cookies { for _, c := range cookies {
if bytes.Equal(key, c.Key()) { if bytes.Equal(key, c.Key()) {
@ -240,6 +237,5 @@ func searchCookieByKeyAndPath(key, path []byte, cookies []*fasthttp.Cookie) *fas
} }
} }
} }
return nil return nil
} }

View File

@ -16,23 +16,21 @@ import (
var boundary = "--FiberFormBoundary" var boundary = "--FiberFormBoundary"
// RequestHook is a function that receives Agent and Request, // RequestHook is a function invoked before the request is sent.
// it can change the data in Request and Agent. // It receives a Client and a Request, allowing you to modify the Request or Client data.
//
// Called before a request is sent.
type RequestHook func(*Client, *Request) error type RequestHook func(*Client, *Request) error
// ResponseHook is a function that receives Agent, Response and Request, // ResponseHook is a function invoked after a response is received.
// it can change the data is Response or deal with some effects. // It receives a Client, Response, and Request, allowing you to modify the Response data
// // or perform actions based on the response.
// Called after a response has been received.
type ResponseHook func(*Client, *Response, *Request) error type ResponseHook func(*Client, *Response, *Request) error
// RetryConfig is an alias for config in the `addon/retry` package. // RetryConfig is an alias for the `retry.Config` type from the `addon/retry` package.
type RetryConfig = retry.Config type RetryConfig = retry.Config
// addMissingPort will add the corresponding port number for host. // addMissingPort appends the appropriate port number to the given address if it doesn't have one.
func addMissingPort(addr string, isTLS bool) string { //revive:disable-line:flag-parameter // Accepting a bool param named isTLS if fine here // If isTLS is true, it uses port 443; otherwise, it uses port 80.
func addMissingPort(addr string, isTLS bool) string { //revive:disable-line:flag-parameter
n := strings.Index(addr, ":") n := strings.Index(addr, ":")
if n >= 0 { if n >= 0 {
return addr return addr
@ -44,15 +42,14 @@ func addMissingPort(addr string, isTLS bool) string { //revive:disable-line:flag
return net.JoinHostPort(addr, strconv.Itoa(port)) return net.JoinHostPort(addr, strconv.Itoa(port))
} }
// `core` stores middleware and plugin definitions, // core stores middleware and plugin definitions and defines the request execution process.
// and defines the execution process
type core struct { type core struct {
client *Client client *Client
req *Request req *Request
ctx context.Context //nolint:containedctx // It's needed to be stored in the core. ctx context.Context //nolint:containedctx // Context is needed here.
} }
// getRetryConfig returns the retry configuration of the client. // getRetryConfig returns a copy of the client's retry configuration.
func (c *core) getRetryConfig() *RetryConfig { func (c *core) getRetryConfig() *RetryConfig {
c.client.mu.RLock() c.client.mu.RLock()
defer c.client.mu.RUnlock() defer c.client.mu.RUnlock()
@ -70,19 +67,16 @@ func (c *core) getRetryConfig() *RetryConfig {
} }
} }
// execFunc is the core function of the client. // execFunc is the core logic to send the request and receive the response.
// It sends the request and receives the response. // It leverages the fasthttp client, optionally with retries or redirects.
func (c *core) execFunc() (*Response, error) { func (c *core) execFunc() (*Response, error) {
resp := AcquireResponse() resp := AcquireResponse()
resp.setClient(c.client) resp.setClient(c.client)
resp.setRequest(c.req) resp.setRequest(c.req)
// To avoid memory allocation reuse of data structures such as errch.
done := int32(0) done := int32(0)
errCh, reqv := acquireErrChan(), fasthttp.AcquireRequest() errCh, reqv := acquireErrChan(), fasthttp.AcquireRequest()
defer func() { defer releaseErrChan(errCh)
releaseErrChan(errCh)
}()
c.req.RawRequest.CopyTo(reqv) c.req.RawRequest.CopyTo(reqv)
cfg := c.getRetryConfig() cfg := c.getRetryConfig()
@ -96,11 +90,11 @@ func (c *core) execFunc() (*Response, error) {
}() }()
if cfg != nil { if cfg != nil {
// Use an exponential backoff retry strategy.
err = retry.NewExponentialBackoff(*cfg).Retry(func() error { err = retry.NewExponentialBackoff(*cfg).Retry(func() error {
if c.req.maxRedirects > 0 && (string(reqv.Header.Method()) == fiber.MethodGet || string(reqv.Header.Method()) == fiber.MethodHead) { if c.req.maxRedirects > 0 && (string(reqv.Header.Method()) == fiber.MethodGet || string(reqv.Header.Method()) == fiber.MethodHead) {
return c.client.fasthttp.DoRedirects(reqv, respv, c.req.maxRedirects) return c.client.fasthttp.DoRedirects(reqv, respv, c.req.maxRedirects)
} }
return c.client.fasthttp.Do(reqv, respv) return c.client.fasthttp.Do(reqv, respv)
}) })
} else { } else {
@ -124,7 +118,7 @@ func (c *core) execFunc() (*Response, error) {
select { select {
case err := <-errCh: case err := <-errCh:
if err != nil { if err != nil {
// When get error should release Response // Release the response if an error occurs.
ReleaseResponse(resp) ReleaseResponse(resp)
return nil, err return nil, err
} }
@ -136,21 +130,19 @@ func (c *core) execFunc() (*Response, error) {
} }
} }
// preHooks Exec request hook // preHooks runs all request hooks before sending the request.
func (c *core) preHooks() error { func (c *core) preHooks() error {
c.client.mu.Lock() c.client.mu.Lock()
defer c.client.mu.Unlock() defer c.client.mu.Unlock()
for _, f := range c.client.userRequestHooks { for _, f := range c.client.userRequestHooks {
err := f(c.client, c.req) if err := f(c.client, c.req); err != nil {
if err != nil {
return err return err
} }
} }
for _, f := range c.client.builtinRequestHooks { for _, f := range c.client.builtinRequestHooks {
err := f(c.client, c.req) if err := f(c.client, c.req); err != nil {
if err != nil {
return err return err
} }
} }
@ -158,21 +150,19 @@ func (c *core) preHooks() error {
return nil return nil
} }
// afterHooks Exec response hooks // afterHooks runs all response hooks after receiving the response.
func (c *core) afterHooks(resp *Response) error { func (c *core) afterHooks(resp *Response) error {
c.client.mu.Lock() c.client.mu.Lock()
defer c.client.mu.Unlock() defer c.client.mu.Unlock()
for _, f := range c.client.builtinResponseHooks { for _, f := range c.client.builtinResponseHooks {
err := f(c.client, resp, c.req) if err := f(c.client, resp, c.req); err != nil {
if err != nil {
return err return err
} }
} }
for _, f := range c.client.userResponseHooks { for _, f := range c.client.userResponseHooks {
err := f(c.client, resp, c.req) if err := f(c.client, resp, c.req); err != nil {
if err != nil {
return err return err
} }
} }
@ -180,7 +170,7 @@ func (c *core) afterHooks(resp *Response) error {
return nil return nil
} }
// timeout deals with timeout // timeout applies the configured timeout to the request, if any.
func (c *core) timeout() context.CancelFunc { func (c *core) timeout() context.CancelFunc {
var cancel context.CancelFunc var cancel context.CancelFunc
@ -193,35 +183,32 @@ func (c *core) timeout() context.CancelFunc {
return cancel return cancel
} }
// execute will exec each hooks and plugins. // execute runs all hooks, applies timeouts, sends the request, and runs response hooks.
func (c *core) execute(ctx context.Context, client *Client, req *Request) (*Response, error) { func (c *core) execute(ctx context.Context, client *Client, req *Request) (*Response, error) {
// keep a reference, because pass param is boring // Store references locally.
c.ctx = ctx c.ctx = ctx
c.client = client c.client = client
c.req = req c.req = req
// The built-in hooks will be executed only // Execute pre request hooks (user-defined and built-in).
// after the user-defined hooks are executed. if err := c.preHooks(); err != nil {
err := c.preHooks()
if err != nil {
return nil, err return nil, err
} }
// Apply timeout if specified.
cancel := c.timeout() cancel := c.timeout()
if cancel != nil { if cancel != nil {
defer cancel() defer cancel()
} }
// Do http request // Perform the actual HTTP request.
resp, err := c.execFunc() resp, err := c.execFunc()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// The built-in hooks will be executed only // Execute after response hooks (built-in and then user-defined).
// before the user-defined hooks are executed. if err := c.afterHooks(resp); err != nil {
err = c.afterHooks(resp)
if err != nil {
resp.Close() resp.Close()
return nil, err return nil, err
} }
@ -235,38 +222,35 @@ var errChanPool = &sync.Pool{
}, },
} }
// acquireErrChan returns an empty error chan from the pool. // acquireErrChan returns an empty error channel from the pool.
// //
// The returned error chan may be returned to the pool with releaseErrChan when no longer needed. // The returned channel may be returned to the pool with releaseErrChan when no longer needed,
// This allows reducing GC load. // reducing GC load.
func acquireErrChan() chan error { func acquireErrChan() chan error {
ch, ok := errChanPool.Get().(chan error) ch, ok := errChanPool.Get().(chan error)
if !ok { if !ok {
panic(errors.New("failed to type-assert to chan error")) panic(errors.New("failed to type-assert to chan error"))
} }
return ch return ch
} }
// releaseErrChan returns the object acquired via acquireErrChan to the pool. // releaseErrChan returns the error channel to the pool.
// //
// Do not access the released core object, otherwise data races may occur. // Do not use the released channel afterward to avoid data races.
func releaseErrChan(ch chan error) { func releaseErrChan(ch chan error) {
errChanPool.Put(ch) errChanPool.Put(ch)
} }
// newCore returns an empty core object. // newCore returns a new core object.
func newCore() *core { func newCore() *core {
c := &core{} return &core{}
return c
} }
var ( var (
ErrTimeoutOrCancel = errors.New("timeout or cancel") ErrTimeoutOrCancel = errors.New("timeout or cancel")
ErrURLFormat = errors.New("the url is a mistake") ErrURLFormat = errors.New("the URL is incorrect")
ErrNotSupportSchema = errors.New("the protocol is not support, only http or https") ErrNotSupportSchema = errors.New("protocol not supported; only http or https are allowed")
ErrFileNoName = errors.New("the file should have name") ErrFileNoName = errors.New("the file should have a name")
ErrBodyType = errors.New("the body type should be []byte") ErrBodyType = errors.New("the body type should be []byte")
ErrNotSupportSaveMethod = errors.New("file path and io.Writer are supported") ErrNotSupportSaveMethod = errors.New("only file paths and io.Writer are supported")
) )

View File

@ -1,7 +1,6 @@
package client package client
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"math/rand" "math/rand"
@ -31,10 +30,10 @@ var (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
letterIdxBits = 6 // 6 bits to represent a letter index letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits letterIdxMax = 63 / letterIdxBits // # of letter indices fitting into 63 bits
) )
// randString returns a random string with n length // randString returns a random string of length n.
func randString(n int) string { func randString(n int) string {
b := make([]byte, n) b := make([]byte, n)
length := len(letterBytes) length := len(letterBytes)
@ -56,17 +55,14 @@ func randString(n int) string {
return utils.UnsafeString(b) return utils.UnsafeString(b)
} }
// parserRequestURL will set the options for the hostclient // parserRequestURL sets options for the hostclient and normalizes the URL.
// and normalize the url. // It merges the baseURL with the request URI if needed and applies query and path parameters.
// The baseUrl will be merge with request uri.
// Query params and path params deal in this function.
func parserRequestURL(c *Client, req *Request) error { func parserRequestURL(c *Client, req *Request) error {
splitURL := strings.Split(req.url, "?") splitURL := strings.Split(req.url, "?")
// I don't want to judge splitURL length. // Ensure splitURL has at least two elements.
splitURL = append(splitURL, "") splitURL = append(splitURL, "")
// Determine whether to superimpose baseurl based on // If the URL doesn't start with http/https, prepend the baseURL.
// whether the URL starts with the protocol
uri := splitURL[0] uri := splitURL[0]
if !protocolCheck.MatchString(uri) { if !protocolCheck.MatchString(uri) {
uri = c.baseURL + uri uri = c.baseURL + uri
@ -75,7 +71,7 @@ func parserRequestURL(c *Client, req *Request) error {
} }
} }
// set path params // Set path parameters from the request and client.
req.path.VisitAll(func(key, val string) { req.path.VisitAll(func(key, val string) {
uri = strings.ReplaceAll(uri, ":"+key, val) uri = strings.ReplaceAll(uri, ":"+key, val)
}) })
@ -83,47 +79,47 @@ func parserRequestURL(c *Client, req *Request) error {
uri = strings.ReplaceAll(uri, ":"+key, val) uri = strings.ReplaceAll(uri, ":"+key, val)
}) })
// set uri to request and other related setting // Set the URI in the raw request.
req.RawRequest.SetRequestURI(uri) req.RawRequest.SetRequestURI(uri)
// merge query params // Merge query parameters.
hashSplit := strings.Split(splitURL[1], "#") hashSplit := strings.Split(splitURL[1], "#")
hashSplit = append(hashSplit, "") hashSplit = append(hashSplit, "")
args := fasthttp.AcquireArgs() args := fasthttp.AcquireArgs()
defer func() { defer fasthttp.ReleaseArgs(args)
fasthttp.ReleaseArgs(args)
}()
args.Parse(hashSplit[0]) args.Parse(hashSplit[0])
c.params.VisitAll(func(key, value []byte) { c.params.VisitAll(func(key, value []byte) {
args.AddBytesKV(key, value) args.AddBytesKV(key, value)
}) })
req.params.VisitAll(func(key, value []byte) { req.params.VisitAll(func(key, value []byte) {
args.AddBytesKV(key, value) args.AddBytesKV(key, value)
}) })
req.RawRequest.URI().SetQueryStringBytes(utils.CopyBytes(args.QueryString())) req.RawRequest.URI().SetQueryStringBytes(utils.CopyBytes(args.QueryString()))
req.RawRequest.URI().SetHash(hashSplit[1]) req.RawRequest.URI().SetHash(hashSplit[1])
return nil return nil
} }
// parserRequestHeader will make request header up. // parserRequestHeader merges client and request headers, and sets headers automatically based on the request data.
// It will merge headers from client and request. // It also sets the User-Agent and Referer headers, and applies any cookies from the cookie jar.
// Header should be set automatically based on data.
// User-Agent should be set.
func parserRequestHeader(c *Client, req *Request) error { func parserRequestHeader(c *Client, req *Request) error {
// set method // Set HTTP method.
req.RawRequest.Header.SetMethod(req.Method()) req.RawRequest.Header.SetMethod(req.Method())
// merge header
// Merge headers from the client.
c.header.VisitAll(func(key, value []byte) { c.header.VisitAll(func(key, value []byte) {
req.RawRequest.Header.AddBytesKV(key, value) req.RawRequest.Header.AddBytesKV(key, value)
}) })
// Merge headers from the request.
req.header.VisitAll(func(key, value []byte) { req.header.VisitAll(func(key, value []byte) {
req.RawRequest.Header.AddBytesKV(key, value) req.RawRequest.Header.AddBytesKV(key, value)
}) })
// according to data set content-type // Set Content-Type and Accept headers based on the request body type.
switch req.bodyType { switch req.bodyType {
case jsonBody: case jsonBody:
req.RawRequest.Header.SetContentType(applicationJSON) req.RawRequest.Header.SetContentType(applicationJSON)
@ -136,15 +132,16 @@ func parserRequestHeader(c *Client, req *Request) error {
req.RawRequest.Header.SetContentType(applicationForm) req.RawRequest.Header.SetContentType(applicationForm)
case filesBody: case filesBody:
req.RawRequest.Header.SetContentType(multipartFormData) req.RawRequest.Header.SetContentType(multipartFormData)
// set boundary // If boundary is default, append a random string to it.
if req.boundary == boundary { if req.boundary == boundary {
req.boundary += randString(16) req.boundary += randString(16)
} }
req.RawRequest.Header.SetMultipartFormBoundary(req.boundary) req.RawRequest.Header.SetMultipartFormBoundary(req.boundary)
default: default:
// noBody or rawBody do not require special handling here.
} }
// set useragent // Set User-Agent header.
req.RawRequest.Header.SetUserAgent(defaultUserAgent) req.RawRequest.Header.SetUserAgent(defaultUserAgent)
if c.userAgent != "" { if c.userAgent != "" {
req.RawRequest.Header.SetUserAgent(c.userAgent) req.RawRequest.Header.SetUserAgent(c.userAgent)
@ -153,22 +150,23 @@ func parserRequestHeader(c *Client, req *Request) error {
req.RawRequest.Header.SetUserAgent(req.userAgent) req.RawRequest.Header.SetUserAgent(req.userAgent)
} }
// set referer // Set Referer header.
req.RawRequest.Header.SetReferer(c.referer) req.RawRequest.Header.SetReferer(c.referer)
if req.referer != "" { if req.referer != "" {
req.RawRequest.Header.SetReferer(req.referer) req.RawRequest.Header.SetReferer(req.referer)
} }
// set cookie // Set cookies from the cookie jar if available.
// add cookie form jar to req
if c.cookieJar != nil { if c.cookieJar != nil {
c.cookieJar.dumpCookiesToReq(req.RawRequest) c.cookieJar.dumpCookiesToReq(req.RawRequest)
} }
// Set cookies from the client.
c.cookies.VisitAll(func(key, val string) { c.cookies.VisitAll(func(key, val string) {
req.RawRequest.Header.SetCookie(key, val) req.RawRequest.Header.SetCookie(key, val)
}) })
// Set cookies from the request.
req.cookies.VisitAll(func(key, val string) { req.cookies.VisitAll(func(key, val string) {
req.RawRequest.Header.SetCookie(key, val) req.RawRequest.Header.SetCookie(key, val)
}) })
@ -176,8 +174,7 @@ func parserRequestHeader(c *Client, req *Request) error {
return nil return nil
} }
// parserRequestBody automatically serializes the data according to // parserRequestBody serializes the request body based on its type and sets it into the RawRequest.
// the data type and stores it in the body of the rawRequest
func parserRequestBody(c *Client, req *Request) error { func parserRequestBody(c *Client, req *Request) error {
switch req.bodyType { switch req.bodyType {
case jsonBody: case jsonBody:
@ -209,14 +206,14 @@ func parserRequestBody(c *Client, req *Request) error {
return ErrBodyType return ErrBodyType
} }
case noBody: case noBody:
// No body to set.
return nil return nil
} }
return nil return nil
} }
// parserRequestBodyFile parses request body if body type is file // parserRequestBodyFile handles the case where the request contains files to be uploaded.
// this is an addition of parserRequestBody.
func parserRequestBodyFile(req *Request) error { func parserRequestBodyFile(req *Request) error {
mw := multipart.NewWriter(req.RawRequest.BodyWriter()) mw := multipart.NewWriter(req.RawRequest.BodyWriter())
err := mw.SetBoundary(req.boundary) err := mw.SetBoundary(req.boundary)
@ -224,13 +221,14 @@ func parserRequestBodyFile(req *Request) error {
return fmt.Errorf("set boundary error: %w", err) return fmt.Errorf("set boundary error: %w", err)
} }
defer func() { defer func() {
err := mw.Close() e := mw.Close()
if err != nil { if e != nil {
// Close errors are typically ignored.
return return
} }
}() }()
// add formdata // Add form data.
req.formData.VisitAll(func(key, value []byte) { req.formData.VisitAll(func(key, value []byte) {
if err != nil { if err != nil {
return return
@ -241,25 +239,25 @@ func parserRequestBodyFile(req *Request) error {
return fmt.Errorf("write formdata error: %w", err) return fmt.Errorf("write formdata error: %w", err)
} }
// add file // Add files.
b := make([]byte, 512) fileBuf := make([]byte, 1<<20) // 1MB buffer
for i, v := range req.files { for i, v := range req.files {
if v.name == "" && v.path == "" { if v.name == "" && v.path == "" {
return ErrFileNoName return ErrFileNoName
} }
// if name is not exist, set name // Set the file name if not provided.
if v.name == "" && v.path != "" { if v.name == "" && v.path != "" {
v.path = filepath.Clean(v.path) v.path = filepath.Clean(v.path)
v.name = filepath.Base(v.path) v.name = filepath.Base(v.path)
} }
// if field name is not exist, set it // Set the field name if not provided.
if v.fieldName == "" { if v.fieldName == "" {
v.fieldName = "file" + strconv.Itoa(i+1) v.fieldName = "file" + strconv.Itoa(i+1)
} }
// check the reader // If reader is not set, open the file.
if v.reader == nil { if v.reader == nil {
v.reader, err = os.Open(v.path) v.reader, err = os.Open(v.path)
if err != nil { if err != nil {
@ -267,30 +265,17 @@ func parserRequestBodyFile(req *Request) error {
} }
} }
// write file // Create form file and copy the content.
w, err := mw.CreateFormFile(v.fieldName, v.name) w, err := mw.CreateFormFile(v.fieldName, v.name)
if err != nil { if err != nil {
return fmt.Errorf("create file error: %w", err) return fmt.Errorf("create file error: %w", err)
} }
for { if _, err := io.CopyBuffer(w, v.reader, fileBuf); err != nil {
n, err := v.reader.Read(b) return fmt.Errorf("failed to copy file data: %w", err)
if err != nil && !errors.Is(err, io.EOF) {
return fmt.Errorf("read file error: %w", err)
} }
if errors.Is(err, io.EOF) { if err := v.reader.Close(); err != nil {
break
}
_, err = w.Write(b[:n])
if err != nil {
return fmt.Errorf("write file error: %w", err)
}
}
err = v.reader.Close()
if err != nil {
return fmt.Errorf("close file error: %w", err) return fmt.Errorf("close file error: %w", err)
} }
} }
@ -298,7 +283,7 @@ func parserRequestBodyFile(req *Request) error {
return nil return nil
} }
// parserResponseCookie will parse the response header and store it in the response // parserResponseCookie parses the Set-Cookie headers from the response and stores them.
func parserResponseCookie(c *Client, resp *Response, req *Request) error { func parserResponseCookie(c *Client, resp *Response, req *Request) error {
var err error var err error
resp.RawResponse.Header.VisitAllCookie(func(key, value []byte) { resp.RawResponse.Header.VisitAllCookie(func(key, value []byte) {
@ -308,7 +293,6 @@ func parserResponseCookie(c *Client, resp *Response, req *Request) error {
return return
} }
cookie.SetKeyBytes(key) cookie.SetKeyBytes(key)
resp.cookie = append(resp.cookie, cookie) resp.cookie = append(resp.cookie, cookie)
}) })
@ -316,7 +300,7 @@ func parserResponseCookie(c *Client, resp *Response, req *Request) error {
return err return err
} }
// store cookies to jar // Store cookies in the cookie jar if available.
if c.cookieJar != nil { if c.cookieJar != nil {
c.cookieJar.parseCookiesFromResp(req.RawRequest.URI().Host(), req.RawRequest.URI().Path(), resp.RawResponse) c.cookieJar.parseCookiesFromResp(req.RawRequest.URI().Host(), req.RawRequest.URI().Path(), resp.RawResponse)
} }
@ -324,7 +308,7 @@ func parserResponseCookie(c *Client, resp *Response, req *Request) error {
return nil return nil
} }
// logger is a response hook that logs the request and response // logger is a response hook that logs request and response data if debug mode is enabled.
func logger(c *Client, resp *Response, req *Request) error { func logger(c *Client, resp *Response, req *Request) error {
if !c.debug { if !c.debug {
return nil return nil

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ import (
"context" "context"
"errors" "errors"
"io" "io"
"maps"
"mime/multipart" "mime/multipart"
"net" "net"
"os" "os"
@ -157,6 +158,42 @@ func Test_Request_Header(t *testing.T) {
}) })
} }
func Test_Request_Headers(t *testing.T) {
t.Parallel()
req := AcquireRequest()
req.AddHeaders(map[string][]string{
"foo": {"bar", "fiber"},
"bar": {"foo"},
})
headers := maps.Collect(req.Headers())
require.Contains(t, headers["Foo"], "fiber")
require.Contains(t, headers["Foo"], "bar")
require.Contains(t, headers["Bar"], "foo")
require.Len(t, headers, 2)
}
func Benchmark_Request_Headers(b *testing.B) {
req := AcquireRequest()
req.AddHeaders(map[string][]string{
"foo": {"bar", "fiber"},
"bar": {"foo"},
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for k, v := range req.Headers() {
_ = k
_ = v
}
}
}
func Test_Request_QueryParam(t *testing.T) { func Test_Request_QueryParam(t *testing.T) {
t.Parallel() t.Parallel()
@ -282,6 +319,42 @@ func Test_Request_QueryParam(t *testing.T) {
}) })
} }
func Test_Request_Params(t *testing.T) {
t.Parallel()
req := AcquireRequest()
req.AddParams(map[string][]string{
"foo": {"bar", "fiber"},
"bar": {"foo"},
})
pathParams := maps.Collect(req.Params())
require.Contains(t, pathParams["foo"], "bar")
require.Contains(t, pathParams["foo"], "fiber")
require.Contains(t, pathParams["bar"], "foo")
require.Len(t, pathParams, 2)
}
func Benchmark_Request_Params(b *testing.B) {
req := AcquireRequest()
req.AddParams(map[string][]string{
"foo": {"bar", "fiber"},
"bar": {"foo"},
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for k, v := range req.Params() {
_ = k
_ = v
}
}
}
func Test_Request_UA(t *testing.T) { func Test_Request_UA(t *testing.T) {
t.Parallel() t.Parallel()
@ -364,6 +437,41 @@ func Test_Request_Cookie(t *testing.T) {
}) })
} }
func Test_Request_Cookies(t *testing.T) {
t.Parallel()
req := AcquireRequest()
req.SetCookies(map[string]string{
"foo": "bar",
"bar": "foo",
})
cookies := maps.Collect(req.Cookies())
require.Equal(t, "bar", cookies["foo"])
require.Equal(t, "foo", cookies["bar"])
require.Len(t, cookies, 2)
}
func Benchmark_Request_Cookies(b *testing.B) {
req := AcquireRequest()
req.SetCookies(map[string]string{
"foo": "bar",
"bar": "foo",
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for k, v := range req.Cookies() {
_ = k
_ = v
}
}
}
func Test_Request_PathParam(t *testing.T) { func Test_Request_PathParam(t *testing.T) {
t.Parallel() t.Parallel()
@ -441,6 +549,41 @@ func Test_Request_PathParam(t *testing.T) {
}) })
} }
func Test_Request_PathParams(t *testing.T) {
t.Parallel()
req := AcquireRequest()
req.SetPathParams(map[string]string{
"foo": "bar",
"bar": "foo",
})
pathParams := maps.Collect(req.PathParams())
require.Equal(t, "bar", pathParams["foo"])
require.Equal(t, "foo", pathParams["bar"])
require.Len(t, pathParams, 2)
}
func Benchmark_Request_PathParams(b *testing.B) {
req := AcquireRequest()
req.SetPathParams(map[string]string{
"foo": "bar",
"bar": "foo",
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for k, v := range req.PathParams() {
_ = k
_ = v
}
}
}
func Test_Request_FormData(t *testing.T) { func Test_Request_FormData(t *testing.T) {
t.Parallel() t.Parallel()
@ -610,6 +753,40 @@ func Test_Request_File(t *testing.T) {
}) })
} }
func Test_Request_Files(t *testing.T) {
t.Parallel()
req := AcquireRequest()
req.AddFile("../.github/index.html")
req.AddFiles(AcquireFile(SetFileName("tmp.txt")))
files := req.Files()
require.Equal(t, "../.github/index.html", files[0].path)
require.Nil(t, files[0].reader)
require.Equal(t, "tmp.txt", files[1].name)
require.Nil(t, files[1].reader)
require.Len(t, files, 2)
}
func Benchmark_Request_Files(b *testing.B) {
req := AcquireRequest()
req.AddFile("../.github/index.html")
req.AddFiles(AcquireFile(SetFileName("tmp.txt")))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for k, v := range req.Files() {
_ = k
_ = v
}
}
}
func Test_Request_Timeout(t *testing.T) { func Test_Request_Timeout(t *testing.T) {
t.Parallel() t.Parallel()
@ -1181,6 +1358,42 @@ func Test_Request_Body_With_Server(t *testing.T) {
}) })
} }
func Test_Request_AllFormData(t *testing.T) {
t.Parallel()
req := AcquireRequest()
req.AddFormDatas(map[string][]string{
"foo": {"bar", "fiber"},
"bar": {"foo"},
})
pathParams := maps.Collect(req.AllFormData())
require.Contains(t, pathParams["foo"], "bar")
require.Contains(t, pathParams["foo"], "fiber")
require.Contains(t, pathParams["bar"], "foo")
require.Len(t, pathParams, 2)
}
func Benchmark_Request_AllFormData(b *testing.B) {
req := AcquireRequest()
req.AddFormDatas(map[string][]string{
"foo": {"bar", "fiber"},
"bar": {"foo"},
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for k, v := range req.AllFormData() {
_ = k
_ = v
}
}
}
func Test_Request_Error_Body_With_Server(t *testing.T) { func Test_Request_Error_Body_With_Server(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("json error", func(t *testing.T) { t.Run("json error", func(t *testing.T) {
@ -1317,14 +1530,16 @@ func Test_Request_MaxRedirects(t *testing.T) {
func Test_SetValWithStruct(t *testing.T) { func Test_SetValWithStruct(t *testing.T) {
t.Parallel() t.Parallel()
// test SetValWithStruct vai QueryParam struct. // test SetValWithStruct via QueryParam struct.
type args struct { type args struct {
TString string TString string
TSlice []string TSlice []string
TIntSlice []int `param:"int_slice"` TIntSlice []int `param:"int_slice"`
unexport int unexport int
TInt int TInt int
TUint uint
TFloat float64 TFloat float64
TComplex complex128
TBool bool TBool bool
} }
@ -1337,18 +1552,22 @@ func Test_SetValWithStruct(t *testing.T) {
SetValWithStruct(p, "param", args{ SetValWithStruct(p, "param", args{
unexport: 5, unexport: 5,
TInt: 5, TInt: 5,
TUint: 5,
TString: "string", TString: "string",
TFloat: 3.1, TFloat: 3.1,
TComplex: 3 + 4i,
TBool: false, TBool: false,
TSlice: []string{"foo", "bar"}, TSlice: []string{"foo", "bar"},
TIntSlice: []int{1, 2}, TIntSlice: []int{0, 1, 2},
}) })
require.Equal(t, "", string(p.Peek("unexport"))) require.Equal(t, "", string(p.Peek("unexport")))
require.Equal(t, []byte("5"), p.Peek("TInt")) require.Equal(t, []byte("5"), p.Peek("TInt"))
require.Equal(t, []byte("5"), p.Peek("TUint"))
require.Equal(t, []byte("string"), p.Peek("TString")) require.Equal(t, []byte("string"), p.Peek("TString"))
require.Equal(t, []byte("3.1"), p.Peek("TFloat")) require.Equal(t, []byte("3.1"), p.Peek("TFloat"))
require.Equal(t, "", string(p.Peek("TBool"))) require.Equal(t, []byte("(3+4i)"), p.Peek("TComplex"))
require.Equal(t, []byte("false"), p.Peek("TBool"))
require.True(t, func() bool { require.True(t, func() bool {
for _, v := range p.PeekMulti("TSlice") { for _, v := range p.PeekMulti("TSlice") {
if string(v) == "foo" { if string(v) == "foo" {
@ -1367,6 +1586,15 @@ func Test_SetValWithStruct(t *testing.T) {
return false return false
}()) }())
require.True(t, func() bool {
for _, v := range p.PeekMulti("int_slice") {
if string(v) == "0" {
return true
}
}
return false
}())
require.True(t, func() bool { require.True(t, func() bool {
for _, v := range p.PeekMulti("int_slice") { for _, v := range p.PeekMulti("int_slice") {
if string(v) == "1" { if string(v) == "1" {
@ -1442,24 +1670,6 @@ func Test_SetValWithStruct(t *testing.T) {
}()) }())
}) })
t.Run("the zero val should be ignore", func(t *testing.T) {
t.Parallel()
p := &QueryParam{
Args: fasthttp.AcquireArgs(),
}
SetValWithStruct(p, "param", &args{
TInt: 0,
TString: "",
TFloat: 0.0,
})
require.Equal(t, "", string(p.Peek("TInt")))
require.Equal(t, "", string(p.Peek("TString")))
require.Equal(t, "", string(p.Peek("TFloat")))
require.Empty(t, p.PeekMulti("TSlice"))
require.Empty(t, p.PeekMulti("int_slice"))
})
t.Run("error type should ignore", func(t *testing.T) { t.Run("error type should ignore", func(t *testing.T) {
t.Parallel() t.Parallel()
p := &QueryParam{ p := &QueryParam{
@ -1471,14 +1681,16 @@ func Test_SetValWithStruct(t *testing.T) {
} }
func Benchmark_SetValWithStruct(b *testing.B) { func Benchmark_SetValWithStruct(b *testing.B) {
// test SetValWithStruct vai QueryParam struct. // test SetValWithStruct via QueryParam struct.
type args struct { type args struct {
TString string TString string
TSlice []string TSlice []string
TIntSlice []int `param:"int_slice"` TIntSlice []int `param:"int_slice"`
unexport int unexport int
TInt int TInt int
TUint uint
TFloat float64 TFloat float64
TComplex complex128
TBool bool TBool bool
} }
@ -1494,19 +1706,23 @@ func Benchmark_SetValWithStruct(b *testing.B) {
SetValWithStruct(p, "param", args{ SetValWithStruct(p, "param", args{
unexport: 5, unexport: 5,
TInt: 5, TInt: 5,
TUint: 5,
TString: "string", TString: "string",
TFloat: 3.1, TFloat: 3.1,
TComplex: 3 + 4i,
TBool: false, TBool: false,
TSlice: []string{"foo", "bar"}, TSlice: []string{"foo", "bar"},
TIntSlice: []int{1, 2}, TIntSlice: []int{0, 1, 2},
}) })
} }
require.Equal(b, "", string(p.Peek("unexport"))) require.Equal(b, "", string(p.Peek("unexport")))
require.Equal(b, []byte("5"), p.Peek("TInt")) require.Equal(b, []byte("5"), p.Peek("TInt"))
require.Equal(b, []byte("5"), p.Peek("TUint"))
require.Equal(b, []byte("string"), p.Peek("TString")) require.Equal(b, []byte("string"), p.Peek("TString"))
require.Equal(b, []byte("3.1"), p.Peek("TFloat")) require.Equal(b, []byte("3.1"), p.Peek("TFloat"))
require.Equal(b, "", string(p.Peek("TBool"))) require.Equal(b, []byte("(3+4i)"), p.Peek("TComplex"))
require.Equal(b, []byte("false"), p.Peek("TBool"))
require.True(b, func() bool { require.True(b, func() bool {
for _, v := range p.PeekMulti("TSlice") { for _, v := range p.PeekMulti("TSlice") {
if string(v) == "foo" { if string(v) == "foo" {
@ -1525,6 +1741,15 @@ func Benchmark_SetValWithStruct(b *testing.B) {
return false return false
}()) }())
require.True(b, func() bool {
for _, v := range p.PeekMulti("int_slice") {
if string(v) == "0" {
return true
}
}
return false
}())
require.True(b, func() bool { require.True(b, func() bool {
for _, v := range p.PeekMulti("int_slice") { for _, v := range p.PeekMulti("int_slice") {
if string(v) == "1" { if string(v) == "1" {
@ -1604,29 +1829,6 @@ func Benchmark_SetValWithStruct(b *testing.B) {
}()) }())
}) })
b.Run("the zero val should be ignore", func(b *testing.B) {
p := &QueryParam{
Args: fasthttp.AcquireArgs(),
}
b.ReportAllocs()
b.StartTimer()
for i := 0; i < b.N; i++ {
SetValWithStruct(p, "param", &args{
TInt: 0,
TString: "",
TFloat: 0.0,
})
}
require.Empty(b, string(p.Peek("TInt")))
require.Empty(b, string(p.Peek("TString")))
require.Empty(b, string(p.Peek("TFloat")))
require.Empty(b, p.PeekMulti("TSlice"))
require.Empty(b, p.PeekMulti("int_slice"))
})
b.Run("error type should ignore", func(b *testing.B) { b.Run("error type should ignore", func(b *testing.B) {
p := &QueryParam{ p := &QueryParam{
Args: fasthttp.AcquireArgs(), Args: fasthttp.AcquireArgs(),

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
"iter"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
@ -14,7 +15,7 @@ import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
// Response is the result of a request. This object is used to access the response data. // Response represents the result of a request. It provides access to the response data.
type Response struct { type Response struct {
client *Client client *Client
request *Request request *Request
@ -23,76 +24,101 @@ type Response struct {
cookie []*fasthttp.Cookie cookie []*fasthttp.Cookie
} }
// setClient method sets client object in response instance. // setClient sets the client instance in the response. The client object is used by core functionalities.
// Use core object in the client.
func (r *Response) setClient(c *Client) { func (r *Response) setClient(c *Client) {
r.client = c r.client = c
} }
// setRequest method sets Request object in response instance. // setRequest sets the request object in the response. The request is released when Response.Close is called.
// The request will be released when the Response.Close is called.
func (r *Response) setRequest(req *Request) { func (r *Response) setRequest(req *Request) {
r.request = req r.request = req
} }
// Status method returns the HTTP status string for the executed request. // Status returns the HTTP status message of the executed request.
func (r *Response) Status() string { func (r *Response) Status() string {
return string(r.RawResponse.Header.StatusMessage()) return string(r.RawResponse.Header.StatusMessage())
} }
// StatusCode method returns the HTTP status code for the executed request. // StatusCode returns the HTTP status code of the executed request.
func (r *Response) StatusCode() int { func (r *Response) StatusCode() int {
return r.RawResponse.StatusCode() return r.RawResponse.StatusCode()
} }
// Protocol method returns the HTTP response protocol used for the request. // Protocol returns the HTTP protocol used for the request.
func (r *Response) Protocol() string { func (r *Response) Protocol() string {
return string(r.RawResponse.Header.Protocol()) return string(r.RawResponse.Header.Protocol())
} }
// Header method returns the response headers. // Header returns the value of the specified response header field.
func (r *Response) Header(key string) string { func (r *Response) Header(key string) string {
return utils.UnsafeString(r.RawResponse.Header.Peek(key)) return utils.UnsafeString(r.RawResponse.Header.Peek(key))
} }
// Cookies method to access all the response cookies. // Headers returns all headers in the response using an iterator.
// Use maps.Collect() to gather them into a map if needed.
//
// The returned values are valid only until the response object is released.
// Do not store references to returned values; make copies instead.
func (r *Response) Headers() iter.Seq2[string, []string] {
return func(yield func(string, []string) bool) {
keys := r.RawResponse.Header.PeekKeys()
for _, key := range keys {
vals := r.RawResponse.Header.PeekAll(utils.UnsafeString(key))
valsStr := make([]string, len(vals))
for i, v := range vals {
valsStr[i] = utils.UnsafeString(v)
}
if !yield(utils.UnsafeString(key), valsStr) {
return
}
}
}
}
// Cookies returns all cookies set by the response.
//
// The returned values are valid only until the response object is released.
// Do not store references to returned values; make copies instead.
func (r *Response) Cookies() []*fasthttp.Cookie { func (r *Response) Cookies() []*fasthttp.Cookie {
return r.cookie return r.cookie
} }
// Body method returns HTTP response as []byte array for the executed request. // Body returns the HTTP response body as a byte slice.
func (r *Response) Body() []byte { func (r *Response) Body() []byte {
return r.RawResponse.Body() return r.RawResponse.Body()
} }
// String method returns the body of the server response as String. // String returns the response body as a trimmed string.
func (r *Response) String() string { func (r *Response) String() string {
return utils.Trim(string(r.Body()), ' ') return utils.Trim(string(r.Body()), ' ')
} }
// JSON method will unmarshal body to json. // JSON unmarshals the response body into the given interface{} using JSON.
func (r *Response) JSON(v any) error { func (r *Response) JSON(v any) error {
return r.client.jsonUnmarshal(r.Body(), v) return r.client.jsonUnmarshal(r.Body(), v)
} }
// CBOR method will unmarshal body to CBOR. // CBOR unmarshals the response body into the given interface{} using CBOR.
func (r *Response) CBOR(v any) error { func (r *Response) CBOR(v any) error {
return r.client.cborUnmarshal(r.Body(), v) return r.client.cborUnmarshal(r.Body(), v)
} }
// XML method will unmarshal body to xml. // XML unmarshals the response body into the given interface{} using XML.
func (r *Response) XML(v any) error { func (r *Response) XML(v any) error {
return r.client.xmlUnmarshal(r.Body(), v) return r.client.xmlUnmarshal(r.Body(), v)
} }
// Save method will save the body to a file or io.Writer. // Save writes the response body to a file or io.Writer.
// If a string path is provided, it creates directories if needed, then writes to a file.
// If an io.Writer is provided, it writes directly to it.
func (r *Response) Save(v any) error { func (r *Response) Save(v any) error {
switch p := v.(type) { switch p := v.(type) {
case string: case string:
file := filepath.Clean(p) file := filepath.Clean(p)
dir := filepath.Dir(file) dir := filepath.Dir(file)
// create directory // Create directory if it doesn't exist
if _, err := os.Stat(dir); err != nil { if _, err := os.Stat(dir); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("failed to check directory: %w", err) return fmt.Errorf("failed to check directory: %w", err)
@ -103,22 +129,21 @@ func (r *Response) Save(v any) error {
} }
} }
// create file // Create and write to file
outFile, err := os.Create(file) outFile, err := os.Create(file)
if err != nil { if err != nil {
return fmt.Errorf("failed to create file: %w", err) return fmt.Errorf("failed to create file: %w", err)
} }
defer func() { _ = outFile.Close() }() //nolint:errcheck // not needed defer func() { _ = outFile.Close() }() //nolint:errcheck // not needed
_, err = io.Copy(outFile, bytes.NewReader(r.Body())) if _, err = io.Copy(outFile, bytes.NewReader(r.Body())); err != nil {
if err != nil {
return fmt.Errorf("failed to write response body to file: %w", err) return fmt.Errorf("failed to write response body to file: %w", err)
} }
return nil return nil
case io.Writer: case io.Writer:
_, err := io.Copy(p, bytes.NewReader(r.Body())) if _, err := io.Copy(p, bytes.NewReader(r.Body())); err != nil {
if err != nil {
return fmt.Errorf("failed to write response body to io.Writer: %w", err) return fmt.Errorf("failed to write response body to io.Writer: %w", err)
} }
defer func() { defer func() {
@ -126,14 +151,14 @@ func (r *Response) Save(v any) error {
_ = pc.Close() //nolint:errcheck // not needed _ = pc.Close() //nolint:errcheck // not needed
} }
}() }()
return nil return nil
default: default:
return ErrNotSupportSaveMethod return ErrNotSupportSaveMethod
} }
} }
// Reset clears the Response object. // Reset clears the Response object, making it ready for reuse.
func (r *Response) Reset() { func (r *Response) Reset() {
r.client = nil r.client = nil
r.request = nil r.request = nil
@ -147,8 +172,8 @@ func (r *Response) Reset() {
r.RawResponse.Reset() r.RawResponse.Reset()
} }
// Close method will release Request object and Response object, // Close releases both the Request and Response objects back to their pools.
// after call Close please don't use these object. // After calling Close, do not use these objects.
func (r *Response) Close() { func (r *Response) Close() {
if r.request != nil { if r.request != nil {
tmp := r.request tmp := r.request
@ -167,10 +192,8 @@ var responsePool = &sync.Pool{
}, },
} }
// AcquireResponse returns an empty response object from the pool. // AcquireResponse returns a new (pooled) Response object.
// // When done, release it with ReleaseResponse to reduce GC load.
// The returned response may be returned to the pool with ReleaseResponse when no longer needed.
// This allows reducing GC load.
func AcquireResponse() *Response { func AcquireResponse() *Response {
resp, ok := responsePool.Get().(*Response) resp, ok := responsePool.Get().(*Response)
if !ok { if !ok {
@ -179,9 +202,8 @@ func AcquireResponse() *Response {
return resp return resp
} }
// ReleaseResponse returns the object acquired via AcquireResponse to the pool. // ReleaseResponse returns the Response object to the pool.
// // Do not use the released Response afterward to avoid data races.
// Do not access the released Response object, otherwise data races may occur.
func ReleaseResponse(resp *Response) { func ReleaseResponse(resp *Response) {
resp.Reset() resp.Reset()
responsePool.Put(resp) responsePool.Put(resp)

View File

@ -199,6 +199,85 @@ func Test_Response_Header(t *testing.T) {
resp.Close() resp.Close()
} }
func Test_Response_Headers(t *testing.T) {
t.Parallel()
server := startTestServer(t, func(app *fiber.App) {
app.Get("/", func(c fiber.Ctx) error {
c.Response().Header.Add("foo", "bar")
c.Response().Header.Add("foo", "bar2")
c.Response().Header.Add("foo2", "bar")
return c.SendString("hello world")
})
})
defer server.stop()
client := New().SetDial(server.dial())
resp, err := AcquireRequest().
SetClient(client).
Get("http://example.com")
require.NoError(t, err)
headers := make(map[string][]string)
for k, v := range resp.Headers() {
headers[k] = append(headers[k], v...)
}
require.Equal(t, "hello world", resp.String())
require.Contains(t, headers["Foo"], "bar")
require.Contains(t, headers["Foo"], "bar2")
require.Contains(t, headers["Foo2"], "bar")
require.Len(t, headers, 3) // Foo + Foo2 + Date
resp.Close()
}
func Benchmark_Headers(b *testing.B) {
server := startTestServer(
b,
func(app *fiber.App) {
app.Get("/", func(c fiber.Ctx) error {
c.Response().Header.Add("foo", "bar")
c.Response().Header.Add("foo", "bar2")
c.Response().Header.Add("foo", "bar3")
c.Response().Header.Add("foo2", "bar")
c.Response().Header.Add("foo2", "bar2")
c.Response().Header.Add("foo2", "bar3")
return c.SendString("helo world")
})
},
)
client := New().SetDial(server.dial())
resp, err := AcquireRequest().
SetClient(client).
Get("http://example.com")
require.NoError(b, err)
b.Cleanup(func() {
resp.Close()
server.stop()
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for k, v := range resp.Headers() {
_ = k
_ = v
}
}
}
func Test_Response_Cookie(t *testing.T) { func Test_Response_Cookie(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -116,6 +116,8 @@ app.Listen(":8080", fiber.ListenConfig{
| <Reference id="onshutdownerror">OnShutdownError</Reference> | `func(err error)` | Allows to customize error behavior when gracefully shutting down the server by given signal. Prints error with `log.Fatalf()` | `nil` | | <Reference id="onshutdownerror">OnShutdownError</Reference> | `func(err error)` | Allows to customize error behavior when gracefully shutting down the server by given signal. Prints error with `log.Fatalf()` | `nil` |
| <Reference id="onshutdownsuccess">OnShutdownSuccess</Reference> | `func()` | Allows customizing success behavior when gracefully shutting down the server by given signal. | `nil` | | <Reference id="onshutdownsuccess">OnShutdownSuccess</Reference> | `func()` | Allows customizing success behavior when gracefully shutting down the server by given signal. | `nil` |
| <Reference id="tlsconfigfunc">TLSConfigFunc</Reference> | `func(tlsConfig *tls.Config)` | Allows customizing `tls.Config` as you want. | `nil` | | <Reference id="tlsconfigfunc">TLSConfigFunc</Reference> | `func(tlsConfig *tls.Config)` | Allows customizing `tls.Config` as you want. | `nil` |
| <Reference id="autocertmanager">AutoCertManager</Reference> | `*autocert.Manager` | Manages TLS certificates automatically using the ACME protocol. Enables integration with Let's Encrypt or other ACME-compatible providers. | `nil` |
| <Reference id="tlsminversion">TLSMinVersion</Reference> | `uint16` | Allows customizing the TLS minimum version. | `tls.VersionTLS12` |
### Listen ### Listen
@ -166,6 +168,25 @@ app.Listen(":443", fiber.ListenConfig{CertClientFile: "./ca-chain-cert.pem"})
app.Listen(":443", fiber.ListenConfig{CertFile: "./cert.pem", CertKeyFile: "./cert.key", CertClientFile: "./ca-chain-cert.pem"}) app.Listen(":443", fiber.ListenConfig{CertFile: "./cert.pem", CertKeyFile: "./cert.key", CertClientFile: "./ca-chain-cert.pem"})
``` ```
#### TLS AutoCert support (ACME / Let's Encrypt)
Provides automatic access to certificates management from Let's Encrypt and any other ACME-based providers.
```go title="Examples"
// Certificate manager
certManager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
// Replace with your domain name
HostPolicy: autocert.HostWhitelist("example.com"),
// Folder to store the certificates
Cache: autocert.DirCache("./certs"),
}
app.Listen(":444", fiber.ListenConfig{
AutoCertManager: certManager,
})
```
### Listener ### Listener
You can pass your own [`net.Listener`](https://pkg.go.dev/net/#Listener) using the `Listener` method. This method can be used to enable **TLS/HTTPS** with a custom tls.Config. You can pass your own [`net.Listener`](https://pkg.go.dev/net/#Listener) using the `Listener` method. This method can be used to enable **TLS/HTTPS** with a custom tls.Config.

View File

@ -2,19 +2,35 @@
id: hooks id: hooks
title: 🎣 Hooks title: 🎣 Hooks
description: >- description: >-
Hooks are used to manipulate request/response process of Fiber client. Hooks are used to manipulate the request/response process of the Fiber client.
sidebar_position: 4 sidebar_position: 4
--- ---
With hooks, you can manipulate the client on before request/after response stages or more complex logging/tracing cases. Hooks allow you to intercept and modify the request or response flow of the Fiber client. They are particularly useful for:
There are 2 kinds of hooks: - Changing request parameters (e.g., URL, headers) before sending the request.
- Logging request and response details.
- Integrating complex tracing or monitoring tools.
- Handling authentication, retries, or other custom logic.
There are two kinds of hooks:
## Request Hooks ## Request Hooks
They are called before the HTTP request has been sent. You can use them make changes on Request object. **Request hooks** are functions executed before the HTTP request is sent. They follow the signature:
You need to use `RequestHook func(*Client, *Request) error` function signature while creating the hooks. You can use request hooks to change host URL, log request properties etc. Here is an example about how to create request hooks: ```go
type RequestHook func(*Client, *Request) error
```
A request hook receives both the `Client` and the `Request` objects, allowing you to modify the request before it leaves your application. For example, you could:
- Change the host URL.
- Log request details (method, URL, headers).
- Add or modify headers or query parameters.
- Intercept and apply custom authentication logic.
**Example:**
```go ```go
type Repository struct { type Repository struct {
@ -31,9 +47,9 @@ type Repository struct {
func main() { func main() {
cc := client.New() cc := client.New()
// Add a request hook that modifies the request URL before sending.
cc.AddRequestHook(func(c *client.Client, r *client.Request) error { cc.AddRequestHook(func(c *client.Client, r *client.Request) error {
r.SetURL("https://api.github.com/" + r.URL()) r.SetURL("https://api.github.com/" + r.URL())
return nil return nil
}) })
@ -48,7 +64,6 @@ func main() {
} }
fmt.Printf("Status code: %d\n", resp.StatusCode()) fmt.Printf("Status code: %d\n", resp.StatusCode())
fmt.Printf("Repository: %s\n", repo.FullName) fmt.Printf("Repository: %s\n", repo.FullName)
fmt.Printf("Description: %s\n", repo.Description) fmt.Printf("Description: %s\n", repo.Description)
fmt.Printf("Homepage: %s\n", repo.Homepage) fmt.Printf("Homepage: %s\n", repo.Homepage)
@ -73,18 +88,20 @@ Full Name: gofiber/fiber
</details> </details>
There are also some builtin request hooks provide some functionalities for Fiber client. Here is a list of them: ### Built-in Request Hooks
- [parserRequestURL](https://github.com/gofiber/fiber/blob/main/client/hooks.go#L62): parserRequestURL customizes the URL according to the path params and query params. It's necessary for `PathParam` and `QueryParam` methods. Fiber provides some built-in request hooks:
- [parserRequestHeader](https://github.com/gofiber/fiber/blob/main/client/hooks.go#L113): parserRequestHeader sets request headers, cookies, body type, referer, user agent according to client and request properties. It's necessary to make request header and cookiejar methods functional. - **parserRequestURL**: Normalizes and customizes the URL based on path and query parameters. Required for `PathParam` and `QueryParam` methods.
- **parserRequestHeader**: Sets request headers, cookies, content type, referer, and user agent based on client and request properties.
- [parserRequestBody](https://github.com/gofiber/fiber/blob/main/client/hooks.go#L178): parserRequestBody serializes the body automatically. It is useful for XML, JSON, form, file bodies. - **parserRequestBody**: Automatically serializes the request body (JSON, XML, form, file uploads, etc.).
:::info :::info
If any error returns from request hook execution, it will interrupt the request and return the error. If any request hook returns an error, the request is interrupted and the error is returned immediately.
::: :::
**Example with Multiple Hooks:**
```go ```go
func main() { func main() {
cc := client.New() cc := client.New()
@ -123,9 +140,15 @@ exit status 2
## Response Hooks ## Response Hooks
They are called after the HTTP response has been completed. You can use them to get some information about response and request. **Response hooks** are functions executed after the HTTP response is received. They follow the signature:
You need to use `ResponseHook func(*Client, *Response, *Request) error` function signature while creating the hooks. You can use response hook for logging, tracing etc. Here is an example about how to create response hooks: ```go
type ResponseHook func(*Client, *Response, *Request) error
```
A response hook receives the `Client`, `Response`, and `Request` objects, allowing you to inspect and modify the response or perform additional actions such as logging, tracing, or processing response data.
**Example:**
```go ```go
func main() { func main() {
@ -173,16 +196,19 @@ X-Cache: HIT
</details> </details>
There are also some builtin request hooks provide some functionalities for Fiber client. Here is a list of them: ### Built-in Response Hooks
- [parserResponseCookie](https://github.com/gofiber/fiber/blob/main/client/hooks.go#L293): parserResponseCookie parses cookies and saves into the response objects and cookiejar if it's exists. Fiber provides built-in response hooks:
- [logger](https://github.com/gofiber/fiber/blob/main/client/hooks.go#L319): logger prints some RawRequest and RawResponse information. It uses [log.CommonLogger](https://github.com/gofiber/fiber/blob/main/log/log.go#L49) interface for logging. - **parserResponseCookie**: Parses cookies from the response and stores them in the response object and cookie jar if available.
- **logger**: Logs information about the raw request and response. It uses the `log.CommonLogger` interface.
:::info :::info
If any error is returned from executing the response hook, it will return the error without executing other response hooks. If a response hook returns an error, it stops executing any further hooks and returns the error.
::: :::
**Example with Multiple Response Hooks:**
```go ```go
func main() { func main() {
cc := client.New() cc := client.New()
@ -225,9 +251,11 @@ exit status 2
</details> </details>
:::info ## Hook Execution Order
Hooks work as FIFO (first-in-first-out). You need to check the order while adding the hooks.
::: Hooks run in FIFO order (First-In-First-Out). That means hooks are executed in the order they were added. Keep this in mind when adding multiple hooks, as the order can affect the outcome.
**Example:**
```go ```go
func main() { func main() {

File diff suppressed because it is too large Load Diff

View File

@ -6,30 +6,27 @@ description: >-
sidebar_position: 3 sidebar_position: 3
--- ---
The `Response` structure in Gofiber's HTTP client represents the server's response to an HTTP request. It contains all the necessary information received from the server. This includes: The `Response` structure in Gofiber's HTTP client represents the server's response to an HTTP request. It includes:
- **Status Code**: The HTTP status code returned by the server (e.g., 200 OK, 404 Not Found). - **Status Code**: The HTTP status code returned by the server (e.g., `200 OK`, `404 Not Found`).
- **Headers**: HTTP headers received from the server that provide additional information about the response. - **Headers**: All HTTP headers returned by the server, providing additional response-related information.
- **Body**: The data received from the server, typically in the form of a JSON, XML, or plain text format. - **Body**: The response body content, which can be JSON, XML, plain text, or other formats.
- **Cookies**: Any cookies sent by the server along with the response. - **Cookies**: Any cookies the server sent along with the response.
This structure allows users to easily access and manage the data returned by the server, facilitating efficient handling of HTTP responses. This structure makes it easy to inspect and handle the data sent back by the server.
```go ```go
type Response struct { type Response struct {
client *Client client *Client
request *Request request *Request
cookie []*fasthttp.Cookie cookie []*fasthttp.Cookie
RawResponse *fasthttp.Response RawResponse *fasthttp.Response
} }
``` ```
## AcquireResponse ## AcquireResponse
AcquireResponse returns an empty response object from the pool. **AcquireResponse** returns a new (pooled) `Response` object. When finished, release it using `ReleaseResponse` to reduce GC overhead.
The returned response may be returned to the pool with ReleaseResponse when no longer needed.
This allows reducing GC load.
```go title="Signature" ```go title="Signature"
func AcquireResponse() *Response func AcquireResponse() *Response
@ -37,8 +34,7 @@ func AcquireResponse() *Response
## ReleaseResponse ## ReleaseResponse
ReleaseResponse returns the object acquired via AcquireResponse to the pool. **ReleaseResponse** returns the `Response` object to the pool. Avoid using the response after releasing it to prevent data races.
Do not access the released Response object; otherwise, data races may occur.
```go title="Signature" ```go title="Signature"
func ReleaseResponse(resp *Response) func ReleaseResponse(resp *Response)
@ -46,7 +42,7 @@ func ReleaseResponse(resp *Response)
## Status ## Status
Status method returns the HTTP status string for the executed request. **Status** returns the HTTP status message (e.g., `OK`, `Not Found`) associated with the response.
```go title="Signature" ```go title="Signature"
func (r *Response) Status() string func (r *Response) Status() string
@ -54,7 +50,7 @@ func (r *Response) Status() string
## StatusCode ## StatusCode
StatusCode method returns the HTTP status code for the executed request. **StatusCode** returns the numeric HTTP status code of the response.
```go title="Signature" ```go title="Signature"
func (r *Response) StatusCode() int func (r *Response) StatusCode() int
@ -62,12 +58,15 @@ func (r *Response) StatusCode() int
## Protocol ## Protocol
Protocol method returns the HTTP response protocol used for the request. **Protocol** returns the HTTP protocol used (e.g., `HTTP/1.1`, `HTTP/2`) for the response.
```go title="Signature" ```go title="Signature"
func (r *Response) Protocol() string func (r *Response) Protocol() string
``` ```
<details>
<summary>Example</summary>
```go title="Example" ```go title="Example"
resp, err := client.Get("https://httpbin.org/get") resp, err := client.Get("https://httpbin.org/get")
if err != nil { if err != nil {
@ -77,8 +76,7 @@ if err != nil {
fmt.Println(resp.Protocol()) fmt.Println(resp.Protocol())
``` ```
<details> **Output:**
<summary>Click here to see the result</summary>
```text ```text
HTTP/1.1 HTTP/1.1
@ -88,20 +86,82 @@ HTTP/1.1
## Header ## Header
Header method returns the response headers. **Header** retrieves the value of a specific response header by key. If multiple values exist for the same header, this returns the first one.
```go title="Signature" ```go title="Signature"
func (r *Response) Header(key string) string func (r *Response) Header(key string) string
``` ```
## Headers
**Headers** returns an iterator over all response headers. Use `maps.Collect()` to convert them into a map if desired. The returned values are only valid until the response is released, so make copies if needed.
```go title="Signature"
func (r *Response) Headers() iter.Seq2[string, []string]
```
<details>
<summary>Example</summary>
```go title="Example"
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
panic(err)
}
for key, values := range resp.Headers() {
fmt.Printf("%s => %s\n", key, strings.Join(values, ", "))
}
```
**Output:**
```text
Date => Wed, 04 Dec 2024 15:28:29 GMT
Connection => keep-alive
Access-Control-Allow-Origin => *
Access-Control-Allow-Credentials => true
```
</details>
<details>
<summary>Example with maps.Collect()</summary>
```go title="Example with maps.Collect()"
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
panic(err)
}
headers := maps.Collect(resp.Headers())
for key, values := range headers {
fmt.Printf("%s => %s\n", key, strings.Join(values, ", "))
}
```
**Output:**
```text
Date => Wed, 04 Dec 2024 15:28:29 GMT
Connection => keep-alive
Access-Control-Allow-Origin => *
Access-Control-Allow-Credentials => true
```
</details>
## Cookies ## Cookies
Cookies method to access all the response cookies. **Cookies** returns a slice of all cookies set by the server in this response. The slice is only valid until the response is released.
```go title="Signature" ```go title="Signature"
func (r *Response) Cookies() []*fasthttp.Cookie func (r *Response) Cookies() []*fasthttp.Cookie
``` ```
<details>
<summary>Example</summary>
```go title="Example" ```go title="Example"
resp, err := client.Get("https://httpbin.org/cookies/set/go/fiber") resp, err := client.Get("https://httpbin.org/cookies/set/go/fiber")
if err != nil { if err != nil {
@ -114,8 +174,7 @@ for _, cookie := range cookies {
} }
``` ```
<details> **Output:**
<summary>Click here to see the result</summary>
```text ```text
go => fiber go => fiber
@ -125,7 +184,7 @@ go => fiber
## Body ## Body
Body method returns HTTP response as []byte array for the executed request. **Body** returns the raw response body as a byte slice.
```go title="Signature" ```go title="Signature"
func (r *Response) Body() []byte func (r *Response) Body() []byte
@ -133,7 +192,7 @@ func (r *Response) Body() []byte
## String ## String
String method returns the body of the server response as String. **String** returns the response body as a trimmed string.
```go title="Signature" ```go title="Signature"
func (r *Response) String() string func (r *Response) String() string
@ -141,12 +200,15 @@ func (r *Response) String() string
## JSON ## JSON
JSON method will unmarshal body to json. **JSON** unmarshals the response body into the provided variable `v` using JSON. `v` should be a pointer to a struct or a type compatible with JSON unmarshalling.
```go title="Signature" ```go title="Signature"
func (r *Response) JSON(v any) error func (r *Response) JSON(v any) error
``` ```
<details>
<summary>Example</summary>
```go title="Example" ```go title="Example"
type Body struct { type Body struct {
Slideshow struct { Slideshow struct {
@ -162,16 +224,14 @@ if err != nil {
panic(err) panic(err)
} }
err = resp.JSON(&out) if err = resp.JSON(&out); err != nil {
if err != nil {
panic(err) panic(err)
} }
fmt.Printf("%+v\n", out) fmt.Printf("%+v\n", out)
``` ```
<details> **Output:**
<summary>Click here to see the result</summary>
```text ```text
{Slideshow:{Author:Yours Truly Date:date of publication Title:Sample Slide Show}} {Slideshow:{Author:Yours Truly Date:date of publication Title:Sample Slide Show}}
@ -181,7 +241,7 @@ fmt.Printf("%+v\n", out)
## XML ## XML
XML method will unmarshal body to xml. **XML** unmarshals the response body into the provided variable `v` using XML decoding.
```go title="Signature" ```go title="Signature"
func (r *Response) XML(v any) error func (r *Response) XML(v any) error
@ -189,7 +249,7 @@ func (r *Response) XML(v any) error
## CBOR ## CBOR
CBOR method will unmarshal body to CBOR. **CBOR** unmarshals the response body into `v` using CBOR decoding.
```go title="Signature" ```go title="Signature"
func (r *Response) CBOR(v any) error func (r *Response) CBOR(v any) error
@ -197,7 +257,7 @@ func (r *Response) CBOR(v any) error
## Save ## Save
Save method will save the body to a file or io.Writer. **Save** writes the response body to a file or an `io.Writer`. If `v` is a string, it interprets it as a file path, creates the file (and directories if needed), and writes the response to it. If `v` is an `io.Writer`, it writes directly to it.
```go title="Signature" ```go title="Signature"
func (r *Response) Save(v any) error func (r *Response) Save(v any) error
@ -205,7 +265,7 @@ func (r *Response) Save(v any) error
## Reset ## Reset
Reset clears the Response object. **Reset** clears the `Response` object, making it ready for reuse by `ReleaseResponse`.
```go title="Signature" ```go title="Signature"
func (r *Response) Reset() func (r *Response) Reset()
@ -213,7 +273,11 @@ func (r *Response) Reset()
## Close ## Close
Close method will release the Request and Response objects; after calling Close, please do not use these objects. **Close** releases both the associated `Request` and `Response` objects back to their pools.
:::warning
After calling `Close`, any attempt to use the request or response may result in data races or undefined behavior. Ensure all processing is complete before closing.
:::
```go title="Signature" ```go title="Signature"
func (r *Response) Close() func (r *Response) Close()

View File

@ -7,18 +7,18 @@ sidebar_position: 1
toc_max_heading_level: 5 toc_max_heading_level: 5
--- ---
The Fiber Client for Fiber v3 is a powerful HTTP client optimized for high performance and ease of use in server-side applications. Built on top of the robust FastHTTP library, it inherits FastHTTP's high-speed HTTP protocol implementation. The client is designed to make HTTP requests both internally within services or externally to other web services. The Fiber Client for Fiber v3 is a powerful HTTP client optimized for high performance and ease of use in server-side applications. Built atop the FastHTTP library, it inherits FastHTTP's high-speed HTTP protocol implementation. The client is designed for making both internal requests (within a microservices architecture) and external requests to other web services.
## Features ## Features
- **Lightweight & Fast**: Leveraging the minimalistic design of FastHTTP, the Fiber Client is lightweight and extremely fast. - **Lightweight & Fast**: Due to its FastHTTP foundation, the Fiber Client is both lightweight and extremely performant.
- **Flexible Configuration**: Configure client-level settings such as timeouts, headers, and more, which apply to all requests. Specific requests can further override or merge these settings. - **Flexible Configuration**: Set client-level configurations (e.g., timeouts, headers) that apply to all requests, while still allowing overrides at the individual request level.
- **Connection Pooling**: Manages a pool of persistent connections that reduce the overhead of repeatedly establishing connections. - **Connection Pooling**: Maintains a pool of persistent connections, minimizing the overhead of establishing new connections for each request.
- **Timeouts & Retries**: Supports setting request timeouts and retry mechanisms to handle transient failures. - **Timeouts & Retries**: Supports request-level timeouts and configurable retries to handle transient errors gracefully.
## Usage ## Usage
To use the Fiber Client, instantiate it with the desired configuration. Here's a simple example: Instantiate the Fiber Client with your desired configurations, then send requests:
```go ```go
package main package main
@ -34,7 +34,7 @@ func main() {
cc := client.New() cc := client.New()
cc.SetTimeout(10 * time.Second) cc.SetTimeout(10 * time.Second)
// Get request // Send a GET request
resp, err := cc.Get("https://httpbin.org/get") resp, err := cc.Get("https://httpbin.org/get")
if err != nil { if err != nil {
panic(err) panic(err)
@ -45,7 +45,7 @@ func main() {
} }
``` ```
You can check out [examples](examples.md) for more examples! Check out [examples](examples.md) for more detailed usage examples.
```go ```go
type Client struct { type Client struct {
@ -65,16 +65,16 @@ type Client struct {
timeout time.Duration timeout time.Duration
// user defined request hooks // user-defined request hooks
userRequestHooks []RequestHook userRequestHooks []RequestHook
// client package defined request hooks // client package-defined request hooks
builtinRequestHooks []RequestHook builtinRequestHooks []RequestHook
// user defined response hooks // user-defined response hooks
userResponseHooks []ResponseHook userResponseHooks []ResponseHook
// client package defined response hooks // client package-defined response hooks
builtinResponseHooks []ResponseHook builtinResponseHooks []ResponseHook
jsonMarshal utils.JSONMarshal jsonMarshal utils.JSONMarshal
@ -99,7 +99,7 @@ type Client struct {
### New ### New
New creates and returns a new Client object. **New** creates and returns a new Client object.
```go title="Signature" ```go title="Signature"
func New() *Client func New() *Client
@ -107,7 +107,7 @@ func New() *Client
### NewWithClient ### NewWithClient
NewWithClient creates and returns a new Client object from an existing client object. **NewWithClient** creates and returns a new Client object from an existing `fasthttp.Client`.
```go title="Signature" ```go title="Signature"
func NewWithClient(c *fasthttp.Client) *Client func NewWithClient(c *fasthttp.Client) *Client
@ -115,9 +115,11 @@ func NewWithClient(c *fasthttp.Client) *Client
## REST Methods ## REST Methods
The following methods send HTTP requests using the configured client:
### Get ### Get
Get provides an API like axios which sends a get request. Sends a GET request, similar to axios.
```go title="Signature" ```go title="Signature"
func (c *Client) Get(url string, cfg ...Config) (*Response, error) func (c *Client) Get(url string, cfg ...Config) (*Response, error)
@ -125,7 +127,7 @@ func (c *Client) Get(url string, cfg ...Config) (*Response, error)
### Post ### Post
Post provides an API like axios which send post request. Sends a POST request, similar to axios.
```go title="Signature" ```go title="Signature"
func (c *Client) Post(url string, cfg ...Config) (*Response, error) func (c *Client) Post(url string, cfg ...Config) (*Response, error)
@ -133,7 +135,7 @@ func (c *Client) Post(url string, cfg ...Config) (*Response, error)
### Put ### Put
Put provides an API like axios which send put request. Sends a PUT request, similar to axios.
```go title="Signature" ```go title="Signature"
func (c *Client) Put(url string, cfg ...Config) (*Response, error) func (c *Client) Put(url string, cfg ...Config) (*Response, error)
@ -141,7 +143,7 @@ func (c *Client) Put(url string, cfg ...Config) (*Response, error)
### Patch ### Patch
Patch provides an API like axios which send patch request. Sends a PATCH request, similar to axios.
```go title="Signature" ```go title="Signature"
func (c *Client) Patch(url string, cfg ...Config) (*Response, error) func (c *Client) Patch(url string, cfg ...Config) (*Response, error)
@ -149,7 +151,7 @@ func (c *Client) Patch(url string, cfg ...Config) (*Response, error)
### Delete ### Delete
Delete provides an API like axios which send delete request. Sends a DELETE request, similar to axios.
```go title="Signature" ```go title="Signature"
func (c *Client) Delete(url string, cfg ...Config) (*Response, error) func (c *Client) Delete(url string, cfg ...Config) (*Response, error)
@ -157,7 +159,7 @@ func (c *Client) Delete(url string, cfg ...Config) (*Response, error)
### Head ### Head
Head provides an API like axios which send head request. Sends a HEAD request, similar to axios.
```go title="Signature" ```go title="Signature"
func (c *Client) Head(url string, cfg ...Config) (*Response, error) func (c *Client) Head(url string, cfg ...Config) (*Response, error)
@ -165,7 +167,7 @@ func (c *Client) Head(url string, cfg ...Config) (*Response, error)
### Options ### Options
Options provides an API like axios which send options request. Sends an OPTIONS request, similar to axios.
```go title="Signature" ```go title="Signature"
func (c *Client) Options(url string, cfg ...Config) (*Response, error) func (c *Client) Options(url string, cfg ...Config) (*Response, error)
@ -173,7 +175,7 @@ func (c *Client) Options(url string, cfg ...Config) (*Response, error)
### Custom ### Custom
Custom provides an API like axios which send custom request. Sends a custom HTTP request, similar to axios, allowing you to specify any method.
```go title="Signature" ```go title="Signature"
func (c *Client) Custom(url, method string, cfg ...Config) (*Response, error) func (c *Client) Custom(url, method string, cfg ...Config) (*Response, error)
@ -181,9 +183,11 @@ func (c *Client) Custom(url, method string, cfg ...Config) (*Response, error)
## Request Configuration ## Request Configuration
Config for easy to set the request parameters, it should be noted that when setting the request body will use JSON as the default serialization mechanism, while the priority of Body is higher than FormData, and the priority of FormData is higher than File. The `Config` type helps configure request parameters. When setting the request body, JSON is used as the default serialization. The priority of the body sources is:
It can be used to configure request data while sending requests using Get, Post, etc. 1. Body
2. FormData
3. File
```go ```go
type Config struct { type Config struct {
@ -205,221 +209,223 @@ type Config struct {
} }
``` ```
### R ## R
R raise a request from the client. **R** creates a new `Request` object from the client's request pool. Use `ReleaseRequest()` to return it to the pool when done.
It acquires a request from the pool. You have to release it using `ReleaseRequest()` when it's no longer needed.
```go title="Signature" ```go title="Signature"
func (c *Client) R() *Request func (c *Client) R() *Request
``` ```
### Hooks ## Hooks
#### RequestHook Hooks allow you to add custom logic before a request is sent or after a response is received.
RequestHook Request returns user-defined request hooks. ### RequestHook
**RequestHook** returns user-defined request hooks.
```go title="Signature" ```go title="Signature"
func (c *Client) RequestHook() []RequestHook func (c *Client) RequestHook() []RequestHook
``` ```
#### ResponseHook ### ResponseHook
ResponseHook return user-define response hooks. **ResponseHook** returns user-defined response hooks.
```go title="Signature" ```go title="Signature"
func (c *Client) ResponseHook() []ResponseHook func (c *Client) ResponseHook() []ResponseHook
``` ```
#### AddRequestHook ### AddRequestHook
AddRequestHook Add user-defined request hooks. Adds one or more user-defined request hooks.
```go title="Signature" ```go title="Signature"
func (c *Client) AddRequestHook(h ...RequestHook) *Client func (c *Client) AddRequestHook(h ...RequestHook) *Client
``` ```
#### AddResponseHook ### AddResponseHook
AddResponseHook Add user-defined response hooks. Adds one or more user-defined response hooks.
```go title="Signature" ```go title="Signature"
func (c *Client) AddResponseHook(h ...ResponseHook) *Client func (c *Client) AddResponseHook(h ...ResponseHook) *Client
``` ```
### JSON ## JSON
#### JSONMarshal ### JSONMarshal
JSONMarshal returns json marshal function in Core. Returns the JSON marshaler function used by the client.
```go title="Signature" ```go title="Signature"
func (c *Client) JSONMarshal() utils.JSONMarshal func (c *Client) JSONMarshal() utils.JSONMarshal
``` ```
#### JSONUnmarshal ### JSONUnmarshal
JSONUnmarshal returns json unmarshal function in Core. Returns the JSON unmarshaller function used by the client.
```go title="Signature" ```go title="Signature"
func (c *Client) JSONUnmarshal() utils.JSONUnmarshal func (c *Client) JSONUnmarshal() utils.JSONUnmarshal
``` ```
#### SetJSONMarshal ### SetJSONMarshal
SetJSONMarshal sets the JSON encoder. Sets a custom JSON marshaler.
```go title="Signature" ```go title="Signature"
func (c *Client) SetJSONMarshal(f utils.JSONMarshal) *Client func (c *Client) SetJSONMarshal(f utils.JSONMarshal) *Client
``` ```
#### SetJSONUnmarshal ### SetJSONUnmarshal
Set the JSON decoder. Sets a custom JSON unmarshaller.
```go title="Signature" ```go title="Signature"
func (c *Client) SetJSONUnmarshal(f utils.JSONUnmarshal) *Client func (c *Client) SetJSONUnmarshal(f utils.JSONUnmarshal) *Client
``` ```
### XML ## XML
#### XMLMarshal ### XMLMarshal
XMLMarshal returns xml marshal function in Core. Returns the XML marshaler function used by the client.
```go title="Signature" ```go title="Signature"
func (c *Client) XMLMarshal() utils.XMLMarshal func (c *Client) XMLMarshal() utils.XMLMarshal
``` ```
#### XMLUnmarshal ### XMLUnmarshal
XMLUnmarshal returns xml unmarshal function in Core. Returns the XML unmarshaller function used by the client.
```go title="Signature" ```go title="Signature"
func (c *Client) XMLUnmarshal() utils.XMLUnmarshal func (c *Client) XMLUnmarshal() utils.XMLUnmarshal
``` ```
#### SetXMLMarshal ### SetXMLMarshal
SetXMLMarshal sets the XML encoder. Sets a custom XML marshaler.
```go title="Signature" ```go title="Signature"
func (c *Client) SetXMLMarshal(f utils.XMLMarshal) *Client func (c *Client) SetXMLMarshal(f utils.XMLMarshal) *Client
``` ```
#### SetXMLUnmarshal ### SetXMLUnmarshal
SetXMLUnmarshal sets the XML decoder. Sets a custom XML unmarshaller.
```go title="Signature" ```go title="Signature"
func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client
``` ```
### CBOR ## CBOR
#### CBORMarshal ### CBORMarshal
CBORMarshal returns CBOR marshal function in Core. Returns the CBOR marshaler function used by the client.
```go title="Signature" ```go title="Signature"
func (c *Client) CBORMarshal() utils.CBORMarshal func (c *Client) CBORMarshal() utils.CBORMarshal
``` ```
#### CBORUnmarshal ### CBORUnmarshal
CBORUnmarshal returns CBOR unmarshal function in Core. Returns the CBOR unmarshaller function used by the client.
```go title="Signature" ```go title="Signature"
func (c *Client) CBORUnmarshal() utils.CBORUnmarshal func (c *Client) CBORUnmarshal() utils.CBORUnmarshal
``` ```
#### SetCBORMarshal ### SetCBORMarshal
SetCBORMarshal sets CBOR encoder. Sets a custom CBOR marshaler.
```go title="Signature" ```go title="Signature"
func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client
``` ```
#### SetCBORUnmarshal ### SetCBORUnmarshal
SetCBORUnmarshal sets CBOR decoder. Sets a custom CBOR unmarshaller.
```go title="Signature" ```go title="Signature"
func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client
``` ```
### TLS ## TLS
#### TLSConfig ### TLSConfig
TLSConfig returns tlsConfig in client. Returns the client's TLS configuration. If none is set, it initializes a new one.
If the client doesn't have a tlsConfig, this function will initialize it.
```go title="Signature" ```go title="Signature"
func (c *Client) TLSConfig() *tls.Config func (c *Client) TLSConfig() *tls.Config
``` ```
#### SetTLSConfig ### SetTLSConfig
SetTLSConfig sets tlsConfig in client. Sets the TLS configuration for the client.
```go title="Signature" ```go title="Signature"
func (c *Client) SetTLSConfig(config *tls.Config) *Client func (c *Client) SetTLSConfig(config *tls.Config) *Client
``` ```
#### SetCertificates ### SetCertificates
SetCertificates method sets client certificates into client. Adds client certificates to the TLS configuration.
```go title="Signature" ```go title="Signature"
func (c *Client) SetCertificates(certs ...tls.Certificate) *Client func (c *Client) SetCertificates(certs ...tls.Certificate) *Client
``` ```
#### SetRootCertificate ### SetRootCertificate
SetRootCertificate adds one or more root certificates into client. Adds one or more root certificates to the client's trust store.
```go title="Signature" ```go title="Signature"
func (c *Client) SetRootCertificate(path string) *Client func (c *Client) SetRootCertificate(path string) *Client
``` ```
#### SetRootCertificateFromString ### SetRootCertificateFromString
SetRootCertificateFromString method adds one or more root certificates into the client. Adds one or more root certificates from a string.
```go title="Signature" ```go title="Signature"
func (c *Client) SetRootCertificateFromString(pem string) *Client func (c *Client) SetRootCertificateFromString(pem string) *Client
``` ```
### SetProxyURL ## SetProxyURL
SetProxyURL sets proxy url in client. It will apply via core to hostclient. Sets a proxy URL for the client. All subsequent requests will use this proxy.
```go title="Signature" ```go title="Signature"
func (c *Client) SetProxyURL(proxyURL string) error func (c *Client) SetProxyURL(proxyURL string) error
``` ```
### RetryConfig ## RetryConfig
RetryConfig returns retry config in client. Returns the retry configuration of the client.
```go title="Signature" ```go title="Signature"
func (c *Client) RetryConfig() *RetryConfig func (c *Client) RetryConfig() *RetryConfig
``` ```
### SetRetryConfig ## SetRetryConfig
SetRetryConfig sets retry config in client, which is impl by addon/retry package. Sets the retry configuration for the client.
```go title="Signature" ```go title="Signature"
func (c *Client) SetRetryConfig(config *RetryConfig) *Client func (c *Client) SetRetryConfig(config *RetryConfig) *Client
``` ```
## BaseURL
### BaseURL ### BaseURL
BaseURL returns baseurl in Client instance. **BaseURL** returns the base URL currently set in the client.
```go title="Signature" ```go title="Signature"
func (c *Client) BaseURL() string func (c *Client) BaseURL() string
@ -427,12 +433,14 @@ func (c *Client) BaseURL() string
### SetBaseURL ### SetBaseURL
SetBaseURL Set baseUrl which is prefix of real url. Sets a base URL prefix for all requests made by the client.
```go title="Signature" ```go title="Signature"
func (c *Client) SetBaseURL(url string) *Client func (c *Client) SetBaseURL(url string) *Client
``` ```
**Example:**
```go title="Example" ```go title="Example"
cc := client.New() cc := client.New()
cc.SetBaseURL("https://httpbin.org/") cc.SetBaseURL("https://httpbin.org/")
@ -457,125 +465,111 @@ fmt.Println(string(resp.Body()))
</details> </details>
## Headers
### Header ### Header
Header method returns header value via key, this method will visit all field in the header Retrieves all values of a header key at the client level. The returned values apply to all requests.
```go title="Signature" ```go title="Signature"
func (c *Client) Header(key string) []string func (c *Client) Header(key string) []string
``` ```
#### AddHeader ### AddHeader
AddHeader method adds a single header field and its value in the client instance. Adds a single header to all requests initiated by this client.
These headers will be applied to all requests raised from this client instance.
Also, it can be overridden at request level header options.
```go title="Signature" ```go title="Signature"
func (c *Client) AddHeader(key, val string) *Client func (c *Client) AddHeader(key, val string) *Client
``` ```
#### SetHeader ### SetHeader
SetHeader method sets a single header field and its value in the client instance. Sets a single header, overriding any existing headers with the same key.
These headers will be applied to all requests raised from this client instance.
Also, it can be overridden at request level header options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetHeader(key, val string) *Client func (c *Client) SetHeader(key, val string) *Client
``` ```
#### AddHeaders ### AddHeaders
AddHeaders method adds multiple headers field and its values at one go in the client instance. Adds multiple headers at once, all applying to all future requests from this client.
These headers will be applied to all requests raised from this client instance.
Also it can be overridden at request level headers options.
```go title="Signature" ```go title="Signature"
func (c *Client) AddHeaders(h map[string][]string) *Client func (c *Client) AddHeaders(h map[string][]string) *Client
``` ```
#### SetHeaders ### SetHeaders
SetHeaders method sets multiple headers field and its values at one go in the client instance. Sets multiple headers at once, overriding previously set headers.
These headers will be applied to all requests raised from this client instance.
Also it can be overridden at request level headers options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetHeaders(h map[string]string) *Client func (c *Client) SetHeaders(h map[string]string) *Client
``` ```
## Query Parameters
### Param ### Param
Param method returns params value via key, this method will visit all field in the query param. Returns the values for a given query parameter key.
```go title="Signature" ```go title="Signature"
func (c *Client) Param(key string) []string func (c *Client) Param(key string) []string
``` ```
#### AddParam ### AddParam
AddParam method adds a single query param field and its value in the client instance. Adds a single query parameter for all requests.
These params will be applied to all requests raised from this client instance.
Also, it can be overridden at request level param options.
```go title="Signature" ```go title="Signature"
func (c *Client) AddParam(key, val string) *Client func (c *Client) AddParam(key, val string) *Client
``` ```
#### SetParam ### SetParam
SetParam method sets a single query param field and its value in the client instance. Sets a single query parameter, overriding previously set values.
These params will be applied to all requests raised from this client instance.
Also, it can be overridden at request level param options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetParam(key, val string) *Client func (c *Client) SetParam(key, val string) *Client
``` ```
#### AddParams ### AddParams
AddParams method adds multiple query params field and its values at one go in the client instance. Adds multiple query parameters from a map of string slices.
These params will be applied to all requests raised from this client instance.
Also it can be overridden at request level params options.
```go title="Signature" ```go title="Signature"
func (c *Client) AddParams(m map[string][]string) *Client func (c *Client) AddParams(m map[string][]string) *Client
``` ```
#### SetParams ### SetParams
SetParams method sets multiple params field and its values at one go in the client instance. Sets multiple query parameters from a map, overriding previously set values.
These params will be applied to all requests raised from this client instance.
Also it can be overridden at request level params options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetParams(m map[string]string) *Client func (c *Client) SetParams(m map[string]string) *Client
``` ```
#### SetParamsWithStruct ### SetParamsWithStruct
SetParamsWithStruct method sets multiple params field and its values at one go in the client instance. Sets multiple query parameters from a struct. Nested structs are not currently supported.
These params will be applied to all requests raised from this client instance.
Also it can be overridden at request level params options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetParamsWithStruct(v any) *Client func (c *Client) SetParamsWithStruct(v any) *Client
``` ```
#### DelParams ### DelParams
DelParams method deletes single or multiple params field and its values in client. Deletes one or more query parameters.
```go title="Signature" ```go title="Signature"
func (c *Client) DelParams(key ...string) *Client func (c *Client) DelParams(key ...string) *Client
``` ```
## UserAgent & Referer
### SetUserAgent ### SetUserAgent
SetUserAgent method sets the userAgent field and its value in the client instance. Sets the user agent header for all requests.
This ua will be applied to all requests raised from this client instance.
Also it can be overridden at request level ua options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetUserAgent(ua string) *Client func (c *Client) SetUserAgent(ua string) *Client
@ -583,80 +577,74 @@ func (c *Client) SetUserAgent(ua string) *Client
### SetReferer ### SetReferer
SetReferer method sets referer field and its value in the client instance. Sets the referer header for all requests.
This referer will be applied to all requests raised from this client instance.
Also it can be overridden at request level referer options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetReferer(r string) *Client func (c *Client) SetReferer(r string) *Client
``` ```
## Path Parameters
### PathParam ### PathParam
PathParam returns the path param be set in request instance. Returns the value of a named path parameter, if set.
If the path param doesn't exist, return empty string.
```go title="Signature" ```go title="Signature"
func (c *Client) PathParam(key string) string func (c *Client) PathParam(key string) string
``` ```
#### SetPathParam ### SetPathParam
SetPathParam method sets a single path param field and its value in the client instance. Sets a single path parameter.
These path params will be applied to all requests raised from this client instance.
Also it can be overridden at request level path params options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetPathParam(key, val string) *Client func (c *Client) SetPathParam(key, val string) *Client
``` ```
#### SetPathParams ### SetPathParams
SetPathParams method sets multiple path params field and its values at one go in the client instance. Sets multiple path parameters at once.
These path params will be applied to all requests raised from this client instance.
Also it can be overridden at request level path params options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetPathParams(m map[string]string) *Client func (c *Client) SetPathParams(m map[string]string) *Client
``` ```
#### SetPathParamsWithStruct ### SetPathParamsWithStruct
SetPathParamsWithStruct method sets multiple path params field and its values at one go in the client instance. Sets multiple path parameters from a struct.
These path params will be applied to all requests raised from this client instance.
Also it can be overridden at request level path params options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetPathParamsWithStruct(v any) *Client func (c *Client) SetPathParamsWithStruct(v any) *Client
``` ```
#### DelPathParams ### DelPathParams
DelPathParams method deletes single or multiple path params field and its values in client. Deletes one or more path parameters.
```go title="Signature" ```go title="Signature"
func (c *Client) DelPathParams(key ...string) *Client func (c *Client) DelPathParams(key ...string) *Client
``` ```
## Cookies
### Cookie ### Cookie
Cookie returns the cookie be set in request instance. Returns the value of a named cookie if set at the client level.
If cookie doesn't exist, return empty string.
```go title="Signature" ```go title="Signature"
func (c *Client) Cookie(key string) string func (c *Client) Cookie(key string) string
``` ```
#### SetCookie ### SetCookie
SetCookie method sets a single cookie field and its value in the client instance. Sets a single cookie for all requests.
These cookies will be applied to all requests raised from this client instance.
Also it can be overridden at request level cookie options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetCookie(key, val string) *Client func (c *Client) SetCookie(key, val string) *Client
``` ```
**Example:**
```go title="Example" ```go title="Example"
cc := client.New() cc := client.New()
cc.SetCookie("john", "doe") cc.SetCookie("john", "doe")
@ -682,71 +670,73 @@ fmt.Println(string(resp.Body()))
</details> </details>
#### SetCookies ### SetCookies
SetCookies method sets multiple cookies field and its values at one go in the client instance. Sets multiple cookies at once.
These cookies will be applied to all requests raised from this client instance.
Also it can be overridden at request level cookie options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetCookies(m map[string]string) *Client func (c *Client) SetCookies(m map[string]string) *Client
``` ```
#### SetCookiesWithStruct ### SetCookiesWithStruct
SetCookiesWithStruct method sets multiple cookies field and its values at one go in the client instance. Sets multiple cookies from a struct.
These cookies will be applied to all requests raised from this client instance.
Also it can be overridden at request level cookies options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetCookiesWithStruct(v any) *Client func (c *Client) SetCookiesWithStruct(v any) *Client
``` ```
#### DelCookies ### DelCookies
DelCookies method deletes single or multiple cookies field and its values in client. Deletes one or more cookies.
```go title="Signature" ```go title="Signature"
func (c *Client) DelCookies(key ...string) *Client func (c *Client) DelCookies(key ...string) *Client
``` ```
## Timeout
### SetTimeout ### SetTimeout
SetTimeout method sets timeout val in client instance. Sets a default timeout for all requests, which can be overridden per request.
This value will be applied to all requests raised from this client instance.
Also, it can be overridden at request level timeout options.
```go title="Signature" ```go title="Signature"
func (c *Client) SetTimeout(t time.Duration) *Client func (c *Client) SetTimeout(t time.Duration) *Client
``` ```
## Debugging
### Debug ### Debug
Debug enable log debug level output. Enables debug-level logging output.
```go title="Signature" ```go title="Signature"
func (c *Client) Debug() *Client func (c *Client) Debug() *Client
``` ```
#### DisableDebug ### DisableDebug
DisableDebug disables log debug level output. Disables debug-level logging output.
```go title="Signature" ```go title="Signature"
func (c *Client) DisableDebug() *Client func (c *Client) DisableDebug() *Client
``` ```
## Cookie Jar
### SetCookieJar ### SetCookieJar
SetCookieJar sets cookie jar in client instance. Assigns a cookie jar to the client to store and manage cookies across requests.
```go title="Signature" ```go title="Signature"
func (c *Client) SetCookieJar(cookieJar *CookieJar) *Client func (c *Client) SetCookieJar(cookieJar *CookieJar) *Client
``` ```
## Dial & Logger
### SetDial ### SetDial
SetDial sets dial function in client. Sets a custom dial function.
```go title="Signature" ```go title="Signature"
func (c *Client) SetDial(dial fasthttp.DialFunc) *Client func (c *Client) SetDial(dial fasthttp.DialFunc) *Client
@ -754,7 +744,7 @@ func (c *Client) SetDial(dial fasthttp.DialFunc) *Client
### SetLogger ### SetLogger
SetLogger sets logger instance in client. Sets the logger instance used by the client.
```go title="Signature" ```go title="Signature"
func (c *Client) SetLogger(logger log.CommonLogger) *Client func (c *Client) SetLogger(logger log.CommonLogger) *Client
@ -762,15 +752,17 @@ func (c *Client) SetLogger(logger log.CommonLogger) *Client
### Logger ### Logger
Logger returns logger instance of client. Returns the current logger instance.
```go title="Signature" ```go title="Signature"
func (c *Client) Logger() log.CommonLogger func (c *Client) Logger() log.CommonLogger
``` ```
## Reset
### Reset ### Reset
Reset clears the Client object Clears and resets the client to its default state.
```go title="Signature" ```go title="Signature"
func (c *Client) Reset() func (c *Client) Reset()
@ -778,12 +770,11 @@ func (c *Client) Reset()
## Default Client ## Default Client
Default client is default client object of Gofiber and created using `New()`. Fiber provides a default client (created with `New()`). You can configure it or replace it as needed.
You can configurate it as you wish or replace it with another clients.
### C ### C
C gets default client. **C** returns the default client.
```go title="Signature" ```go title="Signature"
func C() *Client func C() *Client
@ -847,10 +838,10 @@ func Options(url string, cfg ...Config) (*Response, error)
### Replace ### Replace
Replace the defaultClient, the returned function can undo. **Replace** replaces the default client with a new one. It returns a function that can restore the old client.
:::caution :::caution
The default client should not be changed concurrently. Do not modify the default client concurrently.
::: :::
```go title="Signature" ```go title="Signature"

View File

@ -130,6 +130,33 @@ In this example, a custom context `CustomCtx` is created with an additional meth
</details> </details>
### Configurable TLS Minimum Version
We have added support for configuring the TLS minimum version. This field allows you to set the TLS minimum version for TLSAutoCert and the server listener.
```go
app.Listen(":444", fiber.ListenConfig{TLSMinVersion: tls.VersionTLS12})
```
#### TLS AutoCert support (ACME / Let's Encrypt)
We have added native support for automatic certificates management from Let's Encrypt and any other ACME-based providers.
```go
// Certificate manager
certManager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
// Replace with your domain name
HostPolicy: autocert.HostWhitelist("example.com"),
// Folder to store the certificates
Cache: autocert.DirCache("./certs"),
}
app.Listen(":444", fiber.ListenConfig{
AutoCertManager: certManager,
})
```
## 🗺 Router ## 🗺 Router
We have slightly adapted our router interface We have slightly adapted our router interface
@ -340,7 +367,7 @@ testConfig := fiber.TestConfig{
### SendStreamWriter ### SendStreamWriter
In v3, we added support for buffered streaming by providing the new method `SendStreamWriter()`. In v3, we introduced support for buffered streaming with the addition of the `SendStreamWriter` method:
```go ```go
func (c Ctx) SendStreamWriter(streamWriter func(w *bufio.Writer)) func (c Ctx) SendStreamWriter(streamWriter func(w *bufio.Writer))
@ -553,7 +580,6 @@ func main() {
app.Listen(":3000") app.Listen(":3000")
} }
``` ```
```sh ```sh
@ -590,7 +616,6 @@ func main() {
``` ```
```sh ```sh
curl "http://localhost:3000/query?age=25" curl "http://localhost:3000/query?age=25"
# Output: 25 # Output: 25
@ -902,9 +927,9 @@ app.Route("/api").Route("/user/:id?")
### 🗺 RebuildTree ### 🗺 RebuildTree
We have added a new method that allows the route tree stack to be rebuilt in runtime, with it, you can add a route while your application is running and rebuild the route tree stack to make it registered and available for calls. We introduced a new method that enables rebuilding the route tree stack at runtime. This allows you to add routes dynamically while your application is running and update the route tree to make the new routes available for use.
You can find more reference on it in the [app](./api/app.md#rebuildtree): For more details, refer to the [app documentation](./api/app.md#rebuildtree):
#### Example Usage #### Example Usage
@ -920,10 +945,9 @@ app.Get("/define", func(c Ctx) error { // Define a new route dynamically
}) })
``` ```
In this example, a new route is defined and then `RebuildTree()` is called to make sure the new route is registered and available. In this example, a new route is defined, and `RebuildTree()` is called to ensure the new route is registered and available.
**Note:** Use this method with caution. It is **not** thread-safe and calling it can be very performance-intensive, so it should be used sparingly and only in Note: Use this method with caution. It is **not** thread-safe and can be very performance-intensive. Therefore, it should be used sparingly and primarily in development mode. It should not be invoke concurrently.
development mode. Avoid using it concurrently.
### 🧠 Context ### 🧠 Context

11
go.mod
View File

@ -9,9 +9,10 @@ require (
github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/tinylib/msgp v1.2.4 github.com/tinylib/msgp v1.2.5
github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/bytebufferpool v1.0.0
github.com/valyala/fasthttp v1.57.0 github.com/valyala/fasthttp v1.58.0
golang.org/x/crypto v0.31.0
) )
require ( require (
@ -23,8 +24,8 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/net v0.31.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

22
go.sum
View File

@ -23,26 +23,28 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.2.4 h1:yLFeUGostXXSGW5vxfT5dXG/qzkn4schv2I7at5+hVU= github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
github.com/tinylib/msgp v1.2.4/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg= github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE= github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -23,6 +23,7 @@ import (
"github.com/gofiber/fiber/v3/log" "github.com/gofiber/fiber/v3/log"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"golang.org/x/crypto/acme/autocert"
) )
// Figlet text to show Fiber ASCII art on startup message // Figlet text to show Fiber ASCII art on startup message
@ -69,6 +70,13 @@ type ListenConfig struct {
// //
// Default: nil // Default: nil
OnShutdownSuccess func() OnShutdownSuccess func()
// AutoCertManager manages TLS certificates automatically using the ACME protocol,
// Enables integration with Let's Encrypt or other ACME-compatible providers.
//
// Default: nil
AutoCertManager *autocert.Manager `json:"auto_cert_manager"`
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only) // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only)
// WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen. // WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen.
// //
@ -100,6 +108,12 @@ type ListenConfig struct {
// Default: 10 * time.Second // Default: 10 * time.Second
ShutdownTimeout time.Duration `json:"shutdown_timeout"` ShutdownTimeout time.Duration `json:"shutdown_timeout"`
// TLSMinVersion allows to set TLS minimum version.
//
// Default: tls.VersionTLS12
// WARNING: TLS1.0 and TLS1.1 versions are not supported.
TLSMinVersion uint16 `json:"tls_min_version"`
// When set to true, it will not print out the «Fiber» ASCII art and listening address. // When set to true, it will not print out the «Fiber» ASCII art and listening address.
// //
// Default: false // Default: false
@ -120,6 +134,7 @@ type ListenConfig struct {
func listenConfigDefault(config ...ListenConfig) ListenConfig { func listenConfigDefault(config ...ListenConfig) ListenConfig {
if len(config) < 1 { if len(config) < 1 {
return ListenConfig{ return ListenConfig{
TLSMinVersion: tls.VersionTLS12,
ListenerNetwork: NetworkTCP4, ListenerNetwork: NetworkTCP4,
OnShutdownError: func(err error) { OnShutdownError: func(err error) {
log.Fatalf("shutdown: %v", err) //nolint:revive // It's an option log.Fatalf("shutdown: %v", err) //nolint:revive // It's an option
@ -139,6 +154,14 @@ func listenConfigDefault(config ...ListenConfig) ListenConfig {
} }
} }
if cfg.TLSMinVersion == 0 {
cfg.TLSMinVersion = tls.VersionTLS12
}
if cfg.TLSMinVersion != tls.VersionTLS12 && cfg.TLSMinVersion != tls.VersionTLS13 {
panic("unsupported TLS version, please use tls.VersionTLS12 or tls.VersionTLS13")
}
return cfg return cfg
} }
@ -160,8 +183,8 @@ func (app *App) Listen(addr string, config ...ListenConfig) error {
} }
tlsHandler := &TLSHandler{} tlsHandler := &TLSHandler{}
tlsConfig = &tls.Config{ tlsConfig = &tls.Config{ //nolint:gosec // This is a user input
MinVersion: tls.VersionTLS12, MinVersion: cfg.TLSMinVersion,
Certificates: []tls.Certificate{ Certificates: []tls.Certificate{
cert, cert,
}, },
@ -183,9 +206,15 @@ func (app *App) Listen(addr string, config ...ListenConfig) error {
// Attach the tlsHandler to the config // Attach the tlsHandler to the config
app.SetTLSHandler(tlsHandler) app.SetTLSHandler(tlsHandler)
} else if cfg.AutoCertManager != nil {
tlsConfig = &tls.Config{ //nolint:gosec // This is a user input
MinVersion: cfg.TLSMinVersion,
GetCertificate: cfg.AutoCertManager.GetCertificate,
NextProtos: []string{"http/1.1", "acme-tls/1"},
}
} }
if cfg.TLSConfigFunc != nil { if tlsConfig != nil && cfg.TLSConfigFunc != nil {
cfg.TLSConfigFunc(tlsConfig) cfg.TLSConfigFunc(tlsConfig)
} }

View File

@ -236,6 +236,43 @@ func Test_Listen_Prefork(t *testing.T) {
require.NoError(t, app.Listen(":99999", ListenConfig{DisableStartupMessage: true, EnablePrefork: true})) require.NoError(t, app.Listen(":99999", ListenConfig{DisableStartupMessage: true, EnablePrefork: true}))
} }
// go test -run Test_Listen_TLSMinVersion
func Test_Listen_TLSMinVersion(t *testing.T) {
testPreforkMaster = true
app := New()
// Invalid TLSMinVersion
require.Panics(t, func() {
_ = app.Listen(":443", ListenConfig{TLSMinVersion: tls.VersionTLS10}) //nolint:errcheck // ignore error
})
require.Panics(t, func() {
_ = app.Listen(":443", ListenConfig{TLSMinVersion: tls.VersionTLS11}) //nolint:errcheck // ignore error
})
// Prefork
require.Panics(t, func() {
_ = app.Listen(":443", ListenConfig{DisableStartupMessage: true, EnablePrefork: true, TLSMinVersion: tls.VersionTLS10}) //nolint:errcheck // ignore error
})
require.Panics(t, func() {
_ = app.Listen(":443", ListenConfig{DisableStartupMessage: true, EnablePrefork: true, TLSMinVersion: tls.VersionTLS11}) //nolint:errcheck // ignore error
})
// Valid TLSMinVersion
go func() {
time.Sleep(1000 * time.Millisecond)
assert.NoError(t, app.Shutdown())
}()
require.NoError(t, app.Listen(":0", ListenConfig{TLSMinVersion: tls.VersionTLS13}))
// Valid TLSMinVersion with Prefork
go func() {
time.Sleep(1000 * time.Millisecond)
assert.NoError(t, app.Shutdown())
}()
require.NoError(t, app.Listen(":99999", ListenConfig{DisableStartupMessage: true, EnablePrefork: true, TLSMinVersion: tls.VersionTLS13}))
}
// go test -run Test_Listen_TLS // go test -run Test_Listen_TLS
func Test_Listen_TLS(t *testing.T) { func Test_Listen_TLS(t *testing.T) {
app := New() app := New()

View File

@ -166,25 +166,9 @@ func Test_Session_Middleware(t *testing.T) {
h(ctx) h(ctx)
require.Equal(t, fiber.StatusOK, ctx.Response.StatusCode()) require.Equal(t, fiber.StatusOK, ctx.Response.StatusCode())
// Verify the session cookie is set to expire // Verify the session cookie has expired
setCookieHeader := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie)) setCookieHeader := string(ctx.Response.Header.Peek(fiber.HeaderSetCookie))
require.Contains(t, setCookieHeader, "expires=") require.Contains(t, setCookieHeader, "max-age=0")
cookieParts := strings.Split(setCookieHeader, ";")
expired := false
for _, part := range cookieParts {
if strings.Contains(part, "expires=") {
part = strings.TrimSpace(part)
expiryDateStr := strings.TrimPrefix(part, "expires=")
// Correctly parse the date with "GMT" timezone
expiryDate, err := time.Parse(time.RFC1123, strings.TrimSpace(expiryDateStr))
require.NoError(t, err)
if expiryDate.Before(time.Now()) {
expired = true
break
}
}
}
require.True(t, expired, "Session cookie should be expired")
// Sleep so that the session expires // Sleep so that the session expires
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)