Learn HTTP Status Code Part.1
by xujiwei( http://www.xujiwei.com/external)
1. 前言
HTTP 状态码在 Web 开发中是相当重要的一部分,掌握了 HTTP 的运用,可以在网站速度、性能等等的优化上有迹可循。当前,掌握 HTTP 状态码也可以为进一步加深对 HTTP 协议的理解打下基础。
以前,我所熟知的 HTTP 状态码仅限于 200、404、302 等几个,但是在一次与  smallshellexternal 聊天的过程中,谈到了 HTTP 状态码,让我对剩余许多我并不熟知的 HTTP 状态码起了兴趣。不过在这之后的一段时间里都比较忙,因此这个想写的这个系列就一直拖着,这几天算是稍微清闲下来了,准备好好学习了一下 HTTP 状态码。
嗯,我也是在学习的过程中,所以,如果我写的有什么不对,欢迎指正,留言或 email(vipxjw at 163 dot com) 均可:)
2. 准备
本文基于  RFC2616 第10节 Status Code Definitionsexternal 而写,如果对本文有不明白的地方,可以参考完整的  HTTP 1.1 协议external
3. 1xx 系列
1xx 系列一般是用在服务器与代理之间,表示一些信息,这在我们写服务端程序时并不常用,并且 1xx 系列状态码是在 HTTP 1.1 中才加入的,按照 w3c 的说法, 除非在实验性的环境下,不要发送 1xx 系列状态码到使用 HTTP 1.0 协议的客户端
如果需要了解两个 1xx 系列的状态 100 Continue 和 101 Switching Protocols 的作用,可以在  RFC2616 第10节external中找到相关资料。
4. 2xx 系列
2xx 系列表示成功的响应,最为人所知的就是  200 OK 了,它表示一下正确响应了客户端所发送过来的请求,那么在客户端,也就需要以“正确”的态度来对待服务端过来的响应内容。
4.1 200 OK
呃,200 OK 是最最常见的状态码了,每次我们在浏览器输入一个网址按下回车后,就会收到一堆 200 OK,就是一堆 200 OK,呈现给我们了一个丰富多彩的页面。
因此,通常情况下,在编写服务端程序时,也应该返回一个 200 OK 的状态码。但是也因为 200 OK 是如此的普通,如此的常用,可能在一些特殊的情况下,开发人员没有注意到 200 OK 并不是一个可取的状态码,而这个特殊的情况,最明显的一个就是采用了  自定义错误页 或  Url Rewrite 的时候。
在使用了自定义错误页或者 Url Rewrite 时,如果碰到了一个并不存在的资源,服务器会将请求重定向到指定的页面,而这时服务器会认为请 求得到了正确的响应,响应状态码会被设置为 200 OK,如果在程序中不另加设置,客户端就会请求一个不存在的资源时得到了 200 OK 这个本该只 在得到正确响应时才能得到的状态码。这对于一般用户来说,可能没有多大的影响,但对于搜索引擎却是不友好的,搜索引擎会不断的索引这些并不存在的资源。
所以, 如果使用了自定义错误页或 Url Rewrite,那么需要在程序中根据实际情况来设置状态码是 200 OK 还是 404 Not Found
例如,在 IIS 中设置自定义错误页为 404.asp 后,IIS 会把对不存在资源的请求重定向到 404.asp,状态码被设置为 200 OK,在 404.asp 中,就需要把状态码给改回来:
[ 复制代码到剪贴板 ]
  • <%
  • Response.Status = "404 Not Found"
  • %>
