use crate::prelude::*;
use hyper::body::Incoming;
use hyper_util::rt::TokioIo;
use std::pin::Pin;
use std::str::FromStr;
use tokio::net::TcpStream;
use tokio::net::lookup_host;
use tokio_native_tls::{TlsConnector, native_tls};
type BoxedHttpConnection =
Pin<Box<dyn std::future::Future<Output = Result<(), hyper::Error>> + Send>>;
pub struct ProxyForwardRequestPayload<'a> {
pub uri: Uri,
pub method: &'a Method,
pub headers: &'a HeaderMap,
pub body: Option<Bytes>,
}
impl Proxy {
pub(crate) async fn forward_request(
payload: ProxyForwardRequestPayload<'_>,
origin: String,
) -> Result<(Request<Full<Bytes>>, Response<Incoming>), CinemaError> {
let origin_uri = Uri::from_str(&origin).unwrap();
let host = origin_uri.host().unwrap();
let is_https = origin_uri.scheme_str() == Some("https");
let port = origin_uri
.port_u16()
.unwrap_or(if is_https { 443 } else { 80 });
let lookup_for = format!("{}:{}", host, port);
let addr = lookup_host(&lookup_for)
.await
.map_err(|err| CinemaError::LookupHost(err, lookup_for.to_string()))?
.next()
.unwrap();
let stream = TcpStream::connect(&addr)
.await
.map_err(|err| CinemaError::ConnectTcpStream(err, addr.to_string()))?;
let mut sender;
let conn_future: BoxedHttpConnection = if is_https {
let native_tls = native_tls::TlsConnector::new()?;
let tls_connector = TlsConnector::from(native_tls);
let tls_stream = tls_connector.connect(host, stream).await?;
let io = TokioIo::new(tls_stream);
let (s, conn) = hyper::client::conn::http1::handshake(io)
.await
.map_err(|err| CinemaError::Handshake(err, origin.clone()))?;
sender = s;
Box::pin(conn)
} else {
let io = TokioIo::new(stream);
let (s, conn) = hyper::client::conn::http1::handshake(io)
.await
.map_err(|err| CinemaError::Handshake(err, origin.clone()))?;
sender = s;
Box::pin(conn)
};
tokio::task::spawn(async move {
if let Err(err) = conn_future.await {
println!("Connection failed: {:?}", err);
}
});
let mut req_builder = Request::builder().method(payload.method).uri(payload.uri);
req_builder = req_builder
.header(hyper::header::USER_AGENT, "cinema/0.1")
.header(hyper::header::HOST, host);
for (name, value) in payload.headers.iter() {
match *name {
hyper::header::HOST | hyper::header::USER_AGENT => {}
_ => {
req_builder = req_builder.header(name, value);
}
}
}
let forward_req = req_builder
.body(Full::new(match payload.body {
Some(body) => body,
None => Bytes::new(),
}))
.map_err(|err| CinemaError::ForwardBody(err))?;
let res = sender
.send_request(forward_req.clone())
.await
.map_err(|err| CinemaError::ForwardRequest(err))?;
Ok((forward_req, res))
}
}