HTTP状态码是服务器用于告知客户端一次HTTP请求结果的描述符,而以3开头的3XX状态码表明服务端不会就此次请求返回客户端想要的结果,客户端需要执行某些特殊的处理来正确处理请求。其中301
,302
,303
,307
这4个状态码都表示重定向这个意思,即当前请求的地址不能正确获得资源,需要客户端根据返回的Location
头字段去另一个地址获取资源。那么重定向的这四种状态码除了都实现了重定向功能以及给开发人员带来困惑之外,它们之间的有什么区别呢?
根据标准的定义,我们可以将这四个状态码分为两大阵营,301
代表的永久重定向阵营和302
、303
及307
组成的临时重定向阵营。
永久重定向(301) vs 临时重定向(302、303、307)
以两个页面A和B为例,假设对A页面的请求做了重定向到B页面的操作,那么所谓永久重定向,可以理解为A页面的URL所代表的资源永久地被放弃了,以后都由B页面代替A,从今往后大家都忘记A吧,只要记住B就可以了;而所谓临时重定向,可以理解为A因为某些原因暂时不能提供页面资源,大家暂时先到B页面获取资源,不过大家千万别忘了A,因为说不定哪天A页面又可以正常访问了。
用RFC2616里的话怎么说呢,对于301
:
The requested resource has been assigned a new permanent URI and any future references to this resource SHOULD use one of the returned URIs.(已经为当前请求的资源重新分配了一个URI,并且将来在使用这俩个URI中的任意一个的时候这些资源都应该被返回)
而对于302:
The requested resource resides temporarily under a different URI.Since the redirection might be altered on occasion, the client SHOULD continue to use the Request-URI for future requests.(请求的资源暂时位于不同的URI,但是由于重定向或许会在将来被改变,所以在访问资源时应该继续使用之前的那个URI)
但是,无论是301
还是302
(或303
、307
),用户得到的体验是一样的,那就是访问A的URL时看到B的页面。那区分这两者的意义何在?我们不要忘记,除了用户(人),Web世界还有两个重要参与者,那就是浏览器和搜索引擎。对于它们来说,对永久重定向和临时重定向的处理是不一样的。
浏览器的处理
首先来说浏览器,对永久重定向和临时重定向的区别主要体现在对3XX响应的缓存处理上面。RFC2616中对对301
关于缓存的定义:
This response is cacheable unless indicated otherwise.(再无特殊说明的情况下,请求会被缓存)
而302(或307):
This response is only cacheable if indicated by a Cache-Control or Expires header field.(只有在Cache-Control或者Expires被设置的情况下,请求才会被缓存)
303讲法上略有不同:
The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable.(303禁止被缓存,但是第二次重定向的请求可以被缓存)
我们可以发现,在默认的情况下,301
响应是被客户端(通常是浏览器)缓存的,而其他重定向响应是不被缓存的。也就是说,当对A的请求被301
重定向之后,这个重定向响应会被浏览器记住,后续就算A不再重定向了,浏览器根据缓存的结果,也会将请求重定向到B(这里涉及到另一个问题是浏览器对301
响应会缓存多久,因为标准里没有明确定义,所以各个浏览器的处理也是各不相同)。而302
、303
及307
响应,默认情况下都不会被浏览器缓存,所以A说恢复就能恢复。不过,以上所说的都是默认情况下,只要我们加上Cache-Control
或Expires
头,一切都可以改变。比如给301
响应加上Cache-Control: no-cache
,浏览器就不会记住它,或者给302
响应加上Cache-Control: max-age=2592000
,浏览器就会将其缓存。
搜索引擎的处理
搜素引擎对重定向的处理要复杂得多,从理论上讲,搜索引擎遇见永久重定向时会删除对旧页面/旧站点A的索引,新页面/新站点B会作为A的替代者或继承者被收录到索引中,并且搜索引擎会转移A的权重到B身上;而对于临时重定向,搜索引擎会保留A地址,同时抓取B的内容,A的权重和排名并不会转移到B的身上。
但是搜索引擎在实际处理时并没有这么简单,Google与百度的做法可能也不相同。就301
来说,搜索引擎可能还得考虑是子域名间的跳转,还是不同域名间的跳转,亦或是同个域名下的页面地址跳转。假设域名A更换成域名B,对A的请求做301
重定向,如果A和B有不同的目录结构,比如B域名只有一个首页,而A域名下路径/old.html在B域名下并没有,这样B/old.html就会是404
,那么B的权重就会损失,所以B不一定就能得到A的权重和排序。除此之外,搜索引擎还要考虑A域名的重定向是否值得信任,通常更换域名之后得经过一段时间B才能获得跟之前A一样的权重。
重定向在搜索引擎这里还存在一个叫做PR劫持的问题,即空壳网站A利用临时重定向(也可能是301
)到B,让搜索引擎收录A,同时记录B的内容,这样A不费吹灰之力就利用了B辛辛苦苦做出的内容,从而提高自己的PageRank。对于这种情况,搜索引擎有可能会判断出A在利用别人的内容推销自己,进而对A网站做出惩罚。所以,当我们在做页面重定向,特别是需要考虑到SEO时,最好用301
永久重定向,避免影响搜索引擎对网站动机的判断。
临时重定向之间的区别
临时重定向会有302
、303
、307
三种状态码与HTTP协议推行的历史有关,在HTTP/1.0
中,只有302
是表示临时重定向的,而在HTTP/1.1
中又添加了303
和307
,它们之间区别的焦点主要集中在对非HEAD和非GET方式的HTTP请求进行临时重定向的处理方式。我们先看一下RFC对302
在这方面的定义:
If the 302 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.(如果收到302状态代码以响应GET或HEAD以外的请求,则用户代理不得自动重定向请求,除非用户可以确认,因为这可能会改变发出请求的条件)
也就是说在规范里明确定义了302
重定向在响应POST或PUT请求时,是不能自动进行重定向的,需要先得到用户的确认。可是现实中的情况是多数的浏览器并没有遵守这个规范,它们的做法是自动用GET方法重定向请求目标地址。而为了让这种混乱的情况得到解决,303
和307
被添加进来,303在规范中被定义为如今302
被实现的情况,也就是对于303
响应浏览器都进行GET重定向:
The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource.(在被重定向到其他URI下面时可以使用GET请求)
而307
在规范中被定义为302
规范所定义的情况,所以根据RFC2616,我觉得可以这么说,303
和307
是用来替代302
的,它们一个被定义成302
当前的现状,一个被定义成302
原本想要实现的状态,用这两个状态码来明确指定两种不同的方式,避免302
规范与实现不符的情况。
RFC7231
但是需要注意的是,1999年发布的RFC2616在2014年遭到废弃,原来的RFC2616拆分为6个单独的协议说明,其中重定向部分在RFC7231中可以找到最新的定义,然后你会发现,新规范并没有放弃302,相反,302
被明确地定义成与它目前的现状保持一致,即可以用GET方式重定向POST请求,规范对302这样写道:
Note: For historical reasons, a user agent MAY change the request method from POST to GET for the subsequent request. If this behavior is undesired, the 307 (Temporary Redirect) status code can be used instead.
RFC7231接受了302
的现状并且使其成为新的规范,307
仍然遵守不能更改重定向方法的规定,不过把需要先得到用户的确认这部分去掉了,而是描述成:
the user agent MUST NOT change the request method if it performs an automatic redirection to that URI.
那么问题来了,原来是用303
和307
替换302
,现在变成了用新定义的302
和307
替换旧定义的302
,那303
干什么去了呢?在对303
的最新定义中,虽然对其的定义文字比之前增加了很多,但我实在没发现它跟之前有什么本质的区别,似乎303
反倒成了被抛弃的对象。
那么还有一个疑问是,301
对于非HEAD和非GET方法请求时的处理是怎么样的呢?其实在这方面,301
跟302
是一样的状况,在RFC2616中存在标准与实现不一致的情况,在RFC7231中事实现状被定义成了新的标准。
总结
针对RFC2616的标准,对301
、302
、303
、307
可以归纳为这个表格:
状态码 | HTTP版本 | 类别 | 对POST请求的处理 |
---|---|---|---|
301 | 1.0、1.1 | 永久 | RFC2616:不能用GET重定向;实现:自动GET重定向 |
302 | 1.0、1.1 | 临时 | RFC2616:不能用GET重定向;实现:自动GET重定向 |
303 | 1.1 | 临时 | 自动GET重定向 |
307 | 1.1 | 临时 | 不能GET重定向 |