Implement proxy_all for proxying entire domains

I wanted to add another domain behind a reverse proxy. The existing
reverse proxy functionality seemed to only work for subpaths so I
implemented this. (I'm happy with another better way if you can point one
to me.)

I also implemented streaming responses when reverse proxying. That way
the responses stay as similar as possible to the upstream server.
This commit is contained in:
Hannu Hartikainen
2020-07-09 00:58:17 +03:00
committed by int 80h
parent cb4ee95620
commit 7f3248e92d
4 changed files with 64 additions and 0 deletions

View File

@@ -29,6 +29,8 @@ pub struct Server {
pub usrdir: Option<bool>,
#[cfg(feature = "proxy")]
pub proxy: Option<HashMap<String, String>>,
#[cfg(feature = "proxy")]
pub proxy_all: Option<String>,
pub redirect: Option<HashMap<String, String>>,
#[cfg(feature = "scgi")]
pub scgi: Option<HashMap<String, String>>,

View File

@@ -1,5 +1,7 @@
use std::io;
use std::marker::Unpin;
use std::net::SocketAddr;
use tokio::net::TcpStream;
use tokio::prelude::*;
use tokio_openssl::SslStream;
@@ -40,4 +42,9 @@ impl Connection {
self.stream.flush().await?;
Ok(())
}
pub async fn send_stream<S: AsyncRead + Unpin>(&mut self, reader: &mut S) -> Result<(), io::Error> {
tokio::io::copy(reader, &mut self.stream).await?;
Ok(())
}
}

View File

@@ -238,6 +238,25 @@ async fn handle_connection(
None => {}
}
#[cfg(feature = "proxy")]
if let Some(pr) = &srv.server.proxy_all {
let host_port: Vec<&str> = pr.splitn(2, ':').collect();
let host = host_port[0];
let port: Option<u16>;
if host_port.len() == 2 {
port = host_port[1].parse().ok();
} else {
port = None;
}
let mut upstream_url = url.clone();
upstream_url.set_host(Some(host)).unwrap();
upstream_url.set_port(port).unwrap();
revproxy::proxy_all(pr, upstream_url, con).await?;
return Ok(());
}
#[cfg(feature = "proxy")]
match &srv.server.proxy {
Some(pr) => match url.path_segments().map(|c| c.collect::<Vec<_>>()) {

View File

@@ -55,3 +55,39 @@ pub async fn proxy(addr: String, u: url::Url, mut con: conn::Connection) -> Resu
con.send_raw(&buf).await?;
Ok(())
}
pub async fn proxy_all(addr: &str, u: url::Url, mut con: conn::Connection) -> Result<(), io::Error> {
let mut connector = SslConnector::builder(SslMethod::tls()).unwrap();
connector.set_verify(openssl::ssl::SslVerifyMode::NONE);
let config = connector.build().configure().unwrap();
// TCP handshake
let stream = match TcpStream::connect(&addr).await {
Ok(s) => s,
Err(_) => {
logger::logger(con.peer_addr, Status::ProxyError, u.as_str());
con.send_status(Status::ProxyError, None).await?;
return Ok(());
}
};
let domain = addr.splitn(2, ':').next().unwrap();
// TLS handshake with SNI
let mut stream = match tokio_openssl::connect(config, domain, stream).await {
Ok(s) => s,
Err(_) => {
logger::logger(con.peer_addr, Status::ProxyError, u.as_str());
con.send_status(Status::ProxyError, None).await?;
return Ok(());
}
};
// send request: URL + CRLF
stream.write_all(u.as_ref().as_bytes()).await?;
stream.write_all(b"\r\n").await?;
stream.flush().await?;
// stream to client
con.send_stream(&mut stream).await?;
Ok(())
}