Blogger Information
Blog 82
fans 0
comment 1
visits 108252
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
express源码阅读记 -- 当一个hello worle 渲染到前端界面的时候express做了什么
子龙的博客搬家啦httpswwwcnblogscomZiLongZiLong
Original
1146 people have browsed it

前几天有空瞄了几眼express4.x的源码,今天做一下总结。 首先我会使用以下代码做为一个入口,开始

  1. const express = require('express');
  2. const app = express();
  3. app.get('/',indexHandler)
  4. function indexHandler(req,res,next){
  5. res.set('Content-Type',"text/html;charset=utf-8");
  6. res.send(`<h1 style="color:red">hello world</h1>`)
  7. }
  8. app.listen(9999)

从第5行代码开始看起,先翻到源码:

  1. function createApplication() {
  2. var app = function(req, res, next) {
  3. app.handle(req, res, next);
  4. };
  5. mixin(app, EventEmitter.prototype, false);
  6. mixin(app, proto, false);
  7. // expose the prototype that will get set on requests
  8. app.request = Object.create(req, {
  9. app: { configurable: true, enumerable: true, writable: true, value: app }
  10. })
  11. // expose the prototype that will get set on responses
  12. app.response = Object.create(res, {
  13. app: { configurable: true, enumerable: true, writable: true, value: app }
  14. })
  15. app.init();
  16. return app;
  17. }

首先注意一下这个叫做app的函数,他既是我们本段代码的入口,也是http请求过来时要流过的第一个函数,然后下来的两个minxin,第一个是把EventEmitter.prototype中的所有属性合并到app上来,这样一来app就拥有了事件订阅和提交的功能,关于EventEmitter可以查看node官网的文档详情。 第二个minxin 是将一个叫做proto的东西合并到了app上,这个proto是在application.js文件里边,定义了app上的方法,诸如listen,enabled,disabled,set等等,这些方法可以在express官方文档这里看到
下来,是定义了request,response,并分别在其上面用app属性引用了app,然后调用app.init()方法。

  1. app.init = function init() {
  2. this.cache = {};
  3. this.engines = {};
  4. this.settings = {};
  5. this.defaultConfiguration();
  6. };

chache是于保存render时的结果的,engines是模板引擎,至于settings保存的是一些设置,诸如是否开启e-tag,x-powerd,还有响应头的一些字段。下来调用了defaultConfiguration。代码比较长,直接在源码里做注释了:

  1. app.defaultConfiguration = function defaultConfiguration() {
  2. var env = process.env.NODE_ENV || 'development';
  3. // default settings
  4. /*添加头x-powered-by*/
  5. this.enable('x-powered-by');
  6. /* 设置etag
  7. > tips:ETag有两种类型:强ETag(strong ETag)与弱ETag(weak ETag)。
  8. 强ETag表示形式:"22FAA065-2664-4197-9C5E-C92EA03D0A16"。
  9. 弱ETag表现形式:w/"22FAA065-2664-4197-9C5E-C92EA03D0A16"。
  10. 具体的策略得看浏览器的不同实现
  11. */
  12. this.set('etag', 'weak');
  13. /*设置环境变量,开发还是生产*/
  14. this.set('env', env);
  15. /* query解析函数,extendend策略下最终调用的是:qs.parse(str, {allowPrototypes: true});*/
  16. this.set('query parser', 'extended');
  17. /*访问req.subdomains时host用.分割成数组之后需要删除后边的数目是几个,举例: 默认为2,tobi.ferrets.example.com 的subdomains 就是["ferrets", "tobi"]
  18. */
  19. this.set('subdomain offset', 2);
  20. /*是否信任代理,*/
  21. this.set('trust proxy', false);
  22. // trust proxy inherit back-compat
  23. Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
  24. configurable: true,
  25. value: true
  26. });
  27. debug('booting in %s mode', env);
  28. /* 当一个子app 挂载到父app的时候会触发 */
  29. this.on('mount', function onmount(parent) {
  30. // inherit trust proxy
  31. if (this.settings[trustProxyDefaultSymbol] === true
  32. && typeof parent.settings['trust proxy fn'] === 'function') {
  33. delete this.settings['trust proxy'];
  34. delete this.settings['trust proxy fn'];
  35. }
  36. // inherit protos
  37. setPrototypeOf(this.request, parent.request)
  38. setPrototypeOf(this.response, parent.response)
  39. setPrototypeOf(this.engines, parent.engines)
  40. setPrototypeOf(this.settings, parent.settings)
  41. });
  42. // setup locals
  43. this.locals = Object.create(null);
  44. // top-most app is mounted at /
  45. this.mountpath = '/';
  46. // default locals
  47. this.locals.settings = this.settings;
  48. // default configuration
  49. /* view为render的时候渲染模板的一个数据结构 */
  50. this.set('view', View);
  51. /* 模板目录,默认为views */
  52. this.set('views', resolve('views'));
  53. /* 设置jsonp 回调函数的名字*/
  54. this.set('jsonp callback name', 'callback');
  55. /*生产模式开启view缓存*/
  56. if (env === 'production') {
  57. this.enable('view cache');
  58. }
  59. /* 4.x不再支持app.router式的调用 */
  60. Object.defineProperty(this, 'router', {
  61. get: function() {
  62. throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.');
  63. }
  64. });
  65. };

