通过 CTX 在阶段之间共享状态
使用 CTX
用户在请求的不同阶段实现的自定义过滤器并不直接相互交互。为了在过滤器之间共享信息和状态,用户可以定义一个 CTX 结构体。每个请求拥有一个单独的 CTX 对象。所有过滤器都能够读取和更新 CTX 对象的成员。CTX 对象将在请求结束时被丢弃。
示例
在以下示例中,代理在 request_filter 阶段解析请求头部,它存储了一个布尔标志,以便在稍后的 upstream_peer 阶段使用该标志来决定将流量路由到哪个服务器。(技术上,头部可以在 upstream_peer 阶段解析,但我们只是为了演示而在更早的阶段进行解析。)
pub struct MyProxy();pub struct MyCtx {beta_user: bool,}fn check_beta_user(req: &pingora_http::RequestHeader) -> bool {// 一些简单的逻辑来检查用户是否是 beta 用户req.headers.get("beta-flag").is_some()}#[async_trait]impl ProxyHttp for MyProxy {type CTX = MyCtx;fn new_ctx(&self) -> Self::CTX {MyCtx { beta_user: false }}async fn request_filter(&self, session: &mut Session, ctx: &mut Self::CTX) -> Result<bool> {ctx.beta_user = check_beta_user(session.req_header());Ok(false)}async fn upstream_peer(&self,_session: &mut Session,ctx: &mut Self::CTX,) -> Result<Box<HttpPeer>> {let addr = if ctx.beta_user {info!("I'm a beta user");("1.0.0.1", 443)} else {("1.1.1.1", 443)};let peer = Box::new(HttpPeer::new(addr, true, "one.one.one.one".to_string()));Ok(peer)}}
在请求之间共享状态
共享诸如计数器、缓存和其他信息等状态在请求之间是常见的。在 Pingora 中共享请求之间的资源和数据并没有什么特别需要的。可以使用 Arc、static 或任何其他机制。
示例
让我们修改上面的示例,以跟踪 beta 访客的数量以及总访客的数量。计数器可以定义在 MyProxy 结构体本身中,也可以定义为全局变量。由于计数器可以被并发访问,这里使用了 Mutex。
// 全局计数器static REQ_COUNTER: Mutex<usize> = Mutex::new(0);pub struct MyProxy {// 服务的计数器beta_counter: Mutex<usize>, // AtomicUsize 也可以}pub struct MyCtx {beta_user: bool,}fn check_beta_user(req: &pingora_http::RequestHeader) -> bool {// 一些简单的逻辑来检查用户是否是 betareq.headers.get("beta-flag").is_some()}#[async_trait]impl ProxyHttp for MyProxy {type CTX = MyCtx;fn new_ctx(&self) -> Self::CTX {MyCtx { beta_user: false }}async fn request_filter(&self, session: &mut Session, ctx: &mut Self::CTX) -> Result<bool> {ctx.beta_user = check_beta_user(session.req_header());Ok(false)}async fn upstream_peer(&self,_session: &mut Session,ctx: &mut Self::CTX,) -> Result<Box<HttpPeer>> {let mut req_counter = REQ_COUNTER.lock().unwrap();*req_counter += 1;let addr = if ctx.beta_user {let mut beta_count = self.beta_counter.lock().unwrap();*beta_count += 1;info!("I'm a beta user #{beta_count}");("1.0.0.1", 443)} else {info!("I'm a user #{req_counter}");("1.1.1.1", 443)};let peer = Box::new(HttpPeer::new(addr, true, "one.one.one.one".to_string()));Ok(peer)}}
完整的示例可以在 pingora-proxy/examples/ctx.rs 下找到。您可以使用 cargo 运行它:
RUST_LOG=INFO cargo run --example ctx
