模拟express中间件
const http = require('http') const slice = Array.prototype.slice class LikeExpress { constructor() { // 存放中间件的列表 this.routes = { all: [], // app.use(...) get: [], // app.get(...) post: [] // app.post(...) } } register(path) { const info = {} if (typeof path === 'string') { info.path = path // 从第二个参数开始,转换为数组,存入 stack info.stack = slice.call(arguments, 1) } else { info.path = '/' // 从第一个参数开始,转换为数组,存入 stack info.stack = slice.call(arguments, 0) } return info } use() { const info = this.register.apply(this, arguments) 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) } match(method, url) { let stack = [] if (url === '/favicon.ico') { return stack } // 获取 routes let curRoutes = [] curRoutes = curRoutes.concat(this.routes.all) curRoutes = curRoutes.concat(this.routes[method]) curRoutes.forEach(routeInfo => { if (url.indexOf(routeInfo.path) === 0) { // url === '/api/get-cookie' 且 routeInfo.path === '/' // url === '/api/get-cookie' 且 routeInfo.path === '/api' // url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie' stack = stack.concat(routeInfo.stack) } }) return stack } // 核心的 next 机制 handle(req, res, stack) { const next = () => { // 拿到第一个匹配的中间件 const middleware = stack.shift() if (middleware) { // 执行中间件函数 middleware(req, res, next) } } next() } callback() { return (req, res) => { res.json = (data) => { res.setHeader('Content-type', 'application/json') res.end( JSON.stringify(data) ) } const url = req.url const method = req.method.toLowerCase() const resultList = this.match(method, url) this.handle(req, res, resultList) } } listen(...args) { const server = http.createServer(this.callback()) server.listen(...args) } } // 工厂函数 module.exports = () => { return new LikeExpress() }
const express = require('./like-express') // 本次 http 请求的实例 const app = express() app.use((req, res, next) => { console.log('请求开始...', req.method, req.url) next() }) app.use((req, res, next) => { // 假设在处理 cookie console.log('处理 cookie ...') req.cookie = { userId: 'abc123' } next() }) app.use('/api', (req, res, next) => { console.log('处理 /api 路由') next() }) app.get('/api', (req, res, next) => { console.log('get /api 路由') next() }) // 模拟登录验证 function loginCheck(req, res, next) { setTimeout(() => { console.log('模拟登陆成功') next() }) } app.get('/api/get-cookie', loginCheck, (req, res, next) => { console.log('get /api/get-cookie') res.json({ errno: 0, data: req.cookie }) }) app.listen(8000, () => { console.log('server is running on port 8000') })
模拟koa2中间件
const http = require('http') // 组合中间件 function compose(middlewareList) { return function (ctx) { function dispatch(i) { const fn = middlewareList[i] try { return Promise.resolve( fn(ctx, dispatch.bind(null, i + 1)) // promise ) } catch (err) { return Promise.reject(err) } } return dispatch(0) } } class LikeKoa2 { constructor() { this.middlewareList = [] } use(fn) { this.middlewareList.push(fn) return this } createContext(req, res) { const ctx = { req, res } ctx.query = req.query return ctx } handleRequest(ctx, fn) { return fn(ctx) } callback() { const fn = compose(this.middlewareList) return (req, res) => { const ctx = this.createContext(req, res) return this.handleRequest(ctx, fn) } } listen(...args) { const server = http.createServer(this.callback()) server.listen(...args) } } module.exports = LikeKoa2
const Koa = require('./like-koa2'); const app = new Koa(); // logger app.use(async (ctx, next) => { await next(); const rt = ctx['X-Response-Time']; console.log(`${ctx.req.method} ${ctx.req.url} - ${rt}`); }); // x-response-time app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; ctx['X-Response-Time'] = `${ms}ms`; }); // response app.use(async ctx => { ctx.res.end('This is like koa2'); }); app.listen(8000);
命令行下分别执行 node x-app.js
访问各注册路由查看结果