An Interest In:
Web News this Week
- April 4, 2024
- April 3, 2024
- April 2, 2024
- April 1, 2024
- March 31, 2024
- March 30, 2024
- March 29, 2024
Go Fiber by Examples: Delving into built-in functions
Introduction
Hey, DEV friends!
As promised in last week's post, today I've posted the slightly edited Chapter 2 from my book for the Packt Publisher that didn't happen. If you missed it, I told that at the beginning of previous article.
Plan for the Chapter 2
In this second article (or chapter), we will get even closer to the inner workings of the Fiber web framework, its routing technique, built-in components and methods.
In this article, were going to cover the following main topics
Table of contents
- Fiber application
- Configuration
- Routing technique
- Built-in methods
- Working with Fiber Context methods
- Summary
Fiber application
Like many other web frameworks, the Fiber application also starts with the creation of a new named instance. To create, simply import the package and call the fiber.New
method:
// ./go/app.goimport "github.com/gofiber/fiber/v2"func main() { // Create a new Fiber instance, called 'app' app := fiber.New() // ...}
After executing, a new Fiber instance will be created in the app variable with the default configuration. We will cover manual configuration of the instance later in this article.
Configuration
Sometimes, the default settings are not enough to configure an application the way we need it. To satisfy such users, Fiber has a special fiber.Config
structure that contains many fields for fine-tuning.
For example, let's change the standard output of the server header:
// Create config variableconfig := fiber.Config{ ServerHeader: "My Server", // add custom server header}// Create a new Fiber instance with custom configapp := fiber.New(config)
After that, every request to that application will return that server name along with a Response header. In a real application, it might look like this:
Access-Control-Allow-Origin: *Content-Length: 51Content-Type: application/jsonDate: Mon, 03 May 2021 07:00:39 GMTServer: My ServerVary: Origin
Simple, isn't it? Let's now list all of the existing Fiber application configuration fields and go into a little more detail about each one.
Prefork
Type: bool
, default: false
.
Enables use of the SO_REUSEPORT
socket option. This will spawn multiple Go processes listening on the same port. Also, it's called socket sharding. If enabled, the application will need to be run through a shell because prefork mode sets environment variables.
Note: When using Docker, make sure the application is running with
CMD ./app
orCMD ["sh", "-c", "/app"]
into project'sDockerfile
.
ServerHeader
Type: string
, default: ""
(empty string).
Enables the Server HTTP header with the given value.
StrictRouting
Type: bool
, default: false
.
When enabled, the Fiber router treats /foo
and /foo/
as different routes. This can be useful, if we want to improve the SEO (Search Engine Optimization) of the website.
CaseSensitive
Type: bool
, default: false
.
When enabled, the Fiber router treats /Foo
and /foo
as different routes.
Immutable
Type: bool
, default: false
.
When enabled, all values returned by context methods are immutable. By default, they are valid until you return from the handler.
UnescapePath
Type: bool
, default: false
.
Converts all encoded characters in the route back before setting the path for the context, so that the routing can also work with URL encoded special characters.
ETag
Type: bool
, default: false
.
Enable ETag header generation, since both weak and strong etags are generated using the same hashing method (CRC-32).
BodyLimit
Type: int
, default: 4 * 1024 * 1024
.
Sets the maximum allowed size for a request body. If the size exceeds the configured limit, it sends HTTP 413 Payload Too Large response.
Concurrency
Type: int
, default: 256 * 1024
.
Maximum number of concurrent connections.
Views
Views
is the interface to provide your own template engine and contains Load
and Render
methods.
The Load
method is executed by Fiber on app initialization to load/parse the templates. And the Render
method is linked to the ctx.Render
function that accepts a template name and binding data.
The Fiber team supports template package that provides wrappers for multiple template engines:
- Standard Go's html/template engine
- Ace
- Amber
- DTL or Django Template Language
- Handlebars
- Jet
- Mustache
- Pug
Here's a simple example of how to use it:
// ./go/views.gopackage mainimport ( "github.com/gofiber/fiber/v2" "github.com/gofiber/template/html" // add engine)func main() { // Initialize a standard Go's html/template engine engine := html.New("./views", ".html") // Create a new Fiber template with template engine app := fiber.New(fiber.Config{ Views: engine, }) app.Get("/", func(c *fiber.Ctx) error { // Render a template named 'index.html' with content return c.Render("index", fiber.Map{ "Title": "Hello, World!", "Description": "This is a template.", }) }) // Start server on port 3000 app.Listen(":3000")}
And the HTML template itself for rendering content in the ./views
folder:
<!-- ./go/views/index.html --><!DOCTYPE html><head> <title>{{.Title}}</title></head><body> <h1>{{.Title}}</h1> <p>{{.Description}}</p></body></html>
By running this code (go run ./go/views.go
) and going to http://localhost:3000/
, we will see that the code works exactly as we expected:
ReadTimeout
Type: time.Duration
, default: nil
.
The amount of time allowed to read the full request, including the body. Set to nil
for unlimited timeout.
WriteTimeout
Type: time.Duration
, default: nil
.
The maximum duration before timing out writes of the response. Set to nil
for unlimited timeout.
IdleTimeout
Type: time.Duration
, default: nil
.
The maximum amount of time to wait for the next request when keep-alive is enabled.
Note: If IdleTimeout is zero, the value of ReadTimeout is used.
ReadBufferSize
Type: int
, default: 4096
.
Per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer, if your clients send multi-KB RequestURIs and/or multi-KB headers.
WriteBufferSize
Type: int
, default: 4096
.
Per-connection buffer size for responses' writing.
CompressedFileSuffix
Type: string
, default: ".fiber.gz"
.
Adds a suffix to the original file name and tries saving the resulting compressed file under the new file name.
ProxyHeader
Type: string
, default: ""
(empty string).
This will enable ctx.IP
to return the value of the given header key. By default, ctx.IP
will return the Remote IP from the TCP connection.
Note: This property can be useful if you are behind a load balancer, e.g.
X-Forwarded-*
.
GETOnly
Type: bool
, default: false
.
Enables to rejects all non-GET requests. This option is useful as anti-DoS protection for servers accepting only GET requests.
Note: If
GETOnly
is set to true, the request size is limited byReadBufferSize
.
ErrorHandler
Type: ErrorHandler
, default: DefaultErrorHandler
.
ErrorHandler
is executed, when an error is returned from fiber.Handler
.
DisableKeepalive
Type: bool
, default: false
.
Disable keep-alive connections. The server will close incoming connections after sending the first response to the client.
DisableDefaultDate
Type: bool
, default: false
.
When set to true
causes the default date header to be excluded from the response.
DisableDefaultContentType
Type: bool
, default: false
.
When set to true, causes the default Content-Type
header to be excluded from the Response.
DisableHeaderNormalizing
Type: bool
, default: false
.
By default, all header names are normalized. For example, header cOnteNT-tYPE
will convert to more readable Content-Type
.
DisableStartupMessage
Type: bool
, default: false
.
When set to true, it will not print out debug information and startup message, like this:
Fiber v2.17.0 http://127.0.0.1:5000 (bound on host 0.0.0.0 and port 5000) Handlers ............ 21 Processes ........... 1 Prefork ....... Disabled PID ............. 92607
Routing technique
The central part of any application, especially if it's a REST API, is routing requests to the appropriate endpoints. Fiber has an excellent set of options for creating a router for any task.
Note: Route paths, combined with a request method, define the endpoints at which requests can be made. Route paths can be strings or string patterns.
Route patterns 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 ctx.Params
function, with the name of the route pattern specified in the path as their respective keys or for unnamed patterns the character (*
or +
) and the counter of this.
- The characters
:
,+
, and*
are characters that introduce a pattern. - Greedy patterns are indicated by wildcard (
*
) or plus (+
) signs.
The routing also offers the possibility to use optional patterns, for the named patterns these are marked with a final ?
sign, unlike the plus sign which is not optional. You can use the wildcard character for a pattern range which is optional and greedy.
Let's go over them with some real-world examples.
Simple routes
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
, not a file with the same name, as you might think. We will talk about how to serve a file later in this article.
app.Get("/random.txt", func(c *fiber.Ctx) error { return c.SendString("random.txt")})
Named routes
This dynamic path example will expect the author name (in the author parameter) and book title (in the title parameter) to be output when queried:
app.Get("/library/:author/books/:title", func(c *fiber.Ctx) error { str := fmt.Sprintf("%s, %s", c.Params("author"), c.Params("title")) return c.SendString(str)})
If you call this path in the running Fiber application like http://localhost:3000/user/Tolkien/books/Hobbit
. Then this line will be displayed: Tolkien, Hobbit
.
Sometimes, we can't know right away if a parameter will be passed to a URL. For this, the Fiber developers introduced optional parameters:
app.Get("/user/:name?", func(c *fiber.Ctx) error { return c.SendString(c.Params("name"))})
It returns empty string, if name is not specified.
Routes with greedy parameters
The Fiber web framework also supports so-called greedy parameters. Such parameters will be useful when we have a task to build an address for an endpoint with previously unknown parameters. They can be either optional or required.
Let's look at an example of a required greedy parameter in the example below:
app.Get("/user/+", func(c *fiber.Ctx) error { return c.SendString(c.Params("+"))})
In the place where there is a plus sign, we must necessarily pass parameters. But we are not limited to anything, as we can pass either a single parameter or a whole chain of parameters:
http://localhost:3000/user/?name=Bilbohttp://localhost:3000/user/?name=Bilbo&family=Baggins&city=Shire
The optional greedy parameters work in a similar way:
app.Get("/user/*", func(c *fiber.Ctx) error { return c.SendString(c.Params("*"))})
There is a little limitation when using greedy parameters in routes: we cannot use multiple wildcards (*
) in the same endpoint at once.
The following example will only consider the first wildcard segment, and the rest will be skipped:
app.Get("/user/*/work/*/job/*", func(c *fiber.Ctx) error { return c.SendString(c.Params("*"))})
More complex routes
Since the hyphen (-
) and the dot (.
) are interpreted literally, they can be used along with route parameters for useful purposes. Fiber's intelligent router recognizes that the introductory parameter characters should be part of the request route in this case and can process them as such.
This allows us to build very complex endpoint paths, for almost any task:
app.Get("/flights/:from-:to/time::at", func(c *fiber.Ctx) error { str := fmt.Sprintf("%s-%s at %s", c.Params("from"), c.Params("to"), c.Params("at")) return c.SendString(str)})
By sending a request to this URL http://localhost:3000/flights/LAX-SFO/time:10PM
, we get the string LAX-SFO
at 10PM
. As we expected at the beginning.
Thus, Fiber was able to support a fairly complex route, but for the user such a record is still as clear as possible!
Limitations for characters in the path
All special characters in path must be escaped with backslash (\
) for lose their original value. For example, if route path must match to /resource/key:value
, we need to escape colon (:
) sign, like this:
app.Get("/resource/key\\:value", func(c *fiber.Ctx) error { return c.SendString("escaped key:value")})
Regexp routes
The Fiber web framework aims to be one of the fastest and clearest web frameworks in Go, so deliberately slowing down routing for the sake of supporting a rather specific case is not in the developers' plans.
And, yes, Fiber does not currently support regular expressions in routes, like other frameworks. But it's not so important, because most of the routing cases can close the techniques described above.
Built-in methods
And now let's talk about the methods built into the Fiber web framework that are designed to ease most of the routine operations so that you write less duplicate code.
Static
Use the Static
method to serve static files such as images, CSS, and JavaScript. By default, it will serve index.html
files in response to a request on a directory.
The following code serves all files and folders in the directory named ./public
(in the root folder of our project):
app.Static("/", "./public")
By running the application, we can access the contents of that folder at these addresses (of course, if they are there):
http://localhost:3000/page.htmlhttp://localhost:3000/js/script.jshttp://localhost:3000/css/style.css
Yes, we can easily serve files in completely different directories within a single Fiber application, for example:
// Serve files from './public' directoryapp.Static("/", "./public")// Serve files from './storage/user-uploads' directoryapp.Static("/files", "./storage/user-uploads")
This method is great for creating a complete SPA (Single Page Application).
Also, the Static
method has settings that allow you to fine-tune the behavior of the Fiber application when serving files. To access them, call the method with the fiber.Static
interface as the third argument:
// Create a new config for Static methodconfig := fiber.Static{ Compress: true, ByteRange: true, Browse: true, Index: "page.html" CacheDuration: 60 * time.Second, MaxAge: 3600, Next: nil}// Serve files from './public' directory with configapp.Static("/", "./public", config)
Let's break down each setting item in more detail.
Compress
Type: bool
, default: false
.
When set to true, the server tries minimizing CPU usage by caching compressed files.
Note: This works differently than the compression middleware, which is supported by the Fiber team.
ByteRange
Type: bool
, default: false
.
When set to true
, enables byte range requests.
Browse
Type: bool
, default: false
.
When set to true
, enables directory browsing.
Index
Type: string
, default: "index.html"
.
The name of the index file for serving a directory.
CacheDuration
Type: time.Duration
, default: 10 * time.Second
.
Expiration duration for inactive file handlers. Use a negative time.Duration
to disable it.
MaxAge
Type: int
, default: 0
.
The value for the Cache-Control
HTTP header that is set on the file response. MaxAge
is defined in seconds.
Next
Type: func(c *Ctx) bool
, default: nil
.
Next defines a function to skip this middleware, when true
is returned.
Note: The setting can be useful when it is necessary to serve a folder with files, as a result of some external state changes or user requests in the application.
Route Handlers
Use the following methods for register a route bound to a specific HTTP method.
// Create a new route with GET methodapp.Get("/", handler)// Create a new route with POST methodapp.Post("/article/add", handler)// Create a new route with PUT methodapp.Put("/article/:id/edit", handler)// Create a new route with PATCH methodapp.Patch("/article/:id/edit", handler)// Create a new route with DELETE methodapp.Delete("/article/:id", handler)// ...
The Fiber Web Framework has the following helper methods for handling HTTP methods: Add
and All
.
The Add
method allows you to use an HTTP method, as the value:
// Set HTTP method to variablemethod := "POST"// Create a new route with this methodapp.Add(method, "/form/create", handler)
And the All
method opens up possibilities for us to register a route that can accept any valid HTTP methods, which can be handy in some cases:
// Create a new route for all HTTP methodsapp.All("/all", handler)
Mount
Use the Mount
method for mount one Fiber instance to another by creating the *Mount
struct.
// ./go/mount.gopackage mainimport "github.com/gofiber/fiber/v2"func main() { // Create the first Fiber instance micro := fiber.New() // Create a new route for the first instance micro.Get("/doe", func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) // send HTTP 200 OK }) // Create the second Fiber instance app := fiber.New() // Create a new route for the second instance, // with included first instance's route app.Mount("/john", micro) // Start server on port 3000 app.Listen(":3000")}
The /john/doe
route of this Fiber application will now give a status HTTP 200 OK.
Group
Use Group
method for grouping routes by creating the *Group
struct.
// ./go/group.gopackage mainimport "github.com/gofiber/fiber/v2"func main() { // Create a new Fiber instance app := fiber.New() // Create a new route group '/api' api := app.Group("/api", handler) // Create a new route for API v1 v1 := api.Group("/v1", handler) v1.Get("/list", handler) // Create a new route for API v1 v2 := api.Group("/v2", handler) v2.Get("/list", handler) // Start server on port 3000 app.Listen(":3000")}
This built-in method is great for versioning our application's API.
Server
Use Server
method for returns the underlying fasthttp server.
// Set the fasthttp option 'MaxConnsPerIP' to '1'app.Server().MaxConnsPerIP = 1
Stack
Use Stack
method for return the original router stack.
// ./go/stack.gopackage mainimport ( "encoding/json" "fmt" "github.com/gofiber/fiber/v2")func main() { // Create a new Fiber instance app := fiber.New() // Create new routes app.Get("/john/:age", handler) app.Post("/register", handler) // Print the router stack in JSON format data, _ := json.MarshalIndent(app.Stack(), "", " ") fmt.Println(string(data)) // Start server on port 3000 app.Listen(":3000")}
The result will be a list of all routes in a nicely formatted JSON format:
[ [ { "method": "GET", "path": "/john/:age", "params": [ "age" ] } ], [ { "method": "POST", "path": "/register", "params": null } ]]
Config
Use Config
method for return the app config as value (read-only).
// Print ServerHeader valuefmt.Println(app.Config().ServerHeader)
Handler
Use Handler
method for return the server handler that can be used to serve custom *fasthttp.RequestCtx
requests (more info about this is here).
Listen
Use Listen
method for serve HTTP requests from the given address.
// Listen on port 8080 app.Listen(":8080")// Listen on the custom host and portapp.Listen("127.0.0.2:9090")
ListenTLS
User ListenTLS
method for serve HTTPS (secure HTTP) requests from the given address using certFile
and keyFile
paths to TLS certificate and key.
// Listen on port 443 with TLS cert and keyapp.ListenTLS(":443", "./cert.pem", "./cert.key")
Listener
Use Listener
method for pass custom net.Listener. This method can be used to enable TLS/HTTPS with a custom tls.Config.
// Create a new net.Listener TCP instance on port 3000ln, _ := net.Listen("tcp", ":3000")// Set TLS key pair (certificate and key)certs, _ := tls.LoadX509KeyPair("./server.crt", "./server.key")// Configure a new TLS listener with paramslr := tls.NewListener( ln, &tls.Config{ Certificates: []tls.Certificate{ certs, }, },)// Start server with TLS listenerapp.Listener(lr)
Test
Testing your application is done with the Test method. Use this method when you need to debug your routing logic.
We will consider this method in more detail at the next article. Stay tuned!
NewError
Use NewError
method for create a new HTTP error instance with an optional message.
// Create a custom error with HTTP code 782app.Get("/error", func(c *fiber.Ctx) error { return fiber.NewError(782, "Custom error message")})IsChildUse IsChild method for determine, if the current process is a result of Prefork.// Create a new Fiber instance with configapp := fiber.New(fiber.Config{ Prefork: true, // enable Prefork})// Cheking current processif fiber.IsChild() { fmt.Println("I'm a child process")} else { fmt.Println("I'm the parent process")}// ...
Working with Fiber Context methods
In the Fiber web framework you will find a huge variety of methods for working with request contexts. As of v2.17.0
, there are a total of 55, and this number grows with each major version. Therefore, we will not go through them all, so as not to waste our time, but focus only on those that we will use in this series (in its practical part).
Note: For a complete list of all
Context
methods, its signatures and code examples, please visit the official documentation page of the Fiber web framework at https://docs.gofiber.io/api/ctx
BodyParser
Binds the request body to a struct. BodyParser
supports decoding query parameters and the following content types based on the Content-Type
header:
- application/json
- application/xml
- application/x-www-form-urlencoded
- multipart/form-data
// ./go/body_parser.gopackage mainimport ( "fmt" "github.com/gofiber/fiber/v2")// Define the Person structtype Person struct { Name string `json:"name" xml:"name" form:"name"` Email string `json:"email" xml:"email" form:"email"`}func main() { // Create a new Fiber instance app := fiber.New() // Create a new route with POST method app.Post("/create", func(c *fiber.Ctx) error { // Define a new Person struct person := new(Person) // Binds the request body to the Person struct if err := c.BodyParser(person); err != nil { return err } // Print data from the Person struct fmt.Println(person.Name, person.Email) return nil }) // Start server on port 3000 app.Listen(":3000")}
If we run this application and send data from the form to the route http://localhost:3000/create
by POST, we will see in the console the data we sent.
Note: Field names in a struct should start with an uppercase letter.
JSON
Converts any interface or string to JSON using the segmentio/encoding package. Also, the JSON method sets the content header to application/json
.
// ./go/json.gopackage mainimport "github.com/gofiber/fiber/v2"func main() { // Create a new Fiber instance app := fiber.New() // Create a new route with GET method app.Get("/json", func(c *fiber.Ctx) error { // Return response in JSON format return c.JSON(fiber.Map{ "name": "John", "age": 33, }) }) // Start server on port 3000 app.Listen(":3000")}
As we could see, in this example a special helper method fiber.Map
was used, which is just an empty Go interface. The result of this application will display data in JSON format: {"name": "John", "age": 33}
.
Params
The Params
method can be used to get the route parameters. We can pass an optional default value that will be returned, if the param key does not exist.
// ./go/params.gopackage mainimport ( "fmt" "github.com/gofiber/fiber/v2")func main() { // Create a new Fiber instance app := fiber.New() // Create a new route with named params app.Get("/user/:name", func(c *fiber.Ctx) error { // Print name from params fmt.Println(c.Params("name")) return nil }) // Create a new route with optional greedy params app.Get("/user/*", func(c *fiber.Ctx) error { // Print all data, given from '*' param fmt.Println(c.Params("*")) return nil }) // Start server on port 3000 app.Listen(":3000")}
We dealt with the topic of parameters in routes in more detail earlier in this article.
Query
Query
method is an object containing a property for each query string parameter in the route. We can pass an optional default value that will be returned, if the query key does not exist.
// ./go/query.gopackage mainimport ( "fmt" "github.com/gofiber/fiber/v2")func main() { // Create a new Fiber instance app := fiber.New() // Create a new route with query params app.Get("/user", func(c *fiber.Ctx) error { // Print name from query param fmt.Println(c.Query("name")) return nil }) // Start server on port 3000 app.Listen(":3000")}
By calling this endpoint and passing the query parameter with the name key, we get its value.
Set
The Set
method sets the responses HTTP header field to the specified key and value.
// Create a new route with text/plain content typeapp.Get("/text", func(c *fiber.Ctx) error { // Set a new Content-Type for this route c.Set("Content-Type", "text/plain") // ...})
Status and SendStatus
The Status
and SendStatus
methods set the HTTP status code for the response and the correct status message in the body, if the response body is empty. But there is a small difference in the options for using these methods.
The Status
method is a chainable. This means that it can be used in conjunction with other methods, like this:
// Create a new route with HTTP statusapp.Get("/bad-request", func(c *fiber.Ctx) error { // Return status with a custom message return c.Status(fiber.StatusBadRequest).SendString("Bad Request")})
And method SendStatus
should be used only when we don't need anything except returning HTTP code. For example:
// Create a new route with HTTP statusapp.Get("/forbidden", func(c *fiber.Ctx) error { // Return only status return c.SendStatus(fiber.StatusForbidden)})
Note: We can view all supported Fiber helpers for HTTP statuses here.
Summary
So that was a very informative article!
- We learned the basic instance of the Fiber framework, were able to configure it to our liking.
- We looked at how Fiber works with routes, what it can and can't do with them.
- Learned about all the necessary built-in web framework methods and learned how to work with context.
In the next article, we will get even closer to the Test
method.
Photos and videos by
- Aaron Burden feat. Vic Shstak https://unsplash.com/photos/QJDzYT_K8Xg
P.S.
If you want more articles like this on this blog, then post a comment below and subscribe to me. Thanks!
Original Link: https://dev.to/koddr/go-fiber-by-examples-delving-into-built-in-functions-1p3k
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To