diff --git a/Cargo.toml b/Cargo.toml index c213679..fbe7c5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ bytes = "1.2.0" quinn = { version = "0.8.3", optional = true } h3 = { git = "https://github.com/hyperium/h3.git", optional = true } h3-quinn = { git = "https://github.com/hyperium/h3.git", optional = true } +thiserror = "1.0.31" [target.'cfg(not(target_env = "msvc"))'.dependencies] diff --git a/src/backend_opt.rs b/src/backend_opt.rs index e40edc7..8774733 100644 --- a/src/backend_opt.rs +++ b/src/backend_opt.rs @@ -7,12 +7,12 @@ pub enum UpstreamOption { // TODO: Adds more options for heder override } impl TryFrom<&str> for UpstreamOption { - type Error = anyhow::Error; + type Error = RpxyError; fn try_from(val: &str) -> Result { match val { "override_host" => Ok(Self::OverrideHost), "upgrade_insecure_requests" => Ok(Self::UpgradeInsecureRequests), - _ => Err(anyhow!("Unsupported header option")), + _ => Err(RpxyError::Other(anyhow!("Unsupported header option"))), } } } diff --git a/src/config/parse.rs b/src/config/parse.rs index 0bb3f4d..4fe244e 100644 --- a/src/config/parse.rs +++ b/src/config/parse.rs @@ -14,7 +14,7 @@ use std::net::SocketAddr; // #[cfg(feature = "tls")] use std::path::PathBuf; -pub fn parse_opts(globals: &mut Globals) -> Result<()> { +pub fn parse_opts(globals: &mut Globals) -> std::result::Result<(), anyhow::Error> { let _ = include_str!("../../Cargo.toml"); let options = clap::command!().arg( Arg::new("config_file") @@ -191,7 +191,7 @@ pub fn parse_opts(globals: &mut Globals) -> Result<()> { Ok(()) } -fn get_reverse_proxy(rp_settings: &[ReverseProxyOption]) -> Result { +fn get_reverse_proxy(rp_settings: &[ReverseProxyOption]) -> std::result::Result { let mut upstream: HashMap = HashMap::default(); rp_settings.iter().for_each(|rpo| { let path = match &rpo.path { diff --git a/src/config/toml.rs b/src/config/toml.rs index 90a1961..5f31ba6 100644 --- a/src/config/toml.rs +++ b/src/config/toml.rs @@ -78,7 +78,7 @@ impl UpstreamParams { } impl ConfigToml { - pub fn new(config_file: &str) -> Result { + pub fn new(config_file: &str) -> std::result::Result { let config_str = fs::read_to_string(config_file).context("Failed to read config file")?; toml::from_str(&config_str).context("Failed to parse toml config") diff --git a/src/error.rs b/src/error.rs index 4b30b2d..4e55bfb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,42 @@ -pub use anyhow::{anyhow, bail, ensure, Context, Result}; -// use thiserror::Error; +pub use anyhow::{anyhow, bail, ensure, Context}; +use std::io; +use thiserror::Error; -// #[allow(clippy::enum_variant_names)] -// #[derive(Debug, Error)] -// pub enum RpxyError { -// // IoError, -// #[error("ConfigError: {0}")] -// ConfigError(String), -// // ConnectionError, -// // HttpError, -// } +pub type Result = std::result::Result; + +/// Describes things that can go wrong in the Rpxy +#[derive(Debug, Error)] +pub enum RpxyError { + #[error("Http Message Handler Error: {0}")] + Handler(&'static str), + + #[error("Http Request Message Error: {0}")] + Request(&'static str), + + #[error("I/O Error")] + Io(#[from] io::Error), + + #[error("Quic Connection Error")] + QuicConn(#[from] quinn::ConnectionError), + + #[error("H3 Error")] + H3(#[from] h3::Error), + + #[error("rustls Connection Error")] + Rustls(#[from] rustls::Error), + + #[error("Hyper Error")] + Hyper(#[from] hyper::Error), + + #[error("Hyper Http Error")] + HyperHttp(#[from] hyper::http::Error), + + #[error("Hyper Http HeaderValue Error")] + HyperHeaderValue(#[from] hyper::header::InvalidHeaderValue), + + #[error("Hyper Http HeaderName Error")] + HyperHeaderName(#[from] hyper::header::InvalidHeaderName), + + #[error(transparent)] + Other(#[from] anyhow::Error), +} diff --git a/src/handler/handler_main.rs b/src/handler/handler_main.rs index 615e479..b85eb55 100644 --- a/src/handler/handler_main.rs +++ b/src/handler/handler_main.rs @@ -263,7 +263,9 @@ where apply_upstream_options_to_header(headers, client_addr, upstream_group, &upstream_chosen.uri)?; // update uri in request - ensure!(upstream_chosen.uri.authority().is_some() && upstream_chosen.uri.scheme().is_some()); + if !(upstream_chosen.uri.authority().is_some() && upstream_chosen.uri.scheme().is_some()) { + return Err(RpxyError::Handler("Upstream uri `scheme` and `authority` is broken")); + }; let new_uri = Uri::builder() .scheme(upstream_chosen.uri.scheme().unwrap().as_str()) .authority(upstream_chosen.uri.authority().unwrap().as_str()); @@ -277,7 +279,9 @@ where let new_pq = match &upstream_group.replace_path { Some(new_path) => { let matched_path: &[u8] = upstream_group.path.as_ref(); - ensure!(!matched_path.is_empty() && org_pq.len() >= matched_path.len()); + if matched_path.is_empty() || org_pq.len() < matched_path.len() { + return Err(RpxyError::Handler("Upstream uri `path and query` is broken")); + }; let mut new_pq = Vec::::with_capacity(org_pq.len() - matched_path.len() + new_path.len()); new_pq.extend_from_slice(new_path); new_pq.extend_from_slice(&org_pq[matched_path.len()..]); diff --git a/src/handler/utils_request.rs b/src/handler/utils_request.rs index 1a131c6..9ff480d 100644 --- a/src/handler/utils_request.rs +++ b/src/handler/utils_request.rs @@ -63,10 +63,9 @@ impl ParseHost for Request { let uri_host = self.uri().host(); // let uri_port = self.uri().port_u16(); - ensure!( - !(headers_host.is_none() && uri_host.is_none()), - "No host in request header" - ); + if !(!(headers_host.is_none() && uri_host.is_none())) { + return Err(RpxyError::Request("No host in request header")); + } // prioritize server_name in uri uri_host.map_or_else( @@ -75,8 +74,8 @@ impl ParseHost for Request { if m.starts_with(&[b'[']) { // v6 address with bracket case. if port is specified, always it is in this case. let mut iter = m.split(|ptr| ptr == &b'[' || ptr == &b']'); - iter.next().ok_or_else(|| anyhow!("Invalid Host"))?; // first item is always blank - iter.next().ok_or_else(|| anyhow!("Invalid Host")) + iter.next().ok_or(RpxyError::Request("Invalid Host"))?; // first item is always blank + iter.next().ok_or(RpxyError::Request("Invalid Host")) } else if m.len() - m.split(|v| v == &b':').fold(0, |acc, s| acc + s.len()) >= 2 { // v6 address case, if 2 or more ':' is contained Ok(m) @@ -85,7 +84,7 @@ impl ParseHost for Request { m.split(|colon| colon == &b':') .into_iter() .next() - .ok_or_else(|| anyhow!("Invalid Host")) + .ok_or(RpxyError::Request("Invalid Host")) } }, |v| Ok(v.as_bytes()), diff --git a/src/proxy/proxy_h3.rs b/src/proxy/proxy_h3.rs index 9cfd494..5892b1d 100644 --- a/src/proxy/proxy_h3.rs +++ b/src/proxy/proxy_h3.rs @@ -56,7 +56,7 @@ where } Err(err) => { warn!("QUIC accepting connection failed: {:?}", err); - return Err(anyhow!("{}", err)); + return Err(RpxyError::QuicConn(err)); } } @@ -104,7 +104,7 @@ where debug!("HTTP/3 incoming request trailers"); sender.send_trailers(trailers.unwrap()).await?; } - Ok(()) as Result<()> + Ok(()) }); let new_req: Request = Request::from_parts(req_parts, req_body);