4.2 201 Created
201 Created 通常用来表示请求的资源不存在,但是已经被创建了。一般可以用于一些发布协议,如  Atom Publishing Protocolexternal 等,你在使用 Atom 协议去发布一篇 blog 时,服务端可能会返回一个 201 Created 的状态码,表示你发送的请求已经被接受了,并且你的文章也已经成功创建了。
如果服务端要返回一个 201 Created 状态码,那么同时也应该在响应体头部的 Location 字段中指出新创建资源的位置,例如如果 是发布一篇 blog,那么服务端在返回 201 状态码的时候,同时在 Location 字段中指出这篇新的 blog 的 URI 是什么,这样客 户端可以根据这个 URI 立即去访问这篇 blog。
4.3 202 Accepted
在 201 Created 中提到,使用发布协议时,服务端可以返回一个 201 的状态码,提示客户所发送的请求已经被正确响应,并且资源也已 经被正确创建了。但是在某些时候,客户所请求的资源并不是可以立即创建,可能需要被审核,可能在一个队列当中等等,在这时,服务端并不能直接返回一 个 201 Created 状态码,因为返回这个状态码时,客户端认为它所发送的请求已经被正确处理了,但事实中,这个请求仍旧在处理过程之中。
这个时候,202 Accepted 状态码就是一个更好的选择。202 Accepted 可以理解为:你的请求我收到了,但是还没有处理完成, 请稍候。更实际一点,你可以理解为你去某个 Google Group 申请了成员资格,Google Group 系统接受了你的申请,但是这 个 Group 是需要管理员审核的,于是,你得到了一个 202 Accepted 的状态,表示你的请求已经被收到了,但是还没有处理完成,你需要等 待。
202 Accepted 要求客户端等待服务端的处理完成,但是 HTTP 协议是无状态的,因此服务端并不会也不能主动去通知客户商,说“哎, 你的请求已经处理好了,地址是xxx”。那么,跟踪请求状态这个任务就需要客户端去做。但是客户端要跟踪,也得有个对象,那个这个对象就是服务端该提供给 客户端的了。
服务端在返回 202 Accepted 状态码的同时,应该给出当前请求的状态,或者给出一个状态监视器的地址。
服务端直接在响应 202 Accepted 状态码的同时给出请求处理的状态就相当于我们打电话去订餐,第一次打电话给餐厅,餐厅响应一 个 202 Accepted:“啊,我们收到你的订单了,一会儿就给您送去。”,那么等了一会,怎么还没来,再打电话过去,餐厅接着响应一个 202, 并告诉我们:“您的订单已经在路上了,过会就能收到了,请稍等。”,于是,我们可以通过不断地发送请求来知道我们的订单到底怎么样了。
而另外一种方式,服务端返回一个状态监视器的地址则相当在我们在申请某个 Google Group 成员资格时,提交申请之后,我们要做的就是看看邮箱,看看有没有来自 Google Group 的审核邮件。
当然,客户端与服务端对 202 Accepted 所返回的响应体应该有一个共同的规范,这样才能通过这个响应来了解请求的处理进程。
4.4 203 Non-Authoritative Information
203 表示非权威应答,说明这个响应头可能并不是原始服务器的响应头,在中间传输过程中附加了一些其他的信息,诸如通过了一些代理,加了一些额外的头部信息等等。
4.5 204 No Content
服务端返回 204 No Content 这个状态码时,表示客户端发送的已经正确响应了,但是并不会返回响应实体,只会更新头部信息。客户端在 收到 204 No Content 时,当前显示的内容不会变化,显示的内容还是之前的文档,只是地址栏里的地址已经变成新的网址。
4.6 205 Reset Content
按照 RFC2616 中的描述,客户端在收到 205 Reset Content 状态码时,应该重置当前文档内容,相当于重新加载发送请求的 页面,但是测试发现,Firefox 3.0 与 Chrome 依旧显示当前文档,不会重新加载,而 Safari 和 IE 则直接重定向到了所请求 的网址,相当于不支持 205 Reset Content 状态码。
4.7 206 Partial Content
206 Partial Cotent 在多线程下载中相当常见,它表示服务端只处理了部分内容或者是只返回了部分内容。
按照 RFC2616 说明,在返回 206 Partial Content 状态码时,服务端需要在头部中指定一些必需的字段,如 Date、 Content-Length 等,这些头部字段的使用都有一定的条件及限制,如 Content-Length 必须与响应体的实际大小一致,如果在响 应 200 OK 时服务端发送了 ETag 或者 Content-Location 的头部字段,在响 应 206 Partial Content 时也必须发送同样的头部字段,具体可以参考  RFC2616 Section 10.2.7external
例如可以在 ASP 中实现部分响应来支持多线程下载:
[ 复制代码到剪贴板 ]
  • <%@LANGUAGE="JScript" CODEPAGE="65001"%>
  • <%
  • // code from www.xujiwei.com
  • // 获取请求中是否有 Range 参数
  • var http_range = Request.ServerVariables("HTTP_RANGE") + ';
  • var filename = "asplite.zip";
  • // 创建 ADODB.Stream 对象并载入文件
  • var stream = new ActiveXObject("ADODB.Stream");
  • stream.Type = 1;
  • stream.Mode = 3;
  • stream.Open();
  • stream.LoadFromFile(Server.MapPath(filename));
  • // 让客户端自动识别文件类型及文件名
  • Response.ContentType = "application/x-zip-compressed";
  • Response.AddHeader("Content-Disposition", "inline; filename=" + filename);
  • // 判断是否响应部分
  • if(http_range == ' || http_range == 'undefined') {
  •     Response.Status = "200 OK";
  •     Response.BinaryWrite(stream.Read());
  • }
  • else {
  •     Response.Status = "206 Partial Content";
  •     var ranges = http_range.substr(6).split("-");
  •     var start = +ranges[0], end = +ranges[2];
  •     stream.Position = start;
  •     var size = end - start;
  •     if(size > 0) {
  •         Response.BinaryWrite(stream.Read(size));
  •     }
  •     else {
  •         Response.BinaryWrite(stream.Read());
  •     }
  • }
  • %>
