use std::{borrow::Cow, collections::HashMap, sync::Arc};
use async_trait::async_trait;
use http::Uri;
use pingora::{upstreams::peer::HttpPeer, ErrorType::HTTPStatus};
use pingora_http::ResponseHeader;
use pingora_proxy::{ProxyHttp, Session};
use tracing::info;
use crate::stores::routes::{RouteStore, RouteStoreContainer};
use super::{
middleware::{execute_request_plugins, execute_response_plugins},
DEFAULT_PEER_OPTIONS,
};
pub struct Router {
pub store: RouteStore,
}
pub struct RouterContext {
pub host: String,
pub route_container: Option<Arc<RouteStoreContainer>>,
pub extensions: HashMap<Cow<'static, str>, String>,
}
#[async_trait]
impl ProxyHttp for Router {
type CTX = RouterContext;
fn new_ctx(&self) -> Self::CTX {
RouterContext {
host: String::new(),
route_container: None,
extensions: HashMap::new(),
}
}
async fn request_filter(
&self,
session: &mut Session,
ctx: &mut Self::CTX,
) -> pingora::Result<bool> {
let req_host = get_host(session);
let host_without_port = req_host.split(':').collect::<Vec<_>>()[0];
ctx.host = host_without_port.to_string();
let route_container = self.store.get(host_without_port);
if route_container.is_none() {
session.respond_error(404).await;
return Ok(true);
}
let uri = get_uri(session);
let route_container = route_container.unwrap();
match &route_container.path_matcher.pattern {
Some(pattern) if pattern.find(uri.path()).is_none() => {
session.respond_error(404).await;
return Ok(true);
}
_ => {}
}
ctx.route_container = Some(route_container.value().clone());
if let Ok(true) = execute_request_plugins(session, ctx, &route_container.plugins).await {
return Ok(true);
}
Ok(false)
}
async fn upstream_peer(
&self,
_session: &mut Session,
ctx: &mut Self::CTX,
) -> pingora::Result<Box<HttpPeer>> {
let upstream = ctx.route_container.as_ref();
if upstream.is_none() {
return Err(pingora::Error::new(HTTPStatus(404)));
}
let upstream = upstream.unwrap();
let healthy_upstream = upstream.load_balancer.select(b"", 32);
if healthy_upstream.is_none() {
info!("No healthy upstream found");
return Err(pingora::Error::new(HTTPStatus(503)));
}
let mut peer = HttpPeer::new(healthy_upstream.unwrap(), false, ctx.host.clone());
peer.options = DEFAULT_PEER_OPTIONS;
Ok(Box::new(peer))
}
async fn response_filter(
&self,
session: &mut Session,
upstream_response: &mut ResponseHeader,
ctx: &mut Self::CTX,
) -> pingora::Result<()> {
let container = ctx.route_container.as_ref().unwrap();
for (name, value) in &container.host_header_add {
upstream_response.insert_header(name, value)?;
}
for name in &container.host_header_remove {
upstream_response.remove_header(name);
}
execute_response_plugins(session, ctx, &container.plugins).await?;
Ok(())
}
}
fn get_uri(session: &mut Session) -> Uri {
session.req_header().uri.clone()
}
fn get_host(session: &mut Session) -> &str {
if let Some(host) = session.get_header(http::header::HOST) {
return host.to_str().unwrap_or("");
}
if let Some(host) = session.req_header().uri.host() {
return host;
}
""
}