<audio id="audio" title="18 | 四通八达：HTTP的重定向和跳转" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ce/f0/ce5c9c5647eb34a1af654866d8533bf0.mp3"></audio>

在专栏[第1讲](https://time.geekbang.org/column/article/97837)时我曾经说过，为了实现在互联网上构建超链接文档系统的设想，蒂姆·伯纳斯-李发明了万维网，使用HTTP协议传输“超文本”，让全世界的人都能够自由地共享信息。

“超文本”里含有“超链接”，可以从一个“超文本”跳跃到另一个“超文本”，对线性结构的传统文档是一个根本性的变革。

能够使用“超链接”在网络上任意地跳转也是万维网的一个关键特性。它把分散在世界各地的文档连接在一起，形成了复杂的网状结构，用户可以在查看时随意点击链接、转换页面。再加上浏览器又提供了“前进”“后退”“书签”等辅助功能，让用户在文档间跳转时更加方便，有了更多的主动性和交互性。

那么，点击页面“链接”时的跳转是怎样的呢？具体一点，比如在Nginx的主页上点了一下“download”链接，会发生什么呢？

结合之前的课程，稍微思考一下你就能得到答案：浏览器首先要解析链接文字里的URI。

```
http://nginx.org/en/download.html

```

再用这个URI发起一个新的HTTP请求，获取响应报文后就会切换显示内容，渲染出新URI指向的页面。

这样的跳转动作是由浏览器的使用者主动发起的，可以称为“**主动跳转**”，但还有一类跳转是由服务器来发起的，浏览器使用者无法控制，相对地就可以称为“**被动跳转**”，这在HTTP协议里有个专门的名词，叫做“**重定向**”（Redirection）。

## 重定向的过程

其实之前我们就已经见过重定向了，在[第12讲](https://time.geekbang.org/column/article/102483)里3××状态码时就说过，301是“永久重定向”，302是“临时重定向”，浏览器收到这两个状态码就会跳转到新的URI。

那么，它们是怎么做到的呢？难道仅仅用这两个代码就能够实现跳转页面吗？

先在实验环境里看一下重定向的过程吧，用Chrome访问URI “/18-1”，它会使用302立即跳转到“/index.html”。

<img src="https://static001.geekbang.org/resource/image/ad/2d/ad5eb7546ee7ef62a9987120934b592d.png" alt="">

从这个实验可以看到，这一次“重定向”实际上发送了两次HTTP请求，第一个请求返回了302，然后第二个请求就被重定向到了“/index.html”。但如果不用开发者工具的话，你是完全看不到这个跳转过程的，也就是说，重定向是“用户无感知”的。

我们再来看看第一个请求返回的响应报文：

<img src="https://static001.geekbang.org/resource/image/a4/ac/a4276db758bf90f63fd5a5c2357af4ac.png" alt="">

这里出现了一个新的头字段“Location: /index.html”，它就是301/302重定向跳转的秘密所在。

“**Location**”字段属于响应字段，必须出现在响应报文里。但只有配合301/302状态码才有意义，它**标记了服务器要求重定向的URI**，这里就是要求浏览器跳转到“index.html”。

浏览器收到301/302报文，会检查响应头里有没有“Location”。如果有，就从字段值里提取出URI，发出新的HTTP请求，相当于自动替我们点击了这个链接。

在“Location”里的URI既可以使用绝对URI，也可以使用相对URI。所谓“绝对URI”，就是完整形式的URI，包括scheme、host:port、path等。所谓“相对URI”，就是省略了scheme和host:port，只有path和query部分，是不完整的，但可以从请求上下文里计算得到。

例如，刚才的实验例子里的“Location: /index.html”用的就是相对URI。它没有说明访问URI的协议和主机，但因为是由“[http://www.chrono.com/18-1](http://www.chrono.com/18-1)”重定向返回的响应报文，所以浏览器就可以拼出完整的URI：

```
http://www.chrono.com/index.html

```

实验环境的URI“/18-1”还支持使用query参数“dst=xxx”，指明重定向的URI，你可以用这种形式再多试几次重定向，看看浏览器是如何工作的。

```
http://www.chrono.com/18-1?dst=/15-1?name=a.json
http://www.chrono.com/18-1?dst=/17-1

```

注意，在重定向时如果只是在站内跳转，你可以放心地使用相对URI。但如果要跳转到站外，就必须用绝对URI。

例如，如果想跳转到Nginx官网，就必须在“nginx.org”前把“http://”都写出来，否则浏览器会按照相对URI去理解，得到的就会是一个不存在的URI“[http://www.chrono.com/nginx.org”](http://www.chrono.com/nginx.org%E2%80%9D)

```
http://www.chrono.com/18-1?dst=nginx.org           #错误
http://www.chrono.com/18-1?dst=http://nginx.org    #正确

```

<img src="https://static001.geekbang.org/resource/image/00/aa/006059602ee75b176a80429f49ffc9aa.png" alt="">

那么，如果301/302跳转时没有Location字段会怎么样呢？

这个你也可以自己试一下，使用第12讲里的URI“/12-1”，查询参数用“code=302”：

```
http://www.chrono.com/12-1?code=302

```

## 重定向状态码

刚才我把重定向的过程基本讲完了，现在来说一下重定向用到的状态码。

最常见的重定向状态码就是301和302，另外还有几个不太常见的，例如303、307、308等。它们最终的效果都差不多，让浏览器跳转到新的URI，但语义上有一些细微的差别，使用的时候要特别注意。

**301**俗称“永久重定向”（Moved Permanently），意思是原URI已经“永久”性地不存在了，今后的所有请求都必须改用新的URI。

浏览器看到301，就知道原来的URI“过时”了，就会做适当的优化。比如历史记录、更新书签，下次可能就会直接用新的URI访问，省去了再次跳转的成本。搜索引擎的爬虫看到301，也会更新索引库，不再使用老的URI。

**302**俗称“临时重定向”（“Moved Temporarily”），意思是原URI处于“临时维护”状态，新的URI是起“顶包”作用的“临时工”。

浏览器或者爬虫看到302，会认为原来的URI仍然有效，但暂时不可用，所以只会执行简单的跳转页面，不记录新的URI，也不会有其他的多余动作，下次访问还是用原URI。

301/302是最常用的重定向状态码，在3××里剩下的几个还有：

- 303 See Other：类似302，但要求重定向后的请求改为GET方法，访问一个结果页面，避免POST/PUT重复操作；
- 307 Temporary Redirect：类似302，但重定向后请求里的方法和实体不允许变动，含义比302更明确；
- 308 Permanent Redirect：类似307，不允许重定向后的请求变动，但它是301“永久重定向”的含义。

不过这三个状态码的接受程度较低，有的浏览器和服务器可能不支持，开发时应当慎重，测试确认浏览器的实际效果后才能使用。

## 重定向的应用场景

理解了重定向的工作原理和状态码的含义，我们就可以**在服务器端拥有主动权**，控制浏览器的行为，不过要怎么利用重定向才好呢？

使用重定向跳转，核心是要理解“**重定向**”和“**永久/临时**”这两个关键词。

先来看什么时候需要重定向。

一个最常见的原因就是“**资源不可用**”，需要用另一个新的URI来代替。

至于不可用的原因那就很多了。例如域名变更、服务器变更、网站改版、系统维护，这些都会导致原URI指向的资源无法访问，为了避免出现404，就需要用重定向跳转到新的URI，继续为网民提供服务。

另一个原因就是“**避免重复**”，让多个网址都跳转到一个URI，增加访问入口的同时还不会增加额外的工作量。

例如，有的网站都会申请多个名称类似的域名，然后把它们再重定向到主站上。比如，你可以访问一下“qq.com”“github.com ”“bing.com”（记得事先清理缓存），看看它是如何重定向的。

决定要实行重定向后接下来要考虑的就是“永久”和“临时”的问题了，也就是选择301还是302。

301的含义是“**永久**”的。

如果域名、服务器、网站架构发生了大幅度的改变，比如启用了新域名、服务器切换到了新机房、网站目录层次重构，这些都算是“永久性”的改变。原来的URI已经不能用了，必须用301“永久重定向”，通知浏览器和搜索引擎更新到新地址，这也是搜索引擎优化（SEO）要考虑的因素之一。

302的含义是“**临时**”的。

原来的URI在将来的某个时间点还会恢复正常，常见的应用场景就是系统维护，把网站重定向到一个通知页面，告诉用户过一会儿再来访问。另一种用法就是“服务降级”，比如在双十一促销的时候，把订单查询、领积分等不重要的功能入口暂时关闭，保证核心服务能够正常运行。

## 重定向的相关问题

重定向的用途很多，掌握了重定向，就能够在架设网站时获得更多的灵活性，不过在使用时还需要注意两个问题。

第一个问题是“**性能损耗**”。很明显，重定向的机制决定了一个跳转会有两次请求-应答，比正常的访问多了一次。

虽然301/302报文很小，但大量的跳转对服务器的影响也是不可忽视的。站内重定向还好说，可以长连接复用，站外重定向就要开两个连接，如果网络连接质量差，那成本可就高多了，会严重影响用户的体验。

所以重定向应当适度使用，决不能滥用。

第二个问题是“**循环跳转**”。如果重定向的策略设置欠考虑，可能会出现“A=&gt;B=&gt;C=&gt;A”的无限循环，不停地在这个链路里转圈圈，后果可想而知。

所以HTTP协议特别规定，浏览器必须具有检测“循环跳转”的能力，在发现这种情况时应当停止发送请求并给出错误提示。

实验环境的URI“/18-2”就模拟了这样的一个“循环跳转”，它跳转到“/18-1”，并用参数“dst=/18-2”再跳回自己，实现了两个URI的无限循环。

使用Chrome访问这个地址，会得到“该网页无法正常运作”的结果：

<img src="https://static001.geekbang.org/resource/image/4b/2f/4b91aeea08d90f173c62493934e5f52f.png" alt="">

## 小结

今天我们学习了HTTP里的重定向和跳转，简单小结一下这次的内容：

1. 重定向是服务器发起的跳转，要求客户端改用新的URI重新发送请求，通常会自动进行，用户是无感知的；
1. 301/302是最常用的重定向状态码，分别是“永久重定向”和“临时重定向”；
1. 响应头字段Location指示了要跳转的URI，可以用绝对或相对的形式；
1. 重定向可以把一个URI指向另一个URI，也可以把多个URI指向同一个URI，用途很多；
1. 使用重定向时需要当心性能损耗，还要避免出现循环跳转。

## 课下作业

1. 301和302非常相似，试着结合第12讲，用自己的理解再描述一下两者的异同点。
1. 你能结合自己的实际情况，再列出几个应当使用重定向的场景吗？

欢迎你把自己的学习体会写在留言区，与我和其他同学一起讨论。如果你觉得有所收获，也欢迎把文章分享给你的朋友。

<img src="https://static001.geekbang.org/resource/image/9b/05/9b873d25e33f86bb2818fc8b50fbff05.png" alt="unpreview">


