记一次不久前五一黄金周的事故。一直在测试环境和预发环境稳定发挥的第三方登录插件,在部署生产环境后却表现出不断重定向回本页的操作。扒了生产的log和插件源码却迟迟发现不了问题。打点的log日志报错完全正常符合自己埋log的预期,扒了几遍自己写的插件没有发现问题。找了好久才发现部署的同学填跳转url时没有带上协议导致的~QAQ。所以决定深入剖析重定向的原理来好好做一次复盘。
1、首先先复习一下浏览器是如何重定向的:
详细来说,301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)
2、对比一下各框架不同的重定向方法
在本地java起服务尝试一下java的重定向操作是否不一致:
这里用spring boot做了个简单的重定向转发,得出的结果是:
重定向的跳转地址还是本域名。不带协议的跳转url拼接到host后面作为路径名了。后续把路径名加上则跳转正确。
这里我们先记录spring boot(其实也就是servlet)的重定向返回的header是这个样子的:
然后再回到我们的egg来试下。
在egg中,通过middleware中加一层拦截来做重定向,简单模拟了下登录插件的逻辑。
ctx.redirect('www.baidu.com') // middleware中直接重定向到www.baidu.com
return
返回头部如下:
结果可以看到response header中Location直接set为根目录了。而每一次重定向回根路径又会走一次middleware,也就重现了无限重定向的事故。
现在我们不在middleware上操作。试试在controller中看看重定向的效果。
结果可以看到Location仍然为根路径。到这里我们可以大致推断出ctx.redirect的重定向逻辑是若重定向url不正确,就跳转到根目录。
最后一步,试下返回重定向请求时用原生写法塞url到response header试试。
async test() {
const { ctx } = this
const headers = {
Location: 'www.baidu.com',
'Content-Type': 'text/html; charset=utf-8'
}
ctx.status = 302
ctx.set(headers)
}
我们又发现熟悉的url拼到host后面了。这里我们也惊奇地发现浏览器对不合法重定向URL的处理是会把该URL拼接到当前域名后重新请求一次。
redirect(url, alt) { // koa中的ctx.redirect方法
// location
console.log(1, url) // 调试代码
if ('back' == url) url = this.ctx.get('Referrer') || alt || '/';
console.log(2, url) // 调试代码
this.set('Location', encodeUrl(url));
console.log(3, url) // 调试代码
// status
if (!statuses.redirect[this.status]) this.status = 302; // html
if (this.ctx.accepts('html')) {
url = escape(url);
this.type = 'text/html; charset=utf-8';
this.body = `Redirecting to <a href="${url}">${url}</a>.`;
return;
} // text
this.type = 'text/plain; charset=utf-8';
this.body = `Redirecting to ${url}.`;
},
在node_module中找到koa的依赖,找到其中的redirect实现方法。通过调试发现redirect函数并无对不合法url变更操作。所以是egg对koa封装那一层会对重定向url有一层过滤。