Posts for: #HTTP

HTTP2 流量控制

核心问题

注:文章由 codex 整理。

HTTP/2 的一个核心收益是 multiplexing:多个 request/response 可以复用同一条 TCP connection,并且不同 stream 的 frame 可以交错发送。这个能力解决了 HTTP/1.x 依赖多连接并发的问题,但也引入了一个新问题:如果某个接收方、某个 stream 或某个应用层消费者处理不过来,发送方应该怎样被限制,而不至于把整个连接的内存打爆?

HTTP/2 flow control 解决的就是这个问题。它不是拥塞控制(congestion control),也不是优先级调度(prioritization),而是一个接收方驱动的背压机制:

  • TCP 拥塞控制关心网络路径能承受多少数据。
  • HTTP/2 flow control 关心接收端应用和协议栈愿意接收多少 DATA payload。
  • HTTP/2 priority 关心多个 stream 竞争发送机会时先发谁。

可以把它理解为:TCP 管路决定“路上能跑多少车”,HTTP/2 flow control 决定“每个收货口和总仓库还能接多少货”。

基本模型

HTTP/2 的流量控制基于 credit/window:

  1. 接收方声明自己还能接收多少字节。
  2. 发送方发送 DATA frame 时消耗窗口。
  3. 接收方消费掉数据、释放缓冲后,通过 WINDOW_UPDATE 把窗口加回来。
  4. 如果窗口耗尽,发送方必须暂停发送受流控约束的数据。

这里有两个层级的窗口:

窗口 作用范围 用来限制什么
stream flow-control window 单个 stream 某个 request/response body 的发送量
connection flow-control window 整条 HTTP/2 connection 所有 stream 的 DATA frame 总发送量

发送一个 DATA frame 之前,发送方必须同时检查两个窗口:frame payload 长度不能超过 stream window,也不能超过 connection window。发送后,这两个窗口都会按 payload 长度扣减。注意,HTTP/2 frame header 的 9 个字节不计入流控窗口。

WebSocket Origin Header 校验失败

最近做 APISIX 线上服务时遇到一个场景:业务使用 websocket 转发时,在浏览器会出现 WebSocket close with status code 1006 的错误。打开调试工具查看发现在 websocket 握手时服务端返回了 403。

76f75e967736245ec923202987a22482_MD5

4efbf4b7a2093ed4f1d6e522139dc92e_MD5

非常奇怪的是,如果业务不经过 APISIX 直接访问后端 code-server 是没有问题的(中间也得经过一层 Ingress 转发)。

简单的流量模型如下:

                                                                                                                      
                                                                                                                      
                               +--------------+                              +--------------+          +-------------+
  https://domain.com:9443/xxx  |              |  http://domain.com:23480/xxx |              |          |             |
--------------------------------   APISIX     -------------------------------+   Ingress    -----------+ code-server |
                               |              |                              |              |          |             |
                               +--------------+                              +--------------+          +-------------+

经过对比发现,两者的请求头里面 Host 和 Origin 是存在差异的。尝试在 APISIX 中强制修改 Host 头部,问题没有解决。然后利用 proxy-rewrite 强制修改 Origin 头部,请求恢复正常。

    "plugins": {
      "proxy-rewrite": {
        "uri": "/anything",
        "headers": {
          "set": {
	        "Origin": "http://domain.com"
          }
        }
      }
    },

那么问题来了,为什么改完 Origin Header 就行了?code-server 是如何处理 Origin Header 的?为什么 Ingress 可以,APISIX 不行?