当然,上面代码中的判断部分并不严密,但是,多线程下载工具也并不一定严格遵守这些规则,所以,还是可以偷偷懒的。
后记
断网了几天,断电了两天,这篇文章终于还是出来了。1xx 和 2xx 系列的写完了,接下来就是重定向的 3xx 系列了。
Learn HTTP 1.1 Status Code Part.2
by xujiwei( http://www.xujiwei.com/external)
前一篇介绍了 2xx 系列状态码,这一篇介绍 3xx 重定向系列。
5 重定向 3xx 系列状态码
3xx 系列状态码一般是用来作为重定向的,并且在重定向的过程中,一般不需要用户的参与,也就是说,重定向的过程是由浏览器来控制的。但是如果重定向后的请求的方法不是 GET 或 HEAD 的话,还是需要用户参与的,不过也许仅仅是确认一下是否同意发送数据而已:)
另外,开发者要注意的是, RFC2616 Section 10.3external 的 备注中提到了在前一版本的 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.3external 的备注中说到在  RFC 1945external 和  RFC 2068external 中是不允许客户端改变重定向后的请求方法的,但是目前多数的客户端都是直接重定向然后把方法改为 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。可以二者选其一,也可以两个都用。 PJBlogexternal 就使用了 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 = "p_w_picpath/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.6external 中并没有详细指出 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 很重要:)
前一篇介绍了 3xx 系列状态码,这一篇介绍 4xx 客户端错误系列。
6. 客户端错误 4xx 系列状态码
4xx 系列状态码可以算是对用户来说相当熟悉的一类状态码,这个系列的状态码通常都会带一段描述信息来描述服务端在处理请求时出现了什么状况,用户能直观地看到服务端返回的信息,而不像 3xx 系列,浏览器会自动处理那些个状态码,不需要用户的参与。
但是 4xx 系列状态码之间的区别只是它所代表的意义不同,它们的表现形式是可以随心所欲的,例如一些著名站点的 404 页面都是精心设计过的。
6.1. 400 Bad Request
请求格式错误,这可能存在于客户端构造的 HTTP 头不符合要求等,这时服务端会返回一个 400 Bad Request,而客户端在不修改请求数据的情况,不可以再次发送这个请求。
6.2. 401 Unauthorized
未授权错误,用于 HTTP 认证。如果客户端请求的资源需要认证,那么服务端可以响应一个 401 Unauthorized,同时在头部添加一 个 WWW-Authenticate 字段表示认证方式,客户端在收到 401 Unauthorized 时,可以提示用户输入用户名和密码,并根据 服务端发送过来的认证方式加密认证数据,再重新发送之前的请求。
HTTP 认证方式中认证数据的传送及格式可以参考  RFC 2617 HTTP Authentication: Basic and Digest Access Authenticationexternal
[ 复制代码到剪贴板 ]
  • <%@LANGUAGE="JScript" CODEPAGE="65001"%>
  • <%
  • // code from www.xujiwei.com
  • var auth = Request.ServerVariables("HTTP_AUTHORIZATION") + "";
  • // 判断客户端输入的用户名及密码是否为 hello 及 world
  • // aGVsbG86d29ybGQ= 是将用户名密码以冒号连接并使用 BASE64 编码后的字符串
  • if (auth == 'Basic aGVsbG86d29ybGQ=') {
  •     Response.AddHeader("WWW-Authenticate", auth);
  •     Response.Write("Authorized");
  • }
  • else {
  •     Response.Status = "401 Unauthorized";
  •     Response.AddHeader('WWW-Authenticate', 'Basic realm=AuthTest');
  •     Response.Write("Unauthorized");
  • }
  • %>
