彻底搞懂 HTTP 3XX 重定向状态码和浏览器重定向

·  阅读 1237

最近好好研究了一下 HTTP 的 3XX 状态码,发现这里面涉及到的知识点还挺多的,很多在我们日常开发过程中也会用到,于是就想通过这篇文章来和大家聊一聊它们的含义以及应用场景。

3XX 状态码是关于重定向的,常见的状态码有:301,302,303,304,307 和 308。这些状态码大致可以分为三类,其中包括:

  • 永久重定向:301,308
  • 临时重定向:302,303, 307
  • 其他重定向:304

这篇文章主要分析永久重定向和临时重定向相关的状态码,至于其他重定向就不在这里细讲了。

PS: 正文里提到的 3XX 响应只包含永久重定向和临时重定向所对应的 HTTP 状态码,不包括其他重定向。

开始

我们既需要明白服务器发送的各种重定向状态码是什么含义,还需要搞清楚浏览器收到这些重定向状态码之后会怎么做。因此本文会从服务器和浏览器两个部分进行讲解。

服务器

永久重定向

永久重定向,是指用户请求的资源地址已经废弃了,现在需要使用新地址来访问,并通过响应 Header 的 Location 字段将这个新的地址告知给用户。

就好比胡同口有一家我们常去的饭店,但是由于旧城改造这家店搬迁到了另一个地方。有一天我们准备去这家店吃饭,发现门口张贴告示:此店已迁移到 XXX,并附上了详细的新地址。也就是说,之后如果我还想去这家店吃饭,只能去新的地址。

301 Moved Permanently

表示请求资源已经被移动到了由 Location 头部指定的 URI 上,是固定的不会再改变。在用新 URL 发起请求时,对于 GET 请求保持不变,但是对于 POST 请求,尽管标准要求浏览器在收到该响应时不应该修改 HTTP Method 和 Request Body,但是大多数浏览器没有遵守这个标准,会把原本为 POST 的请求重定向到 GET 请求上。

我们拿 Chrome 浏览器测试了一下,结果如下:

image.png image.png

从上面的截图中可以看出,浏览器在收到 302 响应之后,从 Location 里面获取到了 /pets 这个新的地址,并发起了新的请求。但是,这个新的请求使用的是 GET 方法而非我们原来的 POST 方法,Request Body 也丢失了。

应用场景:

比如某个应用进行了整站重构,重构之后 URL 发生了改变,这时候可以考虑将老 URL 重定向到新 URL。

308 Permanent Redirect

跟 301 一样,唯一的区别就是浏览器不会改变请求的 HTTP Method 和 Request Body。

我们又继续拿 Chrome 测试了一下,结果如下:

image.png image.png

可以看出,使用 308 之后,请求的 Method 和 Request Body 都没有发生改变,还是跟原来一样。

临时重定向

由于某些原因,导致用户请求的资源地址临时不可用,但其他某个地址是可访问的,于是就通过响应 Header 的 Location 字段将这个临时地址告知给用户。

就好比我们去一家饭店吃饭,到门口发现老板张贴告示:家里临时有事,请去附近另一个分店用餐,并附上了分店的详细地址。这样,我们就可以先临时去这家分店用餐,等之后老板回来了,我们又可以在原来这家店继续用餐。

302 Found

在收到 302 响应之后,浏览器会发起新的请求,尽管标准要求浏览器在收到该响应时不应该修改 HTTP Method 和 Request Body,但是大多数浏览器都没有遵守这个标准。大多数浏览器会将 302 请求视为 303 请求,也就是说 302 和 303 几乎是一样的。

应用场景举例:

通常图片资源会放到类似 S3 这样的静态资源服务器,出于对隐私和安全的考虑,我们有时不能直接通过静态资源服务器的 URL 访问到这个图片,而是需要后端通过身份凭证去 S3 签发一个临时地址(这个地址用一次就失效了,下一次需要重新签发),然后我们才能通过这个临时地址访问到真正的图片资源。

对于这个场景,302/303 就非常有用了。比如,前端可以使用一个固定的 URL 来访问图片资源 (<img src="/image/foo.jpg"),这个 URL 由后端提供,后端在生成完临时地址之后,可以直接通过 302/303 重定向到这个新的地址,接下来浏览器会再次发起请求,获取新地址指向的这个图片资源。

303 See Other

浏览器在收到 303 响应之后,除 GET 方法保持不变之外,其他所有方法都会被改为 GET 方法,同时 Request Body 也会丢失。一般用于将 POST 方法重定向到 GET 方法。

应用场景举例:

假设我们有一个非常原始的表单,这个表单是通过在 HTML 上设置 action 和 method 来提交的。在表单提交之后,浏览器会自动跳转到 action 所在的地址,并使用 method 所指定的方法向服务器发起请求,在收到服务器的303 响应之后,又需要跳转回原来这个页面。

