有关重定向的一些细节

2020/6/14

众所周知,重定向很简单🤥

3开头的HTTP状态码都比较有意思,比如与缓存有关的304,比如与重定向有关的301、302。正好最近做项目遇到了一些重定向有关的内容,稍微整理一下。

# 重定向的基本流程

重定向的流程是这样的(以302临时重定向为例,因为最常用,下同):

client               server
  |      request       |
  |------------------->|
  |        302         |
  |<-------------------|
  |     new request    |
  |------------------->|
  |      response      |
  |<-------------------|

可见实际上是发了2次请求,第一次请求后,服务端返回302状态码,前端浏览器再朝着跳转目的地址发起一个新的请求。跳转的目的地址是在第一次请求响应的location头部指定的。

location header

location没有限制一定得是同域的URL,因此跳转到其他域下面是可以的。

需要注意2点:

  1. 要想能够成功重定向,状态码和location头部二者缺一不可。
  2. 重定向这个操作是后端控制的,浏览器会自动识别跳转场景然后重新向跳转地址发起新请求。整个过程对前端是透明的,也就是在前端眼中就像是只有一次请求一样,仿佛前面的请求是空气。

# 可以多次重定向

重定向可以有多次,比如连续的302,此时流程大致就是这样:

client               server
  |      request       |
  |------------------->|
  |        302         |
  |<-------------------|
  |     new request    |
  |------------------->|
  |        302         |
  |<-------------------|
           ...
  |     new request    |
  |------------------->|
  |        302         |
  |<-------------------|
  |     new request    |
  |------------------->|
  |      response      |
  |<-------------------|

这是个什么场景呢?就是location对应的URL又返回了302和新的location,如此重复直到不再跳转位置。

当然,为了防止出现无限重定向的情况,重定向的次数是有上限的。经过测试Chrome浏览器的重定向次数是20,也就是一次请求如果超过20次重定向,就会报ERR_TOO_MANY_REDIRECT错误:

too many redirect

另外在实际测试的过程中偶然发现即使页面已经报TOO_MANY_REDIRECT了,chrome还是会“不死心”地过一段时间再跳转几次😂,如果新的跳转最终停下来了,页面就不再报TOO_MANY_REDIRECT,所以理论上这个重试的上限是其实并不是那么严格(这里只测试了Chrome)。

# 浏览器会忽略掉重定向请求的body

浏览器如果发现当前请求的响应要重定向,则会直接忽略掉response的body,无法在开发者工具中的network面板上看到body,这点跟浏览器对跨域请求的处理很像。

redirect no response body

这里有一个比较迷惑的地方,如果你看network面板,会发现response的size很小,好像只有header一样:

redirect response size

最开始在测试的时候我也在想“会不会是后端没有返回body?”,然后用curl测试了一下,是可以拿到body的。所以说明这是浏览器给忽略了(或者说是丢弃了)

举一个实际中的例子:比如后端在GET一个页面重定向的时候除了返回302之外,还在body里返回了一个页面(希望做loading效果),实际上浏览器在最终的页面请求返回之前始终是白屏,那个中间页面是不会生效的,因为被浏览器丢弃了。不知道有什么办法能拿到body,反正我是没有找到。

仔细想一想,这样还是蛮合理的,你都重定向了,自然也压根不用关心返回了啥,只要告诉我新的目标地址就行了,少啰嗦。本身重定向就是对前端透明的,前端看来就像是一次请求一样。

似乎只要是3开头的HTTP响应状态码,浏览器都会忽略body?🤨

既然如此,在实现后端的时候,看到3开头的请求,也别返回什么body了,只返回header就行了。

# 不光普通的页面GET是可以重定向的,AJAX同样可以重定向

比如我们做个试验,启动一个node server:

const http = require('http');
http.createServer((req, res) => {
  console.log(req.method, req.url);

  if (req.url === '/redirect') {
    res.writeHead(302, {
      location: 'http://localhost:8848/done',
    });
  } else {
    res.writeHead(200);
    res.write('hello');
  }

  res.end();
}).listen(8848);

在浏览器中发起一个AJAX请求:

redirect POST

可以看到返回的内容是hello

如果你理解了上文提到的重定向的流程,那你应该就自然能够理解下面的几个问题了:

  1. 如果AJAX请求在跳转期间也有数据返回(response body),最终的请求以哪个数据为准?
  2. 如果AJAX请求是上传一个文件,那这个文件会被上传几次?
  3. 既然location可以是不同域的,那么可以通过302实现跨域AJAX吗?
  4. 对于GET页面的请求,重定向后第二次发起的请求里的refer是最开始的页面的URL还是第一次请求的URL?

答案是:

  1. 跳转期间的body都会被浏览器忽略掉,只有最后一次的body是有效的。
  2. 发了几次请求就会上传几次文件,因为浏览器会在跳转时发起同样的新请求。所以如果有这样的场景,千万别跳转,太蛋疼了😑。
  3. 不能,因为跳转的本质上就是多发了一次请求,该有的跨域限制一样还是会有,如果跳转过程出现了跨域问题,那后面的请求也就不会继续了。
  4. 是最开始的页面的URL,因为重定向相当于是重新发了一次请求,请求所属页面的URL并没有变化。

上面那个node的例子,如果你观察server端的log,你会发现: