diff --git a/Cargo.lock b/Cargo.lock index 50e5e015a9..35f27b8ce2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2362,9 +2362,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -3789,6 +3789,8 @@ dependencies = [ "futures", "hex", "http", + "hyper", + "image", "infer 0.13.0", "josekit", "jsonwebtoken", diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index f0af3ad7f7..38a231d848 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -51,6 +51,7 @@ error-stack = "0.3.1" futures = "0.3.28" hex = "0.4.3" http = "0.2.9" +hyper = "0.14.27" image = "0.23.14" infer = "0.13.0" josekit = "0.8.3" diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index d432bfeec8..34d1c0ab8f 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -13,6 +13,9 @@ pub(crate) const ALPHABETS: [char; 62] = [ pub const REQUEST_TIME_OUT: u64 = 30; pub const REQUEST_TIMEOUT_ERROR_CODE: &str = "TIMEOUT"; pub const REQUEST_TIMEOUT_ERROR_MESSAGE: &str = "Connector did not respond in specified time"; +pub const CONNECTION_CLOSED_ERROR_CODE: &str = "CONNECTION_CLOSED"; +pub const CONNECTION_CLOSED_ERROR_MESSAGE: &str = + "Connection closed before a message could complete"; ///Payment intent fulfillment default timeout (in seconds) pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60; diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 6a59d54ee9..eacaaefa5f 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -273,6 +273,9 @@ pub enum ApiClientError { #[error("Server responded with Request Timeout")] RequestTimeoutReceived, + #[error("connection closed before a message could complete")] + ConnectionClosed, + #[error("Server responded with Internal Server Error")] InternalServerErrorReceived, #[error("Server responded with Bad Gateway")] @@ -566,6 +569,9 @@ impl ApiClientError { pub fn is_upstream_timeout(&self) -> bool { self == &Self::RequestTimeoutReceived } + pub fn is_connection_closed(&self) -> bool { + self == &Self::ConnectionClosed + } } impl ConnectorError { diff --git a/crates/router/src/core/payments/access_token.rs b/crates/router/src/core/payments/access_token.rs index e7b07efe73..887b2b8f41 100644 --- a/crates/router/src/core/payments/access_token.rs +++ b/crates/router/src/core/payments/access_token.rs @@ -172,7 +172,7 @@ pub async fn refresh_connector_auth( code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(), message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(), reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()), - status_code: 200, + status_code: 504, }; Ok(Err(error_response)) diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index aad24b232e..897ab80e9e 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -3,6 +3,7 @@ pub mod request; use std::{ collections::HashMap, + error::Error, fmt::Debug, future::Future, str, @@ -384,8 +385,27 @@ where } Err(error) => { if error.current_context().is_upstream_timeout() { - Err(error - .change_context(errors::ConnectorError::RequestTimeoutReceived)) + let error_response = ErrorResponse { + code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(), + message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(), + reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()), + status_code: 504, + }; + router_data.response = Err(error_response); + router_data.connector_http_status_code = Some(504); + Ok(router_data) + } else if error.current_context().is_connection_closed() { + let error_response = ErrorResponse { + code: consts::CONNECTION_CLOSED_ERROR_CODE.to_string(), + message: consts::CONNECTION_CLOSED_ERROR_MESSAGE.to_string(), + reason: Some( + consts::CONNECTION_CLOSED_ERROR_MESSAGE.to_string(), + ), + status_code: 504, + }; + router_data.response = Err(error_response); + router_data.connector_http_status_code = Some(504); + Ok(router_data) } else { Err(error.change_context( errors::ConnectorError::ProcessingStepFailed(None), @@ -505,6 +525,10 @@ pub async fn send_request( metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); errors::ApiClientError::RequestTimeoutReceived } + error if is_connection_closed(&error) => { + metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); + errors::ApiClientError::ConnectionClosed + } _ => errors::ApiClientError::RequestNotSent(error.to_string()), }) .into_report() @@ -519,6 +543,19 @@ pub async fn send_request( .await } +fn is_connection_closed(error: &reqwest::Error) -> bool { + let mut source = error.source(); + while let Some(err) = source { + if let Some(hyper_err) = err.downcast_ref::() { + if hyper_err.is_incomplete_message() { + return true; + } + } + source = err.source(); + } + false +} + #[instrument(skip_all)] async fn handle_response( response: CustomResult,