咱们在开发的过程当中,或多或少会用到Node.js,好比用Node.js在本地起一个静态文件服务器等。可是Node.js 的 API 对开发者来讲并非很是友好。例如,若是咱们想从服务器发送一个 JPEG 图片的话,可能须要至少 四五 行代码才行。建立可复用 HTML 模版则更复杂。另外,Node.js 的 HTTP 模块虽然强大,可是仍然缺乏一些实用特性。 Express 的出现就是为了解决这些问题,让咱们可以高效的使用 Node.js 来编写 Web 应用。javascript
从大的方面来讲,Express 为 Node.js 的 HTTP 模块带来了两大特性:html
下面咱们说几个express的几个经常使用api:java
根据请求路径来处理客户端发出的GET等各类请求。第一个参数path为请求的路径, 第二个参数为处理请求的回调函数。git
let express = require('express');
let app = express();
app.listen(8080, () => {
console.log('started success');
});
app.get('/', function (req, res) {
res.end('ok');
});
复制代码
中间件就是处理HTTP请求的函数,用来完成各类特定的任务,好比检查用户是否登陆、检测用户是否有权限访问等。github
app.use中放入的函数称为中间件函数,通常有三个特色:express
const express = require('../lib/express');
const app = express();
app.use(function (req, res, next) {
console.log('Ware1:', Date.now());
next('wrong');
});
app.get('/', function (req, res, next) {
res.end('1');
});
const user = express.Router();
user.use(function (req, res, next) {
console.log('Ware2', Date.now());
next();
});
user.use('/2', function (req, res, next) {
res.end('2');
});
app.use('/user', user);
app.use(function (err, req, res, next) {
res.end('catch ' + err);
});
app.listen(3000, function () {
console.log('server started at port 3000');
});
复制代码
监听客户端向服务器发送请求的函数api
批量处理相同参数数组
const express = require('../lib/express');
const app = express();
app.param('uid',function(req,res,next,val,name){
req.user = {id:1,name:'Lucy'};
console.log('1');
next();
})
app.param('uid',function(req,res,next,val,name){
req.user.name = 'Tom';
next();
})
app.get('/user/:uid',function(req,res){
console.log(req.user);
res.end('user');
});
app.listen(3000);
复制代码
设置参数,好比渲染模板的时候咱们会常用到。bash
app.set('views', path.resolve(path.join(__dirname, 'views')));
app.set('view engine', 'html');
复制代码
规定何种文件用何种方法来渲染服务器
app.engine('html', html);
复制代码
简单的介绍了集中经常使用api的用法,接下来就要开始进入主题了,那就是根据express源码,模拟express框架,实现上述的集中api。
本次模拟实现的api有app.get()
、app.use()
、app.listen()
、app.param()
、 app.render()
、app.set()
、app.engine()
。
项目结构以下:
lib/
|
| - middle/
| | - init.js 内置中间件
|
| - route/
| | - index.js 路由系统
| | - layer.js 层
| | - route.js 路由
|
| - application.js 应用
| - html.js 模板引擎
| - express.js 入口
|
test/ 这里放入的是测试用例
|
复制代码
接下来咱们一一介绍一下express的实现逻辑。由于express都是经过app来操做的,即express.js文件是express的入口,express.js的代码实现很简单,就是导出一个Application的实例。express把主要的方法放在Application上面了,咱们先来张Application的概览图,来直观的感觉下,以下图:
Application上的属性
为了便于描述咱们将Application
的实例称为app
(下同)。app
是实现express功能的入口,顺着图中第一个箭头的方向,app._router
属性指向一个Router
的实例(灰色背景部分),app._router
是一个路由系统,这个路由系统中会管理客户端发来请求的回调函数的执行。Router
上的属性也位于左侧的一栏文字中,咱们先来解释一下属性(一样的,黑色部分为实例上的属性,红色加粗部分为原型上的属性)。
Router上的属性
Layer
的实例(Layer
下面接着会介绍)在咱们处理客户端发来请求的回调函数的过程当中,主要靠的是循环app._router.stack
中的每一层(如图中的layer一、layer二、layer3)来实现,那么每一层究竟是什么呢?我我的根据处理的逻辑把layer作了一个分类,包括三类:路由层、中间件层、具备子路由系统的中间件层。咱们详细介绍一下这三个类:
Layer上的属性
/user/getlist
Route
的实例key
组成的数组path
是否和请求的url地址匹配this.handler
属性对应的方法next()
函数传来的参数路由层是经过app.get(path, handler)
订阅的,该层会经过app._router.stack.push()
放入到app._router.stack
中,app._router.stack
是一个数组,存放的是各类层(layer),包括后面的中间件层也会放到app._router.stack
中。须要注意的是路由层的route属性指向是一个Route
的实例,而且在new Layer
的时候将Route
的实例上的dispatch
方法做为第二个参数传递给Layer
,以下代码:
let route = new Route(path);
let layer = new Layer(path, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
复制代码
须要注意的是路由层的route
属性指向一个Route
的实例。
Route的属性
app.get(path, handlers)
中handlers中的单个handler。app._router.stack
中的layer的handler属性指向的是route.dispatch.bind(route)
stack
中的方法的集合[method]
方法最终是派发给route
中来实现,因此这个方法就是将派发来的[method]
方法push
到stack
中app.use(path, handler)
订阅的,该层也会放入到
app._router.stack
中。须要注意的是该层的
route
属性为
undefined
。
app._router
所属的类是同一个类,因此这个子路由系统跟
app._router
具备相同的属性和相同的原型上的方法。这一层也是经过
app.use()
订阅的,可是稍有不一样,以下代码:
//中间件层 的订阅方式
app.use('/', function (req, res, next) {
console.log('Ware1:', Date.now());
next('wrong');
});
// 具备子路由系统的中间件层 的订阅方式
const user = express.Router();
user.use(function (req, res, next) {
console.log('Ware2', Date.now());
next();
});
user.use('/2', function (req, res, next) {
res.end('2');
});
复制代码
当请求函数走到这一层的时候,this.handler
执行时会进入到图中箭头指向的灰色背景部分,即子路由系统,这个子路由系统中的stack也是存放的是子路由系统中订阅的函数。
介绍了这么多,到底这些Router
、Layer
、Route
等是如何配合工做的?下面咱们详细介绍一下。
当客户端发起请求的时候app就会派发给_router.handle
执行,_router.handle
的逻辑就是把订阅在_router.stack
中的handler依次执行,以下图:
接下来我把_router.stack
里面每个layer时的执行书序逻辑图抽离出来,以下图:
express还有一个功能就是能够实现模板引擎,实现的代码逻辑以下:
let head = "let tpl = ``;\nwith (obj) {\n tpl+=`";
str = str.replace(/<%=([\s\S]+?)%>/g, function () {
return "${" + arguments[1] + "}";
});
str = str.replace(/<%([\s\S]+?)%>/g, function () {
return "`;\n" + arguments[1] + "\n;tpl+=`";
});
let tail = "`}\n return tpl; ";
let html = head + str + tail;
let fn = new Function('obj', html);
let result = fn(options);
复制代码
写到这里,express框架的经常使用api已经介绍完了,本文只是介绍了实现逻辑,具体的项目代码以及测试用例请参见个人GitHub。
参考文献