The basic structure and writing specifications of middleware
Basic Structure of Middleware
Koa2 middleware is essentially an asynchronous function that takes two parameters: ctx
and next
. ctx
encapsulates the request and response objects, while next
represents the execution control of the next middleware. Middleware uses async/await
syntax to handle asynchronous operations, forming an onion-model call chain.
async function middleware(ctx, next) {
// Pre-processing
console.log('Before next()');
await next(); // Execute downstream middleware
// Post-processing
console.log('After next()');
}
The typical structure consists of three phases:
- Request pre-processing: Modify
ctx
state or intercept requests. next()
call: Transfer control to downstream.- Response post-processing: Modify response data or log records.
Writing Standards
Single Responsibility Principle
Each middleware should handle only a specific function. For example, an authentication middleware should not contain business logic:
// Good practice
async function auth(ctx, next) {
const token = ctx.headers['authorization'];
if (!verifyToken(token)) {
ctx.throw(401, 'Unauthorized');
}
await next();
}
// Anti-pattern
async function authAndProcess(ctx, next) {
// Mixing authentication with business logic
const token = ctx.headers['authorization'];
if (verifyToken(token)) {
ctx.user = getUser(token);
await processBusiness(ctx); // Business logic mixed in
}
await next();
}
Error Handling Standards
Synchronous errors are caught automatically, while asynchronous errors require explicit handling:
// Error handling middleware example
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: err.message,
code: err.code
};
ctx.app.emit('error', err, ctx); // Trigger application-level error event
}
});
// Business middleware throwing an error
app.use(async ctx => {
if (!ctx.query.requiredParam) {
const err = new Error('Missing parameter');
err.code = 'MISSING_PARAM';
err.status = 400;
throw err;
}
});
Performance Optimization Tips
- Avoid blocking operations: Long synchronous tasks should be converted to asynchronous.
- Use caching wisely: Cache results of high-frequency operations.
- Control the number of middleware: Each request traverses the entire middleware stack.
// Caching example
const cache = new Map();
app.use(async (ctx, next) => {
const key = ctx.url;
if (cache.has(key)) {
ctx.body = cache.get(key);
return;
}
await next();
if (ctx.status === 200) {
cache.set(key, ctx.body);
}
});
Advanced Patterns
Middleware Factory Functions
Create configurable middleware using closures:
function logger(format = ':method :url') {
return async (ctx, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log(format
.replace(':method', ctx.method)
.replace(':url', ctx.url)
.replace(':duration', duration)
);
};
}
app.use(logger());
app.use(logger(':method :url - :duration ms'));
Composing Middleware
Use koa-compose
to create sub-middleware stacks:
const compose = require('koa-compose');
const middleware1 = async (ctx, next) => {
console.log('Middleware 1 start');
await next();
console.log('Middleware 1 end');
};
const middleware2 = async (ctx, next) => {
console.log('Middleware 2 start');
await next();
console.log('Middleware 2 end');
};
const combined = compose([middleware1, middleware2]);
app.use(combined);
Response Time Extension
Extend custom methods via context prototype:
// Extend ctx prototype
app.context.formatResponse = function(data) {
this.type = 'application/json';
this.body = {
timestamp: Date.now(),
data: data,
status: this.status
};
};
// Using the extended method
app.use(async ctx => {
const users = await User.findAll();
ctx.formatResponse(users);
});
Debugging and Testing
Debugging Techniques
- Use the
debug
module to label middleware. - Insert debugging middleware to inspect context state.
const debug = require('debug')('koa:middleware');
app.use(async (ctx, next) => {
debug('Request headers:', ctx.headers);
const start = Date.now();
await next();
debug(`Response time: ${Date.now() - start}ms`);
});
Unit Test Example
Test middleware behavior using supertest
:
const request = require('supertest');
const Koa = require('koa');
describe('Auth Middleware', () => {
it('should 401 without token', async () => {
const app = new Koa();
app.use(authMiddleware);
app.use(ctx => { ctx.body = 'OK' });
await request(app.callback())
.get('/')
.expect(401);
});
});
Best Practices
Configuration Management
Share configurations via app.context
:
// During initialization
app.context.config = {
maxRequests: 1000,
timeout: 3000
};
// Usage in middleware
app.use(async (ctx, next) => {
if (ctx.request.count > ctx.config.maxRequests) {
ctx.throw(429);
}
await next();
});
Rate Limiting Implementation
Token bucket algorithm middleware example:
class TokenBucket {
constructor(rate, capacity) {
this.tokens = capacity;
this.rate = rate;
this.lastFill = Date.now();
}
take() {
this.refill();
if (this.tokens < 1) return false;
this.tokens -= 1;
return true;
}
refill() {
const now = Date.now();
const elapsed = (now - this.lastFill) / 1000;
this.tokens = Math.min(
this.capacity,
this.tokens + elapsed * this.rate
);
this.lastFill = now;
}
}
// Token bucket middleware usage
const bucket = new TokenBucket(10, 20); // 10 tokens/second, 20 max
app.use(async (ctx, next) => {
if (!bucket.take()) {
ctx.status = 429;
ctx.body = 'Too Many Requests';
return;
}
await next();
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:性能监控工具的初步设置
下一篇:同步与异步中间件的区别