kopia lustrzana https://github.com/c9/core
286 wiersze
8.4 KiB
JavaScript
286 wiersze
8.4 KiB
JavaScript
"use strict";
|
|
|
|
var url = require("url");
|
|
var Types = require("./types").Types;
|
|
var Route = require("./route");
|
|
var flatten = require("./utils").flatten;
|
|
|
|
module.exports = function Section(name, description, types) {
|
|
|
|
types = types || new Types();
|
|
var routes = [];
|
|
var sections = {};
|
|
var self = this;
|
|
|
|
this.middlewares = [];
|
|
this.globals = [];
|
|
this.errorHandlers = [];
|
|
this.name = name;
|
|
|
|
var methods = ["head", "get", "put", "post", "delete", "patch", "upgrade", "options"];
|
|
methods.forEach(function(method) {
|
|
routes[method] = [];
|
|
|
|
self[method] = (function(method, route, options, handler) {
|
|
// options is optional
|
|
if (typeof options == "function" || Array.isArray(options)) {
|
|
handler = options;
|
|
options = {};
|
|
}
|
|
options.method = method;
|
|
return self._route(route, options, handler);
|
|
}).bind(self, method);
|
|
});
|
|
this.del = this["delete"];
|
|
|
|
this.registerType = types.register.bind(types);
|
|
|
|
this.all = function(method, route, options, handler) {
|
|
var self = this;
|
|
var args = arguments;
|
|
|
|
methods.forEach(function(method) {
|
|
self[method].apply(self, args);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Insert global middleware(s) for this section: The parent section is
|
|
* always an empty container that delegates to it's sub-sections, and
|
|
* executes all the middelwares for that section.
|
|
*
|
|
* This method allows us to inject a "global" middleware that is inserted
|
|
* *after* decodeParams and *before* all other middelwares.
|
|
*/
|
|
this.global = function( middleware ){
|
|
this.globals.push.apply(this.globals, flatten(middleware));
|
|
};
|
|
|
|
this.use = function(middleware) {
|
|
this.middlewares.push.apply(this.middlewares, flatten(middleware));
|
|
};
|
|
|
|
this.error = function(middleware) {
|
|
this.errorHandlers.push.apply(this.errorHandlers, flatten(middleware));
|
|
};
|
|
|
|
this._route = function(route, options, handler) {
|
|
route = new Route(route, options, handler, types);
|
|
route.parent = this;
|
|
routes[route.method].push(route);
|
|
return this;
|
|
};
|
|
|
|
this.section = function(name, description) {
|
|
var section = new Section(name, description, types);
|
|
section.parent = this;
|
|
if (!sections[name])
|
|
sections[name] = [];
|
|
|
|
sections[name].push(section);
|
|
return section;
|
|
};
|
|
|
|
this.getRoutes = function(){
|
|
return routes;
|
|
};
|
|
|
|
/**
|
|
* Mount an existing section on this instance.
|
|
*
|
|
* Note that when the first argument is omitted, the section's routes will
|
|
* be copied onto this instance's routing table.
|
|
*
|
|
* @param {string} mountpoint Where to mount this secion, e.g. "/capture/"
|
|
* @param {Section} section A frontdoor section
|
|
*/
|
|
|
|
this.mount = function(name, section) {
|
|
var that = this;
|
|
|
|
if (arguments.length == 1) {
|
|
section = arguments[0];
|
|
|
|
if (!(section instanceof Section))
|
|
throw new Error("Single argument to mount must be a Section!");
|
|
|
|
var addRoutes = section.getRoutes();
|
|
|
|
Object.keys(addRoutes).forEach(function(method) {
|
|
var r = addRoutes[method];
|
|
r.forEach(function(route) {
|
|
route.parent = that;
|
|
})
|
|
routes[method] = [].concat(routes[method], r);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
if (!sections[name])
|
|
sections[name] = [];
|
|
|
|
section.parent = this;
|
|
sections[name].push(section);
|
|
};
|
|
|
|
/**
|
|
* Sortcut for client-side routing without a `next`
|
|
*
|
|
* Allows you to omit the third argument, "next" and avoid special
|
|
* treatment in router.js (see "wrapHandler").
|
|
*
|
|
* Has a variable number of argumnets: no need to wrap handlers in an array
|
|
*/
|
|
this.on = function(path, handler){
|
|
var middlewares = Array.prototype.slice.call(arguments, 1);
|
|
handler = middlewares.shift();
|
|
|
|
middlewares.unshift(function(req, res, next) {
|
|
handler(req, res, next);
|
|
});
|
|
|
|
this._route(path, {
|
|
method: "get"
|
|
}, middlewares);
|
|
};
|
|
|
|
this._rootHandler = function(req, res) {
|
|
this.handle(req, res, function(err) {
|
|
if (err) {
|
|
res.statusCode = err.code || err.statusCode || 500;
|
|
res.end(err.message || err.toString);
|
|
} else {
|
|
res.statusCode = 404;
|
|
res.end("cannot " + req.method);
|
|
}
|
|
});
|
|
};
|
|
|
|
this.handle = (function(path, req, res, next) {
|
|
var that = this;
|
|
|
|
if (arguments.length == 2) {
|
|
return this._rootHandler.apply(this, arguments);
|
|
}
|
|
|
|
if (arguments.length === 3) {
|
|
req = arguments[0];
|
|
res = arguments[1];
|
|
next = arguments[2];
|
|
|
|
if (!req.parsedUrl)
|
|
req.parsedUrl = url.parse(req.url, true);
|
|
|
|
path = req.parsedUrl.pathname;
|
|
}
|
|
|
|
req.params = req.params || {};
|
|
|
|
var method = req.method.toLowerCase();
|
|
if (methods.indexOf(method) == -1)
|
|
return next();
|
|
|
|
var handler = this.match(req, path, method);
|
|
if (!handler)
|
|
return next();
|
|
|
|
var middleware = [];
|
|
var errorHandlers = [];
|
|
while (handler) {
|
|
middleware.unshift.apply(middleware, handler.middlewares || []);
|
|
errorHandlers.unshift.apply(errorHandlers, handler.errorHandlers || []);
|
|
handler = handler.parent;
|
|
}
|
|
|
|
if (this.globals.length)
|
|
middleware.splice.apply( middleware, [1, 0].concat( this.globals ) );
|
|
|
|
var i = 0;
|
|
function processNext() {
|
|
handler = middleware[i++];
|
|
if (!handler || i > middleware.length)
|
|
return next();
|
|
|
|
handler(req, res, function(err) {
|
|
if (err)
|
|
return that.handleError(errorHandlers, err, req, res, next);
|
|
|
|
processNext();
|
|
});
|
|
}
|
|
|
|
processNext();
|
|
}).bind(this);
|
|
|
|
this.handleError = function (errorHandlers, err, req, res, next) {
|
|
var i = 0;
|
|
function processNext(err) {
|
|
if (!err)
|
|
return next();
|
|
|
|
var handler = errorHandlers[i++];
|
|
if (!handler || i > errorHandlers.length)
|
|
return next(err);
|
|
|
|
handler(err, req, res, processNext);
|
|
}
|
|
|
|
processNext(err);
|
|
};
|
|
|
|
this.match = function(req, path, method) {
|
|
var splitPath = path.split("/");
|
|
if (!splitPath[0])
|
|
splitPath.shift();
|
|
|
|
if (splitPath.length && sections.hasOwnProperty(splitPath[0])) {
|
|
var section = sections[splitPath[0]];
|
|
if (section && section.length) {
|
|
var subPath = "/" + splitPath.slice(1).join("/");
|
|
for (var i = 0; i < section.length; i++) {
|
|
var handler = section[i].match(req, subPath, method);
|
|
if (handler)
|
|
return handler;
|
|
}
|
|
}
|
|
}
|
|
|
|
var methodRoutes = routes[method];
|
|
for (var i = 0; i < methodRoutes.length; i++) {
|
|
var route = methodRoutes[i];
|
|
if (route.match(req, path))
|
|
return route;
|
|
}
|
|
};
|
|
|
|
this.describe = function() {
|
|
// sections and routes
|
|
var api = {};
|
|
|
|
if (name)
|
|
api.name = name;
|
|
|
|
if (description)
|
|
api.description = description;
|
|
api.sections = [];
|
|
for (var key in sections) {
|
|
for (var i=0; i < sections[key].length; i++) {
|
|
api.sections.push(sections[key][i].describe());
|
|
}
|
|
}
|
|
if (!api.sections.length)
|
|
delete api.sections;
|
|
|
|
api.routes = [];
|
|
for (var method in routes) {
|
|
for (var i=0; i < routes[method].length; i++) {
|
|
api.routes.push(routes[method][i].describe());
|
|
}
|
|
}
|
|
if (!api.routes.length)
|
|
delete api.routes;
|
|
|
|
return api;
|
|
};
|
|
}; |