Middleware
A Middleware, as the name implies, sits in the middle of a request lifecycle and lets you intercept an
incoming request and act on it. Think of it as like filters that an HttpCall
must pass through.
For example, a logging middleware might log the details of an incoming request to a file, or an authentication
middleware might check the current session and decide whether to let a user continue or to return an error.
In fact, dev.alpas.auth.middleware.AuthOnlyMiddleware
that Alpas includes does exactly that. Alpas
ships with many more such middleware.
Creating Middleware
You create a middleware by extending dev.alpas.Middleware<T>
class and overriding the invoke()
method.
Alternatively, you can use make:middleware
Alpas console command to create a new middleware and put
it under middleware
folder.
$ alpas make:middleware AdminOnlyMiddleware
Let's say this middleware checks if the logged in user is actually an admin. If the checks passes,
it will forward the call. If not, it throws an AuthenticationException
exception.
class AdminOnlyMiddleware : Middleware<HttpCall>() {
override fun invoke(call: HttpCall, forward: Handler<HttpCall>) {
if (call.user.isAdmin()) {
forward(call)
} else {
throw AuthenticationException()
}
}
}
This middleware can then be applied to a route to make sure the route passes through it.
Here's another example of a middleware that just does some logging.
class LogMiddleware : Middleware<HttpCall>() {
val logger: Logger = Logger()
override fun invoke(call: HttpCall, forward: Handler<HttpCall>) {
logger.info(call.referrer)
forward(call)
}
}
As you can see, a middleware doesn't always have to be "active" by taking an action on the call but could be "passive"—doing something and forwarding the call.
Before/After Middleware
Inside of a middleware, you can decide what part of code gets run before a request hits a matching route
and what part of code runs after the request is handled. Any code before forward(call)
call gets
run before and any code after forward(call)
gets called after the call has been handled.
class LogMiddleware : Middleware<HttpCall>() {
val logger: Logger = Logger()
override fun invoke(call: HttpCall, forward: Handler<HttpCall>) {
logger.info("Hello! This runs before matching the route.")
forward(call)
logger.info("This runs after handling the call. Bye!")
}
}
Registering Middleware
Route Entry Middleware
Route entry middleware are invoked for each request once the target route has matched but before invoking the actual route handler. These middleware can be assigned to a route, to a route group, or can be grouped together with a name and then be assigned it to either a route, or a route group.
Server Entry Middleware
Server entry middleware are invoked for each request regardless of whether the intended route matches or not.
You can register a server entry middleware by overriding HttpKernel#serverEntryMiddleware()
method and
adding the middleware to the list passed.
//...
override fun registerServerEntryMiddleware(middleware: MutableList<KClass<out Middleware<HttpCall>>>) {
middleware.add(SecretServerMiddleware::class)
middleware.add(SuperSecretServerMiddleware::class)
}
//...
Appending Middleware
Instead of registering a middleware from inside HttpKernel
's registerServerEntryMiddleware()
method, you
can append a middleware class to [Server Entry Middleware] stack from anywhere by calling append()
method
on an app instance. This makes it very easy to push a middleware from, for an example, a third-party
service provider package.
class AdminServiceProvider : ServiceProvider {
override fun boot(app: Application) {
app.append(AdminMiddleware::class)
}
}
Named Middleware Group
Instead of assigning a list of middleware to a group or to a route, sometimes it is more convenient to make a list of middleware, give it a name, and then assign this name. This allows you to share a middleware group and uniformly apply it to different routes and route groups.
To create a named middleware route, you need to first register your middleware group inside HttpKernel
's
registerRouteMiddlewareGroups()
method with a name and then call middlewareGroup()
on your routes,
or your route groups, by passing the name.
- Register the middleware group
//...
override fun registerRouteMiddlewareGroups(
groups: HashMap<String,
List<KClass<out Middleware<HttpCall>>>>) {
groups["secret"] = listOf(SecretMiddleware::class, SuperSecretMiddleware::class)
}
//...
- Apply the middleware group name
fun Router.addRoutes() {
group("admin/profile") {
// all the middleware defined under key 'secret'
// will be applied to this route
get<AdminProfileController>("secret")
}.middlewareGroup("secret")
group("user/profile") {
// all the middleware defined under key 'secret'
// will be applied to this route as well
get<UserProfileController>("secret")
}.middlewareGroup("secret")
}