mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-04 05:59:48 +08:00 
			
		
		
		
	feat(router): send connector timeouts and connection closures as 2xx response instead of giving 5xx response (#2047)
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							c9fe389b2c
						
					
				
				
					commit
					31088b6062
				
			
							
								
								
									
										6
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -2362,9 +2362,9 @@ dependencies = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "hyper"
 | 
					name = "hyper"
 | 
				
			||||||
version = "0.14.26"
 | 
					version = "0.14.27"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
 | 
					checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "bytes",
 | 
					 "bytes",
 | 
				
			||||||
 "futures-channel",
 | 
					 "futures-channel",
 | 
				
			||||||
@ -3789,6 +3789,8 @@ dependencies = [
 | 
				
			|||||||
 "futures",
 | 
					 "futures",
 | 
				
			||||||
 "hex",
 | 
					 "hex",
 | 
				
			||||||
 "http",
 | 
					 "http",
 | 
				
			||||||
 | 
					 "hyper",
 | 
				
			||||||
 | 
					 "image",
 | 
				
			||||||
 "infer 0.13.0",
 | 
					 "infer 0.13.0",
 | 
				
			||||||
 "josekit",
 | 
					 "josekit",
 | 
				
			||||||
 "jsonwebtoken",
 | 
					 "jsonwebtoken",
 | 
				
			||||||
 | 
				
			|||||||
@ -51,6 +51,7 @@ error-stack = "0.3.1"
 | 
				
			|||||||
futures = "0.3.28"
 | 
					futures = "0.3.28"
 | 
				
			||||||
hex = "0.4.3"
 | 
					hex = "0.4.3"
 | 
				
			||||||
http = "0.2.9"
 | 
					http = "0.2.9"
 | 
				
			||||||
 | 
					hyper = "0.14.27"
 | 
				
			||||||
image = "0.23.14"
 | 
					image = "0.23.14"
 | 
				
			||||||
infer = "0.13.0"
 | 
					infer = "0.13.0"
 | 
				
			||||||
josekit = "0.8.3"
 | 
					josekit = "0.8.3"
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,9 @@ pub(crate) const ALPHABETS: [char; 62] = [
 | 
				
			|||||||
pub const REQUEST_TIME_OUT: u64 = 30;
 | 
					pub const REQUEST_TIME_OUT: u64 = 30;
 | 
				
			||||||
pub const REQUEST_TIMEOUT_ERROR_CODE: &str = "TIMEOUT";
 | 
					pub const REQUEST_TIMEOUT_ERROR_CODE: &str = "TIMEOUT";
 | 
				
			||||||
pub const REQUEST_TIMEOUT_ERROR_MESSAGE: &str = "Connector did not respond in specified time";
 | 
					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)
 | 
					///Payment intent fulfillment default timeout (in seconds)
 | 
				
			||||||
pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60;
 | 
					pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60;
 | 
				
			||||||
 | 
				
			|||||||
@ -273,6 +273,9 @@ pub enum ApiClientError {
 | 
				
			|||||||
    #[error("Server responded with Request Timeout")]
 | 
					    #[error("Server responded with Request Timeout")]
 | 
				
			||||||
    RequestTimeoutReceived,
 | 
					    RequestTimeoutReceived,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[error("connection closed before a message could complete")]
 | 
				
			||||||
 | 
					    ConnectionClosed,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[error("Server responded with Internal Server Error")]
 | 
					    #[error("Server responded with Internal Server Error")]
 | 
				
			||||||
    InternalServerErrorReceived,
 | 
					    InternalServerErrorReceived,
 | 
				
			||||||
    #[error("Server responded with Bad Gateway")]
 | 
					    #[error("Server responded with Bad Gateway")]
 | 
				
			||||||
@ -566,6 +569,9 @@ impl ApiClientError {
 | 
				
			|||||||
    pub fn is_upstream_timeout(&self) -> bool {
 | 
					    pub fn is_upstream_timeout(&self) -> bool {
 | 
				
			||||||
        self == &Self::RequestTimeoutReceived
 | 
					        self == &Self::RequestTimeoutReceived
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    pub fn is_connection_closed(&self) -> bool {
 | 
				
			||||||
 | 
					        self == &Self::ConnectionClosed
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl ConnectorError {
 | 
					impl ConnectorError {
 | 
				
			||||||
 | 
				
			|||||||
@ -172,7 +172,7 @@ pub async fn refresh_connector_auth(
 | 
				
			|||||||
                    code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(),
 | 
					                    code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(),
 | 
				
			||||||
                    message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(),
 | 
					                    message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(),
 | 
				
			||||||
                    reason: Some(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))
 | 
					                Ok(Err(error_response))
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ pub mod request;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    collections::HashMap,
 | 
					    collections::HashMap,
 | 
				
			||||||
 | 
					    error::Error,
 | 
				
			||||||
    fmt::Debug,
 | 
					    fmt::Debug,
 | 
				
			||||||
    future::Future,
 | 
					    future::Future,
 | 
				
			||||||
    str,
 | 
					    str,
 | 
				
			||||||
@ -384,8 +385,27 @@ where
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        Err(error) => {
 | 
					                        Err(error) => {
 | 
				
			||||||
                            if error.current_context().is_upstream_timeout() {
 | 
					                            if error.current_context().is_upstream_timeout() {
 | 
				
			||||||
                                Err(error
 | 
					                                let error_response = ErrorResponse {
 | 
				
			||||||
                                    .change_context(errors::ConnectorError::RequestTimeoutReceived))
 | 
					                                    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 {
 | 
					                            } else {
 | 
				
			||||||
                                Err(error.change_context(
 | 
					                                Err(error.change_context(
 | 
				
			||||||
                                    errors::ConnectorError::ProcessingStepFailed(None),
 | 
					                                    errors::ConnectorError::ProcessingStepFailed(None),
 | 
				
			||||||
@ -505,6 +525,10 @@ pub async fn send_request(
 | 
				
			|||||||
                metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]);
 | 
					                metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]);
 | 
				
			||||||
                errors::ApiClientError::RequestTimeoutReceived
 | 
					                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()),
 | 
					            _ => errors::ApiClientError::RequestNotSent(error.to_string()),
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .into_report()
 | 
					        .into_report()
 | 
				
			||||||
@ -519,6 +543,19 @@ pub async fn send_request(
 | 
				
			|||||||
    .await
 | 
					    .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::<hyper::Error>() {
 | 
				
			||||||
 | 
					            if hyper_err.is_incomplete_message() {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        source = err.source();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[instrument(skip_all)]
 | 
					#[instrument(skip_all)]
 | 
				
			||||||
async fn handle_response(
 | 
					async fn handle_response(
 | 
				
			||||||
    response: CustomResult<reqwest::Response, errors::ApiClientError>,
 | 
					    response: CustomResult<reqwest::Response, errors::ApiClientError>,
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user