mirror of https://github.com/gofiber/fiber.git
Merge branch 'main' into JIeJaitt-jiejaitt-feature/add-CSRF-userContext-support
commit
675f2b274e
2
ctx.go
2
ctx.go
|
@ -1349,7 +1349,7 @@ func (c *DefaultCtx) getLocationFromRoute(route Route, params Map) (string, erro
|
|||
|
||||
for key, val := range params {
|
||||
isSame := key == segment.ParamName || (!c.app.config.CaseSensitive && utils.EqualFold(key, segment.ParamName))
|
||||
isGreedy := segment.IsGreedy && len(key) == 1 && isInCharset(key[0], greedyParameters)
|
||||
isGreedy := segment.IsGreedy && len(key) == 1 && bytes.IndexByte(greedyParameters, key[0]) != -1
|
||||
if isSame || isGreedy {
|
||||
_, err := buf.WriteString(utils.ToString(val))
|
||||
if err != nil {
|
||||
|
|
|
@ -55,13 +55,13 @@ app.Use(logger.New(logger.Config{
|
|||
}))
|
||||
|
||||
// Custom File Writer
|
||||
file, err := os.OpenFile("./123.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
accessLog, err := os.OpenFile("./access.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Fatalf("error opening file: %v", err)
|
||||
log.Fatalf("error opening access.log file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
defer accessLog.Close()
|
||||
app.Use(logger.New(logger.Config{
|
||||
Output: file,
|
||||
Stream: accessLog,
|
||||
}))
|
||||
|
||||
// Add Custom Tags
|
||||
|
@ -115,7 +115,7 @@ func main() {
|
|||
|
||||
// Use the logger middleware with zerolog logger
|
||||
app.Use(logger.New(logger.Config{
|
||||
Output: logger.LoggerToWriter(zap, log.LevelDebug),
|
||||
Stream: logger.LoggerToWriter(zap, log.LevelDebug),
|
||||
}))
|
||||
|
||||
// Define a route
|
||||
|
@ -129,7 +129,7 @@ func main() {
|
|||
```
|
||||
|
||||
:::tip
|
||||
Writing to os.File is goroutine-safe, but if you are using a custom Output that is not goroutine-safe, make sure to implement locking to properly serialize writes.
|
||||
Writing to os.File is goroutine-safe, but if you are using a custom Stream that is not goroutine-safe, make sure to implement locking to properly serialize writes.
|
||||
:::
|
||||
|
||||
## Config
|
||||
|
@ -138,31 +138,30 @@ Writing to os.File is goroutine-safe, but if you are using a custom Output that
|
|||
|
||||
| Property | Type | Description | Default |
|
||||
|:-----------------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------|
|
||||
| Next | `func(fiber.Ctx) bool` | Next defines a function to skip this middleware when returned true. | `nil` |
|
||||
| Done | `func(fiber.Ctx, []byte)` | Done is a function that is called after the log string for a request is written to Output, and pass the log string as parameter. | `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` |
|
||||
| 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` |
|
||||
| Format | `string` | Format defines the logging tags. | `[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n` |
|
||||
| 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"` |
|
||||
| TimeInterval | `time.Duration` | TimeInterval is the delay before the timestamp is updated. | `500 * time.Millisecond` |
|
||||
| Output | `io.Writer` | Output 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` |
|
||||
| DisableColors | `bool` | DisableColors defines if the logs output should be colorized. | `false` |
|
||||
| enableColors | `bool` | Internal field for enabling colors in the log output. (This is not a user-configurable field) | - |
|
||||
| enableLatency | `bool` | Internal field for enabling latency measurement in logs. (This is not a user-configurable field) | - |
|
||||
| timeZoneLocation | `*time.Location` | Internal field for the time zone location. (This is not a user-configurable field) | - |
|
||||
|
||||
## Default Config
|
||||
|
||||
```go
|
||||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Skip nil,
|
||||
Done: nil,
|
||||
Format: "[${time}] ${ip} ${status} - ${latency} ${method} ${path} ${error}\n",
|
||||
TimeFormat: "15:04:05",
|
||||
TimeZone: "Local",
|
||||
TimeInterval: 500 * time.Millisecond,
|
||||
Output: os.Stdout,
|
||||
Stream: os.Stdout,
|
||||
DisableColors: false,
|
||||
LoggerFunc: defaultLoggerInstance,
|
||||
}
|
||||
|
|
|
@ -912,6 +912,31 @@ func main() {
|
|||
|
||||
</details>
|
||||
|
||||
The `Skip` is a function to determine if logging is skipped or written to `Stream`.
|
||||
|
||||
<details>
|
||||
<summary>Example Usage</summary>
|
||||
|
||||
```go
|
||||
app.Use(logger.New(logger.Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
// Skip logging HTTP 200 requests
|
||||
return c.Response().StatusCode() == fiber.StatusOK
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
```go
|
||||
app.Use(logger.New(logger.Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
// Only log errors, similar to an error.log
|
||||
return c.Response().StatusCode() < 400
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Filesystem
|
||||
|
||||
We've decided to remove filesystem middleware to clear up the confusion between static and filesystem middleware.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package keyauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -59,7 +60,10 @@ func New(config ...Config) fiber.Handler {
|
|||
valid, err := cfg.Validator(c, key)
|
||||
|
||||
if err == nil && valid {
|
||||
// Store in both Locals and Context
|
||||
c.Locals(tokenKey, key)
|
||||
ctx := context.WithValue(c.Context(), tokenKey, key)
|
||||
c.SetContext(ctx)
|
||||
return cfg.SuccessHandler(c)
|
||||
}
|
||||
return cfg.ErrorHandler(c, err)
|
||||
|
@ -68,12 +72,20 @@ func New(config ...Config) fiber.Handler {
|
|||
|
||||
// TokenFromContext returns the bearer token from the request context.
|
||||
// returns an empty string if the token does not exist
|
||||
func TokenFromContext(c fiber.Ctx) string {
|
||||
token, ok := c.Locals(tokenKey).(string)
|
||||
if !ok {
|
||||
return ""
|
||||
func TokenFromContext(c any) string {
|
||||
switch ctx := c.(type) {
|
||||
case context.Context:
|
||||
if token, ok := ctx.Value(tokenKey).(string); ok {
|
||||
return token
|
||||
}
|
||||
case fiber.Ctx:
|
||||
if token, ok := ctx.Locals(tokenKey).(string); ok {
|
||||
return token
|
||||
}
|
||||
default:
|
||||
panic("unsupported context type, expected fiber.Ctx or context.Context")
|
||||
}
|
||||
return token
|
||||
return ""
|
||||
}
|
||||
|
||||
// MultipleKeySourceLookup creates a CustomKeyLookup function that checks multiple sources until one is found
|
||||
|
|
|
@ -503,33 +503,67 @@ func Test_TokenFromContext_None(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_TokenFromContext(t *testing.T) {
|
||||
app := fiber.New()
|
||||
// Wire up keyauth middleware to set TokenFromContext now
|
||||
app.Use(New(Config{
|
||||
KeyLookup: "header:Authorization",
|
||||
AuthScheme: "Basic",
|
||||
Validator: func(_ fiber.Ctx, key string) (bool, error) {
|
||||
if key == CorrectKey {
|
||||
return true, nil
|
||||
}
|
||||
return false, ErrMissingOrMalformedAPIKey
|
||||
},
|
||||
}))
|
||||
// Define a test handler that checks TokenFromContext
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString(TokenFromContext(c))
|
||||
// Test that TokenFromContext returns the correct token
|
||||
t.Run("fiber.Ctx", func(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
KeyLookup: "header:Authorization",
|
||||
AuthScheme: "Basic",
|
||||
Validator: func(_ fiber.Ctx, key string) (bool, error) {
|
||||
if key == CorrectKey {
|
||||
return true, nil
|
||||
}
|
||||
return false, ErrMissingOrMalformedAPIKey
|
||||
},
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString(TokenFromContext(c))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Add("Authorization", "Basic "+CorrectKey)
|
||||
res, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CorrectKey, string(body))
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Add("Authorization", "Basic "+CorrectKey)
|
||||
// Send
|
||||
res, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
t.Run("context.Context", func(t *testing.T) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
KeyLookup: "header:Authorization",
|
||||
AuthScheme: "Basic",
|
||||
Validator: func(_ fiber.Ctx, key string) (bool, error) {
|
||||
if key == CorrectKey {
|
||||
return true, nil
|
||||
}
|
||||
return false, ErrMissingOrMalformedAPIKey
|
||||
},
|
||||
}))
|
||||
// Verify that TokenFromContext works with context.Context
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
ctx := c.Context()
|
||||
token := TokenFromContext(ctx)
|
||||
return c.SendString(token)
|
||||
})
|
||||
|
||||
// Read the response body into a string
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CorrectKey, string(body))
|
||||
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
|
||||
req.Header.Add("Authorization", "Basic "+CorrectKey)
|
||||
res, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CorrectKey, string(body))
|
||||
})
|
||||
|
||||
t.Run("invalid context type", func(t *testing.T) {
|
||||
require.Panics(t, func() {
|
||||
_ = TokenFromContext("invalid")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AuthSchemeToken(t *testing.T) {
|
||||
|
|
|
@ -10,16 +10,21 @@ import (
|
|||
|
||||
// Config defines the config for middleware.
|
||||
type Config struct {
|
||||
// Output is a writer where logs are written
|
||||
// Stream is a writer where logs are written
|
||||
//
|
||||
// Default: os.Stdout
|
||||
Output io.Writer
|
||||
Stream io.Writer
|
||||
|
||||
// Next defines a function to skip this middleware when returned true.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Next func(c fiber.Ctx) bool
|
||||
|
||||
// Skip is a function to determine if logging is skipped or written to Stream.
|
||||
//
|
||||
// Optional. Default: nil
|
||||
Skip func(c fiber.Ctx) bool
|
||||
|
||||
// Done is a function that is called after the log string for a request is written to Output,
|
||||
// and pass the log string as parameter.
|
||||
//
|
||||
|
@ -98,12 +103,13 @@ type LogFunc func(output Buffer, c fiber.Ctx, data *Data, extraParam string) (in
|
|||
// ConfigDefault is the default config
|
||||
var ConfigDefault = Config{
|
||||
Next: nil,
|
||||
Skip: nil,
|
||||
Done: nil,
|
||||
Format: defaultFormat,
|
||||
TimeFormat: "15:04:05",
|
||||
TimeZone: "Local",
|
||||
TimeInterval: 500 * time.Millisecond,
|
||||
Output: os.Stdout,
|
||||
Stream: os.Stdout,
|
||||
BeforeHandlerFunc: beforeHandlerFunc,
|
||||
LoggerFunc: defaultLoggerInstance,
|
||||
enableColors: true,
|
||||
|
@ -126,6 +132,9 @@ func configDefault(config ...Config) Config {
|
|||
if cfg.Next == nil {
|
||||
cfg.Next = ConfigDefault.Next
|
||||
}
|
||||
if cfg.Skip == nil {
|
||||
cfg.Skip = ConfigDefault.Skip
|
||||
}
|
||||
if cfg.Done == nil {
|
||||
cfg.Done = ConfigDefault.Done
|
||||
}
|
||||
|
@ -141,8 +150,8 @@ func configDefault(config ...Config) Config {
|
|||
if int(cfg.TimeInterval) <= 0 {
|
||||
cfg.TimeInterval = ConfigDefault.TimeInterval
|
||||
}
|
||||
if cfg.Output == nil {
|
||||
cfg.Output = ConfigDefault.Output
|
||||
if cfg.Stream == nil {
|
||||
cfg.Stream = ConfigDefault.Stream
|
||||
}
|
||||
|
||||
if cfg.BeforeHandlerFunc == nil {
|
||||
|
@ -154,7 +163,7 @@ func configDefault(config ...Config) Config {
|
|||
}
|
||||
|
||||
// Enable colors if no custom format or output is given
|
||||
if !cfg.DisableColors && cfg.Output == ConfigDefault.Output {
|
||||
if !cfg.DisableColors && cfg.Stream == ConfigDefault.Stream {
|
||||
cfg.enableColors = true
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,12 @@ import (
|
|||
|
||||
// default logger for fiber
|
||||
func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
||||
// Check if Skip is defined and call it.
|
||||
// Now, if Skip(c) == true, we SKIP logging:
|
||||
if cfg.Skip != nil && cfg.Skip(c) {
|
||||
return nil // Skip logging if Skip returns true
|
||||
}
|
||||
|
||||
// Alias colors
|
||||
colors := c.App().Config().ColorScheme
|
||||
|
||||
|
@ -91,7 +97,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
|||
}
|
||||
|
||||
// Write buffer to output
|
||||
writeLog(cfg.Output, buf.Bytes())
|
||||
writeLog(cfg.Stream, buf.Bytes())
|
||||
|
||||
if cfg.Done != nil {
|
||||
cfg.Done(c, buf.Bytes())
|
||||
|
@ -125,7 +131,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
|||
buf.WriteString(err.Error())
|
||||
}
|
||||
|
||||
writeLog(cfg.Output, buf.Bytes())
|
||||
writeLog(cfg.Stream, buf.Bytes())
|
||||
|
||||
if cfg.Done != nil {
|
||||
cfg.Done(c, buf.Bytes())
|
||||
|
@ -141,9 +147,9 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
|
|||
func beforeHandlerFunc(cfg Config) {
|
||||
// If colors are enabled, check terminal compatibility
|
||||
if cfg.enableColors {
|
||||
cfg.Output = colorable.NewColorableStdout()
|
||||
cfg.Stream = colorable.NewColorableStdout()
|
||||
if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
|
||||
cfg.Output = colorable.NewNonColorable(os.Stdout)
|
||||
cfg.Stream = colorable.NewNonColorable(os.Stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func Test_Logger(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${error}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(_ fiber.Ctx) error {
|
||||
|
@ -94,7 +94,7 @@ func Test_Logger_locals(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -171,6 +171,147 @@ func Test_Logger_Done(t *testing.T) {
|
|||
require.Positive(t, buf.Len(), 0)
|
||||
}
|
||||
|
||||
// Test_Logger_Filter tests the Filter functionality of the logger middleware.
|
||||
// It verifies that logs are written or skipped based on the filter condition.
|
||||
func Test_Logger_Filter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Test Not Found", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Return true to skip logging for all requests != 404
|
||||
app.Use(New(Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
return c.Response().StatusCode() != fiber.StatusNotFound
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/nonexistent", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusNotFound, resp.StatusCode)
|
||||
|
||||
// Expect logs for the 404 request
|
||||
require.Contains(t, logOutput.String(), "404")
|
||||
})
|
||||
|
||||
t.Run("Test OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Return true to skip logging for all requests == 200
|
||||
app.Use(New(Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
return c.Response().StatusCode() == fiber.StatusOK
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
})
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
// We skip logging for status == 200, so "200" should not appear
|
||||
require.NotContains(t, logOutput.String(), "200")
|
||||
})
|
||||
|
||||
t.Run("Always Skip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Filter always returns true => skip all logs
|
||||
app.Use(New(Config{
|
||||
Skip: func(_ fiber.Ctx) bool {
|
||||
return true // always skip
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
app.Get("/something", func(c fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusTeapot).SendString("I'm a teapot")
|
||||
})
|
||||
|
||||
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/something", nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect NO logs
|
||||
require.Empty(t, logOutput.String())
|
||||
})
|
||||
|
||||
t.Run("Never Skip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Filter always returns false => never skip logs
|
||||
app.Use(New(Config{
|
||||
Skip: func(_ fiber.Ctx) bool {
|
||||
return false // never skip
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
app.Get("/always", func(c fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusTeapot).SendString("Teapot again")
|
||||
})
|
||||
|
||||
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/always", nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect some logging - check any substring
|
||||
require.Contains(t, logOutput.String(), strconv.Itoa(fiber.StatusTeapot))
|
||||
})
|
||||
|
||||
t.Run("Skip /healthz", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
|
||||
logOutput := bytes.Buffer{}
|
||||
|
||||
// Filter returns true (skip logs) if the request path is /healthz
|
||||
app.Use(New(Config{
|
||||
Skip: func(c fiber.Ctx) bool {
|
||||
return c.Path() == "/healthz"
|
||||
},
|
||||
Stream: &logOutput,
|
||||
}))
|
||||
|
||||
// Normal route
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello World!")
|
||||
})
|
||||
// Health route
|
||||
app.Get("/healthz", func(c fiber.Ctx) error {
|
||||
return c.SendString("OK")
|
||||
})
|
||||
|
||||
// Request to "/" -> should be logged
|
||||
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, logOutput.String(), "200")
|
||||
|
||||
// Reset output buffer
|
||||
logOutput.Reset()
|
||||
|
||||
// Request to "/healthz" -> should be skipped
|
||||
_, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/healthz", nil))
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, logOutput.String())
|
||||
})
|
||||
}
|
||||
|
||||
// go test -run Test_Logger_ErrorTimeZone
|
||||
func Test_Logger_ErrorTimeZone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
@ -234,7 +375,7 @@ func Test_Logger_LoggerToWriter(t *testing.T) {
|
|||
|
||||
app.Use("/"+level, New(Config{
|
||||
Format: "${error}",
|
||||
Output: LoggerToWriter(logger, tc.
|
||||
Stream: LoggerToWriter(logger, tc.
|
||||
level),
|
||||
}))
|
||||
|
||||
|
@ -276,7 +417,7 @@ func Test_Logger_ErrorOutput_WithoutColor(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
Stream: o,
|
||||
DisableColors: true,
|
||||
}))
|
||||
|
||||
|
@ -293,7 +434,7 @@ func Test_Logger_ErrorOutput(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
Stream: o,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
|
@ -312,7 +453,7 @@ func Test_Logger_All(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${pid}${reqHeaders}${referer}${scheme}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${reqHeader:test}${query:test}${form:test}${cookie:test}${non}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
// Alias colors
|
||||
|
@ -358,7 +499,7 @@ func Test_Logger_WithLatency(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
logger := New(Config{
|
||||
Output: buff,
|
||||
Stream: buff,
|
||||
Format: "${latency}",
|
||||
})
|
||||
app.Use(logger)
|
||||
|
@ -403,7 +544,7 @@ func Test_Logger_WithLatency_DefaultFormat(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
logger := New(Config{
|
||||
Output: buff,
|
||||
Stream: buff,
|
||||
})
|
||||
app.Use(logger)
|
||||
|
||||
|
@ -453,7 +594,7 @@ func Test_Query_Params(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${queryParams}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?foo=bar&baz=moz", nil))
|
||||
|
@ -474,7 +615,7 @@ func Test_Response_Body(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${resBody}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -508,7 +649,7 @@ func Test_Request_Body(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Post("/", func(c fiber.Ctx) error {
|
||||
|
@ -536,7 +677,7 @@ func Test_Logger_AppendUint(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -611,7 +752,7 @@ func Test_Response_Header(t *testing.T) {
|
|||
}))
|
||||
app.Use(New(Config{
|
||||
Format: "${respHeader:X-Request-ID}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
|
@ -634,7 +775,7 @@ func Test_Req_Header(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${reqHeader:test}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
|
@ -658,7 +799,7 @@ func Test_ReqHeader_Header(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${reqHeader:test}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
|
@ -689,7 +830,7 @@ func Test_CustomTags(t *testing.T) {
|
|||
return output.WriteString(customTag)
|
||||
},
|
||||
},
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello fiber!")
|
||||
|
@ -713,7 +854,7 @@ func Test_Logger_ByteSent_Streaming(t *testing.T) {
|
|||
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: buf,
|
||||
Stream: buf,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -759,7 +900,7 @@ func Test_Logger_EnableColors(t *testing.T) {
|
|||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Output: o,
|
||||
Stream: o,
|
||||
}))
|
||||
|
||||
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
|
||||
|
@ -782,7 +923,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
|
@ -794,7 +935,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
b.Run("DefaultFormat", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -805,7 +946,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
DisableColors: true,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -819,7 +960,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
logger := fiberlog.DefaultLogger()
|
||||
logger.SetOutput(io.Discard)
|
||||
app.Use(New(Config{
|
||||
Output: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
Stream: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -831,7 +972,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
|
@ -844,7 +985,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Locals("demo", "johndoe")
|
||||
|
@ -857,7 +998,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/int", func(c fiber.Ctx) error {
|
||||
c.Locals("demo", 55)
|
||||
|
@ -874,7 +1015,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
io.Discard.Write(logString) //nolint:errcheck // ignore error
|
||||
}
|
||||
},
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/logging", func(ctx fiber.Ctx) error {
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
|
@ -886,7 +1027,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${pid}${reqHeaders}${referer}${scheme}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${reqHeader:test}${query:test}${form:test}${cookie:test}${non}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -898,7 +1039,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("Connection", "keep-alive")
|
||||
|
@ -927,7 +1068,7 @@ func Benchmark_Logger(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${resBody}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Sample response body")
|
||||
|
@ -950,7 +1091,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
|
@ -962,7 +1103,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
b.Run("DefaultFormat", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -975,7 +1116,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
logger := fiberlog.DefaultLogger()
|
||||
logger.SetOutput(io.Discard)
|
||||
app.Use(New(Config{
|
||||
Output: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
Stream: LoggerToWriter(logger, fiberlog.LevelDebug),
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -986,7 +1127,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
b.Run("DefaultFormatDisableColors", func(bb *testing.B) {
|
||||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
DisableColors: true,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
|
@ -999,7 +1140,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status} ${reqHeader:test}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("test", "test")
|
||||
|
@ -1012,7 +1153,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Locals("demo", "johndoe")
|
||||
|
@ -1025,7 +1166,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${locals:demo}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/int", func(c fiber.Ctx) error {
|
||||
c.Locals("demo", 55)
|
||||
|
@ -1042,7 +1183,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
io.Discard.Write(logString) //nolint:errcheck // ignore error
|
||||
}
|
||||
},
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/logging", func(ctx fiber.Ctx) error {
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
|
@ -1054,7 +1195,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${pid}${reqHeaders}${referer}${scheme}${protocol}${ip}${ips}${host}${url}${ua}${body}${route}${black}${red}${green}${yellow}${blue}${magenta}${cyan}${white}${reset}${error}${reqHeader:test}${query:test}${form:test}${cookie:test}${non}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
|
@ -1066,7 +1207,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${bytesReceived} ${bytesSent} ${status}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Set("Connection", "keep-alive")
|
||||
|
@ -1095,7 +1236,7 @@ func Benchmark_Logger_Parallel(b *testing.B) {
|
|||
app := fiber.New()
|
||||
app.Use(New(Config{
|
||||
Format: "${resBody}",
|
||||
Output: io.Discard,
|
||||
Stream: io.Discard,
|
||||
}))
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("Sample response body")
|
||||
|
|
55
path.go
55
path.go
|
@ -7,9 +7,11 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
|
@ -25,6 +27,12 @@ type routeParser struct {
|
|||
plusCount int // number of plus parameters, used internally to give the plus parameter its number
|
||||
}
|
||||
|
||||
var routerParserPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &routeParser{}
|
||||
},
|
||||
}
|
||||
|
||||
// routeSegment holds the segment metadata
|
||||
type routeSegment struct {
|
||||
// const information
|
||||
|
@ -162,7 +170,10 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
|
|||
patternPretty = utils.TrimRight(patternPretty, '/')
|
||||
}
|
||||
|
||||
parser := parseRoute(string(patternPretty))
|
||||
parser, _ := routerParserPool.Get().(*routeParser) //nolint:errcheck // only contains routeParser
|
||||
parser.reset()
|
||||
parser.parseRoute(string(patternPretty))
|
||||
defer routerParserPool.Put(parser)
|
||||
|
||||
if string(patternPretty) == "/" && path == "/" {
|
||||
return true
|
||||
|
@ -183,10 +194,16 @@ func RoutePatternMatch(path, pattern string, cfg ...Config) bool {
|
|||
return string(patternPretty) == path
|
||||
}
|
||||
|
||||
func (parser *routeParser) reset() {
|
||||
parser.segs = parser.segs[:0]
|
||||
parser.params = parser.params[:0]
|
||||
parser.wildCardCount = 0
|
||||
parser.plusCount = 0
|
||||
}
|
||||
|
||||
// parseRoute analyzes the route and divides it into segments for constant areas and parameters,
|
||||
// this information is needed later when assigning the requests to the declared routes
|
||||
func parseRoute(pattern string, customConstraints ...CustomConstraint) routeParser {
|
||||
parser := routeParser{}
|
||||
func (parser *routeParser) parseRoute(pattern string, customConstraints ...CustomConstraint) {
|
||||
var n int
|
||||
var seg *routeSegment
|
||||
for len(pattern) > 0 {
|
||||
|
@ -206,7 +223,13 @@ func parseRoute(pattern string, customConstraints ...CustomConstraint) routePars
|
|||
parser.segs[len(parser.segs)-1].IsLast = true
|
||||
}
|
||||
parser.segs = addParameterMetaInfo(parser.segs)
|
||||
}
|
||||
|
||||
// parseRoute analyzes the route and divides it into segments for constant areas and parameters,
|
||||
// this information is needed later when assigning the requests to the declared routes
|
||||
func parseRoute(pattern string, customConstraints ...CustomConstraint) routeParser {
|
||||
parser := routeParser{}
|
||||
parser.parseRoute(pattern, customConstraints...)
|
||||
return parser
|
||||
}
|
||||
|
||||
|
@ -289,7 +312,7 @@ func (*routeParser) analyseConstantPart(pattern string, nextParamPosition int) (
|
|||
}
|
||||
|
||||
// analyseParameterPart find the parameter end and create the route segment
|
||||
func (routeParser *routeParser) analyseParameterPart(pattern string, customConstraints ...CustomConstraint) (int, *routeSegment) {
|
||||
func (parser *routeParser) analyseParameterPart(pattern string, customConstraints ...CustomConstraint) (int, *routeSegment) {
|
||||
isWildCard := pattern[0] == wildcardParam
|
||||
isPlusParam := pattern[0] == plusParam
|
||||
|
||||
|
@ -308,7 +331,7 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
|||
parameterEndPosition = 0
|
||||
case parameterEndPosition == -1:
|
||||
parameterEndPosition = len(pattern) - 1
|
||||
case !isInCharset(pattern[parameterEndPosition+1], parameterDelimiterChars):
|
||||
case bytes.IndexByte(parameterDelimiterChars, pattern[parameterEndPosition+1]) == -1:
|
||||
parameterEndPosition++
|
||||
}
|
||||
|
||||
|
@ -376,11 +399,11 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
|||
|
||||
// add access iterator to wildcard and plus
|
||||
if isWildCard {
|
||||
routeParser.wildCardCount++
|
||||
paramName += strconv.Itoa(routeParser.wildCardCount)
|
||||
parser.wildCardCount++
|
||||
paramName += strconv.Itoa(parser.wildCardCount)
|
||||
} else if isPlusParam {
|
||||
routeParser.plusCount++
|
||||
paramName += strconv.Itoa(routeParser.plusCount)
|
||||
parser.plusCount++
|
||||
paramName += strconv.Itoa(parser.plusCount)
|
||||
}
|
||||
|
||||
segment := &routeSegment{
|
||||
|
@ -397,16 +420,6 @@ func (routeParser *routeParser) analyseParameterPart(pattern string, customConst
|
|||
return n, segment
|
||||
}
|
||||
|
||||
// isInCharset check is the given character in the charset list
|
||||
func isInCharset(searchChar byte, charset []byte) bool {
|
||||
for _, char := range charset {
|
||||
if char == searchChar {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findNextCharsetPosition search the next char position from the charset
|
||||
func findNextCharsetPosition(search string, charset []byte) int {
|
||||
nextPosition := -1
|
||||
|
@ -474,9 +487,9 @@ func splitNonEscaped(s, sep string) []string {
|
|||
}
|
||||
|
||||
// getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
|
||||
func (routeParser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { //nolint: revive // Accepting a bool param is fine here
|
||||
func (parser *routeParser) getMatch(detectionPath, path string, params *[maxParams]string, partialCheck bool) bool { //nolint: revive // Accepting a bool param is fine here
|
||||
var i, paramsIterator, partLen int
|
||||
for _, segment := range routeParser.segs {
|
||||
for _, segment := range parser.segs {
|
||||
partLen = len(detectionPath)
|
||||
// check const segment
|
||||
if !segment.IsParam {
|
||||
|
|
Loading…
Reference in New Issue