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

在上一篇文章中,介绍了journey的使用和源码中路由规则设置的实现部分。
接下去要介绍的是journey中处理请求的实现部分,源码地址在上一篇文章中。
下面的代码依然是Router类的原型对象中定义的函数,是所有Router类生成的对象所共同拥有的方法,重要代码如下:

    handle: function (request, body, callback) {

var promise = new(events.EventEmitter);
var request = Object.create(request);
var that = this;

request.url = url.parse(request.url);

// Call the router asynchronously, so we can return a promise
process.nextTick(function () {
// Dispatch the HTTP request:
// As the last argument, we send the function to be called when the response is ready
// to be sent back to the client – this allows us to keep our entry and exit point
// in the same spot. outcome is an object with a status, a body and headers
that.dispatch(request, body || "", function (outcome) {


/这里的outcome就是处理的结果,这个函数的功能就是将outcome设置一下,然后调用呈现的函数将这个outcome输出,同时进行日志记录/

});
});
return promise;
}

handle方法是router对象收到请求后调用的方法,其中需要注意的是第14行,第14行调用dispatch方法,dispatch方法主要是检查请求是否符合要求(检查Headers),然后将符合要求的请求进行解析,下面会有解释。dispatch的第三个参数是一个回调函数,当请求处理完返回结果时,这个函数输出请求处理后的结果。

下面是dispatch函数的实现:
    dispatch: function (request, body, respond) {

var route, parser, that = this,
params = querystring.parse(request.url.query || null);

if (! this.verifyHeaders(request)) {
return respond(new(journey.NotAcceptable)(request.headers.accept));
}

this.resolve(request, body, function (err, resolved) {
if (err) {
if (err.status) { // If it’s an HTTP Error
return respond({
headers: err.headers || {},
status: err.status,
body: JSON.stringify(err.body)
});
} else {
throw err;
}
}

route = that.draw(request, respond);

if (resolved) {
if (body) {
parser = /^application/json/.test(
request.headers[“content-type”]
) ? JSON.parse : querystring.parse;

try {
body = parser(body);
} catch (e) {
return respond(new(journey.BadRequest)(“malformed data”));
}

// If the body is an Array, we want to return params as an array,
// else, an object. The mixin function will preserve the type
// of its first parameter.
params = Array.isArray(body) ? mixin(body, params) : mixin(params, body);
}
return route.go(resolved, params);
} else {
return respond(new(journey.NotFound)(“request not found”));
}
});
}

dispatch方法中最重要的是第9行resolve方法的调用,resolve方法用来解析请求。最后一个参数是回调函数,当请求处理完,这个回调函数根据默认设置构造一个路由规则(第22行,这里的路由规则是指通用的一些规则,比如返回文本的类型等),然后运行这个路由规则(第41行),这里运行路由规则会运行这个规则绑定的处理函数。

draw函数我就不具体介绍了,就是返回一个设置好的路由规则,而go方法要介绍一下,这个方法是运行路由规则的关键:
go: function (destination, params) {

this.send = this.responder;

try {
if (that.options.api === ‘http’) {
destination.call(this, this.request, this, params || {});
} else {
destination.call(this, this, params || {});
}
} catch (err) {
this.respond({
body: { error: err.message || err,
stack: err.stack && err.stack.split(‘\n’) },
status: err.status || 500, headers: {}
});
}
}

参数中destination是函数,这个函数会进行一些针对这个请求的设置,然后调用请求对应的处理函数。第6行和第8行就是将destination放在当前调用这个方法的对象this中运行。

下面介绍下之前提到的resolve方法:
    resolve: function (request, body, dispatcher) {

var that = this, allowedMethods = [];
//
// Return the first matching route
//
(function find(routes, callback) {
var route = routes.shift();
if (route) { // While there are still routes to process
that.validateRoute(route, request, body, allowedMethods, function (err, found, method) {
if (err) { dispatcher(err) }
else if (found) { dispatcher(null, found) }
else { find(routes, callback) }
});
} else if (allowedMethods.length) {
dispatcher(new(journey.MethodNotAllowed)(allowedMethods.join(‘,’)));
} else {
dispatcher(null, false);
}
})(this.routes.slice(0));
}

resolve方法调用find方法,find方法就是将路由规则数组里的路由规则对象一个个弹出来,然后检查这个路由规则对象是否满足当下的请求,不满足则递归调用find方法。判断一个路由规则是否满足请求是由第9行的validateRoute方法来确定的,这个函数很重要,是分析寻找路由规则的关键!

下面说明validateRoute方法:
validateRoute: function (route, request, body, allowedMethods, cb) {

// Match the pattern with the url
var match = (function (pattern) {
var path = request.url.pathname;

if (! path) { return new(BadRequest) }

return (path.length > 1 ? path.slice(1) : path).match(pattern);
})(route.pattern);

//
// Return here if no match to avoid potentially expensive
// async constraint operations.
//
if (!Array.isArray(match)) {
return match === null ? cb(null, false) : cb(match);
}

//
// Run through the specified constraints,
// asynchronously making sure everything passes.
//
(function checkConstraints(constraints) {
var constraint = constraints.shift();

if (constraint) {
// If the constraint is a function then expect it to have a method signature:
// asyncConstraint(request, body, callback);
constraint(request, body, function (err) {
if (err) return cb(err);
checkConstraints(constraints);
});
} else {
// If there is no handler for this route, return a new NotImplemented exception
if (! (‘handler’ in route)) { return cb(new(journey.NotImplemented)(“unbound route”)) }

// Otherwise, validate the route method, and return accordingly
if ((route.method.indexOf(request.method) !== -1) || !route.method) {
return cb(null, function (res, params) {
var args = [];
args.push(res);
args.push.apply(args, match.slice(1).map(function (m) {
return /^\d+$/.test(m) ? parseInt(m) : m;
}));
args.push(params);
return route.handler.apply(this, args);
});
} else {
for (var i = 0; i < route.method.length; i++) {
if (allowedMethods.indexOf(route.method[i]) === -1) {
allowedMethods.push(route.method[i]);
}
}
return cb(null, false);
}
}
})(route.constraints.slice(0));
}

1. 第3行的match是某个路由规则匹配请求匹配出来的结果(哪个路由规则呢,看resolve方法中,调用validateRoute之前先弹出了一个路由规则)。

2. 第15行检查match是否是一个数组(match可能是数组是因为路由规则中的条件是正则表达式,可能对某个请求有多种匹配情况),如果不是的话判断是否为null,为null说明这个路由规则不匹配这个请求,直接返回null,重新下一个路由规则的匹配检查,如果不为null,直接调用回到函数。

3. 第23行开始是很重要的地方,也是整个路由请求处理的关键。26到32行都在反复检查所有的限制条件(保存在限制条件数组constraints中),只要有一个限制条件限制住了请求,则返回错误。如果都通过,则会进入else语句,即通过了限制条件检查。

4. 35行开始,先检查路由规则中是否有对应请求的处理函数,如果有,则先压入参数然后调用处理函数,如果没有,则将所有的其他请求处理函数返回(第49行),这样就完成了路由的功能。

到此,整个journey模块都介绍完全了,整个模块内部的逻辑还是有些复杂的,下面是两点心得:
1. 完全的事件驱动有他带来的设计上的好处,但不可否认的是代码的层次变得有些多。
2. 其次javascript语言的灵活性也使得很多功能的实现变得很灵活,整个请求路由的功能在一个js文件中就实现了,包括其中的过滤器,这使得我们看到了nodejs在开发web应用上的潜力。

回到顶部