4

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 交互响应过程:

终端 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      ## 一些可选的 header
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
    
  • 2xx :成功响应
    200 OK
    201 Created
    204 No Content
    206 Partial Content
    
  • 3xx :重定向
    301 Moved Permanently
    302 Found
    304 Not Modified
    307 Temporary Redirect
    308 Permanent Redirect
    
  • 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
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/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-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):

HTTPCachtType.png

更灵活的本地缓存策略可以考虑上 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

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

Pragma: no-cache (Since HTTP/1.0)

cache-control: no-cache
cache-control

缓存 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 的缓存资源是否过期?

    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 协议降级处理。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK