中间件示例代码讲解
这段Koa代码使用三个中间件,分别用于记录请求的URL和响应时间,计算请求处理时间,以及设置实际的响应体。
中间件是按照顺序执行的函数,每个中间件可以访问请求对象(ctx.request)和响应对象(ctx.response)
ts
复制代码
import Koa from 'koa'
const app = new Koa()
// 中间件1,next是一个函数,用于调用下一个中间件。在这个中间件中,它等待下一个中间件执行完成,然后记录请求的URL和响应时间。
app.use(async (ctx, next) => {
await next();
const time = ctx.response.get('X-Response-Time');
console.log(`${ctx.url} - ${time}`);
});
// 中间件2:它记录请求处理开始的时间(start),然后调用下一个中间件。当下一个中间件执行完成后,计算请求处理时间,并将其设置为响应头的X-Response-Time字段。
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const time = Date.now() - start;
ctx.set('X-Response-Time', `${time}ms`);
});
// 这是最后一个中间件,它设置响应体为字符串'Hello World'。在这个例子中,这是最终的响应。
app.use(async ctx => {
ctx.body = 'Hello World';
// await next()
});
app.listen(3000)
await next()
是什么意思
next()表示进入下一个函数
下一个函数会返回一个Promise对象,称为p
下一个函数所有代码执行完毕后,将p置为成功
await会等待p成功后,再回头执行剩余的代码
上图中1后面的await next()会等待4执行完毕
2后面的await next()会等待3执行完毕
js
复制代码
app.use(async (ctx, next) => {
const start = Date.now();
await next(); // 等待 下一个中间件()
const time = Date.now() - start;
ctx.set('X-Response-Time', `${time}ms`);
});
js
复制代码
app.use(async (ctx,next)=> {
const start = Date.now();
return next().then(()=> {
const time = Date.now() - start;
ctx.set('X-Response-Time',${time}ms)
});
}
一定要写return,因为中间件必返回Promise对象
错误处理在这里写有点反模式,用app.on('error')更方便一点
express如何实现相同的计时功能
思路一:用两个app.use加res.locals
js
复制代码
const express = require('express');
const app = express();
// 中间件1
app.use((req, res, next) => {
res.locals.start = new Date()
next()
});
// 中间件2
app.get('/', (req, res,next) => {
for (let i = 0; i < 10000; i++) {
res.write(i.toString())
}
res.write('Hello World');
res.end()
next()
});
// 中间件3
app.use((req, res, next) => {
const time = new Date().valueOf() - res.locals.start
console.log(`time: ${time}`)
next();
});
// 启动应用
app.listen(3001, () => {
console.log('Server listening on port 3001');
});
js
复制代码
const express = require('express');
const app = express();
const responseTime = require('response-time')
app.use(responseTime())
app.get('/', (req, res,next) => {
for (let i = 0; i < 10000; i++) {
res.write(i.toString())
}
res.write('Hello World');
res.end()
next()
});
// 这里只是打印一下时间
app.use((req, res, next) => {
console.log(res)
console.log(res.get('X-Response-Time'))
next()
});
// 启动应用
app.listen(3001, () => {
console.log('Server listening on port 3001');
});
通过阅读response-time的源码,我们可以发现它用了on-headers的库
js
复制代码
var onHeaders = require('on-headers')
return function responseTime (req, res, next) {
var startAt = process.hrtime()
// 监听了开始写header,只要开始write header就认为任务结束了
onHeaders(res, function onHeaders () {
var diff = process.hrtime(startAt)
var time = diff[0] * 1e3 + diff[1] * 1e-6
fn(req, res, time)
})
next()
}
查看on-headers
库的源码
它的作用会在一个响应即将写入头部时执行监听器
它的实现是改写了res.writeHead,创建一个新的 writeHead 函数(可以理解为一个假的writeHeader),中间实现了监听的逻辑
最终它还是会使用之前原始的writeHeader,
可以理解成面向切面编程,关注点是在调用 writeHead 函数时执行的一些逻辑,
而这个关注点被分离到了 listener 函数中。通过创建一个新的 writeHead 函数,
代码确保在执行 listener 之后仍能够调用原始的 writeHead 函数。
这种模式允许在不直接修改主业务逻辑的情况下,插入额外的逻辑。
js
复制代码
function createWriteHead (prevWriteHead, listener) {
var fired = false
// return function with core name and argument list
return function writeHead (statusCode) {
// set headers from arguments
var args = setWriteHeadHeaders.apply(this, arguments)
// fire listener
if (!fired) {
fired = true
listener.call(this)
// pass-along an updated status code
if (typeof args[0] === 'number' && this.statusCode !== args[0]) {
args[0] = this.statusCode
args.length = 1
}
}
return prevWriteHead.apply( , args)
}
}