update error handling
This commit is contained in:
parent
1a70869227
commit
f080f2e12d
7 changed files with 23 additions and 14 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
# rpxy: A simple and ultrafast reverse-proxy for multiple host names with TLS termination, written in pure Rust
|
# rpxy: A simple and ultrafast reverse-proxy for multiple host names with TLS termination, written in pure Rust
|
||||||
|
|
||||||
|
[](LICENSE)
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
@ -8,11 +9,13 @@
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
`rpxy` [ahr-pik-see] is an (currently experimental) implementation of simple and lightweight reverse-proxy, which is based on [`hyper`](https://github.com/hyperium/hyper), [`rustls`](https://github.com/rustls/rustls) and [`tokio`](https://github.com/tokio-rs/tokio), i.e., written in pure Rust. Our `rpxy` allows to route multiple host names to appropriate backend application servers while serving TLS connections.
|
`rpxy` [ahr-pik-see] is an implementation of simple and lightweight reverse-proxy with some additional features. The implementation is based on [`hyper`](https://github.com/hyperium/hyper), [`rustls`](https://github.com/rustls/rustls) and [`tokio`](https://github.com/tokio-rs/tokio), i.e., written in pure Rust. Our `rpxy` allows to route multiple host names to appropriate backend application servers while serving TLS connections.
|
||||||
|
|
||||||
This project is still *work-in-progress*. But it is already working in some production environments and serves numbers of domain names. Furthermore it dramatically outperforms NGINX and Caddy in the setting of very simple HTTP reverse-proxy scenario (See [`bench`](./bench/) directory).
|
As default, `rpxy` provides the *TLS connection sanitization* by correctly binding a certificate used to establish secure channel with backend application. Specifically, it always keeps the consistency between the given SNI (server name indication) in `ClientHello` of the underlying TLS and the domain name given by the overlaid HTTP HOST header (or URL in Request line) [^1]. Additionally, as a somewhat unstable feature, our `rpxy` can handle the brand-new HTTP/3 connection thanks to [`quinn`](https://github.com/quinn-rs/quinn) and [`hyperium/h3`](https://github.com/hyperium/h3).
|
||||||
|
|
||||||
`rpxy` provides the sanitization of TLS's SNI (server name indication) in default by correctly binding a certificate used to establish an underlying TLS connection with backend application specified in the overlaid HTTP HOST header (or URL in Request line). Additionally, as a somewhat unstable feature, our `rpxy` can handle the brand-new HTTP/3 connection thanks to [`quinn`](https://github.com/quinn-rs/quinn) and [`hyperium/h3`](https://github.com/hyperium/h3).
|
This project is still *work-in-progress*. But it is already working in some production environments and serves numbers of domain names. Furthermore it *significantly outperforms* NGINX and Caddy, e.g., *1.5x faster than NGINX*, in the setting of very simple HTTP reverse-proxy scenario (See [`bench`](./bench/) directory).
|
||||||
|
|
||||||
|
[^1]: We should note that NGINX doesn't guarantee such a consistency by default. To this end, you have to add `if` statement in the configuration file in NGINX.
|
||||||
|
|
||||||
## Making an executable binary
|
## Making an executable binary
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ use crate::{
|
||||||
use clap::Arg;
|
use clap::Arg;
|
||||||
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
// #[cfg(feature = "tls")]
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn parse_opts(globals: &mut Globals) -> std::result::Result<(), anyhow::Error> {
|
pub fn parse_opts(globals: &mut Globals) -> std::result::Result<(), anyhow::Error> {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ pub enum RpxyError {
|
||||||
#[error("Http Request Message Error: {0}")]
|
#[error("Http Request Message Error: {0}")]
|
||||||
Request(&'static str),
|
Request(&'static str),
|
||||||
|
|
||||||
|
#[error("TCP/UDP Proxy Layer Error: {0}")]
|
||||||
|
Proxy(String),
|
||||||
|
|
||||||
#[error("I/O Error")]
|
#[error("I/O Error")]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,15 +144,18 @@ where
|
||||||
self.globals.runtime_handle.spawn(async move {
|
self.globals.runtime_handle.spawn(async move {
|
||||||
let mut response_upgraded = onupgrade.await.map_err(|e| {
|
let mut response_upgraded = onupgrade.await.map_err(|e| {
|
||||||
error!("Failed to upgrade response: {}", e);
|
error!("Failed to upgrade response: {}", e);
|
||||||
anyhow!("Failed to upgrade response: {}", e)
|
RpxyError::Hyper(e)
|
||||||
})?;
|
})?;
|
||||||
let mut request_upgraded = request_upgraded.await.map_err(|e| {
|
let mut request_upgraded = request_upgraded.await.map_err(|e| {
|
||||||
error!("Failed to upgrade request: {}", e);
|
error!("Failed to upgrade request: {}", e);
|
||||||
anyhow!("Failed to upgrade request: {}", e)
|
RpxyError::Hyper(e)
|
||||||
})?;
|
})?;
|
||||||
copy_bidirectional(&mut response_upgraded, &mut request_upgraded)
|
copy_bidirectional(&mut response_upgraded, &mut request_upgraded)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow!("Coping between upgraded connections failed: {}", e))?;
|
.map_err(|e| {
|
||||||
|
error!("Coping between upgraded connections failed: {}", e);
|
||||||
|
RpxyError::Io(e)
|
||||||
|
})?;
|
||||||
Ok(()) as Result<()>
|
Ok(()) as Result<()>
|
||||||
});
|
});
|
||||||
log_data.status_code(&res_backend.status()).output();
|
log_data.status_code(&res_backend.status()).output();
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,12 @@ pub(super) fn apply_upstream_options_to_header(
|
||||||
match opt {
|
match opt {
|
||||||
UpstreamOption::OverrideHost => {
|
UpstreamOption::OverrideHost => {
|
||||||
// overwrite HOST value with upstream hostname (like 192.168.xx.x seen from rpxy)
|
// overwrite HOST value with upstream hostname (like 192.168.xx.x seen from rpxy)
|
||||||
let upstream_host = upstream_base_uri.host().ok_or_else(|| anyhow!("none"))?;
|
let upstream_host = upstream_base_uri
|
||||||
|
.host()
|
||||||
|
.ok_or_else(|| anyhow!("No hostname is given in override_host option"))?;
|
||||||
headers
|
headers
|
||||||
.insert(header::HOST, HeaderValue::from_str(upstream_host)?)
|
.insert(header::HOST, HeaderValue::from_str(upstream_host)?)
|
||||||
.ok_or_else(|| anyhow!("none"))?;
|
.ok_or_else(|| anyhow!("Failed to insert host header in override_host option"))?;
|
||||||
}
|
}
|
||||||
UpstreamOption::UpgradeInsecureRequests => {
|
UpstreamOption::UpgradeInsecureRequests => {
|
||||||
// add upgrade-insecure-requests in request header if not exist
|
// add upgrade-insecure-requests in request header if not exist
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ where
|
||||||
size += body.remaining();
|
size += body.remaining();
|
||||||
if size > max_body_size {
|
if size > max_body_size {
|
||||||
error!("Exceeds max request body size for HTTP/3");
|
error!("Exceeds max request body size for HTTP/3");
|
||||||
return Err(anyhow!("Exceeds max request body size for HTTP/3"));
|
return Err(RpxyError::Proxy("Exceeds max request body size for HTTP/3".to_string()));
|
||||||
}
|
}
|
||||||
// create stream body to save memory, shallow copy (increment of ref-count) to Bytes using copy_to_bytes
|
// create stream body to save memory, shallow copy (increment of ref-count) to Bytes using copy_to_bytes
|
||||||
sender.send_data(body.copy_to_bytes(body.remaining())).await?;
|
sender.send_data(body.copy_to_bytes(body.remaining())).await?;
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ where
|
||||||
debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name);
|
debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name);
|
||||||
let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec()));
|
let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec()));
|
||||||
if server_name.is_none(){
|
if server_name.is_none(){
|
||||||
Err(anyhow!("No SNI is given"))
|
Err(RpxyError::Proxy("No SNI is given".to_string()))
|
||||||
} else {
|
} else {
|
||||||
// this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake.
|
// this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake.
|
||||||
self_inner.client_serve(stream, server_clone, client_addr, server_name); // TODO: don't want to pass copied value...
|
self_inner.client_serve(stream, server_clone, client_addr, server_name); // TODO: don't want to pass copied value...
|
||||||
|
|
@ -76,11 +76,11 @@ where
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
Err(anyhow!("Failed to handshake TLS: {}", e))
|
Err(RpxyError::Proxy(format!("Failed to handshake TLS: {}", e)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
Err(anyhow!("Timeout to handshake TLS: {}", e))
|
Err(RpxyError::Proxy(format!("Timeout to handshake TLS: {}", e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue