koa 中间件式HTTP服务实现(2)
在使用 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 原文学习

浙公网安备 33010602011771号