use std::sync::Arc;
use http::{Request, Response, StatusCode, header, response::Builder};
use http_body::Body;
use http_body_util::{BodyExt, Full, combinators::BoxBody};
use hyper::body::Bytes;
use sub_host::sub_host;
use crate::{Error, IntoError, Result, Route, req_host, route::Protocol::H1};
pub static mut N: usize = 0;
pub async fn proxy<B>(req: Request<B>, route: Arc<Route>) -> Response<BoxBody<Bytes, hyper::Error>>
where
B: Body<Data = Bytes> + Send + 'static,
B::Error: IntoError + Send + Sync + 'static,
{
let host = req_host(&req).to_owned();
let path = req
.uri()
.path_and_query()
.map(|x| x.as_str())
.unwrap_or("")
.to_owned();
match _proxy(&host, &path, req, route).await {
Ok(res) => {
let status = res.status();
log::info!("{status} {host} {path}");
res
}
Err(err) => {
let err = err.to_string();
log::warn!("Error: {host} {path} {err}");
response(|b| b.status(500), err).unwrap_or_default()
}
}
}
fn response(
build: impl Fn(Builder) -> Builder,
body: impl Into<Bytes>,
) -> Result<Response<BoxBody<Bytes, hyper::Error>>> {
Ok(
build(Builder::new()).body(
Full::new(body.into())
.map_err(|never| match never {})
.boxed(),
)?,
)
}
pub async fn _proxy<B>(
host: &str,
path_and_query: &str,
req: Request<B>,
route: Arc<Route>,
) -> Result<Response<BoxBody<Bytes, hyper::Error>>>
where
B: Body<Data = Bytes> + Send + 'static,
B::Error: IntoError + Send + Sync + 'static,
{
if let Some(site_conf) = route.host_conf.get(host) {
let site_conf = site_conf.value();
let upstream = &site_conf.upstream;
let protocol = &upstream.protocol;
let upstream_addr_li = &upstream.addr_li;
let len = upstream_addr_li.len();
if len == 0 {
return Err(Error::UpstreamNotFound);
}
let (mut parts, body) = req.into_parts();
match protocol {
H1 => {
parts.version = http::Version::HTTP_11;
parts.headers.insert(
header::CONNECTION,
header::HeaderValue::from_static("keep-alive"),
);
}
}
let body = body.collect().await.map_err(|e| e.into_error())?.to_bytes();
let mut pos = unsafe {
N = N.overflowing_add(1).0;
N
} % len;
let mut retry = 0;
loop {
let upstream_addr = upstream_addr_li[pos];
let req = Request::from_parts(parts.clone(), Full::new(body.clone()));
let r = match protocol {
H1 => pooled_fetch::http(upstream_addr, req).await,
};
match r {
Ok(res) => {
return Ok(res.map(|b| b.boxed()));
}
Err(err) => {
log::warn!("Error: {host} {path_and_query} {upstream_addr} {}", err);
retry += 1;
if retry > upstream.max_retry {
return Err(err.into());
}
pos = (pos + 1) % len;
}
}
}
} else {
if let Some(host) = sub_host(host)
&& route.host_conf.get(&faststr::FastStr::new(&host)).is_some()
{
return response(
|b| {
let new_uri = format!("https://{}{}", host, path_and_query);
b.status(StatusCode::MOVED_PERMANENTLY)
.header(header::LOCATION, new_uri)
},
&b""[..],
);
}
response(|b| b.status(404), &b"404: Not Found"[..])
}
}