6.3. Payment Required
保留的状态码,看字面意思可以在未来用于电子商务之类的网站。
6.4. Forbidden
很直接的,服务端知道客户端想干嘛,但是不想满足它的请求。在 服务端XMLHTTP进阶应用-User Agent伪装中有一个简单的例子,使用 ServerXMLHTTP 抓取 Google 新闻的 RSS:
[ 复制代码到剪贴板 ]
  • <%@LANGUAGE="JScript" CODEPAGE="65001"%>
  • <%
  • // code from www.xujiwei.com
  • var url = "http://news.google.cn/?output=rss";
  • var xmlhttp = new ActiveXObject("MSXML2.ServerXMLHTTP.5.0");
  • xmlhttp.open("GET", url, false);
  • xmlhttp.send("");
  • Response.BinaryWrite(xmlhttp.responseBody);
  • xmlhttp = null;
  • %>
Google 的服务器很直接的返回了一个 403 Forbidden,也就是说,Google 的服务器知道我的程序想去抓取新闻的 RSS, 但 ServerXMLHTTP 默认的 User-Agent 会让 Google 服务端的程序识别出来访问页面的并不是一般的浏览器而是一 个 ServerXMLHTTP 组件,这很可能是一些垃圾网站来抓取的,而这是它所不允许的,所以返回一个 403 Forbidden 禁止这种行 为。
如果请求的方法不为 HEAD 且请求被禁止的原因可以让客户端知道的话,服务端可以直接在响应中描述请求被禁止的原因,这样客户端就可以显示出来 以提示用户是什么原因导致了这次请求被拒绝。如果不提供请求被禁止的原因,服务端可以使用 404 Not Found 来代 替 403 Forbidden,这样可以让客户端少一些迷惑。
6.5. 404 Not Found
服务器找不到到对应请求 URI 的任何资源,也不会指出这个情况是暂时的还是永久的。
对用户来说,404 Not Found 这个状态码可以算是最直接的了,一般网址拼错了,服务器很直接的就会发送一 个 404 Not Found,通常情况话,可以认为表示用户输入的网址有问题,但事实上是什么情况可能就随服务端程序来定了,如果程序认为用户没有授 权查看这个页面,也有可能直接返回一个 404 Not Found,所以,404 Not Found 不是一个很友好的状态码。
所以,如果用户所请求的资源已经永久的被删除了,那么服务器可以返回一个 410 Gone 来代替 404 Not Found。
另外,正如在 第一篇里所讲的,如果服务器定义了自定义错误页面,开发者必须注意服务器是否会仍旧发送 404 Not Found 这个状态码,如果不是,那么就需要在程序中指定状态码为 404 Not Found。
6.6. 405 Method Not Allowed
如果收到了这个状态码,表示客户端使用了一个对于所请求的资源来说不被允许的方法,例如在 IIS 里默认情况下是不允许使用 POST 方法来请 求静态 HTML 页面的,如果用 POST 方法来请求静态 HTML 页面,IIS 就会返回 405 Method Not Allowed:
test.html
[ 复制代码到剪贴板 ]
  • <!-- code from www.xujiwei.com -->
  • <form action="test.html" method="post">
  • <input type="text" name="message" /><br />
  • <input type="submit" value="Submit" />
  • </form>
