狭义中间件-请求\响应拦截
koa-logger 实现
前言
狭义中间件,请求/拦截 最显著的特征是
- 直接被
app.use()
- 拦截请求
- 操作响应
最简单的场景是 Koa.js 官方支持传输静态文件中间件的实现koa-logger
。
本节主要以官方的
koa-logger
中间件为参考,实现了一个最简单的koa-logger
实现,方便原理讲解和后续二次自定义优化开发。
实现步骤
- step 01 拦截请求,打印请求 URL
- step 02 操作响应,打印响应 URL
实现源码
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-04-01
const logger = async function(ctx, next) {
let res = ctx.res;
// 拦截操作请求 request
console.log(`<-- ${ctx.method} ${ctx.url}`);
await next();
// 拦截操作响应 request
res.on('finish', () => {
console.log(`--> ${ctx.method} ${ctx.url}`);
});
};
module.exports = logger
koa-send 实现
前言
狭义中间件,请求/拦截 最显著的特征是
- 直接被
app.use()
- 拦截请求
- 操作响应
最典型的场景是 Koa.js 官方支持传输静态文件中间件的实现koa-send
。
主要实现场景流程是
- 拦截请求,判断该请求是否请求本地静态资源文件
- 操作响应,返回对应的静态文件文本内容或出错提示
本节主要以官方的
koa-send
中间件为参考,实现了一个最简单的koa-end
实现,方便原理讲解和后续二次自定义优化开发。
实现步骤
- step 01 配置静态资源绝对目录地址
- step 02 判断是否支持隐藏文件
- step 03 获取文件或者目录信息
- step 04 判断是否需要压缩
- step 05 设置HTTP头信息
- step 06 静态文件读取
实现源码
demo源码
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-04-02
koa-send 源码解读
const fs = require('fs');
const path = require('path');
const {
basename,
extname
} = path;
const defaultOpts = {
root: '',
maxage: 0,
immutable: false,
extensions: false,
hidden: false,
brotli: false,
gzip: false,
setHeaders: () => {}
};
async function send(ctx, urlPath, opts = defaultOpts) {
const { root, hidden, immutable, maxage, brotli, gzip, setHeaders } = opts;
let filePath = urlPath;
// step 01: normalize path
// 配置静态资源绝对目录地址
try {
filePath = decodeURIComponent(filePath);
// check legal path
if (/[\.]{2,}/ig.test(filePath)) {
ctx.throw(403, 'Forbidden');
}
} catch (err) {
ctx.throw(400, 'failed to decode');
}
filePath = path.join(root, urlPath);
const fileBasename = basename(filePath);
// step 02: check hidden file support
// 判断是否支持隐藏文件
if (hidden !== true && fileBasename.startsWith('.')) {
ctx.throw(404, '404 Not Found');
return;
}
// step 03: stat
// 获取文件或者目录信息
let stats;
try {
stats = fs.statSync(filePath);
if (stats.isDirectory()) {
ctx.throw(404, '404 Not Found');
}
} catch (err) {
const notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR']
if (notfound.includes(err.code)) {
ctx.throw(404, '404 Not Found');
return;
}
err.status = 500
throw err
}
let encodingExt = '';
// step 04 check zip
// 判断是否需要压缩
if (ctx.acceptsEncodings('br', 'identity') === 'br' && brotli && (fs.existsSync(filePath + '.br'))) {
filePath = filePath + '.br';
ctx.set('Content-Encoding', 'br');
ctx.res.removeHeader('Content-Length');
encodingExt = '.br';
} else if (ctx.acceptsEncodings('gzip', 'identity') === 'gzip' && gzip && (fs.existsSync(filePath + '.gz'))) {
filePath = filePath + '.gz';
ctx.set('Content-Encoding', 'gzip');
ctx.res.removeHeader('Content-Length');
encodingExt = '.gz';
}
// step 05 setHeaders
// 设置HTTP头信息
if (typeof setHeaders === 'function') {
setHeaders(ctx.res, filePath, stats);
}
ctx.set('Content-Length', stats.size);
if (!ctx.response.get('Last-Modified')) {
ctx.set('Last-Modified', stats.mtime.toUTCString());
}
if (!ctx.response.get('Cache-Control')) {
const directives = ['max-age=' + (maxage / 1000 | 0)];
if (immutable) {
directives.push('immutable');
}
ctx.set('Cache-Control', directives.join(','));
}
const ctxType = encodingExt !== '' ? extname(basename(filePath, encodingExt)) : extname(filePath);
ctx.type = ctxType;
// step 06 stream
// 静态文件读取
ctx.body = fs.createReadStream(filePath);
}
module.exports = send;
const send = require('./index');
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
await send(ctx, ctx.path, { root: `${__dirname}/public` });
});
app.listen(3001, () => {
console.log('listening on port 3001');
});
http://localhost:3001/index.html
koa-static 实现
前言
狭义中间件 请求/拦截,最典型的场景是 Koa.js 传输静态文件中间件的实现koa-send
。Koa.js 官方对 koa-send
进行二次封装,推出了koa-static
中间件,目标是用于做静态服务器或者项目静态资源管理。
本节主要以官方的
koa-static
中间件为参考,基于上一节实现的最简单koa-send
, 实现了一个最简单的koa-static
中间件,方便原理讲解和后续二次自定义优化开发。
实现步骤
- step 01 配置静态资源绝对目录地址
- step 02 判断是否支持等待其他请求
- step 03 判断是否为 GET 和 HEAD 类型的请求
- step 04 通过
koa-send
中间件读取和返回静态文件
实现源码
demo源码
https://github.com/chenshenhai/koajs-design-note/tree/master/demo/chapter-04-03
koa-static 依赖
koa-send
中间件,这里只用了上一节实现的最简单`koa-send
koa-static 解读
const {resolve} = require('path');
const send = require('./send');
function statics(opts = {
root: ''
}) {
opts.root = resolve(opts.root);
// 是否需要等待其他请求
if (opts.defer !== true) {
// 如果需要等待其他请求
return async function statics(ctx, next) {
let done = false;
if (ctx.method === 'HEAD' || ctx.method === 'GET') {
try {
await send(ctx, ctx.path, opts);
done = true;
} catch (err) {
if (err.status !== 404) {
throw err;
}
}
}
if (!done) {
await next();
}
};
} else {
// 如果不需要等待其他请求
return async function statics(ctx, next) {
await next();
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') {
return;
}
if (ctx.body != null || ctx.status !== 404) {
return;
}
try {
await send(ctx, ctx.path, opts);
} catch (err) {
if (err.status !== 404) {
throw err;
}
}
};
}
}
module.exports = statics;