UNPKG

stick

Version:

JSGI based webapp framework

221 lines (200 loc) 6.9 kB
/** * @fileOverview The `stick` module provides the `Application` class * which is the centerpiece of the Stick framework. * * A Stick Application * is a JSGI application which provides means to compose complex * applications out of modular middleware components. * */ /** * Shortcut to the `stick/helpers` module. */ var helpers = exports.helpers = require("./helpers"); /** * Shortcut to the `stick/middleware` module. */ var stickware = exports.middleware = require("./middleware"); /** * The application object is a JSGI application that wraps a middleware chain. * * When invoked without arguments, the `Application` constructor returns * an application that wraps an `unhandled` middleware that throws an Error * when called. Use `[configure](#Application.prototype.configure)`. * * When invoked with an argument, it is used as initial value for the * application's middleware chain. * * @param {function} nested the nested application (optional) */ var Application = exports.Application = function Application() { var middleware = unhandled, environments = {}, locals = (typeof java !== 'undefined') ? new java.lang.ThreadLocal() : { set: function(value) { this._value = value; }, get: function() { return this._value; }, remove: function() { delete this._value; } }; function app(req) { var oldLocals = locals.get(); try { locals.set({request: req, base: req.scriptName}); return middleware(req); } finally { locals.set(oldLocals); } } /** * Apply one or more middleware factories to this Application. Middleware will * be wrapped around the existing middleware chain, starting with the rightmost * argument. For example, the following invocation of configure on a newly * created Application object: * * app.configure("error", "route"); * * will result in `error(route(unhandled()))` as middleware chain. * @name Application.prototype.configure * @param middleware... one or middleware factories. These can be * defined as module names, imported modules, or middleware factories. * For the middleware that is part of Stick it is sufficient to * use the last part of the module id. e.g. "error" or "route". */ Object.defineProperty(app, "configure", { value: function() { // prepend to existing middleware chain var components = Array.prototype.slice.call(arguments); middleware = wrap(components, middleware); return app; } }); /** * Returns an Application object that shares the middleware chain of the original application, * but can be configured to contain additional middleware that is not shared with the parent application. * * Repeated calls of the env method with the same argument return the same application object. * * @name Application.prototype.env * @param {string} name the environment name */ Object.defineProperty(app, "env", { value: function(name) { if (!environments[name]) { environments[name] = Application(proxy); } return environments[name]; } }); /** * The request object associated with the current thread, or null. * @name Application.prototype.request * @type object */ Object.defineProperty(app, "request", { get: function() { var loc = locals.get(); return loc ? loc.request : null; } }); /** * This application's base URI path as used by the current request, * or null if unknown. * @name Application.prototype.base * @type string */ Object.defineProperty(app, "base", { get: function() { var loc = locals.get(); return loc ? loc.base : null; } }); // resolve middleware helper function resolve(middleware) { if (typeof middleware === "string") { var module = stickware[middleware] || require(middleware); middleware = module.middleware; } else if (Array.isArray(middleware)) { // allow a middleware item to be itself a list of middlewares middleware = middleware.reduceRight(function(inner, outer) { return compose(resolve(outer), resolve(inner)); }); } if (typeof middleware !== "function") { if (middleware && typeof middleware.middleware === "function") { middleware = middleware.middleware; } else { throw new Error("Could not resolve middleware: " + middleware); } } return middleware; } // wrap middleware helper function wrap(middleware, next) { next = next || unhandled; if (!Array.isArray(middleware)) { return compose(resolve(middleware), next); } return middleware.reduceRight(function(inner, outer) { return compose(resolve(outer), inner); }, next); } // return the result of calling outer(inner, app), do some error checking // and set up better toString() methods function compose(outer, inner) { var composed = outer(inner, app); if (typeof composed !== "function") { throw new Error("Expected function as middleware, got " + composed); } var functionToString = Function.prototype.toString; if (composed.toString === functionToString) { var name = composed.name || outer.name || "anonymous"; var innerName = inner.toString === functionToString ? inner.name || "anonymous" : String(inner); composed.toString = function() { return name + " > " + innerName; } } return composed; } function proxy(req) { return middleware(req) } proxy.toString = function() { return app.toString(); }; app.toString = function() { return middleware.toString(); }; // If we got an argument use it as initial app/middleware if (arguments.length) { middleware = helpers.resolveApp(arguments[0]); } return app; }; /** * Unhandled request handler. */ function unhandled(req) { var error = new Error("Unhandled request"); error.notfound = true; throw error; } unhandled.toString = function() { return "unhandled"; }; global.resolve = function(m, name) { if(m.resolve) { // RingoJS return m.resolve(name); } else if(m.filename){ // Common Node var fs = require("fs-base"); return fs.normal(fs.join(fs.directory(m.filename), name)); } throw new Error('Unknown ServerJS platform, cannot resolve'); }