2

HTTP Header 中的黑科技

 3 years ago
source link: https://blog.dteam.top/posts/2021-02/http-header-technology.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

简单认识 HTTP 协议

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

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

nc -kl 8080
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 总结

  • 1xx: 信息响应
    • 100 Continue,这个临时响应表明,迄今为止的所有内容都是可行的,客户端应该继续请求,如果已经完成,则忽略它。
    • 101 Switching Protocol,该代码是响应客户端的 Upgrade 标头发送的,并且指示服务器也正在切换的协议。
  • 2xx:成功响应
    • 200 OK
    • 201 Created
    • 204 No Content
    • 206 Partial Content,部分成功,断点续传必备
  • 3xx:重定向
    • 301 Moved Permanently,注意所有的请求都将被定向为 GET
    • 302 Found,与 301 最直观的区别是 302 请求不会被搜索引擎收录
    • 304 Not Modified,见下文缓存部分
    • 307 Temporary Redirect,与 302 唯一的区别在于 method 保持原样
    • 308 Permanent Redirect,与 301 唯一的区别在于 method 保持原样
  • 4xx: 客户端错误
    • 400 Bad Request
    • 401 Unauthorized
    • 403 Forbidden
    • 404 Not Found
    • 405 Method Not Allowed
  • 5xx: 服务端失败
    • 500 Internal Server Error
    • 501 Not Implemented
    • 502 Bad Gateway,常见于反向代理
    • 503 Service Unavailable,常见于云加速服务
    • 504 Gateway Timeout,常见于反向代理

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

HTTP 协议的版本的主要演进

HTTP/1.0 增加以下内容:

  • 增加协议版本号,如 GET HTTP/1.0, POST HTTP/1.1
  • 引入 header 内容,无论请求成功与否都可以添加 header,增加灵活性
  • 增加 Content Type 支持,以便 http 协议传输更多更丰富的内容

HTTP/1.1 增加以下内容:

  • 连接可以复用,增加 keepalive 支持
  • 支持 pipeline,允许在第一个应答被完全发送之前就发送第二个请求,以降低通信延迟。
  • 支持分块编码技术(chunked)
  • 引入额外缓存机制(基于相对时间缓存的 cache-control 头)
  • 引入内容协商头(Accept-*头)
  • 引入 Host 头,以便支持虚拟主机

HTTP/2 主要改进:

  • 不再基于纯文本,而是使用二进制(但第一次协商为了向下兼容 HTTP/1.1 依旧使用纯文本)
  • 是一个复用协议。并行请求和响应在同一个链接完成
  • 压缩 headers,节省传输成本

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

  • 基于 UDP 协议,废弃 TCP 协议

参考资料:

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/htmlcontent-type: application/jsonaccept-language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7Content-Language: zh-CNaccept-encoding: br, gzip, defletecontent-encoding: gzipVary: 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-type 非浏览器能直接支持的,或者为默认的 application/octet-stream
  • 响应头中包含 Content-Disposition: attachment,无论 content-type 是什么,都将变为文件下载,如果 Content-Disposition 的值包含 filename=for.bar,则默认下载文件名为指定 filename 的值。例:
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

哪些响应可以被缓存:

  • 通常只有 GET 可以被浏览器缓存,而 OPTIONSHEAD 可以被 CDN 缓存(可能需要配置)
  • 响应码为 200, 206, 301, 404

常见缓存头:

Expires(Since HTTP/1.0)

  • Expires: <http-date>: Expires: Thu, 01 Dec 1994 16:00:00 GMT
  • 本地时间到达指定时间后缓存会过期,会重新发起 HTTP 请求
  • 本地时间如果不同步会影响缓存效果

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

  • Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
  • If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
  • 使用服务端时间,因此本地时间不同步时不影响缓存效果
  • 资源未过期服务端应该返回 304 Not Modified

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

  • ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
  • If-Non-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"
  • 和本地时间无关,本地时间不影响缓存效果
  • 资源未过期服务端应该返回 304 Not Modified
  • 使用 CDN 及分布式存储的场景通常不建议使用 Etag 头

Cache-Control(Since HTTP/1.1)

  • cache-control: max-age=3600,public
  • max-age 字段表示自获取资源之后,在本地缓存多久(单位:秒)
  • public/private 代表这个是公共还是私有缓存,公共缓存是可以被所有中间层缓存的,私有缓存只能在本地浏览器中缓存。包含 max-age 参数或包含 Expires 头的时候,默认为 public,否则默认 private
  • cache-control 头可以同时包含在请求头与响应头中

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

Cache-Control(Since HTTP/1.1)

  • no-store:不使用任何缓存,每次重新发起请求,下载最新资源
  • no-cache:每次重新验证,如果服务端未更新返回 304 Not Modified

Pragma: no-cache(Since HTTP/1.0)

  • cache-control: no-cache 效果相同
  • 不能代替 cache-control,只是向后兼容 HTTP/1.0 的缓存层和客户端

缓存 FAQ

  • 浏览器直接地址栏输入和 F5 以及 CTRL+F5 有什么区别?

    • 地址栏直接输入相比普通请求无任何区别,如果缓存还未过期,不会发起请求
    • 使用 F5/CTRL+R 刷新页面会在请求头加上 cache-control: max-age=0,强行发起缓存验证,会保留 If-Modified-Since/If-Non-Match 请求头,如果服务端的资源未更新,返回 304 Not Modified。而页面其他相关联的请求(如 js, css, 图片资源等)不做任何特殊处理。
    • 使用 CTRL+F5/CTRL+SHIFT+R 刷新页面会在请求头带上 cache-control: no-cache 和 pragma: no-cache,同时会去掉 If-Modified-Since/If-Non-Match 请求头,完全不使用本地缓存,强行从服务端重新获取资源,成功应该返回 200 OK。页面其他相关请求做同样缓存过期处理。这个效果几乎就等同于开发者工具中 Network-Disable Cache 勾选上的效果
  • 如何判断浏览器/CDN 的缓存资源是否过期?

    • 如果资源由缓存服务器或 CDN 响应,通常还同时会返回一个 Age: <seconds>头表示资源在服务器上缓存了多久(单位:秒),可以根据这个头判断最近资源是否有更新
    • 通过 Last-Modified 头判断
  • 如何判断响应后端是真实的服务端还是 CDN?

    • 通过 Server 头判断。绝大多数自建服务器的 Server 可能为 Apache, Nginx, Tomcat 之类的,而 CDN 通常不直接使用这些 Server
    • 绝大多数 CDN 通常会带上一个 Via 头,可以通过这个头的内容判断是否来自于 CDN,以及使用哪个 CDN
  • 如何判断是否命中 CDN 的缓存?

    • CDN 厂商通常都会添加一些自定义的头: x-****,这些信息用来帮助用户调试 CDN。多数 CDN 厂商会把是否命中缓存放在 x-cache 这个头。如阿里云 CDN:x-cache: HIT TCP_MEM_HIT dirn:11:115947616,Cloudfront: x-cache: Hit from cloudfront

参考资料: 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 等等。升级方式为添加以下两个头:

  • Connection: Upgrade
  • Upgrade: protocols

升级成功服务端应该返回 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 头白名单的方式一定程度上实现防盗链。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK