作者:勇哥
时间:20200409
一、最小的web系统
web服务中最小的功能系统,包含两部分:
- HTTP Server HTTP服务器,处理请求和响应。
- Router 路由解析,对不同的请求返回不同的数据。
Node.js原生http模块使用
const http = require('http');
const app = http.createServer((req, res) => {
res.end('hello wold!');
});
app.listen(3000, () => {
console.log('the app is started at port 3000');
});
http服务构成
请求 req
提供HTTP请求request的内容和操作的方法
响应 res
提供HTTP响应response操作的方法
http服务的拆分
对不同的请求进行拆分,形成路由 router。
const http = require('http');
// 路由拆分
const router = (req, res) => {
if (req.url === '/') {
res.end('I am index page!');
} else if (req.url.startsWith('/home')) {
res.end('I am home page!');
} else {
res.end('I am 404 page!');
}
}
const app = http.createServer(router);
app.listen(3000, () => {
console.log('the app is started at port 3000');
});
对路由进行拆分形成 controller
const http = require('http');
// 控制器
const controller = {
index(req, res) {
res.end('I am index page!');
},
home(req, res) {
res.end('I am home page!');
},
_404(req, res) {
res.end('I am 404 page!');
}
};
// 路由拆分
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 app = http.createServer(router);
app.listen(3000, () => {
console.log('the app is started at port 3000');
});
二、中间件引擎实现
Koa 中间件使用
koa2中间件使用如下代码所示:
const Koa = require('koa');
let app = new Koa();
const middleware1 = async (ctx, next) => {
console.log(1);
await next();
console.log(6);
}
const middleware2 = async (ctx, next) => {
console.log(2);
await next();
console.log(5);
}
const middleware3 = async (ctx, next) => {
console.log(3);
await next();
console.log(4);
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.use(async (ctx, next) => {
ctx.body = 'hello world'
})
app.listen(3000);
//控制台输出
// 1
// 2
// 3
// 4
// 5
// 6
依次输出1、2、3、4、5、6,koa.js通过洋葱模型来实现这样一个中间件引擎。下面将一步步实现这样的一个中间件引擎。
中间件原理
读者可以先自行了解下洋葱模型,这里不再进行讲解。中间件在 await next() 前后的操作,和数据结构中栈相似,先进后出。下面先用Promise做简单的实现:
let context = {
data: []
};
async function middleware1(ctx, next) {
console.log('action 1');
ctx.data.push(1);
await next();
console.log('action 6');
ctx.data.push(6);
}
async function middleware2(ctx, next) {
console.log('action 2');
ctx.data.push(2);
await next();
console.log('action 5');
ctx.data.push(5);
}
async function middleware3(ctx, next) {
console.log('action 3');
ctx.data.push(3);
await next();
console.log('action 4');
ctx.data.push(4);
}
Promise.resolve(middleware1(context, async () => {
return Promise.resolve(middleware2(context, async () => {
return Promise.resolve(middleware3(context, async () => {
return Promise.resolve();
}))
}))
})).then(() => {
console.log('context = ', context.data);
})
// result
// action 1
// action 2
// action 3
// action 4
// action 5
// action 6
//context = [1,2,3,4,5,6]
中间件引擎 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);
}
}
};
}
测试如下:
let middleware = [];
let context = {
data: []
};
middleware.push(async (ctx, next) => {
console.log('action 1');
ctx.data.push(1);
await next();
console.log('action 6');
ctx.data.push(6);
});
middleware.push(async (ctx, next) => {
console.log('action 2');
ctx.data.push(2);
await next();
console.log('action 5');
ctx.data.push(5);
});
middleware.push(async (ctx, next) => {
console.log('action 3');
ctx.data.push(3);
await next();
console.log('action 4');
ctx.data.push(4);
});
const fn = compose(middleware);
fn(context)
.then(() => {
console.log('context = ', context);
});
// result
// action 1
// action 2
// action 3
// action 4
// action 5
// action 6
//context = [1,2,3,4,5,6]
三、最简koa实现
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 Koa extends Emitter {
constructor() {
super();
this.middleware = [];
this.context = Object.create(context);
}
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
// 注册中间件
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;
}
// 异常捕获
onerror(err) {
console.log(err);
}
// 上下文
createContext(req, res) {
let context = Object.create(this.context);
context.req = req;
context.res = res;
return context;
}
}
module.exports = Koa;
至此实现了一个最简单的koa模型,koa2中还对一些API进行了扩展和处理,后面会继续进行分析。