Core services
Every handler invocation comes with its own request context, which is represented as the type flamego.Context
open in new window. Data and state are not shared among these request contexts, which makes handler invocations independent from each other unless your web application has defined some other shared resources (e.g. database connections, cache).
Thereforce, flamego.Context
is available to use out-of-the-box by your handlers:
When a route is matched by a request, the Flame instance (including middleware) to be invoked in the same order as they are registered.
By default, the next handler will only be invoked after the previous one in the chain has finished. You may change this behvaior using the Next
method, which allows you to pause the execution of the current handler and resume after the rest of the chain finished.
package main
import (
"fmt"
"github.com/flamego/flamego"
)
func main() {
f := flamego.New()
f.Get("/",
func(c flamego.Context) {
fmt.Println("starting the first handler")
c.Next()
fmt.Println("exiting the first handler")
},
func() {
fmt.Println("executing the second handler")
},
)
f.Run()
}
When you run the above program and do curl http://localhost:2830/
, the following logs are printed to your terminal:
[Flamego] Listening on 0.0.0.0:2830 (development)
starting the first handler
executing the second handler
exiting the first handler
The routing logger is taking advantage of this feature to .
Remote address
Web applications often want to know where clients are coming from, then the RemoteAddr()
method is the convenient helper made for you:
func main() {
f := flamego.New()
f.Get("/", func(c flamego.Context) string {
return "The remote address is " + c.RemoteAddr()
})
f.Run()
}
The RemoteAddr()
method is smarter than the standard library that only looks at the http.Request.RemoteAddr
field (which stops working if your web application is behind a reverse proxy), it also takes into consideration of some well-known headers.
This method looks at following things in the order to determine which one is more likely to contain the real client address:
- The
X-Real-IP
request header - The
X-Forwarded-For
request header - The
http.Request.RemoteAddr
field
This way, you can configure your reverse proxy to pass on one of these headers.
WARNING
The client can always fake its address using a proxy or VPN, getting the remote address is always considered as a best effort in web applications.
The Redirect
method is given the fact that the request context knows what the http.ResponseWriter
and *http.Request
are for the current request, and uses the http.StatusFound
as the default status code for the redirection:
- Code
- Test
package main
import (
"net/http"
"github.com/flamego/flamego"
)
func main() {
f := flamego.New()
f.Get("/", func(c flamego.Context) {
c.Redirect("/signup")
})
f.Get("/login", func(c flamego.Context) {
c.Redirect("/signin", http.StatusMovedPermanently)
})
f.Run()
}
$ curl -i http://localhost:2830/
HTTP/1.1 302 Found
...
$ curl -i http://localhost:2830/login
HTTP/1.1 301 Moved Permanently
...
WARNING
Be aware that the Redirect
method does a naive redirection and is vulnerable to the open redirect vulnerabilityopen in new window.
For example, the following is also works as a valid redirection:
c.Redirect("https://www.google.com")
Please make sure to always first validating the user input!
URL parameters
URL parametersopen in new window, also known as “URL query parameters”, or “URL query strings”, are commonly used to pass arguments to the backend for all HTTP methods (POST forms have to be sent with POST method, as the counterpart).
The Query
method is built as a helper for accessing the URL parameters, and return an empty string if no such parameter found with the given key:
- Code
- Test
package main
import (
"github.com/flamego/flamego"
)
func main() {
f := flamego.New()
f.Get("/", func(c flamego.Context) string {
return "The name is " + c.Query("name")
})
f.Run()
}
$ curl http://localhost:2830?name=joe
The name is joe
$ curl http://localhost:2830
There is a family of Query
methods available at your fingertips, including:
QueryTrim
trims spaces and returns value.QueryStrings
returns a list of strings.QueryUnescape
returns unescaped query result.QueryBool
returns value parsed as bool.QueryInt
returns value parsed as int.QueryInt
returns value parsed as int64.QueryFloat64
returns value parsed as float64.
TIP
vals := c.Request().URL.Query()
No.
The flamego.Context
is a representation of the request context and should live within the routing layer, where the is a general purpose context and can be propogated to almost anywhere (e.g. database layer).
You can retrieve the context.Context
of a request using the following methods:
f.Get(..., func(c flamego.Context) {
ctx := c.Request().Context()
...
})
// or
f.Get(..., func(r *http.Request) {
ctx := r.Context()
...
})
Default logger
The *log.Logger
open in new window is available to all handers for general logging purposes, this is particularly useful if you’re writing middleware:
When you run the above program and do curl http://localhost:2830/
, the following logs are printed to your terminal:
[Flamego] Listening on 0.0.0.0:2830 (development)
[Flamego] Hello, Flamego!
The is taking advantage of this feature to print the duration and status code of requestsopen in new window.
The response stream of a request is represented by the type , you may use it as an argument of your handlers or through the ResponseWriter
method of the flamego.Context
:
f.Get(..., func(w http.ResponseWriter) {
...
})
// or
f.Get(..., func(c flamego.Context) {
w := c.ResponseWriter()
...
})
💡 小贴士
Not all handlers that are registered for a route are always being invoked, the request context (flamego.Context
) stops invoking subsequent handlers when the response status code has been writtenopen in new window by the current handler. This is similar to how the works.
Request object
The request object is represented by the type , you may use it as an argument of your handlers or through the Request().Request
field of the flamego.Context
:
f.Get(..., func(r *http.Request) {
...
})
// or
f.Get(..., func(c flamego.Context) {
r := c.Request().Request
...
})
You may wonder what does c.Request()
return in the above example?
Good catch! It returns a thin wrapper of the *http.Request
and has the type *flamego.Request
open in new window, which provides helpers to read the request body:
f.Get(..., func(c flamego.Context) {
body := c.Request().Body().Bytes()
...
})
TIP
This middleware is automatically registered when you use to create a Flame instance.
The flamego.Logger
open in new window is the middleware that provides logging of requested routes and corresponding status code:
package main
import (
"github.com/flamego/flamego"
)
func main() {
f := flamego.New()
f.Use(flamego.Logger())
f.Get("/", func() (int, error) {
return http.StatusOK, nil
})
f.Run()
}
When you run the above program and do curl http://localhost:2830/
, the following logs are printed to your terminal:
[Flamego] Listening on 0.0.0.0:2830 (development)
[Flamego] ...: Started GET / for 127.0.0.1
[Flamego] ...: Completed GET / 200 OK in 165.791µs
Panic recovery
TIP
This middleware is automatically registered when you use flamego.Classic
open in new window to create a Flame instance.
The is the middleware that is for recovering from panic:
package main
import (
"github.com/flamego/flamego"
)
func main() {
f := flamego.New()
f.Use(flamego.Recovery())
f.Get("/", func() {
panic("I can't breath")
})
f.Run()
}
When you run the above program and visit http://localhost:2830/open in new window, the recovered page is displayed:
This middleware is automatically registered when you use to create a Flame instance.
The flamego.Static
open in new window is the middleware that is for serving static files, and it accepts an optional :
func main() {
f := flamego.New()
f.Use(flamego.Static(
flamego.StaticOptions{
Directory: "public",
},
))
f.Run()
}
You may also omit passing the options for using all default values:
func main() {
f := flamego.New()
f.Use(flamego.Static())
f.Run()
}
Example: Serving the source file
In this example, we’re going to treat our source code file as the static resources:
package main
import (
"github.com/flamego/flamego"
)
func main() {
f.Use(flamego.Static(
Directory: "./",
Index: "main.go",
},
))
f.Run()
}
On line 11, we changed the Directory
to be the working directory ("./"
) instead of the default value "public"
.
On line 12, we changed the index file (the file to be served when listing a directory) to be main.go
instead of the default value "index.html"
.
When you save the above program as main.go
and run it, both curl http://localhost:2830/
and curl http://localhost:2830/main.go
will response the content of this main.go
back to you.
In this example, we’re going to serve static resources for two different directories.
Here is the setup for the example:
- Directory
- css/main.css
- js/main.js
- main.go
- Test
html {
color: red;
}
console.log("Hello, Flamego!");
package main
import (
"github.com/flamego/flamego"
)
func main() {
f := flamego.New()
f.Use(flamego.Static(
flamego.StaticOptions{
Directory: "js",
},
))
f.Use(flamego.Static(
flamego.StaticOptions{
Directory: "css",
},
))
f.Run()
}
$ curl http://localhost:2830/main.css
html {
color: red;
}
$ curl http://localhost:2830/main.js
console.log("Hello, Flamego!");
You may have noticed that the client should not include the value of Directory
, which are "css"
and "js"
in the example. If you would like the client to include these values, you can use the Prefix
option:
f.Use(flamego.Static(
flamego.StaticOptions{
Directory: "css",
Prefix: "css",
},
))
💡 小贴士
The value of Prefix
does not have to be the same as the value of Directory
.
Example: Serving the embed.FS
In this example, we’re going to serve static resources from the embed.FS
open in new window that was .
Here is the setup for the example:
- Directory
- css/main.css
- main.go
- Test
tree .
.
├── css
│ └── main.css
├── go.mod
├── go.sum
└── main.go
html {
color: red;
}
package main
import (
"embed"
"net/http"
"github.com/flamego/flamego"
)
//go:embed css
var css embed.FS
func main() {
f := flamego.New()
f.Use(flamego.Static(
flamego.StaticOptions{
FileSystem: http.FS(css),
},
))
f.Run()
}
$ curl http://localhost:2830/css/main.css
html {
color: red;
}
WARNING
Because the Go embed encodes the entire path (i.e. including parent directories), the client have to use the full path, which is different from serving static files directly from the local disk.
In other words, the following command will not work for the example:
$ curl http://localhost:2830/main.css
404 page not found
Rendering content
The is a minimal middleware that is for rendering content, and it accepts an optional flamego.RenderOptions
open in new window.
The service is injected to your request context and you can use it to render JSON, XML, binary and plain text content:
- Code
- Test
$ curl -i http://localhost:2830/
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
...
{
"id": 1,
"username": "joe"
TIP
Try changing the line 13 to JSONIndent: "",
, then redo all test requests and see what changes.