Express中间件深入解析
1. 中间件基础概念
中间件函数是可以访问请求对象(req)、响应对象(res)以及应用程序请求-响应循环中的下一个中间件函数的函数。下一个中间件函数通常由名为next的变量表示。
中间件函数可以执行以下任务:
- 执行任何代码
- 修改请求和响应对象
- 结束请求-响应循环
- 调用堆栈中的下一个中间件函数
如果当前中间件函数没有结束请求-响应循环,它必须调用next()将控制权传递给下一个中间件函数,否则,请求将被挂起。
2. 中间件类型
Express应用程序可以使用以下类型的中间件:
2.1 应用级中间件
绑定到Express应用程序实例的中间件:
const express = require('express')
const app = express()
// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})
// 挂载在 /user/:id 路径的中间件,任何指向 /user/:id 的请求都会执行它
app.use('/user/:id', (req, res, next) => {
console.log('Request Type:', req.method)
next()
})
// 路由和处理函数(中间件系统)
app.get('/user/:id', (req, res, next) => {
res.send('USER')
})
2.2 路由级中间件
路由级中间件与应用级中间件类似,但它绑定到express.Router()实例:
const express = require('express')
const app = express()
const router = express.Router()
// 没有挂载路径的中间件,通过该路由器的每个请求都会执行该中间件
router.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})
// 挂载在 /user/:id 路径的中间件,任何指向 /user/:id 的请求都会执行它
router.use('/user/:id', (req, res, next) => {
console.log('Request URL:', req.originalUrl)
next()
}, (req, res, next) => {
console.log('Request Type:', req.method)
next()
})
// 将路由器挂载到应用程序
app.use('/', router)
2.3 错误处理中间件
错误处理中间件始终有四个参数(err, req, res, next)。必须提供四个参数以将其标识为错误处理中间件:
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
2.4 内置中间件
Express 4.x之后,Express不再依赖于Connect,而是提供了一些内置的中间件:
- express.static:提供静态文件服务
- express.json:解析JSON格式的请求体
- express.urlencoded:解析URL编码的请求体
2.5 第三方中间件
要使用第三方中间件,需要先安装它,然后在应用程序中将其加载到应用级别或路由级别:
npm install cookie-parser
const express = require('express')
const app = express()
const cookieParser = require('cookie-parser')
// 加载cookie解析中间件
app.use(cookieParser())
3. 中间件执行流程
了解中间件的执行流程对于正确编写Express应用程序至关重要。
3.1 基本执行顺序
中间件按照它们被定义的顺序执行:
const express = require('express')
const app = express()
app.use((req, res, next) => {
console.log('First middleware')
next()
})
app.use((req, res, next) => {
console.log('Second middleware')
next()
})
app.get('/', (req, res) => {
res.send('Hello World!')
})
当访问根路径时,控制台输出将是:
First middleware
Second middleware
3.2 路由和中间件的组合
路由处理程序可以看作是一种特殊类型的中间件,只在特定的HTTP方法和路径匹配时执行:
const express = require('express')
const app = express()
// 应用级中间件
app.use((req, res, next) => {
console.log('Time:', Date.now())
next()
})
// 路由级中间件
app.get('/user/:id', (req, res, next) => {
if (req.params.id === '0') next('route')
else next()
}, (req, res) => {
res.send('regular')
})
// 处理上述路由中调用 next('route') 的情况
app.get('/user/:id', (req, res) => {
res.send('special')
})
4. 中间件高级用法
4.1 中间件链
多个中间件可以链接在一起,形成处理管道:
app.use('/user/:id',
(req, res, next) => {
console.log('Authenticating...')
next()
},
(req, res, next) => {
console.log('Authorizing...')
next()
},
(req, res) => {
res.send('User profile')
}
)
4.2 中间件数组
对于可重用的中间件组合,可以使用数组:
const validateUser = [
(req, res, next) => {
if (!req.body.username) {
return res.status(400).json({ error: 'Username is required' })
}
next()
},
(req, res, next) => {
if (!req.body.password) {
return res.status(400).json({ error: 'Password is required' })
}
next()
}
]
app.post('/users', validateUser, (req, res) => {
res.status(201).json({ message: 'User created' })
})
4.3 路由特定中间件
为特定路由应用专用中间件:
const authMiddleware = (req, res, next) => {
// 验证用户是否已登录
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' })
}
next()
}
// 只有登录用户才能访问此路由
app.get('/dashboard', authMiddleware, (req, res) => {
res.json({ dashboard: 'data' })
})
4.4 异步中间件
处理异步操作的中间件:
const asyncMiddleware = fn => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next)
}
}
app.get('/data', asyncMiddleware(async (req, res) => {
const data = await fetchSomeData()
res.json(data)
}))
5. 常见中间件模式
5.1 日志中间件
记录请求信息:
const logger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
next()
}
app.use(logger)
5.2 错误处理中间件
集中处理应用程序中的错误:
// 开发环境错误处理
const devErrorHandler = (err, req, res, next) => {
res.status(err.status || 500)
res.json({
error: err.message,
stack: err.stack
})
}
// 生产环境错误处理
const prodErrorHandler = (err, req, res, next) => {
res.status(err.status || 500)
res.json({
error: 'Something went wrong'
})
}
app.use(process.env.NODE_ENV === 'development' ? devErrorHandler : prodErrorHandler)
5.3 CORS中间件
处理跨域资源共享:
const cors = (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
next()
}
app.use(cors)
5.4 速率限制中间件
限制API的访问频率:
const rateLimit = {
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 限制每个IP在15分钟内最多请求100次
requests: {}
}
const rateLimiter = (req, res, next) => {
const ip = req.ip
const now = Date.now()
if (!rateLimit.requests[ip]) {
rateLimit.requests[ip] = []
}
// 移除过期的请求
rateLimit.requests[ip] = rateLimit.requests[ip].filter(
timestamp => now - timestamp < rateLimit.windowMs
)
if (rateLimit.requests[ip].length >= rateLimit.max) {
return res.status(429).json({ error: 'Too many requests' })
}
rateLimit.requests[ip].push(now)
next()
}
app.use(rateLimiter)
6. 中间件最佳实践
- 错误处理中间件放在最后:确保它能捕获所有错误
- 模块化中间件:将相关功能封装到单独的中间件模块中
- 使用第三方中间件:利用社区提供的成熟中间件解决方案
- 适当使用
next():正确调用next()以控制流程 - 处理异步错误:确保异步操作中的错误被正确捕获并传递给
next(err) - 保持中间件简洁:每个中间件只做一件事
- 中间件命名:为中间件函数提供有意义的名称,以便调试
- 记录中间件性能:对于关键中间件,记录其执行时间
7. 练习项目
创建一个包含以下中间件的Express应用程序:
- 请求日志中间件
- 身份验证中间件
- 错误处理中间件
- 数据验证中间件
- API速率限制中间件
通过这个练习,你将深入理解Express中间件的工作原理和实际应用,为构建健壮的Web应用程序打下坚实的基础。