在使用 koa 框架时,我们会去监听一个接口去启动服务,那~ 就是这句话
app.listen(PORT, () => {
console.log(`the web server is starting at port ${PORT}`);
});
在这过程中,koa 是如何实现它的呢?当然就需要借助到我们 node.js 原生 http 模块
http 模块使用
服务容器 server~,先大概看一下,这个服务容器大概就是用来放一些 处理请求的逻辑和响应请求的回调等就是服务内容。
- 建立了通信连接
- 指定了通信接口
- 提供了可自定内容服务容器,即服务的回调函数的容器
const http = require('http'); const PORT = 3001; const server = http.createServer((req, res) => { // TODO 容器内容 // TODO 服务回调内容 }) server.listen(PORT, function() { console.log(`the server is started at port ${PORT}`) })
然后是关于服务回调的内容
- 解析服务的请求 req
- 对请求内容作出响应 res
const router = (req, res) => { res.end(`this page url = ${req.url}`); }
req: 是服务回调中的第一个参数,主要是提供了HTTP请求request
的内容和操作内容的方法。
res: 是服务回调中的第二个参数,主要是提供了HTTP响应response
的内容和操作内容的方法。如果请求结束,一定要执行响应 res.end()
,要不然请求会一直等待阻塞,直至连接断掉页面崩溃。
最后是整体的一个实现,通过以上的描述,主要HTTP服务内容是在 “服务回调
” 中处理的,那我们来根据不同连接拆分一下,就形成了路由router
,根据路由内容的拆分,就形成了控制器 controller
。
const http = require('http'); const PORT = 3001; // 控制器 const controller = { index(req, res) { res.end('This is index page') }, home(req, res) { res.end('This is home page') }, _404(req, res) { res.end('404 Not Found') } } // 路由器 const router = (req, res) => { if( req.url === '/' ) { controller.index(req, res) } else if( req.url.startsWith('/home') ) { controller.home(req, res) } else { controller._404(req, res) } } // 服务 const server = http.createServer(router) server.listen(PORT, function() { console.log(`the server is started at port ${PORT}`) })
知道了 http 模块的使用,那么就来看看 koa.js 是怎么利用它实现的吧~~
服务模块封装~
const http = require('http'); // 这是引用原生 node.js http const Emitter = require('events'); // 引用原生 node.js events 事件模块 class WebServer extends Emitter { constructor() { super(); // 中间件数组 this.middleware = []; // 上下文对象 this.context = Object.create({}); } /** * 服务事件监听 * @param {*} args */ listen(...args) { // 创建一个服务容器 放入回调 const server = http.createServer(this.callback()); return server.listen(...args); } /** * 注册使用中间件 * @param {Function} fn */ use(fn) { if (typeof fn === 'function') { this.middleware.push(fn); } } /** * 中间件总回调方法 */ callback() { let that = this; if (this.listeners('error').length === 0) { this.on('error', this.onerror); } // 定义一个处理请求的方法 const handleRequest = (req, res) => { // 创建一个上下文 let context = that.createContext(req, res); // 循环中间件数组,cb就是某个中间件,执行每个中间件 this.middleware.forEach((cb, idx) => { try { cb(context); } catch (err) { that.onerror(err); } // 到了最后就结束请求 if (idx + 1 >= this.middleware.length) { if (res && typeof res.end === 'function') { res.end(); } } }); }; return handleRequest; } /** * 异常处理监听 * @param {EndOfStreamError} err */ onerror(err) { console.log(err); } /** * 创建通用上下文 * @param {Object} req * @param {Object} res */ createContext(req, res) { let context = Object.create(this.context); context.req = req; context.res = res; return context; } } module.exports = WebServer;
然后最后是对服务的使用
const WebServer = require('./index'); const app = new WebServer(); const PORT = 3001; app.use(ctx => { ctx.res.write('<p>line 1</p>'); }); app.use(ctx => { ctx.res.write('<p>line 2</p>'); }); app.use(ctx => { ctx.res.write('<p>line 3</p>'); }); app.listen(PORT, () => { console.log(`the web server is starting at port ${PORT}`); });
这时候,还没把之前(1)的东西加进来,即中间件的执行机制
那是少了哪一部分呢,当然是 compose 这块!即洋葱模式的执行方式,先进后出呗!
const compose = require('./compose');
那再想一想,什么时候去调用它呢?
这还用想~ 当然是在处理请求的时候生成的中间链并执行咯~
将上面的callback改成这样~
callback() { if (this.listeners('error').length === 0) { this.on('error', this.onerror); } const handleRequest = (req, res) => { let context = this.createContext(req, res); let middleware = this.middleware; // 执行中间件 compose(middleware)(context).catch(err => this.onerror(err)); }; return handleRequest; }
总的源码如下~
compose.js
module.exports = compose; function compose(middleware) { if (!Array.isArray(middleware)) { throw new TypeError('Middleware stack must be an array!'); } return function(ctx, next) { let index = -1; return dispatch(0); function dispatch(i) { if (i < index) { return Promise.reject(new Error('next() called multiple times')); } index = i; let fn = middleware[i]; if (i === middleware.length) { fn = next; } if (!fn) { return Promise.resolve(); } try { return Promise.resolve(fn(ctx, () => { return dispatch(i + 1); })); } catch (err) { return Promise.reject(err); } } }; }
index.js // 这个就把它想成是 koa.js 框架的入口吧
const http = require('http'); const Emitter = require('events'); const compose = require('./compose'); /** * 通用上下文 */ const context = { _body: null, get body() { return this._body; }, set body(val) { this._body = val; this.res.end(this._body); } }; class SimpleKoa extends Emitter { constructor() { super(); this.middleware = []; this.context = Object.create(context); } /** * 服务事件监听 * @param {*} args */ listen(...args) { const server = http.createServer(this.callback()); return server.listen(...args); } /** * 注册使用中间件 * @param {Function} fn */ use(fn) { if (typeof fn === 'function') { this.middleware.push(fn); } } /** * 中间件总回调方法 */ callback() { if (this.listeners('error').length === 0) { this.on('error', this.onerror); } const handleRequest = (req, res) => { let context = this.createContext(req, res); let middleware = this.middleware; // 执行中间件 compose(middleware)(context).catch(err => this.onerror(err)); }; return handleRequest; } /** * 异常处理监听 * @param {EndOfStreamError} err */ onerror(err) { console.log(err); } /** * 创建通用上下文 * @param {Object} req * @param {Object} res */ createContext(req, res) { let context = Object.create(this.context); context.req = req; context.res = res; return context; } } module.exports = SimpleKoa;
example 最后引入这个 koa.js 框架即 index.js 测试下~
const SimpleKoa = require('./index'); const app = new SimpleKoa(); const PORT = 3001; app.use(async ctx => { ctx.body = '<p>this is a body</p>'; }); app.listen(PORT, () => { console.log(`the web server is starting at port ${PORT}`); });
这么一来~ 一个最简单的 koa.js 框架就成了
https://chenshenhai.github.io/koajs-design-note/note/chapter01/07.html 原文学习