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.

  1. Register the middleware group

//...

override fun registerRouteMiddlewareGroups(
    groups: HashMap<String,
    List<KClass<out Middleware<HttpCall>>>>) {

    groups["secret"] = listOf(SecretMiddleware::class, SuperSecretMiddleware::class)
}

//...

  1. 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")
}