<form action="http://example.com/api/form" method="POST">
    <input type="text" name="name" placeholder="name">
    <input type="email" name="email" placeholder="email">
    <input type="submit" value="Submit">
</form>
复制代码

这里就会出现一个问题,假设我们页面的 URL 是 example.com ,我们只能通过 GET 方法请求这个页面。而提交表单所使用的 URL 是 example.com/api/form ,我们只能通过 POST 方法来调用这个接口。在使用 POST 方法提交表单之后,如果我们想返回原来的页面,只能使用 GET 方法发起请求,因此我们需要使用 303 状态码,将原来的 POST 方法改成 GET。

307 Temporary Redirect

307 和 303 一样,唯一的区别就是浏览器不会改变请求的 HTTP Method 和 Request Body。对于 POST/PUT 等非 GET 请求很有用。

应用场景:

在上传文件时,我们通常会调用后端提供的某个 API (/upload),但由于上传会占用大量的服务器资源和带宽,这个 API 一般不会处理真正的上传,而是通过另一个专门的上传服务来完成。这意味着我们需要调用两次 API 才能完成上传功能,一次是调用后端接口获取真正的上传地址,另一次是携带 Request Body 请求这个上传地址,完成真正的上传。

如果让两个 API 的请求参数和方法保持一致,那么对于前端来说,只需要发起一次请求即可,后端在生成完上传地址后,通过 307 重定向到这个地址,然后浏览器会使用之前的 Request Body 和 HTTP Method 请求这个新的地址,这样就完成了上传。

注意,如果使用 302 会导致上传失败,原因是 302 会将 PUT 改为 GET 请求,同时 Request Body 也会丢失。

其他常见的重定向场景

域名别称

  1. 扩大站点的用户覆盖面。比如访问 www.google.com 会被重定向到 google.com
  2. 迁移到另一个域名。
  3. 强制使用 HTTPS 协议。比如访问 baidu.com 会被重定向到 baidu.com

保持链接有效

当重构网站时,可能会让资源的 URL 发生改变。但我们并不想因此使旧链接失效,因为这些链接不仅可以给你带来宝贵的用户,还能够帮助优化 SEO,因此需要建立从旧链接到新链接的重定向映射。

浏览器

我们已经了解了各种关于重定向的 HTTP 状态码,那么浏览器收到这些状态码之后会做些什么呢?当浏览器收到 3XX 响应之后,是否会将地址栏的 URL 修改为响应 Header 中 Location 字段所指定的 URL,并跳转到这个新的 URL 呢? 请接着往下看。

收到 3XX 响应后,浏览器是否会改变地址栏并跳转?

浏览器是否会改变地址栏并跳转, 其实取决于请求的 URL 是否会让文档加载/重新加载。这里所指的文档加载/重新加载,既包括当我们通过浏览器地址栏访问某个 URL、刷新页面所导致的文档加载,也包括我们通过 window.location 让文档用新的 URL 加载。只有在这些情况下,如果我们请求的 URL 返回了 3XX 响应,浏览器才会读取响应 Header 里面的 Location 字段,并将浏览器的地址栏修改为这个字段所指定的 URL,然后跳转到这个 URL。

一般来说文档加载/重新加载都会产生一个 Document 类型的请求,如下所示: image.png

PS: React Router 这样的路由跳转,不会让文档重新加载 ( 因为使用的是 History API )

但是,如果是通过 fetch/XMLHttpRequest 调用所获取到的 3XX 响应,浏览器是不会改变当前地址栏的 URL 并跳转的。试想,我们在访问图片时,经常会得到很多 302 响应,如果浏览器要跳转 URL,岂不是会导致整个浏览器崩溃。

window.location + 3XX 响应

window.location 能够让文档用新的 URL 加载,默认会将新的 URL push 到浏览器 history 的路由堆栈里面,如果你想使用 replace 的方式,可以调用 window.location.replace() 方法:

window.location = "http://www.mozilla.org";

function reloadPageWithHash() {
  var initialPage = window.location.pathname;
  window.location.replace('http://example.com/#' + initialPage);
}
复制代码

应用场景:

在使用 OAuth2 鉴权时(比如微信登录),可以通过 window.location 重定向到自己服务器的授权地址(支持多个平台登录时,可以由后端统一处理),然后服务器会生成一个三方授权点的地址,并通过 302 响应告知给浏览器,浏览器在收到响应之后会跳转到这个三方授权点的 URL(微信登录页),完成授权之后,三方授权页面会通过 window.location 再重定向回我们自己的页面。

通过 window.location 再配合 302 响应,我们可以快速将用户导向三方授权点,不需要加载任何 JS,非常快速方便。

参考:developer.mozilla.org/zh-CN/docs/…

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改