bricks is very modular, and provides a great deal of flexibility. Almost every aspect of
bricks can be modified, giving you an extremely powerful application framework.
Routes separation is an important concept within bricks. Unlike other application frameworks,
routing is broken into four sections; each section has its own set of routes that can be run,
each with different capabilities. Route separation allows for multiple matches to the same path to be
executed at different points during the request.
Routes are always traversed in the same order: pre, main, post,
and final. Unless specifically skipped over by calling response.final(),
all routes will be run sequentially. A section can be specified while adding a
route. If you do not specify a section, the route will be
added to the main route.
appserver.addRoute("/static/.+", appServer.plugins.filehandler, { section: "main" });
appserver.addRoute(".+", appServer.plugins.loghandler, { section: "final" });
The first route is the pre route. The pre route is responsible
for initial request and response manipulation, including but not limited to cookies, session handling,
loading a user, aliasing, and redirects.
This is the main route. It is default route, and is responsible for normal
application runs. This is the route that should be used for normal client output.
The post route is responsible for final manipulation of the output before data is sent to
the requesting client. This is where final headers can be set, and manipulation such as replacing text,
or gzip occurs.
Once the post route has been run, the buffers are written to the requesting
client, and response.write() will throw an exception if written to.
The final route is responsible for request and response accounting. This is route
is read-only, and should be used for any logging or accounting that needs to be completed.
A route is added to the bricks appserver via appserver.addRoute().
appserver.addRoute("/static/.+", appServer.plugins.filehandler, { basedir: "./htdocs" });
appserver.addRoute(".+", appServer.plugins.fourohfour);
Route matching occurs in the order that routes have been added. Decisions whether each
route will be executed is based on a simple true or false
truth value. Matching only occurs on the path, and does not take into account URL
parameters.
String matching occurs using String.match(). Either static strings or
regular expressions can be used to match a route.
appserver.addRoute("/ajax/api", apiHandler);
appserver.addRoute("^/index\.(htm|html)$", indexHandler);
Matches may also occur with regular expressions.
var re = new RegExp("^/index\.(htm|html)$");
appserver.addRoute(re, indexHandler);
Function matches also work on truth values.
function checkPost(path, request) {
if (path === '/api/user' && request.method === 'GET') {
return true;
}
return false;
}
By default, a route is checked against the defaultRouteChecker(). It
is possible to override this at application server setup. With this you can completely change
how routes are matched. This does not change the number of routes,
nor the order that they are executed.
function alternateCheckRoute(route, path, request) {
if (typeof(route) === 'function') {
var match = route(path);
if (match) {
return true;
}
} else {
if ((typeof(route) === 'string') && path.match(route)) {
return true;
}
}
return false;
}
var appserver = new bricks.appserver({ checkRoute: alternateCheckRoute });
Once a route has been matched, execution occurs. Execution can be against either
a function or a plugin.
appserver.addRoute("/static/.+", appserver.plugins.fileserver);
appserver.addRoute(".+", function (request, response) { response.write("Hello World"); });
When a function is added to the route, it is executed directly once the
route is determined as matched. A function is passed the request,
response and any options passed during route addition.
function hello(request, response, options) {
response.write("Hello World");
response.end();
}
appserver.addRoute("/hello", hello);
Plugins are a little more advanced than using a function. There is an initialization
function, as well as a plugin function. This allows for more advanced
stateful operation. Upon addition the the route, the init function is
executed. Upon route execution, the plugin function is executed.
There are a small number of built-in plugins that are shipped by default with
bricks. These plugins are meant to provide basic functionality to the
application server. Built-in plugins are available as part of the
application server.
appserver.addRoute(".+", appserver.plugins.filehandler);
The filehandler plugin serves up static files from the filesystem in a very
basic manner. The filehandler options include the basedir
which defaults to ./. There is no caching, or any advanced features with this
plugin.
appserver.addRoute("/static/.+", appserver.plugins.filehandler, { basedir: "./htdocs" });
The fourohfour plugin is a very basic 404 handler. It simply sets
the response.statusCode() to 404, and returns 404 Error
to the requesting client. The fourohfour plugin is typically added at the end
of the main route as a catch-all.
The loghandler is a
Common Log Format (with referrer and user-agent) logging handler.
The filename option sets the output file for logging. The loghandler
plugin is typically added as part of the final route. The loghandler
calls response.next() so is safe to add at the beginning of the final route.
appserver.addRoute(".+", appserver.plugins.loghandler, { section: "final", filename: "access.log" });
The request plugin adds a little bit extra to the request object to better
deal with POST, PUT, parameters, and the body.
appserver.addRoute("^/admin/.+", appserver.plugins.request, { section: "pre" });
function matchedRoute (request, response) {
console.log("param foo: " + request.param('foo'));
console.log("body = " + request.body);
response.end();
}
appserver.addRoute("^/admin/.+", matchedRoute);
The redirect handler plugin handles temporary and permanent redirects. The
redirect handler uses the same defaultRouteChecker() as the appserver
to match paths. By default the redirects are considered temporary
and response.statusCode() is set to 307. If the redirect is marked
permanent, then the response.statusCode() is set to 301. The
redirect handler uses options to set up its redirects. The redirect handler
should be added as the first entry of the pre route.
var redirects = {
section: "pre",
routes: [
{
path: "^/bar$",
url: 'http://foo.com/bar'
},
{
path: "^/foo$",
url: 'http://bar.com/foo',
permanent: true
}
]
};
appserver.addRoute(".+", appserver.plugins.redirecthandler, redirects);
The sessionhandler handles basic sessions and should be added to the
beginning of the pre route. The sessionhandler exposes the session
via request.session. By default it sets a cookie and stores
all session data in memory.
var options = {
section: "pre",
session: "choclatechip", // session name, defaults to 'bricks-session'
sessionStore: myStorage // defaults to in-memory session storage
};
appserver.addRoute("/secured/.+", plugins.sessionhandler, options);
appserver.addRoute("/secured/test", function(request, response) { request.session.secured = true; });
Plugin development is simple. There are two functions for functionality:
exports.init and exports.plugin. The init function
is called when added to the route with any options passed for
configuration.
The plugin function handles execution when the route has been
matched.
var hits;
exports.init = function (options) {
options = options || { };
hits = options.start || 0;
}
exports.plugin = function (request, response, options) {
response.write(hits + " hits so far.");
}
Flow control is very straightforward. Each matching route is executed in order until
either completion of the route or response.end() has been called. All
flow control is available via response.
This continues the running of the routes until the last route is reached.
Flow is passed on to the next matching route.
The response.end() call immediately ends the current route, and the next
route begins (if applicable).
Calling response.final() immediately halts execution of further routes,
and begins execution of the final route. This is a very powerful call, as it has
the ability to circumvent any further route matches that may need to execute
response.write(). It is recommended that this function be used with
caution.
Events play an important part of bricks. Events allow for asynchronous actions to be
executed under various circumstances, including the completion of sections and any
errors.
Event listeners can be set on response to listen for the completion of
sections. Each event will emit its own event for the end of each
route. Events can have more than one listener, and can be responded
to more than once.
function mainCompletion(event, response) {
response.write("Main Completion");
}
response.on('main.complete', mainCompletion);
Upon completion of the pre route the pre.complete event is fired.
Upon completion of the main route the main.complete event is fired.
Upon completion of the post route the post.complete event is fired.
Upon completion of the final route the final.complete event is fired.
Error events are fired upon error. There are two error events,
the run.fatal event, and the route.fatal event. These events
are triggered when an error occurs either during the route match, or during
a route execution. Unlike route completion, these events require
the registration of an error handler on the appserver itself.
appserver.addEventHandler('route.fatal', function (error) { console.log("FATAL: " + error) });