您现在的位置是:网站首页 > 路由系统与URL处理文章详情
路由系统与URL处理
陈川
【
Node.js
】
45312人已围观
6587字
路由系统与URL处理是Express框架的核心功能之一,它决定了如何响应客户端对不同URL的请求。通过路由系统,开发者可以定义应用程序的端点(URI)以及对这些端点的响应方式。URL处理则涉及解析、匹配和响应请求的路径和参数,是构建动态Web应用的基础。
路由基础
Express的路由系统基于HTTP方法和URL路径进行匹配。每个路由可以包含一个或多个处理函数,这些函数在路由匹配时执行。基本的路由定义如下:
const express = require('express');
const app = express();
// 基本GET路由
app.get('/', (req, res) => {
res.send('首页');
});
// 基本POST路由
app.post('/submit', (req, res) => {
res.send('表单已提交');
});
路由方法对应于HTTP方法,常见的有get
、post
、put
、delete
等。路由路径可以是字符串、字符串模式或正则表达式。
路由路径与参数
路由路径可以包含动态参数,这些参数会被捕获并存储在req.params
对象中。动态参数通过冒号:
标识:
// 带参数的路由
app.get('/users/:userId', (req, res) => {
res.send(`用户ID: ${req.params.userId}`);
});
// 多个参数
app.get('/posts/:year/:month', (req, res) => {
res.send(`文章发布于 ${req.params.year}年${req.params.month}月`);
});
参数名称只能包含字母、数字和下划线。Express还支持通过正则表达式定义更复杂的路径匹配规则:
// 使用正则表达式限制参数格式
app.get('/products/:id(\\d+)', (req, res) => {
res.send(`产品ID必须是数字: ${req.params.id}`);
});
路由处理程序
路由处理程序可以是单个函数,也可以是多个函数组成的数组或链式调用。多个处理函数会按顺序执行,除非某个函数终止了请求-响应周期:
// 多个处理函数
app.get('/example',
(req, res, next) => {
console.log('第一个处理函数');
next(); // 控制权传递给下一个函数
},
(req, res) => {
res.send('第二个处理函数响应');
}
);
// 使用数组定义处理函数
const middleware1 = (req, res, next) => { /* ... */ };
const middleware2 = (req, res, next) => { /* ... */ };
app.get('/array', [middleware1, middleware2]);
路由模块化
随着应用规模扩大,可以将路由组织到单独的模块中。Express的Router
类可以创建模块化的路由处理程序:
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('用户列表');
});
router.get('/:id', (req, res) => {
res.send(`用户详情: ${req.params.id}`);
});
module.exports = router;
// 主文件
const userRouter = require('./routes/users');
app.use('/users', userRouter);
app.use()
方法将路由挂载到特定路径前缀下,使得路由结构更加清晰。
URL查询参数
除了路径参数,Express还自动解析URL中的查询字符串,结果存储在req.query
对象中:
// 处理/search?q=express&page=2
app.get('/search', (req, res) => {
const { q, page } = req.query;
res.send(`搜索: ${q}, 页码: ${page || 1}`);
});
查询参数不需要在路由路径中声明,它们总是可选的。对于复杂的查询参数,可以使用qs
模块进行深度解析:
// 启用复杂查询解析
app.set('query parser', 'extended');
路由中间件
路由可以应用特定的中间件,这些中间件只对特定路由生效。这在权限控制、数据验证等场景非常有用:
// 认证中间件
const authenticate = (req, res, next) => {
if (req.headers.authorization) {
next();
} else {
res.status(401).send('未授权');
}
};
// 应用中间件到特定路由
app.get('/admin', authenticate, (req, res) => {
res.send('管理员面板');
});
中间件可以精确控制到HTTP方法和路径,实现细粒度的请求处理。
路由路径匹配规则
Express的路由路径匹配遵循特定规则:
- 字符串路径必须完全匹配(除了大小写不敏感)
?
、+
、*
和()
不是正则表达式字符,而是字符串的一部分- 连字符
-
和点号.
按字面解释 - 可以通过正则表达式实现更灵活的匹配
// 匹配/acd和/abcd
app.get('/ab?cd', (req, res) => {
res.send('匹配/acd或/abcd');
});
// 匹配/abcd、/abbcd、/abbbcd等
app.get('/ab+cd', (req, res) => {
res.send('匹配一个或多个b');
});
// 匹配/abcd、/abxcd、/abANYcd等
app.get('/ab*cd', (req, res) => {
res.send('匹配ab和cd之间的任意字符');
});
// 匹配/abe和/abcde
app.get('/ab(cd)?e', (req, res) => {
res.send('匹配/abe或/abcde');
});
错误处理路由
Express提供了特殊的路由处理错误情况。错误处理中间件需要四个参数:
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('服务器错误');
});
// 异步错误处理示例
app.get('/async', async (req, res, next) => {
try {
await someAsyncOperation();
res.send('成功');
} catch (err) {
next(err); // 传递给错误处理中间件
}
});
错误处理路由应该定义在所有其他路由之后,作为最后的处理环节。
路由的响应方法
Express的响应对象res
提供了多种发送响应的方法:
app.get('/response', (req, res) => {
// 发送文本
res.send('普通文本');
// 发送JSON
res.json({ message: 'JSON响应' });
// 发送文件
res.sendFile('/path/to/file.pdf');
// 重定向
res.redirect('/new-location');
// 设置状态码
res.status(404).send('未找到');
// 设置响应头
res.set('Content-Type', 'text/plain');
});
路由的链式调用
对于同一路径的不同HTTP方法,可以使用route()
方法进行链式调用:
app.route('/book')
.get((req, res) => {
res.send('获取书籍信息');
})
.post((req, res) => {
res.send('添加新书籍');
})
.put((req, res) => {
res.send('更新书籍信息');
});
这种方法使代码更加紧凑,特别适合RESTful API设计。
静态文件路由
Express内置了express.static
中间件用于处理静态文件:
// 提供public目录下的静态文件
app.use(express.static('public'));
// 带虚拟路径前缀
app.use('/static', express.static('public'));
// 多个静态目录
app.use(express.static('public'));
app.use(express.static('files'));
静态文件中间件会按顺序查找文件,直到找到匹配的文件为止。
路由的加载顺序
Express按照路由定义的顺序进行匹配,先定义的路由优先匹配。这一点在处理通配符路由时特别重要:
// 这个路由会拦截所有/users开头的请求
app.get('/users*', (req, res) => {
res.send('通用用户路由');
});
// 这个路由永远不会被匹配到
app.get('/users/:id', (req, res) => {
res.send('特定用户路由');
});
正确的做法是将特定路由放在通用路由之前:
app.get('/users/:id', (req, res) => {
res.send('特定用户路由');
});
app.get('/users*', (req, res) => {
res.send('通用用户路由');
});
路由的性能考虑
路由匹配的性能对应用整体性能有重要影响:
- 尽量减少通配符路由的使用
- 将最常访问的路由放在前面
- 对静态路由使用字符串匹配而非正则表达式
- 避免在路由处理函数中进行同步阻塞操作
// 性能较差的写法
app.get(/^\/([a-z0-9-]+)$/, (req, res) => {
// 复杂的正则匹配
});
// 性能更好的写法
app.get('/:slug([a-z0-9-]+)', (req, res) => {
// 使用命名参数限制格式
});
路由与RESTful API设计
Express的路由系统非常适合实现RESTful API。典型的资源路由设计如下:
// 用户资源路由
app.route('/api/users')
.get((req, res) => { /* 获取用户列表 */ })
.post((req, res) => { /* 创建新用户 */ });
app.route('/api/users/:id')
.get((req, res) => { /* 获取单个用户 */ })
.put((req, res) => { /* 更新用户 */ })
.delete((req, res) => { /* 删除用户 */ });
这种设计遵循了REST原则,使API结构清晰、易于理解。
路由的版本控制
对于长期维护的API,路由版本控制是常见需求。可以通过路径前缀或请求头实现:
// 路径前缀版本控制
app.use('/v1/users', v1UserRouter);
app.use('/v2/users', v2UserRouter);
// 请求头版本控制
app.use('/api/users', (req, res, next) => {
const version = req.headers['api-version'] || 'v1';
if (version === 'v1') return v1UserRouter(req, res, next);
if (version === 'v2') return v2UserRouter(req, res, next);
next();
});
版本控制策略应根据项目需求选择,保持一致性最重要。
路由的测试
测试路由时,可以使用supertest
等库模拟HTTP请求:
const request = require('supertest');
const app = require('../app');
describe('GET /users', () => {
it('应返回用户列表', async () => {
const res = await request(app).get('/users');
expect(res.statusCode).toEqual(200);
expect(res.body).toBeInstanceOf(Array);
});
});
测试应该覆盖各种边界情况,包括无效参数、缺失参数等场景。
路由的安全考虑
路由设计需要考虑多种安全因素:
- 验证所有输入参数
- 防范SQL注入
- 限制敏感路由的访问
- 使用HTTPS传输敏感数据
// 参数验证示例
app.get('/products', (req, res, next) => {
const page = parseInt(req.query.page) || 1;
if (page < 1) {
return res.status(400).send('页码必须大于0');
}
next();
});
安全措施应该作为路由设计的一部分,而非事后补充。
上一篇: Express的授权许可与开源协议
下一篇: 中间件机制与执行流程