请求路由模块journey的源码解读和学习心得(一)
发布于 4年前 作者 yixuan 2344 次浏览 最后一次编辑是 3年前

journey由cloudhead编写,模块下载地址https://github.com/cloudhead/journey

这几天在看别人的一个web service的实现,看到journey这个模块,感觉这个模块挺实用的,就拿过来仔细学习一下。
这个模块的功能虽然不是很复杂,但却完全按照事件驱动的思想来实现,作为nodejs的学习代码,感觉还是很有帮助的。如果你是在学习事件驱动的设计思想或者对nodejs还不是很熟的话,可以和我一起来了解这个模块。

一个使用journey模块的示例程序https://github.com/indexzero/nodejs-intro/tree/master/lib/01routing

journey模块是一个主要用来路由请求的模块。通过自定义自己的请求路由规则,可以将发往服务器的请求进行不同的处理。
路由的功能我们自己也能实现,为什么要使用journey这个模块呢?
这是因为journey封装了许多路由的细节,只暴露一些对设置规则的接口,所以使用journey模块只需要把注意力集中在路由规则上就可以了。其次,journey还提供了过滤器(filter),可以在服务器路由请求前拦截请求进行处理。

使用:


下面先简单介绍一下journey是如何使用的,代码来源为https://github.com/indexzero/nodejs-intro/tree/master/lib/03authentication
因为只是简单说明使用方式,我删去了细节实现,留下框架
/*

* service.js: Defines the web service for the Pinpoint module.
*
* © 2011 Charlie Robbins
* MIT LICENSE
*
/

var journey = require(‘journey’),

//创建一个新的请求路由
exports.createRouter = function (resource) {
var router = new (journey.Router)({
strict: false,
strictUrls: false,
api: 'basic’,
filter: helpers.auth.basicAuth
});

//说明请求上的公共路径
router.path(//bookmarks/, function () {

//设置过滤器
this.filter(function () {

//把GET请求绑定到相应函数上
this.get().bind(function (res) {});

//满足正则表达式的GET请求绑定在相应函数上
this.get(//([\w|\d|-|_]+)/).bind(function (res, id) { });

//绑定POST请求
this.post().bind(function (res, bookmark) { });

//绑定PUT请求
this.put(//([\w|\d|-|_]+)/).bind(function (res, id, bookmark) {});

//绑定DEL请求
this.del(//([\w|\d|-|_]+)/).bind(function (res, id) { });
});
});

return router;
};

如同上面的代码和注释,journey提供了Router类,只需要实例化Router类并且把各种请求按照代码中的样子和指定函数绑定起来,就可以实现路由的功能了。需要注意的是,Router支持用正则表达式来区分不同的请求,这使得不同请求的处理变得容易。
代码中的filter就是过滤器,过滤器的设置在创建Router对象时设置(有个filter属性,这个属性对应的就是过滤的方法。这个方法的实现需要满足参数要求)

这只是创建请求路由对象和设置路由规则,那如何使用这个路由对象呢,如下面代码:
exports.createServer = function (port, database) {

router = service.createRouter(resource);

var server = http.createServer(function (request, response) {
var body = '’;



var emitter = router.handle(request, body, function (route) {
response.writeHead(route.status, route.headers);
response.end(route.body);
});



})
});

if (port) {
server.listen(port);
}

return server;
};

我们可以看到,代码第9行的router.handle方法,这个方法就是让路由器来处理请求,根据路由规则转发请求的地方。

实现:


知道了如何使用journey模块后,我们将注意力转移到journey是如何实现的。
下面我们来看一下journey.js中核心的几块代码的实现(journey.js的源代码在本文开始第一行提供了下载地址):
journey.Router = function Router(options) {

var that = this;

this.routes = [];
this.options = mixin({}, journey.options, options || {});

if (this.options.extension) {
this.options.extension = this.options.extension.replace('.’, ‘\.’);
}
};

function mixin(target) {
var args = Array.prototype.slice.call(arguments, 1);

args.forEach(function (a) {
var keys = Object.keys(a);
for (var i = 0; i < keys.length; i++) {
target[keys[i]] = a[keys[i]];
}
});
return target;
}

这段代码的作用是在实例化router时候根据传入的参数为router设置各种属性。
其中mixin方法就是用来将用户自定义的参数和router的默认参数结合,将原来默认的属性覆盖,并且加上新的属性

上面是对某个具体的router对象的属性设置,下面着重介绍的是Router的原型,也就是所有Router最共同的部分也是最核心的部分,所以下面讲的所有方法都是Router原型中定义的方法,用于所有的Router类型的对象。
先讲设置路由规则的一些重要函数,代码如下:
journey.Router.prototype = {

//
// Define the routing table
//
map: function (routes) {
// Calls the function in the context of this instance,
// so it can be used to define routes on this.
routes.call(this, this);
},
paths: [],
required: [],

filter: function (/
variable arguments /) {
var args = Array.prototype.slice.call(arguments),
map = (typeof(args[args.length - 1]) === ‘function’) && args.pop(),
filter = args.pop() || this.options.filter;

this.required.push(filter);
map.call(this, this);
this.required.pop();
},

get: function (pattern, opts) { return this.route('GET’, pattern, opts) },
put: function (pattern, opts) { return this.route('PUT’, pattern, opts) },
post: function (pattern, opts) { return this.route('POST’, pattern, opts) },
del: function (pattern, opts) { return this.route('DELETE’, pattern, opts) },

route: function (/
variable arguments /) {
if (arguments[0].headers) { throw new(Error)(“Router#route method renamed to 'handle’”) }

var that = this, route,
args = Array.prototype.slice.call(arguments).filter(function (a) { return a }),
// Defaults
pattern = this.paths.length ? ‘’ : /.
/,
ignoreCase = false,
method = journey.Router.methods.slice(0),
constraints = [],
extension = this.options.extension ? '(?:’ + this.options.extension + ')?’ : '’;

Array.prototype.push.apply(constraints, this.required);

args.forEach(function (arg) {
if (journey.Router.methods.indexOf(arg) !== -1 || Array.isArray(arg)) {
method = arg;
} else if (typeof(arg) === “string” || arg.exec) {
pattern = arg;
} else {
throw new(Error)(“cannot understand route.”);
}
});

if (typeof(pattern) === “string”) {
pattern = escapeRe(pattern);
} else {
// If we’re in a nested path, ‘/i’ doesn’t mean much,
// as we concatinate strings and regexps.
ignoreCase = this.paths.length || pattern.ignoreCase;
pattern = pattern.source;
}
// Trim trailing and duplicate slashes and add ^$ markers
pattern = ‘^’ + this.paths.concat(pattern ? [pattern] : [])
.join(‘/’)
.match(/^^?(.*?)$?$/)[1] // Strip ^ and $
.replace(/^(/|\/)(?!$)/, ‘’) // Strip root / if pattern != ‘/’
.replace(/(/|\/)+/g, ‘/’) + // Squeeze slashes
extension;
pattern += this.options.strictUrls ? ‘$’ : '\/?$’; // Add optional trailing slash if requested
pattern = new(RegExp)(pattern, ignoreCase ? ‘i’ : ‘’);

this.routes.push(route = {
pattern: pattern,
method: Array.isArray(method) ? method : [method],
constraints: constraints
});

return {
bind: function (handler) {
route.handler = handler;
return route;
},
ensure: function (handler) {
route.constraints.push(handler);
return this;
},
filter: function (handler) {
return this.ensure(handler || that.options.filter);
}
};
}
}

1.第5行的map函数的作用是运行路由规则函数(routes),将路由规则设置进路由对象中,值得注意的是第8行的call函数,由于现在是在原型内定义函数,但函数的运行要依托于具体的router对象,所以用call函数将函数routes在this(指向运行时对象)所指对象中运行。将设定路由规则的函数放在指定的对象内运行是map函数唯一的作用。

2.filter方法主要是设置router的过滤函数,在第18,19,20行,18行将过滤器压入栈中,然后运行map方法,map方法运行时就会运行下面的route方法,这个方法是用来生成路由规则对象的,在下面会介绍。这里之所以压入过滤器是因为每个路由规则对象里都有一个数组是用来记录限制条件的,压入过滤器后,在设置生成路由规则对象时就能将栈中包括过滤器等限制条件设置到其限制条件数组中(这个栈可以看作是一个由限制条件组成的数组),这样在得到请求时,检查某个路由规则时,能够通过查看限制数组检查请求是否符合限制条件。

3.get,put,post,del方法传入正则表达式参数,然后调用route方法来设置路由规则。

4.route方法中根据参数定义了一个规则中必须的几个属性,例如40行设置了constraints,61行设置了这个路由规则的符合条件(字符串匹配模式),然后在71行处新建一个路由规则对象,将上述的属性设置给这个对象,然后将这个路由规则对象压到路由对象的路由规则数组中,76行开始的return函数返回了这个规则可以操作的一些方法,比如绑定这个规则到某个处理函数上(bind函数)等等。

综上,路由对象的设置主要是设置各个路由规则,而各个路由规则的设置主要是设置其中的限制数组,路由条件等。然后用路由规则对象提供的bind函数绑定这个路由规则对应的处理函数。

这篇文章介绍的主要就是journey的使用和源码中如何设置路由规则的部分,由于篇幅过长,将journey.js中如何根据路由规则处理请求的部分放在下一篇文章中介绍。

2 回复

看来我这个水平看这个还真有点困难呢

回到顶部