diff --git a/rama-http-backend/src/client/proxy/layer/mod.rs b/rama-http-backend/src/client/proxy/layer/mod.rs index a2949490..26f08afe 100644 --- a/rama-http-backend/src/client/proxy/layer/mod.rs +++ b/rama-http-backend/src/client/proxy/layer/mod.rs @@ -8,4 +8,4 @@ pub use proxy_auth_header::{SetProxyAuthHttpHeaderLayer, SetProxyAuthHttpHeaderS mod proxy_connector; #[doc(inline)] -pub use proxy_connector::{HttpProxyConnector, HttpProxyConnectorLayer}; +pub use proxy_connector::{HttpProxyConnector, HttpProxyConnectorLayer, HttpProxyError}; diff --git a/rama-http-backend/src/client/proxy/layer/proxy_connector/connector.rs b/rama-http-backend/src/client/proxy/layer/proxy_connector/connector.rs index 734077c8..436d24df 100644 --- a/rama-http-backend/src/client/proxy/layer/proxy_connector/connector.rs +++ b/rama-http-backend/src/client/proxy/layer/proxy_connector/connector.rs @@ -11,6 +11,8 @@ use rama_http_types::{ use rama_net::{address::Authority, stream::Stream}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use super::HttpProxyError; + #[derive(Debug, Clone)] /// Connector for HTTP proxies. /// @@ -64,7 +66,7 @@ impl InnerHttpProxyConnector { pub(super) async fn handshake( &self, mut stream: S, - ) -> Result { + ) -> Result { // TODO: handle user-agent and host better // TODO: use h1 protocol from embedded hyper directly here! let mut request = format!( @@ -100,7 +102,8 @@ impl InnerHttpProxyConnector { return Err(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "http conn handshake read incomplete", - )); + ) + .into()); } pos += n; @@ -113,27 +116,24 @@ impl InnerHttpProxyConnector { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "http conn handshake response too large", - )); + ) + .into()); } // else read more } else if recvd.starts_with(b"HTTP/1.1 407") { - return Err(std::io::Error::new( - std::io::ErrorKind::PermissionDenied, - "http conn handshake proxy auth required", - )); + return Err(HttpProxyError::AuthRequired); + } else if recvd.starts_with(b"HTTP/1.1 503") { + return Err(HttpProxyError::Unavailable); } else { let input = String::from_utf8_lossy(recvd); - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!( - "invalid http conn handshake start: [{}]", - if let Some((line, _)) = input.split_once("\r\n") { - Cow::Borrowed(line) - } else { - input - } - ), - )); + return Err(HttpProxyError::Other(format!( + "invalid http conn handshake start: [{}]", + if let Some((line, _)) = input.split_once("\r\n") { + Cow::Borrowed(line) + } else { + input + } + ))); } } } diff --git a/rama-http-backend/src/client/proxy/layer/proxy_connector/mod.rs b/rama-http-backend/src/client/proxy/layer/proxy_connector/mod.rs index 5b62c360..c4d3fa4a 100644 --- a/rama-http-backend/src/client/proxy/layer/proxy_connector/mod.rs +++ b/rama-http-backend/src/client/proxy/layer/proxy_connector/mod.rs @@ -2,6 +2,10 @@ mod connector; // internal usage only use connector::InnerHttpProxyConnector; +mod proxy_error; +#[doc(inline)] +pub use proxy_error::HttpProxyError; + mod layer; #[doc(inline)] pub use layer::HttpProxyConnectorLayer; diff --git a/rama-http-backend/src/client/proxy/layer/proxy_connector/proxy_error.rs b/rama-http-backend/src/client/proxy/layer/proxy_connector/proxy_error.rs new file mode 100644 index 00000000..ada4a776 --- /dev/null +++ b/rama-http-backend/src/client/proxy/layer/proxy_connector/proxy_error.rs @@ -0,0 +1,59 @@ +use std::fmt; + +#[derive(Debug)] +/// error that can be returned in case a http proxy +/// did not manage to establish a connection +pub enum HttpProxyError { + /// Proxy Authentication Required + /// + /// (Proxy returned HTTP 407) + AuthRequired, + /// Proxy is Unavailable + /// + /// (Proxy returned HTTP 503) + Unavailable, + /// I/O error happened as part of HTTP Proxy Connection Establishment + /// + /// (e.g. some kind of TCP error) + Transport(std::io::Error), + /// Something went wrong, but classification did not happen. + /// + /// (First header line of http response is included in error) + Other(String), +} + +impl fmt::Display for HttpProxyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + HttpProxyError::AuthRequired => { + write!(f, "http proxy error: proxy auth required (http 407)") + } + HttpProxyError::Unavailable => { + write!(f, "http proxy error: proxy unavailable (http 503)") + } + HttpProxyError::Transport(error) => { + write!(f, "http proxy error: transport error: I/O [{}]", error) + } + HttpProxyError::Other(header) => { + write!(f, "http proxy error: first line of header = [{}]", header) + } + } + } +} + +impl From for HttpProxyError { + fn from(value: std::io::Error) -> Self { + Self::Transport(value) + } +} + +impl std::error::Error for HttpProxyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + HttpProxyError::AuthRequired => None, + HttpProxyError::Unavailable => None, + HttpProxyError::Transport(err) => Some(err), + HttpProxyError::Other(_) => None, + } + } +}