Learn HTTP 1.1 Status Code Part.2

by xujiwei(http://www.xujiwei.com/)

前一篇介绍了 2xx 系列状态码,这一篇介绍 3xx 重定向系列。

5 重定向 3xx 系列状态码

3xx 系列状态码一般是用来作为重定向的,并且在重定向的过程中,一般不需要用户的参与,也就是说,重定向的过程是由浏览器来控制的。但是如果重定向后的请求的方法不是 GET 或 HEAD 的话,还是需要用户参与的,不过也许仅仅是确认一下是否同意发送数据而已:)

另外,开发者要注意的是,RFC2616 Section 10.3 的备注中提到了在前一版本的 HTTP 协议中建议最大重定向次数为 5 次,也就是说,客户端可能只跟踪 5 次重定向,如果超过 5 次重定向,那么可能客户端就会把第 5 次重定向后得到的结果做为最终结果。这时,客户端得到的可能就是一个错误的结果,这就是开发者不愿看到的,那么在开发时,就应该尽量控制重定向次数在 5 次以内。

5.1  300 Multiple Choices

300 Multiple Choices,字面意思是多重选择,表示请求的这个地址具有多个资源可以响应。客户端在遇到 300 Multiple Choices 时,可以自动选择一个最适合的转向地址,或者将所有选项提供给用户由用户来选择一个最适合的转向地址。

除非客户端使用 HEAD 方法来请求,那么如果服务端要返回 300 状态码,那么也应该同时在响应中指出所有可以选择的转向地址列表以便客户端进行选择。但是,目前这个列表的格式并没有一个统一的规范,所以需要客户端与服务端统一规范。

我觉得这个状态码可以用于镜像选择,客户端向服务器请求一个资源,服务器返回一个所有候选资源的列表,客户端从中选取一个速度最快的镜像进行资源下载。

如果没有客户端与服务端之间没有统一规范哪一个资源才是最适合的选择,那么服务端最好在头部中添加 Location 字段指明以服务端来考虑的最适合的转向地址,客户端可以直接使用 Location 字段中的转向地址进行转向而不需要考虑候选资源列表格式是怎么样的或者哪一个选择才是最好的。

经过测试,目前流行的浏览器中,只有 IE 和 Firefox 会对 300 状态码进行识别,Safari、Opera、Chrome 均直接显示响应的内容。由于前面说过的原因,候选列表的格式没有一个统一的规范,所以 IE 和 Firefox 只会根据 Location 字段的值进行转向,如果不指定 Location 字段的值,那么 IE 和 Firefox 也会跟其他浏览器一样只显示响应的内容。

可以使用以下 ASP 代码来测试 300 Multiple Choices:

  • <%@LANGUAGE="JScript" CODEPAGE="65001"%>
  • <%
  • // code from www.xujiwei.com
  • Response.Status = "300 Multiple Choices"
  • // 添加一个 Location 字段,IE 和 Firefox 会识别这个字段并重定向
  • Response.AddHeader("Location", "test3.asp");
  • // 输出一个候选资源列表,以 XML 文件的形式
  • // 当然,这个格式只是我测试用的,没有浏览器会认识它
  • Response.ContentType = "application/xml";
  • Response.Write("<?xml version='1.0' encoding='utf-8' ?><locations><location>test3.asp</location><location>test.html</location></locations>");
  • %>

在 IE 和 Firefox 中浏览包含以上代码的页面时,会自动转向到 test3.asp,而 Safari、Opera、Chrome 则不会。

5.2  301 Moved Permanently

在诸多 SEO 的文章中所说的永久转向就是这位 301 Moved Permanently 了。301 表示当前请求的资源永久地被转移到一个新的 URI 了,除非请求的方法是 HEAD,这个新的 URI 必须在头部的 Location 字段中指出,同时响应内容中最好包含一个指定新地址的超链接,这样,在不支持自动转向的浏览器用户也可以通过点击超链接来重定向到新的 URI。

如果请求时所用的方法不是 GET 或者 HEAD,那么客户端在用户确认前不应自动进行重定向。例如如果浏览器发送了一个 POST 请求,但是收到了 301 Moved Permanently 的响应,那么在重定向到新的 URI 时,可能会使用 GET 方法来请求而不是 POST 方法。

事实上,测试表明,目前的主流浏览器都不会直接重发 POST 请求到 301 重定向后的 URI,而是将请求转换成 GET 方法后再发送,也就是说,在 301 重定向到的服务端程序中,是接收不到最开始客户端 POST 的数据。

通过以下代码可以测试出 POST 请求被转发为 GET 请求:

test.html

  • <!-- code from www.xujiwei.com -->
  • <form action="test.asp" method="post">
  • <input type="text" name="hello" value="world" /><input type="submit" />
  • </form>

test.asp

  • <%@LANGUAGE="JScript" CODEPAGE="65001"%>
  • <%
  • // code from www.xujiwei.com
  • // 重定向到 test2.asp
  • Response.Status = "301 Moved Permanently"
  • Response.AddHeader("Location", "test2.asp");
  • %>

test2.asp

  • <%@LANGUAGE="JScript" CODEPAGE="65001"%>
  • <%
  • // code from www.xujiwei.com
  • Response.ContentType = "text/html; charset=utf-8";
  • Response.Write(new Date().toLocaleString() + "<br/>");
  • // 输出请求方法,预期为 POST,实际为 GET
  • Response.Write(Request.ServerVariables("HTTP_METHOD"));
  • // 输出表单中字段 hello 的值,预期为 "world",实际为空字符串
  • Response.Write(Request.Form("hello"));
  • %>

5.3  302 Found

