Middlewares
Why middleware?
In Fano Framework, middleware is an optional software component that is executed before or after request is passed to actual request handler. It is similar concept to firewall, in which, it can pass, block or modify request or response.
For example, middleware allows developer to test if user is logged in before request reaches controller. If user is not logged in, it blocks request. So when controller is executed, developer can be sure that user must be logged in.
One or more middleware instances can be attached to one or more routes. This allows centralized action to be taken for multiple controllers.
So, instead of tedious check each time controller is executed,
function TMy1Controller.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader
) : IResponse;
var sess : ISession;
begin
sess := fSessionMgr[request];
if sess.has('loggedIn') then
begin
result := doSomething1WhenUserLoggedIn(request, response, args);
end else
begin
result := doSomethingWhenUserNotLoggedIn(request, response, args);
end;
end;
...
function TMy2Controller.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader
) : IResponse;
var sess : ISession;
begin
sess := fSessionMgr[request];
if sess.has('loggedIn') then
begin
result := doSomething2WhenUserLoggedIn(request, response, args);
end else
begin
result := doSomethingWhenUserNotLoggedIn(request, response, args);
end;
end;
You create one middleware that will check if user is logged in and attach it to any routes that must be accessible only to logged in user.
function TAuthMiddleware.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
var sess : ISession;
begin
sess := fSessionMgr[request];
if sess.has('loggedIn') then
begin
//user is logged in
result := next.handleRequest(request, response, args);
end else
begin
result := doSomethingWhenUserNotLoggedIn(request, response, args);
end;
end;
Then controllers become more simple as you can assume that when execution reach controller, user must be logged in.
function TMy1Controller.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader
) : IResponse;
begin
result := doSomething1WhenUserLoggedIn(request, response, args);
end;
...
function TMy2Controller.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader
) : IResponse;
begin
result := doSomething2WhenUserLoggedIn(request, response, args);
end;
Read Working with Session for information about session.
Middleware architecture in Fano Framework
Fano Framework use simple chained middleware list. Each middleware can decide whether to pass request to next middleware or block. If middleware blocks a request, it must return response.
In Fano Framework, any class implements IMiddleware
interface can be used as middleware. This interface has one methods handleRequest()
which class must implement.
function handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
request
is current request objectresponse
is current response objectargs
is current route arguments. Read Working with Router for more information about route argument.next
is next middleware to execute
If a middleware should let execution to continue, it must call next
request handler, otherwise execution stops and response that is returned by middleware’s handleRequest()
method will be response what client browser received.
For example, following code will stop execution if request is not AJAX request otherwise it will continue execution.
function TAjaxOnlyMiddleware.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
begin
if (not request.isXhr()) then
begin
response.headers().setHeader('Status', '403 Not Ajax Request');
result := response;
end else
begin
result := next.handleRequest(request, response, args);
end;
end;
You can also raise an exception to stop execution. Fano Framework will handle exception and call apropriate error handler.
function TAjaxOnlyMiddleware.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
begin
if (not request.isXhr()) then
begin
raise EForbidden.create('Not Ajax Request');
end else
begin
result := next.handleRequest(request, response, args);
end;
end;
Type of middlewares
Based on scope
Application middleware
Application middleware is middleware that is attached and applied globally to all routes.
Route middleware
Route middleware is middleware that is attached and applied to one or more specific routes.
Based on order of execution
Before middleware
Before middleware is any middleware that is executed before controller execution. This is mostly type of middleware that can be used to modify request or to act as gate that block or pass request.
function TMyMiddleware.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
begin
doSomething();
result := next.handleRequest(request, response, args);
end;
After middleware
After middleware is any middleware that is executed after controller execution.
function TMyMiddleware.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
begin
result := next.handleRequest(request, response, args);
doSomething();
end;
Before and after middleware
Combination of both, before and after,
function TMyMiddleware.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
begin
doSomething1();
result := next.handleRequest(request, response, args);
doSomething2();
end;
Creating middleware
var ajaxOnly : IMiddleware;
authOnly : IMiddleware;
...
ajaxOnly := TAjaxOnlyMiddleware.create();
authOnly := TAuthOnlyMiddleware.create();
Attaching middleware to application middleware
When you use IDispatcher
implementation which support middlewares, such as TDispatcher
class, you are required to setup one global IMiddlewareLinkList
instances which stores list of middlewares applied globally to all routes. Read Dispatcher for more information.
As shown in following code
{-----------------------------------------------
register middleware list for application
------------------------------------------------}
container.add('appMiddlewares', TMiddlewareListFactory.create());
In you dispatcher initialization, you need to set global middlewares collection to use by dispatcher instance.
{-----------------------------------------------
setup basic application request dispatcher
which support middleware
------------------------------------------------}
container.add(
'dispatcher',
TDispatcherFactory.create(
container.get('appMiddlewares') as IMiddlewareLinkList,
aRouterInst as IRouteMatcher,
TRequestResponseFactory.create()
)
);
and then you can register a middleware to application middlewares as follows
var appMiddlewares : IMiddlewareList;
...
appMiddlewares := container.get('appMiddlewares') as IMiddlewareList;
appMiddlewares.add(authOnly);
If you do not need application middleware list, you can use null class as shown in following code,
container.add('appMiddlewares', TNullMiddlewareListFactory.create());
This will create TNullMiddlewareList
class instance which basically does nothing.
Creating project with middleware support with Fano CLI
When creating project with Fano CLI, you can automate task to setup middleware support with --with-middleware
command line argument. Read Add middleware support section for more information.
Creating middleware with Fano CLI
Fano CLI provides middleware creation command --middleware
to simplify task for creating and setting up middleware with dependency container. Read Creating Middleware section for more information.
Attaching middleware to route
When you register the controller to route, you can add middleware as shown in following code
router.get(
'/hi/{name}',
hiController
).add(authOnly);
router.post(
'/hi/{name}',
hiController
).add(ajaxOnly)
.add(authOnly);
Built-in middlewares
Fano Framework provides several built-in middlewares.
TNullMiddleware
, middleware class that does nothing and just pass the request.TCompositeMiddleware
, middleware class which group several middlewares as one.TRequestHandlerAsMiddleware
, adapter middleware which can turn request handler as a middleware.TCorsMiddleware
, middleware class which adds CORS response header. Read Handling CORS for more information.TCsrfMiddleware
, middleware class which adds CSRF protection. Read Cross-Site Request Forgery (CSRF) for more information.TValidationMiddleware
, middleware class which validate request. Read Form Validation for more information.TJsonContentTypeMiddleware
, middleware class which handle request withapplication/json
in its header. For more information, read Handling request with JSON body.TCacheControlMiddleware
, middleware class which addsCache-Control
response header. For more information, read Http cache header.TNoCacheMiddleware
, middleware class which addsCache-Control
response header to prevent browser from caching response.TStaticFilesMiddleware
, middleware class which serves static files. For more information, read Serving static files.TThrottleMiddleware
, middleware class which limits rate of request to one or more routes.TFuncMiddleware
, adapter middleware class which allow function be used as middleware.TMethodMiddleware
, adapter middleware class which allow class method be used as middleware.
Group several middlewares as one
For example if you have following route registration which each of routes is using same middlewares
router.get(
'/hello/{name}',
helloController
).add(cors)
.add(authOnly)
.add(ajaxOnly);
router.get(
'/hi/{name}',
hiController
).add(cors)
.add(authOnly)
.add(ajaxOnly);
You can simplify it to become
router.get(
'/hello/{name}',
helloController
).add(corsAuthAjax);
router.get(
'/hi/{name}',
hiController
).add(corsAuthAjax);
where corsAuthAjax
middleware is defined as follows
var corsAuthAjax : IMiddleware;
...
corsAuthAjax := TCompositeMiddleware.create([cors, authOnly, ajaxOnly]);
Use controller as a middleware
TRequestHandlerAsMiddleware
is an adapter middleware that allows instance of IRequestHandler
interface to be used as IMiddleware
. It means you can wrap a controller instance and attach it to a route and to be executed as part of middleware chain. However, controller instance used this way can not block request but only can modify request and/or response because TRequestHandlerAsMiddleware
class always passes request to next middleware.
var helloCtrlMiddleware : IMiddleware;
helloController : IRequestHandler;
...
helloCtrlMiddleware := TRequestHandlerAsMiddleware.create(helloController);
Use function or method as a middleware
TFuncMiddleware
and TMethodMiddleware
are adapter middlewares that allows function and method of class be used as middleware. It means you can wrap a function and attach it to a route and to be executed as part of middleware chain.
For example, if you have following code
unit mymiddleware;
interface
uses
fano;
function myAjax(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
implementation
function myAjax(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader;
const next : IRequestHandler
) : IResponse;
begin
if (not request.isXhr()) then
begin
response.headers().setHeader('Status', '403 Not Ajax Request');
result := response;
end else
begin
result := next.handleRequest(request, response, args);
end;
end;
end.
You can use myAjax()
as middleware as shown below.
router.get(
'/hello/{name}',
helloController
).add(TFuncMiddleware.create(@myMiddleware.myAjax));
Middleware issues
Middleware is not being called
When you attach middleware to a route but middleware is not being called, make sure you use dispatcher implementation which supports middleware. Middleware support is not enabled by default.
Performance consideration
Using middleware adds overhead as request must go multiple execution points before reaching actual request handler. Also, you need to aware that each middleware will call next middleware recursively so you should limit number of middlewares in use.