至此,app.init行完毕。 然后express函数返回app实例。
接着,我们使用app.get定义了我们的第一条路由,至于app.get的源码,是在这里:

  1. // methods = ['get','post'...] 等一系列http动词
  2. methods.forEach(function(method){
  3. app[method] = function(path){
  4. if (method === 'get' && arguments.length === 1) {
  5. // app.get(setting)
  6. return this.set(path);
  7. }
  8. this.lazyrouter();
  9. var route = this._router.route(path);
  10. route[method].apply(route, slice.call(arguments, 1));
  11. return this;
  12. };
  13. });

app[method]向外引用,function,闭包了当前method的名字,这里注意到当以app.get(‘key’)形式调用的时候,程序实际return的是当前set[‘key’]的值。
如果是定义路由,则走了下面的步骤,首先会给当前的app实例初始化一个router实例,源码如下:

  1. app.lazyrouter = function lazyrouter() {
  2. if (!this._router) {
  3. this._router = new Router({
  4. caseSensitive: this.enabled('case sensitive routing'),
  5. strict: this.enabled('strict routing')
  6. });
  7. this._router.use(query(this.get('query parser fn')));
  8. this._router.use(middleware.init(this));
  9. }
  10. };

这种如果没有再定义的策略,设计模式上叫单例模式,初次执行肯定没有,所以这里先会初始化一个rouer绑定到app._router上,caseSensitive 表示路由对大小写敏感,strict开启路由的严格模式,好,接着走到Router构造函数:

  1. var proto = module.exports = function(options) {
  2. var opts = options || {};
  3. function router(req, res, next) {
  4. router.handle(req, res, next);
  5. }
  6. // mixin Router class functions
  7. setPrototypeOf(router, proto)
  8. router.params = {};
  9. router._params = [];
  10. router.caseSensitive = opts.caseSensitive;
  11. router.mergeParams = opts.mergeParams;
  12. router.strict = opts.strict;
  13. router.stack = [];
  14. return router;
  15. };

router本身是一个函数,调用自身的而handle方法,传递http,setPrototypeOf 将router的__proto__属性指向proto,借此实现js式的继承,至于proto,也定义于此处,就是router的一系列方法,诸如param,handle,use等等。当然router也有自己的get,post等等方法,这些和app上定义的时候大同小异的,最后声明了几个保存变量的属性,将router返回了出来。初始化router完毕之后,router使用了两个中间件,第一个是parse query的,第二个中间件是用于初始化req和res的,他做了一件很重要的事就是将express框架的request和response 绑定到了当前的req和res上,部分代码如下:

  1. setPrototypeOf(req, app.request)
  2. setPrototypeOf(res, app.response)

这些方法里边包含了很多东西,诸如req的属性,res的send,set等等。
lazyRouter执行完毕,然后执行router上的route方法:

  1. function route(path) {
  2. var route = new Route(path);
  3. var layer = new Layer(path, {
  4. sensitive: this.caseSensitive,
  5. strict: this.strict,
  6. end: true
  7. }, route.dispatch.bind(route));
  8. layer.route = route;
  9. this.stack.push(layer);
  10. return route;
  11. };

