express : web application framework for node

express是一个Nodejs的web框架。 阅读版本: express@3.2.5 connect@2.7.10

它可以自动创建一个工程,目录结构大概是这样的:

其中app.js为程序入口文件,在这里配置、启动应用程序。怎么使用就不说了。

1、从app.js说起

//这里引用express模块。
var express = require('express')
  	, routes = require('./routes')
  	, user = require('./routes/user')
  	, http = require('http')
  	, path = require('path');
  	//实例化express
var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
//app.use 为引用需要的中间件
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}
app.get('/', routes.index);
app.get('/users', user.list);
//程序启动啦
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

上面写了那么多,主要的就是 var app = express();这里相当于实例化了一个应用程序。 所以要看下express核心文件express.js

2、express.js

作为模块,express文件夹在node_modules下。 lib文件夹下是所有文件。上面实例化的,就是express.js文件的内容。

它一共做了三件事。

a、创建一个app,就是createApplication()

app继承自 connect, 并且app合并proto对象(express的application), 然后将app.requset对象的原型链指向req,同理respose。 最后初始化,返回app对象。

初始化在application里的init里。

b、返回静态方法res req route,application(都是自己的)

C、把connect里的middleware 暴露出来

以上相当于把express继承与connect,在connect外面包了一层,然后merge上自己的方法。

3、application.js 首先文件引入的时候: 会遍历一下methods数组(就是有http各种请求的一个数组) this._router[method].apply(this._router, arguments); 把各个路由方法放到map(this._router)大数组中去

1、然后app.use(this.router) 为connect.use 之前做个配置。 如:“restore .app property on req and res”

connect.use其实就是配置是否使用某中间件,后文会提到。

2、之后执行一下this._router[method]方法

也就是Router.route方法:创造命名空间,放到一个map大全局对象中去。 并把返回的Router对象挂载app下

如:app.get 其实是app._route[‘get’],_route其实是当前初始化的app实例化的Router 而Router在实例化时如下文所示。

这样app.method(path,callbacks)最后就变成了( new Router ).route(methodget, path, callbacks)

3、methods里有get,post等http请求方法

4、之前在express里有app.init

a)app.init

this.cache = {};
this.settings = {};
this.engines = {};
this.defaultConfiguration();
defaultConfiguration里做配置

<1> 设置是否输出头里有x-powered-by,是否开发环境等,写在settings对象里。

<2> 声明中间件 this.use(connect.query()); this.use(middleware.init(this)); use方法在下面

<3> 继承protos 监听mount,父发起的东西放在自己的原型链上。 this.on(‘mount’, function(parent){ this.request.proto = parent.request; this.response.proto = parent.response; this.engines.proto = parent.engines; this.settings.proto = parent.settings; });

<4> 实例化Router,route是个对象

app.routes = Router.map map是所有路径方法的对象列表

app.router = Router.middleware middleware也就是Router里内部方法_dispatch

也即是说当想获取this.router时,即访问时 就会使用_dispatch,也就会检查path,找到对应的callback执行。

<5> 其他设置

b)app.use

参数变换为{route, handle}这样形式的,route代表路由路径,而fn代表回调函数

<1> 如果传递进来的route不是string,则认为是function,于是将fn = route, route = ‘/’;也就是route就是fn(app里基本都是直接传fn一个参数的,so)

<2> 如果是express app用的use,就把fn挂载app上继续,判定fn为express app.

<3> 如果有app(即步骤2为true了),app.route = route;(即“/”) 然后定义一个函数,功能是执行app.handle,并把返回的req.app.request,req.app.response,放到req,res的__proto__里,也就是res,req的原型链里。 。

最后, next(err);出栈操作。

也就是跳到下一个在app.js配置的中间件去了。

<4> 把上面定义的函数,传到connect中执行。connect.proto.use.call(this, route, fn);

<5> mounted an app

