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

在上一篇文章中,介绍了journey的使用和源码中路由规则设置的实现部分。 <br/>接下去要介绍的是journey中处理请求的实现部分,源码地址在上一篇文章中。 <br/>下面的代码依然是Router类的原型对象中定义的函数,是所有Router类生成的对象所共同拥有的方法,重要代码如下: <br/><pre escaped=“true” lang=“javascript” line=“1”> handle: function (request, body, callback) { <br/> var promise = new(events.EventEmitter); <br/> var request = Object.create(request); <br/> var that = this; <br/> <br/> request.url = url.parse(request.url); <br/> <br/> // Call the router asynchronously, so we can return a promise <br/> process.nextTick(function () { <br/> // Dispatch the HTTP request: <br/> // As the last argument, we send the function to be called when the response is ready <br/> // to be sent back to the client – this allows us to keep our entry and exit point <br/> // in the same spot. outcome is an object with a status, a body and headers <br/> that.dispatch(request, body || “”, function (outcome) { <br/>
<br/>/这里的outcome就是处理的结果,这个函数的功能就是将outcome设置一下,然后调用呈现的函数将这个outcome输出,同时进行日志记录/ <br/> <br/> }); <br/> }); <br/> return promise; <br/> }</pre> <br/>handle方法是router对象收到请求后调用的方法,其中需要注意的是第14行,第14行调用dispatch方法,dispatch方法主要是检查请求是否符合要求(检查Headers),然后将符合要求的请求进行解析,下面会有解释。dispatch的第三个参数是一个回调函数,当请求处理完返回结果时,这个函数输出请求处理后的结果。 <br/> <br/>下面是dispatch函数的实现: <br/><pre escaped=“true” lang=“javascript” line=“1”> dispatch: function (request, body, respond) { <br/> var route, parser, that = this, <br/> params = querystring.parse(request.url.query || null); <br/> <br/> if (! this.verifyHeaders(request)) { <br/> return respond(new(journey.NotAcceptable)(request.headers.accept)); <br/> } <br/> <br/> this.resolve(request, body, function (err, resolved) { <br/> if (err) { <br/> if (err.status) { // If it’s an HTTP Error <br/> return respond({ <br/> headers: err.headers || {}, <br/> status: err.status, <br/> body: JSON.stringify(err.body) <br/> }); <br/> } else { <br/> throw err; <br/> } <br/> } <br/> <br/> route = that.draw(request, respond); <br/> <br/> if (resolved) { <br/> if (body) { <br/> parser = /^application/json/.test( <br/> request.headers[“content-type”] <br/> ) ? JSON.parse : querystring.parse; <br/> <br/> try { <br/> body = parser(body); <br/> } catch (e) { <br/> return respond(new(journey.BadRequest)(“malformed data”)); <br/> } <br/> <br/> // If the body is an Array, we want to return params as an array, <br/> // else, an object. The mixin function will preserve the type <br/> // of its first parameter. <br/> params = Array.isArray(body) ? mixin(body, params) : mixin(params, body); <br/> } <br/> return route.go(resolved, params); <br/> } else { <br/> return respond(new(journey.NotFound)(“request not found”)); <br/> } <br/> }); <br/> }</pre> <br/>dispatch方法中最重要的是第9行resolve方法的调用,resolve方法用来解析请求。最后一个参数是回调函数,当请求处理完,这个回调函数根据默认设置构造一个路由规则(第22行,这里的路由规则是指通用的一些规则,比如返回文本的类型等),然后运行这个路由规则(第41行),这里运行路由规则会运行这个规则绑定的处理函数。 <br/> <br/>draw函数我就不具体介绍了,就是返回一个设置好的路由规则,而go方法要介绍一下,这个方法是运行路由规则的关键: <br/><pre escaped=“true” lang=“javascript” line=“1”>go: function (destination, params) { <br/> this.send = this.responder; <br/> <br/> try { <br/> if (that.options.api === ‘http’) { <br/> destination.call(this, this.request, this, params || {}); <br/> } else { <br/> destination.call(this, this, params || {}); <br/> } <br/> } catch (err) { <br/> this.respond({ <br/> body: { error: err.message || err, <br/> stack: err.stack && err.stack.split(’\n’) }, <br/> status: err.status || 500, headers: {} <br/> }); <br/> } <br/>}</pre> <br/>参数中destination是函数,这个函数会进行一些针对这个请求的设置,然后调用请求对应的处理函数。第6行和第8行就是将destination放在当前调用这个方法的对象this中运行。 <br/> <br/>下面介绍下之前提到的resolve方法: <br/><pre escaped=“true” lang=“javascript” line=“1”> resolve: function (request, body, dispatcher) { <br/> var that = this, allowedMethods = []; <br/> // <br/> // Return the first matching route <br/> // <br/> (function find(routes, callback) { <br/> var route = routes.shift(); <br/> if (route) { // While there are still routes to process <br/> that.validateRoute(route, request, body, allowedMethods, function (err, found, method) { <br/> if (err) { dispatcher(err) } <br/> else if (found) { dispatcher(null, found) } <br/> else { find(routes, callback) } <br/> }); <br/> } else if (allowedMethods.length) { <br/> dispatcher(new(journey.MethodNotAllowed)(allowedMethods.join(’,’))); <br/> } else { <br/> dispatcher(null, false); <br/> } <br/> })(this.routes.slice(0)); <br/> }</pre> <br/>resolve方法调用find方法,find方法就是将路由规则数组里的路由规则对象一个个弹出来,然后检查这个路由规则对象是否满足当下的请求,不满足则递归调用find方法。判断一个路由规则是否满足请求是由第9行的validateRoute方法来确定的,这个函数很重要,是分析寻找路由规则的关键! <br/> <br/>下面说明validateRoute方法: <br/><pre escaped=“true” lang=“javascript” line=“1”>validateRoute: function (route, request, body, allowedMethods, cb) { <br/> // Match the pattern with the url <br/> var match = (function (pattern) { <br/> var path = request.url.pathname; <br/> <br/> if (! path) { return new(BadRequest) } <br/> <br/> return (path.length > 1 ? path.slice(1) : path).match(pattern); <br/> })(route.pattern); <br/> <br/> // <br/> // Return here if no match to avoid potentially expensive <br/> // async constraint operations. <br/> // <br/> if (!Array.isArray(match)) { <br/> return match === null ? cb(null, false) : cb(match); <br/> } <br/> <br/> // <br/> // Run through the specified constraints, <br/> // asynchronously making sure everything passes. <br/> // <br/> (function checkConstraints(constraints) { <br/> var constraint = constraints.shift(); <br/> <br/> if (constraint) { <br/> // If the constraint is a function then expect it to have a method signature: <br/> // asyncConstraint(request, body, callback); <br/> constraint(request, body, function (err) { <br/> if (err) return cb(err); <br/> checkConstraints(constraints); <br/> }); <br/> } else { <br/> // If there is no handler for this route, return a new NotImplemented exception <br/> if (! (‘handler’ in route)) { return cb(new(journey.NotImplemented)(“unbound route”)) } <br/> <br/> // Otherwise, validate the route method, and return accordingly <br/> if ((route.method.indexOf(request.method) !== -1) || !route.method) { <br/> return cb(null, function (res, params) { <br/> var args = []; <br/> args.push(res); <br/> args.push.apply(args, match.slice(1).map(function (m) { <br/> return /^\d+$/.test(m) ? parseInt(m) : m; <br/> })); <br/> args.push(params); <br/> return route.handler.apply(this, args); <br/> }); <br/> } else { <br/> for (var i = 0; i < route.method.length; i++) { <br/> if (allowedMethods.indexOf(route.method[i]) === -1) { <br/> allowedMethods.push(route.method[i]); <br/> } <br/> } <br/> return cb(null, false); <br/> } <br/> } <br/> })(route.constraints.slice(0)); <br/> }</pre> <br/>1. 第3行的match是某个路由规则匹配请求匹配出来的结果(哪个路由规则呢,看resolve方法中,调用validateRoute之前先弹出了一个路由规则)。 <br/> <br/>2. 第15行检查match是否是一个数组(match可能是数组是因为路由规则中的条件是正则表达式,可能对某个请求有多种匹配情况),如果不是的话判断是否为null,为null说明这个路由规则不匹配这个请求,直接返回null,重新下一个路由规则的匹配检查,如果不为null,直接调用回到函数。 <br/> <br/>3. 第23行开始是很重要的地方,也是整个路由请求处理的关键。26到32行都在反复检查所有的限制条件(保存在限制条件数组constraints中),只要有一个限制条件限制住了请求,则返回错误。如果都通过,则会进入else语句,即通过了限制条件检查。 <br/> <br/>4. 35行开始,先检查路由规则中是否有对应请求的处理函数,如果有,则先压入参数然后调用处理函数,如果没有,则将所有的其他请求处理函数返回(第49行),这样就完成了路由的功能。 <br/> <br/>到此,整个journey模块都介绍完全了,整个模块内部的逻辑还是有些复杂的,下面是两点心得: <br/>1. 完全的事件驱动有他带来的设计上的好处,但不可否认的是代码的层次变得有些多。 <br/>2. 其次javascript语言的灵活性也使得很多功能的实现变得很灵活,整个请求路由的功能在一个js文件中就实现了,包括其中的过滤器,这使得我们看到了nodejs在开发web应用上的潜力。

回到顶部