当Ajax遇上302, 孽缘!

什么是 HTTP 状态码 302 ?

根据MDN的文档

HTTP 302Found 重定向状态码表明请求的资源被暂时的移动到了由 Location 头部指定的 URL 上。浏览器会重定向到这个URL, 但是搜索引擎不会对该资源的链接进行更新

以及

有时候请求的资源无法从其标准地址访问,但是却可以从另外的地方访问。在这种情况下可以使用临时重定向。

搜索引擎不会记录该新的、临时的链接。在创建、更新或者删除资源的时候,临时重定向也可以用于显示临时性的进度页面。


302的使用场景

在 express.js 中,res.redirect(url) 实际上就是设置了302 HTTP status

app.get('/doc', (req, res) => {
    const redirectUrl = '/temp-doc';
    res.redirect(redirectUrl);
})

可以暂时将 /doc 重定向到另一个api /temp-doc

另外,在 OAuth 流程中,302 也被多次用到 (参考我关于Oauth的文章 FreewheelLee:非常详细!深入理解OAuth原理和实践细节



当 AJAX 遇上 302

大多数 302 的使用场景都是比较简单直接且同步的 —— 即用户在浏览器里输入一个URL,这个URL的背后服务器返回一个302状态和Location header,浏览器就自动重定向到Location指定的新地址上。

但是随着Ajax的广泛使用,尤其是SPA(单页面应用)的流行,越来越多的网络请求都是通过Ajax发起的,而Ajax的目标URL如果返回 302 就开始带来一些问题。


AJAX + 302 + CORS !?

首当其冲的就是跨域问题。

以OAuth流程为例

如果使用 Ajax 对以下这个 /login API 发起请求,就会发生CORS 错误。

而在浏览器中直接输入 /login 完整地址则是没问题的。

浏览器直接输入
Ajax 调用出错

为什么呢?—— 因为跨域了。

细节:

  1. 用户打开目标网站,假设为

2. 某个动作触发了 AJAX 发起请求

2. 后端返回 302 ,Location 值为

3. 浏览器 自动 把 Ajax 的目标url 切成新的地址,即 发生 Ajax 请求到

4. 由于这是个Ajax请求,且用户处在 you-app-domain 的域名上,而请求地址为 github 域名,因此这是个跨域请求

5. 显然 github 不可能随便对其他域名打开 CORS 限制,因此这个 ajax 就失败了。(对 CORS 不熟悉的读者请自行搜索学学习)


痛点

AJAX + 302 + CORS 问题的痛点在于 —— 浏览器 自动 发起对 重定向地址的请求,js 无法插手干预。


解决方案

浏览器的“自动行为”其实是因为 302 状态码的设计规范,换句话说,如果不使用 302 就能解决这种“自动行为”,因此,

解决方案一 —— 返回200而不是302

后端API可以返回200状态码的同时在内容中加入重定向地址的信息,如

app.get('/login', (req, res) => {
    const redirectUrl = `https://github.com/login/oauth/authorize?client_id=xxxx`;
    res.status(200);
    res.send({
        redirectUrl: redirectUrl