摘要:在后续的总结中,我会继续分析,并准备将一些值得分析的逐一解读,也会涉及一些。从一个官方示例开始这是官方给出的一个简单程序,运行后访问显示。第一行载入了框架,我们来看源代码中的。代码的开始定义了一个函数,函数有形参,,为回调函数。
这两天仔细看了看express的源码,对其的整个实现有了较清晰的认识,所以想总结一下写出来,如果有什么不对的地方,望指出。
这是第一篇,首先介绍一个最简单的express应用运行过程,初步分析了其在源码中的具体实现,还没有涉及到一些比较重要的内容比如路由组件的实现方式,中间件的触发流程等。在后续的总结中,我会继续分析,并准备将一些值得分析的public api逐一解读,也会涉及一些private api。
基于的版本截止写这篇文章时目前最新的tags是4.4.2。我是直接看的master分支。express的commits提交非常频繁,但总体的实现思路应该不会有大的变化。其在4.x后做了较大的改动,相对于3.x最大的地方在于不再依赖connect,并移除了几乎所有的内置中间件,具体的变动请看官方wiki的 Migrating from 3.x to 4.x 及 New features in 4.x。
从一个官方示例开始var express = require("express"); var app = express(); app.get("/", function(req, res){ res.send("Hello World"); }); app.listen(3000);
这是官方给出的一个简单程序,运行后访问localhost:3000显示Hello World。下面我们就来仔细看看这段程序。
首先第一行
var express = require("express");
这是典型的Node.js模块载入代码,关于Node.js的模块载入机制,不了解的同学建议看看朴灵的深入Node.js的模块机制,非常有帮助。
第一行载入了express框架,我们来看源代码中的index.js。
module.exports = require("./lib/express");
好吧,还要继续require,我们看./lib/express.js
exports = module.exports = createApplication;
从这里我们可以看出,程序的第一行express最后实际是这个createApplication函数。第二行则是运行了这个函数,然后返回值赋给了app。该函数代码如下
var EventEmitter = require("events").EventEmitter; var mixin = require("utils-merge"); var proto = require("./application"); var req = require("./request"); var res = require("./response"); function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, proto); mixin(app, EventEmitter.prototype); app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app }; app.init(); return app; }
可以发现,这个就相当于express的"main"函数,其中完成了所有创建express实例所需要的动作,并在执行完毕后返回一个函数。
代码的开始定义了一个函数,函数有形参req,res,next为回调函数。
函数体只有一条语句,执行app.handle,handle方法在application.js文件中定义,此处是通过mixin导入(见下文),handle的代码如下
app.handle = function(req, res, done) { var router = this._router; // final handler done = done || finalhandler(req, res, { env: this.get("env"), onerror: logerror.bind(this) }); // no routes if (!router) { debug("no routes defined on app"); // generate error var err = new Error("No routes or middlewares have been defined"); err.status = 500; done(err); return; } router.handle(req, res, done); };
它的作用就是将每对[req,res]进行逐级分发,作用在每个定义好的路由及中间件上,直到最后完成,具体的过程我们会在后续进行分析。
然后来看看中间的两行
mixin(app, proto); mixin(app, EventEmitter.prototype);
mixin是在头部的require处载入的utils-merge模块,它的代码如下
exports = module.exports = function(a, b){ if (a && b) { for (var key in b) { a[key] = b[key]; } } return a; };
很明显,mixin(app, proto);的作用即是将proto中所有的property全部导入进app,proto在头部的require处载入的是./lib/application.js文件,其中定义了大部分express的public api,如app.set,app.get,app.use...详见官方的API文档。
mixin(app, EventEmitter.prototype);则将Node.js的EventEmitter中的原型方法全部导入了app。
再来看接下来的两行
app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app };
这里定义了app的request和response对象,使用了对象的字面量表示法,使其分别继承自req(顶部导入的request.js)和res(顶部导入的response.js),并反向引用了app自身。为什么要这样做呢?这个问题我一开始想不明白,后来我就干脆把这两行代码删了,运行,当然就是报错,答案就在错误中的信息里。
TypeError: Object #
has no method "send"
显示找不到"send"方法,为什么呢?首先我们从app.get()方法看起,不熟悉的人会找不到它在源码中的位置,其实它在application.js中是这样的
methods.forEach(function(method){ app[method] = function(path){ if ("get" == method && 1 == arguments.length) return this.set(path); this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, [].slice.call(arguments, 1)); return this; }; });
methods在顶部模块引入中定义,其实是一个包含各个HTTP请求方法的数组,具体代码在这里。
从上面的代码中我们可以看到,这里实际上是遍历了所有methods中定义的方法,当然其中包括get,而且get方法是被"重载"的,即当app.get();的参数只有一个时候,执行的是获取变量的功能,否则,执行route组件中的route.get方法,将该路由和回调函数(即第二个参数)存储进一个栈中(后续会进一步分析)。
回到原来的问题,在这里,关键是看中间的
this.lazyrouter();
我们看它的具体代码
app.lazyrouter = function() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled("case sensitive routing"), strict: this.enabled("strict routing") }); this._router.use(query()); this._router.use(middleware.init(this)); } };
它的作用是在第一次定义路由的时候初始化路由(添加基本的路由),注意最后一句用到了middleware模块的init方法,继续上代码
exports.init = function(app){ return function expressInit(req, res, next){ if (app.enabled("x-powered-by")) res.setHeader("X-Powered-By", "Express"); req.res = res; res.req = req; req.next = next; req.__proto__ = app.request; res.__proto__ = app.response; res.locals = res.locals || Object.create(null); next(); }; };
它的作用是初始化request和response,可以看到其中用到了我所疑惑app.request和app.respone,它使req和res继承自了request.js和response.js中的定义,也因此在我去掉了那两行代码后会出现res.send找不到的情况。
另外,定义app.response对象时反引用自身,也使得后面在response对象中能够通过this.app获得所创建的express实例。
让我们回到createApplication函数,接下来是app.init();。显然,作用是初始化,做哪些工作呢?
app.init = function(){ this.cache = {}; this.settings = {}; this.engines = {}; this.defaultConfiguration(); };
设定了cache对象(render的时候用到),各种setting的存储对象,engines对象(模板引擎),最后进行默认的配置,代码有点长这里就不上了,就是做一些默认的配置。
好了,createApplication函数就是这些,当然,其中略去了很多重要的问题,比如路由组件的实现方式,中间件的触发流程等,这我会在后续的总结中进行分析。
最开头的官方示例中还有最后一句
app.listen(3000);
代码如下
app.listen = function(){ var server = http.createServer(this); return server.listen.apply(server, arguments); };
实际上是调用了Node.js原生的http模块的CreatServer方法,API文档说明是
http.createServer([requestListener])#
Returns a new web server object.The requestListener is a function which is automatically added to the "request" event.
方法返回的是一个web server对象,其中的参数为HTTP request事件触发后执行的函数(这里我们给的就是我们在createApplication函数中获得的app)。
最后,返回的web server有一个监听端口的listen方法,参数为需要监听的端口号,本示例中即为3000。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87548.html
摘要:就是每一个教程里面开始教学的事例,启动服务器的回调函数。,从入口开始分析源码首先是把模块的属性全部进里面去,在把事件的属性全部进里面去,这是为了给增加事件功能。 express4.X源码解读第一天 express4.X 跟3.X 有很大区别,4.X 去除了connect的依赖,3.X基于connect的中间件基本全部不能用,如果还有可以使用的,也是4.X重写的。所以要想继续使用这些熟悉...
摘要:载入了框架,我们来看源代码中的。函数函数代码如下代码的开始定义了一个函数,函数有形参,,为回调函数。相应的,等同于继承,从而让有了事件处理的能力。 此为裁剪过的笔记版本。 原文在此:https://segmentfault.com/a/11...原文在此: https://cnodejs.org/topic/574... 感谢@YiQi ,@leijianning 带来的好文章。我稍作...
摘要:如果此时我们不想把文件输出到内存里,可以通过修改的源代码来实现。服务启动成功。。。根据请求的,拼接出 webpack-dev-middleware 是express的一个中间件,它的主要作用是以监听模式启动webpack,将webpack编译后的文件输出到内存里,然后将内存的文件输出到epxress服务器上;下面通过一张图片来看一下它的工作原理: showImg(https:...
摘要:最近在研究,学着使用,开始不会用,就百度了一下,没有百度到特别完整的解答。查阅了的,综合了网友的博客,解读了的源码,以及使用和验证,终于明白了中的使用。默认为网站域名过期时间,类型为。使用插件,后续代码直接使用或者即可 最近在研究express,学着使用cookie,开始不会用,就百度了一下,没有百度到特别完整的解答。查阅了express的API,综合了网友的博客,解读了cookie-...
摘要:最近开始看源码,并将源码解读放在了我的计划中。相对于其他源码解读的文章,基本都会从整体设计开始讲起,楼主觉得这个库有点特殊,决定按照自己的思路,从用代替说起。源码没有出现注意,其实有出现一处,是为,而不是,而用代替之。 Why underscore 最近开始看 underscore源码,并将 underscore源码解读 放在了我的 2016计划 中。 阅读一些著名框架类库的源码,就好...
阅读 1078·2021-11-16 11:44
阅读 1367·2019-08-30 13:12
阅读 2400·2019-08-29 16:05
阅读 3069·2019-08-28 18:29
阅读 904·2019-08-26 13:41
阅读 3228·2019-08-26 13:34
阅读 2595·2019-08-26 10:35
阅读 931·2019-08-26 10:28