if (app) {
    app.parent = this;
    app.emit('mount', this);
}

所以,app.user 是 connect#use()的一个代理,把挂载的application设置传进去。

<6> connect里use为主要的声明中间件的方法,express里的use相当于对其的外加配置改造。 参数变换为{route, handle}这样形式的,route代表路由路径,而handle代表回调函数

i、如果传递进来的route不是string,则认为是function,于是将handle = route, route = ‘/’;

ii、如果传递进来的handle.handle是function,则把handle参数改写解套; 把res,req传给middleware

iii、如果传递进来的handle是http.Server的一个实例,handle = handle.listeners(‘request’)[0]; 将request监听器的第一个回调函数复制给handle。

iv、如果route的最后一位是’/’,则把’/’去掉,当然对于指向根目录的也清空了。 最后将拼装好的{route, handle}放入stack数组中,等待出列。

当有客户端请求触发,handle方法就执行,开始一个个从stack堆栈中取出这个 {route, handle} 对象,根据handle.length的不同分别调用。

c)app.handle

app.handle即connect里的proto.handle 把res,req传给middleware的堆栈。 也就是每个 middleware都接收res,req等。middleware执行是堆栈形式,一个个弹出执行.

里面的next也就是出栈操作吧。

next里如果都出栈了,就返回,若出错了就返回500,把错误信息打出来。 要是连err都没返回就404

d) app.render

new 一个View把模板引擎配置好

e)app.listen

Listen for connections.

f)app.param =function(name, fn)

给name注册一个fn,也就是创造个命名空间 遇到路径的时候,给this._route,也就是当前的Router实例创建命名空间。

4、route/index.js

许多api,挂载在Router对象原型上。

初始化的时候,先遍历一下method(http请求方法list) 然后把每个方法都挂载在Router原型上,=》Router.prototype[method]=fn(path){} fn里执行的是this.route方法,参数是当前method加上传给fn的参数 this.route方法就是创建一个Route实例,把路径塞到this.map大对象中去,全局可访问。

p.s也就是可以这么使用 Router.get,Router.post

1)param(name,fn) 给name注册一个fn,也就是创造个命名空间

2)_dispatch (req, res, next) 路由的middleware,也就是请求的分发器 先找一下匹配的Route实例 把这个Route实例的params赋值给req.params 然后执行callback,包括 下一方法,或者错误callback等。

3)matchRequest(req, i, head) 支持一下HEAD method 然后遍历一下map大对象,看里面哪个Route实例满足当前path 满足的话就req._route_index = i,记录一下 返回这个Route

4)match (method, url, i, head) 尝试给route匹配一个http请求方法method 其实就是把四个参数变成三个,为了匹配matchRequest 然后交给matchRequest处理 四个变三个=》var req = { method: method, url: url };

5)route(method, path, callbacks) 传入方法,路径,回调 检验一番new Route 把参数在传进去,并把这个route放到this.map大对象中去 也就是说this.map大对象就是一个个Route实例。

5、route/route.js

每个route的方法

1)Route(method, path, callbacks, options) 把参数都挂到对象下,如this.path = path;

2)match(path) 检查route是否符合路径,如果符合就把值写到this.params里, 也就是把该路径push到在路径的命名空间下。

综上,可以看出整个路由的实现就是: 你在app.js 定义的如:app.get(‘/users’, user.list); 实际上在app初始化的时候就设置好了一个大map对象,对象里有所有的http请求,get就是其一。 如app.map.get<array>,它是个数组。你设置get的时候就往map.get数组里放进了一个route实例,有路径”/users”,有callback user.list方法。 访问时,会遍历一遍里面数组里的实例,看看”/users”和哪个实例对象匹配(matchRequest访问route实例的match),匹配就执行这个实例的callback。

map对象例子: { get: [ { path: ‘/’, method: ‘get’, callbacks: [Object], keys: [], regexp: /^\/\/?$/i }, { path: ‘/users’, method: ‘get’, callbacks: [Object], keys: [], regexp: /^\/users\/?$/i } ] }

