From 2bf9a6f70a3475ad1fce5d80dd32ff2424f618af Mon Sep 17 00:00:00 2001 From: Collins Muriuki Date: Sat, 23 Oct 2021 21:58:31 +0300 Subject: [PATCH 1/3] Address socket exhaustion by using singleton http client --- mpesa_core/src/client.rs | 13 ++++++++++--- mpesa_core/src/services/account_balance.rs | 8 +++++--- mpesa_core/src/services/b2b.rs | 8 +++++--- mpesa_core/src/services/b2c.rs | 8 +++++--- mpesa_core/src/services/c2b_register.rs | 8 +++++--- mpesa_core/src/services/c2b_simulate.rs | 8 +++++--- mpesa_core/src/services/express_request.rs | 8 +++++--- 7 files changed, 40 insertions(+), 21 deletions(-) diff --git a/mpesa_core/src/client.rs b/mpesa_core/src/client.rs index 847a36eaf..9ac22f2fd 100644 --- a/mpesa_core/src/client.rs +++ b/mpesa_core/src/client.rs @@ -1,8 +1,8 @@ use super::environment::Environment; use super::services::{ AccountBalanceBuilder, B2bBuilder, B2cBuilder, C2bRegisterBuilder, C2bSimulateBuilder, + MpesaExpressRequestBuilder, }; -use crate::services::MpesaExpressRequestBuilder; use crate::MpesaSecurity; use mpesa_derive::*; use reqwest::blocking::Client; @@ -19,6 +19,7 @@ pub struct Mpesa { client_secret: String, initiator_password: RefCell>, environment: Environment, + pub(crate) http_client: Client, } impl<'a> Mpesa { @@ -33,11 +34,16 @@ impl<'a> Mpesa { /// ); /// ``` pub fn new(client_key: String, client_secret: String, environment: Environment) -> Self { + let http_client = Client::builder() + .connect_timeout(std::time::Duration::from_millis(10000)) + .build() + .expect("Error building http client"); Self { client_key, client_secret, initiator_password: RefCell::new(None), environment, + http_client, } } @@ -74,7 +80,7 @@ impl<'a> Mpesa { /// Checks if the client can be authenticated pub fn is_connected(&self) -> bool { - self.auth().ok().is_some() + self.auth().is_ok() } /// **Safaricom Oauth** @@ -94,7 +100,8 @@ impl<'a> Mpesa { "{}/oauth/v1/generate?grant_type=client_credentials", self.environment.base_url() ); - let resp = Client::new() + let resp = self + .http_client .get(&url) .basic_auth(&self.client_key, Some(&self.client_secret)) .send()?; diff --git a/mpesa_core/src/services/account_balance.rs b/mpesa_core/src/services/account_balance.rs index 945d124cd..2beb17a5a 100644 --- a/mpesa_core/src/services/account_balance.rs +++ b/mpesa_core/src/services/account_balance.rs @@ -1,7 +1,6 @@ use crate::client::MpesaResult; use crate::constants::{CommandId, IdentifierTypes}; use crate::{Mpesa, MpesaError, MpesaSecurity}; -use reqwest::blocking::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -164,11 +163,14 @@ impl<'a> AccountBalanceBuilder<'a> { security_credential: &credentials, }; - let response = Client::new() + let response = self + .client + .http_client .post(&url) .bearer_auth(self.client.auth()?) .json(&payload) - .send()?; + .send()? + .error_for_status()?; if response.status().is_success() { let value: AccountBalanceResponse = response.json()?; diff --git a/mpesa_core/src/services/b2b.rs b/mpesa_core/src/services/b2b.rs index 78198fac5..a78cac808 100644 --- a/mpesa_core/src/services/b2b.rs +++ b/mpesa_core/src/services/b2b.rs @@ -2,7 +2,6 @@ use crate::client::{Mpesa, MpesaResult}; use crate::constants::{CommandId, IdentifierTypes}; use crate::errors::MpesaError; use crate::MpesaSecurity; -use reqwest::blocking::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -231,11 +230,14 @@ impl<'a> B2bBuilder<'a> { account_reference: self.account_ref.unwrap_or(""), }; - let response = Client::new() + let response = self + .client + .http_client .post(&url) .bearer_auth(self.client.auth()?) .json(&payload) - .send()?; + .send()? + .error_for_status()?; if response.status().is_success() { let value: B2bResponse = response.json()?; diff --git a/mpesa_core/src/services/b2c.rs b/mpesa_core/src/services/b2c.rs index 0b3ddd775..6eabe08b7 100644 --- a/mpesa_core/src/services/b2c.rs +++ b/mpesa_core/src/services/b2c.rs @@ -1,6 +1,5 @@ use crate::client::MpesaResult; use crate::{CommandId, Mpesa, MpesaError, MpesaSecurity}; -use reqwest::blocking::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -197,11 +196,14 @@ impl<'a> B2cBuilder<'a> { occasion: self.occasion.unwrap_or("None"), }; - let response = Client::new() + let response = self + .client + .http_client .post(&url) .bearer_auth(self.client.auth()?) .json(&payload) - .send()?; + .send()? + .error_for_status()?; if response.status().is_success() { let value: B2cResponse = response.json()?; diff --git a/mpesa_core/src/services/c2b_register.rs b/mpesa_core/src/services/c2b_register.rs index a6056faa7..38b37806e 100644 --- a/mpesa_core/src/services/c2b_register.rs +++ b/mpesa_core/src/services/c2b_register.rs @@ -1,7 +1,6 @@ use crate::client::{Mpesa, MpesaResult}; use crate::constants::ResponseType; use crate::errors::MpesaError; -use reqwest::blocking::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -112,11 +111,14 @@ impl<'a> C2bRegisterBuilder<'a> { short_code: self.short_code.unwrap_or("None"), }; - let response = Client::new() + let response = self + .client + .http_client .post(&url) .bearer_auth(self.client.auth()?) .json(&payload) - .send()?; + .send()? + .error_for_status()?; if response.status().is_success() { let value: C2bRegisterResponse = response.json()?; diff --git a/mpesa_core/src/services/c2b_simulate.rs b/mpesa_core/src/services/c2b_simulate.rs index aa8a67d6b..e5a04bbcf 100644 --- a/mpesa_core/src/services/c2b_simulate.rs +++ b/mpesa_core/src/services/c2b_simulate.rs @@ -1,7 +1,6 @@ use crate::client::{Mpesa, MpesaResult}; use crate::constants::CommandId; use crate::errors::MpesaError; -use reqwest::blocking::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -122,11 +121,14 @@ impl<'a> C2bSimulateBuilder<'a> { short_code: self.short_code.unwrap_or("None"), }; - let response = Client::new() + let response = self + .client + .http_client .post(&url) .bearer_auth(self.client.auth()?) .json(&payload) - .send()?; + .send()? + .error_for_status()?; if response.status().is_success() { let value: C2bSimulateResponse = response.json()?; diff --git a/mpesa_core/src/services/express_request.rs b/mpesa_core/src/services/express_request.rs index cd63bcc0e..aee455cc7 100644 --- a/mpesa_core/src/services/express_request.rs +++ b/mpesa_core/src/services/express_request.rs @@ -2,7 +2,6 @@ use crate::client::{Mpesa, MpesaResult}; use crate::constants::CommandId; use crate::errors::MpesaError; use chrono::prelude::Local; -use reqwest::blocking::Client; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -215,11 +214,14 @@ impl<'a> MpesaExpressRequestBuilder<'a> { transaction_desc: self.transaction_desc.unwrap_or("None"), }; - let response = Client::new() + let response = self + .client + .http_client .post(&url) .bearer_auth(self.client.auth()?) .json(&payload) - .send()?; + .send()? + .error_for_status()?; if response.status().is_success() { let value: MpesaExpressRequestResponse = response.json()?; From 711d48782d0128ec06b087c2d44b32149baaf034 Mon Sep 17 00:00:00 2001 From: Collins Muriuki Date: Tue, 4 Jan 2022 09:28:37 +0300 Subject: [PATCH 2/3] Update general workflows --- .github/workflows/general.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index 7c9476d40..907658317 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -22,6 +22,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test + args: --no-fail-fast fmt: name: Rustfmt @@ -72,4 +73,4 @@ jobs: - name: Run cargo-tarpaulin uses: actions-rs/tarpaulin@v0.1 with: - args: '--ignore-tests' + args: '--ignore-tests --no-fail-fast' From 0cd303ab8dce93fc96815470001d89f7bba2b0f4 Mon Sep 17 00:00:00 2001 From: Collins Muriuki Date: Tue, 4 Jan 2022 09:29:44 +0300 Subject: [PATCH 3/3] Add todo comment on Mpesa instantiator --- mpesa_core/src/client.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mpesa_core/src/client.rs b/mpesa_core/src/client.rs index 9ac22f2fd..94bcb3d7b 100644 --- a/mpesa_core/src/client.rs +++ b/mpesa_core/src/client.rs @@ -37,6 +37,7 @@ impl<'a> Mpesa { let http_client = Client::builder() .connect_timeout(std::time::Duration::from_millis(10000)) .build() + // TODO: Potentialy return a `Result` enum from Mpesa::new? .expect("Error building http client"); Self { client_key,