ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Pingora 怎么使用 pingora-error 创建带上下文的错误并在过滤阶段映射为 HTTP 状态码

Pingora 怎么使用 pingora-error 创建带上下文的错误并在过滤阶段映射为 HTTP 状态码 Pingora 怎么使用 pingora-error 创建带上下文的错误并在过滤阶段映射为 HTTP 状态码【免费下载链接】pingoraA library for building fast, reliable and evolvable network services.项目地址: https://gitcode.com/GitHub_Trending/pi/pingora在实现 pingora-proxy 的过滤阶段如request_filter时你经常需要在请求校验失败例如缺少必要的请求头时返回一个错误这个错误要携带自己的类型和一段可写进错误日志的上下文并且最终要决定向下游返回哪个 HTTP 状态码。pingora-errorcrate 提供了 Pingora 全家桶统一的Result类型与错误构建函数负责“创建和包装错误”而错误向 HTTP 状态码的映射则由 pingora-proxy 的fail_to_proxy()阶段完成。本文沿着官方错误处理指南、pingora-error 源码和 fail_to_proxy 默认实现走一遍这条完整路径。错误模型类型、来源、原因链和上下文一切以pingora-error中的Error结构体为中心。它由五个部分组成见 Error 定义字段说明etype: ErrorType错误类型如ConnectRefused、InvalidHTTPHeader、HTTPStatus(u16)esource: ErrorSource错误来源Upstream远端服务器、Downstream远端客户端、Internal内部逻辑或Unsetretry: RetryType该错误是否可重试Decided(bool)或ReusedOnlycause: OptionBoxdyn ErrorTrait Send Sync被包装的底层原因错误context: OptionImmutStr用户提供的任意字符串上下文crate 还导出了两个贯穿 Pingora 其他 crate 的类型别名/// The boxed [Error], the desired way to pass [Error] pub type BError BoxError; /// Syntax sugar for std::ResultT, BError pub type ResultT, E BError StdResultT, E;来源pingora-error/src/lib.rsErrorType是预定义的错误类型枚举涵盖连接、TLS、HTTP 协议、IO、文件等场景其中与本文最直接相关的是HTTPStatus(u16)源码注释写明它是“application error, will return HTTP status code”见 ErrorType 定义。如果预定义类型不够用可以用ErrorType::Custom(static str)或CustomCode(static str, u16)扩展自己的静态错误类型运行时动态生成的字符串更适合放在context里而不是类型里。创建带上下文的错误官方指南给出的选择原则见 errors.md Guidelines没有直接原因、但要附带上下文用Error::explain要包装一个已有的原因错误并补充上下文用Error::because指定错误来源的最小错误用new_in/new_up/new_down分别对应Internal/Upstream/Downstream来源。对应的签名见 Error 构建函数// 包装一个原因错误附加 context pub fn becauseS: IntoImmutStr, E: IntoBoxdyn ErrorTrait Send Sync( e: ErrorType, context: S, cause: E, ) - BError // 只有 context没有原因错误 pub fn explainS: IntoImmutStr(e: ErrorType, context: S) - BError官方示例场景是“预期请求头不存在时返回错误”见 errors.md Examplesfn validate_req_header(req: RequestHeader) - Result() { // validate that the host header exists req.headers() .get(http::header::HOST) .ok_or_else(|| Error::explain(InvalidHTTPHeader, No host header detected)) }这里validate_req_header在host头缺失时用Error::explain新建一个错误类型是InvalidHTTPHeader上下文是No host header detected这段上下文后续会出现在错误日志中。在过滤阶段把错误转换并传播校验函数返回的是普通错误而代理需要决定最终对下游返回什么状态码。pingora-error为此提供了一组作用在Result/Option上的 trait避免手写map_errtrait 方法等价操作行为Result::or_err(et, context)map_errError::because用新的ErrorType和上下文包装原错误原错误成为 causeResult::or_err_with(et, \|\| ...)同上context 由闭包构造适合运行时拼接字符串Result::explain_err(et, \|e\| ...)map_errError::explain用新错误替换原错误原错误不能移出作用域时Result::or_fail()map_errbecause(InternalError, , e)只是让非Error类型能通过?冒泡官方建议优先用or_errOption::or_err(et, context)ok_or(Error::explain(...))None时生成带上下文的错误Result::err_context(\|\| ...)map_errmore_context保留原错误类型和来源只追加一层上下文来源OrErr / OkOrErr / Context trait把这些串起来就是官方文档给出的请求过滤阶段完整写法见 errors.md其中request_filter是 ProxyHttp trait 的过滤阶段impl MyServer { pub async fn handle_request_filter( self, http_session: mut Session, ctx: mut CTX, ) - Resultbool { validate_req_header(session.req_header()?).or_err(HTTPStatus(400), Missing required headers)?; Ok(true) } }要点validate_req_header(...)产生的InvalidHTTPHeader错误在这里被or_err(HTTPStatus(400), Missing required headers)包装成新的HTTPStatus(400)错误原错误降为 cause?使过滤阶段直接以Err返回请求终止并进入错误处理流程官方文档同时说明Error的Display实现会打印整条 cause 链所以最初的InvalidHTTPHeader错误依然可见不会因为被包装而丢失见 errors.md。如果后续还想在传播链上加一层上下文而不改变错误类型和来源可以用more_context实现b().map_err(|e| e.more_context(b failed after a))more_context与Error::because的区别在于它保留原错误的类型和来源而because允许指定新的ErrorType且more_context只适用于Error类型because适用于所有实现std::error::Error的错误。fail_to_proxy() 如何把错误映射为 HTTP 状态码过滤阶段返回Err后请求会进入fail_to_proxy()阶段见 phase.md“This phase is called whenever an error is encounter during any of the phases above”。pingora-proxy 的默认实现按以下规则决定响应状态码见 ProxyHttp::fail_to_proxy条件映射结果etype是HTTPStatus(code)直接用该code来源为Upstream502来源为Downstream且类型为WriteError/ReadError/ConnectionClosed0连接已不可用不发送响应来源为Downstream的其他类型400来源为Internal或Unset500当 code 大于 0 时默认实现调用session.respond_error(code)把错误响应发往下游。所以上面or_err(HTTPStatus(400), ...)的错误经过这条默认路径下游收到的就是400 Bad Request——这正是 errors.md 对示例的结论“it will result in sending a400 Bad Requestresponse downstream”。同时phase.md 说明每个到达fail_to_proxy()的错误都会自动写入错误日志并调用request_summary()输出请求信息。也就是说你在过滤阶段附加的 context 字符串会随之出现在错误日志里这就是“带上下文的错误”在排障时的用途日志机制另见 error_log.md。如果默认映射不符合需要可以自己实现fail_to_proxy()重写响应逻辑——这是该钩子存在的目的“Users may write an error response to the downstream if the downstream is still writable”但本文的主路径使用默认实现。验证错误链的输出pingora-error的单元测试给出了Display输出和root_etype()的确定行为可作为核对错误链是否构造正确的参照以下为仓库单元测试中的断言属文档示例而非你程序的固定输出let e3 Error::new(ErrorType::InternalError); let e4 Error::because(ErrorType::HTTPStatus(400), test, e3); assert_eq!(format!({}, e4), HTTPStatus context: test cause: InternalError); assert_eq!(e4.root_etype().as_str(), InternalError); let e1: Result(), BError Err(Error::new(ErrorType::InternalError)); let e2 e1.or_err(ErrorType::HTTPStatus(400), another); assert_eq!(format!({}, e2.unwrap_err()), HTTPStatus context: another cause: InternalError);可以看出外层错误先打印自己的类型、context再以cause:递归打印内层错误链root_etype()可以取到最底层的错误类型。在自己的过滤器里判断“错误是否被正确包装”可以对照这种格式检查日志文本。端到端的验证方式则回到业务行为向服务发送一个缺少host头的请求例如按 examples 的main模式启动服务后用curl触发预期下游收到400 Bad Request响应且错误日志中出现HTTPStatus、context 以及被包装的InvalidHTTPHeadercause 链。限制可重试错误对代理行为的影响错误还有一个影响代理行为的维度——是否可重试。根据 errors.md如果错误被标记为可重试retry-ablepingora-proxy 会被允许对该上游请求进行重试部分错误仅在复用连接RetryType::ReusedOnly时才可重试用于处理“远端已丢弃了我们试图复用的连接”这类情况新创建的Error默认继承其直接原因错误的重试状态若未指定则视为不可重试。这意味着在过滤阶段用Error::explain创建的、不带原因的新错误默认是不可重试的——对“请求头缺失”这类客户端错误来说正是期望行为。如果你在because包装了一个可重试的上游错误新错误会继承该重试状态此时应确认代理重试符合你的预期。参考文件docs/user_guide/errors.md — 错误处理官方指南示例、Guidelines、Retry 说明pingora-error/src/lib.rs —Error/ErrorType/ErrorSource定义与or_err、explain、because等 APIpingora-proxy/src/proxy_trait.rs —fail_to_proxy()默认实现与状态码映射docs/user_guide/phase.md — 各过滤阶段与fail_to_proxy()的触发时机docs/user_guide/error_log.md — 错误日志级别约定【免费下载链接】pingoraA library for building fast, reliable and evolvable network services.项目地址: https://gitcode.com/GitHub_Trending/pi/pingora创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进