带你深入解析express源码(一)
目录结构
本人将express源码简化,如下所示
const mixin = require('merge-descriptors')
let proto = require('./application')
const http = require('http')
const EventEmitter = require('events').EventEmitter
const Router = require('./router')
exports = module.exports = createApplication
/**
* 创建app
*/
function createApplication() {
let app = function (req, res, next) { // createServer的回调函数
app.handle(req, res, next)
}
mixin(app, proto, false)
mixin(app, EventEmitter.prototype, false)
let req = Object.create(http.IncomingMessage.prototype)
let res = Object.create(http.ServerResponse.prototype)
// 4:新增 讲app和新创建的req相互关联
//给request绑定req和app
app.request = Object.create(req, {
app: {
configurable: true,
enumerable: true,
writable: true,
value: app
}
})
// 4:新增 讲app和新创建的res相互关联
app.response = Object.create(res, {
app: {
configurable: true,
enumerable: true,
writable: true,
value: app
}
})
app.init()
return app
}
// 4:新增 将router暴露出来
exports.Router = Router
- 首先可以看到new express()时执行了createApplication,定义了 app函数,并调用了mixin,mixin方法主要是合并对象,首先合并了application,随后混合了app和事件处理函数,并将node的res和req绑定到了app对象,最终执行了application的init方法。
- 我们来看看init方法,可以看出该方法主要调用了defaultConfiguration方法,该方法设置了环境变量,并设置了一些对象。
- 在application.js中我们可以看到有个遍历methods的方法,该方法在被引入时就会执行,这个是application的核心方法,我们来看看干了些什么。
/**
* 采用的是设计模式中的模块模式,定义app对象,为其挂载方法
*/
const http = require('http')
let app = exports = module.exports = {}
let methods = http.METHODS // 获取http的所有请求方式
const slice = Array.prototype.slice;
const setPrototypeOf = require('setprototypeof')
const Router = require('./router')
const finalhandler = require('finalhandler') // http request 最后的函数处理,主要包括错误处理 https://www.npmjs.com/package/finalhandler
const flatten = require('array-flatten').flatten
const trustProxyDefaultSymbol = '@@symbol:trust_proxy_default'
const compileQueryParser = require('./utils').compileQueryParser
// 3:新增 req.query 中间件的处理
const query = require('./middleware/query')
// 4:新增 初始中间件,对req,res,app等进行相互关联
const middleware = require('./middleware/init')
// console.dir(new Router().toString())
/**
* 初始化app对象需要的一些基础设置
* paths: 存放所有使用get方法注册的请求,单体对象的格式为:
* {
* pathURL 请求的地址
cb 请求对应的回调函数
* }
*/
app.init = function init() {
this.setting = {}
this.paths = []
this.defaultConfiguration()
}
/**
* 设置环境变量env,后期迭代预留
*/
app.defaultConfiguration = function defaultConfiguration() {
let env = process.env.NODE_ENV || 'development'
this.set('env', env)
this.set('jsonp callback name', 'callback')
console.log(this)
}
/**
* 对app中setting对象的操作,为后期迭代预留
*/
app.set = function set(key, val) {
if (arguments.length === 1) {
this.setting[key]
}
this.setting[key] = val
}
//用户的请求
app.handle = function handle(req, res) {
let router = this._router
let done = finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
})
//厂家有没有,没有则报错
if (!router) {
done()
}
//执行handle函数
router.handle(req, res, done)
}
function logerror(err) {
if (this.get('env') !== 'test')
console.error(err.stack || err.toString());
}
/**
* 启动http服务
*/
app.listen = function listen() {
let server = http.createServer(this)
//自执行解构伪数组,还是执行linsten方法,并传入arguments
return server
.listen
.apply(server, arguments)
}
// /**
// * 实现app的get接口,主要是对所有的get请求进行注册,方便handle中实现精准回调
// */
app.get = function get(path, cb) {
let pathObj = {
pathURL: path,
cb: cb
}
this.paths.push(pathObj)
}
/**
* 对路由实现装载,实例化
*/
app.lazyrouter = function () {
if (!this._router) {
this._router = new Router()
// 3:新增 注册处理query的中间件
this
._router
.use(query(this.get('query parser fn')))
// 4:新增 初始中间件,对req,res,app等进行相互关联
this
._router
.use(middleware.init(this))
}
}
/**
* 实现post,get等http.METHODS 对应的方法
*/
methods.forEach((method) => {
method = method.toLowerCase()
app[method] = function (path) {
if (method === 'get' && arguments.length === 1) { // 当为一个参数时app的get方法,返回settings中的属性值
return this.set(path)
}
this.lazyrouter()
let route = this._router.route(path) // 调用_router的route方法,对path和route注册 slice.call(arguments, 1)是取出url后面的参数
console.log(method)
// this._router.route(path).get(()=>{})
//slice.call(arguments, 1) 就是将伪数组切割,得到的是回调函数
//apply相当于执行该函数,第二个参数为该函数执行所需要的参数为[]
route[method].apply(route, slice.call(arguments, 1)) // 调用route的method方法,对method和callbacks注册
}
})
/**
* 3:新增 实现app的param接口
* @param {*} name 参数名称 可以是数组 或者 字符串
* @param {*} fn 需要处理的中间件
*/
app.param = function param(name, fn) {
this.lazyrouter()
// 如果name是数组时,分割调用自身
if (Array.isArray(name)) {
for (let i = 0; i < name.length; i++) {
this.param(name[i], fn)
}
return this
}
this
._router
.param(name, fn)
return this
}
/**
* 3:新增 暴露给用户注册中间件的结构,主要调用router的use方法
* @param {*} fn
*/
app.use = function use(fn) {
let offset = 0
let path = '/'
if (typeof fn !== 'function') {
let arg = fn
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0]
}
if (typeof arg !== 'function') {
offset = 1
path = fn
}
}
// 4:新增 对传入的参数进行处理,是参数可以传入数组
let fns = flatten(slice.call(arguments, offset))
if (fns.length === 0) {
throw new TypeError('app.use() require a middlewaare function')
}
//给res,req,request挂载一些参数
this.lazyrouter()
let router = this._router
// fns.forEach(function (fn) {
// router.use(path, fn)
// })
fns.forEach(function (fn) {
// 4:修改 通常use里面是一个express对象,或者router对象时会包含handle和set,不包含为普通中间件
if (!fn || !fn.handle || !fn.set) {
return router.use(path, fn)
}
// 4:新增 此时的fn为express或router对象,将当前express对象关联到fn
fn.parent = this
router.use(path, function mounted_app(req, res, next) {
let orig = req.app
// 4:新增 在中间件的回调函数中调用fn的handle
fn.handle(req, res, function (err) {
setPrototypeOf(req, orig.request)
setPrototypeOf(res, orig.response)
next(err)
})
})
// 4:新增 触发fn挂载完成事件
fn.emit('mount', this)
}, this)
}
3.1 首先将获取到的methods转化小写,并给每个method设置为函数,该函数仅仅当该method获取一个参数时的方法,返回settings中的属性值。
随后调用了lazyrouter方法,该方法实例化了router文件夹下的index.js方法,我们来看看实例化该方法主要获得什么。
实例化对象如下所示,该对象最重要的是stack数组,该数组放置了Layer,这个Layer是什么呢?我贴出代码来分析下。
this._router=[Function] {
route: [Function: route],
handle: [Function: handle],
params: {},
stack: [
Layer {
handle: [Function: bound dispatch],
params: undefined,
path: undefined,
keys: [],
regexp: /^\/index[\/#\?]?$/i,
route: [Route]
},
Layer {
handle: [Function: bound dispatch],
params: undefined,
path: undefined,
keys: [],
regexp: /^\/index[\/#\?]?$/i,
route: [Route]
},
Layer {
handle: [Function: bound dispatch],
params: undefined,
path: undefined,
keys: [],
regexp: /^\/index[\/#\?]?$/i,
route: [Route]
}
]
}
index.js代码如下所示,我们可以看出,主要实例化了Route和layer,实例化又干了些什么呢?我们先留着疑问下篇文章分析,下章主要分析new Route和new Layer时发生了什么。
'use strict'
const debug = require('debug')('router')
const Route = require('./route')
const Layer = require('./layer')
let methods = require('http').METHODS
const parseUrl = require('parseurl')
const mixin = require('merge-descriptors')
const flatten = require('array-flatten').flatten
// 3:新增,用于获取对象类型
const objectRegExp = /^\[object (\S+)\]$/
const slice = Array.prototype.slice
const toString = Object.prototype.toString
/**
* @description:
* @param {type}
* @return {type}
*/
let proto = module.exports = function (options) {
let ops = options || {}
function router(req, res, next) {
router.handle(req, res, next)
}
mixin(router, proto)
router.params = {}
router._params = []
router.stack = []
return router
}
/**
* 将path和route对应起来,并放进stack中,对象实例为layer
*/
proto.route = function route(path) {
//拿着这个path去route.js中生成route对象,Route { path: '/', stack: [], methods: {} }+dispatch
let route = new Route(path)
//route.dispatch.bind(route) 得到dispatch函数,不会执行
let layer = new Layer(path, {
end: true
}, route.dispatch.bind(route))//从Route { path: '/', stack: [], methods: {} }里面去取值 [route.dispatch.bind(route): bound dispatch]
/**console.log(layer)
{
handle: [Function: bound dispatch],
params: undefined,
path: undefined,
keys: [],
regexp: /^\/index[\/#\?]?$/i
}
**/
layer.route = route
/**
*
* layer=
* {
handle: [Function: bound dispatch],
params: undefined,
path: undefined,
keys: [],
regexp: /^\/index[\/#\?]?$/i,
route: { path: '/index', stack: [], methods: {} }
}
*
* **/
this
.stack
.push(layer)
return route
}
/**
* 遍历stack数组,并处理函数, 将res req 传给route
*/
proto.handle = function handle(req, res, out) {
let self = this
debug('dispatching %s %s', req.method, req.url)
let idx = 0
//商家首先拿到stack和用户请求的url
let stack = self.stack
let url = req.url
let done = restore(out, req, 'baseUrl', 'next', 'params')
let paramcalled = {}
next() //第一次调用next
function next(err) {
let layerError = err === 'route'? null: err
if (layerError === 'router') { //如果错误存在,再当前任务结束前调用最终处理函数
setImmediate(done, null)
return
}
if (idx >= stack.length) { // 遍历完成之后调用最终处理函数
setImmediate(done, layerError)
return
}
//
let layer
let match
let route
while (match !== true && idx < stack.length) { //从数组中找到匹配的路由
//拿到第一个layer
layer = stack[idx++]
//正则匹配,匹配到了就是true
match = matchLayer(layer, url)
//拿到route类
route = layer.route
if (typeof match !== 'boolean') {
layerError = layerError || match
}
//没有匹配则跳过
if (match !== true) {
continue
}
if (layerError) {
match = false
continue
}
//拿到用户请求方式
let method = req.method
let has_method = route._handles_method(method)
if (!has_method) {
match = false
continue
}
}
//找了一圈都没找到
if (match !== true) { // 循环完成没有匹配的路由,调用最终处理函数
return done(layerError)
}
res.params = Object.assign({}, layer.params) // 将解析的‘/get/:id’ 中的id剥离出来
// 3:新增,主要是处理app.param
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err)
}
if (route) {
//调用route的dispatch方法,dispatch完成之后在此调用next,进行下一次循环
return layer.handle_request(req, res, next)
}
// 3:新增,加入handle_error处理
trim_prefix(layer, layerError, '', path)
})
function trim_prefix(layer, layerError, layerPath, path) {
if (layerPath.length !== 0) {
let c = path[layerPath.length]
if (c && c !== '/' && c !== '.')
return next(layerError)
}
if (layerError) {
layer.handle_error(layerError, req, res, next)
} else {
layer.handle_request(req, res, next)
}
}
}
}
/**
* 3:新增 获取除query部分的路径 例如: /get?id=12 --> /get
*/
function getPathname(req) {
try {
return parseUrl(req).pathname
} catch (e) {
return undefined
}
}
/**
* 判断url是否符合layer.path的规则
*/
function matchLayer(layer, path) {
try {
return layer.match(path);
} catch (err) {
return err;
}
}
/**
* 3:新增 对传过来的参数进行拦截,将参数拦截相关存入到params中,在handle中进行分解执行
*/
proto.param = function param(name, fn) {
if (typeof name === 'function') {
this
._params
.push(name)
return
}
if (name[0] === ':') {
name = name.substr(1)
}
let params = this._params
let len = this._params.length
let ret
for (let i = 0; i < len; i++) {
if (ret = params[i](name, fn)) {
fn = ret
}
}
(this.params[name] = this.params[name] || []).push(fn)
}
/**
* 3:新增 遍历params,匹配到合适的param,执行拦截
* @param {*} layer 当前layer
* @param {*} called 已经执行过的param
* @param {*} req
* @param {*} res
* @param {*} done
*/
proto.process_params = function process_params(layer, called, req, res, done) {
//包含参数和回调函数
let params = this.params
let keys = layer.keys
let keysIndex = 0
let key
let name
let paramcbIndex = 0
let paramcallbacks
let paramVal
let paramCalled
if (keys.length === 0) {
return done()
}
/**
* 3:新增 遍历当前url所对应的param,存于layer.keys中
* @param {} err
*/
function param(err) {
if (err) {
return done(err)
}
// 临界值判断,跳出循环
if (keysIndex >= keys.length) {
return done()
}
key = keys[keysIndex++]
name = key.name
paramcbIndex = 0
paramVal = req.params[name]
//拿到回调函数
paramcallbacks = params[name]
paramCalled = called[name]
if (paramVal === undefined || !paramcallbacks) {
return param()
}
if (paramCalled && (paramCalled.match === paramVal || (paramCalled.error && paramCalled.error !== 'route'))) {
req.params[name] = paramCalled.value
return param()
}
called[name] = paramCalled = {
error: null,
match: paramVal,
value: paramVal
}
paramCallback()
}
/**
* 3:新增 遍历当前param-->key 对应的functions --> callbacks
* @param {} err
*/
function paramCallback(err) {
//中间件回调函数为[fn]
let fn = paramcallbacks[paramcbIndex++]
paramCalled.value = req.params[key.name]
if (err) {
paramCalled.error = err
return param(err)
}
// 临界值,跳出循环
if (!fn) {
return param(err)
}
try {
// 执行中间件
fn(req, res, paramCallback, paramVal, name)
} catch (e) {
paramCallback(e)
}
}
param()
}
/**
* 3:新增 主要用于注册路由相关的中间件,此迭代中,在注册query中间件中使用到
* @param {*} fn
*/
proto.use = function use(fn) {
let path = '/'
let offset = 0
// 为app.use 接口准备,第一个参数可能时路径的正则表达式
if (typeof fn !== 'function') {
let arg = fn
while (Array.isArray(arg) && arg.length != 0) {
arg = arg[0]
}
if (typeof arg !== 'function') {
offset = 1
path = arg
}
}
let callbacks = slice.call(arguments, offset)
if (callbacks.length === 0) {
throw new TypeError('Router.use() requires a middleware function')
}
// 将中间件加入到stack栈中,方便handle函数遍历中执行
for (let i = 0; i < callbacks.length; i++) {
let fn = callbacks[i]
if (typeof fn !== 'function') {
throw new TypeError('Router.use() requires a middleware function but not a ' + gettype(fn))
}
let layer = new Layer(path, {
strict: false,
end: false
}, fn)
layer.route = undefined
this
.stack
.push(layer)
}
}
/**
* 3: 新增 获取对象的类型
* @param {} obj
*/
function gettype(obj) {
let type = typeof obj
if (type !== 'object') {
return type
}
return toString
.call(obj)
.replace(objectRegExp, '$1')
}
/**
* 3:新增 对obj对象的一些属性进行恢复出厂设置
* @param {*} fn 恢复值之后需要调用的函数
* @param {*} obj 需要恢复值的对象
* @param {*} augments[i+2] obj需要恢复的属性
*/
function restore(fn, obj) {
let props = new Array(arguments.length - 2)
let vals = new Array(arguments.length - 2)
// 保存函数调用时,obj对应属性的值
for (let i = 0; i < props.length; i++) {
props[i] = arguments[i + 2]
vals[i] = obj[props[i]]
}
return function () {
// 调用函数时,对obj属性值进行恢复
for (let i = 0; i < props.length; i++) {
obj[props[i]] = vals[i]
}
fn.apply(this, arguments);
}
}
// 4:新增 增加router.methods对外接口
//本质是调用route的get方法,并放到route的stack中,相当于app.get方法
methods
.forEach(function (method) {
method = method.toLowerCase()
proto[method] = function (path) {
let route = this.route(path)
route[method].apply(route, slice.call(arguments, 1))
return this
}
})