示例:控制请求
在这一部分,我们将介绍如何路由、修改或拒绝请求。
路由
请求中的任何信息都可以用于进行路由决策。Pingora 并不对用户如何实现自己的路由逻辑施加任何限制。
在以下示例中,代理仅当请求路径以 /family/
开头时,将流量发送到 1.0.0.1
。所有其他请求将被路由到 1.1.1.1
。
pub struct MyGateway;
#[async_trait]
impl ProxyHttp for MyGateway {
type CTX = ();
fn new_ctx(&self) -> Self::CTX {}
async fn upstream_peer(
&self,
session: &mut Session,
_ctx: &mut Self::CTX,
) -> Result<Box<HttpPeer>> {
let addr = if session.req_header().uri.path().starts_with("/family/") {
("1.0.0.1", 443)
} else {
("1.1.1.1", 443)
};
info!("connecting to {addr:?}");
let peer = Box::new(HttpPeer::new(addr, true, "one.one.one.one".to_string()));
Ok(peer)
}
}
修改头部
可以在请求和响应的各个阶段添加、删除或修改请求头和响应头。在以下示例中,我们在 response_filter
阶段添加了逻辑,用于更新 Server
头部并移除 alt-svc
头部。
#[async_trait]
impl ProxyHttp for MyGateway {
...
async fn response_filter(
&self,
_session: &mut Session,
upstream_response: &mut ResponseHeader,
_ctx: &mut Self::CTX,
) -> Result<()>
where
Self::CTX: Send + Sync,
{
// 替换现有的 Server 头部(如果有)
upstream_response
.insert_header("Server", "MyGateway")
.unwrap();
// 因为我们不支持 h3 协议
upstream_response.remove_header("alt-svc");
Ok(())
}
}
返回错误页面
有时,在某些情况下(例如认证失败时),您可能希望代理不再转发流量,而是直接返回一个错误页面。
fn check_login(req: &pingora_http::RequestHeader) -> bool {
// 在此实现您自己的登录检查逻辑
req.headers.get("Authorization").map(|v| v.as_bytes()) == Some(b"password")
}
#[async_trait]
impl ProxyHttp for MyGateway {
...
async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> {
if session.req_header().uri.path().starts_with("/login")
&& !check_login(session.req_header())
{
let _ = session.respond_error(403).await;
// true: 告诉代理响应已经写入
return Ok(true);
}
Ok(false)
}
}
日志记录
日志记录逻辑可以添加到 Pingora 的 logging
阶段。该阶段在每次请求处理结束前运行,无论请求是成功还是失败。
在下面的示例中,我们将 Prometheus 指标和访问日志添加到代理中。为了让指标能够被抓取,我们还在另一个端口上启动了一个 Prometheus 指标服务。
pub struct MyGateway {
req_metric: prometheus::IntCounter,
}
#[async_trait]
impl ProxyHttp for MyGateway {
...
async fn logging(
&self,
session: &mut Session,
_e: Option<&pingora::Error>,
ctx: &mut Self::CTX,
) {
let response_code = session
.response_written()
.map_or(0, |resp| resp.status.as_u16());
// 访问日志
info!(
"{} response code: {response_code}",
self.request_summary(session, ctx)
);
self.req_metric.inc();
}
}
fn main() {
...
let mut prometheus_service_http =
pingora::services::listening::Service::prometheus_http_service();
prometheus_service_http.add_tcp("127.0.0.1:6192");
my_server.add_service(prometheus_service_http);
my_server.run_forever();
}
这些示例展示了如何通过 Pingora 代理在不同的阶段进行灵活的请求和响应处理,包括路由选择、头部修改、错误页面返回以及日志记录等功能。