Middleware

Owl runs every incoming request through a cascade of middleware before it ever reaches your Service's method, the mechanism behind RateLimiter and TypeChecker and one you can also write your own filters for.

Client sends a request

    ├── Global Middleware (Inbound) - defined in Owl.Start()
    ├── Service Middleware (Inbound) - defined in CreateService()
    ├── Remote Middleware (Inbound) - defined in CreateSignal/Property()

    └── Service method executes

A middleware is a function with the signature (plr, args) -> (boolean, ...any):

  • returning true lets the request through to the next middleware and eventually the Service method
  • returning false rejects the request immediately optionally with a message as the second return value, useful for logging why

Because they run in a cascade, the earliest middleware to reject a request is the only one that runs a global rate limiter rejecting a spammed request means your Service-specific middleware (and the method itself) never executes at all.

Global middleware

Defined via Owl.Config or inline in Owl.Start(). Applied to every remote, on every Service.

-- > // Method A: via Owl.Config (before Start)
Owl.Config.GlobalMiddleware = {
    Inbound = {
        Owl.Util.RateLimiter.perPlayer(30, 1, "Global"),
    },
    Outbound = {},
}
 
-- > // Method B: directly inside Owl.Start()
Owl.Start({
    GlobalMiddleware = {
        Inbound = {
            Owl.Util.RateLimiter.perPlayer(30, 1, "Global"),
        },
    },
}):catch(warn)

Reach for global middleware for anything that should apply uniformly no matter which Service or remote is being hit a baseline per-player rate limit being the canonical example, since without one, any single remote you forget to protect individually becomes an open door.

Per-Service middleware

Applied only to a specific Service's remotes, in addition to whatever global middleware already ran.

local SecureService = Owl.CreateService({
    Name = "SecureService",
    Middleware = {
        Inbound = {
            function(plr: Player, args: {any}): (boolean, ...any)
                if plr:GetAttribute("IsBanned") then
                    return false, "Player is banned."
                end
 
                return true
            end,
        },
    },
    Client = { --[[ ... ]] },
})

This is the right level for checks that only make sense for one Service in particular a trading Service checking a ban flag, for instance, has no reason to run on every single remote in the game.

Outbound middleware follows the same signature and cascade, just on the way out useful for logging or transforming a value right before it's sent to the client, rather than filtering what comes in.

Where to go next

  • RateLimiter - the built-in middleware behind Owl.Util.RateLimiter and its full configuration options.
  • TypeChecker - validating a remote's arguments as middleware, instead of by hand at the top of every method.