6、middleware.js

在初始化app的时候,就执行了middleware.js exposing the request and response to eachother, as well as defaulting the X-Powered-By header field.

7、view.js

会调用模板引擎的__express 方法,把参数传进去


connect中间件:

在http访问请求时,会先经过一坨中间件,它们一个挨一个把请求传递下去,最终走到业务逻辑。最后把请求返回。

下面是connect中间件的介绍: 1、basicAuth 显示身份认证的浏览器弹框,进行basic认证

2、bodyParser

请求内容解析 等于 app.use(connect.json()); app.use(connect.urlencoded()); app.use(connect.multipart()); 把请求json=》urlencoded=》multipart一轮

3、compress

gzip压缩

4、 Cookie parser

cookie解析中间件,解析Cookies的头通过req.cookies得到cookies。将信息保存在req.cookie中,还可以通过req.secret加密cookies

5、cookieSession

基于cookies的会话中间件

6、csrf

跨域请求csrf保护中间件,通过req.csrfToken()令牌函数绑定到请求的表单字段。这个令牌会对访客会话进行验证

默认情况会检查通过bodyParser()产生的req.body,query()函数产生的req.query,和X-CSRF-Token的header。

在session里加个加密的csrf值,发请求的时候进行对比

7、directory

目录列表中间件,列出指定目录下的文件 可以用来搞静态文件服务。

8、errorHandler

错误处理中间件,对于开发过程中的错误,提供栈跟踪和错误响应,接受3种类型text,html,json。

9、favicon

网页图标中间件,指定favicon的路径

这东西看来是浏览器主动要求来拿的,所以就找一找,返回回去了

10、json

JSON解析中间件,req.body传值

JSON.parse一下req.body

11、limit

请求内容大小限制中间件,可以用mb,kb,gb表示单位。

12、logger

用来输出用户请求日志

13、methodOverride

提供伪造HTTP中间件,检查一个method是否被重写,通过传递一个key,得到_method,原始的方法通过req.originalMethod获得

是http method呦

14、multipart

multipart/form-data请求解析中间件,解析req.body和req.files.

15、query

URL解析中间件,自动解析URL查询参数req.query req.query会自动解析URL的查询参数,不解析POST的数据。

16、responseTime

计算响应时间中间件,在response的header增加X-Response-Time,计算响应时间

17、session

会话管理中间件

18、static

静态文件处理中间件,设置root路径作为静态文件服务器 root路径暴露的文件夹or文件 可以被访问。

19、staticCache

静态文件缓存中间件,最大可以缓存128个对象,每个对象最大256K,总加起来32mb。

缓存算法采用LRU(最近最少使用)算法,让最活跃的对象保存在缓存中,从而增加命中。

20、timeout

请求超时中间件,默认超时时间是5000ms,可以清除这个时间通过req.clearTimeout()函数。超时的错误,可以通过next()函数传递。我们也可以设置超时的响应错误状态码:.timeout=503

21、urlencoded

application/x-www-form-urlencode请求解析中间件

22、vhost

虚拟二级域名映射中间件,设置hostname和server。

+++++++++++++++++++++++++++++++++++++++++++++++++++++

以上是粗读的express源码,总体来说就是请求去遍历那一坨坨的中间件,中间件加工处理请求,像流水线一般,然后输出。其次就是路由管理,map的路由大对象(如上面所写)。

express框架一层套一层,并且connect是特意封装的核心模块,读起来真是想死的心都有。绕啊绕,终于绕出点儿头绪来,不过估计还是有好多地方没弄懂,上面就有好多不是很清晰的解释,因为都是断章取义的,没有全局联系起来。

不过不管了,大致就是上面这段的意思。有机会看看朴灵大神写的九浅一深NodeJs就行了。

记于2013年12月12日 EOF