boxlang-interceptors
BoxLang Interceptors
Overview
BoxLang interceptors provide Aspect-Oriented Programming (AOP) capabilities through an event-driven announcement system. Interceptors listen to named events and execute cross-cutting logic — logging, security, caching, validation — without coupling that logic to business code. Priority controls execution order; async announcements run in background threads.
Creating an Interceptor
/**
* interceptors/RequestLogger.bx
*/
class {
function onRequestStart( struct interceptData ) {
request.startTime = getTickCount()
writeLog( text: "Request started: #cgi.script_name#", type: "information" )
}
function onRequestEnd( struct interceptData ) {
var duration = getTickCount() - request.startTime
writeLog( text: "Completed in #duration#ms: #cgi.script_name#", type: "information" )
}
}
Registering Interceptors
Application.bx (application-scoped)
class {
this.name = "MyApp"
this.interceptors = [
{ class: "interceptors.RequestLogger" },
{ class: "interceptors.SecurityCheck", priority: 1 },
{ class: "interceptors.CacheWarmer", priority: 10 }
]
function onApplicationStart() {
announce( "onApplicationStart", { timestamp: now() } )
}
}
Programmatic Registration (BIF)
// Register at runtime using BoxRegisterInterceptor()
BoxRegisterInterceptor( "interceptors.RequestLogger" )
// With named options
BoxRegisterInterceptor(
class: "interceptors.SecurityCheck",
priority: 1,
properties: { secret: getSetting( "JWT_SECRET" ) }
)
Announcing Events
// Synchronous — all listeners execute before returning
announce( "userCreated", {
userID: newUser.id,
email: newUser.email,
timestamp: now()
} )
// Asynchronous — runs in a background thread
announceAsync( "emailQueued", {
to: user.email,
subject: "Welcome"
} )
Common Interceptor Patterns
Pre / Post Operation Hooks
class DataSanitizer {
/** Sanitize data before save */
function preDataSave( interceptData ) {
var data = interceptData.data
data.each( ( key, value ) -> {
if ( isSimpleValue( value ) ) data[key] = trim( value )
} )
if ( data.keyExists( "description" ) ) {
data.description = htmlEditFormat( data.description )
}
}
/** Clear cache after save */
function postDataSave( interceptData ) {
cacheRemove( "#interceptData.entityName#_#interceptData.entityID#" )
}
}
Validation Guard
class ValidationInterceptor {
property name="validationService" inject="ValidationService"
function preEntitySave( interceptData ) {
var result = validationService.validate( interceptData.entity )
if ( result.hasErrors() ) {
interceptData.abortProcessing = true
throw(
type: "ValidationException",
message: "Validation failed",
detail: serializeJSON( result.getAllErrors() )
)
}
}
}
Security Guard
class SecurityInterceptor {
property name="authService" inject="AuthService"
function preHandler( interceptData ) {
// Skip public areas
if ( listFindNoCase( "home,auth", interceptData.handler ) ) return
if ( !authService.isLoggedIn() ) {
interceptData.event.overrideEvent( "auth.login" )
interceptData.abortProcessing = true
}
}
}
Async Background Processing
class AsyncProcessor {
/** Kick off work asynchronously */
function onEmailQueued( interceptData ) {
announceAsync( "processEmail", {
to: interceptData.to,
subject: interceptData.subject,
body: interceptData.body
} )
}
/** Worker — runs in background thread */
function onProcessEmail( interceptData ) {
mailService.send(
to: interceptData.to,
subject: interceptData.subject,
body: interceptData.body
)
}
}
Chain of Responsibility with Priority
// Lower number = runs earlier
this.interceptors = [
{ class: "interceptors.RequestLogger", priority: 1 },
{ class: "interceptors.AuthCheck", priority: 2 },
{ class: "interceptors.RateLimiter", priority: 3 }
]
Interceptor State
class RequestTracker {
variables.requestMap = {}
function onRequestStart( interceptData ) {
var id = createUUID()
variables.requestMap[id] = { startTime: getTickCount(), path: cgi.script_name }
interceptData.requestID = id
}
function onRequestEnd( interceptData ) {
var id = interceptData.requestID
var data = variables.requestMap[id]
writeLog( "Request #id# done in #getTickCount() - data.startTime#ms" )
structDelete( variables.requestMap, id )
}
}
Module Registration
Modules register interceptors in ModuleConfig.bx:
// ModuleConfig.bx
class {
function configure() {
interceptors = [
{ class: "#moduleMapping#.interceptors.MyInterceptor", priority: 5 }
]
}
}
Best Practices
- Single responsibility — one interceptor, one concern
- Keep interceptors fast — move slow work to
announceAsync() - Always
try/catchinside handlers — a thrown exception insideonUserCreatedshould never break user creation - Conditional early return — skip irrelevant entities or events at the top of the method
- Priority — register security interceptors at priority 1 (lowest number), logging at high numbers
- Avoid recursion — never announce the same event you are listening to
// ✅ Fast: log then hand off heavy work
function onUserCreated( interceptData ) {
writeLog( "User created: #interceptData.email#" )
announceAsync( "sendWelcomeEmail", interceptData )
}
// ✅ Guard with early return
function onDataSave( interceptData ) {
if ( interceptData.entityName != "User" ) return
// user-specific logic
}
// ✅ Error-safe side effects
function onUserCreated( interceptData ) {
try {
sendEmail( interceptData.email )
} catch ( any e ) {
writeLog( "Email error: #e.message#" )
// Do NOT rethrow — don't break user creation
}
}
// ❌ Recursion: triggers same interceptor again
function onDataSave( interceptData ) {
announce( "onDataSave", data ) // infinite loop
}
More from ortus-boxlang/skills
boxlang-functional-programming
Use this skill when working with BoxLang lambdas, closures, arrow functions, higher-order functions, functional array/struct pipelines (map, filter, reduce, flatMap, groupBy, etc.), destructuring, or spread syntax.
10boxlang-code-reviewer
Use this skill when reviewing BoxLang code for quality, correctness, security vulnerabilities, performance issues, style violations, or when providing structured code review feedback following BoxLang best practices and security guidelines.
9boxlang-best-practices
Use this skill when writing, reviewing, or improving BoxLang code to ensure it follows community best practices for naming, structure, scoping, error handling, performance, and maintainability.
9boxlang-classes-and-oop
Use this skill when writing BoxLang classes, components, interfaces, inheritance hierarchies, annotations, properties, constructors, or applying object-oriented design patterns in BoxLang.
9boxlang-web-development
Use this skill when building BoxLang web applications: Application.bx lifecycle, request/response handling, sessions, forms, REST APIs, HTTP clients, routing, CSRF protection, Server-Sent Events, or configuring CommandBox/MiniServer.
8boxlang-configuration
Use this skill when configuring BoxLang runtime settings via boxlang.json, setting environment variables for config overrides, configuring datasources, caches, executors, modules, logging, security, or schedulers — or when helping someone understand the BoxLang configuration system.
8