跳到主要内容

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. 中间件最佳实践

  1. 错误处理中间件放在最后:确保它能捕获所有错误
  2. 模块化中间件:将相关功能封装到单独的中间件模块中
  3. 使用第三方中间件:利用社区提供的成熟中间件解决方案
  4. 适当使用next():正确调用next()以控制流程
  5. 处理异步错误:确保异步操作中的错误被正确捕获并传递给next(err)
  6. 保持中间件简洁:每个中间件只做一件事
  7. 中间件命名:为中间件函数提供有意义的名称,以便调试
  8. 记录中间件性能:对于关键中间件,记录其执行时间

7. 练习项目

创建一个包含以下中间件的Express应用程序:

  1. 请求日志中间件
  2. 身份验证中间件
  3. 错误处理中间件
  4. 数据验证中间件
  5. API速率限制中间件

通过这个练习,你将深入理解Express中间件的工作原理和实际应用,为构建健壮的Web应用程序打下坚实的基础。