Koa 在众多NodeJs框架中,以短小精悍而著称,核心代码只有大约570行,非常适合源码阅读。
实际上核心来说,Koa主要是两块
ctx
对象。本文就核心阅读中间件的源码。
中间件可以理解为插件,对于Koa来说,就是很简单的use()
API。
const Koa = require(‘koa’);
const app = new Koa();
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
app.listen(3000);
甚至实际应用必不可少的路由,对Koa来说也是一个中间件。
const Koa = require(‘koa’);
const Router = require(‘koa-router’);
const app = new Koa();
const router = new Router();
router.get(‘/‘, (ctx, next) => {
// ctx.router available
});
app
.use(router.routes())
.use(router.allowedMethods());
原生Node实现一个Http Server很是简单:
const http = require(‘http’);
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
抽象Koa调用如下
class Koa {
middleware = [];
// 监听端口,开启服务
public listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
// 收集中间件
public use(fu) {
this.middleware.push(fn);
return this;
}
// 请求callback
private callback() {
const fn = compose(this.middleware)
const handleRequest = (req, res) => {
const ctx = this.createContext(req,res);
return this.handleRequest(ctx, fn)
}
}
private handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err)
const handleResponse = () => this.respond(ctx)
onFinished(res, onerror); // 做收尾工作,例如关闭文件,socket链接等
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
// 集中处理请求响应的收尾,减少重复业务代码
private respond(ctx) {
// allow bypassing koa
if (false === ctx.respond) return;
if (!ctx.writable) return;
const res = ctx.res;
let body = ctx.body;
const code = ctx.status;
// ignore body
if (statuses.empty[code]) {
// strip headers
ctx.body = null;
return res.end();
}
if ('HEAD' == ctx.method) {
if (!res.headersSent && isJSON(body)) {
ctx.length = Buffer.byteLength(JSON.stringify(body));
}
return res.end();
}
// status body
if (null == body) {
if (ctx.req.httpVersionMajor >= 2) {
body = String(code);
} else {
body = ctx.message || String(code);
}
if (!res.headersSent) {
ctx.type = 'text';
ctx.length = Buffer.byteLength(body);
}
return res.end(body);
}
// responses
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);
// body: json
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
}
是不是比想象的还要简单😄
Koa最为人称道的就是这点。甚至Koa在GitHub中的简介只是:
Expressive middleware for node.js using ES2017 async functions
下面这张图很好的表达了什么是”洋葱模型“。
洋葱的每一层就是中间件。这种精巧结构的实现实际上不在Koa源码中,是由koajs/compose 这个独立的库实现的,源码更加的简单。
'use strict'
/**
* Expose compositor.
*/
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
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(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
需要注意的是
Promise.resolve()
支持,同步和异步函数,因此中间件函数也都支持同步和异步函数。next()
时间上就是下一个中间件函数,如果你不调用,之后的其它中间件都不会调用了。实现上compose
这个简单精巧的函数在前端界很有名了,Redux的插件系统也是取经于此。
Koa 在众多NodeJs框架中,以短小精悍而著称,核心代码只有大约570行,非常适合源码阅读。
实际上核心来说,Koa主要是两块
ctx
对象。本文就核心阅读中间件的源码。
中间件可以理解为插件,对于Koa来说,就是很简单的use()
API。
const Koa = require(‘koa’);
const app = new Koa();
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
app.listen(3000);
甚至实际应用必不可少的路由,对Koa来说也是一个中间件。
const Koa = require(‘koa’);
const Router = require(‘koa-router’);
const app = new Koa();
const router = new Router();
router.get(‘/‘, (ctx, next) => {
// ctx.router available
});
app
.use(router.routes())
.use(router.allowedMethods());
原生Node实现一个Http Server很是简单:
const http = require(‘http’);
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
抽象Koa调用如下
class Koa {
middleware = [];
// 监听端口,开启服务
public listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
// 收集中间件
public use(fu) {
this.middleware.push(fn);
return this;
}
// 请求callback
private callback() {
const fn = compose(this.middleware)
const handleRequest = (req, res) => {
const ctx = this.createContext(req,res);
return this.handleRequest(ctx, fn)
}
}
private handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
const onerror = err => ctx.onerror(err)
const handleResponse = () => this.respond(ctx)
onFinished(res, onerror); // 做收尾工作,例如关闭文件,socket链接等
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
// 集中处理请求响应的收尾,减少重复业务代码
private respond(ctx) {
// allow bypassing koa
if (false === ctx.respond) return;
if (!ctx.writable) return;
const res = ctx.res;
let body = ctx.body;
const code = ctx.status;
// ignore body
if (statuses.empty[code]) {
// strip headers
ctx.body = null;
return res.end();
}
if ('HEAD' == ctx.method) {
if (!res.headersSent && isJSON(body)) {
ctx.length = Buffer.byteLength(JSON.stringify(body));
}
return res.end();
}
// status body
if (null == body) {
if (ctx.req.httpVersionMajor >= 2) {
body = String(code);
} else {
body = ctx.message || String(code);
}
if (!res.headersSent) {
ctx.type = 'text';
ctx.length = Buffer.byteLength(body);
}
return res.end(body);
}
// responses
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);
// body: json
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
}
是不是比想象的还要简单😄
Koa最为人称道的就是这点。甚至Koa在GitHub中的简介只是:
Expressive middleware for node.js using ES2017 async functions
下面这张图很好的表达了什么是”洋葱模型“。
洋葱的每一层就是中间件。这种精巧结构的实现实际上不在Koa源码中,是由koajs/compose 这个独立的库实现的,源码更加的简单。
'use strict'
/**
* Expose compositor.
*/
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
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(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
需要注意的是
Promise.resolve()
支持,同步和异步函数,因此中间件函数也都支持同步和异步函数。next()
时间上就是下一个中间件函数,如果你不调用,之后的其它中间件都不会调用了。实现上compose
这个简单精巧的函数在前端界很有名了,Redux的插件系统也是取经于此。