请求的生命周期:pingora-proxy 的阶段和过滤器

介绍

Pingora-proxy HTTP 代理框架支持高度可编程的代理行为。这通过允许用户在请求生命周期的不同阶段插入自定义逻辑来实现。

一个代理 HTTP 请求的生命周期

  1. 一个代理 HTTP 请求的生命周期始于代理从 下游(即客户端)读取请求头。
  2. 然后,代理连接到 上游(即远程服务器)。如果之前已经建立了 可复用连接,此步骤将被跳过。
  3. 代理随后将请求头发送到上游。
  4. 一旦请求头发送完毕,代理进入双工模式,同时代理:
    a. 将上游响应(包括头部和正文)发送到下游,
    b. 将下游请求正文(如果有)发送到上游。
  5. 当整个请求/响应完成时,请求的生命周期结束。所有资源将被释放,下游连接和上游连接将被回收以供重用(如适用)。

Pingora-proxy 的阶段和过滤器

Pingora-proxy 允许用户在请求的生命周期中插入任意逻辑。

  1. graph TD;
  2. start("new request")-->early_request_filter;
  3. early_request_filter-->request_filter;
  4. request_filter-->upstream_peer;
  5. upstream_peer-->Connect{{IO: connect to upstream}};
  6. Connect--connection success-->connected_to_upstream;
  7. Connect--connection failure-->fail_to_connect;
  8. connected_to_upstream-->upstream_request_filter;
  9. upstream_request_filter --> request_body_filter;
  10. request_body_filter --> SendReq{{IO: send request to upstream}};
  11. SendReq-->RecvResp{{IO: read response from upstream}};
  12. RecvResp-->upstream_response_filter-->response_filter-->upstream_response_body_filter-->response_body_filter-->logging-->endreq("request done");
  13. fail_to_connect --can retry-->upstream_peer;
  14. fail_to_connect --can't retry-->fail_to_proxy--send error response-->logging;
  15. RecvResp--failure-->IOFailure;
  16. SendReq--failure-->IOFailure;
  17. error_while_proxy--can retry-->upstream_peer;
  18. error_while_proxy--can't retry-->fail_to_proxy;
  19. request_filter --send response-->logging;
  20. Error>any response filter error]-->error_while_proxy;
  21. IOFailure>IO error]-->error_while_proxy;

通用过滤器使用指南

  • 大多数过滤器返回一个 pingora_error::Result<_>。当返回值为 Result::Err 时,将调用 fail_to_proxy() 并终止请求。
  • 大多数过滤器是异步函数,允许在过滤器中执行其他异步操作(例如 IO)。
  • 每个请求可以定义一个 CTX 对象,用于在同一请求的过滤器之间共享状态。所有过滤器都可以对该对象进行可变访问。
  • 大多数过滤器是可选的。
  • 同时存在 upstream_response_*_filter()response_*_filter() 的原因是为了 HTTP 缓存集成(仍在进行中)。

early_request_filter()

这是每个请求的第一个阶段。

此函数类似于 request_filter(),但执行时间早于任何其他逻辑,包括下游模块逻辑。此函数的主要目的是为模块行为提供更细粒度的控制。

request_filter()

此阶段通常用于验证请求输入、限流以及初始化上下文。

request_body_filter()

此阶段在响应正文准备好发送到上游之后触发。每次接收到一部分请求正文时都会调用它。

proxy_upstream_filter()

此阶段决定是否继续访问上游以获取响应。如果中断,则默认返回 502,但可以实现不同的响应。

此阶段返回一个布尔值,以决定是否继续访问上游或返回错误。

upstream_peer()

此阶段决定连接到哪个上游(例如,通过 DNS 查找和哈希/轮询)以及如何连接。

此阶段返回一个定义要连接上游的 Peer。实现此阶段是 必须的

connected_to_upstream()

此阶段在成功连接到上游时执行。

此阶段通常用于记录目的。此阶段报告的连接信息包括 RTT 和上游 TLS 密码套件等。

fail_to_connect()

connected_to_upstream() 的对应阶段。当连接上游时遇到错误时,将调用此阶段。

在此阶段,用户可以在 Sentry/Prometheus/错误日志中报告错误。用户还可以决定错误是否可重试。

如果错误可重试,将再次调用 upstream_peer(),在这种情况下,用户可以决定是否重试相同的上游或切换到次要上游。

如果错误不可重试,请求将结束。

upstream_request_filter()

此阶段用于在发送到上游之前修改请求。

upstream_response_filter()/upstream_response_body_filter()/upstream_response_trailer_filter()

此阶段在接收到上游响应头/正文/尾部之后触发。

此阶段用于在发送到下游之前修改或处理响应头、正文或尾部。请注意,此阶段在 HTTP 缓存之前调用,因此在此处所做的任何更改都将影响存储在 HTTP 缓存中的响应。

response_filter()/response_body_filter()/response_trailer_filter()

此阶段在响应头/正文/尾部准备好发送到下游之后触发。

此阶段用于在发送到下游之前修改它们。

error_while_proxy()

此阶段在上游代理错误期间触发,即在连接建立之后。

此阶段可能决定是否重试请求(如果连接已复用且 HTTP 方法是幂等的)。

fail_to_proxy()

此阶段在上述任何阶段期间遇到错误时调用。

此阶段通常用于错误日志记录以及向下游报告错误。

logging()

这是在请求完成(或出错)之后且在释放其任何资源之前运行的最后一个阶段。每个请求最终都将进入此阶段。

此阶段通常用于日志记录和请求后清理。

request_summary()

这不是一个阶段,而是一个常用的回调函数。

每个到达 fail_to_proxy() 的错误都会自动记录在错误日志中。当记录错误时,将调用 request_summary() 来转储有关请求的信息。

此回调返回一个字符串,允许用户自定义在错误日志中转储哪些信息以帮助跟踪和调试故障。

suppress_error_log()

这也不是一个阶段,而是另一个回调函数。

fail_to_proxy() 错误会自动记录在错误日志中,但用户可能并不关心每个错误。例如,如果客户端提前断开连接,下游错误会记录下来,但如果用户主要关注上游问题,这些错误可能会显得嘈杂。此回调可以检查错误并返回 true 或 false。如果返回 true,则不会将错误写入日志。

缓存过滤器

有待补充