302 Found 常用于客户端重定向,例如登录完成后重定向到首页等。它表示的是临时重定向,虽然用得多,但是在对于搜索引擎而言却是一个不太友好的东西,所以尽量只在临时转向时才用它。

302 Found 的用法跟 301 Moved Permanently 一样,在 Location 字段中指出要转向的地址,只是所包含的含义不一样而已。

另外,在 RFC 2616 Section 10.3.3 的备注中说到在 RFC 1945 和 RFC 2068 中是不允许客户端改变重定向后的请求方法的,但是目前多数的客户端都是直接重定向然后把方法改为 GET 并舍去 POST 部分的数据,所以在最新的 HTTP 1.1 中添加了 303 和 307 两个状态码来明确是否需要客户端继续以 POST 方式发送请求。

5.4  303 See Other

上面提到了 303 和 307 是用来解决重定向是否要重发 POST 数据不清晰的问题,其中 303 就是用来表示客户端不需要重新发送 POST 数据,可以使用 GET 方法来请求新的地址。

要注意的是,303 响应中 Location 指出的地址并不是原请求地址的完整响应,也就是说,303 可能只是个说明页面,并不一定会对原来请求做出正确的响应

备注中说一些以前的 HTTP 1.1 客户端可能不认识 303,不过现在可以放心的是,主流浏览器都是支持 303 状态码的。

test.asp

  • <%@LANGUAGE="JScript" CODEPAGE="65001"%>
  • <%
  • // code from www.xujiwei.com
  • Response.Status = "303 See Other"
  • Response.AddHeader("Location", "test2.asp");
  • %>

反正客户端是会将请求以 GET 方法重新发送的,所以 POST 部分的数据就不用指望了:)

5.5  304 Not Modified

304 Not Modified 是用来表示客户端所请求的资源和上次所请求时没有发生改变,这样服务端就不用重新发送资源的内容,从而减少了网络的负担。

服务端可以由两个头部变量来判断是否发送 304 Not Modified 状态码,一个是 If-Modified-Since,另一个是 If-None-Match,相对应的服务端需要发送的头部变量是 Last-Modified 和 ETag。可以二者选其一,也可以两个都用。PJBlog 就使用了 304 Not Modified 来优化边栏的性能。

  • <%@LANGUAGE="JScript" CODEPAGE="65001"%>
  • <%
  • // code from www.xujiwei.com
  • // 从 Session 中获取 ETag 和 Last-Modified 值并发送到客户端
  • var etag = Session('etag') + ', lastmod = Session("lastmod") + ';
  • if(etag == ' || etag == 'undefined') {
  • Session('etag') = (+new Date()).toString(16);
  • etag = Session('etag') + ';
  • }
  • if(lastmod == ' || lastmod == 'undefined') {
  • Session('lastmod') = new Date().toUTCString();
  • lastmod = Session('lastmod') + ';
  • }
  • Response.AddHeader("ETag", etag);
  • Response.AddHeader("Last-Modified", lastmod);
  • Response.ContentType = "image/jpeg";
  • // 获取客户端发送过来的 If-None-Match 和 If-Modified-Since 值
  • var etag2 = Request.ServerVariables("HTTP_IF_NONE_MATCH") + "";
  • var lastmod2 = Request.ServerVariables("HTTP_IF_MODIFIED_SINCE") + "";
  • if(etag2 == etag || lastmod == lastmod2) {
  • // 直接响应 304 Not Modified
  • Response.Status = "304 Not Modified";
  • }
  • else {
  • // 发送完成内容
  • Response.Status = "200 OK"
  • var stream = new ActiveXObject("ADODB.Stream");
  • stream.Type = 1;
  • stream.Mode = 3;
  • stream.Open();
  • stream.LoadFromFile(Server.MapPath('test.jpg'));
  • Response.BinaryWrite(stream.Read());
  • stream.Close();
  • }
  • %>

5.6  305 Use Proxy

305 Use Proxy 指示客户端需要通过代理来请求当前资源,在头部的 Location 字段中指定代理地址,并且这个状态码只能由原始服务器返回。

但是 RFC 2616 Section 10.3.6 中并没有详细指出 Location 的格式是怎么样的,通过测试发现只有 Safari 会识别 305 Use Proxy,并且直接转向到 Location 中指定的地址。

暂且把它当做一个转向的状态码来用吧,嗯,尽量少用吧,许多浏览器不认识它。

5.7  306 (Unused)

HTTP 1.1 中把它给扔了,所以我们不用管这个状态码了,如果碰到了,直接当错误处理吧。

5.8  307 Temporary Redirect

唔,又是一个不被完全支持的状态码。客户端收到这个状态码时,应该将请求重发到新的地址。而且这个状态码,主要用于 POST 请求,因为如果原始请求是用 GET 方法发送的,那么很可怜的在重定向到新的地址以后,GET 参数会全部舍弃,如果需要在转向后的地址处理 GET 参数,那么自己在服务端程序中加上去吧……

测试中只有 Firefox、Opera 和 IE 会将 POST 请求重定向到新的地址,其中 Firefox 和 Opera 会让用户选择是否重新发送表单到新的地址,而 IE 则是不经用户选择直接把表单重新发送到新的地址,相对而言,Firefox 和 Opera 更安全一些。

如果一定要用这个状态码,为了兼容那些个不认识它的浏览器,服务端需要在响应内容中加个指向新地址的超链接,再加个说明啥的,说明一下怎么用这个新的地址。anyway,少用为上。

后记

3xx 重定向系列用得相当广泛,301、302、304 这三个用得最多。特别需要注意的是 POST 的数据在目前来说基本上是无法直接重定向到一个新的地址的。

ps. 用好 304 很重要:)

系列目录

Learn HTTP 1.1 Status Code Part.1

Comments (0), Views (1091), Pings (0), Leave a response!