diff --git a/app.go b/app.go index 9ab4e4daef8..5147e9e482b 100644 --- a/app.go +++ b/app.go @@ -276,6 +276,8 @@ type Config struct { // StreamRequestBody enables request body streaming, // and calls the handler sooner when given body is // larger than the current limit. + // + // Default: false StreamRequestBody bool // Will not pre parse Multipart Form data if set to true. @@ -284,6 +286,8 @@ type Config struct { // multipart form data as a binary blob, or choose when to parse the data. // // Server pre parses multipart form data by default. + // + // Default: false DisablePreParseMultipartForm bool // Aggressively reduces memory usage at the cost of higher CPU usage @@ -296,14 +300,6 @@ type Config struct { // Default: false ReduceMemoryUsage bool `json:"reduce_memory_usage"` - // FEATURE: v2.3.x - // The router executes the same handler by default if StrictRouting or CaseSensitive is disabled. - // Enabling RedirectFixedPath will change this behavior into a client redirect to the original route path. - // Using the status code 301 for GET requests and 308 for all other request methods. - // - // Default: false - // RedirectFixedPath bool - // When set by an external client of Fiber it will use the provided implementation of a // JSONMarshal // @@ -595,13 +591,13 @@ func (app *App) RegisterCustomConstraint(constraint CustomConstraint) { app.customConstraints = append(app.customConstraints, constraint) } -// You can register custom binders to use as Bind().Custom("name"). +// RegisterCustomBinder Allows to register custom binders to use as Bind().Custom("name"). // They should be compatible with CustomBinder interface. func (app *App) RegisterCustomBinder(binder CustomBinder) { app.customBinders = append(app.customBinders, binder) } -// You can use SetTLSHandler to use ClientHelloInfo when using TLS with Listener. +// SetTLSHandler Can be used to set ClientHelloInfo when using TLS with Listener. func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { // Attach the tlsHandler to the config app.mutex.Lock() diff --git a/bind.go b/bind.go index 0c9a63c4a8f..f7e449f6e3a 100644 --- a/bind.go +++ b/bind.go @@ -5,26 +5,25 @@ import ( "github.com/gofiber/utils/v2" ) -// An interface to register custom binders. +// CustomBinder An interface to register custom binders. type CustomBinder interface { Name() string MIMETypes() []string Parse(c Ctx, out any) error } -// An interface to register custom struct validator for binding. +// StructValidator is an interface to register custom struct validator for binding. type StructValidator interface { - Engine() any - ValidateStruct(out any) error + Validate(out any) error } // Bind struct type Bind struct { - ctx *DefaultCtx + ctx Ctx should bool } -// To handle binder errors manually, you can prefer Should method. +// Should To handle binder errors manually, you can prefer Should method. // It's default behavior of binder. func (b *Bind) Should() *Bind { b.should = true @@ -32,7 +31,7 @@ func (b *Bind) Should() *Bind { return b } -// If you want to handle binder errors automatically, you can use Must. +// Must If you want to handle binder errors automatically, you can use Must. // If there's an error it'll return error and 400 as HTTP status. func (b *Bind) Must() *Bind { b.should = false @@ -52,15 +51,15 @@ func (b *Bind) returnErr(err error) error { // Struct validation. func (b *Bind) validateStruct(out any) error { - validator := b.ctx.app.config.StructValidator + validator := b.ctx.App().config.StructValidator if validator != nil { - return validator.ValidateStruct(out) + return validator.Validate(out) } return nil } -// To use custom binders, you have to use this method. +// Custom To use custom binders, you have to use this method. // You can register them from RegisterCustomBinder method of Fiber instance. // They're checked by name, if it's not found, it will return an error. // NOTE: Should/Must is still valid for Custom binders. @@ -103,7 +102,7 @@ func (b *Bind) Cookie(out any) error { return b.validateStruct(out) } -// QueryParser binds the query string into the struct, map[string]string and map[string][]string. +// Query binds the query string into the struct, map[string]string and map[string][]string. func (b *Bind) Query(out any) error { if err := b.returnErr(binder.QueryBinder.Bind(b.ctx.Context(), out)); err != nil { return err @@ -141,7 +140,7 @@ func (b *Bind) Form(out any) error { // URI binds the route parameters into the struct, map[string]string and map[string][]string. func (b *Bind) URI(out any) error { - if err := b.returnErr(binder.URIBinder.Bind(b.ctx.route.Params, b.ctx.Params, out)); err != nil { + if err := b.returnErr(binder.URIBinder.Bind(b.ctx.Route().Params, b.ctx.Params, out)); err != nil { return err } @@ -167,6 +166,16 @@ func (b *Bind) Body(out any) error { ctype := utils.ToLower(utils.UnsafeString(b.ctx.Context().Request.Header.ContentType())) ctype = binder.FilterFlags(utils.ParseVendorSpecificContentType(ctype)) + // Check custom binders + binders := b.ctx.App().customBinders + for _, customBinder := range binders { + for _, mime := range customBinder.MIMETypes() { + if mime == ctype { + return b.returnErr(customBinder.Parse(b.ctx, out)) + } + } + } + // Parse body accordingly switch ctype { case MIMEApplicationJSON: @@ -179,16 +188,6 @@ func (b *Bind) Body(out any) error { return b.MultipartForm(out) } - // Check custom binders - binders := b.ctx.App().customBinders - for _, customBinder := range binders { - for _, mime := range customBinder.MIMETypes() { - if mime == ctype { - return b.returnErr(customBinder.Parse(b.ctx, out)) - } - } - } - // No suitable content type found return ErrUnprocessableEntity } diff --git a/bind_test.go b/bind_test.go index 0c5b848f024..c2edaca6a33 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1547,11 +1547,7 @@ func Test_Bind_Must(t *testing.T) { // simple struct validator for testing type structValidator struct{} -func (*structValidator) Engine() any { - return "" -} - -func (*structValidator) ValidateStruct(out any) error { +func (*structValidator) Validate(out any) error { out = reflect.ValueOf(out).Elem().Interface() sq, ok := out.(simpleQuery) if !ok { diff --git a/constants.go b/constants.go new file mode 100644 index 00000000000..c856183955c --- /dev/null +++ b/constants.go @@ -0,0 +1,331 @@ +package fiber + +// HTTP methods were copied from net/http. +const ( + MethodGet = "GET" // RFC 7231, 4.3.1 + MethodHead = "HEAD" // RFC 7231, 4.3.2 + MethodPost = "POST" // RFC 7231, 4.3.3 + MethodPut = "PUT" // RFC 7231, 4.3.4 + MethodPatch = "PATCH" // RFC 5789 + MethodDelete = "DELETE" // RFC 7231, 4.3.5 + MethodConnect = "CONNECT" // RFC 7231, 4.3.6 + MethodOptions = "OPTIONS" // RFC 7231, 4.3.7 + MethodTrace = "TRACE" // RFC 7231, 4.3.8 + methodUse = "USE" +) + +// MIME types that are commonly used +const ( + MIMETextXML = "text/xml" + MIMETextHTML = "text/html" + MIMETextPlain = "text/plain" + MIMETextJavaScript = "text/javascript" + MIMEApplicationXML = "application/xml" + MIMEApplicationJSON = "application/json" + // Deprecated: use MIMETextJavaScript instead + MIMEApplicationJavaScript = "application/javascript" + MIMEApplicationForm = "application/x-www-form-urlencoded" + MIMEOctetStream = "application/octet-stream" + MIMEMultipartForm = "multipart/form-data" + + MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" + MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" + MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" + MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8" + MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" + MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" + // Deprecated: use MIMETextJavaScriptCharsetUTF8 instead + MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8" +) + +// HTTP status codes were copied from net/http with the following updates: +// - Rename StatusNonAuthoritativeInfo to StatusNonAuthoritativeInformation +// - Add StatusSwitchProxy (306) +// NOTE: Keep this list in sync with statusMessage +const ( + StatusContinue = 100 // RFC 9110, 15.2.1 + StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2 + StatusProcessing = 102 // RFC 2518, 10.1 + StatusEarlyHints = 103 // RFC 8297 + + StatusOK = 200 // RFC 9110, 15.3.1 + StatusCreated = 201 // RFC 9110, 15.3.2 + StatusAccepted = 202 // RFC 9110, 15.3.3 + StatusNonAuthoritativeInformation = 203 // RFC 9110, 15.3.4 + StatusNoContent = 204 // RFC 9110, 15.3.5 + StatusResetContent = 205 // RFC 9110, 15.3.6 + StatusPartialContent = 206 // RFC 9110, 15.3.7 + StatusMultiStatus = 207 // RFC 4918, 11.1 + StatusAlreadyReported = 208 // RFC 5842, 7.1 + StatusIMUsed = 226 // RFC 3229, 10.4.1 + + StatusMultipleChoices = 300 // RFC 9110, 15.4.1 + StatusMovedPermanently = 301 // RFC 9110, 15.4.2 + StatusFound = 302 // RFC 9110, 15.4.3 + StatusSeeOther = 303 // RFC 9110, 15.4.4 + StatusNotModified = 304 // RFC 9110, 15.4.5 + StatusUseProxy = 305 // RFC 9110, 15.4.6 + StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused) + StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8 + StatusPermanentRedirect = 308 // RFC 9110, 15.4.9 + + StatusBadRequest = 400 // RFC 9110, 15.5.1 + StatusUnauthorized = 401 // RFC 9110, 15.5.2 + StatusPaymentRequired = 402 // RFC 9110, 15.5.3 + StatusForbidden = 403 // RFC 9110, 15.5.4 + StatusNotFound = 404 // RFC 9110, 15.5.5 + StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6 + StatusNotAcceptable = 406 // RFC 9110, 15.5.7 + StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8 + StatusRequestTimeout = 408 // RFC 9110, 15.5.9 + StatusConflict = 409 // RFC 9110, 15.5.10 + StatusGone = 410 // RFC 9110, 15.5.11 + StatusLengthRequired = 411 // RFC 9110, 15.5.12 + StatusPreconditionFailed = 412 // RFC 9110, 15.5.13 + StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14 + StatusRequestURITooLong = 414 // RFC 9110, 15.5.15 + StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16 + StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17 + StatusExpectationFailed = 417 // RFC 9110, 15.5.18 + StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused) + StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20 + StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21 + StatusLocked = 423 // RFC 4918, 11.3 + StatusFailedDependency = 424 // RFC 4918, 11.4 + StatusTooEarly = 425 // RFC 8470, 5.2. + StatusUpgradeRequired = 426 // RFC 9110, 15.5.22 + StatusPreconditionRequired = 428 // RFC 6585, 3 + StatusTooManyRequests = 429 // RFC 6585, 4 + StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 + StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 + + StatusInternalServerError = 500 // RFC 9110, 15.6.1 + StatusNotImplemented = 501 // RFC 9110, 15.6.2 + StatusBadGateway = 502 // RFC 9110, 15.6.3 + StatusServiceUnavailable = 503 // RFC 9110, 15.6.4 + StatusGatewayTimeout = 504 // RFC 9110, 15.6.5 + StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6 + StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 + StatusInsufficientStorage = 507 // RFC 4918, 11.5 + StatusLoopDetected = 508 // RFC 5842, 7.2 + StatusNotExtended = 510 // RFC 2774, 7 + StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 +) + +// Errors +var ( + ErrBadRequest = NewError(StatusBadRequest) // 400 + ErrUnauthorized = NewError(StatusUnauthorized) // 401 + ErrPaymentRequired = NewError(StatusPaymentRequired) // 402 + ErrForbidden = NewError(StatusForbidden) // 403 + ErrNotFound = NewError(StatusNotFound) // 404 + ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // 405 + ErrNotAcceptable = NewError(StatusNotAcceptable) // 406 + ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // 407 + ErrRequestTimeout = NewError(StatusRequestTimeout) // 408 + ErrConflict = NewError(StatusConflict) // 409 + ErrGone = NewError(StatusGone) // 410 + ErrLengthRequired = NewError(StatusLengthRequired) // 411 + ErrPreconditionFailed = NewError(StatusPreconditionFailed) // 412 + ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // 413 + ErrRequestURITooLong = NewError(StatusRequestURITooLong) // 414 + ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // 415 + ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // 416 + ErrExpectationFailed = NewError(StatusExpectationFailed) // 417 + ErrTeapot = NewError(StatusTeapot) // 418 + ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // 421 + ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // 422 + ErrLocked = NewError(StatusLocked) // 423 + ErrFailedDependency = NewError(StatusFailedDependency) // 424 + ErrTooEarly = NewError(StatusTooEarly) // 425 + ErrUpgradeRequired = NewError(StatusUpgradeRequired) // 426 + ErrPreconditionRequired = NewError(StatusPreconditionRequired) // 428 + ErrTooManyRequests = NewError(StatusTooManyRequests) // 429 + ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // 431 + ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // 451 + + ErrInternalServerError = NewError(StatusInternalServerError) // 500 + ErrNotImplemented = NewError(StatusNotImplemented) // 501 + ErrBadGateway = NewError(StatusBadGateway) // 502 + ErrServiceUnavailable = NewError(StatusServiceUnavailable) // 503 + ErrGatewayTimeout = NewError(StatusGatewayTimeout) // 504 + ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // 505 + ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // 506 + ErrInsufficientStorage = NewError(StatusInsufficientStorage) // 507 + ErrLoopDetected = NewError(StatusLoopDetected) // 508 + ErrNotExtended = NewError(StatusNotExtended) // 510 + ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // 511 +) + +// HTTP Headers were copied from net/http. +const ( + HeaderAuthorization = "Authorization" + HeaderProxyAuthenticate = "Proxy-Authenticate" + HeaderProxyAuthorization = "Proxy-Authorization" + HeaderWWWAuthenticate = "WWW-Authenticate" + HeaderAge = "Age" + HeaderCacheControl = "Cache-Control" + HeaderClearSiteData = "Clear-Site-Data" + HeaderExpires = "Expires" + HeaderPragma = "Pragma" + HeaderWarning = "Warning" + HeaderAcceptCH = "Accept-CH" + HeaderAcceptCHLifetime = "Accept-CH-Lifetime" + HeaderContentDPR = "Content-DPR" + HeaderDPR = "DPR" + HeaderEarlyData = "Early-Data" + HeaderSaveData = "Save-Data" + HeaderViewportWidth = "Viewport-Width" + HeaderWidth = "Width" + HeaderETag = "ETag" + HeaderIfMatch = "If-Match" + HeaderIfModifiedSince = "If-Modified-Since" + HeaderIfNoneMatch = "If-None-Match" + HeaderIfUnmodifiedSince = "If-Unmodified-Since" + HeaderLastModified = "Last-Modified" + HeaderVary = "Vary" + HeaderConnection = "Connection" + HeaderKeepAlive = "Keep-Alive" + HeaderAccept = "Accept" + HeaderAcceptCharset = "Accept-Charset" + HeaderAcceptEncoding = "Accept-Encoding" + HeaderAcceptLanguage = "Accept-Language" + HeaderCookie = "Cookie" + HeaderExpect = "Expect" + HeaderMaxForwards = "Max-Forwards" + HeaderSetCookie = "Set-Cookie" + HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" + HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" + HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" + HeaderAccessControlMaxAge = "Access-Control-Max-Age" + HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" + HeaderAccessControlRequestMethod = "Access-Control-Request-Method" + HeaderOrigin = "Origin" + HeaderTimingAllowOrigin = "Timing-Allow-Origin" + HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" + HeaderDNT = "DNT" + HeaderTk = "Tk" + HeaderContentDisposition = "Content-Disposition" + HeaderContentEncoding = "Content-Encoding" + HeaderContentLanguage = "Content-Language" + HeaderContentLength = "Content-Length" + HeaderContentLocation = "Content-Location" + HeaderContentType = "Content-Type" + HeaderForwarded = "Forwarded" + HeaderVia = "Via" + HeaderXForwardedFor = "X-Forwarded-For" + HeaderXForwardedHost = "X-Forwarded-Host" + HeaderXForwardedProto = "X-Forwarded-Proto" + HeaderXForwardedProtocol = "X-Forwarded-Protocol" + HeaderXForwardedSsl = "X-Forwarded-Ssl" + HeaderXUrlScheme = "X-Url-Scheme" + HeaderLocation = "Location" + HeaderFrom = "From" + HeaderHost = "Host" + HeaderReferer = "Referer" + HeaderReferrerPolicy = "Referrer-Policy" + HeaderUserAgent = "User-Agent" + HeaderAllow = "Allow" + HeaderServer = "Server" + HeaderAcceptRanges = "Accept-Ranges" + HeaderContentRange = "Content-Range" + HeaderIfRange = "If-Range" + HeaderRange = "Range" + HeaderContentSecurityPolicy = "Content-Security-Policy" + HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" + HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" + HeaderExpectCT = "Expect-CT" + HeaderPermissionsPolicy = "Permissions-Policy" + HeaderPublicKeyPins = "Public-Key-Pins" + HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" + HeaderStrictTransportSecurity = "Strict-Transport-Security" + HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" + HeaderXContentTypeOptions = "X-Content-Type-Options" + HeaderXDownloadOptions = "X-Download-Options" + HeaderXFrameOptions = "X-Frame-Options" + HeaderXPoweredBy = "X-Powered-By" + HeaderXXSSProtection = "X-XSS-Protection" + HeaderLastEventID = "Last-Event-ID" + HeaderNEL = "NEL" + HeaderPingFrom = "Ping-From" + HeaderPingTo = "Ping-To" + HeaderReportTo = "Report-To" + HeaderTE = "TE" + HeaderTrailer = "Trailer" + HeaderTransferEncoding = "Transfer-Encoding" + HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" + HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" + HeaderSecWebSocketKey = "Sec-WebSocket-Key" + HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" + HeaderSecWebSocketVersion = "Sec-WebSocket-Version" + HeaderAcceptPatch = "Accept-Patch" + HeaderAcceptPushPolicy = "Accept-Push-Policy" + HeaderAcceptSignature = "Accept-Signature" + HeaderAltSvc = "Alt-Svc" + HeaderDate = "Date" + HeaderIndex = "Index" + HeaderLargeAllocation = "Large-Allocation" + HeaderLink = "Link" + HeaderPushPolicy = "Push-Policy" + HeaderRetryAfter = "Retry-After" + HeaderServerTiming = "Server-Timing" + HeaderSignature = "Signature" + HeaderSignedHeaders = "Signed-Headers" + HeaderSourceMap = "SourceMap" + HeaderUpgrade = "Upgrade" + HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" + HeaderXPingback = "X-Pingback" + HeaderXRequestID = "X-Request-ID" + HeaderXRequestedWith = "X-Requested-With" + HeaderXRobotsTag = "X-Robots-Tag" + HeaderXUACompatible = "X-UA-Compatible" + HeaderAccessControlAllowPrivateNetwork = "Access-Control-Allow-Private-Network" + HeaderAccessControlRequestPrivateNetwork = "Access-Control-Request-Private-Network" +) + +// Network types that are commonly used +const ( + NetworkTCP = "tcp" + NetworkTCP4 = "tcp4" + NetworkTCP6 = "tcp6" +) + +// Compression types +const ( + StrGzip = "gzip" + StrBr = "br" + StrDeflate = "deflate" + StrBrotli = "brotli" +) + +// Cookie SameSite +// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7 +const ( + CookieSameSiteDisabled = "disabled" // not in RFC, just control "SameSite" attribute will not be set. + CookieSameSiteLaxMode = "lax" + CookieSameSiteStrictMode = "strict" + CookieSameSiteNoneMode = "none" +) + +// Route Constraints +const ( + ConstraintInt = "int" + ConstraintBool = "bool" + ConstraintFloat = "float" + ConstraintAlpha = "alpha" + ConstraintGUID = "guid" + ConstraintMinLen = "minLen" + ConstraintMaxLen = "maxLen" + ConstraintLen = "len" + ConstraintBetweenLen = "betweenLen" + ConstraintMinLenLower = "minlen" + ConstraintMaxLenLower = "maxlen" + ConstraintBetweenLenLower = "betweenlen" + ConstraintMin = "min" + ConstraintMax = "max" + ConstraintRange = "range" + ConstraintDatetime = "datetime" + ConstraintRegex = "regex" +) diff --git a/ctx.go b/ctx.go index 4be0d4f49fc..e630a345f6d 100644 --- a/ctx.go +++ b/ctx.go @@ -594,6 +594,9 @@ func (c *DefaultCtx) GetReqHeaders() map[string][]string { // Host contains the host derived from the X-Forwarded-Host or Host HTTP header. // Returned value is only valid within the handler. Do not store any references. +// In a network context, `Host` refers to the combination of a hostname and potentially a port number used for connecting, +// while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. +// Example: URL: https://example.com:8080 -> Host: example.com:8080 // Make copies or use the Immutable setting instead. // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. func (c *DefaultCtx) Host() string { @@ -611,6 +614,7 @@ func (c *DefaultCtx) Host() string { // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. // Returned value is only valid within the handler. Do not store any references. +// Example: URL: https://example.com:8080 -> Hostname: example.com // Make copies or use the Immutable setting instead. // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. func (c *DefaultCtx) Hostname() string { @@ -1202,9 +1206,9 @@ func (c *DefaultCtx) Redirect() *Redirect { return c.redirect } -// Bind Add vars to default view var map binding to template engine. +// ViewBind Add vars to default view var map binding to template engine. // Variables are read by the Render method and may be overwritten. -func (c *DefaultCtx) BindVars(vars Map) error { +func (c *DefaultCtx) ViewBind(vars Map) error { // init viewBindMap - lazy map for k, v := range vars { c.viewBindMap.Store(k, v) @@ -1694,7 +1698,7 @@ func (c *DefaultCtx) IsFromLocal() bool { return c.isLocalHost(c.fasthttp.RemoteIP().String()) } -// You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. +// Bind You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. // It gives custom binding support, detailed binding options and more. // Replacement of: BodyParser, ParamsParser, GetReqHeaders, GetRespHeaders, AllParams, QueryParser, ReqHeaderParser func (c *DefaultCtx) Bind() *Bind { @@ -1707,7 +1711,7 @@ func (c *DefaultCtx) Bind() *Bind { return c.bind } -// Converts a string value to a specified type, handling errors and optional default values. +// Convert a string value to a specified type, handling errors and optional default values. func Convert[T any](value string, convertor func(string) (T, error), defaultValue ...T) (T, error) { converted, err := convertor(value) if err != nil { diff --git a/ctx_interface.go b/ctx_interface.go index bb8cdec2607..2950c088de5 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -40,6 +40,13 @@ type Ctx interface { // Attachment sets the HTTP response Content-Disposition header field to attachment. Attachment(filename ...string) + // AutoFormat performs content-negotiation on the Accept HTTP header. + // It uses Accepts to select a proper format. + // The supported content types are text/html, text/plain, application/json, and application/xml. + // For more flexible content negotiation, use Format. + // If the header is not specified or there is no proper format, text/plain is used. + AutoFormat(body any) error + // BaseURL returns (protocol + host + base path). BaseURL() string @@ -97,13 +104,6 @@ type Ctx interface { // StatusNotAcceptable is sent. Format(handlers ...ResFmt) error - // AutoFormat performs content-negotiation on the Accept HTTP header. - // It uses Accepts to select a proper format. - // The supported content types are text/html, text/plain, application/json, and application/xml. - // For more flexible content negotiation, use Format. - // If the header is not specified or there is no proper format, text/plain is used. - AutoFormat(body any) error - // FormFile returns the first file by key from a MultipartForm. FormFile(key string) (*multipart.FileHeader, error) @@ -146,12 +146,16 @@ type Ctx interface { // Host contains the host derived from the X-Forwarded-Host or Host HTTP header. // Returned value is only valid within the handler. Do not store any references. + // In a network context, `Host` refers to the combination of a hostname and potentially a port number used for connecting, + // while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. + // Example: URL: https://example.com:8080 -> Host: example.com:8080 // Make copies or use the Immutable setting instead. // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. Host() string // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. // Returned value is only valid within the handler. Do not store any references. + // Example: URL: https://example.com:8080 -> Hostname: example.com // Make copies or use the Immutable setting instead. // Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. Hostname() string @@ -274,9 +278,9 @@ type Ctx interface { // You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection. Redirect() *Redirect - // Add vars to default view var map binding to template engine. + // ViewBind Add vars to default view var map binding to template engine. // Variables are read by the Render method and may be overwritten. - BindVars(vars Map) error + ViewBind(vars Map) error // GetRouteURL generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831" GetRouteURL(routeName string, params Map) (string, error) @@ -367,7 +371,7 @@ type Ctx interface { // Reset is a method to reset context fields by given request when to use server handlers. Reset(fctx *fasthttp.RequestCtx) - // You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. + // Bind You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. // It gives custom binding support, detailed binding options and more. // Replacement of: BodyParser, ParamsParser, GetReqHeaders, GetRespHeaders, AllParams, QueryParser, ReqHeaderParser Bind() *Bind diff --git a/ctx_test.go b/ctx_test.go index fdd21a1672c..fab8a690ec8 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -1353,8 +1353,8 @@ func Benchmark_Ctx_Fresh_WithNoCache(b *testing.B) { } } -// go test -run Test_Ctx_Parsers -v -func Test_Ctx_Parsers(t *testing.T) { +// go test -run Test_Ctx_Binders -v +func Test_Ctx_Binders(t *testing.T) { t.Parallel() // setup app := New() @@ -1386,7 +1386,7 @@ func Test_Ctx_Parsers(t *testing.T) { require.Equal(t, []string{"foo", "bar", "test"}, testStruct.TestEmbeddedStruct.Names) } - t.Run("BodyParser:xml", func(t *testing.T) { + t.Run("Body:xml", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { c.Request().Header.SetContentType(MIMEApplicationXML) @@ -1394,7 +1394,7 @@ func Test_Ctx_Parsers(t *testing.T) { return c.Bind().Body(testStruct) }) }) - t.Run("BodyParser:form", func(t *testing.T) { + t.Run("Body:form", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { c.Request().Header.SetContentType(MIMEApplicationForm) @@ -1410,7 +1410,7 @@ func Test_Ctx_Parsers(t *testing.T) { return c.Bind().Body(testStruct) }) }) - t.Run("BodyParser:multiform", func(t *testing.T) { + t.Run("Body:multiform", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\nfoo\r\n--b\r\nContent-Disposition: form-data; name=\"class\"\r\n\r\n111\r\n--b\r\nContent-Disposition: form-data; name=\"name2\"\r\n\r\nbar\r\n--b\r\nContent-Disposition: form-data; name=\"class2\"\r\n\r\n222\r\n--b\r\nContent-Disposition: form-data; name=\"names\"\r\n\r\nfoo\r\n--b\r\nContent-Disposition: form-data; name=\"names\"\r\n\r\nbar\r\n--b\r\nContent-Disposition: form-data; name=\"names\"\r\n\r\ntest\r\n--b--") @@ -1420,31 +1420,31 @@ func Test_Ctx_Parsers(t *testing.T) { return c.Bind().Body(testStruct) }) }) - t.Run("CookieParser", func(t *testing.T) { + t.Run("Cookie", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { c.Request().Header.Set("Cookie", "name=foo;name2=bar;class=111;class2=222;names=foo,bar,test") return c.Bind().Cookie(testStruct) }) }) - t.Run("QueryParser", func(t *testing.T) { + t.Run("Query", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { c.Request().URI().SetQueryString("name=foo&name2=bar&class=111&class2=222&names=foo,bar,test") return c.Bind().Query(testStruct) }) }) - t.Run("ParamsParser", func(t *testing.T) { - t.Skip("ParamsParser is not ready for v3") + t.Run("URI", func(t *testing.T) { + t.Skip("URI is not ready for v3") //nolint:gocritic // TODO: uncomment - // t.Parallel() - // withValues(t, func(c Ctx, testStruct *TestStruct) error { - // c.route = &Route{Params: []string{"name", "name2", "class", "class2"}} - // c.values = [30]string{"foo", "bar", "111", "222"} - // return c.ParamsParser(testStruct) - // }) + //t.Parallel() + //withValues(t, func(c Ctx, testStruct *TestStruct) error { + // c.Route().Params = []string{"name", "name2", "class", "class2"} + // c.Params().value = [30]string{"foo", "bar", "111", "222"} + // return c.Bind().URI(testStruct) + //}) }) - t.Run("ReqHeaderParser", func(t *testing.T) { + t.Run("ReqHeader", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { c.Request().Header.Add("name", "foo") @@ -3441,13 +3441,13 @@ func Test_Ctx_RenderWithLocals(t *testing.T) { }) } -func Test_Ctx_RenderWithBindVars(t *testing.T) { +func Test_Ctx_RenderWithViewBind(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - err := c.BindVars(Map{ + err := c.ViewBind(Map{ "Title": "Hello, World!", }) require.NoError(t, err) @@ -3462,12 +3462,12 @@ func Test_Ctx_RenderWithBindVars(t *testing.T) { require.Equal(t, "

Hello, World!

", string(c.Response().Body())) } -func Test_Ctx_RenderWithOverwrittenBind(t *testing.T) { +func Test_Ctx_RenderWithOverwrittenViewBind(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - err := c.BindVars(Map{ + err := c.ViewBind(Map{ "Title": "Hello, World!", }) require.NoError(t, err) @@ -3484,7 +3484,7 @@ func Test_Ctx_RenderWithOverwrittenBind(t *testing.T) { require.Equal(t, "

Hello from Fiber!

", string(c.Response().Body())) } -func Test_Ctx_RenderWithBindVarsLocals(t *testing.T) { +func Test_Ctx_RenderWithViewBindLocals(t *testing.T) { t.Parallel() app := New(Config{ PassLocalsToViews: true, @@ -3492,7 +3492,7 @@ func Test_Ctx_RenderWithBindVarsLocals(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - err := c.BindVars(Map{ + err := c.ViewBind(Map{ "Title": "Hello, World!", }) require.NoError(t, err) @@ -3528,7 +3528,7 @@ func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) { require.Equal(t, "

Hello, World!

", string(c.Response().Body())) } -func Benchmark_Ctx_RenderWithLocalsAndBindVars(b *testing.B) { +func Benchmark_Ctx_RenderWithLocalsAndViewBind(b *testing.B) { engine := &testTemplateEngine{} err := engine.Load() require.NoError(b, err) @@ -3538,7 +3538,7 @@ func Benchmark_Ctx_RenderWithLocalsAndBindVars(b *testing.B) { }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - err = c.BindVars(Map{ + err = c.ViewBind(Map{ "Title": "Hello, World!", }) require.NoError(b, err) @@ -3578,7 +3578,7 @@ func Benchmark_Ctx_RenderLocals(b *testing.B) { require.Equal(b, "

Hello, World!

", string(c.Response().Body())) } -func Benchmark_Ctx_RenderBindVars(b *testing.B) { +func Benchmark_Ctx_RenderViewBind(b *testing.B) { engine := &testTemplateEngine{} err := engine.Load() require.NoError(b, err) @@ -3586,7 +3586,7 @@ func Benchmark_Ctx_RenderBindVars(b *testing.B) { app.config.Views = engine c := app.AcquireCtx(&fasthttp.RequestCtx{}) - err = c.BindVars(Map{ + err = c.ViewBind(Map{ "Title": "Hello, World!", }) require.NoError(b, err) diff --git a/docs/api/_category_.json b/docs/api/_category_.json index c0fc66388a9..1204e36b79c 100644 --- a/docs/api/_category_.json +++ b/docs/api/_category_.json @@ -1,6 +1,6 @@ { - "label": "API", - "position": 2, + "label": "\uD83D\uDEE0\uFE0F API", + "position": 3, "link": { "type": "generated-index", "description": "API documentation for Fiber." diff --git a/docs/api/app.md b/docs/api/app.md index 2fd98d65383..d1e4bbfc3e3 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -5,9 +5,13 @@ description: The app instance conventionally denotes the Fiber application. sidebar_position: 2 --- +import Reference from '@site/src/components/reference'; + +## Routing + import RoutingHandler from './../partials/routing/handler.md'; -## Static +### Static Use the **Static** method to serve static files such as **images**, **CSS,** and **JavaScript**. @@ -21,17 +25,13 @@ func (app *App) Static(prefix, root string, config ...Static) Router Use the following code to serve files in a directory named `./public` -```go +```go title="Examples" +// Serve files from multiple directories app.Static("/", "./public") // => http://localhost:3000/hello.html // => http://localhost:3000/js/jquery.js // => http://localhost:3000/css/style.css -``` - -```go title="Examples" -// Serve files from multiple directories -app.Static("/", "./public") // Serve files from "./files" directory: app.Static("/", "./files") @@ -47,55 +47,21 @@ app.Static("/static", "./public") // => http://localhost:3000/static/css/style.css ``` +#### Config + If you want to have a little bit more control regarding the settings for serving static files. You could use the `fiber.Static` struct to enable specific settings. -```go title="fiber.Static{}" -// Static defines configuration options when defining static assets. -type Static struct { - // When set to true, the server tries minimizing CPU usage by caching compressed files. - // This works differently than the github.com/gofiber/compression middleware. - // Optional. Default value false - Compress bool `json:"compress"` - - // When set to true, enables byte range requests. - // Optional. Default value false - ByteRange bool `json:"byte_range"` - - // When set to true, enables directory browsing. - // Optional. Default value false. - Browse bool `json:"browse"` - - // When set to true, enables direct download. - // Optional. Default value false. - Download bool `json:"download"` - - // The name of the index file for serving a directory. - // Optional. Default value "index.html". - Index string `json:"index"` - - // Expiration duration for inactive file handlers. - // Use a negative time.Duration to disable it. - // - // Optional. Default value 10 * time.Second. - CacheDuration time.Duration `json:"cache_duration"` - - // The value for the Cache-Control HTTP-header - // that is set on the file response. MaxAge is defined in seconds. - // - // Optional. Default value 0. - MaxAge int `json:"max_age"` - - // ModifyResponse defines a function that allows you to alter the response. - // - // Optional. Default: nil - ModifyResponse Handler - - // Next defines a function to skip this middleware when returned true. - // - // Optional. Default: nil - Next func(c Ctx) bool -} -``` +| Property | Type | Description | Default | +|------------------------------------------------------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------| +| Compress | `bool` | When set to true, the server tries minimizing CPU usage by caching compressed files. This works differently than the [compress](../middleware/compress.md) middleware. | false | +| ByteRange | `bool` | When set to true, enables byte range requests. | false | +| Browse | `bool` | When set to true, enables directory browsing. | false | +| Download | `bool` | When set to true, enables direct download. | false | +| Index | `string` | The name of the index file for serving a directory. | "index.html" | +| CacheDuration | `time.Duration` | Expiration duration for inactive file handlers. Use a negative `time.Duration` to disable it. | 10 * time.Second | +| MaxAge | `int` | The value for the `Cache-Control` HTTP-header that is set on the file response. MaxAge is defined in seconds. | 0 | +| ModifyResponse | `Handler` | ModifyResponse defines a function that allows you to alter the response. | nil | +| Next | `func(c Ctx) bool` | Next defines a function to skip this middleware when returned true. | nil | ```go title="Example" // Custom config @@ -109,23 +75,19 @@ app.Static("/", "./public", fiber.Static{ }) ``` -## Route Handlers +### Route Handlers -## Mount +### Mounting -You can Mount Fiber instance by creating a `*Mount` - -```go title="Signature" -func (a *App) Mount(prefix string, app *App) Router -``` +You can Mount Fiber instance using the [`app.Use`](./app.md#use) method similar to [`express`](https://expressjs.com/en/api.html#router.use). ```go title="Examples" func main() { app := fiber.New() micro := fiber.New() - app.Mount("/john", micro) // GET /john/doe -> 200 OK + app.Use("/john", micro) // GET /john/doe -> 200 OK micro.Get("/doe", func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) @@ -135,7 +97,7 @@ func main() { } ``` -## MountPath +### MountPath The `MountPath` property contains one or more path patterns on which a sub-app was mounted. @@ -150,9 +112,9 @@ func main() { two := fiber.New() three := fiber.New() - two.Mount("/three", three) - one.Mount("/two", two) - app.Mount("/one", one) + two.Use("/three", three) + one.Use("/two", two) + app.Use("/one", one) one.MountPath() // "/one" two.MountPath() // "/one/two" @@ -165,7 +127,7 @@ func main() { Mounting order is important for MountPath. If you want to get mount paths properly, you should start mounting from the deepest app. ::: -## Group +### Group You can group routes by creating a `*Group` struct. @@ -191,7 +153,7 @@ func main() { } ``` -## Route +### Route You can define routes with a common prefix inside the common function. @@ -212,39 +174,7 @@ func main() { } ``` -## Server - -Server returns the underlying [fasthttp server](https://godoc.org/github.com/valyala/fasthttp#Server) - -```go title="Signature" -func (app *App) Server() *fasthttp.Server -``` - -```go title="Examples" -func main() { - app := fiber.New() - - app.Server().MaxConnsPerIP = 1 - - // ... -} -``` - -## Server Shutdown - -Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners and then waits indefinitely for all connections to return to idle before shutting down. - -ShutdownWithTimeout will forcefully close any active connections after the timeout expires. - -ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded. - -```go -func (app *App) Shutdown() error -func (app *App) ShutdownWithTimeout(timeout time.Duration) error -func (app *App) ShutdownWithContext(ctx context.Context) error -``` - -## HandlersCount +### HandlersCount This method returns the amount of registered handlers. @@ -252,7 +182,7 @@ This method returns the amount of registered handlers. func (app *App) HandlersCount() uint32 ``` -## Stack +### Stack This method returns the original router stack @@ -276,7 +206,10 @@ func main() { } ``` -```javascript title="Result" +
+Click here to see the result + +```json [ [ { @@ -305,8 +238,9 @@ func main() { ] ] ``` +
-## Name +### Name This method assigns the name of latest created route. @@ -342,7 +276,10 @@ func main() { } ``` -```javascript title="Result" +
+Click here to see the result + +```json [ [ { @@ -407,8 +344,9 @@ func main() { null ] ``` +
-## GetRoute +### GetRoute This method gets the route by name. @@ -429,11 +367,13 @@ func main() { app.Listen(":3000") - } ``` -```javascript title="Result" +
+Click here to see the result + +```json { "method": "GET", "name": "index", @@ -441,8 +381,9 @@ func main() { "params": null } ``` +
-## GetRoutes +### GetRoutes This method gets all routes. @@ -462,7 +403,10 @@ func main() { } ``` -```javascript title="Result" +
+Click here to see the result + +```json [ { "method": "POST", @@ -472,10 +416,11 @@ func main() { } ] ``` +
## Config -Config returns the app config as value \( read-only \). +Config returns the [app config](./fiber.md#config) as value \( read-only \). ```go title="Signature" func (app *App) Config() Config @@ -483,150 +428,124 @@ func (app *App) Config() Config ## Handler -Handler returns the server handler that can be used to serve custom \*fasthttp.RequestCtx requests. +Handler returns the server handler that can be used to serve custom [`\*fasthttp.RequestCtx`](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) requests. ```go title="Signature" func (app *App) Handler() fasthttp.RequestHandler ``` -## Listen +## ErrorHandler -Listen serves HTTP requests from the given address. +Errorhandler executes the process which was defined for the application in case of errors, this is used in some cases in middlewares. ```go title="Signature" -func (app *App) Listen(addr string) error -``` - -```go title="Examples" -// Listen on port :8080 -app.Listen(":8080") - -// Custom host -app.Listen("127.0.0.1:8080") +func (app *App) ErrorHandler(ctx Ctx, err error) error ``` -## ListenTLS +## NewCtxFunc -ListenTLS serves HTTPs requests from the given address using certFile and keyFile paths to as TLS certificate and key file. +NewCtxFunc allows to customize the ctx struct as we want. ```go title="Signature" -func (app *App) ListenTLS(addr, certFile, keyFile string) error +func (app *App) NewCtxFunc(function func(app *App) CustomCtx) ``` ```go title="Examples" -app.ListenTLS(":443", "./cert.pem", "./cert.key"); -``` - -Using `ListenTLS` defaults to the following config \( use `Listener` to provide your own config \) - -```go title="Default \*tls.Config" -&tls.Config{ - MinVersion: tls.VersionTLS12, - Certificates: []tls.Certificate{ - cert, - }, +type CustomCtx struct { + DefaultCtx } -``` - -## ListenTLSWithCertificate - -```go title="Signature" -func (app *App) ListenTLS(addr string, cert tls.Certificate) error -``` - -```go title="Examples" -app.ListenTLSWithCertificate(":443", cert); -``` - -Using `ListenTLSWithCertificate` defaults to the following config \( use `Listener` to provide your own config \) -```go title="Default \*tls.Config" -&tls.Config{ - MinVersion: tls.VersionTLS12, - Certificates: []tls.Certificate{ - cert, - }, +// Custom method +func (c *CustomCtx) Params(key string, defaultValue ...string) string { + return "prefix_" + c.DefaultCtx.Params(key) } -``` - -## ListenMutualTLS -ListenMutualTLS serves HTTPs requests from the given address using certFile, keyFile and clientCertFile are the paths to TLS certificate and key file - -```go title="Signature" -func (app *App) ListenMutualTLS(addr, certFile, keyFile, clientCertFile string) error -``` - -```go title="Examples" -app.ListenMutualTLS(":443", "./cert.pem", "./cert.key", "./ca-chain-cert.pem"); -``` - -Using `ListenMutualTLS` defaults to the following config \( use `Listener` to provide your own config \) - -```go title="Default \*tls.Config" -&tls.Config{ - MinVersion: tls.VersionTLS12, - ClientAuth: tls.RequireAndVerifyClientCert, - ClientCAs: clientCertPool, - Certificates: []tls.Certificate{ - cert, - }, -} +app := New() +app.NewCtxFunc(func(app *fiber.App) fiber.CustomCtx { + return &CustomCtx{ + DefaultCtx: *NewDefaultCtx(app), + } +}) +// curl http://localhost:3000/123 +app.Get("/:id", func(c Ctx) error { + // use custom method - output: prefix_123 + return c.SendString(c.Params("id")) +}) ``` -## ListenMutualTLSWithCertificate +## RegisterCustomBinder -ListenMutualTLSWithCertificate serves HTTPs requests from the given address using certFile, keyFile and clientCertFile are the paths to TLS certificate and key file +You can register custom binders to use as [`Bind().Custom("name")`](bind.md#custom). +They should be compatible with CustomBinder interface. ```go title="Signature" -func (app *App) ListenMutualTLSWithCertificate(addr string, cert tls.Certificate, clientCertPool *x509.CertPool) error +func (app *App) RegisterCustomBinder(binder CustomBinder) ``` ```go title="Examples" -app.ListenMutualTLSWithCertificate(":443", cert, clientCertPool); -``` - -Using `ListenMutualTLSWithCertificate` defaults to the following config \( use `Listener` to provide your own config \) +app := fiber.New() -```go title="Default \*tls.Config" -&tls.Config{ - MinVersion: tls.VersionTLS12, - ClientAuth: tls.RequireAndVerifyClientCert, - ClientCAs: clientCertPool, - Certificates: []tls.Certificate{ - cert, - }, +// My custom binder +customBinder := &customBinder{} +// Name of custom binder, which will be used as Bind().Custom("name") +func (*customBinder) Name() string { + return "custom" +} +// Is used in the Body Bind method to check if the binder should be used for custom mime types +func (*customBinder) MIMETypes() []string { + return []string{"application/yaml"} } +// Parse the body and bind it to the out interface +func (*customBinder) Parse(c Ctx, out any) error { + // parse yaml body + return yaml.Unmarshal(c.Body(), out) +} +// Register custom binder +app.RegisterCustomBinder(customBinder) + +// curl -X POST http://localhost:3000/custom -H "Content-Type: application/yaml" -d "name: John" +app.Post("/custom", func(c Ctx) error { + var user User + // output: {Name:John} + // Custom binder is used by the name + if err := c.Bind().Custom("custom", &user); err != nil { + return err + } + // ... + return c.JSON(user) +}) +// curl -X POST http://localhost:3000/normal -H "Content-Type: application/yaml" -d "name: Doe" +app.Post("/normal", func(c Ctx) error { + var user User + // output: {Name:Doe} + // Custom binder is used by the mime type + if err := c.Bind().Body(&user); err != nil { + return err + } + // ... + return c.JSON(user) +}) ``` -## Listener +## RegisterCustomConstraint -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. +RegisterCustomConstraint allows to register custom constraint. ```go title="Signature" -func (app *App) Listener(ln net.Listener) error +func (app *App) RegisterCustomConstraint(constraint CustomConstraint) ``` -```go title="Examples" -ln, _ := net.Listen("tcp", ":3000") - -cer, _:= tls.LoadX509KeyPair("server.crt", "server.key") - -ln = tls.NewListener(ln, &tls.Config{Certificates: []tls.Certificate{cer}}) +See [Custom Constraint](../guide/routing.md#custom-constraint) section for more information. -app.Listener(ln) -``` -## RegisterCustomConstraint +## SetTLSHandler -RegisterCustomConstraint allows to register custom constraint. +Use SetTLSHandler to set [ClientHelloInfo](https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2) when using TLS with Listener. ```go title="Signature" -func (app *App) RegisterCustomConstraint(constraint CustomConstraint) +func (app *App) SetTLSHandler(tlsHandler *TLSHandler) ``` -See [Custom Constraint](../guide/routing.md#custom-constraint) section for more information. - ## Test Testing your application is done with the **Test** method. Use this method for creating `_test.go` files or when you need to debug your routing logic. The default timeout is `1s` if you want to disable a timeout altogether, pass `-1` as a second argument. @@ -660,8 +579,8 @@ if resp.StatusCode == fiber.StatusOK { ## Hooks -Hooks is a method to return [hooks](../guide/hooks.md) property. +Hooks is a method to return [hooks](./hooks.md) property. ```go title="Signature" func (app *App) Hooks() *Hooks -``` \ No newline at end of file +``` diff --git a/docs/api/bind.md b/docs/api/bind.md new file mode 100644 index 00000000000..f955202e287 --- /dev/null +++ b/docs/api/bind.md @@ -0,0 +1,590 @@ +--- +id: bind +title: 📎 Bind +description: Binds the request and response items to a struct. +sidebar_position: 4 +toc_max_heading_level: 4 +--- + +Bindings are used to parse the request/response body, query parameters, cookies and much more into a struct. + +:::info + +All binder returned value are only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: + + +## Binders + +- [Body](#body) + - [Form](#form) + - [JSON](#json) + - [MultipartForm](#multipartform) + - [XML](#xml) +- [Cookie](#cookie) +- [Header](#header) +- [Query](#query) +- [RespHeader](#respheader) +- [URI](#uri) + +### Body + +Binds the request body to a struct. + +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called Pass, you would use a struct field of `json:"pass"`. + +| content-type | struct tag | +| ----------------------------------- | ---------- | +| `application/x-www-form-urlencoded` | form | +| `multipart/form-data` | form | +| `application/json` | json | +| `application/xml` | xml | +| `text/xml` | xml | + +```go title="Signature" +func (b *Bind) Body(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `json:"name" xml:"name" form:"name"` + Pass string `json:"pass" xml:"pass" form:"pass"` +} + +app.Post("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().Body(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + + // ... +}) + +// Run tests with the following curl commands + +// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 + +// curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 + +// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 + +// curl -X POST -F name=john -F pass=doe http://localhost:3000 + +// curl -X POST "http://localhost:3000/?name=john&pass=doe" +``` + + +**The methods for the various bodies can also be used directly:** + +#### Form + +Binds the request form body to a struct. + +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a Form body with a field called Pass, you would use a struct field of `form:"pass"`. + + +```go title="Signature" +func (b *Bind) Form(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `form:"name"` + Pass string `form:"pass"` +} + +app.Post("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().Form(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + + // ... +}) + +// Run tests with the following curl commands + +// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 +``` + +#### JSON + +Binds the request json body to a struct. + +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called Pass, you would use a struct field of `json:"pass"`. + + +```go title="Signature" +func (b *Bind) JSON(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `json:"name"` + Pass string `json:"pass"` +} + +app.Post("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().JSON(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + + // ... +}) + +// Run tests with the following curl commands + +// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 + +``` + +#### MultipartForm + +Binds the request multipart form body to a struct. + +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a MultipartForm body with a field called Pass, you would use a struct field of `form:"pass"`. + + +```go title="Signature" +func (b *Bind) MultipartForm(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `form:"name"` + Pass string `form:"pass"` +} + +app.Post("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().MultipartForm(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + + // ... +}) + +// Run tests with the following curl commands + +// curl -X POST -H "Content-Type: multipart/form-data" -F "name=john" -F "pass=doe" localhost:3000 + +``` + +#### XML + +Binds the request xml form body to a struct. + +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a XML body with a field called Pass, you would use a struct field of `xml:"pass"`. + + +```go title="Signature" +func (b *Bind) XML(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `xml:"name"` + Pass string `xml:"pass"` +} + +app.Post("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().XML(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + + // ... +}) + +// Run tests with the following curl commands + +// curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 +``` + + +### Cookie + +This method is similar to [Body-Binding](#body), but for cookie parameters. +It is important to use the struct tag "cookie". For example, if you want to parse a cookie with a field called Age, you would use a struct field of `cookie:"age"`. + +```go title="Signature" +func (b *Bind) Cookie(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `cookie:"name"` + Age int `cookie:"age"` + Job bool `cookie:"job"` +} + +app.Get("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().Cookie(p); err != nil { + return err + } + + log.Println(p.Name) // Joseph + log.Println(p.Age) // 23 + log.Println(p.Job) // true +}) +// Run tests with the following curl command +// curl.exe --cookie "name=Joseph; age=23; job=true" http://localhost:8000/ +``` + + +### Header + +This method is similar to [Body-Binding](#body), but for request headers. +It is important to use the struct tag "header". For example, if you want to parse a request header with a field called Pass, you would use a struct field of `header:"pass"`. + +```go title="Signature" +func (b *Bind) Header(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `header:"name"` + Pass string `header:"pass"` + Products []string `header:"products"` +} + +app.Get("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().Header(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + log.Println(p.Products) // [shoe, hat] + + // ... +}) +// Run tests with the following curl command + +// curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat" +``` + + +### Query + +This method is similar to [Body-Binding](#body), but for query parameters. +It is important to use the struct tag "query". For example, if you want to parse a query parameter with a field called Pass, you would use a struct field of `query:"pass"`. + +```go title="Signature" +func (b *Bind) Query(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `query:"name"` + Pass string `query:"pass"` + Products []string `query:"products"` +} + +app.Get("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().Query(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + // fiber.Config{EnableSplittingOnParsers: false} - default + log.Println(p.Products) // ["shoe,hat"] + // fiber.Config{EnableSplittingOnParsers: true} + // log.Println(p.Products) // ["shoe", "hat"] + + + // ... +}) +// Run tests with the following curl command + +// curl "http://localhost:3000/?name=john&pass=doe&products=shoe,hat" +``` + +:::info +For more parser settings please look here [Config](fiber.md#enablesplittingonparsers) +::: + + +### RespHeader + +This method is similar to [Body-Binding](#body), but for response headers. +It is important to use the struct tag "respHeader". For example, if you want to parse a request header with a field called Pass, you would use a struct field of `respHeader:"pass"`. + +```go title="Signature" +func (b *Bind) Header(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `respHeader:"name"` + Pass string `respHeader:"pass"` + Products []string `respHeader:"products"` +} + +app.Get("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().RespHeader(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + log.Println(p.Products) // [shoe, hat] + + // ... +}) +// Run tests with the following curl command + +// curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat" +``` + +### URI + +This method is similar to [Body-Binding](#body), but for path parameters. It is important to use the struct tag "uri". For example, if you want to parse a path parameter with a field called Pass, you would use a struct field of uri:"pass" + +```go title="Signature" +func (b *Bind) URI(out any) error +``` + +```go title="Example" +// GET http://example.com/user/111 +app.Get("/user/:id", func(c fiber.Ctx) error { + param := struct {ID uint `uri:"id"`}{} + + c.Bind().URI(¶m) // "{"id": 111}" + + // ... +}) + +``` + +## Custom + +To use custom binders, you have to use this method. + +You can register them from [RegisterCustomBinder](./app.md#registercustombinder) method of Fiber instance. + +```go title="Signature" +func (b *Bind) Custom(name string, dest any) error +``` + +```go title="Example" +app := fiber.New() + +// My custom binder +customBinder := &customBinder{} +// Name of custom binder, which will be used as Bind().Custom("name") +func (*customBinder) Name() string { + return "custom" +} +// Is used in the Body Bind method to check if the binder should be used for custom mime types +func (*customBinder) MIMETypes() []string { + return []string{"application/yaml"} +} +// Parse the body and bind it to the out interface +func (*customBinder) Parse(c Ctx, out any) error { + // parse yaml body + return yaml.Unmarshal(c.Body(), out) +} +// Register custom binder +app.RegisterCustomBinder(customBinder) + +// curl -X POST http://localhost:3000/custom -H "Content-Type: application/yaml" -d "name: John" +app.Post("/custom", func(c Ctx) error { + var user User + // output: {Name:John} + // Custom binder is used by the name + if err := c.Bind().Custom("custom", &user); err != nil { + return err + } + // ... + return c.JSON(user) +}) +``` + +Internally they are also used in the [Body](#body) method. +For this the MIMETypes method is used to check if the custom binder should be used for the given content type. + +## Options + +For more control over the error handling, you can use the following methods. + +### Must + +If you want to handle binder errors automatically, you can use Must. +If there's an error it'll return error and 400 as HTTP status. + +```go title="Signature" +func (b *Bind) Must() *Bind +``` + +### Should + +To handle binder errors manually, you can prefer Should method. +It's default behavior of binder. + +```go title="Signature" +func (b *Bind) Should() *Bind +``` + + +## SetParserDecoder + +Allow you to config BodyParser/QueryParser decoder, base on schema's options, providing possibility to add custom type for parsing. + +```go title="Signature" +func SetParserDecoder(parserConfig fiber.ParserConfig{ + IgnoreUnknownKeys bool, + ParserType []fiber.ParserType{ + Customtype any, + Converter func(string) reflect.Value, + }, + ZeroEmpty bool, + SetAliasTag string, +}) +``` + +```go title="Example" + +type CustomTime time.Time + +// String() returns the time in string +func (ct *CustomTime) String() string { + t := time.Time(*ct).String() + return t + } + + // Register the converter for CustomTime type format as 2006-01-02 + var timeConverter = func(value string) reflect.Value { + fmt.Println("timeConverter", value) + if v, err := time.Parse("2006-01-02", value); err == nil { + return reflect.ValueOf(v) + } + return reflect.Value{} +} + +customTime := fiber.ParserType{ + Customtype: CustomTime{}, + Converter: timeConverter, +} + +// Add setting to the Decoder +fiber.SetParserDecoder(fiber.ParserConfig{ + IgnoreUnknownKeys: true, + ParserType: []fiber.ParserType{customTime}, + ZeroEmpty: true, +}) + +// Example to use CustomType, you pause custom time format not in RFC3339 +type Demo struct { + Date CustomTime `form:"date" query:"date"` + Title string `form:"title" query:"title"` + Body string `form:"body" query:"body"` +} + +app.Post("/body", func(c fiber.Ctx) error { + var d Demo + c.BodyParser(&d) + fmt.Println("d.Date", d.Date.String()) + return c.JSON(d) +}) + +app.Get("/query", func(c fiber.Ctx) error { + var d Demo + c.QueryParser(&d) + fmt.Println("d.Date", d.Date.String()) + return c.JSON(d) +}) + +// curl -X POST -F title=title -F body=body -F date=2021-10-20 http://localhost:3000/body + +// curl -X GET "http://localhost:3000/query?title=title&body=body&date=2021-10-20" + +``` + + +## Validation + +Validation is also possible with the binding methods. You can specify your validation rules using the `validate` struct tag. + +Specify your struct validator in the [config](./fiber.md#structvalidator) + +Setup your validator in the config: + +```go title="Example" +import "github.com/go-playground/validator/v10" + +type structValidator struct { + validate *validator.Validate +} + +// Validator needs to implement the Validate method +func (v *structValidator) Validate(out any) error { + return v.validate.Struct(out) +} + +// Setup your validator in the config +app := fiber.New(fiber.Config{ + StructValidator: &structValidator{validate: validator.New()}, +}) +``` + +Usage of the validation in the binding methods: + +```go title="Example" +type Person struct { + Name string `json:"name" validate:"required"` + Age int `json:"age" validate:"gte=18,lte=60"` +} + +app.Post("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().JSON(p); err != nil {// <- here you receive the validation errors + return err + } +}) +``` + + + diff --git a/docs/api/client.md b/docs/api/client.md index ae174916b73..1ffe50f4109 100644 --- a/docs/api/client.md +++ b/docs/api/client.md @@ -2,7 +2,7 @@ id: client title: 🌎 Client description: The Client struct represents the Fiber HTTP Client. -sidebar_position: 5 +sidebar_position: 6 --- ## Start request @@ -537,7 +537,8 @@ agent.SetResponse(resp) ReleaseResponse(resp) ``` -
Example handling for response values +
+Example handling for response values ```go title="Example handling response" // Create a Fiber HTTP client agent diff --git a/docs/api/constants.md b/docs/api/constants.md index 8a436a9f3b8..c9fa7904c49 100644 --- a/docs/api/constants.md +++ b/docs/api/constants.md @@ -2,10 +2,10 @@ id: constants title: 📋 Constants description: Some constants for Fiber. -sidebar_position: 4 +sidebar_position: 9 --- -HTTP methods were copied from net/http. +### HTTP methods were copied from net/http. ```go const ( @@ -22,7 +22,7 @@ const ( ) ``` -MIME types that are commonly used +### MIME types that are commonly used ```go const ( @@ -45,7 +45,7 @@ const ( ) ``` -HTTP status codes were copied from net/http. +### HTTP status codes were copied from net/http. ```go const ( @@ -114,7 +114,7 @@ const ( ) ``` -Errors +### Errors ```go var ( @@ -288,4 +288,4 @@ const ( HeaderXRobotsTag = "X-Robots-Tag" HeaderXUACompatible = "X-UA-Compatible" ) -``` \ No newline at end of file +``` diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 3b86ae0f9c3..4b1c1d51154 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -100,31 +100,6 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -## AllParams - -Params is used to get all route parameters. -Using Params method to get params. - -```go title="Signature" -func (c Ctx) AllParams() map[string]string -``` - -```go title="Example" -// GET http://example.com/user/fenny -app.Get("/user/:name", func(c fiber.Ctx) error { - c.AllParams() // "{"name": "fenny"}" - - // ... -}) - -// GET http://example.com/user/fenny/123 -app.Get("/user/*", func(c fiber.Ctx) error { - c.AllParams() // "{"*1": "fenny/123"}" - - // ... -}) -``` - ## App Returns the [\*App](ctx.md) reference so you could easily access all application settings. @@ -244,45 +219,23 @@ app.Get("/", func(c fiber.Ctx) error { ## Bind -Add vars to default view var map binding to template engine. -Variables are read by the Render method and may be overwritten. - -```go title="Signature" -func (c Ctx) Bind(vars Map) error -``` - -```go title="Example" -app.Use(func(c fiber.Ctx) error { - c.Bind(fiber.Map{ - "Title": "Hello, World!", - }) -}) +Bind is a method that support supports bindings for the request/response body, query parameters, URL parameters, cookies and much more. +It returns a pointer to the [Bind](./bind.md) struct which contains all the methods to bind the request/response data. -app.Get("/", func(c fiber.Ctx) error { - return c.Render("xxx.tmpl", fiber.Map{}) // Render will use Title variable -}) -``` - -## BodyRaw - -Returns the raw request **body**. +For detailed information check the [Bind](./bind.md) documentation. ```go title="Signature" -func (c Ctx) BodyRaw() []byte +func (c Ctx) Bind() *Bind ``` ```go title="Example" -// curl -X POST http://localhost:8080 -d user=john - app.Post("/", func(c fiber.Ctx) error { - // Get raw body from POST request: - return c.Send(c.BodyRaw()) // []byte("user=john") + user := new(User) + // Bind the request body to a struct: + return c.Bind().Body(user) }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) - ## Body As per the header `Content-Encoding`, this method will try to perform a file decompression from the **body** bytes. In case no `Content-Encoding` header is sent, it will perform as [BodyRaw](#bodyraw). @@ -300,62 +253,36 @@ app.Post("/", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info -## BodyParser +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) -Binds the request body to a struct. +::: -It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called Pass, you would use a struct field of `json:"pass"`. +## BodyRaw -| content-type | struct tag | -| ----------------------------------- | ---------- | -| `application/x-www-form-urlencoded` | form | -| `multipart/form-data` | form | -| `application/json` | json | -| `application/xml` | xml | -| `text/xml` | xml | +Returns the raw request **body**. ```go title="Signature" -func (c Ctx) BodyParser(out any) error +func (c Ctx) BodyRaw() []byte ``` ```go title="Example" -// Field names should start with an uppercase letter -type Person struct { - Name string `json:"name" xml:"name" form:"name"` - Pass string `json:"pass" xml:"pass" form:"pass"` -} +// curl -X POST http://localhost:8080 -d user=john app.Post("/", func(c fiber.Ctx) error { - p := new(Person) - - if err := c.BodyParser(p); err != nil { - return err - } - - log.Println(p.Name) // john - log.Println(p.Pass) // doe - - // ... + // Get raw body from POST request: + return c.Send(c.BodyRaw()) // []byte("user=john") }) +``` -// Run tests with the following curl commands - -// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000 - -// curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 - -// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000 - -// curl -X POST -F name=john -F pass=doe http://localhost:3000 +:::info -// curl -X POST "http://localhost:3000/?name=john&pass=doe" -``` +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +::: ## ClearCookie @@ -475,38 +402,6 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -## CookieParser - -This method is similar to [BodyParser](ctx.md#bodyparser), but for cookie parameters. -It is important to use the struct tag "cookie". For example, if you want to parse a cookie with a field called Age, you would use a struct field of `cookie:"age"`. - -```go title="Signature" -func (c Ctx) CookieParser(out any) error -``` - -```go title="Example" -// Field names should start with an uppercase letter -type Person struct { - Name string `cookie:"name"` - Age int `cookie:"age"` - Job bool `cookie:"job"` -} - -app.Get("/", func(c fiber.Ctx) error { - p := new(Person) - - if err := c.CookieParser(p); err != nil { - return err - } - - log.Println(p.Name) // Joseph - log.Println(p.Age) // 23 - log.Println(p.Job) // true -}) -// Run tests with the following curl command -// curl.exe --cookie "name=Joseph; age=23; job=true" http://localhost:8000/ -``` - ## Cookies Get cookie value by key, you could pass an optional default value that will be returned if the cookie key does not exist. @@ -524,8 +419,12 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: ## Download @@ -638,8 +537,12 @@ app.Post("/", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: ## Fresh @@ -674,8 +577,12 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: ## GetReqHeaders @@ -685,8 +592,12 @@ Returns the HTTP request headers as a map. Since a header can be set multiple ti func (c Ctx) GetReqHeaders() map[string][]string ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: ## GetRespHeader @@ -709,8 +620,12 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: ## GetRespHeaders @@ -720,8 +635,12 @@ Returns the HTTP response headers as a map. Since a header can be set multiple t func (c Ctx) GetRespHeaders() map[string][]string ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: ## GetRouteURL @@ -748,6 +667,34 @@ app.Get("/test", func(c fiber.Ctx) error { // /test returns "/user/1" ``` +## Host + +Returns the host derived from the [Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) HTTP header. + +In a network context, [`Host`](#host) refers to the combination of a hostname and potentially a port number used for connecting, while [`Hostname`](#hostname) refers specifically to the name assigned to a device on a network, excluding any port information. + +```go title="Signature" +func (c Ctx) Host() string +``` + +```go title="Example" +// GET http://google.com:8080/search + +app.Get("/", func(c fiber.Ctx) error { + c.Host() // "google.com:8080" + c.Hostname() // "google.com" + + // ... +}) +``` + +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: + ## Hostname Returns the hostname derived from the [Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) HTTP header. @@ -766,8 +713,12 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: ## IP @@ -785,7 +736,7 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -When registering the proxy request header in the fiber app, the ip address of the header is returned [(Fiber configuration)](fiber.md#config) +When registering the proxy request header in the fiber app, the ip address of the header is returned [(Fiber configuration)](fiber.md#proxyheader) ```go app := fiber.New(fiber.Config{ @@ -857,6 +808,35 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` +## IsProxyTrusted + +Checks trustworthiness of remote ip. +If [`EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) false, it returns true +IsProxyTrusted can check remote ip by proxy ranges and ip map. + +```go title="Signature" +func (c Ctx) IsProxyTrusted() bool +``` + +```go title="Example" + +app := fiber.New(fiber.Config{ + // EnableTrustedProxyCheck enables the trusted proxy check + EnableTrustedProxyCheck: true, + // TrustedProxies is a list of trusted proxy IP addresses + TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}, +}) + + +app.Get("/", func(c fiber.Ctx) error { + // If request came from trusted proxy, return true else return false + c.IsProxyTrusted() + + // ... +}) + +``` + ## JSON Converts any **interface** or **string** to JSON using the [encoding/json](https://pkg.go.dev/encoding/json) package. @@ -1145,8 +1125,12 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: ## Params @@ -1194,8 +1178,12 @@ app.Get("/v1/*/shop/*", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: In certain scenarios, it can be useful to have an alternative approach to handle different types of parameters, not @@ -1225,42 +1213,40 @@ The generic Params function supports returning the following data types based on - String: string - Byte array: []byte -## ParamsParser -This method is similar to BodyParser, but for path parameters. It is important to use the struct tag "params". For example, if you want to parse a path parameter with a field called Pass, you would use a struct field of params:"pass" +## Path + +Contains the path part of the request URL. Optionally, you could override the path by passing a string. For internal redirects, you might want to call [RestartRouting](ctx.md#restartrouting) instead of [Next](ctx.md#next). ```go title="Signature" -func (c Ctx) ParamsParser(out any) error +func (c Ctx) Path(override ...string) string ``` ```go title="Example" -// GET http://example.com/user/111 -app.Get("/user/:id", func(c fiber.Ctx) error { - param := struct {ID uint `params:"id"`}{} +// GET http://example.com/users?sort=desc - c.ParamsParser(¶m) // "{"id": 111}" +app.Get("/users", func(c fiber.Ctx) error { + c.Path() // "/users" + + c.Path("/john") + c.Path() // "/john" // ... }) - ``` -## Path +## Port -Contains the path part of the request URL. Optionally, you could override the path by passing a string. For internal redirects, you might want to call [RestartRouting](ctx.md#restartrouting) instead of [Next](ctx.md#next). +Returns the remote port of the request. ```go title="Signature" -func (c Ctx) Path(override ...string) string +func (c Ctx) Port() string ``` ```go title="Example" -// GET http://example.com/users?sort=desc - -app.Get("/users", func(c fiber.Ctx) error { - c.Path() // "/users" - - c.Path("/john") - c.Path() // "/john" +// GET http://example.com:8080 +app.Get("/", func(c fiber.Ctx) error { + c.Port() // "8080" // ... }) @@ -1372,8 +1358,12 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -> _Returned value is only valid within the handler. Do not store any references. -> Make copies or use the_ [_**`Immutable`**_](ctx.md) _setting instead._ [_Read more..._](../#zero-allocation) +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: In certain scenarios, it can be useful to have an alternative approach to handle different types of query parameters, not just strings. This can be achieved using a generic Query function known as `Query[V GenericType](c Ctx, key string, defaultValue ...V) V`. @@ -1412,49 +1402,6 @@ The generic Query function supports returning the following data types based on - String: string - Byte array: []byte -## QueryParser - -This method is similar to [BodyParser](ctx.md#bodyparser), but for query parameters. -It is important to use the struct tag "query". For example, if you want to parse a query parameter with a field called Pass, you would use a struct field of `query:"pass"`. - -```go title="Signature" -func (c Ctx) QueryParser(out any) error -``` - -```go title="Example" -// Field names should start with an uppercase letter -type Person struct { - Name string `query:"name"` - Pass string `query:"pass"` - Products []string `query:"products"` -} - -app.Get("/", func(c fiber.Ctx) error { - p := new(Person) - - if err := c.QueryParser(p); err != nil { - return err - } - - log.Println(p.Name) // john - log.Println(p.Pass) // doe - // fiber.Config{EnableSplittingOnParsers: false} - default - log.Println(p.Products) // ["shoe,hat"] - // fiber.Config{EnableSplittingOnParsers: true} - // log.Println(p.Products) // ["shoe", "hat"] - - - // ... -}) -// Run tests with the following curl command - -// curl "http://localhost:3000/?name=john&pass=doe&products=shoe,hat" -``` - -:::info -For more parser settings please look here [Config](fiber.md#config) -::: - ## Range A struct containing the type and a slice of ranges will be returned. @@ -1478,104 +1425,31 @@ app.Get("/", func(c fiber.Ctx) error { ## Redirect -Redirects to the URL derived from the specified path, with specified status, a positive integer that corresponds to an HTTP status code. +Returns the Redirect reference. -:::info -If **not** specified, status defaults to **302 Found**. -::: +For detailed information check the [Redirect](./redirect.md) documentation. ```go title="Signature" -func (c Ctx) Redirect(location string, status ...int) error +func (c Ctx) Redirect() *Redirect ``` ```go title="Example" app.Get("/coffee", func(c fiber.Ctx) error { - return c.Redirect("/teapot") + return c.Redirect().To("/teapot") }) app.Get("/teapot", func(c fiber.Ctx) error { - return c.Status(fiber.StatusTeapot).Send("🍵 short and stout 🍵") -}) -``` - -```go title="More examples" -app.Get("/", func(c fiber.Ctx) error { - return c.Redirect("/foo/bar") - return c.Redirect("../login") - return c.Redirect("http://example.com") - return c.Redirect("http://example.com", 301) -}) -``` - -## RedirectToRoute - -Redirects to the specific route along with the parameters and with specified status, a positive integer that corresponds to an HTTP status code. - -:::info -If **not** specified, status defaults to **302 Found**. -::: - -:::info -If you want to send queries to route, you must add **"queries"** key typed as **map[string]string** to params. -::: - -```go title="Signature" -func (c Ctx) RedirectToRoute(routeName string, params fiber.Map, status ...int) error -``` - -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - // /user/fiber - return c.RedirectToRoute("user", fiber.Map{ - "name": "fiber" - }) -}) - -app.Get("/with-queries", func(c fiber.Ctx) error { - // /user/fiber?data[0][name]=john&data[0][age]=10&test=doe - return c.RedirectToRoute("user", fiber.Map{ - "name": "fiber", - "queries": map[string]string{"data[0][name]": "john", "data[0][age]": "10", "test": "doe"}, - }) + return c.Status(fiber.StatusTeapot).Send("🍵 short and stout 🍵") }) - -app.Get("/user/:name", func(c fiber.Ctx) error { - return c.SendString(c.Params("name")) -}).Name("user") ``` -## RedirectBack - -Redirects back to refer URL. It redirects to fallback URL if refer header doesn't exists, with specified status, a positive integer that corresponds to an HTTP status code. - -:::info -If **not** specified, status defaults to **302 Found**. -::: - -```go title="Signature" -func (c Ctx) RedirectBack(fallback string, status ...int) error -``` - -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - return c.SendString("Home page") -}) -app.Get("/test", func(c fiber.Ctx) error { - c.Set("Content-Type", "text/html") - return c.SendString(`Back`) -}) - -app.Get("/back", func(c fiber.Ctx) error { - return c.RedirectBack("/") -}) -``` ## Render Renders a view with data and sends a `text/html` response. By default `Render` uses the default [**Go Template engine**](https://pkg.go.dev/html/template/). If you want to use another View engine, please take a look at our [**Template middleware**](https://docs.gofiber.io/template). ```go title="Signature" -func (c Ctx) Render(name string, bind any, layouts ...string) error +func (c Ctx) Render(name string, bind Map, layouts ...string) error ``` ## Request @@ -1593,41 +1467,6 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -## ReqHeaderParser - -This method is similar to [BodyParser](ctx.md#bodyparser), but for request headers. -It is important to use the struct tag "reqHeader". For example, if you want to parse a request header with a field called Pass, you would use a struct field of `reqHeader:"pass"`. - -```go title="Signature" -func (c Ctx) ReqHeaderParser(out any) error -``` - -```go title="Example" -// Field names should start with an uppercase letter -type Person struct { - Name string `reqHeader:"name"` - Pass string `reqHeader:"pass"` - Products []string `reqHeader:"products"` -} - -app.Get("/", func(c fiber.Ctx) error { - p := new(Person) - - if err := c.ReqHeaderParser(p); err != nil { - return err - } - - log.Println(p.Name) // john - log.Println(p.Pass) // doe - log.Println(p.Products) // [shoe, hat] - - // ... -}) -// Run tests with the following curl command - -// curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat" -``` - ## Response Response return the [\*fasthttp.Response](https://godoc.org/github.com/valyala/fasthttp#Response) pointer @@ -1644,6 +1483,16 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` +## Reset + +Reset the context fields by given request when to use server handlers. + +```go title="Signature" +func (c Ctx) Reset(fctx *fasthttp.RequestCtx) +``` + +It is used outside of the Fiber Handlers to reset the context for the next request. + ## RestartRouting Instead of executing the next method when calling [Next](ctx.md#next), **RestartRouting** restarts execution from the first method that matches the current route. This may be helpful after overriding the path, i. e. an internal redirect. Note that handlers might be executed again which could result in an infinite loop. @@ -1767,6 +1616,27 @@ app.Post("/", func(c fiber.Ctx) error { }) ``` +## Schema + +Contains the request protocol string: http or https for TLS requests. + +:::info +Please use [`Config.EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) to prevent header spoofing, in case when your app is behind the proxy. +::: + +```go title="Signature" +func (c Ctx) Schema() string +``` + +```go title="Example" +// GET http://example.com +app.Get("/", func(c fiber.Ctx) error { + c.Schema() // "http" + + // ... +}) +``` + ## Secure A boolean property that is `true` , if a **TLS** connection is established. @@ -1847,7 +1717,7 @@ app.Get("/file-with-url-chars", func(c fiber.Ctx) error { ``` :::info -For sending files from embedded file system [this functionality](./middleware/filesystem.md#sendfile) can be used +For sending files from embedded file system [this functionality](../middleware/filesystem.md#sendfile) can be used ::: ## SendStatus @@ -1873,95 +1743,51 @@ app.Get("/not-found", func(c fiber.Ctx) error { }) ``` -## Set +## SendStream -Sets the response’s HTTP header field to the specified `key`, `value`. +Sets response body to a stream of data and add optional body size. ```go title="Signature" -func (c Ctx) Set(key string, val string) +func (c Ctx) SendStream(stream io.Reader, size ...int) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.Set("Content-Type", "text/plain") - // => "Content-type: text/plain" - - // ... + return c.SendStream(bytes.NewReader([]byte("Hello, World!"))) + // => "Hello, World!" }) ``` -## SetParserDecoder +## SendString -Allow you to config BodyParser/QueryParser decoder, base on schema's options, providing possibility to add custom type for parsing. +Sets the response body to a string. ```go title="Signature" -func SetParserDecoder(parserConfig fiber.ParserConfig{ - IgnoreUnknownKeys bool, - ParserType []fiber.ParserType{ - Customtype any, - Converter func(string) reflect.Value, - }, - ZeroEmpty bool, - SetAliasTag string, -}) +func (c Ctx) SendString(body string) error ``` ```go title="Example" - -type CustomTime time.Time - -// String() returns the time in string -func (ct *CustomTime) String() string { - t := time.Time(*ct).String() - return t -} - -// Register the converter for CustomTime type format as 2006-01-02 -var timeConverter = func(value string) reflect.Value { - fmt.Println("timeConverter", value) - if v, err := time.Parse("2006-01-02", value); err == nil { - return reflect.ValueOf(v) - } - return reflect.Value{} -} - -customTime := fiber.ParserType{ - Customtype: CustomTime{}, - Converter: timeConverter, -} - -// Add setting to the Decoder -fiber.SetParserDecoder(fiber.ParserConfig{ - IgnoreUnknownKeys: true, - ParserType: []fiber.ParserType{customTime}, - ZeroEmpty: true, +app.Get("/", func(c fiber.Ctx) error { + return c.SendString("Hello, World!") + // => "Hello, World!" }) +``` -// Example to use CustomType, you pause custom time format not in RFC3339 -type Demo struct { - Date CustomTime `form:"date" query:"date"` - Title string `form:"title" query:"title"` - Body string `form:"body" query:"body"` -} - -app.Post("/body", func(c fiber.Ctx) error { - var d Demo - c.BodyParser(&d) - fmt.Println("d.Date", d.Date.String()) - return c.JSON(d) -}) +## Set -app.Get("/query", func(c fiber.Ctx) error { - var d Demo - c.QueryParser(&d) - fmt.Println("d.Date", d.Date.String()) - return c.JSON(d) -}) +Sets the response’s HTTP header field to the specified `key`, `value`. -// curl -X POST -F title=title -F body=body -F date=2021-10-20 http://localhost:3000/body +```go title="Signature" +func (c Ctx) Set(key string, val string) +``` -// curl -X GET "http://localhost:3000/query?title=title&body=body&date=2021-10-20" +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + c.Set("Content-Type", "text/plain") + // => "Content-type: text/plain" + // ... +}) ``` ## SetUserContext @@ -2017,6 +1843,22 @@ app.Get("/world", func(c fiber.Ctx) error { }) ``` +## String + +Returns unique string representation of the ctx. + +```go title="Signature" +func (c Ctx) String() string +``` + +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + c.String() // => "#0000000100000001 - 127.0.0.1:3000 <-> 127.0.0.1:61516 - GET http://localhost:3000/" + + // ... +}) +``` + ## Subdomains Returns a string slice of subdomains in the domain name of the request. @@ -2042,6 +1884,10 @@ app.Get("/", func(c fiber.Ctx) error { Sets the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) HTTP header to the MIME type listed [here](https://github.com/nginx/nginx/blob/master/conf/mime.types) specified by the file **extension**. +:::info +Method is a **chainable**. +::: + ```go title="Signature" func (c Ctx) Type(ext string, charset ...string) Ctx ``` @@ -2103,6 +1949,27 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` +## ViewBind + +Add vars to default view var map binding to template engine. +Variables are read by the Render method and may be overwritten. + +```go title="Signature" +func (c Ctx) ViewBind(vars Map) error +``` + +```go title="Example" +app.Use(func(c fiber.Ctx) error { + c.ViewBind(fiber.Map{ + "Title": "Hello, World!", + }) +}) + +app.Get("/", func(c fiber.Ctx) error { + return c.Render("xxx.tmpl", fiber.Map{}) // Render will use Title variable +}) +``` + ## Write Write adopts the Writer interface @@ -2203,26 +2070,3 @@ app.Get("/", func(c fiber.Ctx) error { // }) ``` - -## Convert -Converts a string value to a specified type, handling errors and optional default values. -This function simplifies the conversion process by encapsulating error handling and the management of default values, making your code cleaner and more consistent. -```go title="Signature" -func Convert[T any](value string, convertor func(string) (T, error), defaultValue ...T) (*T, error) -``` - -```go title="Example" -// GET http://example.com/id/bb70ab33-d455-4a03-8d78-d3c1dacae9ff -app.Get("/id/:id", func(c fiber.Ctx) error { - fiber.Convert(c.Params("id"), uuid.Parse) // UUID(bb70ab33-d455-4a03-8d78-d3c1dacae9ff), nil - - -// GET http://example.com/search?id=65f6f54221fb90e6a6b76db7 -app.Get("/search", func(c fiber.Ctx) error) { - fiber.Convert(c.Query("id"), mongo.ParseObjectID) // objectid(65f6f54221fb90e6a6b76db7), nil - fiber.Convert(c.Query("id"), uuid.Parse) // uuid.Nil, error(cannot parse given uuid) - fiber.Convert(c.Query("id"), uuid.Parse, mongo.NewObjectID) // new object id generated and return nil as error. -} - - // ... -}) diff --git a/docs/api/fiber.md b/docs/api/fiber.md index 05fd4941e8d..1618ac34d87 100644 --- a/docs/api/fiber.md +++ b/docs/api/fiber.md @@ -5,9 +5,13 @@ description: Fiber represents the fiber package where you start to create an ins sidebar_position: 1 --- -## New +import Reference from '@site/src/components/reference'; -This method creates a new **App** named instance. You can pass optional [config ](#config)when creating a new instance. +## Server start + +### New + +This method creates a new **App** named instance. You can pass optional [config](#config) when creating a new instance. ```go title="Signature" func New(config ...Config) *App @@ -20,14 +24,13 @@ app := fiber.New() // ... ``` -## Config +### Config You can pass an optional Config when creating a new Fiber instance. ```go title="Example" // Custom config app := fiber.New(fiber.Config{ - Prefork: true, CaseSensitive: true, StrictRouting: true, ServerHeader: "Fiber", @@ -37,52 +40,185 @@ app := fiber.New(fiber.Config{ // ... ``` -**Config fields** - -| Property | Type | Description | Default | -| ---------------------------- | --------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --------------------- | -| AppName | `string` | This allows to setup app name for the app | `""` | -| BodyLimit | `int` | Sets the maximum allowed size for a request body, if the size exceeds the configured limit, it sends `413 - Request Entity Too Large` response. | `4 * 1024 * 1024` | -| CaseSensitive | `bool` | When enabled, `/Foo` and `/foo` are different routes. When disabled, `/Foo`and `/foo` are treated the same. | `false` | -| ColorScheme | [`Colors`](https://github.com/gofiber/fiber/blob/master/color.go) | You can define custom color scheme. They'll be used for startup message, route list and some middlewares. | [`DefaultColors`](https://github.com/gofiber/fiber/blob/master/color.go) | -| CompressedFileSuffix | `string` | Adds a suffix to the original file name and tries saving the resulting compressed file under the new file name. | `".fiber.gz"` | -| Concurrency | `int` | Maximum number of concurrent connections. | `256 * 1024` | -| DisableDefaultContentType | `bool` | When set to true, causes the default Content-Type header to be excluded from the Response. | `false` | -| DisableDefaultDate | `bool` | When set to true causes the default date header to be excluded from the response. | `false` | -| DisableHeaderNormalizing | `bool` | By default all header names are normalized: conteNT-tYPE -> Content-Type | `false` | -| DisableKeepalive | `bool` | Disable keep-alive connections, the server will close incoming connections after sending the first response to the client | `false` | -| DisablePreParseMultipartForm | `bool` | Will not pre parse Multipart Form data if set to true. This option is useful for servers that desire to treat multipart form data as a binary blob, or choose when to parse the data. | `false` | -| DisableStartupMessage | `bool` | When set to true, it will not print out debug information | `false` | -| ETag | `bool` | Enable or disable ETag header generation, since both weak and strong etags are generated using the same hashing method \(CRC-32\). Weak ETags are the default when enabled. | `false` | -| EnableIPValidation | `bool` | If set to true, `c.IP()` and `c.IPs()` will validate IP addresses before returning them. Also, `c.IP()` will return only the first valid IP rather than just the raw header value that may be a comma separated string.

**WARNING:** There is a small performance cost to doing this validation. Keep disabled if speed is your only concern and your application is behind a trusted proxy that already validates this header. | `false` | -| EnablePrintRoutes | `bool` | EnablePrintRoutes enables print all routes with their method, path, name and handler.. | `false` | -| EnableSplittingOnParsers | `bool` | EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true.

For example, you can use it to parse multiple values from a query parameter like this: `/api?foo=bar,baz == foo[]=bar&foo[]=baz` | `false` | -| EnableTrustedProxyCheck | `bool` | When set to true, fiber will check whether proxy is trusted, using TrustedProxies list.

By default `c.Protocol()` will get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header, `c.IP()` will get value from `ProxyHeader` header, `c.Hostname()` will get value from X-Forwarded-Host header.
If `EnableTrustedProxyCheck` is true, and `RemoteIP` is in the list of `TrustedProxies` `c.Protocol()`, `c.IP()`, and `c.Hostname()` will have the same behaviour when `EnableTrustedProxyCheck` disabled, if `RemoteIP` isn't in the list, `c.Protocol()` will return https in case when tls connection is handled by the app, or http otherwise, `c.IP()` will return RemoteIP() from fasthttp context, `c.Hostname()` will return `fasthttp.Request.URI().Host()` | `false` | -| ErrorHandler | `ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler. Mounted fiber error handlers are retained by the top-level app and applied on prefix associated requests. | `DefaultErrorHandler` | -| GETOnly | `bool` | Rejects all non-GET requests if set to true. This option is useful as anti-DoS protection for servers accepting only GET requests. The request size is limited by ReadBufferSize if GETOnly is set. | `false` | -| IdleTimeout | `time.Duration` | The maximum amount of time to wait for the next request when keep-alive is enabled. If IdleTimeout is zero, the value of ReadTimeout is used. | `nil` | -| Immutable | `bool` | When enabled, all values returned by context methods are immutable. By default, they are valid until you return from the handler; see issue [\#185](https://github.com/gofiber/fiber/issues/185). | `false` | -| JSONDecoder | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` | -| JSONEncoder | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` | -| Network | `string` | Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only)

**WARNING:** When prefork is set to true, only "tcp4" and "tcp6" can be chosen. | `NetworkTCP4` | -| PassLocalsToViews | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` | -| Prefork | `bool` | Enables use of the[`SO_REUSEPORT`](https://lwn.net/Articles/542629/)socket option. This will spawn multiple Go processes listening on the same port. learn more about [socket sharding](https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/). **NOTE: if enabled, the application will need to be ran through a shell because prefork mode sets environment variables. If you're using Docker, make sure the app is ran with `CMD ./app` or `CMD ["sh", "-c", "/app"]`. For more info, see** [**this**](https://github.com/gofiber/fiber/issues/1021#issuecomment-730537971) **issue comment.** | `false` | -| ProxyHeader | `string` | This will enable `c.IP()` to return the value of the given header key. By default `c.IP()`will return the Remote IP from the TCP connection, this property can be useful if you are behind a load balancer e.g. _X-Forwarded-\*_. | `""` | -| ReadBufferSize | `int` | per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers \(for example, BIG cookies\). | `4096` | -| ReadTimeout | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `nil` | -| RequestMethods | `[]string` | RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish. | `DefaultMethods` | -| ServerHeader | `string` | Enables the `Server` HTTP header with the given value. | `""` | -| StreamRequestBody | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` | -| StrictRouting | `bool` | When enabled, the router treats `/foo` and `/foo/` as different. Otherwise, the router treats `/foo` and `/foo/` as the same. | `false` | -| TrustedProxies | `[]string` | Contains the list of trusted proxy IP's. Look at `EnableTrustedProxyCheck` doc.

It can take IP or IP range addresses. | `[]string*__*` | -| UnescapePath | `bool` | Converts all encoded characters in the route back before setting the path for the context, so that the routing can also work with URL encoded special characters | `false` | -| Views | `Views` | Views is the interface that wraps the Render function. See our **Template Middleware** for supported engines. | `nil` | -| ViewsLayout | `string` | Views Layout is the global layout for all template render until override on Render function. See our **Template Middleware** for supported engines. | `""` | -| WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | `4096` | -| WriteTimeout | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` | -| XMLEncoder | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` | - -## NewError +#### Config fields + +| Property | Type | Description | Default | +|---------------------------------------------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| AppName | `string` | This allows to setup app name for the app | `""` | +| BodyLimit | `int` | Sets the maximum allowed size for a request body, if the size exceeds the configured limit, it sends `413 - Request Entity Too Large` response. | `4 * 1024 * 1024` | +| CaseSensitive | `bool` | When enabled, `/Foo` and `/foo` are different routes. When disabled, `/Foo`and `/foo` are treated the same. | `false` | +| ColorScheme | [`Colors`](https://github.com/gofiber/fiber/blob/master/color.go) | You can define custom color scheme. They'll be used for startup message, route list and some middlewares. | [`DefaultColors`](https://github.com/gofiber/fiber/blob/master/color.go) | +| CompressedFileSuffix | `string` | Adds a suffix to the original file name and tries saving the resulting compressed file under the new file name. | `".fiber.gz"` | +| Concurrency | `int` | Maximum number of concurrent connections. | `256 * 1024` | +| DisableDefaultContentType | `bool` | When set to true, causes the default Content-Type header to be excluded from the Response. | `false` | +| DisableDefaultDate | `bool` | When set to true causes the default date header to be excluded from the response. | `false` | +| DisableHeaderNormalizing | `bool` | By default all header names are normalized: conteNT-tYPE -> Content-Type | `false` | +| DisableKeepalive | `bool` | Disable keep-alive connections, the server will close incoming connections after sending the first response to the client | `false` | +| DisablePreParseMultipartForm | `bool` | Will not pre parse Multipart Form data if set to true. This option is useful for servers that desire to treat multipart form data as a binary blob, or choose when to parse the data. | `false` | +| EnableIPValidation | `bool` | If set to true, `c.IP()` and `c.IPs()` will validate IP addresses before returning them. Also, `c.IP()` will return only the first valid IP rather than just the raw header value that may be a comma separated string.

**WARNING:** There is a small performance cost to doing this validation. Keep disabled if speed is your only concern and your application is behind a trusted proxy that already validates this header. | `false` | +| EnableSplittingOnParsers | `bool` | EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true.

For example, you can use it to parse multiple values from a query parameter like this: `/api?foo=bar,baz == foo[]=bar&foo[]=baz` | `false` | +| EnableTrustedProxyCheck | `bool` | When set to true, fiber will check whether proxy is trusted, using TrustedProxies list.

By default `c.Protocol()` will get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header, `c.IP()` will get value from `ProxyHeader` header, `c.Hostname()` will get value from X-Forwarded-Host header.
If `EnableTrustedProxyCheck` is true, and `RemoteIP` is in the list of `TrustedProxies` `c.Protocol()`, `c.IP()`, and `c.Hostname()` will have the same behaviour when `EnableTrustedProxyCheck` disabled, if `RemoteIP` isn't in the list, `c.Protocol()` will return https in case when tls connection is handled by the app, or http otherwise, `c.IP()` will return RemoteIP() from fasthttp context, `c.Hostname()` will return `fasthttp.Request.URI().Host()` | `false` | +| ErrorHandler | `ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler. Mounted fiber error handlers are retained by the top-level app and applied on prefix associated requests. | `DefaultErrorHandler` | +| GETOnly | `bool` | Rejects all non-GET requests if set to true. This option is useful as anti-DoS protection for servers accepting only GET requests. The request size is limited by ReadBufferSize if GETOnly is set. | `false` | +| IdleTimeout | `time.Duration` | The maximum amount of time to wait for the next request when keep-alive is enabled. If IdleTimeout is zero, the value of ReadTimeout is used. | `nil` | +| Immutable | `bool` | When enabled, all values returned by context methods are immutable. By default, they are valid until you return from the handler; see issue [\#185](https://github.com/gofiber/fiber/issues/185). | `false` | +| JSONDecoder | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` | +| JSONEncoder | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` | +| PassLocalsToViews | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` | +| ProxyHeader | `string` | This will enable `c.IP()` to return the value of the given header key. By default `c.IP()`will return the Remote IP from the TCP connection, this property can be useful if you are behind a load balancer e.g. _X-Forwarded-\*_. | `""` | +| ReadBufferSize | `int` | per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers \(for example, BIG cookies\). | `4096` | +| ReadTimeout | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `nil` | +| ReduceMemoryUsage | `bool` | Aggressively reduces memory usage at the cost of higher CPU usage if set to true. | `false` | +| RequestMethods | `[]string` | RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish. | `DefaultMethods` | +| ServerHeader | `string` | Enables the `Server` HTTP header with the given value. | `""` | +| StreamRequestBody | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` | +| StrictRouting | `bool` | When enabled, the router treats `/foo` and `/foo/` as different. Otherwise, the router treats `/foo` and `/foo/` as the same. | `false` | +| StructValidator | `StructValidator` | If you want to validate header/form/query... automatically when to bind, you can define struct validator. Fiber doesn't have default validator, so it'll skip validator step if you don't use any validator. | `nil` | +| TrustedProxies | `[]string` | Contains the list of trusted proxy IP's. Look at `EnableTrustedProxyCheck` doc.

It can take IP or IP range addresses. | `nil` | +| UnescapePath | `bool` | Converts all encoded characters in the route back before setting the path for the context, so that the routing can also work with URL encoded special characters | `false` | +| Views | `Views` | Views is the interface that wraps the Render function. See our **Template Middleware** for supported engines. | `nil` | +| ViewsLayout | `string` | Views Layout is the global layout for all template render until override on Render function. See our **Template Middleware** for supported engines. | `""` | +| WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | `4096` | +| WriteTimeout | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` | +| XMLEncoder | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` | + + +## Server listening + +### Config + +You can pass an optional ListenConfig when calling the [`Listen`](#listen) or [`Listener`](#listener) method. + +```go title="Example" +// Custom config +app.Listen(":8080", fiber.ListenConfig{ + EnablePrefork: true, + DisableStartupMessage: true, +}) +``` + +#### Config fields + +| Property | Type | Description | Default | +|-------------------------------------------------------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|---------| +| BeforeServeFunc | `func(app *App) error` | Allows customizing and accessing fiber app before serving the app. | `nil` | +| CertClientFile | `string` | Path of the client certificate. If you want to use mTLS, you must enter this field. | `""` | +| CertFile | `string` | Path of the certificate file. If you want to use TLS, you must enter this field. | `""` | +| CertKeyFile | `string` | Path of the certificate's private key. If you want to use TLS, you must enter this field. | `""` | +| DisableStartupMessage | `bool` | When set to true, it will not print out the «Fiber» ASCII art and listening address. | `false` | +| EnablePrefork | `bool` | When set to true, this will spawn multiple Go processes listening on the same port. | `false` | +| EnablePrintRoutes | `bool` | If set to true, will print all routes with their method, path, and handler. | `false` | +| GracefulContext | `context.Context` | Field to shutdown Fiber by given context gracefully. | `nil` | +| ListenerAddrFunc | `func(addr net.Addr)` | Allows accessing and customizing `net.Listener`. | `nil` | +| ListenerNetwork | `string` | Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only). WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen. | `tcp4` | +| OnShutdownError | `func(err error)` | Allows to customize error behavior when gracefully shutting down the server by given signal. Prints error with `log.Fatalf()` | `nil` | +| OnShutdownSuccess | `func()` | Allows to customize success behavior when gracefully shutting down the server by given signal. | `nil` | +| TLSConfigFunc | `func(tlsConfig *tls.Config)` | Allows customizing `tls.Config` as you want. | `nil` | + + +### Listen + +Listen serves HTTP requests from the given address. + +```go title="Signature" +func (app *App) Listen(addr string, config ...ListenConfig) error +``` + +```go title="Examples" +// Listen on port :8080 +app.Listen(":8080") + +// Listen on port :8080 with Prefork +app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true}) + +// Custom host +app.Listen("127.0.0.1:8080") +``` + +#### Prefork + +Prefork is a feature that allows you to spawn multiple Go processes listening on the same port. This can be useful for scaling across multiple CPU cores. + +```go title="Examples" +app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true}) +``` + +This distributes the incoming connections between the spawned processes and allows more requests to be handled simultaneously. + +#### TLS + +TLS serves HTTPs requests from the given address using certFile and keyFile paths to as TLS certificate and key file. + +```go title="Examples" +app.Listen(":443", fiber.ListenConfig{CertFile: "./cert.pem", CertKeyFile: "./cert.key"}) +``` + +#### TLS with certificate + +```go title="Examples" +app.Listen(":443", fiber.ListenConfig{CertClientFile: "./ca-chain-cert.pem"}) +``` + +#### TLS with certFile, keyFile and clientCertFile + +```go title="Examples" +app.Listen(":443", fiber.ListenConfig{CertFile: "./cert.pem", CertKeyFile: "./cert.key", CertClientFile: "./ca-chain-cert.pem"}) +``` + +### 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. + +```go title="Signature" +func (app *App) Listener(ln net.Listener, config ...ListenConfig) error +``` + +```go title="Examples" +ln, _ := net.Listen("tcp", ":3000") + +cer, _:= tls.LoadX509KeyPair("server.crt", "server.key") + +ln = tls.NewListener(ln, &tls.Config{Certificates: []tls.Certificate{cer}}) + +app.Listener(ln) +``` + +## Server + +Server returns the underlying [fasthttp server](https://godoc.org/github.com/valyala/fasthttp#Server) + +```go title="Signature" +func (app *App) Server() *fasthttp.Server +``` + +```go title="Examples" +func main() { + app := fiber.New() + + app.Server().MaxConnsPerIP = 1 + + // ... +} +``` + +## Server Shutdown + +Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners and then waits indefinitely for all connections to return to idle before shutting down. + +ShutdownWithTimeout will forcefully close any active connections after the timeout expires. + +ShutdownWithContext shuts down the server including by force if the context's deadline is exceeded. + +```go +func (app *App) Shutdown() error +func (app *App) ShutdownWithTimeout(timeout time.Duration) error +func (app *App) ShutdownWithContext(ctx context.Context) error +``` + + +## Helper functions + +### NewError NewError creates a new HTTPError instance with an optional message. @@ -96,7 +232,7 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -## IsChild +### IsChild IsChild determines if the current process is a result of Prefork. @@ -105,16 +241,20 @@ func IsChild() bool ``` ```go title="Example" -// Prefork will spawn child processes -app := fiber.New(fiber.Config{ - Prefork: true, -}) +// Config app +app := fiber.New() -if !fiber.IsChild() { - fmt.Println("I'm the parent process") -} else { - fmt.Println("I'm a child process") -} +app.Get("/", func(c fiber.Ctx) error { + if !fiber.IsChild() { + fmt.Println("I'm the parent process") + } else { + fmt.Println("I'm a child process") + } + return c.SendString("Hello, World!") +}) // ... + +// With prefork enabled, the parent process will spawn child processes +app.Listen(":8080", fiber.ListenConfig{EnablePrefork: true}) ``` diff --git a/docs/guide/hooks.md b/docs/api/hooks.md similarity index 99% rename from docs/guide/hooks.md rename to docs/api/hooks.md index 7e51d9ae847..c9852ad505a 100644 --- a/docs/guide/hooks.md +++ b/docs/api/hooks.md @@ -1,7 +1,7 @@ --- id: hooks title: 🎣 Hooks -sidebar_position: 6 +sidebar_position: 8 --- import Tabs from '@theme/Tabs'; diff --git a/docs/api/log.md b/docs/api/log.md index e53d6d4b0a4..d6a14a6df08 100644 --- a/docs/api/log.md +++ b/docs/api/log.md @@ -2,7 +2,7 @@ id: log title: 📃 Log description: Fiber's built-in log package -sidebar_position: 6 +sidebar_position: 7 --- We can use logs to observe program behavior, diagnose problems, or configure corresponding alarms. diff --git a/docs/api/redirect.md b/docs/api/redirect.md new file mode 100644 index 00000000000..23aa593993b --- /dev/null +++ b/docs/api/redirect.md @@ -0,0 +1,264 @@ +--- +id: redirect +title: ↪️ Redirect +description: Fiber's built-in redirect package +sidebar_position: 5 +toc_max_heading_level: 5 +--- + +Is used to redirect the ctx(request) to a different URL/Route. + +## Redirect Methods + +### To + +Redirects to the URL derived from the specified path, with specified [status](#status), a positive integer that +corresponds to an HTTP status code. + +:::info +If **not** specified, status defaults to **302 Found**. +::: + +```go title="Signature" +func (r *Redirect) To(location string) error +``` + +```go title="Example" +app.Get("/coffee", func(c fiber.Ctx) error { + // => HTTP - GET 301 /teapot + return c.Redirect().Status(fiber.StatusMovedPermanently).To("/teapot") +}) + +app.Get("/teapot", func(c fiber.Ctx) error { + return c.Status(fiber.StatusTeapot).Send("🍵 short and stout 🍵") +}) +``` + +```go title="More examples" +app.Get("/", func(c fiber.Ctx) error { + // => HTTP - GET 302 /foo/bar + return c.Redirect().To("/foo/bar") + // => HTTP - GET 302 ../login + return c.Redirect().To("../login") + // => HTTP - GET 302 http://example.com + return c.Redirect().To("http://example.com") + // => HTTP - GET 301 https://example.com + return c.Redirect().Status(301).To("http://example.com") +}) +``` + +### Route + +Redirects to the specific route along with the parameters and queries. + +:::info +If you want to send queries and params to route, you must use the [**RedirectConfig**](#redirectconfig) struct. +::: + +```go title="Signature" +func (r *Redirect) Route(name string, config ...RedirectConfig) error +``` + +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + // /user/fiber + return c.Redirect().Route("user", fiber.RedirectConfig{ + Params: fiber.Map{ + "name": "fiber", + }, + }) +}) + +app.Get("/with-queries", func(c fiber.Ctx) error { + // /user/fiber?data[0][name]=john&data[0][age]=10&test=doe + return c.Route("user", RedirectConfig{ + Params: fiber.Map{ + "name": "fiber", + }, + Queries: map[string]string{ + "data[0][name]": "john", + "data[0][age]": "10", + "test": "doe", + }, + }) +}) + +app.Get("/user/:name", func(c fiber.Ctx) error { + return c.SendString(c.Params("name")) +}).Name("user") +``` + +### Back + +Redirects back to refer URL. It redirects to fallback URL if refer header doesn't exists, with specified status, a +positive integer that corresponds to an HTTP status code. + +:::info +If **not** specified, status defaults to **302 Found**. +::: + +```go title="Signature" +func (r *Redirect) Back(fallback string) error +``` + +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + return c.SendString("Home page") +}) +app.Get("/test", func(c fiber.Ctx) error { + c.Set("Content-Type", "text/html") + return c.SendString(`Back`) +}) + +app.Get("/back", func(c fiber.Ctx) error { + return c.Redirect().Back("/") +}) +``` + +## Controls + +:::info +Method are **chainable**. +::: + +### Status + +Sets the HTTP status code for the redirect. + +:::info +Is used in conjunction with [**To**](#to), [**Route**](#route) and [**Back**](#back) methods. +::: + +```go title="Signature" +func (r *Redirect) Status(status int) *Redirect +``` + +```go title="Example" +app.Get("/coffee", func(c fiber.Ctx) error { + // => HTTP - GET 301 /teapot + return c.Redirect().Status(fiber.StatusMovedPermanently).To("/teapot") +}) +``` + +### RedirectConfig + +Sets the configuration for the redirect. + +:::info +Is used in conjunction with the [**Route**](#route) method. +::: + +```go +// RedirectConfig A config to use with Redirect().Route() +type RedirectConfig struct { + Params fiber.Map // Route parameters + Queries map[string]string // Query map +} +``` + +### Flash Message + +Similar to [Laravel](https://laravel.com/docs/11.x/redirects#redirecting-with-flashed-session-data) we can flash a message and retrieve it in the next request. + +#### Messages + +Get flash messages. Check [With](#with) for more information. + +```go title="Signature" +func (r *Redirect) Messages() map[string]string +``` + +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + messages := c.Redirect().Messages() + return c.JSON(messages) +}) +``` + +#### Message + +Get flash message by key. Check [With](#with) for more information. + +```go title="Signature" +func (r *Redirect) Message(key string) *Redirect +``` + +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + message := c.Redirect().Message("status") + return c.SendString(message) +}) +``` + + +#### OldInputs + +Get old input data. Check [WithInput](#withinput) for more information. + +```go title="Signature" +func (r *Redirect) OldInputs() map[string]string +``` + +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + oldInputs := c.Redirect().OldInputs() + return c.JSON(oldInputs) +}) +``` + +#### OldInput + +Get old input data by key. Check [WithInput](#withinput) for more information. + +```go title="Signature" +func (r *Redirect) OldInput(key string) string +``` + +```go title="Example" +app.Get("/name", func(c fiber.Ctx) error { + oldInput := c.Redirect().OldInput("name") + return c.SendString(oldInput) +}) +``` + +#### With + +You can send flash messages by using `With()`. + +```go title="Signature" +func (r *Redirect) With(key, value string) *Redirect +``` + +```go title="Example" +app.Get("/login", func(c fiber.Ctx) error { + return c.Redirect().With("status", "Logged in successfully").To("/") +}) + +app.Get("/", func(c fiber.Ctx) error { + // => Logged in successfully + return c.SendString(c.Redirect().Message("status")) +}) +``` + +#### WithInput + +You can send input data by using `WithInput()`. +They will be sent as a cookie. + +This method can send form, multipart form, query data to redirected route depending on the request content type. + +```go title="Signature" +func (r *Redirect) WithInput() *Redirect +``` + +```go title="Example" +// curl -X POST http://localhost:3000/login -d "name=John" +app.Post("/login", func(c fiber.Ctx) error { + return c.Redirect().WithInput().Route("name") +}) + +app.Get("/name", func(c fiber.Ctx) error { + // => John + return c.SendString(c.Redirect().OldInput("name")) +}).Name("name") +``` diff --git a/docs/extra/_category_.json b/docs/extra/_category_.json index f17f137ab38..3398bed555f 100644 --- a/docs/extra/_category_.json +++ b/docs/extra/_category_.json @@ -1,8 +1,8 @@ { - "label": "Extra", - "position": 4, + "label": "\uD83E\uDDE9 Extra", + "position": 6, "link": { "type": "generated-index", "description": "Extra contents for Fiber." } -} \ No newline at end of file +} diff --git a/docs/extra/faq.md b/docs/extra/faq.md index 37b810cd3af..eaea2a2c419 100644 --- a/docs/extra/faq.md +++ b/docs/extra/faq.md @@ -62,7 +62,7 @@ A complete example demonstrating the use of Air with Fiber can be found in the [ ## How do I set up an error handler? -To override the default error handler, you can override the default when providing a [Config](../api/fiber.md#config) when initiating a new [Fiber instance](../api/fiber.md#new). +To override the default error handler, you can override the default when providing a [Config](../api/fiber.md#errorhandler) when initiating a new [Fiber instance](../api/fiber.md#new). ```go title="Example" app := fiber.New(fiber.Config{ diff --git a/docs/guide/_category_.json b/docs/guide/_category_.json index b0e157aa76c..62226d5d68e 100644 --- a/docs/guide/_category_.json +++ b/docs/guide/_category_.json @@ -1,6 +1,6 @@ { - "label": "Guide", - "position": 3, + "label": "\uD83D\uDCD6 Guide", + "position": 5, "link": { "type": "generated-index", "description": "Guides for Fiber." diff --git a/docs/guide/error-handling.md b/docs/guide/error-handling.md index 7817443b443..584f28eda20 100644 --- a/docs/guide/error-handling.md +++ b/docs/guide/error-handling.md @@ -90,7 +90,7 @@ var DefaultErrorHandler = func(c fiber.Ctx, err error) error { ## Custom Error Handler -A custom error handler can be set using a [Config ](../api/fiber.md#config)when initializing a [Fiber instance](../api/fiber.md#new). +A custom error handler can be set using a [Config](../api/fiber.md#errorhandler) when initializing a [Fiber instance](../api/fiber.md#new). In most cases, the default error handler should be sufficient. However, a custom error handler can come in handy if you want to capture different types of errors and take action accordingly e.g., send a notification email or log an error to the centralized system. You can also send customized responses to the client e.g., error page or just a JSON response. diff --git a/docs/guide/faster-fiber.md b/docs/guide/faster-fiber.md index 95b8ec96cb8..9218a7278d7 100644 --- a/docs/guide/faster-fiber.md +++ b/docs/guide/faster-fiber.md @@ -30,7 +30,8 @@ func main() { ``` ### References + - [Set custom JSON encoder for client](../api/client.md#jsonencoder) - [Set custom JSON decoder for client](../api/client.md#jsondecoder) -- [Set custom JSON encoder for application](../api/fiber.md#config) -- [Set custom JSON decoder for application](../api/fiber.md#config) +- [Set custom JSON encoder for application](../api/fiber.md#jsonencoder) +- [Set custom JSON decoder for application](../api/fiber.md#jsondecoder) diff --git a/docs/guide/routing.md b/docs/guide/routing.md index 3de5bb73658..7dcc2b61e76 100644 --- a/docs/guide/routing.md +++ b/docs/guide/routing.md @@ -5,6 +5,7 @@ description: >- Routing refers to how an application's endpoints (URIs) respond to client requests. sidebar_position: 1 +toc_max_heading_level: 4 --- import Tabs from '@theme/Tabs'; @@ -150,21 +151,21 @@ Constraints aren't validation for parameters. If constraints aren't valid for a | Constraint | Example | Example matches | | ----------------- | ------------------------------------ | ------------------------------------------------------------------------------------------- | -| int | :id | 123456789, -123456789 | -| bool | :active | true,false | -| guid | :id | CD2C1638-1638-72D5-1638-DEADBEEF1638 | -| float | :weight | 1.234, -1,001.01e8 | -| minLen(value) | :username | Test (must be at least 4 characters) | -| maxLen(value) | :filename | MyFile (must be no more than 8 characters | -| len(length) | :filename | somefile.txt (exactly 12 characters) | -| min(value) | :age | 19 (Integer value must be at least 18) | -| max(value) | :age | 91 (Integer value must be no more than 120) | -| range(min,max) | :age | 91 (Integer value must be at least 18 but no more than 120) | -| alpha | :name | Rick (String must consist of one or more alphabetical characters, a-z and case-insensitive) | -| datetime | :dob | 2005-11-01 | -| regex(expression) | :date | 2022-08-27 (Must match regular expression) | - -**Examples** +| int | `:id` | 123456789, -123456789 | +| bool | `:active` | true,false | +| guid | `:id` | CD2C1638-1638-72D5-1638-DEADBEEF1638 | +| float | `:weight` | 1.234, -1,001.01e8 | +| minLen(value) | `:username` | Test (must be at least 4 characters) | +| maxLen(value) | `:filename` | MyFile (must be no more than 8 characters | +| len(length) | `:filename` | somefile.txt (exactly 12 characters) | +| min(value) | `:age` | 19 (Integer value must be at least 18) | +| max(value) | `:age` | 91 (Integer value must be no more than 120) | +| range(min,max) | `:age` | 91 (Integer value must be at least 18 but no more than 120) | +| alpha | `:name` | Rick (String must consist of one or more alphabetical characters, a-z and case-insensitive) | +| datetime | `:dob` | 2005-11-01 | +| regex(expression) | `:date` | 2022-08-27 (Must match regular expression) | + +#### Examples @@ -224,7 +225,7 @@ app.Get(`/:date`, func(c fiber.Ctx) error { You should use `\\` before routing-specific characters when to use datetime constraint (`*`, `+`, `?`, `:`, `/`, `<`, `>`, `;`, `(`, `)`), to avoid wrong parsing. ::: -**Optional Parameter Example** +#### Optional Parameter Example You can impose constraints on optional parameters as well. @@ -240,7 +241,7 @@ app.Get("/:test?", func(c fiber.Ctx) error { // Cannot GET /7.0 ``` -**Custom Constraint Example** +#### Custom Constraint Custom constraints can be added to Fiber using the `app.RegisterCustomConstraint` method. Your constraints have to be compatible with the `CustomConstraint` interface. @@ -297,7 +298,7 @@ func main() { Functions that are designed to make changes to the request or response are called **middleware functions**. The [Next](../api/ctx.md#next) is a **Fiber** router function, when called, executes the **next** function that **matches** the current route. -**Example of a middleware function** +### Example of a middleware function ```go app.Use(func(c fiber.Ctx) error { diff --git a/docs/guide/utils.md b/docs/guide/utils.md new file mode 100644 index 00000000000..3dbff1ab92a --- /dev/null +++ b/docs/guide/utils.md @@ -0,0 +1,125 @@ +--- +id: utils +title: 🧰 Utils +sidebar_position: 8 +toc_max_heading_level: 4 +--- + +## Generics + +### Convert + +Converts a string value to a specified type, handling errors and optional default values. +This function simplifies the conversion process by encapsulating error handling and the management of default values, making your code cleaner and more consistent. + +```go title="Signature" +func Convert[T any](value string, convertor func(string) (T, error), defaultValue ...T) (*T, error) +``` + +```go title="Example" +// GET http://example.com/id/bb70ab33-d455-4a03-8d78-d3c1dacae9ff +app.Get("/id/:id", func(c fiber.Ctx) error { + fiber.Convert(c.Params("id"), uuid.Parse) // UUID(bb70ab33-d455-4a03-8d78-d3c1dacae9ff), nil + + +// GET http://example.com/search?id=65f6f54221fb90e6a6b76db7 +app.Get("/search", func(c fiber.Ctx) error) { + fiber.Convert(c.Query("id"), mongo.ParseObjectID) // objectid(65f6f54221fb90e6a6b76db7), nil + fiber.Convert(c.Query("id"), uuid.Parse) // uuid.Nil, error(cannot parse given uuid) + fiber.Convert(c.Query("id"), uuid.Parse, mongo.NewObjectID) // new object id generated and return nil as error. +} + + // ... +}) +``` + + +### GetReqHeader + +GetReqHeader function utilizing Go's generics feature. +This function allows for retrieving HTTP request headers with a more specific data type. + +```go title="Signature" +func GetReqHeader[V any](c Ctx, key string, defaultValue ...V) V +``` + +```go title="Example" +app.Get("/search", func(c fiber.Ctx) error { + // curl -X GET http://example.com/search -H "X-Request-ID: 12345" -H "X-Request-Name: John" + GetReqHeader[int](c, "X-Request-ID") // => returns 12345 as integer. + GetReqHeader[string](c, "X-Request-Name") // => returns "John" as string. + GetReqHeader[string](c, "unknownParam", "default") // => returns "default" as string. + // ... +}) +``` + +### Locals + +Locals function utilizing Go's generics feature. +This function allows for manipulating and retrieving local values within a request context with a more specific data type. + +```go title="Signature" +func Locals[V any](c Ctx, key any, value ...V) V + +// get local value +func Locals[V any](c Ctx, key any) V +// set local value +func Locals[V any](c Ctx, key any, value ...V) V +``` + +```go title="Example" +app.Use("/user/:user/:id", func(c fiber.Ctx) error { + // set local values + fiber.Locals[string](c, "user", "john") + fiber.Locals[int](c, "id", 25) + // ... + + return c.Next() +}) + + +app.Get("/user/*", func(c fiber.Ctx) error { + // get local values + name := fiber.Locals[string](c, "user") // john + age := fiber.Locals[int](c, "id") // 25 + // ... +}) +``` + +### Params + +Params function utilizing Go's generics feature. +This function allows for retrieving route parameters with a more specific data type. + +```go title="Signature" +func Params[V any](c Ctx, key string, defaultValue ...V) V +``` + +```go title="Example" +app.Get("/user/:user/:id", func(c fiber.Ctx) error { + // http://example.com/user/john/25 + Params[int](c, "id") // => returns 25 as integer. + Params[int](c, "unknownParam", 99) // => returns the default 99 as integer. + // ... + return c.SendString("Hello, " + fiber.Params[string](c, "user")) +}) +``` + +### Query + +Query function utilizing Go's generics feature. +This function allows for retrieving query parameters with a more specific data type. + +```go title="Signature" +func Query[V any](c Ctx, key string, defaultValue ...V) V +``` + +```go title="Example" +app.Get("/search", func(c fiber.Ctx) error { + // http://example.com/search?name=john&age=25 + Query[string](c, "name") // => returns "john" + Query[int](c, "age") // => returns 25 as integer. + Query[string](c, "unknownParam", "default") // => returns "default" as string. + // ... +}) +``` diff --git a/docs/guide/validation.md b/docs/guide/validation.md index 58131bc0163..450255264d5 100644 --- a/docs/guide/validation.md +++ b/docs/guide/validation.md @@ -6,163 +6,39 @@ sidebar_position: 5 ## Validator package -Fiber can make _great_ use of the validator package to ensure correct validation of data to store. +Fiber provides the [Bind](../api/bind.md#validation) function to validate and bind [request data](../api/bind.md#binders) to a struct. -- [Official validator Github page \(Installation, use, examples..\).](https://github.com/go-playground/validator) +```go title="Example" -You can find the detailed descriptions of the _validations_ used in the fields contained on the structs below: +import "github.com/go-playground/validator/v10" -- [Detailed docs](https://pkg.go.dev/github.com/go-playground/validator?tab=doc) - -```go title="Validation Example" -package main - -import ( - "fmt" - "log" - "strings" - - "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v3" -) - -type ( - User struct { - Name string `validate:"required,min=5,max=20"` // Required field, min 5 char long max 20 - Age int `validate:"required,teener"` // Required field, and client needs to implement our 'teener' tag format which we'll see later - } - - ErrorResponse struct { - Error bool - FailedField string - Tag string - Value any - } - - XValidator struct { - validator *validator.Validate - } - - GlobalErrorHandlerResp struct { - Success bool `json:"success"` - Message string `json:"message"` - } -) - -// This is the validator instance -// for more information see: https://github.com/go-playground/validator -var validate = validator.New() - -func (v XValidator) Validate(data any) []ErrorResponse { - validationErrors := []ErrorResponse{} - - errs := validate.Struct(data) - if errs != nil { - for _, err := range errs.(validator.ValidationErrors) { - // In this case data object is actually holding the User struct - var elem ErrorResponse - - elem.FailedField = err.Field() // Export struct field name - elem.Tag = err.Tag() // Export struct tag - elem.Value = err.Value() // Export field value - elem.Error = true - - validationErrors = append(validationErrors, elem) - } - } - - return validationErrors +type structValidator struct { + validate *validator.Validate } -func main() { - myValidator := &XValidator{ - validator: validate, - } - - app := fiber.New(fiber.Config{ - // Global custom error handler - ErrorHandler: func(c fiber.Ctx, err error) error { - return c.Status(fiber.StatusBadRequest).JSON(GlobalErrorHandlerResp{ - Success: false, - Message: err.Error(), - }) - }, - }) - - // Custom struct validation tag format - myValidator.validator.RegisterValidation("teener", func(fl validator.FieldLevel) bool { - // User.Age needs to fit our needs, 12-18 years old. - return fl.Field().Int() >= 12 && fl.Field().Int() <= 18 - }) - - app.Get("/", func(c fiber.Ctx) error { - user := &User{ - Name: fiber.Query[string](c, "name"), - Age: fiber.Query[int](c, "age"), - } - - // Validation - if errs := myValidator.Validate(user); len(errs) > 0 && errs[0].Error { - errMsgs := make([]string, 0) - - for _, err := range errs { - errMsgs = append(errMsgs, fmt.Sprintf( - "[%s]: '%v' | Needs to implement '%s'", - err.FailedField, - err.Value, - err.Tag, - )) - } - - return &fiber.Error{ - Code: fiber.ErrBadRequest.Code, - Message: strings.Join(errMsgs, " and "), - } - } - - // Logic, validated with success - return c.SendString("Hello, World!") - }) - - log.Fatal(app.Listen(":3000")) +// Validator needs to implement the Validate method +func (v *structValidator) Validate(out any) error { + return v.validate.Struct(out) } -/** -OUTPUT - -[1] -Request: - -GET http://127.0.0.1:3000/ - -Response: - -{"success":false,"message":"[Name]: '' | Needs to implement 'required' and [Age]: '0' | Needs to implement 'required'"} - -[2] -Request: - -GET http://127.0.0.1:3000/?name=efdal&age=9 +// Setup your validator in the config +app := fiber.New(fiber.Config{ + StructValidator: &structValidator{validate: validator.New()}, +}) -Response: -{"success":false,"message":"[Age]: '9' | Needs to implement 'teener'"} - -[3] -Request: - -GET http://127.0.0.1:3000/?name=efdal&age= - -Response: -{"success":false,"message":"[Age]: '0' | Needs to implement 'required'"} - -[4] -Request: - -GET http://127.0.0.1:3000/?name=efdal&age=18 - -Response: -Hello, World! - -**/ +type User struct { + Name string `json:"name" form:"name" query:"name" validate:"required"` + Age int `json:"age" form:"age" query:"age" validate:"gte=0,lte=100"` +} +app.Post("/", func(c fiber.Ctx) error { + user := new(User) + + // Works with all bind methods - Body, Query, Form, ... + if err := c.Bind().Body(user); err != nil { // <- here you receive the validation errors + return err + } + + return c.JSON(user) +}) ``` diff --git a/docs/intro.md b/docs/intro.md index 1814a34eca2..6dbc6ca7643 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -92,7 +92,7 @@ func main() { } ``` -```text +```bash go run server.go ``` diff --git a/docs/api/middleware/_category_.json b/docs/middleware/_category_.json similarity index 82% rename from docs/api/middleware/_category_.json rename to docs/middleware/_category_.json index 133ac5147ad..625fd1b39b0 100644 --- a/docs/api/middleware/_category_.json +++ b/docs/middleware/_category_.json @@ -1,9 +1,9 @@ { - "label": "🧬 Middleware", - "position": 7, + "label": "\uD83E\uDDEC Middleware", + "position": 4, "collapsed": true, "link": { "type": "generated-index", "description": "Middleware is a function chained in the HTTP request cycle with access to the Context which it uses to perform a specific action, for example, logging every request or enabling CORS." } -} \ No newline at end of file +} diff --git a/docs/api/middleware/adaptor.md b/docs/middleware/adaptor.md similarity index 100% rename from docs/api/middleware/adaptor.md rename to docs/middleware/adaptor.md diff --git a/docs/api/middleware/basicauth.md b/docs/middleware/basicauth.md similarity index 100% rename from docs/api/middleware/basicauth.md rename to docs/middleware/basicauth.md diff --git a/docs/api/middleware/cache.md b/docs/middleware/cache.md similarity index 98% rename from docs/api/middleware/cache.md rename to docs/middleware/cache.md index 26022cf0f1f..389f42c7165 100644 --- a/docs/api/middleware/cache.md +++ b/docs/middleware/cache.md @@ -66,7 +66,7 @@ app.Get("/", func(c fiber.Ctx) error { | Property | Type | Description | Default | |:---------------------|:------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------| -| Next | `func(fiber.Ctx) bool` | Next defines a function that is executed before creating the cache entry and can be used to execute the request without cache creation. If an entry already exists, it will be used. If you want to completely bypass the cache functionality in certain cases, you should use the [skip middleware](./skip.md). | `nil` | +| Next | `func(fiber.Ctx) bool` | Next defines a function that is executed before creating the cache entry and can be used to execute the request without cache creation. If an entry already exists, it will be used. If you want to completely bypass the cache functionality in certain cases, you should use the [skip middleware](skip.md). | `nil` | | Expiration | `time.Duration` | Expiration is the time that a cached response will live. | `1 * time.Minute` | | CacheHeader | `string` | CacheHeader is the header on the response header that indicates the cache status, with the possible return values "hit," "miss," or "unreachable." | `X-Cache` | | CacheControl | `bool` | CacheControl enables client-side caching if set to true. | `false` | diff --git a/docs/api/middleware/compress.md b/docs/middleware/compress.md similarity index 100% rename from docs/api/middleware/compress.md rename to docs/middleware/compress.md diff --git a/docs/api/middleware/cors.md b/docs/middleware/cors.md similarity index 100% rename from docs/api/middleware/cors.md rename to docs/middleware/cors.md diff --git a/docs/api/middleware/csrf.md b/docs/middleware/csrf.md similarity index 99% rename from docs/api/middleware/csrf.md rename to docs/middleware/csrf.md index 2c098dc5f8f..739802501cb 100644 --- a/docs/api/middleware/csrf.md +++ b/docs/middleware/csrf.md @@ -6,7 +6,7 @@ id: csrf The CSRF middleware for [Fiber](https://github.com/gofiber/fiber) provides protection against [Cross-Site Request Forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) (CSRF) attacks. Requests made using methods other than those defined as 'safe' by [RFC9110#section-9.2.1](https://datatracker.ietf.org/doc/html/rfc9110.html#section-9.2.1) (GET, HEAD, OPTIONS, and TRACE) are validated using tokens. If a potential attack is detected, the middleware will return a default 403 Forbidden error. -This middleware offers two [Token Validation Patterns](#token-validation-patterns): the [Double Submit Cookie Pattern (default)](#double-submit-cookie-pattern-default), and the [Synchronizer Token Pattern (with Session)](#synchronizer-token-pattern-session). +This middleware offers two [Token Validation Patterns](#token-validation-patterns): the [Double Submit Cookie Pattern (default)](#double-submit-cookie-pattern-default), and the [Synchronizer Token Pattern (with Session)](#synchronizer-token-pattern-with-session). As a [Defense In Depth](#defense-in-depth) measure, this middleware performs [Referer Checking](#referer-checking) for HTTPS requests. @@ -180,7 +180,7 @@ app.Use(csrf.New(csrf.Config{ })) ``` -::caution +:::caution When using `TrustedOrigins` with subdomain matching, make sure you control and trust all the subdomains, including all subdomain levels. If not, an attacker could create a subdomain under a trusted origin and use it to send harmful requests. ::: diff --git a/docs/api/middleware/earlydata.md b/docs/middleware/earlydata.md similarity index 100% rename from docs/api/middleware/earlydata.md rename to docs/middleware/earlydata.md diff --git a/docs/api/middleware/encryptcookie.md b/docs/middleware/encryptcookie.md similarity index 100% rename from docs/api/middleware/encryptcookie.md rename to docs/middleware/encryptcookie.md diff --git a/docs/api/middleware/envvar.md b/docs/middleware/envvar.md similarity index 100% rename from docs/api/middleware/envvar.md rename to docs/middleware/envvar.md diff --git a/docs/api/middleware/etag.md b/docs/middleware/etag.md similarity index 100% rename from docs/api/middleware/etag.md rename to docs/middleware/etag.md diff --git a/docs/api/middleware/expvar.md b/docs/middleware/expvar.md similarity index 100% rename from docs/api/middleware/expvar.md rename to docs/middleware/expvar.md diff --git a/docs/api/middleware/favicon.md b/docs/middleware/favicon.md similarity index 100% rename from docs/api/middleware/favicon.md rename to docs/middleware/favicon.md diff --git a/docs/api/middleware/filesystem.md b/docs/middleware/filesystem.md similarity index 100% rename from docs/api/middleware/filesystem.md rename to docs/middleware/filesystem.md diff --git a/docs/api/middleware/healthcheck.md b/docs/middleware/healthcheck.md similarity index 100% rename from docs/api/middleware/healthcheck.md rename to docs/middleware/healthcheck.md diff --git a/docs/api/middleware/helmet.md b/docs/middleware/helmet.md similarity index 100% rename from docs/api/middleware/helmet.md rename to docs/middleware/helmet.md diff --git a/docs/api/middleware/idempotency.md b/docs/middleware/idempotency.md similarity index 100% rename from docs/api/middleware/idempotency.md rename to docs/middleware/idempotency.md diff --git a/docs/api/middleware/keyauth.md b/docs/middleware/keyauth.md similarity index 100% rename from docs/api/middleware/keyauth.md rename to docs/middleware/keyauth.md diff --git a/docs/api/middleware/limiter.md b/docs/middleware/limiter.md similarity index 100% rename from docs/api/middleware/limiter.md rename to docs/middleware/limiter.md diff --git a/docs/api/middleware/logger.md b/docs/middleware/logger.md similarity index 100% rename from docs/api/middleware/logger.md rename to docs/middleware/logger.md diff --git a/docs/api/middleware/monitor.md b/docs/middleware/monitor.md similarity index 100% rename from docs/api/middleware/monitor.md rename to docs/middleware/monitor.md diff --git a/docs/api/middleware/pprof.md b/docs/middleware/pprof.md similarity index 100% rename from docs/api/middleware/pprof.md rename to docs/middleware/pprof.md diff --git a/docs/api/middleware/proxy.md b/docs/middleware/proxy.md similarity index 100% rename from docs/api/middleware/proxy.md rename to docs/middleware/proxy.md diff --git a/docs/api/middleware/recover.md b/docs/middleware/recover.md similarity index 100% rename from docs/api/middleware/recover.md rename to docs/middleware/recover.md diff --git a/docs/api/middleware/redirect.md b/docs/middleware/redirect.md similarity index 100% rename from docs/api/middleware/redirect.md rename to docs/middleware/redirect.md diff --git a/docs/api/middleware/requestid.md b/docs/middleware/requestid.md similarity index 100% rename from docs/api/middleware/requestid.md rename to docs/middleware/requestid.md diff --git a/docs/api/middleware/rewrite.md b/docs/middleware/rewrite.md similarity index 100% rename from docs/api/middleware/rewrite.md rename to docs/middleware/rewrite.md diff --git a/docs/api/middleware/session.md b/docs/middleware/session.md similarity index 100% rename from docs/api/middleware/session.md rename to docs/middleware/session.md diff --git a/docs/api/middleware/skip.md b/docs/middleware/skip.md similarity index 100% rename from docs/api/middleware/skip.md rename to docs/middleware/skip.md diff --git a/docs/api/middleware/timeout.md b/docs/middleware/timeout.md similarity index 100% rename from docs/api/middleware/timeout.md rename to docs/middleware/timeout.md diff --git a/docs/partials/routing/handler.md b/docs/partials/routing/handler.md index 55794b99121..5cff828d4cc 100644 --- a/docs/partials/routing/handler.md +++ b/docs/partials/routing/handler.md @@ -3,26 +3,28 @@ id: route-handlers title: Route Handlers --- +import Reference from '@site/src/components/reference'; + Registers a route bound to a specific [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). ```go title="Signatures" // HTTP methods -func (app *App) Get(path string, handlers ...Handler) Router -func (app *App) Head(path string, handlers ...Handler) Router -func (app *App) Post(path string, handlers ...Handler) Router -func (app *App) Put(path string, handlers ...Handler) Router -func (app *App) Delete(path string, handlers ...Handler) Router -func (app *App) Connect(path string, handlers ...Handler) Router -func (app *App) Options(path string, handlers ...Handler) Router -func (app *App) Trace(path string, handlers ...Handler) Router -func (app *App) Patch(path string, handlers ...Handler) Router +func (app *App) Get(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Head(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Post(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Put(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Delete(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Connect(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Options(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Trace(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Patch(path string, handler Handler, middlewares ...Handler) Router // Add allows you to specify a method as value -func (app *App) Add(method, path string, handlers ...Handler) Router +func (app *App) Add(method, path string, handler Handler, middlewares ...Handler) Router // All will register the route on all HTTP methods // Almost the same as app.Use but not bound to prefixes -func (app *App) All(path string, handlers ...Handler) Router +func (app *App) All(path string, handler Handler, middlewares ...Handler) Router ``` ```go title="Examples" @@ -37,10 +39,18 @@ app.Post("/api/register", func(c fiber.Ctx) error { }) ``` -**Use** can be used for middleware packages and prefix catchers. These routes will only match the beginning of each path i.e. `/john` will match `/john/doe`, `/johnnnnn` etc +**Use** + +Can be used for middleware packages and prefix catchers. These routes will only match the beginning of each path i.e. `/john` will match `/john/doe`, `/johnnnnn` etc ```go title="Signature" func (app *App) Use(args ...any) Router + +// Different usage variations +func (app *App) Use(handler Handler, middlewares ...Handler) Router +func (app *App) Use(path string, handler Handler, middlewares ...Handler) Router +func (app *App) Use(paths []string, handler Handler, middlewares ...Handler) Router +func (app *App) Use(path string, app *App) Router ``` ```go title="Examples" @@ -66,4 +76,7 @@ app.Use("/api", func(c fiber.Ctx) error { }, func(c fiber.Ctx) error { return c.Next() }) + +// Mount a sub-app +app.Use("/api", api) ``` diff --git a/docs/whats_new.md b/docs/whats_new.md new file mode 100644 index 00000000000..8975db3115f --- /dev/null +++ b/docs/whats_new.md @@ -0,0 +1,111 @@ +--- +id: whats_new +title: 🆕 Whats New in v3 +sidebar_position: 2 +--- + +:::caution + +Its a draft, not finished yet. + +::: + +[//]: # (https://github.com/gofiber/fiber/releases/tag/v3.0.0-beta.2) + +## 🎉 Welcome to Fiber v3 + +We are excited to announce the release of Fiber v3! 🚀 + +Fiber v3 is a major release with a lot of new features, improvements, and breaking changes. We have worked hard to make Fiber even faster, more flexible, and easier to use. + +## 🚀 Highlights + +### Drop for old Go versions + +Fiber v3 drops support for Go versions below 1.21. We recommend upgrading to Go 1.21 or higher to use Fiber v3. + +### App changes + +We have made several changes to the Fiber app, including: + +* Listen -> unified with config +* app.Config properties moved to listen config + * DisableStartupMessage + * EnablePrefork -> previously Prefork + * EnablePrintRoutes + * ListenerNetwork -> previously Network + +#### new methods + +* RegisterCustomBinder +* RegisterCustomConstraint +* NewCtxFunc + +#### removed methods + +* Mount -> Use app.Use() instead +* ListenTLS -> Use app.Listen() with tls.Config +* ListenTLSWithCertificate -> Use app.Listen() with tls.Config +* ListenMutualTLS -> Use app.Listen() with tls.Config +* ListenMutualTLSWithCertificate -> Use app.Listen() with tls.Config + +#### changed methods + +* Routing methods -> Get(), Post(), Put(), Delete(), Patch(), Options(), Trace(), Connect() and All() +* Use -> can be used for app mounting +* Test -> timeout changed to 1 second +* Listen -> has a config parameter +* Listener -> has a config parameter + +### Context change +#### interface +#### customizable + +#### new methods + +* AutoFormat -> ExpressJs like +* Host -> ExpressJs like +* Port -> ExpressJs like +* IsProxyTrusted +* Reset +* Schema -> ExpressJs like +* SendStream -> ExpressJs like +* SendString -> ExpressJs like +* String -> ExpressJs like +* ViewBind -> instead of Bind + +#### removed methods + +* AllParams -> c.Bind().URL() ? +* ParamsInt -> Params Generic +* QueryBool -> Query Generic +* QueryFloat -> Query Generic +* QueryInt -> Query Generic +* BodyParser -> c.Bind().Body() +* CookieParser -> c.Bind().Cookie() +* ParamsParser -> c.Bind().URL() +* RedirectToRoute -> c.Redirect().Route() +* RedirectBack -> c.Redirect().Back() +* ReqHeaderParser -> c.Bind().Header() + +#### changed methods + +* Bind -> for Binding instead of View, us c.ViewBind() +* Format -> Param: body interface{} -> handlers ...ResFmt +* Redirect -> c.Redirect().To() + +### Client package + + +### Binding +### Generic functions + +### Middleware refactoring +#### Session middleware +#### Filesystem middleware +### Monitor middleware + +Monitor middleware is now in Contrib package. + +## Migration guide +... diff --git a/helpers.go b/helpers.go index cb03ff8273c..4a564da97a8 100644 --- a/helpers.go +++ b/helpers.go @@ -14,6 +14,7 @@ import ( "os" "path/filepath" "reflect" + "strconv" "strings" "sync" "time" @@ -697,341 +698,117 @@ func IsMethodIdempotent(m string) bool { } } -// HTTP methods were copied from net/http. -const ( - MethodGet = "GET" // RFC 7231, 4.3.1 - MethodHead = "HEAD" // RFC 7231, 4.3.2 - MethodPost = "POST" // RFC 7231, 4.3.3 - MethodPut = "PUT" // RFC 7231, 4.3.4 - MethodPatch = "PATCH" // RFC 5789 - MethodDelete = "DELETE" // RFC 7231, 4.3.5 - MethodConnect = "CONNECT" // RFC 7231, 4.3.6 - MethodOptions = "OPTIONS" // RFC 7231, 4.3.7 - MethodTrace = "TRACE" // RFC 7231, 4.3.8 - methodUse = "USE" -) - -// MIME types that are commonly used -const ( - MIMETextXML = "text/xml" - MIMETextHTML = "text/html" - MIMETextPlain = "text/plain" - MIMETextJavaScript = "text/javascript" - MIMEApplicationXML = "application/xml" - MIMEApplicationJSON = "application/json" - // Deprecated: use MIMETextJavaScript instead - MIMEApplicationJavaScript = "application/javascript" - MIMEApplicationForm = "application/x-www-form-urlencoded" - MIMEOctetStream = "application/octet-stream" - MIMEMultipartForm = "multipart/form-data" - - MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" - MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" - MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" - MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8" - MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" - MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" - // Deprecated: use MIMETextJavaScriptCharsetUTF8 instead - MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8" -) - -// HTTP status codes were copied from net/http with the following updates: -// - Rename StatusNonAuthoritativeInfo to StatusNonAuthoritativeInformation -// - Add StatusSwitchProxy (306) -// NOTE: Keep this list in sync with statusMessage -const ( - StatusContinue = 100 // RFC 9110, 15.2.1 - StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2 - StatusProcessing = 102 // RFC 2518, 10.1 - StatusEarlyHints = 103 // RFC 8297 - - StatusOK = 200 // RFC 9110, 15.3.1 - StatusCreated = 201 // RFC 9110, 15.3.2 - StatusAccepted = 202 // RFC 9110, 15.3.3 - StatusNonAuthoritativeInformation = 203 // RFC 9110, 15.3.4 - StatusNoContent = 204 // RFC 9110, 15.3.5 - StatusResetContent = 205 // RFC 9110, 15.3.6 - StatusPartialContent = 206 // RFC 9110, 15.3.7 - StatusMultiStatus = 207 // RFC 4918, 11.1 - StatusAlreadyReported = 208 // RFC 5842, 7.1 - StatusIMUsed = 226 // RFC 3229, 10.4.1 - - StatusMultipleChoices = 300 // RFC 9110, 15.4.1 - StatusMovedPermanently = 301 // RFC 9110, 15.4.2 - StatusFound = 302 // RFC 9110, 15.4.3 - StatusSeeOther = 303 // RFC 9110, 15.4.4 - StatusNotModified = 304 // RFC 9110, 15.4.5 - StatusUseProxy = 305 // RFC 9110, 15.4.6 - StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused) - StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8 - StatusPermanentRedirect = 308 // RFC 9110, 15.4.9 - - StatusBadRequest = 400 // RFC 9110, 15.5.1 - StatusUnauthorized = 401 // RFC 9110, 15.5.2 - StatusPaymentRequired = 402 // RFC 9110, 15.5.3 - StatusForbidden = 403 // RFC 9110, 15.5.4 - StatusNotFound = 404 // RFC 9110, 15.5.5 - StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6 - StatusNotAcceptable = 406 // RFC 9110, 15.5.7 - StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8 - StatusRequestTimeout = 408 // RFC 9110, 15.5.9 - StatusConflict = 409 // RFC 9110, 15.5.10 - StatusGone = 410 // RFC 9110, 15.5.11 - StatusLengthRequired = 411 // RFC 9110, 15.5.12 - StatusPreconditionFailed = 412 // RFC 9110, 15.5.13 - StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14 - StatusRequestURITooLong = 414 // RFC 9110, 15.5.15 - StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16 - StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17 - StatusExpectationFailed = 417 // RFC 9110, 15.5.18 - StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused) - StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20 - StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21 - StatusLocked = 423 // RFC 4918, 11.3 - StatusFailedDependency = 424 // RFC 4918, 11.4 - StatusTooEarly = 425 // RFC 8470, 5.2. - StatusUpgradeRequired = 426 // RFC 9110, 15.5.22 - StatusPreconditionRequired = 428 // RFC 6585, 3 - StatusTooManyRequests = 429 // RFC 6585, 4 - StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 - StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 - - StatusInternalServerError = 500 // RFC 9110, 15.6.1 - StatusNotImplemented = 501 // RFC 9110, 15.6.2 - StatusBadGateway = 502 // RFC 9110, 15.6.3 - StatusServiceUnavailable = 503 // RFC 9110, 15.6.4 - StatusGatewayTimeout = 504 // RFC 9110, 15.6.5 - StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6 - StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 - StatusInsufficientStorage = 507 // RFC 4918, 11.5 - StatusLoopDetected = 508 // RFC 5842, 7.2 - StatusNotExtended = 510 // RFC 2774, 7 - StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 -) +func IndexRune(str string, needle int32) bool { + for _, b := range str { + if b == needle { + return true + } + } + return false +} -// Errors -var ( - ErrBadRequest = NewError(StatusBadRequest) // 400 - ErrUnauthorized = NewError(StatusUnauthorized) // 401 - ErrPaymentRequired = NewError(StatusPaymentRequired) // 402 - ErrForbidden = NewError(StatusForbidden) // 403 - ErrNotFound = NewError(StatusNotFound) // 404 - ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // 405 - ErrNotAcceptable = NewError(StatusNotAcceptable) // 406 - ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // 407 - ErrRequestTimeout = NewError(StatusRequestTimeout) // 408 - ErrConflict = NewError(StatusConflict) // 409 - ErrGone = NewError(StatusGone) // 410 - ErrLengthRequired = NewError(StatusLengthRequired) // 411 - ErrPreconditionFailed = NewError(StatusPreconditionFailed) // 412 - ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // 413 - ErrRequestURITooLong = NewError(StatusRequestURITooLong) // 414 - ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // 415 - ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // 416 - ErrExpectationFailed = NewError(StatusExpectationFailed) // 417 - ErrTeapot = NewError(StatusTeapot) // 418 - ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // 421 - ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // 422 - ErrLocked = NewError(StatusLocked) // 423 - ErrFailedDependency = NewError(StatusFailedDependency) // 424 - ErrTooEarly = NewError(StatusTooEarly) // 425 - ErrUpgradeRequired = NewError(StatusUpgradeRequired) // 426 - ErrPreconditionRequired = NewError(StatusPreconditionRequired) // 428 - ErrTooManyRequests = NewError(StatusTooManyRequests) // 429 - ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // 431 - ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // 451 - - ErrInternalServerError = NewError(StatusInternalServerError) // 500 - ErrNotImplemented = NewError(StatusNotImplemented) // 501 - ErrBadGateway = NewError(StatusBadGateway) // 502 - ErrServiceUnavailable = NewError(StatusServiceUnavailable) // 503 - ErrGatewayTimeout = NewError(StatusGatewayTimeout) // 504 - ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // 505 - ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // 506 - ErrInsufficientStorage = NewError(StatusInsufficientStorage) // 507 - ErrLoopDetected = NewError(StatusLoopDetected) // 508 - ErrNotExtended = NewError(StatusNotExtended) // 510 - ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // 511 -) +// assertValueType asserts the type of the result to the type of the value +func assertValueType[V GenericType, T any](result T) V { + v, ok := any(result).(V) + if !ok { + panic(fmt.Errorf("failed to type-assert to %T", v)) + } + return v +} -// HTTP Headers were copied from net/http. -const ( - HeaderAuthorization = "Authorization" - HeaderProxyAuthenticate = "Proxy-Authenticate" - HeaderProxyAuthorization = "Proxy-Authorization" - HeaderWWWAuthenticate = "WWW-Authenticate" - HeaderAge = "Age" - HeaderCacheControl = "Cache-Control" - HeaderClearSiteData = "Clear-Site-Data" - HeaderExpires = "Expires" - HeaderPragma = "Pragma" - HeaderWarning = "Warning" - HeaderAcceptCH = "Accept-CH" - HeaderAcceptCHLifetime = "Accept-CH-Lifetime" - HeaderContentDPR = "Content-DPR" - HeaderDPR = "DPR" - HeaderEarlyData = "Early-Data" - HeaderSaveData = "Save-Data" - HeaderViewportWidth = "Viewport-Width" - HeaderWidth = "Width" - HeaderETag = "ETag" - HeaderIfMatch = "If-Match" - HeaderIfModifiedSince = "If-Modified-Since" - HeaderIfNoneMatch = "If-None-Match" - HeaderIfUnmodifiedSince = "If-Unmodified-Since" - HeaderLastModified = "Last-Modified" - HeaderVary = "Vary" - HeaderConnection = "Connection" - HeaderKeepAlive = "Keep-Alive" - HeaderAccept = "Accept" - HeaderAcceptCharset = "Accept-Charset" - HeaderAcceptEncoding = "Accept-Encoding" - HeaderAcceptLanguage = "Accept-Language" - HeaderCookie = "Cookie" - HeaderExpect = "Expect" - HeaderMaxForwards = "Max-Forwards" - HeaderSetCookie = "Set-Cookie" - HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" - HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" - HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" - HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" - HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" - HeaderAccessControlMaxAge = "Access-Control-Max-Age" - HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" - HeaderAccessControlRequestMethod = "Access-Control-Request-Method" - HeaderOrigin = "Origin" - HeaderTimingAllowOrigin = "Timing-Allow-Origin" - HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" - HeaderDNT = "DNT" - HeaderTk = "Tk" - HeaderContentDisposition = "Content-Disposition" - HeaderContentEncoding = "Content-Encoding" - HeaderContentLanguage = "Content-Language" - HeaderContentLength = "Content-Length" - HeaderContentLocation = "Content-Location" - HeaderContentType = "Content-Type" - HeaderForwarded = "Forwarded" - HeaderVia = "Via" - HeaderXForwardedFor = "X-Forwarded-For" - HeaderXForwardedHost = "X-Forwarded-Host" - HeaderXForwardedProto = "X-Forwarded-Proto" - HeaderXForwardedProtocol = "X-Forwarded-Protocol" - HeaderXForwardedSsl = "X-Forwarded-Ssl" - HeaderXUrlScheme = "X-Url-Scheme" - HeaderLocation = "Location" - HeaderFrom = "From" - HeaderHost = "Host" - HeaderReferer = "Referer" - HeaderReferrerPolicy = "Referrer-Policy" - HeaderUserAgent = "User-Agent" - HeaderAllow = "Allow" - HeaderServer = "Server" - HeaderAcceptRanges = "Accept-Ranges" - HeaderContentRange = "Content-Range" - HeaderIfRange = "If-Range" - HeaderRange = "Range" - HeaderContentSecurityPolicy = "Content-Security-Policy" - HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" - HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" - HeaderExpectCT = "Expect-CT" - HeaderPermissionsPolicy = "Permissions-Policy" - HeaderPublicKeyPins = "Public-Key-Pins" - HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" - HeaderStrictTransportSecurity = "Strict-Transport-Security" - HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" - HeaderXContentTypeOptions = "X-Content-Type-Options" - HeaderXDownloadOptions = "X-Download-Options" - HeaderXFrameOptions = "X-Frame-Options" - HeaderXPoweredBy = "X-Powered-By" - HeaderXXSSProtection = "X-XSS-Protection" - HeaderLastEventID = "Last-Event-ID" - HeaderNEL = "NEL" - HeaderPingFrom = "Ping-From" - HeaderPingTo = "Ping-To" - HeaderReportTo = "Report-To" - HeaderTE = "TE" - HeaderTrailer = "Trailer" - HeaderTransferEncoding = "Transfer-Encoding" - HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" - HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" - HeaderSecWebSocketKey = "Sec-WebSocket-Key" - HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" - HeaderSecWebSocketVersion = "Sec-WebSocket-Version" - HeaderAcceptPatch = "Accept-Patch" - HeaderAcceptPushPolicy = "Accept-Push-Policy" - HeaderAcceptSignature = "Accept-Signature" - HeaderAltSvc = "Alt-Svc" - HeaderDate = "Date" - HeaderIndex = "Index" - HeaderLargeAllocation = "Large-Allocation" - HeaderLink = "Link" - HeaderPushPolicy = "Push-Policy" - HeaderRetryAfter = "Retry-After" - HeaderServerTiming = "Server-Timing" - HeaderSignature = "Signature" - HeaderSignedHeaders = "Signed-Headers" - HeaderSourceMap = "SourceMap" - HeaderUpgrade = "Upgrade" - HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" - HeaderXPingback = "X-Pingback" - HeaderXRequestID = "X-Request-ID" - HeaderXRequestedWith = "X-Requested-With" - HeaderXRobotsTag = "X-Robots-Tag" - HeaderXUACompatible = "X-UA-Compatible" - HeaderAccessControlAllowPrivateNetwork = "Access-Control-Allow-Private-Network" - HeaderAccessControlRequestPrivateNetwork = "Access-Control-Request-Private-Network" -) +func genericParseDefault[V GenericType](err error, parser func() V, defaultValue ...V) V { + var v V + if err != nil { + if len(defaultValue) > 0 { + return defaultValue[0] + } + return v + } + return parser() +} -// Network types that are commonly used -const ( - NetworkTCP = "tcp" - NetworkTCP4 = "tcp4" - NetworkTCP6 = "tcp6" -) +func genericParseInt[V GenericType](str string, bitSize int, parser func(int64) V, defaultValue ...V) V { + result, err := strconv.ParseInt(str, 10, bitSize) + return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) +} -// Compression types -const ( - StrGzip = "gzip" - StrBr = "br" - StrDeflate = "deflate" - StrBrotli = "brotli" -) +func genericParseUint[V GenericType](str string, bitSize int, parser func(uint64) V, defaultValue ...V) V { + result, err := strconv.ParseUint(str, 10, bitSize) + return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) +} -// Cookie SameSite -// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7 -const ( - CookieSameSiteDisabled = "disabled" // not in RFC, just control "SameSite" attribute will not be set. - CookieSameSiteLaxMode = "lax" - CookieSameSiteStrictMode = "strict" - CookieSameSiteNoneMode = "none" -) +func genericParseFloat[V GenericType](str string, bitSize int, parser func(float64) V, defaultValue ...V) V { + result, err := strconv.ParseFloat(str, bitSize) + return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) +} -// Route Constraints -const ( - ConstraintInt = "int" - ConstraintBool = "bool" - ConstraintFloat = "float" - ConstraintAlpha = "alpha" - ConstraintGUID = "guid" - ConstraintMinLen = "minLen" - ConstraintMaxLen = "maxLen" - ConstraintLen = "len" - ConstraintBetweenLen = "betweenLen" - ConstraintMinLenLower = "minlen" - ConstraintMaxLenLower = "maxlen" - ConstraintBetweenLenLower = "betweenlen" - ConstraintMin = "min" - ConstraintMax = "max" - ConstraintRange = "range" - ConstraintDatetime = "datetime" - ConstraintRegex = "regex" -) +func genericParseBool[V GenericType](str string, parser func(bool) V, defaultValue ...V) V { + result, err := strconv.ParseBool(str) + return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) +} -func IndexRune(str string, needle int32) bool { - for _, b := range str { - if b == needle { - return true +func genericParseType[V GenericType](str string, v V, defaultValue ...V) V { + switch any(v).(type) { + case int: + return genericParseInt[V](str, 0, func(i int64) V { return assertValueType[V, int](int(i)) }, defaultValue...) + case int8: + return genericParseInt[V](str, 8, func(i int64) V { return assertValueType[V, int8](int8(i)) }, defaultValue...) + case int16: + return genericParseInt[V](str, 16, func(i int64) V { return assertValueType[V, int16](int16(i)) }, defaultValue...) + case int32: + return genericParseInt[V](str, 32, func(i int64) V { return assertValueType[V, int32](int32(i)) }, defaultValue...) + case int64: + return genericParseInt[V](str, 64, func(i int64) V { return assertValueType[V, int64](i) }, defaultValue...) + case uint: + return genericParseUint[V](str, 32, func(i uint64) V { return assertValueType[V, uint](uint(i)) }, defaultValue...) + case uint8: + return genericParseUint[V](str, 8, func(i uint64) V { return assertValueType[V, uint8](uint8(i)) }, defaultValue...) + case uint16: + return genericParseUint[V](str, 16, func(i uint64) V { return assertValueType[V, uint16](uint16(i)) }, defaultValue...) + case uint32: + return genericParseUint[V](str, 32, func(i uint64) V { return assertValueType[V, uint32](uint32(i)) }, defaultValue...) + case uint64: + return genericParseUint[V](str, 64, func(i uint64) V { return assertValueType[V, uint64](i) }, defaultValue...) + case float32: + return genericParseFloat[V](str, 32, func(i float64) V { return assertValueType[V, float32](float32(i)) }, defaultValue...) + case float64: + return genericParseFloat[V](str, 64, func(i float64) V { return assertValueType[V, float64](i) }, defaultValue...) + case bool: + return genericParseBool[V](str, func(b bool) V { return assertValueType[V, bool](b) }, defaultValue...) + case string: + if str == "" && len(defaultValue) > 0 { + return defaultValue[0] + } + return assertValueType[V, string](str) + case []byte: + if str == "" && len(defaultValue) > 0 { + return defaultValue[0] + } + return assertValueType[V, []byte]([]byte(str)) + default: + if len(defaultValue) > 0 { + return defaultValue[0] } + return v } - return false +} + +type GenericType interface { + GenericTypeInteger | GenericTypeFloat | bool | string | []byte +} + +type GenericTypeInteger interface { + GenericTypeIntegerSigned | GenericTypeIntegerUnsigned +} + +type GenericTypeIntegerSigned interface { + int | int8 | int16 | int32 | int64 +} + +type GenericTypeIntegerUnsigned interface { + uint | uint8 | uint16 | uint32 | uint64 +} + +type GenericTypeFloat interface { + float32 | float64 } diff --git a/listen.go b/listen.go index 43759393d49..8bf78b57cb1 100644 --- a/listen.go +++ b/listen.go @@ -101,7 +101,8 @@ type ListenConfig struct { // OnShutdownError allows to customize error behavior when to graceful shutdown server by given signal. // - // Default: Print error with log.Fatalf() + // Print error with log.Fatalf() by default. + // Default: nil OnShutdownError func(err error) // OnShutdownSuccess allows to customize success behavior when to graceful shutdown server by given signal. diff --git a/redirect.go b/redirect.go index 71e71cca588..f03981b8181 100644 --- a/redirect.go +++ b/redirect.go @@ -32,7 +32,7 @@ const ( CookieDataAssigner = ":" ) -// Redirect is a struct to use it with Ctx. +// Redirect is a struct that holds the redirect data. type Redirect struct { c *DefaultCtx // Embed ctx status int // Status code of redirection. Default: StatusFound @@ -41,7 +41,7 @@ type Redirect struct { oldInput map[string]string // Old input data } -// A config to use with Redirect().Route() +// RedirectConfig A config to use with Redirect().Route() // You can specify queries or route parameters. // NOTE: We don't use net/url to parse parameters because of it has poor performance. You have to pass map. type RedirectConfig struct { @@ -86,7 +86,7 @@ func (r *Redirect) Status(code int) *Redirect { return r } -// You can send flash messages by using With(). +// With You can send flash messages by using With(). // They will be sent as a cookie. // You can get them by using: Redirect().Messages(), Redirect().Message() // Note: You must use escape char before using ',' and ':' chars to avoid wrong parsing. @@ -96,7 +96,7 @@ func (r *Redirect) With(key, value string) *Redirect { return r } -// You can send input data by using WithInput(). +// WithInput You can send input data by using WithInput(). // They will be sent as a cookie. // This method can send form, multipart form, query data to redirected route. // You can get them by using: Redirect().OldInputs(), Redirect().OldInput() @@ -117,7 +117,7 @@ func (r *Redirect) WithInput() *Redirect { return r } -// Get flash messages. +// Messages Get flash messages. func (r *Redirect) Messages() map[string]string { msgs := r.c.redirectionMessages flashMessages := make(map[string]string, len(msgs)) @@ -133,7 +133,7 @@ func (r *Redirect) Messages() map[string]string { return flashMessages } -// Get flash message by key. +// Message Get flash message by key. func (r *Redirect) Message(key string) string { msgs := r.c.redirectionMessages @@ -147,7 +147,7 @@ func (r *Redirect) Message(key string) string { return "" } -// Get old input data. +// OldInputs Get old input data. func (r *Redirect) OldInputs() map[string]string { msgs := r.c.redirectionMessages oldInputs := make(map[string]string, len(msgs)) @@ -163,7 +163,7 @@ func (r *Redirect) OldInputs() map[string]string { return oldInputs } -// Get old input data by key. +// OldInput Get old input data by key. func (r *Redirect) OldInput(key string) string { msgs := r.c.redirectionMessages @@ -177,7 +177,7 @@ func (r *Redirect) OldInput(key string) string { return "" } -// Redirect to the URL derived from the specified path, with specified status. +// To redirect to the URL derived from the specified path, with specified status. func (r *Redirect) To(location string) error { r.c.setCanonical(HeaderLocation, location) r.c.Status(r.status) @@ -253,7 +253,7 @@ func (r *Redirect) Route(name string, config ...RedirectConfig) error { return r.To(location) } -// Redirect back to the URL to referer. +// Back redirect to the URL to referer. func (r *Redirect) Back(fallback ...string) error { location := r.c.Get(HeaderReferer) if location == "" { @@ -289,6 +289,7 @@ func (r *Redirect) setFlash() { r.c.ClearCookie(FlashCookieName) } +// parseMessage is a helper function to parse flash messages and old input data func parseMessage(raw string) (string, string) { //nolint: revive // not necessary if i := findNextNonEscapedCharsetPosition(raw, []byte(CookieDataAssigner)); i != -1 { return RemoveEscapeChar(raw[:i]), RemoveEscapeChar(raw[i+1:]) diff --git a/utils.go b/utils.go deleted file mode 100644 index 0e707cfac8f..00000000000 --- a/utils.go +++ /dev/null @@ -1,112 +0,0 @@ -package fiber - -import ( - "fmt" - "strconv" -) - -// assertValueType asserts the type of the result to the type of the value -func assertValueType[V GenericType, T any](result T) V { - v, ok := any(result).(V) - if !ok { - panic(fmt.Errorf("failed to type-assert to %T", v)) - } - return v -} - -func genericParseDefault[V GenericType](err error, parser func() V, defaultValue ...V) V { - var v V - if err != nil { - if len(defaultValue) > 0 { - return defaultValue[0] - } - return v - } - return parser() -} - -func genericParseInt[V GenericType](str string, bitSize int, parser func(int64) V, defaultValue ...V) V { - result, err := strconv.ParseInt(str, 10, bitSize) - return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) -} - -func genericParseUint[V GenericType](str string, bitSize int, parser func(uint64) V, defaultValue ...V) V { - result, err := strconv.ParseUint(str, 10, bitSize) - return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) -} - -func genericParseFloat[V GenericType](str string, bitSize int, parser func(float64) V, defaultValue ...V) V { - result, err := strconv.ParseFloat(str, bitSize) - return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) -} - -func genericParseBool[V GenericType](str string, parser func(bool) V, defaultValue ...V) V { - result, err := strconv.ParseBool(str) - return genericParseDefault[V](err, func() V { return parser(result) }, defaultValue...) -} - -func genericParseType[V GenericType](str string, v V, defaultValue ...V) V { - switch any(v).(type) { - case int: - return genericParseInt[V](str, 0, func(i int64) V { return assertValueType[V, int](int(i)) }, defaultValue...) - case int8: - return genericParseInt[V](str, 8, func(i int64) V { return assertValueType[V, int8](int8(i)) }, defaultValue...) - case int16: - return genericParseInt[V](str, 16, func(i int64) V { return assertValueType[V, int16](int16(i)) }, defaultValue...) - case int32: - return genericParseInt[V](str, 32, func(i int64) V { return assertValueType[V, int32](int32(i)) }, defaultValue...) - case int64: - return genericParseInt[V](str, 64, func(i int64) V { return assertValueType[V, int64](i) }, defaultValue...) - case uint: - return genericParseUint[V](str, 32, func(i uint64) V { return assertValueType[V, uint](uint(i)) }, defaultValue...) - case uint8: - return genericParseUint[V](str, 8, func(i uint64) V { return assertValueType[V, uint8](uint8(i)) }, defaultValue...) - case uint16: - return genericParseUint[V](str, 16, func(i uint64) V { return assertValueType[V, uint16](uint16(i)) }, defaultValue...) - case uint32: - return genericParseUint[V](str, 32, func(i uint64) V { return assertValueType[V, uint32](uint32(i)) }, defaultValue...) - case uint64: - return genericParseUint[V](str, 64, func(i uint64) V { return assertValueType[V, uint64](i) }, defaultValue...) - case float32: - return genericParseFloat[V](str, 32, func(i float64) V { return assertValueType[V, float32](float32(i)) }, defaultValue...) - case float64: - return genericParseFloat[V](str, 64, func(i float64) V { return assertValueType[V, float64](i) }, defaultValue...) - case bool: - return genericParseBool[V](str, func(b bool) V { return assertValueType[V, bool](b) }, defaultValue...) - case string: - if str == "" && len(defaultValue) > 0 { - return defaultValue[0] - } - return assertValueType[V, string](str) - case []byte: - if str == "" && len(defaultValue) > 0 { - return defaultValue[0] - } - return assertValueType[V, []byte]([]byte(str)) - default: - if len(defaultValue) > 0 { - return defaultValue[0] - } - return v - } -} - -type GenericType interface { - GenericTypeInteger | GenericTypeFloat | bool | string | []byte -} - -type GenericTypeInteger interface { - GenericTypeIntegerSigned | GenericTypeIntegerUnsigned -} - -type GenericTypeIntegerSigned interface { - int | int8 | int16 | int32 | int64 -} - -type GenericTypeIntegerUnsigned interface { - uint | uint8 | uint16 | uint32 | uint64 -} - -type GenericTypeFloat interface { - float32 | float64 -}