use std::io;
use crate::config::Config;
use crate::docker::start_rcs;
use crate::utils::{decode_auth_cookie, HttpClient};
use http::header::HOST;
use http::{request, response, Uri};
use hyper::header::HeaderValue;
use hyper::upgrade::Upgraded;
use hyper::{Body, Request, Response, StatusCode};
use tokio::io::DuplexStream;
pub async fn proxy(
client: HttpClient,
mut request: Request<Body>,
config: &'static Config,
) -> Result<Response<Body>, hyper::Error> {
println!(" <-- {} {}", request.method(), request.uri().path());
let token = decode_auth_cookie(request.headers(), config).unwrap();
let authority = start_rcs(&token.email, config).await;
println!("proxying to {}", &authority);
*request.uri_mut() = Uri::builder()
.scheme("http")
.authority(authority.clone())
.path_and_query(
request
.uri()
.path_and_query()
.expect("path and query to exist")
.as_str(),
)
.build()
.unwrap();
(*request.headers_mut()).insert(HOST, HeaderValue::from_str(authority.host()).unwrap());
if hyper_tungstenite::is_upgrade_request(&request) {
let (parts, body) = request.into_parts();
let mut request_copy = request::Builder::new()
.method(parts.method.clone())
.version(parts.version)
.uri(parts.uri.clone())
.body(Body::empty())
.unwrap();
*request_copy.headers_mut() = parts.headers.clone();
let request = Request::from_parts(parts, body);
let response = client.request(request_copy).await?;
let (parts, body) = response.into_parts();
let mut response_copy = response::Builder::new()
.status(parts.status)
.version(parts.version)
.body(Body::empty())
.unwrap();
*response_copy.headers_mut() = parts.headers.clone();
let response = Response::from_parts(parts, body);
assert!(
response.status() == StatusCode::SWITCHING_PROTOCOLS,
"downstream didn't upgrade: {}",
response.status()
);
let (left, right) = tokio::io::duplex(64);
tokio::spawn(async move {
match hyper::upgrade::on(response).await {
Ok(upgraded) => {
if let Err(e) = tunnel(upgraded, left).await {
eprintln!("downstream tunnel io error: {}", e);
};
}
Err(e) => eprintln!("upgrade error: {}", e),
}
});
tokio::spawn(async move {
match hyper::upgrade::on(request).await {
Ok(upgraded) => {
if let Err(e) = tunnel(upgraded, right).await {
eprintln!("upstream tunnel io error: {}", e);
};
}
Err(e) => eprintln!("upgrade error: {}", e),
}
});
Ok(response_copy)
} else {
client.request(request).await
}
}
async fn tunnel(mut upgraded: Upgraded, mut duplex: DuplexStream) -> io::Result<()> {
println!("tunnel open");
let (from_client, from_server) =
tokio::io::copy_bidirectional(&mut upgraded, &mut duplex).await?;
println!(
"tunnel closing: wrote {} bytes and received {} bytes",
from_client, from_server
);
Ok(())
}