Middleware
Introduction
Middleware provide a convenient mechanism for filtering HTTP requests entering your application. Additional middleware can be written to perform a variety of tasks. A CORS middleware might be responsible for adding the proper headers to all responses leaving your application. A logging middleware might log all incoming requests to your application.
There are several middleware included in the Confetti framework. All of these middleware are located in the app/http/middleware
directory.
Defining Middleware
Let's place a new EnsureTokenIsValid
struct within your app/http/middleware
directory. In this middleware, we will only allow access to the route if the supplied token
is valid. Otherwise, we will redirect the users back to the home
URI:
package middleware
import (
"github.com/confetti-framework/contract/inter"
"github.com/confetti-framework/routing/outcome"
)
type EnsureTokenIsValid struct{}
func (c EnsureTokenIsValid) Handle(request inter.Request, next inter.Next) inter.Response {
if request.Parameter("token").String() != "my-secret-token" {
return outcome.RedirectTemporary("home")
}
return next(request)
}
To pass the request deeper into the application (allowing the middleware to "pass"), call the next
callback with the request
. Then the request will be passed further into the application.
It's best to envision middleware as a series of "layers" HTTP requests must pass through before they hit your application. Each layer can examine the request and even reject it entirely.
The request contains the service container, so you may fetch any dependencies you need with
request.Make(...)
.
Before & After Middleware
Whether a middleware runs before or after a request depends on the middleware itself. For example, the following middleware would perform some task before the request is handled by the application:
package middleware
import (
"github.com/confetti-framework/contract/inter"
)
type BeforeMiddleware struct{}
func (c BeforeMiddleware) Handle(request inter.Request, next inter.Next) inter.Response {
// Perform action
return next(request)
}
However, this middleware would perform its task after the request is handled by the application:
package middleware
import (
"github.com/confetti-framework/contract/inter"
)
type AfterMiddleware struct{}
func (c AfterMiddleware) Handle(request inter.Request, next inter.Next) inter.Response {
response := next(request)
// Perform action
return response
}
Registering Middleware
Global Middleware
If you want a middleware to run during every HTTP request to your application, list the middleware struct in the globalMiddlewares
variable in your providers.RouteServiceProvider
located in app/providers/route_service_provider.go
.
Assigning Middleware To Routes
If you would like to assign middleware to specific routes, you can use the Middleware
method to pass the struct of the middleware:
Get("/admin/profile", controllers.AdminProfileStore).
Middleware(
middleware.ValidatePostSize{},
middleware.CheckAge{},
)
When assigning middleware to a group of routes, you may occasionally need to prevent the middleware from being applied to an individual route within the group. You may accomplish this using the WithoutMiddleware
method:
routes := Group(
Get("/roles", controllers.Roles),
Get("/comments", controllers.Comments).
WithoutMiddleware(middleware.CheckAge{}),
).Middleware(
middleware.ValidatePostSize{},
middleware.CheckAge{},
)
The WithoutMiddleware
method can only remove route middleware and does not apply to global middleware. Supply parameters to the struct of WithoutMiddleware has no effect and is therefore superfluous.
Middleware Groups
Sometimes you may want to group several middleware under a single key to make them easier to assign to routes. We call this Middleware Groups.
Out of the box, Confetti comes with Web
and Api
middleware groups that contain common middlewares you may want to apply to your web UI and API routes. Let's see how the Web middleware group might look like:
package middleware
import "github.com/confetti-framework/contract/inter"
var Web = []inter.HttpMiddleware{
EncryptCookies{},
AddQueuedCookiesToResponse{},
StartSession{},
ShareErrorsFromSession{},
VerifyCsrfToken{},
SubstituteBindings{},
}
Middleware groups can be loaded by using the spread operator in the Middleware
method. Again, middleware groups make it more convenient to assign many middlewares to a route at once:
Group(
//
).Middleware(middleware.Web...)
Out of the box, the
Web
middleware group is automatically applied to your web routes byroutes/web.go
.
Middleware Parameters
Middleware can also receive additional parameters. For example, if your application needs to verify that the authenticated user has a given "role" before performing a given action, you could create a CheckRole
middleware that receives a role name as an additional public field.
package middleware
import (
"github.com/confetti-framework/contract/inter"
"github.com/confetti-framework/routing/outcome"
"confetti/src/request-adapter"
)
type CheckRole struct{
Role string
}
func (c CheckRole) Handle(request inter.Request, next inter.Next) inter.Response {
if requestAdapter.CurrentUser(request).Role() != c.Role {
return outcome.RedirectTemporary("/home")
}
return next(request)
}
You can pass the parameters to the public fields of the middleware:
Group(
//
).Middleware(middleware.CheckRole{Role: "editor"})