HTTP

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)

参考