DTeam 技术日志

Doer、Delivery、Dream

HTTP Header 中的黑科技

冯宇 Posted at — Feb 24, 2021 阅读

简单认识 HTTP 协议

HTTP 协议是一种基于 TCP 的纯文本协议,一个基本的 HTTP 协议交互过程如下:

使用 nc 命令演示以及 mock 最基本的 http server 交互响应过程:

终端 1:

nc -kl 8080

终端 2:

curl -v localhost:8080

终端 1 结果类似如下:

GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.75.0
Accept: */*

此时,我们在终端 1 手工输入一个模拟 http server 的响应:

HTTP/1.1 200 OK

Hello

此时,在终端 2 应该看到类似于下面的响应:

< HTTP/1.1 200 OK
<
Hello

HTTP 的协议的请求内容格式如下:

GET / HTTP/1.1          # <======== 第一行为 method location protocol
Host: localhost:8080    ######################
User-Agent: curl/7.75.0 ## 一些可选的 headers
Accept: */*             ######################
                        # 一个空行
{body}                  # 可选的 request body

HTTP 协议的响应内容格式如下:

HTTP/1.0 200 OK                          # 第一行固定 protocol code description
Server: SimpleHTTP/0.6 Python/3.9.1      ################
Date: Sun, 14 Feb 2021 09:24:59 GMT      ## 一些可选的 headers
Content-type: text/html; charset=utf-8   ##
Content-Length: 496                      ################
                                         # 一个空行
{body}                                   # 响应正文内容

通常描述 REST 接口的文档中,直接省略请求过程以及非关键的 Header 部分,贴出关键部分的请求命令和 header,以及 body 部分,以一个登录 API 为例,可能看到的接口文档描述如下:

客户端请求:

POST /login
Content-Type: application/json

{"usernamae":"u1","password":"p1"}

服务端响应:

200 OK
Content-Type: application/json

{"token":"a1b2c3..."}

常见 HTTP Code 总结

参考资料: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status

HTTP 协议的版本的主要演进

HTTP/1.0 增加以下内容:

HTTP/1.1 增加以下内容:

HTTP/2 主要改进:

HTTP/3(QUIC)主要改进(目前仍是草案状态):

参考资料:

HTTP header 中的黑科技实例

基于域名的虚拟主机(Host 头)

通过 client 添加 Host 头(通常不需要用户干预,命令部分和 Host 头部分通常客户端会自动处理)以及服务端响应 Host 头,可以实现同一个服务器上提供多个网站的的场景。例:

GET / HTTP/1.1
Host: localhost:8080

内容协商

内容协商的方式为客户端请求带上 Accept 类的头(尽管 User-Agent 不是标准的内容协商内容,但是实际开发中很多人还是会使用 User-Agent 作为协商依据),服务端会根据这个头响应客户端期望的内容以及对应的 header(header 也可能没有,而是直接返回对应期望的内容或者重定向到目标页面)。

常见的 Accept 对与示例:

客户端 Request 服务端 Response(可能没有)
accept: application/json, text/html content-type: application/json
accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7 Content-Language: zh-CN
accept-encoding: br, gzip, deflete content-encoding: gzip
Vary: Accept-Encoding

Vary 会影响下级服务端或用户浏览器的缓存策略。vary 头表示服务端基于哪个请求头做了内容协商(可能没有),再简单一点就是因为什么头的内容不同而响应内容不同。Vary 头可以防止缓存错乱,但是滥用会导致缓存命中率下降,因此通常不推荐 Vary: *。实际使用常见的 Vary: Accept-EncodingUser-AgentOrigin

跨源资源共享 CORS(仅浏览器)

原理和过程部分强烈建议阅读 MDN 文档: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

客户端请求头:

OPTIONS /resource/foo     # fetch 请求通常使用 OPTIONS 命令
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org

服务端期望的响应头:

HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400

下载文件(仅浏览器)