Route是描述路由的一个数据结构,他的构造函数如下:

  1. function Route(path) {
  2. this.path = path;
  3. this.stack = [];
  4. debug('new %o', path)
  5. // route handlers for various http methods
  6. this.methods = {};
  7. }

path属性包含了当前路由的path,stack是定义路由是用于保存定义路由时生成的layer的数组,至于methods,举一个例子就是当 route.get()发生时,那么this.methods.get的值就是true。
接着会生成layer,layer的构造函数如下:

  1. function Layer(path, options, fn) {
  2. if (!(this instanceof Layer)) {
  3. return new Layer(path, options, fn);
  4. }
  5. debug('new %o', path)
  6. var opts = options || {};
  7. this.handle = fn;
  8. this.name = fn.name || '<anonymous>';
  9. this.params = undefined;
  10. this.path = undefined;
  11. this.regexp = pathRegexp(path, this.keys = [], opts);
  12. // set fast path flags
  13. this.regexp.fast_star = path === '*'
  14. this.regexp.fast_slash = path === '/' && opts.end === false
  15. }

这里边比较重要的一点是会将当前path转化成能匹配他的正则并把这个正则保存到this.regexp上,并且会把param参数在此处提取出来保存到this.keys上,还有一个handle属性保存在这个layer上执行的回调函数,以以上形式生成的layer上,其handle函数为route.dispatch.bind(route),这个函数是route实例上的方法,用于执行他的栈上保存的layer。然后layer生成完毕,此时的layer实例会将一个route属性指向当前的route,此刻,将这个layer保存到router的stacks里。

这里多提一句就是,router.use这种形式生成路由时layer上的route是undefined的,并且layer的handle就是传进去的回调函数。在后边router遍历自己stack上存储的layer时,正是基于此 判断他是中间件还是一个路由业务函数。

router.route执行完毕,接下来开始执行route[method].apply(route, slice.call(arguments, 1));, route[method]的定义方法,和app,router大同小异:

  1. methods.forEach(function(method){
  2. Route.prototype[method] = function(){
  3. var handles = flatten(slice.call(arguments));
  4. for (var i = 0; i < handles.length; i++) {
  5. var handle = handles[i];
  6. if (typeof handle !== 'function') {
  7. var type = toString.call(handle);
  8. var msg = 'Route.' + method + '() requires a callback function but got a ' + type
  9. throw new Error(msg);
  10. }
  11. debug('%s %o', method, this.path)
  12. var layer = Layer('/', {}, handle);
  13. layer.method = method;
  14. this.methods[method] = true;
  15. this.stack.push(layer);
  16. }
  17. return this;
  18. };
  19. });

route.get,post,…等方法也会生成layer,其handle就是定义的回调函数,然后这个layer会保存到route的stack里,在route的dispath方法里调用。
这样的话,route[method].apply(route, slice.call(arguments, 1));也就执行完毕了。至此,路由定义完毕。现在在这里梳理一下,app,router,route的关系:
此刻,app的内部属性_router上引用的router实例,他的stack上此刻应该有如下几个layer:

  • 用于parse query的中间件;
  • 用于初始化req,res的init中间件;

以上两个是初始化的时候就use的中间件,接下来是:

  • 调用app,get 的时候为route创造的layer。

app.get执行时,生成的route实例会在其stack上保存一个layer,该layer的handle就是我们定义的回调函数。
ok。下来app开始listen:

  1. app.listen = function listen() {
  2. var server = http.createServer(this);
  3. return server.listen.apply(server, arguments);
  4. };

server还是使用http模块的createServer创造的, 只不过this指向的是app也就是开头我们提到的整个程序的入口,是个函数,接着用了函数的apply方法,将app.listen调用时传过去的参数使用arguments巧妙的传过去,并将server设置为listen的上下文。listen之后,app就开始正式运行了,监听了9999端口。

——————

当在浏览器上输入http://localhost:9999/ 时,首先,app会被执行,而app里只有一句话就是app.handle(req, res, next); 参数分别是request,response和next(在此时为undefined),所以我们继续往下看app.handle:

  1. app.handle = function handle(req, res, callback) {
  2. var router = this._router;
  3. // final handler
  4. var done = callback || finalhandler(req, res, {
  5. env: this.get('env'),
  6. onerror: logerror.bind(this)
  7. });
  8. // no routes
  9. if (!router) {
  10. debug('no routes defined on app');
  11. done();
  12. return;
  13. }
  14. router.handle(req, res, done);
  15. };

