Adding upstream version 2.52.6.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
a960158181
commit
6d002e9543
441 changed files with 95392 additions and 0 deletions
8
docs/guide/_category_.json
Normal file
8
docs/guide/_category_.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"label": "Guide",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Guides for Fiber."
|
||||
}
|
||||
}
|
128
docs/guide/error-handling.md
Normal file
128
docs/guide/error-handling.md
Normal file
|
@ -0,0 +1,128 @@
|
|||
---
|
||||
id: error-handling
|
||||
title: 🐛 Error Handling
|
||||
description: >-
|
||||
Fiber supports centralized error handling by returning an error to the handler
|
||||
which allows you to log errors to external services or send a customized HTTP
|
||||
response to the client.
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## Catching Errors
|
||||
|
||||
It’s essential to ensure that Fiber catches all errors that occur while running route handlers and middleware. You must return them to the handler function, where Fiber will catch and process them.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="example" label="Example">
|
||||
|
||||
```go
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
// Pass error to Fiber
|
||||
return c.SendFile("file-does-not-exist")
|
||||
})
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Fiber does not handle [panics](https://go.dev/blog/defer-panic-and-recover) by default. To recover from a panic thrown by any handler in the stack, you need to include the `Recover` middleware below:
|
||||
|
||||
```go title="Example"
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(recover.New())
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
panic("This panic is caught by fiber")
|
||||
})
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
You could use Fiber's custom error struct to pass an additional `status code` using `fiber.NewError()`. It's optional to pass a message; if this is left empty, it will default to the status code message \(`404` equals `Not Found`\).
|
||||
|
||||
```go title="Example"
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
// 503 Service Unavailable
|
||||
return fiber.ErrServiceUnavailable
|
||||
|
||||
// 503 On vacation!
|
||||
return fiber.NewError(fiber.StatusServiceUnavailable, "On vacation!")
|
||||
})
|
||||
```
|
||||
|
||||
## Default Error Handler
|
||||
|
||||
Fiber provides an error handler by default. For a standard error, the response is sent as **500 Internal Server Error**. If the error is of type [fiber.Error](https://godoc.org/github.com/gofiber/fiber#Error), the response is sent with the provided status code and message.
|
||||
|
||||
```go title="Example"
|
||||
// Default error handler
|
||||
var DefaultErrorHandler = func(c *fiber.Ctx, err error) error {
|
||||
// Status code defaults to 500
|
||||
code := fiber.StatusInternalServerError
|
||||
|
||||
// Retrieve the custom status code if it's a *fiber.Error
|
||||
var e *fiber.Error
|
||||
if errors.As(err, &e) {
|
||||
code = e.Code
|
||||
}
|
||||
|
||||
// Set Content-Type: text/plain; charset=utf-8
|
||||
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
|
||||
|
||||
// Return status code with error message
|
||||
return c.Status(code).SendString(err.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).
|
||||
|
||||
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.
|
||||
|
||||
The following example shows how to display error pages for different types of errors.
|
||||
|
||||
```go title="Example"
|
||||
// Create a new fiber instance with custom config
|
||||
app := fiber.New(fiber.Config{
|
||||
// Override default error handler
|
||||
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
|
||||
// Status code defaults to 500
|
||||
code := fiber.StatusInternalServerError
|
||||
|
||||
// Retrieve the custom status code if it's a *fiber.Error
|
||||
var e *fiber.Error
|
||||
if errors.As(err, &e) {
|
||||
code = e.Code
|
||||
}
|
||||
|
||||
// Send custom error page
|
||||
err = ctx.Status(code).SendFile(fmt.Sprintf("./%d.html", code))
|
||||
if err != nil {
|
||||
// In case the SendFile fails
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
// Return from handler
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
> Special thanks to the [Echo](https://echo.labstack.com/) & [Express](https://expressjs.com/) framework for inspiration regarding error handling.
|
36
docs/guide/faster-fiber.md
Normal file
36
docs/guide/faster-fiber.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
id: faster-fiber
|
||||
title: ⚡ Make Fiber Faster
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
## Custom JSON Encoder/Decoder
|
||||
Since Fiber v2.32.0, we use **encoding/json** as default json library due to stability and producibility. However, the standard library is a bit slow compared to 3rd party libraries. If you're not happy with the performance of **encoding/json**, we recommend you to use these libraries:
|
||||
- [goccy/go-json](https://github.com/goccy/go-json)
|
||||
- [bytedance/sonic](https://github.com/bytedance/sonic)
|
||||
- [segmentio/encoding](https://github.com/segmentio/encoding)
|
||||
- [mailru/easyjson](https://github.com/mailru/easyjson)
|
||||
- [minio/simdjson-go](https://github.com/minio/simdjson-go)
|
||||
- [wI2L/jettison](https://github.com/wI2L/jettison)
|
||||
|
||||
```go title="Example"
|
||||
package main
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
import "github.com/goccy/go-json"
|
||||
|
||||
func main() {
|
||||
app := fiber.New(fiber.Config{
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
})
|
||||
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
### 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)
|
79
docs/guide/grouping.md
Normal file
79
docs/guide/grouping.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
id: grouping
|
||||
title: 🎭 Grouping
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
:::info
|
||||
In general, the Group functionality in Fiber behaves similarly to ExpressJS. Groups are declared virtually and all routes declared within the group are flattened into a single list with a prefix, which is then checked by the framework in the order it was declared. This means that the behavior of Group in Fiber is identical to that of ExpressJS.
|
||||
:::
|
||||
|
||||
## Paths
|
||||
|
||||
Like **Routing**, groups can also have paths that belong to a cluster.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
api := app.Group("/api", middleware) // /api
|
||||
|
||||
v1 := api.Group("/v1", middleware) // /api/v1
|
||||
v1.Get("/list", handler) // /api/v1/list
|
||||
v1.Get("/user", handler) // /api/v1/user
|
||||
|
||||
v2 := api.Group("/v2", middleware) // /api/v2
|
||||
v2.Get("/list", handler) // /api/v2/list
|
||||
v2.Get("/user", handler) // /api/v2/user
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
A **Group** of paths can have an optional handler.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
api := app.Group("/api") // /api
|
||||
|
||||
v1 := api.Group("/v1") // /api/v1
|
||||
v1.Get("/list", handler) // /api/v1/list
|
||||
v1.Get("/user", handler) // /api/v1/user
|
||||
|
||||
v2 := api.Group("/v2") // /api/v2
|
||||
v2.Get("/list", handler) // /api/v2/list
|
||||
v2.Get("/user", handler) // /api/v2/user
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
:::caution
|
||||
Running **/api**, **/v1** or **/v2** will result in **404** error, make sure you have the errors set.
|
||||
:::
|
||||
|
||||
## Group Handlers
|
||||
|
||||
Group handlers can also be used as a routing path but they must have **Next** added to them so that the flow can continue.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
handler := func(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
api := app.Group("/api") // /api
|
||||
|
||||
v1 := api.Group("/v1", func(c *fiber.Ctx) error { // middleware for /api/v1
|
||||
c.Set("Version", "v1")
|
||||
return c.Next()
|
||||
})
|
||||
v1.Get("/list", handler) // /api/v1/list
|
||||
v1.Get("/user", handler) // /api/v1/user
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
218
docs/guide/hooks.md
Normal file
218
docs/guide/hooks.md
Normal file
|
@ -0,0 +1,218 @@
|
|||
---
|
||||
id: hooks
|
||||
title: 🎣 Hooks
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
With Fiber v2.30.0, you can execute custom user functions when to run some methods. Here is a list of this hooks:
|
||||
- [OnRoute](#onroute)
|
||||
- [OnName](#onname)
|
||||
- [OnGroup](#ongroup)
|
||||
- [OnGroupName](#ongroupname)
|
||||
- [OnListen](#onlisten)
|
||||
- [OnFork](#onfork)
|
||||
- [OnShutdown](#onshutdown)
|
||||
- [OnMount](#onmount)
|
||||
|
||||
## Constants
|
||||
```go
|
||||
// Handlers define a function to create hooks for Fiber.
|
||||
type OnRouteHandler = func(Route) error
|
||||
type OnNameHandler = OnRouteHandler
|
||||
type OnGroupHandler = func(Group) error
|
||||
type OnGroupNameHandler = OnGroupHandler
|
||||
type OnListenHandler = func(ListenData) error
|
||||
type OnForkHandler = func(int) error
|
||||
type OnShutdownHandler = func() error
|
||||
type OnMountHandler = func(*App) error
|
||||
```
|
||||
|
||||
## OnRoute
|
||||
|
||||
OnRoute is a hook to execute user functions on each route registeration. Also you can get route properties by **route** parameter.
|
||||
|
||||
```go title="Signature"
|
||||
func (h *Hooks) OnRoute(handler ...OnRouteHandler)
|
||||
```
|
||||
|
||||
## OnName
|
||||
|
||||
OnName is a hook to execute user functions on each route naming. Also you can get route properties by **route** parameter.
|
||||
|
||||
:::caution
|
||||
OnName only works with naming routes, not groups.
|
||||
:::
|
||||
|
||||
```go title="Signature"
|
||||
func (h *Hooks) OnName(handler ...OnNameHandler)
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="onname-example" label="OnName Example">
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Route().Name)
|
||||
}).Name("index")
|
||||
|
||||
app.Hooks().OnName(func(r fiber.Route) error {
|
||||
fmt.Print("Name: " + r.Name + ", ")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
app.Hooks().OnName(func(r fiber.Route) error {
|
||||
fmt.Print("Method: " + r.Method + "\n")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
app.Get("/add/user", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Route().Name)
|
||||
}).Name("addUser")
|
||||
|
||||
app.Delete("/destroy/user", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Route().Name)
|
||||
}).Name("destroyUser")
|
||||
|
||||
app.Listen(":5000")
|
||||
}
|
||||
|
||||
// Results:
|
||||
// Name: addUser, Method: GET
|
||||
// Name: destroyUser, Method: DELETE
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## OnGroup
|
||||
|
||||
OnGroup is a hook to execute user functions on each group registeration. Also you can get group properties by **group** parameter.
|
||||
|
||||
```go title="Signature"
|
||||
func (h *Hooks) OnGroup(handler ...OnGroupHandler)
|
||||
```
|
||||
|
||||
## OnGroupName
|
||||
|
||||
OnGroupName is a hook to execute user functions on each group naming. Also you can get group properties by **group** parameter.
|
||||
|
||||
:::caution
|
||||
OnGroupName only works with naming groups, not routes.
|
||||
:::
|
||||
|
||||
```go title="Signature"
|
||||
func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler)
|
||||
```
|
||||
|
||||
## OnListen
|
||||
|
||||
OnListen is a hook to execute user functions on Listen, ListenTLS, Listener.
|
||||
|
||||
```go title="Signature"
|
||||
func (h *Hooks) OnListen(handler ...OnListenHandler)
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="onlisten-example" label="OnListen Example">
|
||||
|
||||
```go
|
||||
app := fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
})
|
||||
|
||||
app.Hooks().OnListen(func(listenData fiber.ListenData) error {
|
||||
if fiber.IsChild() {
|
||||
return nil
|
||||
}
|
||||
scheme := "http"
|
||||
if data.TLS {
|
||||
scheme = "https"
|
||||
}
|
||||
log.Println(scheme + "://" + listenData.Host + ":" + listenData.Port)
|
||||
return nil
|
||||
})
|
||||
|
||||
app.Listen(":5000")
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## OnFork
|
||||
|
||||
OnFork is a hook to execute user functions on Fork.
|
||||
|
||||
```go title="Signature"
|
||||
func (h *Hooks) OnFork(handler ...OnForkHandler)
|
||||
```
|
||||
|
||||
## OnShutdown
|
||||
|
||||
OnShutdown is a hook to execute user functions after Shutdown.
|
||||
|
||||
```go title="Signature"
|
||||
func (h *Hooks) OnShutdown(handler ...OnShutdownHandler)
|
||||
```
|
||||
|
||||
## OnMount
|
||||
|
||||
OnMount is a hook to execute user function after mounting process. The mount event is fired when sub-app is mounted on a parent app. The parent app is passed as a parameter. It works for app and group mounting.
|
||||
|
||||
```go title="Signature"
|
||||
func (h *Hooks) OnMount(handler ...OnMountHandler)
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="onmount-example" label="OnMount Example">
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := New()
|
||||
app.Get("/", testSimpleHandler).Name("x")
|
||||
|
||||
subApp := New()
|
||||
subApp.Get("/test", testSimpleHandler)
|
||||
|
||||
subApp.Hooks().OnMount(func(parent *fiber.App) error {
|
||||
fmt.Print("Mount path of parent app: "+parent.MountPath())
|
||||
// ...
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
app.Mount("/sub", subApp)
|
||||
}
|
||||
|
||||
// Result:
|
||||
// Mount path of parent app:
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
:::caution
|
||||
OnName/OnRoute/OnGroup/OnGroupName hooks are mount-sensitive. If you use one of these routes on sub app and you mount it; paths of routes and groups will start with mount prefix.
|
294
docs/guide/routing.md
Normal file
294
docs/guide/routing.md
Normal file
|
@ -0,0 +1,294 @@
|
|||
---
|
||||
id: routing
|
||||
title: 🔌 Routing
|
||||
description: >-
|
||||
Routing refers to how an application's endpoints (URIs) respond to client
|
||||
requests.
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import RoutingHandler from './../partials/routing/handler.md';
|
||||
|
||||
## Handlers
|
||||
|
||||
<RoutingHandler />
|
||||
|
||||
## Paths
|
||||
|
||||
Route paths, combined with a request method, define the endpoints at which requests can be made. Route paths can be **strings** or **string patterns**.
|
||||
|
||||
**Examples of route paths based on strings**
|
||||
|
||||
```go
|
||||
// This route path will match requests to the root route, "/":
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("root")
|
||||
})
|
||||
|
||||
// This route path will match requests to "/about":
|
||||
app.Get("/about", func(c *fiber.Ctx) error {
|
||||
return c.SendString("about")
|
||||
})
|
||||
|
||||
// This route path will match requests to "/random.txt":
|
||||
app.Get("/random.txt", func(c *fiber.Ctx) error {
|
||||
return c.SendString("random.txt")
|
||||
})
|
||||
```
|
||||
|
||||
As with the expressJs framework, the order of the route declaration plays a role.
|
||||
When a request is received, the routes are checked in the order in which they are declared.
|
||||
|
||||
:::info
|
||||
So please be careful to write routes with variable parameters after the routes that contain fixed parts, so that these variable parts do not match instead and unexpected behavior occurs.
|
||||
:::
|
||||
|
||||
## Parameters
|
||||
|
||||
Route parameters are dynamic elements in the route, which are **named** or **not named segments**. This segments that are used to capture the values specified at their position in the URL. The obtained values can be retrieved using the [Params](https://fiber.wiki/context#params) function, with the name of the route parameter specified in the path as their respective keys or for unnamed parameters the character\(\*, +\) and the counter of this.
|
||||
|
||||
The characters :, +, and \* are characters that introduce a parameter.
|
||||
|
||||
Greedy parameters are indicated by wildcard\(\*\) or plus\(+\) signs.
|
||||
|
||||
The routing also offers the possibility to use optional parameters, for the named parameters these are marked with a final "?", unlike the plus sign which is not optional, you can use the wildcard character for a parameter range which is optional and greedy.
|
||||
|
||||
**Example of define routes with route parameters**
|
||||
|
||||
```go
|
||||
// Parameters
|
||||
app.Get("/user/:name/books/:title", func(c *fiber.Ctx) error {
|
||||
fmt.Fprintf(c, "%s\n", c.Params("name"))
|
||||
fmt.Fprintf(c, "%s\n", c.Params("title"))
|
||||
return nil
|
||||
})
|
||||
// Plus - greedy - not optional
|
||||
app.Get("/user/+", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Params("+"))
|
||||
})
|
||||
|
||||
// Optional parameter
|
||||
app.Get("/user/:name?", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Params("name"))
|
||||
})
|
||||
|
||||
// Wildcard - greedy - optional
|
||||
app.Get("/user/*", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Params("*"))
|
||||
})
|
||||
|
||||
// This route path will match requests to "/v1/some/resource/name:customVerb", since the parameter character is escaped
|
||||
app.Get(`/v1/some/resource/name\:customVerb`, func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello, Community")
|
||||
})
|
||||
```
|
||||
|
||||
:::info
|
||||
Since the hyphen \(`-`\) and the dot \(`.`\) are interpreted literally, they can be used along with route parameters for useful purposes.
|
||||
:::
|
||||
|
||||
:::info
|
||||
All special parameter characters can also be escaped with `"\\"` and lose their value, so you can use them in the route if you want, like in the custom methods of the [google api design guide](https://cloud.google.com/apis/design/custom_methods). It's recommended to use backticks `` ` `` because in go's regex documentation, they always use backticks to make sure it is unambiguous and the escape character doesn't interfere with regex patterns in an unexpected way.
|
||||
:::
|
||||
|
||||
```go
|
||||
// http://localhost:3000/plantae/prunus.persica
|
||||
app.Get("/plantae/:genus.:species", func(c *fiber.Ctx) error {
|
||||
fmt.Fprintf(c, "%s.%s\n", c.Params("genus"), c.Params("species"))
|
||||
return nil // prunus.persica
|
||||
})
|
||||
```
|
||||
|
||||
```go
|
||||
// http://localhost:3000/flights/LAX-SFO
|
||||
app.Get("/flights/:from-:to", func(c *fiber.Ctx) error {
|
||||
fmt.Fprintf(c, "%s-%s\n", c.Params("from"), c.Params("to"))
|
||||
return nil // LAX-SFO
|
||||
})
|
||||
```
|
||||
|
||||
Our intelligent router recognizes that the introductory parameter characters should be part of the request route in this case and can process them as such.
|
||||
|
||||
```go
|
||||
// http://localhost:3000/shop/product/color:blue/size:xs
|
||||
app.Get("/shop/product/color::color/size::size", func(c *fiber.Ctx) error {
|
||||
fmt.Fprintf(c, "%s:%s\n", c.Params("color"), c.Params("size"))
|
||||
return nil // blue:xs
|
||||
})
|
||||
```
|
||||
|
||||
In addition, several parameters in a row and several unnamed parameter characters in the route, such as the wildcard or plus character, are possible, which greatly expands the possibilities of the router for the user.
|
||||
|
||||
```go
|
||||
// GET /@v1
|
||||
// Params: "sign" -> "@", "param" -> "v1"
|
||||
app.Get("/:sign:param", handler)
|
||||
|
||||
// GET /api-v1
|
||||
// Params: "name" -> "v1"
|
||||
app.Get("/api-:name", handler)
|
||||
|
||||
// GET /customer/v1/cart/proxy
|
||||
// Params: "*1" -> "customer/", "*2" -> "/cart"
|
||||
app.Get("/*v1*/proxy", handler)
|
||||
|
||||
// GET /v1/brand/4/shop/blue/xs
|
||||
// Params: "*1" -> "brand/4", "*2" -> "blue/xs"
|
||||
app.Get("/v1/*/shop/*", handler)
|
||||
```
|
||||
|
||||
We have adapted the routing strongly to the express routing, but currently without the possibility of the regular expressions, because they are quite slow. The possibilities can be tested with version 0.1.7 \(express 4\) in the online [Express route tester](http://forbeslindesay.github.io/express-route-tester/).
|
||||
|
||||
### Constraints
|
||||
Route constraints execute when a match has occurred to the incoming URL and the URL path is tokenized into route values by parameters. The feature was intorduced in `v2.37.0` and inspired by [.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-6.0#route-constraints).
|
||||
|
||||
:::caution
|
||||
Constraints aren't validation for parameters. If constraints aren't valid for a parameter value, Fiber returns **404 handler**.
|
||||
:::
|
||||
|
||||
| Constraint | Example | Example matches |
|
||||
| ----------------- | ------------------------------------ | ------------------------------------------------------------------------------------------- |
|
||||
| int | `:id<int\>` | 123456789, -123456789 |
|
||||
| bool | `:active<bool\>` | true,false |
|
||||
| guid | `:id<guid\>` | CD2C1638-1638-72D5-1638-DEADBEEF1638 |
|
||||
| float | `:weight<float\>` | 1.234, -1,001.01e8 |
|
||||
| minLen(value) | `:username<minLen(4)\>` | Test (must be at least 4 characters) |
|
||||
| maxLen(value) | `:filename<maxLen(8)\>` | MyFile (must be no more than 8 characters |
|
||||
| len(length) | `:filename<len(12)\>` | somefile.txt (exactly 12 characters) |
|
||||
| min(value) | `:age<min(18)\>` | 19 (Integer value must be at least 18) |
|
||||
| max(value) | `:age<max(120)\>` | 91 (Integer value must be no more than 120) |
|
||||
| range(min,max) | `:age<range(18,120)\>` | 91 (Integer value must be at least 18 but no more than 120) |
|
||||
| alpha | `:name<alpha\>` | Rick (String must consist of one or more alphabetical characters, a-z and case-insensitive) |
|
||||
| datetime | `:dob<datetime(2006\\\\-01\\\\-02)\>` | 2005-11-01 |
|
||||
| regex(expression) | `:date<regex(\\d{4}-\\d{2}-\\d{2})\>` | 2022-08-27 (Must match regular expression) |
|
||||
|
||||
**Examples**
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="single-constraint" label="Single Constraint">
|
||||
|
||||
```go
|
||||
app.Get("/:test<min(5)>", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Params("test"))
|
||||
})
|
||||
|
||||
// curl -X GET http://localhost:3000/12
|
||||
// 12
|
||||
|
||||
// curl -X GET http://localhost:3000/1
|
||||
// Cannot GET /1
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="multiple-constraints" label="Multiple Constraints">
|
||||
|
||||
You can use `;` for multiple constraints.
|
||||
```go
|
||||
app.Get("/:test<min(100);maxLen(5)>", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Params("test"))
|
||||
})
|
||||
|
||||
// curl -X GET http://localhost:3000/120000
|
||||
// Cannot GET /120000
|
||||
|
||||
// curl -X GET http://localhost:3000/1
|
||||
// Cannot GET /1
|
||||
|
||||
// curl -X GET http://localhost:3000/250
|
||||
// 250
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="regex-constraint" label="Regex Constraint">
|
||||
|
||||
Fiber precompiles regex query when to register routes. So there're no performance overhead for regex constraint.
|
||||
```go
|
||||
app.Get(`/:date<regex(\d{4}-\d{2}-\d{2})>`, func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Params("date"))
|
||||
})
|
||||
|
||||
// curl -X GET http://localhost:3000/125
|
||||
// Cannot GET /125
|
||||
|
||||
// curl -X GET http://localhost:3000/test
|
||||
// Cannot GET /test
|
||||
|
||||
// curl -X GET http://localhost:3000/2022-08-27
|
||||
// 2022-08-27
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::caution
|
||||
You should use `\\` before routing-specific characters when to use datetime constraint (`*`, `+`, `?`, `:`, `/`, `<`, `>`, `;`, `(`, `)`), to avoid wrong parsing.
|
||||
:::
|
||||
|
||||
**Optional Parameter Example**
|
||||
|
||||
You can impose constraints on optional parameters as well.
|
||||
|
||||
```go
|
||||
app.Get("/:test<int>?", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Params("test"))
|
||||
})
|
||||
// curl -X GET http://localhost:3000/42
|
||||
// 42
|
||||
// curl -X GET http://localhost:3000/
|
||||
//
|
||||
// curl -X GET http://localhost:3000/7.0
|
||||
// Cannot GET /7.0
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
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**
|
||||
|
||||
```go
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
// Set a custom header on all responses:
|
||||
c.Set("X-Custom-Header", "Hello, World")
|
||||
|
||||
// Go to next middleware:
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
```
|
||||
|
||||
`Use` method path is a **mount**, or **prefix** path, and limits middleware to only apply to any paths requested that begin with it.
|
||||
|
||||
### Constraints on Adding Routes Dynamically
|
||||
|
||||
:::caution
|
||||
Adding routes dynamically after the application has started is not supported due to design and performance considerations. Make sure to define all your routes before the application starts.
|
||||
:::
|
||||
|
||||
|
||||
## Grouping
|
||||
|
||||
If you have many endpoints, you can organize your routes using `Group`.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
|
||||
api := app.Group("/api", middleware) // /api
|
||||
|
||||
v1 := api.Group("/v1", middleware) // /api/v1
|
||||
v1.Get("/list", handler) // /api/v1/list
|
||||
v1.Get("/user", handler) // /api/v1/user
|
||||
|
||||
v2 := api.Group("/v2", middleware) // /api/v2
|
||||
v2.Get("/list", handler) // /api/v2/list
|
||||
v2.Get("/user", handler) // /api/v2/user
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
More information about this in our [Grouping Guide](./grouping.md)
|
267
docs/guide/templates.md
Normal file
267
docs/guide/templates.md
Normal file
|
@ -0,0 +1,267 @@
|
|||
---
|
||||
id: templates
|
||||
title: 📝 Templates
|
||||
description: Fiber supports server-side template engines.
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
Templates are a great tool to render dynamic content without using a separate frontend framework.
|
||||
|
||||
## Template Engines
|
||||
|
||||
Fiber allows you to provide a custom template engine at app initialization.
|
||||
|
||||
```go
|
||||
app := fiber.New(fiber.Config{
|
||||
// Pass in Views Template Engine
|
||||
Views: engine,
|
||||
|
||||
// Default global path to search for views (can be overriden when calling Render())
|
||||
ViewsLayout: "layouts/main",
|
||||
|
||||
// Enables/Disables access to `ctx.Locals()` entries in rendered views
|
||||
// (defaults to false)
|
||||
PassLocalsToViews: false,
|
||||
})
|
||||
```
|
||||
|
||||
### Supported Engines
|
||||
|
||||
The Fiber team maintains a [templates](https://docs.gofiber.io/template) package that provides wrappers for multiple template engines:
|
||||
|
||||
* [ace](https://docs.gofiber.io/template/ace/)
|
||||
* [amber](https://docs.gofiber.io/template/amber/)
|
||||
* [django](https://docs.gofiber.io/template/django/)
|
||||
* [handlebars](https://docs.gofiber.io/template/handlebars)
|
||||
* [html](https://docs.gofiber.io/template/html)
|
||||
* [jet](https://docs.gofiber.io/template/jet)
|
||||
* [mustache](https://docs.gofiber.io/template/mustache)
|
||||
* [pug](https://docs.gofiber.io/template/pug)
|
||||
* [slim](https://docs.gofiber.io/template/slim)
|
||||
|
||||
:::info
|
||||
Custom template engines can implement the `Views` interface to be supported in Fiber.
|
||||
:::
|
||||
|
||||
```go title="Views interface"
|
||||
type Views interface {
|
||||
// Fiber executes Load() on app initialization to load/parse the templates
|
||||
Load() error
|
||||
|
||||
// Outputs a template to the provided buffer using the provided template,
|
||||
// template name, and binded data
|
||||
Render(io.Writer, string, interface{}, ...string) error
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
The `Render` method is linked to the [**ctx.Render\(\)**](../api/ctx.md#render) function that accepts a template name and binding data.
|
||||
:::
|
||||
|
||||
## Rendering Templates
|
||||
|
||||
Once an engine is set up, a route handler can call the [**ctx.Render\(\)**](../api/ctx.md#render) function with a template name and binded data to send the rendered template.
|
||||
|
||||
```go title="Signature"
|
||||
func (c *Ctx) Render(name string, bind Map, layouts ...string) error
|
||||
```
|
||||
|
||||
:::info
|
||||
By default, [**ctx.Render\(\)**](../api/ctx.md#render) searches for the template name in the `ViewsLayout` path. To override this setting, provide the path(s) in the `layouts` argument.
|
||||
:::
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="example" label="Example">
|
||||
|
||||
```go
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.Render("index", fiber.Map{
|
||||
"Title": "Hello, World!",
|
||||
})
|
||||
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="index" label="layouts/index.html">
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>{{.Title}}</h1>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
:::caution
|
||||
If the Fiber config option `PassLocalsToViews` is enabled, then all locals set using `ctx.Locals(key, value)` will be passed to the template. It is important to avoid clashing keys when using this setting.
|
||||
:::
|
||||
|
||||
## Advanced Templating
|
||||
|
||||
### Custom Functions
|
||||
|
||||
Fiber supports adding custom functions to templates.
|
||||
|
||||
#### AddFunc
|
||||
|
||||
Adds a global function to all templates.
|
||||
|
||||
```go title="Signature"
|
||||
func (e *Engine) AddFunc(name string, fn interface{}) IEngineCore
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="add-func-example" label="AddFunc Example">
|
||||
|
||||
```go
|
||||
// Add `ToUpper` to engine
|
||||
engine := html.New("./views", ".html")
|
||||
engine.AddFunc("ToUpper", func(s string) string {
|
||||
return strings.ToUpper(s)
|
||||
}
|
||||
|
||||
// Initialize Fiber App
|
||||
app := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
})
|
||||
|
||||
app.Get("/", func (c *fiber.Ctx) error {
|
||||
return c.Render("index", fiber.Map{
|
||||
"Content": "hello, world!"
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="add-func-template" label="views/index.html">
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<p>This will be in {{ToUpper "all caps"}}:</p>
|
||||
<p>{{ToUpper .Content}}</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### AddFuncMap
|
||||
|
||||
Adds a Map of functions (keyed by name) to all templates.
|
||||
|
||||
```go title="Signature"
|
||||
func (e *Engine) AddFuncMap(m map[string]interface{}) IEngineCore
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="add-func-map-example" label="AddFuncMap Example">
|
||||
|
||||
```go
|
||||
// Add `ToUpper` to engine
|
||||
engine := html.New("./views", ".html")
|
||||
engine.AddFuncMap(map[string]interface{}{
|
||||
"ToUpper": func(s string) string {
|
||||
return strings.ToUpper(s)
|
||||
},
|
||||
})
|
||||
|
||||
// Initialize Fiber App
|
||||
app := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
})
|
||||
|
||||
app.Get("/", func (c *fiber.Ctx) error {
|
||||
return c.Render("index", fiber.Map{
|
||||
"Content": "hello, world!"
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="add-func-map-template" label="views/index.html">
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<p>This will be in {{ToUpper "all caps"}}:</p>
|
||||
<p>{{ToUpper .Content}}</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
* For more advanced template documentation, please visit the [gofiber/template GitHub Repository](https://github.com/gofiber/template).
|
||||
|
||||
## Full Example
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="example" label="Example">
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/template/html/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize standard Go html template engine
|
||||
engine := html.New("./views", ".html")
|
||||
// If you want to use another engine,
|
||||
// just replace with following:
|
||||
// Create a new engine with django
|
||||
// engine := django.New("./views", ".django")
|
||||
|
||||
app := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
})
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
// Render index template
|
||||
return c.Render("index", fiber.Map{
|
||||
"Title": "Go Fiber Template Example",
|
||||
"Description": "An example template",
|
||||
"Greeting": "Hello, world!",
|
||||
});
|
||||
})
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="index" label="views/index.html">
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
<meta name="description" content="{{.Description}}">
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{.Title}}</h1>
|
||||
<p>{{.Greeting}}</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
168
docs/guide/validation.md
Normal file
168
docs/guide/validation.md
Normal file
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
id: validation
|
||||
title: 🔎 Validation
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
## Validator package
|
||||
|
||||
Fiber can make _great_ use of the validator package to ensure correct validation of data to store.
|
||||
|
||||
- [Official validator Github page \(Installation, use, examples..\).](https://github.com/go-playground/validator)
|
||||
|
||||
You can find the detailed descriptions of the _validations_ used in the fields contained on the structs below:
|
||||
|
||||
- [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/v2"
|
||||
)
|
||||
|
||||
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 interface{}
|
||||
}
|
||||
|
||||
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 interface{}) []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
|
||||
}
|
||||
|
||||
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: c.Query("name"),
|
||||
Age: c.QueryInt("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"))
|
||||
}
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
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!
|
||||
|
||||
**/
|
||||
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue