You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 12 | 响应状态码该怎么用?
前两讲中我们学习了HTTP报文里请求行的组成部分包括请求方法和URI。有了请求行加上后面的头字段就形成了请求头可以通过TCP/IP协议发送给服务器。
服务器收到请求报文,解析后需要进行处理,具体的业务逻辑多种多样,但最后必定是拼出一个响应报文发回客户端。
响应报文由响应头加响应体数据组成,响应头又由状态行和头字段构成。
我们先来复习一下状态行的结构,有三部分:
![](https://static001.geekbang.org/resource/image/a1/00/a1477b903cd4d5a69686683c0dbc3300.png)
开头的Version部分是HTTP协议的版本号通常是HTTP/1.1,用处不是很大。
后面的Reason部分是原因短语是状态码的简短文字描述例如“OK”“Not Found”等等也可以自定义。但它只是为了兼容早期的文本客户端而存在提供的信息很有限目前的大多数客户端都会忽略它。
所以,状态行里有用的就只剩下中间的**状态码**Status Code了。它是一个十进制数字以代码的形式表示服务器对请求的处理结果就像我们通常编写程序时函数返回的错误码一样。
不过你要注意它的名字是“状态码”而不是“错误码”。也就是说它的含义不仅是错误更重要的意义在于表达HTTP数据处理的“状态”客户端可以依据代码适时转换处理状态例如继续发送请求、切换协议重定向跳转等有那么点TCP状态转换的意思。
## 状态码
目前RFC标准里规定的状态码是三位数所以取值范围就是从000到999。但如果把代码简单地从000开始顺序编下去就显得有点太“low”不灵活、不利于扩展所以状态码也被设计成有一定的格式。
RFC标准把状态码分成了五类用数字的第一位表示分类而0~99不用这样状态码的实际可用范围就大大缩小了由000~999变成了100~599。
这五类的具体含义是:
* 1××提示信息表示目前是协议处理的中间状态还需要后续的操作
* 2××成功报文已经收到并被正确处理
* 3××重定向资源位置发生变动需要客户端重新发送请求
* 4××客户端错误请求报文有误服务器无法处理
* 5××服务器错误服务器在处理请求时内部发生了错误。
在HTTP协议中正确地理解并应用这些状态码不是客户端或服务器单方的责任而是双方共同的责任。
客户端作为请求的发起方,获取响应报文后,需要通过状态码知道请求是否被正确处理,是否要再次发送请求,如果出错了原因又是什么。这样才能进行下一步的动作,要么发送新请求,要么改正错误重发请求。
服务器端作为请求的接收方也应该很好地运用状态码。在处理请求时选择最恰当的状态码回复客户端告知客户端处理的结果指示客户端下一步应该如何行动。特别是在出错的时候尽量不要简单地返400、500这样意思含糊不清的状态码。
目前RFC标准里总共有41个状态码但状态码的定义是开放的允许自行扩展。所以Apache、Nginx等Web服务器都定义了一些专有的状态码。如果你自己开发Web应用也完全可以在不冲突的前提下定义新的代码。
在我们的实验环境里也可以对这些状态码做测试验证访问URI“**/12-1**”用查询参数“code=xxx”来检查这些状态码的效果服务器不仅会在状态行里显示状态码还会在响应头里用自定义的“Expect-Code”字段输出这个代码。
例如在Chrome里访问“[http://www.chrono.com/12-1?code=405](http://www.chrono.com/12-1?code=405)”的结果如下图。
![](https://static001.geekbang.org/resource/image/07/d7/07e7a40241a09683c5420e7b311227d7.png)
接下来我就挑一些实际开发中比较有价值的状态码逐个详细介绍。
## 1××
1××类状态码属于提示信息是协议处理的中间状态实际能够用到的时候很少。
我们偶尔能够见到的是“**101 Switching Protocols**”。它的意思是客户端使用Upgrade头字段要求在HTTP协议的基础上改成其他的协议继续通信比如WebSocket。而如果服务器也同意变更协议就会发送状态码101但这之后的数据传输就不会再使用HTTP了。
## 2××
2××类状态码表示服务器收到并成功处理了客户端的请求这也是客户端最愿意看到的状态码。
“**200 OK**”是最常见的成功状态码表示一切正常服务器如客户端所期望的那样返回了处理结果如果是非HEAD请求通常在响应头后都会有body数据。
“**204 No Content**”是另一个很常见的成功状态码它的含义与“200 OK”基本相同但响应头后没有body数据。所以对于Web服务器来说正确地区分200和204是很必要的。
“**206 Partial Content**”是HTTP分块下载或断点续传的基础在客户端发送“范围请求”、要求获取资源的部分数据时出现它与200一样也是服务器成功处理了请求但body里的数据不是资源的全部而是其中的一部分。
状态码206通常还会伴随着头字段“**Content-Range**”表示响应报文里body数据的具体范围供客户端确认例如“Content-Range: bytes 0-99/2000”意思是此次获取的是总计2000个字节的前100个字节。
## 3××
3××类状态码表示客户端请求的资源发生了变动客户端必须用新的URI重新发送请求获取资源也就是通常所说的“重定向”包括著名的301、302跳转。
“**301 Moved Permanently**”俗称“永久重定向”含义是此次请求的资源已经不存在了需要改用新的URI再次访问。
与它类似的是“**302 Found**”,曾经的描述短语是“**Moved Temporarily**”俗称“临时重定向”意思是请求的资源还在但需要暂时用另一个URI来访问。
301和302都会在响应头里使用字段**Location**指明后续要跳转的URI最终的效果很相似浏览器都会重定向到新的URI。两者的根本区别在于语义一个是“永久”一个是“临时”所以在场景、用法上差距很大。
比如你的网站升级到了HTTPS原来的HTTP不打算用了这就是“永久”的所以要配置301跳转把所有的HTTP流量都切换到HTTPS。
再比如今天夜里网站后台要系统维护服务暂时不可用这就属于“临时”的可以配置成302跳转把流量临时切换到一个静态通知页面浏览器看到这个302就知道这只是暂时的情况不会做缓存优化第二天还会访问原来的地址。
“**304 Not Modified**” 是一个比较有意思的状态码它用于If-Modified-Since等条件请求表示资源未修改用于缓存控制。它不具有通常的跳转含义但可以理解成“重定向已到缓存的文件”即“缓存重定向”
301、302和304分别涉及了HTTP协议里重要的“重定向跳转”和“缓存控制”在之后的课程中我还会细讲。
## 4××
4××类状态码表示客户端发送的请求报文有误服务器无法处理它就是真正的“错误码”含义了。
“**400 Bad Request**”是一个通用的错误码表示请求报文有错误但具体是数据格式错误、缺少请求头还是URI超长它没有明确说只是一个笼统的错误客户端看到400只会是“一头雾水”“不知所措”。所以在开发Web应用时应当尽量避免给客户端返回400而是要用其他更有明确含义的状态码。
“**403 Forbidden**”实际上不是客户端的请求出错而是表示服务器禁止访问资源。原因可能多种多样例如信息敏感、法律禁止等如果服务器友好一点可以在body里详细说明拒绝请求的原因不过现实中通常都是直接给一个“闭门羹”。
“**404 Not Found**”可能是我们最常看见也是最不愿意看到的一个状态码它的原意是资源在本服务器上未找到所以无法提供给客户端。但现在已经被“用滥了”只要服务器“不高兴”就可以给出个404而我们也无从得知后面到底是真的未找到还是有什么别的原因某种程度上它比403还要令人讨厌。
4××里剩下的一些代码较明确地说明了错误的原因都很好理解开发中常用的有
* 405 Method Not Allowed不允许使用某些方法操作资源例如不允许POST只能GET
* 406 Not Acceptable资源无法满足客户端请求的条件例如请求中文但只有英文
* 408 Request Timeout请求超时服务器等待了过长的时间
* 409 Conflict多个请求发生了冲突可以理解为多线程并发时的竞态
* 413 Request Entity Too Large请求报文里的body太大
* 414 Request-URI Too Long请求行里的URI太大
* 429 Too Many Requests客户端发送了太多的请求通常是由于服务器的限连策略
* 431 Request Header Fields Too Large请求头某个字段或总体太大
## 5××
5××类状态码表示客户端请求报文正确但服务器在处理时内部发生了错误无法返回应有的响应数据是服务器端的“错误码”。
“**500 Internal Server Error**”与400类似也是一个通用的错误码服务器究竟发生了什么错误我们是不知道的。不过对于服务器来说这应该算是好事通常不应该把服务器内部的详细信息例如出错的函数调用栈告诉外界。虽然不利于调试但能够防止黑客的窥探或者分析。
“**501 Not Implemented**”表示客户端请求的功能还不支持这个错误码比500要“温和”一些和“即将开业敬请期待”的意思差不多不过具体什么时候“开业”就不好说了。
“**502 Bad Gateway**”通常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器时发生了错误,但具体的错误原因也是不知道的。
“**503 Service Unavailable**”表示服务器当前很忙暂时无法响应服务我们上网时有时候遇到的“网络服务正忙请稍后重试”的提示信息就是状态码503。
503是一个“临时”的状态很可能过几秒钟后服务器就不那么忙了可以继续提供服务所以503响应报文里通常还会有一个“**Retry-After**”字段,指示客户端可以在多久以后再次尝试发送请求。
## 小结
1. 状态码在响应报文里表示了服务器对请求的处理结果;
2. 状态码后的原因短语是简单的文字描述,可以自定义;
3. 状态码是十进制的三位数分为五类从100到599
4. 2××类状态码表示成功常用的有200、204、206
5. 3××类状态码表示重定向常用的有301、302、304
6. 4××类状态码表示客户端错误常用的有400、403、404
7. 5××类状态码表示服务器错误常用的有500、501、502、503。
## 课下作业
1. 你在开发HTTP客户端收到了一个非标准的状态码比如4××、5××应当如何应对呢
2. 你在开发HTTP服务器处理请求时发现报文里缺了一个必需的query参数应该如何告知客户端错误原因呢
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,欢迎你把文章分享给你的朋友。
![](https://static001.geekbang.org/resource/image/11/ad/11d330fe6de5b9fe34464a6994162dad.png)
![unpreview](https://static001.geekbang.org/resource/image/56/63/56d766fc04654a31536f554b8bde7b63.jpg)