打开链接下载文件需要满足以下二选一:

Content-Disposition: attachment;filename=download.pdf

文件下载带进度提醒

响应头中包含 Content-Length,则客户端可以根据这个文件长度提醒下载进度

多线程下载/断点续传

服务端必须支持 Accept-Ranges 响应,大部分情况下这个值是 bytes

Accept-Ranges: bytes

客户端请求带上 Range:(unit=first byte pos)-[last byte pos],如:

Range: bytes=0-499

服务端应该返回:

206 Partial Content
Content-Range: bytes 0-1023/146515

大数据量传输/流式传输(chunked)

对于大容量数据、动态数据、流式数据这种不可提前预知容量(content-length)的内容,应当采用 chunked 方式。它可以方便处理动态内容,以及动态维持客户端链接。客户端通常会对 chunked 进行流式处理。一个典型的 chunked 响应如下:

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

7\r\n
Mozilla\r\n
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n
\r\n

具体的 chunked 部分格式如下:

[chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n]

缓存(通过 header 控制)

HTTP 的缓存可以针对浏览器,也可以针对中间的代理层(如 CDN 等)。Vary 头会影响缓存效果(下不赘述)。

HTTP 协议的缓存结构可以参考下图(来自: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ ):

What a cache provide, advantages/disadvantages of shared/private caches.

更灵活的本地缓存策略可以考虑上 service worker: https://developers.google.cn/web/ilt/pwa/caching-files-with-service-worker

哪些响应可以被缓存:

常见缓存头:

Expires(Since HTTP/1.0)

Last-Modified / If-Modified-Since(Since HTTP/1.0)

Etag / If-Non-Match(Since HTTP/1.1)

Cache-Control(Since HTTP/1.1)

如果以上头在响应中都包含,并且客户端均支持,那么优先级如下: Cache-Control > Expires > Etag > Last-Modified

缓存控制

Cache-Control(Since HTTP/1.1)

Pragma: no-cache(Since HTTP/1.0)

缓存 FAQ

参考资料: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ

强制 HTTPS

强制 HTTPS 通常有两种方案:重定向和 HSTS

HSTS(HTTP Strict Transport Security)是一个特殊的 http header: Strict-Transport-Security: max-age=<expire-time>,如:

Strict-Transport-Security: max-age=31536000;includeSubDomains

用于告诉浏览器必须使用 HTTPS 访问指定资源。二者比对如下:

重定向(浏览器通常有上限次数限定) HSTS
适用性 所有客户端(必须开启 follow redirect) 仅现代化的浏览器
强制性 强制。不跳转到 Location 无法获取资源 非强制。旧的 URL 依旧可用
额外请求 多一次服务端请求 内部重定向,无额外请求
非标准端口 支持 不支持

参考资料: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/HTTP_Strict_Transport_Security

基于 HTTP/1.1 之上的协议

HTTP/1.1 协议可以通过升级方式使用基于 HTTP/1.1 的高级协议,如 WebsocketWebDAV 等等。升级方式为添加以下两个头:

升级成功服务端应该返回 101 Switching Protocols,并且之后的交互则使用高级协议规范进行交互。如果服务端不支持该升级协议,则应该返回 200 OK,之后由客户端继续按照 HTTP 协议降级处理。

简易图片防盗链(仅浏览器)

图片防盗链一种比较简单的防护策略是通过 Referer 头进行防护。假想你的网页代码中引入图片的部分如下:

<img src="pic.jpg" />

那么浏览器请求这个图片资源的时候,通常还会附带上当前的地址栏到 Referer 头,请求示例如下:

GET /pic.jpg HTTP/1.1
Host: localhost:8080
referer: http://localhost:8080/

服务端可以通过设置 Referer 头白名单的方式一定程度上实现防盗链。

觉得有帮助的话,不妨考虑购买付费文章来支持我们 🙂 :

付费文章

友情链接


相关文章