mirror of https://github.com/gofiber/fiber.git
Merge branch 'main' into revert-3287-context-middleware
commit
56c7372c50
4
app.go
4
app.go
|
@ -109,7 +109,7 @@ type App struct {
|
||||||
// Route stack divided by HTTP methods
|
// Route stack divided by HTTP methods
|
||||||
stack [][]*Route
|
stack [][]*Route
|
||||||
// Route stack divided by HTTP methods and route prefixes
|
// Route stack divided by HTTP methods and route prefixes
|
||||||
treeStack []map[string][]*Route
|
treeStack []map[int][]*Route
|
||||||
// custom binders
|
// custom binders
|
||||||
customBinders []CustomBinder
|
customBinders []CustomBinder
|
||||||
// customConstraints is a list of external constraints
|
// customConstraints is a list of external constraints
|
||||||
|
@ -581,7 +581,7 @@ func New(config ...Config) *App {
|
||||||
|
|
||||||
// Create router stack
|
// Create router stack
|
||||||
app.stack = make([][]*Route, len(app.config.RequestMethods))
|
app.stack = make([][]*Route, len(app.config.RequestMethods))
|
||||||
app.treeStack = make([]map[string][]*Route, len(app.config.RequestMethods))
|
app.treeStack = make([]map[int][]*Route, len(app.config.RequestMethods))
|
||||||
|
|
||||||
// Override colors
|
// Override colors
|
||||||
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
|
||||||
|
|
81
ctx.go
81
ctx.go
|
@ -33,8 +33,11 @@ const (
|
||||||
schemeHTTPS = "https"
|
schemeHTTPS = "https"
|
||||||
)
|
)
|
||||||
|
|
||||||
// maxParams defines the maximum number of parameters per route.
|
const (
|
||||||
const maxParams = 30
|
// maxParams defines the maximum number of parameters per route.
|
||||||
|
maxParams = 30
|
||||||
|
maxDetectionPaths = 3
|
||||||
|
)
|
||||||
|
|
||||||
// The contextKey type is unexported to prevent collisions with context keys defined in
|
// The contextKey type is unexported to prevent collisions with context keys defined in
|
||||||
// other packages.
|
// other packages.
|
||||||
|
@ -49,28 +52,26 @@ const userContextKey contextKey = 0 // __local_user_context__
|
||||||
//
|
//
|
||||||
//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface Ctx --pkg fiber --output ctx_interface_gen.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on."
|
//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface Ctx --pkg fiber --output ctx_interface_gen.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on."
|
||||||
type DefaultCtx struct {
|
type DefaultCtx struct {
|
||||||
app *App // Reference to *App
|
app *App // Reference to *App
|
||||||
route *Route // Reference to *Route
|
route *Route // Reference to *Route
|
||||||
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
|
||||||
bind *Bind // Default bind reference
|
bind *Bind // Default bind reference
|
||||||
redirect *Redirect // Default redirect reference
|
redirect *Redirect // Default redirect reference
|
||||||
req *DefaultReq // Default request api reference
|
req *DefaultReq // Default request api reference
|
||||||
res *DefaultRes // Default response api reference
|
res *DefaultRes // Default response api reference
|
||||||
values [maxParams]string // Route parameter values
|
values [maxParams]string // Route parameter values
|
||||||
viewBindMap sync.Map // Default view map to bind template engine
|
viewBindMap sync.Map // Default view map to bind template engine
|
||||||
method string // HTTP method
|
method string // HTTP method
|
||||||
baseURI string // HTTP base uri
|
baseURI string // HTTP base uri
|
||||||
path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer
|
pathOriginal string // Original HTTP path
|
||||||
detectionPath string // Route detection path -> string copy from detectionPathBuffer
|
flashMessages redirectionMsgs // Flash messages
|
||||||
treePath string // Path for the search in the tree
|
path []byte // HTTP path with the modifications by the configuration
|
||||||
pathOriginal string // Original HTTP path
|
detectionPath []byte // Route detection path
|
||||||
pathBuffer []byte // HTTP path buffer
|
treePathHash int // Hash of the path for the search in the tree
|
||||||
detectionPathBuffer []byte // HTTP detectionPath buffer
|
indexRoute int // Index of the current route
|
||||||
flashMessages redirectionMsgs // Flash messages
|
indexHandler int // Index of the current handler
|
||||||
indexRoute int // Index of the current route
|
methodINT int // HTTP method INT equivalent
|
||||||
indexHandler int // Index of the current handler
|
matched bool // Non use route matched
|
||||||
methodINT int // HTTP method INT equivalent
|
|
||||||
matched bool // Non use route matched
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendFile defines configuration options when to transfer file with SendFile.
|
// SendFile defines configuration options when to transfer file with SendFile.
|
||||||
|
@ -1123,8 +1124,9 @@ func Params[V GenericType](c Ctx, key string, defaultValue ...V) V {
|
||||||
|
|
||||||
// Path returns the path part of the request URL.
|
// Path returns the path part of the request URL.
|
||||||
// Optionally, you could override the path.
|
// Optionally, you could override the path.
|
||||||
|
// Make copies or use the Immutable setting to use the value outside the Handler.
|
||||||
func (c *DefaultCtx) Path(override ...string) string {
|
func (c *DefaultCtx) Path(override ...string) string {
|
||||||
if len(override) != 0 && c.path != override[0] {
|
if len(override) != 0 && string(c.path) != override[0] {
|
||||||
// Set new path to context
|
// Set new path to context
|
||||||
c.pathOriginal = override[0]
|
c.pathOriginal = override[0]
|
||||||
|
|
||||||
|
@ -1133,7 +1135,7 @@ func (c *DefaultCtx) Path(override ...string) string {
|
||||||
// Prettify path
|
// Prettify path
|
||||||
c.configDependentPaths()
|
c.configDependentPaths()
|
||||||
}
|
}
|
||||||
return c.path
|
return c.app.getString(c.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheme contains the request protocol string: http or https for TLS requests.
|
// Scheme contains the request protocol string: http or https for TLS requests.
|
||||||
|
@ -1832,32 +1834,31 @@ func (c *DefaultCtx) XHR() bool {
|
||||||
// configDependentPaths set paths for route recognition and prepared paths for the user,
|
// configDependentPaths set paths for route recognition and prepared paths for the user,
|
||||||
// here the features for caseSensitive, decoded paths, strict paths are evaluated
|
// here the features for caseSensitive, decoded paths, strict paths are evaluated
|
||||||
func (c *DefaultCtx) configDependentPaths() {
|
func (c *DefaultCtx) configDependentPaths() {
|
||||||
c.pathBuffer = append(c.pathBuffer[0:0], c.pathOriginal...)
|
c.path = append(c.path[:0], c.pathOriginal...)
|
||||||
// If UnescapePath enabled, we decode the path and save it for the framework user
|
// If UnescapePath enabled, we decode the path and save it for the framework user
|
||||||
if c.app.config.UnescapePath {
|
if c.app.config.UnescapePath {
|
||||||
c.pathBuffer = fasthttp.AppendUnquotedArg(c.pathBuffer[:0], c.pathBuffer)
|
c.path = fasthttp.AppendUnquotedArg(c.path[:0], c.path)
|
||||||
}
|
}
|
||||||
c.path = c.app.getString(c.pathBuffer)
|
|
||||||
|
|
||||||
// another path is specified which is for routing recognition only
|
// another path is specified which is for routing recognition only
|
||||||
// use the path that was changed by the previous configuration flags
|
// use the path that was changed by the previous configuration flags
|
||||||
c.detectionPathBuffer = append(c.detectionPathBuffer[0:0], c.pathBuffer...)
|
c.detectionPath = append(c.detectionPath[:0], c.path...)
|
||||||
// If CaseSensitive is disabled, we lowercase the original path
|
// If CaseSensitive is disabled, we lowercase the original path
|
||||||
if !c.app.config.CaseSensitive {
|
if !c.app.config.CaseSensitive {
|
||||||
c.detectionPathBuffer = utils.ToLowerBytes(c.detectionPathBuffer)
|
c.detectionPath = utils.ToLowerBytes(c.detectionPath)
|
||||||
}
|
}
|
||||||
// If StrictRouting is disabled, we strip all trailing slashes
|
// If StrictRouting is disabled, we strip all trailing slashes
|
||||||
if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' {
|
if !c.app.config.StrictRouting && len(c.detectionPath) > 1 && c.detectionPath[len(c.detectionPath)-1] == '/' {
|
||||||
c.detectionPathBuffer = utils.TrimRight(c.detectionPathBuffer, '/')
|
c.detectionPath = utils.TrimRight(c.detectionPath, '/')
|
||||||
}
|
}
|
||||||
c.detectionPath = c.app.getString(c.detectionPathBuffer)
|
|
||||||
|
|
||||||
// Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed,
|
// Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed,
|
||||||
// since the first three characters area select a list of routes
|
// since the first three characters area select a list of routes
|
||||||
c.treePath = c.treePath[0:0]
|
c.treePathHash = 0
|
||||||
const maxDetectionPaths = 3
|
|
||||||
if len(c.detectionPath) >= maxDetectionPaths {
|
if len(c.detectionPath) >= maxDetectionPaths {
|
||||||
c.treePath = c.detectionPath[:maxDetectionPaths]
|
c.treePathHash = int(c.detectionPath[0])<<16 |
|
||||||
|
int(c.detectionPath[1])<<8 |
|
||||||
|
int(c.detectionPath[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1958,12 +1959,12 @@ func (c *DefaultCtx) getIndexRoute() int {
|
||||||
return c.indexRoute
|
return c.indexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultCtx) getTreePath() string {
|
func (c *DefaultCtx) getTreePathHash() int {
|
||||||
return c.treePath
|
return c.treePathHash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultCtx) getDetectionPath() string {
|
func (c *DefaultCtx) getDetectionPath() string {
|
||||||
return c.detectionPath
|
return c.app.getString(c.detectionPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DefaultCtx) getPathOriginal() string {
|
func (c *DefaultCtx) getPathOriginal() string {
|
||||||
|
|
|
@ -19,7 +19,7 @@ type CustomCtx interface {
|
||||||
// Methods to use with next stack.
|
// Methods to use with next stack.
|
||||||
getMethodINT() int
|
getMethodINT() int
|
||||||
getIndexRoute() int
|
getIndexRoute() int
|
||||||
getTreePath() string
|
getTreePathHash() int
|
||||||
getDetectionPath() string
|
getDetectionPath() string
|
||||||
getPathOriginal() string
|
getPathOriginal() string
|
||||||
getValues() *[maxParams]string
|
getValues() *[maxParams]string
|
||||||
|
|
|
@ -347,7 +347,7 @@ type Ctx interface {
|
||||||
// Methods to use with next stack.
|
// Methods to use with next stack.
|
||||||
getMethodINT() int
|
getMethodINT() int
|
||||||
getIndexRoute() int
|
getIndexRoute() int
|
||||||
getTreePath() string
|
getTreePathHash() int
|
||||||
getDetectionPath() string
|
getDetectionPath() string
|
||||||
getPathOriginal() string
|
getPathOriginal() string
|
||||||
getValues() *[maxParams]string
|
getValues() *[maxParams]string
|
||||||
|
|
|
@ -8,8 +8,7 @@ sidebar_position: 5
|
||||||
|
|
||||||
Fiber provides the [Bind](../api/bind.md#validation) function to validate and bind [request data](../api/bind.md#binders) to a struct.
|
Fiber provides the [Bind](../api/bind.md#validation) function to validate and bind [request data](../api/bind.md#binders) to a struct.
|
||||||
|
|
||||||
```go title="Example"
|
```go title="Basic Example"
|
||||||
|
|
||||||
import "github.com/go-playground/validator/v10"
|
import "github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
type structValidator struct {
|
type structValidator struct {
|
||||||
|
@ -42,3 +41,71 @@ app.Post("/", func(c fiber.Ctx) error {
|
||||||
return c.JSON(user)
|
return c.JSON(user)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```go title="Advanced Validation Example"
|
||||||
|
type User struct {
|
||||||
|
Name string `json:"name" validate:"required,min=3,max=32"`
|
||||||
|
Email string `json:"email" validate:"required,email"`
|
||||||
|
Age int `json:"age" validate:"gte=0,lte=100"`
|
||||||
|
Password string `json:"password" validate:"required,min=8"`
|
||||||
|
Website string `json:"website" validate:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validation error messages
|
||||||
|
type UserWithCustomMessages struct {
|
||||||
|
Name string `json:"name" validate:"required,min=3,max=32" message:"Name is required and must be between 3 and 32 characters"`
|
||||||
|
Email string `json:"email" validate:"required,email" message:"Valid email is required"`
|
||||||
|
Age int `json:"age" validate:"gte=0,lte=100" message:"Age must be between 0 and 100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Post("/user", func(c fiber.Ctx) error {
|
||||||
|
user := new(User)
|
||||||
|
|
||||||
|
if err := c.Bind().Body(user); err != nil {
|
||||||
|
// Handle validation errors
|
||||||
|
if validationErrors, ok := err.(validator.ValidationErrors); ok {
|
||||||
|
for _, e := range validationErrors {
|
||||||
|
// e.Field() - field name
|
||||||
|
// e.Tag() - validation tag
|
||||||
|
// e.Value() - invalid value
|
||||||
|
// e.Param() - validation parameter
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"field": e.Field(),
|
||||||
|
"error": e.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(user)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```go title="Custom Validator Example"
|
||||||
|
// Custom validator for password strength
|
||||||
|
type PasswordValidator struct {
|
||||||
|
validate *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *PasswordValidator) Validate(out any) error {
|
||||||
|
if err := v.validate.Struct(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom password validation logic
|
||||||
|
if user, ok := out.(*User); ok {
|
||||||
|
if len(user.Password) < 8 {
|
||||||
|
return errors.New("password must be at least 8 characters")
|
||||||
|
}
|
||||||
|
// Add more password validation rules here
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
app := fiber.New(fiber.Config{
|
||||||
|
StructValidator: &PasswordValidator{validate: validator.New()},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
|
@ -79,7 +79,7 @@ app.Use(logger.New(logger.Config{
|
||||||
TimeZone: "Asia/Shanghai",
|
TimeZone: "Asia/Shanghai",
|
||||||
Done: func(c fiber.Ctx, logString []byte) {
|
Done: func(c fiber.Ctx, logString []byte) {
|
||||||
if c.Response().StatusCode() != fiber.StatusOK {
|
if c.Response().StatusCode() != fiber.StatusOK {
|
||||||
reporter.SendToSlack(logString)
|
reporter.SendToSlack(logString)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -88,6 +88,23 @@ app.Use(logger.New(logger.Config{
|
||||||
app.Use(logger.New(logger.Config{
|
app.Use(logger.New(logger.Config{
|
||||||
DisableColors: true,
|
DisableColors: true,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Use predefined formats
|
||||||
|
app.Use(logger.New(logger.Config{
|
||||||
|
Format: logger.FormatCommon,
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Use(logger.New(logger.Config{
|
||||||
|
Format: logger.FormatCombined,
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Use(logger.New(logger.Config{
|
||||||
|
Format: logger.FormatJSON,
|
||||||
|
}))
|
||||||
|
|
||||||
|
app.Use(logger.New(logger.Config{
|
||||||
|
Format: logger.FormatECS,
|
||||||
|
}))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use Logger Middleware with Other Loggers
|
### Use Logger Middleware with Other Loggers
|
||||||
|
@ -136,37 +153,50 @@ Writing to os.File is goroutine-safe, but if you are using a custom Stream that
|
||||||
|
|
||||||
### Config
|
### Config
|
||||||
|
|
||||||
| Property | Type | Description | Default |
|
| Property | Type | Description | Default |
|
||||||
|:-----------------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------|
|
| :------------ | :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- |
|
||||||
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
||||||
| Skip | `func(fiber.Ctx) bool` | Skip is a function to determine if logging is skipped or written to Stream. | `nil` |
|
| Skip | `func(fiber.Ctx) bool` | Skip is a function to determine if logging is skipped or written to Stream. | `nil` |
|
||||||
| Done | `func(fiber.Ctx, []byte)` | Done is a function that is called after the log string for a request is written to Stream, and pass the log string as parameter. | `nil` |
|
| Done | `func(fiber.Ctx, []byte)` | Done is a function that is called after the log string for a request is written to Stream, and pass the log string as parameter. | `nil` |
|
||||||
| CustomTags | `map[string]LogFunc` | tagFunctions defines the custom tag action. | `map[string]LogFunc` |
|
| CustomTags | `map[string]LogFunc` | tagFunctions defines the custom tag action. | `map[string]LogFunc` |
|
||||||
| Format | `string` | Format defines the logging tags. | `[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n` |
|
| `Format` | `string` | Defines the logging tags. See more in [Predefined Formats](#predefined-formats), or create your own using [Tags](#constants). | `[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n` (same as `DefaultFormat`) |
|
||||||
| TimeFormat | `string` | TimeFormat defines the time format for log timestamps. | `15:04:05` |
|
| TimeFormat | `string` | TimeFormat defines the time format for log timestamps. | `15:04:05` |
|
||||||
| TimeZone | `string` | TimeZone can be specified, such as "UTC" and "America/New_York" and "Asia/Chongqing", etc | `"Local"` |
|
| TimeZone | `string` | TimeZone can be specified, such as "UTC" and "America/New_York" and "Asia/Chongqing", etc | `"Local"` |
|
||||||
| TimeInterval | `time.Duration` | TimeInterval is the delay before the timestamp is updated. | `500 * time.Millisecond` |
|
| TimeInterval | `time.Duration` | TimeInterval is the delay before the timestamp is updated. | `500 * time.Millisecond` |
|
||||||
| Stream | `io.Writer` | Stream is a writer where logs are written. | `os.Stdout` |
|
| Stream | `io.Writer` | Stream is a writer where logs are written. | `os.Stdout` |
|
||||||
| LoggerFunc | `func(c fiber.Ctx, data *Data, cfg Config) error` | Custom logger function for integration with logging libraries (Zerolog, Zap, Logrus, etc). Defaults to Fiber's default logger if not defined. | `see default_logger.go defaultLoggerInstance` |
|
| LoggerFunc | `func(c fiber.Ctx, data *Data, cfg Config) error` | Custom logger function for integration with logging libraries (Zerolog, Zap, Logrus, etc). Defaults to Fiber's default logger if not defined. | `see default_logger.go defaultLoggerInstance` |
|
||||||
| DisableColors | `bool` | DisableColors defines if the logs output should be colorized. | `false` |
|
| DisableColors | `bool` | DisableColors defines if the logs output should be colorized. | `false` |
|
||||||
|
|
||||||
## Default Config
|
## Default Config
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var ConfigDefault = Config{
|
var ConfigDefault = Config{
|
||||||
Next: nil,
|
Next: nil,
|
||||||
Skip nil,
|
Skip: nil,
|
||||||
Done: nil,
|
Done: nil,
|
||||||
Format: "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n",
|
Format: DefaultFormat,
|
||||||
TimeFormat: "15:04:05",
|
TimeFormat: "15:04:05",
|
||||||
TimeZone: "Local",
|
TimeZone: "Local",
|
||||||
TimeInterval: 500 * time.Millisecond,
|
TimeInterval: 500 * time.Millisecond,
|
||||||
Stream: os.Stdout,
|
Stream: os.Stdout,
|
||||||
DisableColors: false,
|
BeforeHandlerFunc: beforeHandlerFunc,
|
||||||
LoggerFunc: defaultLoggerInstance,
|
LoggerFunc: defaultLoggerInstance,
|
||||||
|
enableColors: true,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Predefined Formats
|
||||||
|
|
||||||
|
Logger provides predefined formats that you can use by name or directly by specifying the format string.
|
||||||
|
|
||||||
|
| **Format Constant** | **Format String** | **Description** |
|
||||||
|
|---------------------|--------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
|
||||||
|
| `DefaultFormat` | `"[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n"` | Fiber's default logger format. |
|
||||||
|
| `CommonFormat` | `"${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent}\n"` | Common Log Format (CLF) used in web server logs. |
|
||||||
|
| `CombinedFormat` | `"${ip} - - [${time}] "${method} ${url} ${protocol}" ${status} ${bytesSent} "${referer}" "${ua}"\n"` | CLF format plus the `referer` and `user agent` fields. |
|
||||||
|
| `JSONFormat` | `"{time: ${time}, ip: ${ip}, method: ${method}, url: ${url}, status: ${status}, bytesSent: ${bytesSent}}\n"` | JSON format for structured logging. |
|
||||||
|
| `ECSFormat` | `"{\"@timestamp\":\"${time}\",\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":\"${ip}\"},\"http\":{\"request\":{\"method\":\"${method}\",\"url\":\"${url}\",\"protocol\":\"${protocol}\"},\"response\":{\"status_code\":${status},\"body\":{\"bytes\":${bytesSent}}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":\"${method} ${url} responded with ${status}\"}\n"` | Elastic Common Schema (ECS) format for structured logging. |
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
|
@ -937,6 +937,22 @@ app.Use(logger.New(logger.Config{
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
#### Predefined Formats
|
||||||
|
|
||||||
|
Logger provides predefined formats that you can use by name or directly by specifying the format string.
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Example Usage</summary>
|
||||||
|
|
||||||
|
```go
|
||||||
|
app.Use(logger.New(logger.Config{
|
||||||
|
Format: logger.FormatCombined,
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
See more in [Logger](./middleware/logger.md#predefined-formats)
|
||||||
|
</details>
|
||||||
|
|
||||||
### Filesystem
|
### Filesystem
|
||||||
|
|
||||||
We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
|
We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
|
||||||
|
|
|
@ -113,9 +113,9 @@ func (app *App) methodExist(c *DefaultCtx) bool {
|
||||||
// Reset stack index
|
// Reset stack index
|
||||||
c.setIndexRoute(-1)
|
c.setIndexRoute(-1)
|
||||||
|
|
||||||
tree, ok := c.App().treeStack[i][c.getTreePath()]
|
tree, ok := c.App().treeStack[i][c.treePathHash]
|
||||||
if !ok {
|
if !ok {
|
||||||
tree = c.App().treeStack[i][""]
|
tree = c.App().treeStack[i][0]
|
||||||
}
|
}
|
||||||
// Get stack length
|
// Get stack length
|
||||||
lenr := len(tree) - 1
|
lenr := len(tree) - 1
|
||||||
|
@ -157,9 +157,9 @@ func (app *App) methodExistCustom(c CustomCtx) bool {
|
||||||
// Reset stack index
|
// Reset stack index
|
||||||
c.setIndexRoute(-1)
|
c.setIndexRoute(-1)
|
||||||
|
|
||||||
tree, ok := c.App().treeStack[i][c.getTreePath()]
|
tree, ok := c.App().treeStack[i][c.getTreePathHash()]
|
||||||
if !ok {
|
if !ok {
|
||||||
tree = c.App().treeStack[i][""]
|
tree = c.App().treeStack[i][0]
|
||||||
}
|
}
|
||||||
// Get stack length
|
// Get stack length
|
||||||
lenr := len(tree) - 1
|
lenr := len(tree) - 1
|
||||||
|
|
|
@ -1331,56 +1331,65 @@ func Test_CSRF_Cookie_Injection_Exploit(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use this test case and make the unsafe header value bug from https://github.com/gofiber/fiber/issues/2045 reproducible and permanently fixed/tested by this testcase
|
// TODO: use this test case and make the unsafe header value bug from https://github.com/gofiber/fiber/issues/2045 reproducible and permanently fixed/tested by this testcase
|
||||||
// func Test_CSRF_UnsafeHeaderValue(t *testing.T) {
|
func Test_CSRF_UnsafeHeaderValue(t *testing.T) {
|
||||||
// t.Parallel()
|
t.SkipNow()
|
||||||
// app := fiber.New()
|
t.Parallel()
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
// app.Use(New())
|
app.Use(New())
|
||||||
// app.Get("/", func(c fiber.Ctx) error {
|
app.Get("/", func(c fiber.Ctx) error {
|
||||||
// return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
// })
|
})
|
||||||
// app.Get("/test", func(c fiber.Ctx) error {
|
app.Get("/test", func(c fiber.Ctx) error {
|
||||||
// return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
// })
|
})
|
||||||
// app.Post("/", func(c fiber.Ctx) error {
|
app.Post("/", func(c fiber.Ctx) error {
|
||||||
// return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
// })
|
})
|
||||||
|
|
||||||
// resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||||
// require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
// var token string
|
var token string
|
||||||
// for _, c := range resp.Cookies() {
|
for _, c := range resp.Cookies() {
|
||||||
// if c.Name != ConfigDefault.CookieName {
|
if c.Name != ConfigDefault.CookieName {
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
// token = c.Value
|
token = c.Value
|
||||||
// break
|
break
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fmt.Println("token", token)
|
t.Log("token", token)
|
||||||
|
|
||||||
// getReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
getReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||||
// getReq.Header.Set(HeaderName, token)
|
getReq.Header.Set(HeaderName, token)
|
||||||
// resp, err = app.Test(getReq)
|
resp, err = app.Test(getReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
// getReq = httptest.NewRequest(fiber.MethodGet, "/test", nil)
|
getReq = httptest.NewRequest(fiber.MethodGet, "/test", nil)
|
||||||
// getReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
getReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||||
// getReq.Header.Set(fiber.HeaderCacheControl, "no")
|
getReq.Header.Set(fiber.HeaderCacheControl, "no")
|
||||||
// getReq.Header.Set(HeaderName, token)
|
getReq.Header.Set(HeaderName, token)
|
||||||
|
|
||||||
// resp, err = app.Test(getReq)
|
resp, err = app.Test(getReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
// getReq.Header.Set(fiber.HeaderAccept, "*/*")
|
getReq.Header.Set(fiber.HeaderAccept, "*/*")
|
||||||
// getReq.Header.Del(HeaderName)
|
getReq.Header.Del(HeaderName)
|
||||||
// resp, err = app.Test(getReq)
|
resp, err = app.Test(getReq)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
// postReq := httptest.NewRequest(fiber.MethodPost, "/", nil)
|
postReq := httptest.NewRequest(fiber.MethodPost, "/", nil)
|
||||||
// postReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
postReq.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||||
// postReq.Header.Set(HeaderName, token)
|
postReq.Header.Set(HeaderName, token)
|
||||||
// resp, err = app.Test(postReq)
|
resp, err = app.Test(postReq)
|
||||||
// }
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
// go test -v -run=^$ -bench=Benchmark_Middleware_CSRF_Check -benchmem -count=4
|
// go test -v -run=^$ -bench=Benchmark_Middleware_CSRF_Check -benchmem -count=4
|
||||||
func Benchmark_Middleware_CSRF_Check(b *testing.B) {
|
func Benchmark_Middleware_CSRF_Check(b *testing.B) {
|
||||||
|
|
|
@ -50,9 +50,23 @@ type Config struct {
|
||||||
|
|
||||||
timeZoneLocation *time.Location
|
timeZoneLocation *time.Location
|
||||||
|
|
||||||
// Format defines the logging tags
|
// Format defines the logging format for the middleware.
|
||||||
//
|
//
|
||||||
// Optional. Default: [${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}
|
// You can customize the log output by defining a format string with placeholders
|
||||||
|
// such as: ${time}, ${ip}, ${status}, ${method}, ${path}, ${latency}, ${error}, etc.
|
||||||
|
// The full list of available placeholders can be found in 'tags.go' or at
|
||||||
|
// 'https://docs.gofiber.io/api/middleware/logger/#constants'.
|
||||||
|
//
|
||||||
|
// Fiber provides predefined logging formats that can be used directly:
|
||||||
|
//
|
||||||
|
// - DefaultFormat → Uses the default log format: "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}"
|
||||||
|
// - CommonFormat → Uses the Apache Common Log Format (CLF): "${ip} - - [${time}] \"${method} ${url} ${protocol}\" ${status} ${bytesSent}\n"
|
||||||
|
// - CombinedFormat → Uses the Apache Combined Log Format: "${ip} - - [${time}] \"${method} ${url} ${protocol}\" ${status} ${bytesSent} \"${referer}\" \"${ua}\"\n"
|
||||||
|
// - JSONFormat → Uses the JSON log format: "{\"time\":\"${time}\",\"ip\":\"${ip}\",\"method\":\"${method}\",\"url\":\"${url}\",\"status\":${status},\"bytesSent\":${bytesSent}}\n"
|
||||||
|
// - ECSFormat → Uses the Elastic Common Schema (ECS) log format: {\"@timestamp\":\"${time}\",\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":\"${ip}\"},\"http\":{\"request\":{\"method\":\"${method}\",\"url\":\"${url}\",\"protocol\":\"${protocol}\"},\"response\":{\"status_code\":${status},\"body\":{\"bytes\":${bytesSent}}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":\"${method} ${url} responded with ${status}\"}"
|
||||||
|
// If both `Format` and `CustomFormat` are provided, the `CustomFormat` will be used, and the `Format` field will be ignored.
|
||||||
|
// If no format is specified, the default format is used:
|
||||||
|
// "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}"
|
||||||
Format string
|
Format string
|
||||||
|
|
||||||
// TimeFormat https://programming.guide/go/format-parse-string-time-date-example.html
|
// TimeFormat https://programming.guide/go/format-parse-string-time-date-example.html
|
||||||
|
@ -105,7 +119,7 @@ var ConfigDefault = Config{
|
||||||
Next: nil,
|
Next: nil,
|
||||||
Skip: nil,
|
Skip: nil,
|
||||||
Done: nil,
|
Done: nil,
|
||||||
Format: defaultFormat,
|
Format: DefaultFormat,
|
||||||
TimeFormat: "15:04:05",
|
TimeFormat: "15:04:05",
|
||||||
TimeZone: "Local",
|
TimeZone: "Local",
|
||||||
TimeInterval: 500 * time.Millisecond,
|
TimeInterval: 500 * time.Millisecond,
|
||||||
|
@ -115,9 +129,6 @@ var ConfigDefault = Config{
|
||||||
enableColors: true,
|
enableColors: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// default logging format for Fiber's default logger
|
|
||||||
var defaultFormat = "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n"
|
|
||||||
|
|
||||||
// Helper function to set default values
|
// Helper function to set default values
|
||||||
func configDefault(config ...Config) Config {
|
func configDefault(config ...Config) Config {
|
||||||
// Return default config if nothing provided
|
// Return default config if nothing provided
|
||||||
|
|
|
@ -28,7 +28,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
||||||
buf := bytebufferpool.Get()
|
buf := bytebufferpool.Get()
|
||||||
|
|
||||||
// Default output when no custom Format or io.Writer is given
|
// Default output when no custom Format or io.Writer is given
|
||||||
if cfg.Format == defaultFormat {
|
if cfg.Format == DefaultFormat {
|
||||||
// Format error if exist
|
// Format error if exist
|
||||||
formatErr := ""
|
formatErr := ""
|
||||||
if cfg.enableColors {
|
if cfg.enableColors {
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Fiber's default logger
|
||||||
|
DefaultFormat = "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n"
|
||||||
|
// Apache Common Log Format (CLF)
|
||||||
|
CommonFormat = "${ip} - - [${time}] \"${method} ${url} ${protocol}\" ${status} ${bytesSent}\n"
|
||||||
|
// Apache Combined Log Format
|
||||||
|
CombinedFormat = "${ip} - - [${time}] \"${method} ${url} ${protocol}\" ${status} ${bytesSent} \"${referer}\" \"${ua}\"\n"
|
||||||
|
// JSON log formats
|
||||||
|
JSONFormat = "{\"time\":\"${time}\",\"ip\":\"${ip}\",\"method\":\"${method}\",\"url\":\"${url}\",\"status\":${status},\"bytesSent\":${bytesSent}}\n"
|
||||||
|
// Elastic Common Schema (ECS) Log Format
|
||||||
|
ECSFormat = "{\"@timestamp\":\"${time}\",\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":\"${ip}\"},\"http\":{\"request\":{\"method\":\"${method}\",\"url\":\"${url}\",\"protocol\":\"${protocol}\"},\"response\":{\"status_code\":${status},\"body\":{\"bytes\":${bytesSent}}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":\"${method} ${url} responded with ${status}\"}\n"
|
||||||
|
)
|
|
@ -40,7 +40,6 @@ func New(config ...Config) fiber.Handler {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set PID once
|
// Set PID once
|
||||||
pid := strconv.Itoa(os.Getpid())
|
pid := strconv.Itoa(os.Getpid())
|
||||||
|
|
||||||
|
|
|
@ -467,6 +467,124 @@ func Test_Logger_All(t *testing.T) {
|
||||||
require.Equal(t, expected, buf.String())
|
require.Equal(t, expected, buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Logger_CLF_Format(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
buf := bytebufferpool.Get()
|
||||||
|
defer bytebufferpool.Put(buf)
|
||||||
|
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Format: CommonFormat,
|
||||||
|
Stream: buf,
|
||||||
|
}))
|
||||||
|
|
||||||
|
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf("0.0.0.0 - - [%s] \"%s %s %s\" %d %d\n",
|
||||||
|
time.Now().Format("15:04:05"),
|
||||||
|
fiber.MethodGet, "/?foo=bar", "HTTP/1.1",
|
||||||
|
fiber.StatusNotFound,
|
||||||
|
0)
|
||||||
|
logResponse := buf.String()
|
||||||
|
require.Equal(t, expected, logResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Logger_Combined_CLF_Format(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
buf := bytebufferpool.Get()
|
||||||
|
defer bytebufferpool.Put(buf)
|
||||||
|
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Format: CombinedFormat,
|
||||||
|
Stream: buf,
|
||||||
|
}))
|
||||||
|
const expectedUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
||||||
|
const expectedReferer = "http://example.com"
|
||||||
|
req := httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil)
|
||||||
|
req.Header.Set("Referer", expectedReferer)
|
||||||
|
req.Header.Set("User-Agent", expectedUA)
|
||||||
|
resp, err := app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf("0.0.0.0 - - [%s] %q %d %d %q %q\n",
|
||||||
|
time.Now().Format("15:04:05"),
|
||||||
|
fmt.Sprintf("%s %s %s", fiber.MethodGet, "/?foo=bar", "HTTP/1.1"),
|
||||||
|
fiber.StatusNotFound,
|
||||||
|
0,
|
||||||
|
expectedReferer,
|
||||||
|
expectedUA)
|
||||||
|
logResponse := buf.String()
|
||||||
|
require.Equal(t, expected, logResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Logger_Json_Format(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
buf := bytebufferpool.Get()
|
||||||
|
defer bytebufferpool.Put(buf)
|
||||||
|
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Format: JSONFormat,
|
||||||
|
Stream: buf,
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil)
|
||||||
|
resp, err := app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(
|
||||||
|
"{\"time\":%q,\"ip\":%q,\"method\":%q,\"url\":%q,\"status\":%d,\"bytesSent\":%d}\n",
|
||||||
|
time.Now().Format("15:04:05"),
|
||||||
|
"0.0.0.0",
|
||||||
|
fiber.MethodGet,
|
||||||
|
"/?foo=bar",
|
||||||
|
fiber.StatusNotFound,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
logResponse := buf.String()
|
||||||
|
require.Equal(t, expected, logResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Logger_ECS_Format(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
buf := bytebufferpool.Get()
|
||||||
|
defer bytebufferpool.Put(buf)
|
||||||
|
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(New(Config{
|
||||||
|
Format: ECSFormat,
|
||||||
|
Stream: buf,
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(fiber.MethodGet, "/?foo=bar", nil)
|
||||||
|
resp, err := app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(
|
||||||
|
"{\"@timestamp\":%q,\"ecs\":{\"version\":\"1.6.0\"},\"client\":{\"ip\":%q},\"http\":{\"request\":{\"method\":%q,\"url\":%q,\"protocol\":%q},\"response\":{\"status_code\":%d,\"body\":{\"bytes\":%d}}},\"log\":{\"level\":\"INFO\",\"logger\":\"fiber\"},\"message\":%q}\n",
|
||||||
|
time.Now().Format("15:04:05"),
|
||||||
|
"0.0.0.0",
|
||||||
|
fiber.MethodGet,
|
||||||
|
"/?foo=bar",
|
||||||
|
"HTTP/1.1",
|
||||||
|
fiber.StatusNotFound,
|
||||||
|
0,
|
||||||
|
fmt.Sprintf("%s %s responded with %d", fiber.MethodGet, "/?foo=bar", fiber.StatusNotFound),
|
||||||
|
)
|
||||||
|
logResponse := buf.String()
|
||||||
|
require.Equal(t, expected, logResponse)
|
||||||
|
}
|
||||||
|
|
||||||
func getLatencyTimeUnits() []struct {
|
func getLatencyTimeUnits() []struct {
|
||||||
unit string
|
unit string
|
||||||
div time.Duration
|
div time.Duration
|
||||||
|
|
32
router.go
32
router.go
|
@ -110,9 +110,9 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) boo
|
||||||
|
|
||||||
func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool param might be useful for testing
|
func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool param might be useful for testing
|
||||||
// Get stack length
|
// Get stack length
|
||||||
tree, ok := app.treeStack[c.getMethodINT()][c.getTreePath()]
|
tree, ok := app.treeStack[c.getMethodINT()][c.getTreePathHash()]
|
||||||
if !ok {
|
if !ok {
|
||||||
tree = app.treeStack[c.getMethodINT()][""]
|
tree = app.treeStack[c.getMethodINT()][0]
|
||||||
}
|
}
|
||||||
lenr := len(tree) - 1
|
lenr := len(tree) - 1
|
||||||
|
|
||||||
|
@ -158,9 +158,9 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool
|
||||||
|
|
||||||
func (app *App) next(c *DefaultCtx) (bool, error) {
|
func (app *App) next(c *DefaultCtx) (bool, error) {
|
||||||
// Get stack length
|
// Get stack length
|
||||||
tree, ok := app.treeStack[c.methodINT][c.treePath]
|
tree, ok := app.treeStack[c.methodINT][c.treePathHash]
|
||||||
if !ok {
|
if !ok {
|
||||||
tree = app.treeStack[c.methodINT][""]
|
tree = app.treeStack[c.methodINT][0]
|
||||||
}
|
}
|
||||||
lenTree := len(tree) - 1
|
lenTree := len(tree) - 1
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ func (app *App) next(c *DefaultCtx) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it matches the request path
|
// Check if it matches the request path
|
||||||
match = route.match(c.detectionPath, c.path, &c.values)
|
match = route.match(utils.UnsafeString(c.detectionPath), utils.UnsafeString(c.path), &c.values)
|
||||||
if !match {
|
if !match {
|
||||||
// No match, next route
|
// No match, next route
|
||||||
continue
|
continue
|
||||||
|
@ -454,30 +454,28 @@ func (app *App) buildTree() *App {
|
||||||
|
|
||||||
// loop all the methods and stacks and create the prefix tree
|
// loop all the methods and stacks and create the prefix tree
|
||||||
for m := range app.config.RequestMethods {
|
for m := range app.config.RequestMethods {
|
||||||
tsMap := make(map[string][]*Route)
|
tsMap := make(map[int][]*Route)
|
||||||
for _, route := range app.stack[m] {
|
for _, route := range app.stack[m] {
|
||||||
treePath := ""
|
treePathHash := 0
|
||||||
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
|
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= maxDetectionPaths {
|
||||||
treePath = route.routeParser.segs[0].Const[:3]
|
treePathHash = int(route.routeParser.segs[0].Const[0])<<16 |
|
||||||
|
int(route.routeParser.segs[0].Const[1])<<8 |
|
||||||
|
int(route.routeParser.segs[0].Const[2])
|
||||||
}
|
}
|
||||||
// create tree stack
|
// create tree stack
|
||||||
tsMap[treePath] = append(tsMap[treePath], route)
|
tsMap[treePathHash] = append(tsMap[treePathHash], route)
|
||||||
}
|
}
|
||||||
app.treeStack[m] = tsMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop the methods and tree stacks and add global stack and sort everything
|
|
||||||
for m := range app.config.RequestMethods {
|
|
||||||
tsMap := app.treeStack[m]
|
|
||||||
for treePart := range tsMap {
|
for treePart := range tsMap {
|
||||||
if treePart != "" {
|
if treePart != 0 {
|
||||||
// merge global tree routes in current tree stack
|
// merge global tree routes in current tree stack
|
||||||
tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[""]...))
|
tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[0]...))
|
||||||
}
|
}
|
||||||
// sort tree slices with the positions
|
// sort tree slices with the positions
|
||||||
slc := tsMap[treePart]
|
slc := tsMap[treePart]
|
||||||
sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
|
sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
|
||||||
}
|
}
|
||||||
|
app.treeStack[m] = tsMap
|
||||||
}
|
}
|
||||||
app.routesRefreshed = false
|
app.routesRefreshed = false
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue