node-framework
Version:
node-framework
328 lines (266 loc) • 8.71 kB
JavaScript
/**
* @file index.js
* @author sekiyika (px.pengxing@gmail.com)
* @description
*
*/
var events = require('events');
var util = require('util');
var _ = require('lodash');
var Route = require('./route');
var methods = ['get', 'head', 'post', 'put', 'delete', 'trace', 'options', 'connect', 'patch'];
/**
* get router object
* @param {Application} app
* @returns {*}
*/
module.exports = function (app) {
function Router() {
events.EventEmitter.call(this);
/**
* 保存各种method的route
*
* @type {{}}
*/
this.routes = {};
this._defaultRoutes = {
'/:controller/:action/:id((\\d+))': {
controller: '{controller}',
action: '{action}'
},
'/:controller/:action': {
controller: '{controller}',
action: '{action}'
},
'/:controller': {
controller: '{controller}'
}
};
}
util.inherits(Router, events.EventEmitter);
Router.prototype.bind = function (path, target, method, options) {
if (arguments.length < 2) {
app.logger.warn('Router bind error', arguments);
return false;
}
var args = app.util.detectMethod(path);
path = args.path;
method = method || args.method;
options = options || {};
if (typeof target === 'string' && /^(https?:|\/)/.test(target)) {
// 如果target是字符串,并且是http:开头或者/开头,则认为是跳转
this._bindRedirect(path, target, method, options);
} else if (typeof target === 'function') {
// 如果target是函数,则直接把当前fn添加到该route中
this._bindFunction(path, target, method, options);
} else if (target !== null && typeof target === 'object') {
options = _.merge(options, target);
this._bind(path, method, options);
} else {
app.logger.warn('Unknown type: ' + args.original + ' ' + method);
}
return this;
};
Router.prototype.initialize = function () {
app.logger.dProfile('component:Router:initialize');
var me = this;
methods.forEach(function (method) {
me.routes[method] = [];
});
// 绑定默认的route
Object.keys(me._defaultRoutes).forEach(function (key) {
me.all(key, me._defaultRoutes[key]);
});
// 绑定config中的routes
// config中的routes应该从前往后
var config = app.config.routes;
var keys = Object.keys(config);
var key;
for (var i = keys.length - 1; i >= 0; i--) {
key = keys[i];
me.bind(key, config[key]);
}
app.logger.info('Component Router initialize DONE');
app.logger.dProfile('component:Router:initialize');
};
Router.prototype.all = function (path, target, options) {
var me = this;
methods.forEach(function (method) {
me.bind(path, target, method, options);
});
};
Router.prototype.get = function (path, target, options) {
this.bind(path, target, 'get', options);
};
Router.prototype.post = function () {
this.bind(path, target, 'post', options);
};
Router.prototype.put = function () {
this.bind(path, target, 'put', options);
};
Router.prototype.del = function () {
this.bind(path, target, 'delete', options);
};
/**
* 添加redirect
* @param path
* @param target
* @param method
* @param options
* @private
*/
Router.prototype._bindRedirect = function (path, target, method, options) {
options.redirect = target;
this._bind(path, method, options);
};
/**
* 添加middleware
*
* @param path
* @param target
* @param method
* @param options
* @private
*/
Router.prototype._bindFunction = function (path, target, method, options) {
options = options || {};
options.middlewares = options.middlewares || [];
options.middlewares.push(target);
this._bind(path, method, options);
};
Router.prototype._bind = function (path, method, options) {
var me = this;
if (method === 'all') {
methods.forEach(function (method) {
me._bind(path, method, options);
});
return;
}
if (!me._validate(options)) {
app.logger.warn('Wrong options', path, options);
return me;
}
var fn = me._processPath(path);
var routes = me.routes[method];
var route = routes[fn.name] || new Route();
route.fn = fn;
route.name = fn._name;
route.method = method;
route.options = options;
route.isRegExp = fn.isRegExp || false;
route.regexp = fn.regexp;
if (!routes[fn.name]) {
routes.push(route);
}
return me;
};
/**
* 校验options参数是否正确
*
* @private
*/
Router.prototype._validate = function (options) {
// 如果是静态文件类型的时候,需要有type和dir字段
if (options.type === 'static' && !options.dir && !app.config.global.staticDir) {
return false;
}
return true;
};
/**
* 处理path为function
*
* @param {String} path
* @result {Function}
* @private
*/
Router.prototype._processPath = function (path) {
var fn;
// 匹配当前path是否为正则表达式
var regexpRoute = /^r\|(.*)\|(.*)$/;
var params = [];
var matches = path.match(regexpRoute);
if (matches) {
// path为正则表达式
path = new RegExp(matches[1]);
params = matches[2].split(',');
fn = (function (params) {
return function (req, resp) {
var matches = path.exec(req.pathname);
if (!matches) {
return false;
}
req.params = req.params || {};
params.forEach(function (param, index) {
req.params[param] = matches[index + 1];
});
return true;
}
})(params);
fn.isRegExp = true;
} else {
// 统一用path-to-regexp编译一下
var pathToRegExp = require('path-to-regexp');
path = pathToRegExp(path, params);
fn = (function (params) {
return function (req, resp) {
var matches = path.exec(req.pathname);
if (!matches) {
return false;
}
req.params = req.params || {};
params.forEach(function (param, index) {
req.params[param.name] = matches[index + 1];
});
return true;
}
})(params);
}
// 用正则作为当前fn的name
fn._name = path.toString();
fn.regexp = path;
return fn;
};
/**
* 根据req匹配route
*
* @param {http.ClientRequest} req
* @param {http.ServerResponse} resp
* @returns {*}
*/
Router.prototype.match = function (req, resp) {
var me = this;
var method = req.method.toLowerCase();
var routes = this.routes[method];
// 从最新的route开始往前匹配
var route;
for (var i = routes.length - 1; i >= 0; i--) {
route = routes[i];
if (route.match(req, resp)) {
// 匹配成功
route = _.cloneDeep(route);
return me._wrapRoute(route, req);
}
}
return false;
};
/**
* 把route处理一下
* @private
*/
Router.prototype._wrapRoute = function (route, req) {
var params = req.params;
var keys = Object.keys(params);
var value;
Object.keys(route.options).forEach(function (key) {
value = route.options[key];
if (typeof value === 'string') {
keys.forEach(function (k) {
value = value.replace('{' + k + '}', params[k]);
});
route.options[key] = value;
}
});
return route;
};
return new Router();
};