Sails.js 请求对象req.originalUrl详解:获取未被重写前的原始请求 URL
【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails
req.originalUrl是 Sails.js 请求对象(req)上的一个字符串属性,用于保留客户端最初请求的 URL,即使req.url在内部路由、策略(policy)或中间件中被重写,你依然可以拿到原始地址。本文以 Sails.js 仓库中的官方参考文档为主线,结合 请求对象构造源码 与 单元测试 深入讲解该属性的语义、底层实现与典型应用场景,帮助你在开发控制器、策略与自定义中间件时准确判断"用户到底访问了哪个地址"。
属性定义与官方语义
根据 Sails.js 对 Express 4.x API 的引用,req.originalUrl的定义如下:
该属性与
req.url非常相似,但它保留了原始请求 URL,允许你在内部路由时自由重写req.url。
用一句话概括:req.originalUrl是req.url的"不可变快照",它只反映客户端最初发起的请求地址;而在一次请求的完整生命周期中,req.url则可能被框架内部或开发者自己的代码改写。
Sails.js 在绝大多数情况下都建议优先使用req.url。只有当req.url被修改过(例如在某个策略或中间件里为了把请求转到内部路由而重写它)时,req.originalUrl才会体现出不可替代的价值——它始终给你那个"最初被请求的 URL"。
基本用法
req.originalUrl是一个只读字符串属性,直接访问即可,无需任何参数:
req.originalUrl; // => "/search"与req.url相同,它包含路径和查询字符串(query string)后缀,但不包含 URL 片段(fragment / hash,即#之后的部分)。例如:
// 客户端请求: GET /search?q=worlds%20largest%20dogs req.originalUrl; // => "/search?q=worlds%20largest%20dogs" req.url; // => "/search?q=worlds%20largest%20dogs"在未发生任何重写的情况下,req.originalUrl与req.url的值完全一致;二者的差异只会出现在req.url被修改之后。
源码实现:req.originalUrl从哪来
在 Sails.js 中,通用的请求对象由 lib/router/req.js 中的buildRequest(_req)工厂函数构造。该函数是 Sails 实现传输无关(transport-agnostic)中间件支持的基础,既服务于 socket.io 等 hook,也服务于应用级与内核级测试。
构造过程中,originalUrl的赋值逻辑位于 lib/router/req.js#L150:
req = defaultsDeep(req, { params: [], query: (_req && _req.query) || require('querystring').parse(parsedUrl.query) || {}, body: (_req && _req.body) || {}, param: function(paramName, defaultValue) { /* ... */ }, wantsJSON: (_req && _req.wantsJSON === false) ? false : true, method: 'GET', originalUrl: _req.originalUrl || _req.url, path: _req.path || parsedUrl.pathname }, _req||{});从源码结构可以看出两个关键点:
- 回退链:
originalUrl优先取传入的_req.originalUrl,若上层(例如真实的 HTTP 服务器)没有提供该字段,则回退为_req.url。也就是说,即使底层请求对象根本没有originalUrl概念,Sails 也能保证req.originalUrl始终是一个可用的字符串。 - 与
req.url同源:当请求进入 Sails 时,req.url由_req.url携带(见 lib/router/req.js#L76 构造 MockReq 时的url字段),而originalUrl正是以它作为初始快照。二者在请求生命周期的起点指向同一个地址。
此外,构造请求时 Sails 还会用parseurl解析原始 URL,以便从中分离出路径与查询字符串(lib/router/req.js#L38-L40),这与req.path、req.query的取值同源。
测试验证:单元测试如何锁定行为
Sails 仓库在 test/unit/req.test.js 中对req.originalUrl的行为有明确的断言。测试用buildReq({url: '/hello?abc=123&foo=bar'})构造一个虚拟请求,然后校验各属性:
it('.originalUrl', function() { req.originalUrl.should.be.an.String; req.originalUrl.should.equal('/hello?abc=123&foo=bar'); });与之并列的断言还验证了(test/unit/req.test.js#L87-L95):
req.path为/hello(仅路径,不含查询字符串);req.url为/hello?abc=123&foo=bar(路径 + 查询字符串);req.query被解析为{ abc: '123', foo: 'bar' }。
这组测试从侧面印证了一个事实:在未重写的情况下,req.originalUrl、req.url与"路径 + 查询串"三者保持严格一致;而req.path则是剥离查询串后的纯路径。
典型应用场景:当req.url被重写时
Sails 内部的路由与中间件机制中,req.url确实存在被改写的可能性。例如在 lib/router/index.js 的路由处理流程中,框架会基于req.url提取并合并查询参数(lib/router/index.js#L508-L510):
var queryStringPos = req.url.indexOf('?'); if (queryStringPos !== -1) { req.query = _.merge(req.query, QS.parse(req.url.substr(queryStringPos + 1))); }同时,lib/router/bind.js 中的skipRegexesWrapper也会用req.url.match(regexes[i])判断 URL 是否命中某些正则(lib/router/bind.js#L445-L449),用以跳过特定路由的处理器。这类对req.url的读取与潜在改写,正是req.originalUrl存在的意义。
常见的真实场景包括:
场景一:策略(policy)中的内部重定向
在某个策略里将请求改写后转发给内部路由:
// api/policies/redirect-legacy.js module.exports = function (req, res, next) { // 将旧路径重写为新的内部路由 req.url = '/search?q=' + req.param('q'); return next(); };此时在后续的 action 中,req.url已经是/search?...,但req.originalUrl仍保留用户最初请求的旧地址。若需要在日志、统计或鉴权逻辑中判断"用户最初访问了哪里",就必须读取req.originalUrl。
场景二:自定义中间件中的请求改写
与策略类似,在config/http.js中注册的自定义中间件也可能修改req.url以匹配内部路由。凡是你希望"重写后仍然知道原始地址"的地方,都应该访问req.originalUrl。
场景三:日志与审计
在记录请求日志时,同时输出两个字段可以完整还原请求的来龙去脉:
sails.log.info('Original URL: %s | Current URL: %s', req.originalUrl, req.url);注意事项
- 默认情况下请优先使用
req.url:正如官方文档反复强调的,绝大多数业务代码并不关心"原始 URL",req.url才是当前路由实际使用的地址。 - 查询字符串包含在内:
req.originalUrl与req.url一样包含?之后的查询字符串,与仅含路径的req.path不同。需要纯路径时,请使用req.path(对应源码中path: _req.path || parsedUrl.pathname的逻辑,见 lib/router/req.js#L151)。 - URL 片段(hash)不可用:
#之后的部分由浏览器端处理,不会随 HTTP 请求发送到服务器,因此req.originalUrl与req.url都取不到它;如果你用res.redirect()返回 302 响应,用户代理会在跳转后的地址上自动保留并追加原 URL 片段。 - 始终是字符串:单元测试中明确断言了
req.originalUrl.should.be.an.String(test/unit/req.test.js#L98),即便底层请求对象未提供该字段,也会通过回退_req.url保证其类型与可用性。
相关文档与参考
req.url:与req.originalUrl同源但可被重写的当前请求地址- 请求对象构造源码:
originalUrl的回退赋值逻辑(第 150 行) - 请求对象单元测试:对
req.originalUrl等属性的行为断言(第 97-100 行) - Sails 路由器核心实现:基于
req.url的查询参数解析流程 - 路由绑定源码:正则路由包装器对
req.url的匹配逻辑
【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考