HTTP
常见状态码
状态码 | 含义 |
---|---|
1xx | Information response |
100 | continue |
101 | Switching Protocols |
2xx | success |
200 | OK: 成功响应 |
201 | Created |
202 | Accepted |
204 | No Content: 服务器已经成功处理请求,没有返回任何 Body (比如服务器收到一个发邮件的请求,服务器返回 204,表示已经收到请求,邮件后续会发送) |
206 | Partial Content: 服务器返回了某个文件的一部分 |
3xx | redirection |
300 | Multiple Choices 是一个用来表示重定向的响应状态码,表示该请求拥有多种可能的响应。用户代理或者用户自身应该从中选择一个。 |
301 | Moved Permanently: 永久重定向 |
302 | Found: 临时重定向 |
304 | Not Modified: 浏览器通过 If-None-Match 头或 If-Modified-Since 头询问,服务器告知文件未改动 |
4xx | client errors |
400 | Bad Request: 客户端发送的 HTTP 有语法错误、太大、帧错误等 |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
405 | Method Not Allowed |
429 | Too Many Requests |
499 | Nginx 自己定义的,client has closed connection |
5xx | server errors |
500 | Internal Server Error |
502 | Bad Gateway |
503 | Service Unavailable |
504 | Gateway Timeout |
HTTP 方法
方法 | 含义 |
---|---|
GET | 获取数据 |
HEAD | 与 GET 类似,但只返回响应头 |
POST | 提交表单 |
PUT | 用一个新的资源完全替换掉服务器的资源 |
DELETE | 删除资源 |
CONNECT | 建立一个 tunnel |
OPTIONS | 询问服务器支持哪些方法 |
TRACE | 发起环回诊断,主要用于诊断 |
PATCH | 对服务器资源进行部分更新 |
HTTP 报文
在浏览器中输入 “kunzhao.org”,然后敲击回车的时候,浏览器发送的请求报文示例:
GET / HTTP/1.1
Host: kunzhao.org
Accept-Language: en-US
服务器对应的返回的响应报文示例:
HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 155
Last-Modified: Wed, 08 Aug 2020 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: "3f80f-1b6-3e1cb03b"
Accept-Ranges: bytes
Connection: close
<html>
<head>
<title>首页|赵坤的个人网站</title>
</head>
<body>
<p>首页内容示例</p>
</body>
</html>
常见 HTTP 头
通用头
通用头:请求和响应都可以使用的头。
HTTP 头 | 示例 | 含义 |
---|---|---|
Date | Date: Wed, 21 Oct 2015 07:28:00 GMT | 消息的日期和时间 |
Cache-Control | Cache-Control: no-cache | 控制缓存的策略 |
Connection | Connection: keep-alive | 服务器/浏览器读/写完消息后,这个 TCP 连接是否应该维持打开的状态,还是说立即关闭 |
请求头
HTTP 头 | 示例 | 含义 |
---|---|---|
User-Agent | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36 Edg/85.0.564.51 | 包含操作系统、浏览器等字符串形式的描述信息 |
Host | Host: example.com:8001 | 服务器的域名和端口 |
Cookie | Cookie: PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1 | 存储了由服务器 Set-Cookie 头设置的 Cookie 信息 |
Referer | Referer: example.org | 哪个 URL 跳转到当前页面的 |
Accept | Accept: text/html;q=0.9,image/webp,image/apng | 浏览器期望收到的是什么 MIME 类型的资源 |
Accept-Charset | Accept-Charset: utf-8, iso-8859-1;q=0.5 | 浏览器理解哪些字符编码 |
Accept-Encoding | Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 | 浏览器能够处理哪些压缩算法 |
Accept-Language | Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 | 浏览器期望返回的是英文?中文?还是俄文? |
If-Match | If-Match: “67ab43”, “54ed21”, “7892dd” | 只有匹配任意一个 ETag,服务器才会返回资源 |
If-None-Match | If-None-Match: “bfc13a64729c4290ef5b2c2730249c88ca92d82d” | 只要有不匹配的 ETag,服务器就会返回资源,响应码:200 |
If-Modified-Since | If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT | 只有在这个日期之后修改后,服务器才返回资源,响应码:200 |
If-Unmodified-Since | If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT | 只有在这个日期之后未被修改过,服务器才会返回资源 |
响应头
HTTP 头 | 示例 | 含义 |
---|---|---|
Age | Age: 24 | 这个资源在代理缓存中待了有多少秒了 |
Location | Location: /index.html | 页面重定向到哪个 URL 了 |
Server | Server: Apache/2.4.1 (Unix) | 服务器用的是哪款 Web 服务器软件/框架 |
Set-Cookie | Set-Cookie: id=a3fWa; Max-Age=2592000 | 设置 Cookie |
Last-Modified | Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT | 资源的上次修改时间 |
ETag | ETag: “33a64df551425fcc55e4d42a148795d9f25f89d4” | 唯一标识一个资源 |
Entity 头
Entity 头:请求和响应都可以使用的头,并且是用来描述消息体的头。
HTTP 头 | 示例 | 含义 |
---|---|---|
Content-Length | Content-Length:128 | 描述 entity body 的大小(单位:字节) |
Content-Language | Content-Language: en-US | 描述这个 entity 面向的受众,是中国用户?还是美国用户? |
Content-Encoding | Content-Encoding: gzip | 采用的什么压缩算法? |
HTTP 1.1 upgrade
- 升级为 HTTP over TLS
浏览器发送:
GET http://example.bank.com/acct_stat.html?749394889300 HTTP/1.1
Host: example.bank.com
Upgrade: TLS/1.0
Connection: Upgrade
服务器响应:
HTTP/1.1 101 Switching Protocols
Upgrade: TLS/1.0, HTTP/1.1
Connection: Upgrade
- 升级为 WebSocket
浏览器发送:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
服务器响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
- 升级为 HTTP/2
浏览器发送:
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
服务器响应:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
POST vs GET
- 在某些浏览器中,通过 Ajax 请求调用 GET 请求会缓存结果,拿到的响应可能只是一个缓存结果
- GET 参数暴露在了 URL 中
- POST 可以传递更多的信息、更多的数据
GET 存在的必要
既然数据通过 POST 请求也可以获取到,那 GET 存在的必要是什么?
- 浏览器地址栏、
<a href="kunzhao.org">
这些链接等发送的都是GET
请求 - GET 请求的 URL 可以放到收藏夹
- GET 请求的 URL 可以多次刷新
持久连接
HTTP 1.0
Connection: keep-alive
HTTP 1.1
所有连接默认都是持久连接。除非在请求头部显式加上 Connection:Close
属性,服务器才会处理完请求主动关闭连接。Content-Length
对于服务器计算比较困难、耗时,因此引入 Transfer-Encoding: chunked
属性,告诉客户端,响应的 Body 分成了一块块的,块与块之间有间隔符,所有块结尾有特殊标记,方便客户端判断出响应的末尾。
Server 如何知道接受完一个 HTTP 请求了 ?
- GET 方法:收到请求头部结束后的空行,就代表接受完了。
- POST 方法:① 如果没有
Transfer-Encoding
,那么以Content-Length
的长度进行接受,接受完为止;② 有设置Transfer-Encoding
,则以chunked
协议设定的boundary
进行接受。
HTTP 轮询
“轮询”是指不管服务端数据有无更新,客户端每隔定长时间请求拉取一次数据,可能有更新数据返回,也可能什么都没有。配置中心如果使用「轮询」实现动态推送,会有以下问题:
-
推送延迟。客户端每隔 5s 拉取一次配置,若配置变更发生在第 6s,则配置推送的延迟会达到 4s。
-
服务端压力。配置一般不会发生变化,频繁的轮询会给服务端造成很大的压力。
-
推送延迟和服务端压力无法中和。降低轮询的间隔,延迟降低,压力增加;增加轮询的间隔,压力降低,延迟增高。
HTTP 长轮询
携程的 DCC 客户端设计了一个 LongPollService
。
看源码其实就是有一个线程池,定时去请求远程的服务:
//90 seconds, should be longer than server side's long polling timeout, which is now 60 seconds
private static final int LONG_POLLING_READ_TIMEOUT = 90 * 1000;
HttpRequest request = new HttpRequest(url);
request.setReadTimeout(LONG_POLLING_READ_TIMEOUT);
客户端发起长轮询,如果服务端的数据没有发生变更,会 hold 住请求,直到服务端的数据发生变化,或者等待一定时间超时才会返回。返回后,客户端又会立即再次发起下一次长轮询。
可能有人会有疑问,为什么一次长轮询需要等待一定时间超时,超时后又发起长轮询,为什么不让服务端一直 hold 住?主要有两个层面的考虑,一是连接稳定性的考虑,长轮询在传输层本质上还是走的 TCP 协议,如果服务端假死、fullgc 等异常问题,或者是重启等常规操作,长轮询没有应用层的心跳机制,仅仅依靠 TCP 层的心跳保活很难确保可用性,所以一次长轮询设置一定的超时时间也是在确保可用性。除此之外,在配置中心场景,还有一定的业务需求需要这么设计。在配置中心的使用过程中,用户可能随时新增配置监听,而在此之前,长轮询可能已经发出,新增的配置监听无法包含在旧的长轮询中,所以在配置中心的设计中,一般会在一次长轮询结束后,将新增的配置监听给捎带上,而如果长轮询没有超时时间,只要配置一直不发生变化,响应就无法返回,新增的配置也就没法设置监听了。
配置中心在实现长轮询时,往往采用异步响应的方式来实现。而比较方便实现异步 HTTP 的常见手段便是 Servlet3.0 提供的 AsyncContext
机制。
设计 HTTP 幂等接口
生成唯一的处理号,用于标识后续操作。
取钱的 API 设计:
bool withdraw(account_id, amount)
如果取钱的 API 设计需要考虑幂等性:
int create_ticket()
bool idempotent_withdraw(ticket_id, account_id, amount)