示例:控制请求

在这一部分,我们将介绍如何路由、修改或拒绝请求。

路由

请求中的任何信息都可以用于进行路由决策。Pingora 并不对用户如何实现自己的路由逻辑施加任何限制。

在以下示例中,代理仅当请求路径以 /family/ 开头时,将流量发送到 1.0.0.1。所有其他请求将被路由到 1.1.1.1

  1. pub struct MyGateway;
  2. #[async_trait]
  3. impl ProxyHttp for MyGateway {
  4. type CTX = ();
  5. fn new_ctx(&self) -> Self::CTX {}
  6. async fn upstream_peer(
  7. &self,
  8. session: &mut Session,
  9. _ctx: &mut Self::CTX,
  10. ) -> Result<Box<HttpPeer>> {
  11. let addr = if session.req_header().uri.path().starts_with("/family/") {
  12. ("1.0.0.1", 443)
  13. } else {
  14. ("1.1.1.1", 443)
  15. };
  16. info!("connecting to {addr:?}");
  17. let peer = Box::new(HttpPeer::new(addr, true, "one.one.one.one".to_string()));
  18. Ok(peer)
  19. }
  20. }

修改头部

可以在请求和响应的各个阶段添加、删除或修改请求头和响应头。在以下示例中,我们在 response_filter 阶段添加了逻辑,用于更新 Server 头部并移除 alt-svc 头部。

  1. #[async_trait]
  2. impl ProxyHttp for MyGateway {
  3. ...
  4. async fn response_filter(
  5. &self,
  6. _session: &mut Session,
  7. upstream_response: &mut ResponseHeader,
  8. _ctx: &mut Self::CTX,
  9. ) -> Result<()>
  10. where
  11. Self::CTX: Send + Sync,
  12. {
  13. // 替换现有的 Server 头部(如果有)
  14. upstream_response
  15. .insert_header("Server", "MyGateway")
  16. .unwrap();
  17. // 因为我们不支持 h3 协议
  18. upstream_response.remove_header("alt-svc");
  19. Ok(())
  20. }
  21. }

返回错误页面

有时,在某些情况下(例如认证失败时),您可能希望代理不再转发流量,而是直接返回一个错误页面。

  1. fn check_login(req: &pingora_http::RequestHeader) -> bool {
  2. // 在此实现您自己的登录检查逻辑
  3. req.headers.get("Authorization").map(|v| v.as_bytes()) == Some(b"password")
  4. }
  5. #[async_trait]
  6. impl ProxyHttp for MyGateway {
  7. ...
  8. async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> {
  9. if session.req_header().uri.path().starts_with("/login")
  10. && !check_login(session.req_header())
  11. {
  12. let _ = session.respond_error(403).await;
  13. // true: 告诉代理响应已经写入
  14. return Ok(true);
  15. }
  16. Ok(false)
  17. }
  18. }

日志记录

日志记录逻辑可以添加到 Pingora 的 logging 阶段。该阶段在每次请求处理结束前运行,无论请求是成功还是失败。

在下面的示例中,我们将 Prometheus 指标和访问日志添加到代理中。为了让指标能够被抓取,我们还在另一个端口上启动了一个 Prometheus 指标服务。

  1. pub struct MyGateway {
  2. req_metric: prometheus::IntCounter,
  3. }
  4. #[async_trait]
  5. impl ProxyHttp for MyGateway {
  6. ...
  7. async fn logging(
  8. &self,
  9. session: &mut Session,
  10. _e: Option<&pingora::Error>,
  11. ctx: &mut Self::CTX,
  12. ) {
  13. let response_code = session
  14. .response_written()
  15. .map_or(0, |resp| resp.status.as_u16());
  16. // 访问日志
  17. info!(
  18. "{} response code: {response_code}",
  19. self.request_summary(session, ctx)
  20. );
  21. self.req_metric.inc();
  22. }
  23. }
  24. fn main() {
  25. ...
  26. let mut prometheus_service_http =
  27. pingora::services::listening::Service::prometheus_http_service();
  28. prometheus_service_http.add_tcp("127.0.0.1:6192");
  29. my_server.add_service(prometheus_service_http);
  30. my_server.run_forever();
  31. }

这些示例展示了如何通过 Pingora 代理在不同的阶段进行灵活的请求和响应处理,包括路由选择、头部修改、错误页面返回以及日志记录等功能。