开始执行 router.handle并将done默认值作为next参数传递过去: 此处代码稍长,所以还是将解释放到源码里边。

  1. function handle(req, res, out) {
  2. var self = this;
  3. debug('dispatching %s %s', req.method, req.url);
  4. var idx = 0;
  5. var protohost = getProtohost(req.url) || ''
  6. var removed = '';
  7. var slashAdded = false;
  8. var paramcalled = {};
  9. // store options for OPTIONS request
  10. // only used if OPTIONS request
  11. var options = [];
  12. // middleware and routes
  13. var stack = self.stack;
  14. // manage inter-router variables
  15. var parentParams = req.params;
  16. var parentUrl = req.baseUrl || '';
  17. /*重置传进来的next方法,restore的作用是保存初始的baseUrl,next,params的值,该方法最后返回
  18. 一个闭包函数,该闭包函数内req上的以上三个属性会被重置为初始值,然后调用out方法
  19. */
  20. var done = restore(out, req, 'baseUrl', 'next', 'params');
  21. // setup next layer
  22. req.next = next;
  23. // for options requests, respond with a default if nothing else responds
  24. if (req.method === 'OPTIONS') {
  25. done = wrap(done, function(old, err) {
  26. if (err || options.length === 0) return old(err);
  27. sendOptionsResponse(res, options, old);
  28. });
  29. }
  30. // setup basic req values
  31. req.baseUrl = parentUrl;
  32. req.originalUrl = req.originalUrl || req.url;
  33. /*开始执行next方法,遍历router.stack里的layer*/
  34. next();
  35. function next(err) {
  36. var layerError = err === 'route'
  37. ? null
  38. : err;
  39. // remove added slash
  40. if (slashAdded) {
  41. req.url = req.url.substr(1);
  42. slashAdded = false;
  43. }
  44. // restore altered req.url
  45. if (removed.length !== 0) {
  46. req.baseUrl = parentUrl;
  47. req.url = protohost + removed + req.url.substr(protohost.length);
  48. removed = '';
  49. }
  50. // signal to exit router
  51. if (layerError === 'router') {
  52. setImmediate(done, null)
  53. return
  54. }
  55. // no more matching layers
  56. if (idx >= stack.length) {
  57. setImmediate(done, layerError);
  58. // 遍历完毕,则在check阶段执行done方法
  59. return;
  60. }
  61. // get pathname of request
  62. var path = getPathname(req);
  63. if (path == null) {
  64. return done(layerError);
  65. }
  66. // find next matching layer
  67. var layer;
  68. var match;
  69. var route;
  70. /* 循环的目的,找匹配的layer,如果找不到匹配的layer就一直将stack里的layer遍历完毕 */
  71. while (match !== true && idx < stack.length) {
  72. layer = stack[idx++];
  73. /*是否匹配*/
  74. match = matchLayer(layer, path);
  75. route = layer.route;
  76. if (typeof match !== 'boolean') {
  77. // hold on to layerError
  78. layerError = layerError || match;
  79. }
  80. /*不匹配就开始下一轮循环*/
  81. if (match !== true) {
  82. continue;
  83. }
  84. /* 不是以app[method],router[method] 定义的路由就到此为止,跳出while,开始执行 */
  85. if (!route) {
  86. // process non-route handlers normally
  87. continue;
  88. }
  89. if (layerError) {
  90. // routes do not match with a pending error
  91. match = false;
  92. continue;
  93. }
  94. /*拿到http方法动词并判断是否为给出的动词*/
  95. var method = req.method;
  96. var has_method = route._handles_method(method);
  97. /* 如果不是已给出的方法动词,且为options,则在options数组里添加当前route.methods 的keys*/
  98. // build up automatic options response
  99. if (!has_method && method === 'OPTIONS') {
  100. appendMethods(options, route._options());
  101. }
  102. // don't even bother matching route
  103. /*如果不是已给出的方法动词且不是head 则将match重置为false开始下一个循环*/
  104. if (!has_method && method !== 'HEAD') {
  105. match = false;
  106. continue;
  107. }
  108. }
  109. // no match
  110. /* while循环执行完毕之后,有两种结果,第一种匹配到了layer,则开始执行layer,第二种,没有匹配到则执行done方法,重置req对象 */
  111. if (match !== true) {
  112. /*没有匹配到,*/
  113. return done(layerError);
  114. }
  115. // store route for dispatch on change
  116. /*当前为路由形式的layer,也就是layer.route不为undefined的layer,req.route 指向route*/
  117. if (route) {
  118. req.route = route;
  119. }
  120. // Capture one-time layer values
  121. /*获取当前req的params*/
  122. req.params = self.mergeParams
  123. ? mergeParams(layer.params, parentParams)
  124. : layer.params;
  125. var layerPath = layer.path;
  126. // this should be done for the layer
  127. /*process_params方法,有layer匹配当前param的时候,至多执行一次app.params()定义的方法,这些方法执行完毕之后,才开始执行当前的layer,也就是调用layer.handle_request*/
  128. self.process_params(layer, paramcalled, req, res, function (err) {
  129. if (err) {
  130. return next(layerError || err);
  131. }
  132. if (route) {
  133. /*路由形式的layer这么执行*/
  134. return layer.handle_request(req, res, next);
  135. }
  136. /*非路由形式的layer执行前,需要调用trim_prefix方法,该方法会先验证当前layer的path是否合法,然后重新设置req.urlreq.baseUrl等一系列工作,然后调用layout的方法*/
  137. trim_prefix(layer, layerError, layerPath, path);
  138. });
  139. }
  140. function trim_prefix(layer, layerError, layerPath, path) {
  141. if (layerPath.length !== 0) {
  142. // Validate path breaks on a path separator
  143. var c = path[layerPath.length]
  144. if (c && c !== '/' && c !== '.') return next(layerError)
  145. // Trim off the part of the url that matches the route
  146. // middleware (.use stuff) needs to have the path stripped
  147. debug('trim prefix (%s) from url %s', layerPath, req.url);
  148. removed = layerPath;
  149. req.url = protohost + req.url.substr(protohost.length + removed.length);
  150. // Ensure leading slash
  151. if (!protohost && req.url[0] !== '/') {
  152. req.url = '/' + req.url;
  153. slashAdded = true;
  154. }
  155. // Setup base URL (no trailing slash)
  156. req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
  157. ? removed.substring(0, removed.length - 1)
  158. : removed);
  159. }
  160. debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
  161. if (layerError) {
  162. layer.handle_error(layerError, req, res, next);
  163. } else {
  164. layer.handle_request(req, res, next);
  165. }
  166. }
  167. };