在发送 405Method Not Allowed 状态码的同时,服务端也应该在头部中添加一个名为 Allow 的字段,表示请求这个资源所允许的方法,例如上面的代码 IIS 就会返回: Allow : GET, HEAD, OPTIONS, TRACE
因此,如果在开发过程中碰到了 405 错误,可以检查一下服务器的设置,是否对所请求的资源禁止了某些请求方法。
后记
4xx 系列有 18 个状态码,比较多,因此分成几篇来写。
前一篇介绍了 4xx 系列状态码中 400 到 405,这篇接着讲剩下的 4xx 系列。
6.7. 406 Not Acceptable
客户端在收到 406 Not Acceptable 状态码时,一般是因为服务器在处理客户端请求时,找不到对应客户端发送过来的请求头部 的 Accept 字段中列举的响应类型的响应内容,例如使用 Ajax 时,如果通信数据格式选择 json,发送请求时头部 Accept 字段一般 会设置为 application/json,但是如果服务端并不支持 json,那么服务端可以发送一个 406 Not Acceptable,同时 也应该在 Content-Type 字段中指定客户端所请求的资源的格式,这时客户端可以考虑使用另外的格式来接收响应。
不过备注中说到,如果没有合适的资源来响应客户端的请求,HTTP 1.1 服务器也可以发送一个响应。当然发送 406 Not Accepteble 是一个更好的选择,这样客户端就能知道什么格式的数据才能是服务器所能接受的。
6.8. 407 Proxy Authentication Required
通常情况下,这个状态码应该是代理服务器发过来的,表示客户端首先应该进行代理服务器的认证才能继续完成请求,在 IE 里可能会表现为提示用户输入代理服务器的用户名和密码,如果选择取消则会提示“您必须通过代理的身份验证,然后 Web 服务器才能处理您的请求。”。
6.9. 408 Request Timeout
请求超时,出现这个状态码时,有可能是服务端的问题,也有可能是客户端的问题(呃,有点废话……)。但是,可能在一些 Web 服务器中,并不会在 运行超时时返回 408 Request Timeout。例如在 ASP 中,如果运行一个超长时间的脚本,IIS 并不会发 送 408 Request Timeout 状态码,而是发送一个 500 Internal Server Error,所以如果在写一个程序时,如 果运行时间已经超过了预期,那么可以自己结束程序运行并向客户端发送一个 408 Request Timeout。
在 HTTP 1.1 状态码的定义中,如果客户端收到一个 408 Request Timeout 状态码,可以在过一段时间后重新发送这个请 求。因此,如果是因为客户端的原因导致的脚本超时,例如客户端网络速度太慢等,推荐发送一个 408 Request Timeout 状态码,但是如果 是由服务器的原因,例如不能处理过大的文件,则应该直接给出错误,而不是发送一个 408 Request Timeout。
6.10. 409 Conflict
409 Conflict 表示服务器在处理客户端发送的请求时发生了冲突,和 403 Forbidden、404 Not Found 等一样,服务器应该同时也给出出问题的原因或解决方法。
很可能发生 409 Conflict 错误的时候是使用 PUT 方法发送请求时,如果所请求的资源已经被第三方修改过,这时就需要解决两次修改之间的冲突问题。
这个状态码可以与 SVN 中的冲突一起理解,在 SVN 中,如果你修改一个文件的工作副本,在提交之前又有另外一个人修改并提交了同一个文件的副本,这时就需要解决两个版本之间的冲突,例如合并两个版本之间的不同之处等。
6.11. 410 Gone
如果客户端请求的资源已经永久删除了,并且不知道有什么其他的资源可以代替它,那么服务器就会发送一个 410 Gone 状态码,指示这个资源已 经永久删除了。如果需要的话,客户端可以在经过用户确认后删除这个资源的链接。如果服务器不能确定客户端所请求的资源是永久的被删除了还是由于某种原因临 时不可用了,那么 404 Not Found 是一个更好的状态码。
一般情况下,410 Gone 可以用于服务器上一些临时的资源,或者不再提供的某些资源。例如一些不用存档的通知信息等。
与 404 Not Found,这个状态码显示更绝情一些,出现 404 Not Found 时,服务器的意思是过一段时间之后这个资源有可能 还会再可用,但是如果收到了 410 Gone,那么很明显的通知了客户端“你走吧,这个资源已经不在了,而且也找不到它新的地址了”。那么,如果不是那 么确定的时候,还是用 404 Not Found 吧,以后还有“挽回”的可能。
6.12. 411 Length Required
客户端发送请求时,如果是使用的 POST 或 PUT 方法,一般都应该加送一个 Content-Length 头,用来指明所发送请求实体的大小,这样服务器在处理请求时可以提取出整个请求的数据。
如果客户端在发送请求时从服务端收到了一个 411 Length Required 状态码,那么应该在原来请求的基础上添加一个正确可用的 Content-Length 头部字段再重新发送。
6.13. 412 Precondition Failed
预处理失败,这通常在客户端发送来的请求中包含了服务端不允许的头部字段或头部字段中的值或其格式不正确,这样可以防止客户端发送一些非法头部信息。
我对这个状态码的定义并不是很懂,如果理解有错希望指正,原文定义在 这里external
6.14. 413 Request Entity Too Large
客户端在收到 413 Request Entity Too Large 状态码时应该意识到它所发送的请求是否过大了。例如在上传文件时,如果 选择了一个过大的文件,那么服务器在没有接收完所有数据时就可能会发送一个 413 Request Entity Too Large 状态码,这时客 户端应该提示用户文件过大并重新选择一个文件等,同时服务器可能会关闭连接以免继续传输请求浪费带宽。
如果出现请求实体过大并不是因为服务器不能处理过大的文件,而是由于一些临时的原因导致了服务器暂时不能客户端的请求,那么服务器可以在发 送 413 Request Entitiy Too Large 状态码后再头部添加一个 Retry-After 字段,表示客户端可以在此时间之后 重新尝试发送请求。
6.15. 414 Request-URI Too Long
在  HTTP/1.1 Section 3.2.1external推 荐的 URI 长度应该控制在 255 字节以内,因为一些比较老的客户端不支持大于 255 字节的 URI。虽然这个限制在现在已经有些过时了,不过 浏览器对 URI 的长度也还是有限制的,例如在 IE 中 URI 的最大长度在 2083 个字节。测试中发现服务器对于 URI 的长度也是有限制 的,这也就是 414 Request-URI Too Long 存在的意义了。在服务器觉得客户端所发送的 URI 过长时,就可以返回一 个 414 Request-URI Too Long 状态码向客户端说明发生了什么错误。
通常这个情况会发生在客户端使用了 GET 方法去发送本应使用 POST 方法发送的数据,例如将一个表单使用 GET 方法发送,那么表单中的 所有字段会以查询字符器的方式附加到 URI 的最后,这时很有可能 URI 的长度就会超过了服务器的限制,从而使服务器发送了 了 414 Request-URI Too Long 状态码。
还有一种情况就是用户刻意去构造一个超长的 URI 来***服务器了,这个内容不是本篇的范围,就不深入了,我暂时也没能力去讲有关缓冲区溢出之类的内容:)
6.16. 415 Unsupported Media Type
客户端在发送请求时,一般都需要在头部中添加一下 Content-Type 字段,用于指明所发送请求的内容的类型,例如使用 POST 方法发 送表单时 Content-Type 字段的值就为 application/x-www-form-urlencoded。因此,客户端如果收到 了 415 Unsupported Media Type 状态码,也就意味着它发送的请求的格式不被服务器接受,像在 XML-RPC 的调用过程 中,通信体格式应该为 application/xml 或 text/xml,服务端如果严格检查的话,应该在请求格式不为 application /xml 或 text/xml 时给出一个 415 Unsupported Media Type 的状态码,当然这个检查并不是必须的,XML 也 是文本内容,text/plain 的类型应该也是可接受的。
6.17. 416 Requested Range Not Satisfiable
在多线程下载中,下载工具是通过在请求头部中添加 Range 字段来指定客户端所需要的资源的内容范围,服务器通过识别这个 Range 字段, 定位到资源的指定位置,并发送指定范围内的内容。如果客户端请求头部中的 Range 字段的起始值超过了它所请求资源的大小并且请求头部没有 If- Range 字段,那么服务器就会返回 416 Request Range Not Satisfiable,这时客户端最好应该删除已经下载的数据并 重新发送一个不带 Range 字段的请求以确定资源的真实大小。
如果是使用字节确定范围的话,那么服务器在返回 416 Request Range Not Satisfiable 状态码的同时,也应该添加 一个 Content-Range 字段,用于指定客户端所请求的资源的实际大小,这样客户端在收到响应时可以根据请求资源的实际大小重新发送一个请求。
6.18. 417 Expectation Failed
客户端发送的请求中如果有 Except 字段,但是服务器无法满足的话,那么就可以返回一个 417 Expectation Failed 状态码。
这个状态码一个常见的用途就是客户端的请求带了一个 Except: 100-continue 字段,但是代理服务器或者 Web 服务器不能处理这个处理 100 Continue,所以服务器返回 417 Expectation Failded 状态码。
这个状态码与 100 Continue 以及请求头部 Except 字段之间的关系以及使用可以参考  HTTP/1.1: Connection, Section 8.2.3 Use of the 100 (Continue) Statusexternal
后记
上个星期是学期最后一周,一堆考查课作业要交,晕得一塌糊涂……
前一篇介绍了 4xx 系列状态码中 406 到 417,这篇接着讲剩下的 5xx 系列。
7. 5xx 系列内部服务器错误
在服务器处理请求时出问题了,服务器可以发一个 5xx 系列错误码给客户端,表示服务器在处理请求的时候出问题了,问题是出在服务器身上而不是客 户端身上。另外,服务器如果发送了 5xx 系列的状态码,除非客户端是使用的 HEAD 方法,否则服务器还应该在响应中给出错误的描述、原因以及解决 方法等,客户端可以把这些东西给用户看,让用户知道是什么原因导致了请求出错。
不过通常情况下,用户在收到 5xx 系列错误码的同时,是不会收到导致错误的详细技术信息的,这是为了保护服务端程序安全的需要。服务器一般会返 回一个页面描述所出的错误是由什么原因引起的,而不涉及详细技术信息。例如在 ASP.NET 中,可以在 web.config 中 的 customErrors 节配置自定义错误的显示方式,有 On、Off、RemoteOnly 三种方式,可以根据具体的需要来设置, 像 RemoteOnly 就会给本地开发者显示详细的错误信息以及调试信息,而对远程访问者则会只显示服务器运行时出现错误,没有详细的调试信息。
7.1. 500 Inernal Server Error
500 Internal Server Error 表示内部服务器错误,这个对于一般用户来说是比较常见的,许多 IIS 服务器都会配置成不 发送详细错误信息,而 IIS 的默认设置就是在服务器发生内部错误时发送 Internal Server Error 这个字符串代替具体的错误信 息。
对于 ASP 程序来说,如果是使用 VBScript 作为脚本语言,那么可以在代码的最前面加一句 On Error Resume Next 来忽略错误继续运行程序,或者加一个错误处理子程序,或者直接停止脚本的运行,以防止关键信息的泄露。
7.2. 501 Not Implemented
501 未实现表示服务器对于客户端请求的方法没有实现,从而不能满足客户端的请求。这个错误比较少见,我目前还没有见过这个错误,或者在使用一个 开发中的 Web 服务器时会出现这个错误,例如使用 PUT 方法发送请求但是服务器不支持 PUT 方法,那么服务器就可以发 送 501 Not Implemented 错误码。
7.3. 502 Bad Gateway
当服务器是作为一个网关或者代理时,在处理客户端请求时从上游服务器收到了一个无效的响应,这时服务器可以返回一个 502 Bad Gateway。
出现这种情况可能的原因有网关或代理过滤了所请求的网址,或者真正的服务器挂掉了,这些时候代理或网关都可以发送 502 Bad Gateway。
那么,502 Bad Gateway 是用来表示代理或网关在处理请求时发生了错误,并不一定是原始服务器出现了问题。
7.4. 503 Service Unavailable
503 服务不可用,表示服务器当前负载过大或者正在维护,这通常表示这是一个临时的情况,服务器会在一定的时间之后恢复正常。如果时间已知,例如 服务器在维护,那么服务器在发送 503 Service Unavailable 的同时,可以在响应头部添加一个 Retry-After 字段表示 维护会持续多久,在指定的时间之后,客户端就可以尝试再次发送请求了。
如果是使用虚拟主机,那么在网站负载过大的时候,一般都会出现 503 Service Unavailable,因为提供商会限制 CPU 使用率 :)
不过 503 并不一定表示服务器负载过大或者正在维护,也可能是服务器只是想拒绝处理请求而已。那么,我们也可以在处理 IP 屏蔽之类的情况时直接来一个 503 Service Unavailable :)
7.5. 504 Gateway Timeout
与 502 Bad Gateway 类似,这个状态码一般是代理或网关服务器返回的,出现这个状态码意味着代理服务器在处理请求时超过了超时时间仍个没有从原始服务器接收到响应内容,也有可能是因为在解析原始服务器的域名时超时了。
7.6. 505 HTTP Version Not Supported
客户端使用 HTTP 版本不被服务器支持,或者服务器拒绝支持客户端所使用的 HTTP 版本,使用向一个只支持 HTTP 1.0 的服务器发 送了一个使用 HTTP 1.1 的请求,那么服务器可以响应 505 HTTP Version Not Supported。在 RFC 指出服务器 如果发送了 505 HTTP Version Not Supported 状态码,那么同时也应该在响应中指出为什么客户端所用的 HTTP 版本不 被支持以及服务器支持哪些协议的哪些版本。