看了慕课网双越老师的课以后结合本身的理解作了一些简单的总结,若有不恰当之处,欢迎指正。node
提到 express
就不得不提到中间件,接下来就简单的介绍一下 expres
中间件的简单应用与部分经常使用函数的实现。express
在平常项目的开发中,登陆验证是一个很是常见的场景,这个时候 express
中间件就能够派上用场了。接下来分别使用原生 node
和 express
中间件的方法实现简单的登陆验证。json
应用场景:在获取博客列表以前要进行登陆验证,只有在登陆状态下才能够获取博客列表。api
原生 node.js
实现登陆验证:数组
if(method === 'GET' && req.path === '/api/blog/list') { // 若req.session.username有值说明已经登陆 if (req.session.username){ // 登陆状态下能够获取博客列表 获取博客列表 } }
express
中间件实现登陆验证:服务器
function loginCheck(req, res, next) { if (某个登陆成功的条件) { next(); // 登陆成功执行next } else { res.json({ errno: -1, msg: 'login fail' }) } } // 若在登陆状态下,loginCheck会执行next函数,从而执行第二个中间件,获取博客列表。 app.get('/api/blog/list', loginCheck, (req, res, next) => { 得到博客列表 })
虽然上面这个简单的例子中看上去彷佛原生 node.js
要更简单一些,可是使用中间件的方式封装性更好,在一些比价复杂的项目中就能体现出优点了。cookie
use, get, post, next, listen
等方法的实现结合上面的流程图和最后给出的完整代码,简单说明一下实现的过程:session
这里以 use
(在实现思路上和 get
, post
是同样的)为例说明:app
获取路径和中间件函数
use
将用户调用时传入的参数传给 register
函数,register
函数将获取到参数后分离出路径和中间两部分,路径存放在 info.path
中,中间件存放在 info.stack
中。而后 register
函数将分离开的路径和中间件再返回给 use
,use
拿到分离后的路径和中间件以后,会将其赋值给 constructor
函数中 this.routes.all
。
注意:register 在分离路径时须要注意的一点是对于没有路径只有中间件的参数会将其路径赋值为根路径
/
listen
方法的实现
使用 express
时调用 app.listen(3000)
实际上不仅是监听 3000
端口还建立了一个 http
服务,参数是一个回调函数,代码以下:
listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); }
res.json
方法的实现
在回调函数 callback
中首先实现一个 res.json
方法,实现方式也比较简单。
res
的 Header
的 Content-type
为 application/json
,res.end({JSON.stringify(data)})
将用户传入的 data
对象转换为 JSON
字符串输出到屏幕上。获取须要执行的中间件
这部分经过 match
函数实现。经过 req.url
和 req.method.toLowerCase
来获取须要执行的中间件。中间实现的方法就有不少了,思路就是经过method
找出相应的路径和中间件,这个已经在 this.routes
中分类存储了。而后找出路径和中间件以后利用 indexOf
方法,
if (url.indexOf(routeInfo.path) === 0)
若是 if
判断成立说明知足条件,那么须要执行的中间件也就找出来了。
app.next
方法的实现
这部分经过 handle
函数实现。思路:定义一个 next
函数,在那些须要执行的中间件中,先取出第一个,若是不为空那么开始执行,三个参数分别为 req, res, next
。后面须要执行的中间件到底执不执行取决于前一个中间件的程序中是否调用了 next
方法。代码以下:
const next = () => { // shift 取得数组的第一项 const middleware = stack.shift(); if (middleware) { // 执行中间件函数 middleware(req, res, next); } }; // 定义完后当即执行 next();
注意:在定义完next函数以后,必须当即执行一次next函数,不然第一个须要执行的中间件也不会执行了。
中间件中传入的参数中的next,就是在这里定义的next函数,若是前一个中间件程序中执行了next函数,那么会再执行
stack.shift
取出须要执行的中间件中的下一个中间件。
附上完整代码:
const http = require('http'); const slice = Array.prototype.slice; class LikeExpress { constructor() { // 存放路径和中间件,all中存放的是调用use传入的路径和中间件 this.routes = { all: [], get: [], post: [] } } // 分离出路径和中间件,info.path中存放路径,info.stack 中存放中间件 register(path) { const info = {}; if (typeof path === 'string') { info.path = path; // info.stack 存放全部的中间件 // 若是第一个参数是路由在取中间件时就要从数组的第2个位置开始取 // slice.call(arguments, 1) 的做用就是取arguments从第二个位置开始以后的全部元素都取出来并变成数组的形式。 info.stack = slice.call(arguments, 1); } else { // 若是第一个参数不是一个路由,那么咱们就假定第一个参数是一个根路由 info.path = '/'; info.stack = slice.call(arguments, 0); } return info; } use() { // 实际使用时,参数是经过use传递进来的 // 将全部的参数传入到register函数中 const info = this.register.apply(this, arguments); // info 是一个对象,info.path 中存放的是路径,info.stack 中存放的是中间件 this.routes.all.push(info); } get() { const info = this.register.apply(this, arguments); this.routes.get.push(info); } post() { const info = this.register.apply(this, arguments); this.routes.post.push(info); } // 匹配use,get或post方法会执行的中间件 match(method, url) { let stack = []; if (url === '/favicon.ico') { return stack; } // 获取routes let curRoutes = []; // concat 是数组中的一个方法,若是没有参数,那么会生成一个当前数组的副本并将其赋值给前面的变量,若是有参数会将参数加入到生成的副本的后面而后将其赋值给变量 // 若是是use,那么就把use中的路径和中间列表复制到curRoutes中 // 若是方法是get或post那么下面这句话,因为this.routes.all是undefined,因此会将当前curRoutes生成一个副本赋值给curRoutes,其实一点变化都没有 curRoutes = curRoutes.concat(this.routes.all); // 若是是get或post,那么就把相应的路径和中间件复制到curRoutes中 curRoutes = curRoutes.concat(this.routes[method]); curRoutes.forEach(routeInfo => { // url='/api/get-cookie' routeInfo.path='/' // url='/api/get-cookie' routeInfo.path='/api' // url='api/get-cookie' routeInfo.path='/api/get-cookie' if (url.indexOf(routeInfo.path) === 0) { // 匹配成功 stack = stack.concat(routeInfo.stack); } }); return stack; } // 核心的 next 机制 handle(req, res, stack) { const next = () => { // 拿到第一个匹配的中间件 // shift 取得数组的第一项 const middleware = stack.shift(); if (middleware) { // 执行中间件函数 middleware(req, res, next); } }; // 定义完后当即执行 next(); } // callback是一个(req, res) => {} 的函数,结合http-test中的代码去理解 callback() { return (req, res) => { // res.json 是一个函数,在express中使用时传入一个对象便可在屏幕中显示出来,这里实现了这个功能 res.json = (data) => { res.setHeader('Content-type', 'application/json'); res.end( JSON.stringify(data) ); }; const url = req.url; const method = req.method.toLowerCase(); // 找到须要执行的中间件(经过路径和method,有可能一个须要执行的中间件也没有) const resultList = this.match(method, url); this.handle(req, res, resultList); } } // express 中listen的做用不单单是监听端口,还要建立服务器 listen(...args) { const server = http.createServer(this.callback()); server.listen(...args); } } module.exports = () => { return new LikeExpress() };
完