下来开始说layout的执行方法,首先对于正常的layer执行layer.handle_request(req, res, next)方法,其中next就是next函数,由于循环变量idx置于next函数外边,因此开始执行此函数就意味着开始寻找下一个匹配的layer。

  1. Layer.prototype.handle_request = function handle(req, res, next) {
  2. var fn = this.handle;
  3. if (fn.length > 3) {
  4. // not a standard request handler
  5. return next();
  6. }
  7. try {
  8. fn(req, res, next);
  9. } catch (err) {
  10. next(err);
  11. }
  12. };

由上面可以看到如果当前layer的handle的形参大于3就会出错,直接执行下一个layer,如果正确的话,则调用我们传递给他的回调函数。 handle在前面详细说过这里就不提了。
当执行完pase query,以及init 两个中间件之后,开始执行第三个我们定义的route,只不过这里的执行过程是 layer.handle -> router.dispatch -> 遍历route.stack里的layer执行,遍历的流程跟router上的差不多,然后最后就是执行:

  1. function indexHandler(req,res,next){
  2. res.set('Content-Type',"text/html;charset=utf-8");
  3. res.send(`<h1 style="color:red">hello world</h1>`)
  4. }

res的set使用来设置响应头的,各种响应头,至于send,除了为我们写了一些头还判断了http缓存逻辑,最后调用res.end()方法,将我们的<h1 style="color:red">hello world</h1> 返回给了客户端,剩下的工作是一些异步工作,诸如tcp挥手,node自己内部的一些方法,俺也没多做了解,就不展开讲了。至此hello world完成。

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post