client.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. //! HTTP client wrapper
  2. #[cfg(feature = "bitreq")]
  3. use std::sync::Arc;
  4. #[cfg(feature = "bitreq")]
  5. use bitreq::RequestExt;
  6. use serde::de::DeserializeOwned;
  7. use serde::Serialize;
  8. use crate::error::HttpError;
  9. use crate::request::RequestBuilder;
  10. use crate::response::{RawResponse, Response};
  11. /// HTTP client wrapper
  12. #[derive(Clone)]
  13. pub struct HttpClient {
  14. #[cfg(feature = "reqwest")]
  15. inner: reqwest::Client,
  16. #[cfg(feature = "bitreq")]
  17. inner: Arc<bitreq::Client>,
  18. #[cfg(feature = "bitreq")]
  19. proxy_config: Option<ProxyConfig>,
  20. }
  21. impl std::fmt::Debug for HttpClient {
  22. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  23. f.debug_struct("HttpClient").finish()
  24. }
  25. }
  26. #[cfg(feature = "reqwest")]
  27. impl HttpClient {
  28. /// Create a new HTTP client with default settings
  29. pub fn new() -> Self {
  30. Self {
  31. inner: reqwest::Client::new(),
  32. }
  33. }
  34. /// Create an HttpClient from a reqwest::Client
  35. pub fn from_reqwest(client: reqwest::Client) -> Self {
  36. Self { inner: client }
  37. }
  38. /// Create a new HTTP client builder
  39. pub fn builder() -> HttpClientBuilder {
  40. HttpClientBuilder::default()
  41. }
  42. /// GET request, returns JSON deserialized to R
  43. pub async fn fetch<R: DeserializeOwned>(&self, url: &str) -> Response<R> {
  44. let response = self.inner.get(url).send().await.map_err(HttpError::from)?;
  45. let status = response.status();
  46. if !status.is_success() {
  47. let message = response.text().await.unwrap_or_default();
  48. return Err(HttpError::Status {
  49. status: status.as_u16(),
  50. message,
  51. });
  52. }
  53. response.json().await.map_err(HttpError::from)
  54. }
  55. /// POST with JSON body, returns JSON deserialized to R
  56. pub async fn post_json<B: Serialize + ?Sized, R: DeserializeOwned>(
  57. &self,
  58. url: &str,
  59. body: &B,
  60. ) -> Response<R> {
  61. let response = self
  62. .inner
  63. .post(url)
  64. .json(body)
  65. .send()
  66. .await
  67. .map_err(HttpError::from)?;
  68. let status = response.status();
  69. if !status.is_success() {
  70. let message = response.text().await.unwrap_or_default();
  71. return Err(HttpError::Status {
  72. status: status.as_u16(),
  73. message,
  74. });
  75. }
  76. response.json().await.map_err(HttpError::from)
  77. }
  78. /// POST with form data, returns JSON deserialized to R
  79. pub async fn post_form<F: Serialize + ?Sized, R: DeserializeOwned>(
  80. &self,
  81. url: &str,
  82. form: &F,
  83. ) -> Response<R> {
  84. let response = self
  85. .inner
  86. .post(url)
  87. .form(form)
  88. .send()
  89. .await
  90. .map_err(HttpError::from)?;
  91. let status = response.status();
  92. if !status.is_success() {
  93. let message = response.text().await.unwrap_or_default();
  94. return Err(HttpError::Status {
  95. status: status.as_u16(),
  96. message,
  97. });
  98. }
  99. response.json().await.map_err(HttpError::from)
  100. }
  101. /// PATCH with JSON body, returns JSON deserialized to R
  102. pub async fn patch_json<B: Serialize + ?Sized, R: DeserializeOwned>(
  103. &self,
  104. url: &str,
  105. body: &B,
  106. ) -> Response<R> {
  107. let response = self
  108. .inner
  109. .patch(url)
  110. .json(body)
  111. .send()
  112. .await
  113. .map_err(HttpError::from)?;
  114. let status = response.status();
  115. if !status.is_success() {
  116. let message = response.text().await.unwrap_or_default();
  117. return Err(HttpError::Status {
  118. status: status.as_u16(),
  119. message,
  120. });
  121. }
  122. response.json().await.map_err(HttpError::from)
  123. }
  124. /// GET request returning raw response body
  125. pub async fn get_raw(&self, url: &str) -> Response<RawResponse> {
  126. let response = self.inner.get(url).send().await.map_err(HttpError::from)?;
  127. Ok(RawResponse::new(response))
  128. }
  129. /// POST request builder for complex cases
  130. pub fn post(&self, url: &str) -> RequestBuilder {
  131. RequestBuilder::new(self.inner.post(url))
  132. }
  133. /// GET request builder for complex cases
  134. pub fn get(&self, url: &str) -> RequestBuilder {
  135. RequestBuilder::new(self.inner.get(url))
  136. }
  137. /// PATCH request builder for complex cases
  138. pub fn patch(&self, url: &str) -> RequestBuilder {
  139. RequestBuilder::new(self.inner.patch(url))
  140. }
  141. }
  142. #[cfg(feature = "bitreq")]
  143. impl HttpClient {
  144. /// Create a new HTTP client with default settings
  145. pub fn new() -> Self {
  146. Self {
  147. inner: Arc::new(bitreq::Client::new(10)), // Default capacity of 10
  148. proxy_config: None,
  149. }
  150. }
  151. /// Create a new HTTP client builder
  152. pub fn builder() -> HttpClientBuilder {
  153. HttpClientBuilder::default()
  154. }
  155. /// Helper method to apply proxy if URL matches the configured proxy rules
  156. fn apply_proxy_if_needed(
  157. &self,
  158. request: bitreq::Request,
  159. url: &str,
  160. ) -> Response<bitreq::Request> {
  161. apply_proxy_if_needed(request, url, &self.proxy_config)
  162. }
  163. /// GET request, returns JSON deserialized to R
  164. pub async fn fetch<R: DeserializeOwned>(&self, url: &str) -> Response<R> {
  165. let request = bitreq::get(url);
  166. let request = self.apply_proxy_if_needed(request, url)?;
  167. let response = request
  168. .send_async_with_client(&self.inner)
  169. .await
  170. .map_err(HttpError::from)?;
  171. let status = response.status_code;
  172. if !(200..300).contains(&status) {
  173. let message = response.as_str().unwrap_or("").to_string();
  174. return Err(HttpError::Status {
  175. status: status as u16,
  176. message,
  177. });
  178. }
  179. response.json().map_err(HttpError::from)
  180. }
  181. /// POST with JSON body, returns JSON deserialized to R
  182. pub async fn post_json<B: Serialize, R: DeserializeOwned>(
  183. &self,
  184. url: &str,
  185. body: &B,
  186. ) -> Response<R> {
  187. let request = bitreq::post(url).with_json(body).map_err(HttpError::from)?;
  188. let request = self.apply_proxy_if_needed(request, url)?;
  189. let response: bitreq::Response = request
  190. .send_async_with_client(&self.inner)
  191. .await
  192. .map_err(HttpError::from)?;
  193. let status = response.status_code;
  194. if !(200..300).contains(&status) {
  195. let message = response.as_str().unwrap_or("").to_string();
  196. return Err(HttpError::Status {
  197. status: status as u16,
  198. message,
  199. });
  200. }
  201. response.json().map_err(HttpError::from)
  202. }
  203. /// POST with form data, returns JSON deserialized to R
  204. pub async fn post_form<F: Serialize, R: DeserializeOwned>(
  205. &self,
  206. url: &str,
  207. form: &F,
  208. ) -> Response<R> {
  209. let form_str = serde_urlencoded::to_string(form)
  210. .map_err(|e| HttpError::Serialization(e.to_string()))?;
  211. let request = bitreq::post(url)
  212. .with_body(form_str.into_bytes())
  213. .with_header("Content-Type", "application/x-www-form-urlencoded");
  214. let request = self.apply_proxy_if_needed(request, url)?;
  215. let response: bitreq::Response = request
  216. .send_async_with_client(&self.inner)
  217. .await
  218. .map_err(HttpError::from)?;
  219. let status = response.status_code;
  220. if !(200..300).contains(&status) {
  221. let message = response.as_str().unwrap_or("").to_string();
  222. return Err(HttpError::Status {
  223. status: status as u16,
  224. message,
  225. });
  226. }
  227. response.json().map_err(HttpError::from)
  228. }
  229. /// PATCH with JSON body, returns JSON deserialized to R
  230. pub async fn patch_json<B: Serialize, R: DeserializeOwned>(
  231. &self,
  232. url: &str,
  233. body: &B,
  234. ) -> Response<R> {
  235. let request = bitreq::patch(url)
  236. .with_json(body)
  237. .map_err(HttpError::from)?;
  238. let request = self.apply_proxy_if_needed(request, url)?;
  239. let response: bitreq::Response = request
  240. .send_async_with_client(&self.inner)
  241. .await
  242. .map_err(HttpError::from)?;
  243. let status = response.status_code;
  244. if !(200..300).contains(&status) {
  245. let message = response.as_str().unwrap_or("").to_string();
  246. return Err(HttpError::Status {
  247. status: status as u16,
  248. message,
  249. });
  250. }
  251. response.json().map_err(HttpError::from)
  252. }
  253. /// GET request returning raw response body
  254. pub async fn get_raw(&self, url: &str) -> Response<RawResponse> {
  255. let request = bitreq::get(url);
  256. let request = self.apply_proxy_if_needed(request, url)?;
  257. let response = request
  258. .send_async_with_client(&self.inner)
  259. .await
  260. .map_err(HttpError::from)?;
  261. Ok(RawResponse::new(response))
  262. }
  263. /// POST request builder for complex cases
  264. pub fn post(&self, url: &str) -> RequestBuilder {
  265. // Note: Proxy will be applied when the request is sent
  266. RequestBuilder::new(
  267. bitreq::post(url),
  268. url,
  269. self.inner.clone(),
  270. self.proxy_config.clone(),
  271. )
  272. }
  273. /// GET request builder for complex cases
  274. pub fn get(&self, url: &str) -> RequestBuilder {
  275. RequestBuilder::new(
  276. bitreq::get(url),
  277. url,
  278. self.inner.clone(),
  279. self.proxy_config.clone(),
  280. )
  281. }
  282. /// PATCH request builder for complex cases
  283. pub fn patch(&self, url: &str) -> RequestBuilder {
  284. RequestBuilder::new(
  285. bitreq::patch(url),
  286. url,
  287. self.inner.clone(),
  288. self.proxy_config.clone(),
  289. )
  290. }
  291. }
  292. impl Default for HttpClient {
  293. fn default() -> Self {
  294. Self::new()
  295. }
  296. }
  297. /// HTTP client builder for configuring proxy and TLS settings
  298. #[derive(Debug, Default)]
  299. pub struct HttpClientBuilder {
  300. #[cfg(any(feature = "reqwest", feature = "bitreq"))]
  301. proxy: Option<ProxyConfig>,
  302. #[cfg(feature = "bitreq")]
  303. accept_invalid_certs: bool,
  304. }
  305. #[cfg(any(feature = "bitreq", feature = "reqwest"))]
  306. #[derive(Debug, Clone)]
  307. pub(crate) struct ProxyConfig {
  308. url: url::Url,
  309. matcher: Option<regex::Regex>,
  310. }
  311. #[cfg(feature = "bitreq")]
  312. pub(crate) fn apply_proxy_if_needed(
  313. request: bitreq::Request,
  314. url: &str,
  315. proxy_config: &Option<ProxyConfig>,
  316. ) -> Response<bitreq::Request> {
  317. if let Some(ref config) = proxy_config {
  318. if let Some(ref matcher) = config.matcher {
  319. // Check if URL host matches the regex pattern
  320. if let Ok(parsed_url) = url::Url::parse(url) {
  321. if let Some(host) = parsed_url.host_str() {
  322. if matcher.is_match(host) {
  323. let proxy = bitreq::Proxy::new_http(&config.url)
  324. .map_err(|e| HttpError::Proxy(e.to_string()))?;
  325. return Ok(request.with_proxy(proxy));
  326. }
  327. }
  328. }
  329. } else {
  330. // No matcher, apply proxy to all requests
  331. let proxy = bitreq::Proxy::new_http(&config.url)
  332. .map_err(|e| HttpError::Proxy(e.to_string()))?;
  333. return Ok(request.with_proxy(proxy));
  334. }
  335. }
  336. Ok(request)
  337. }
  338. impl HttpClientBuilder {
  339. /// Set a proxy URL (reqwest only)
  340. #[cfg(feature = "reqwest")]
  341. pub fn proxy(mut self, url: url::Url) -> Self {
  342. self.proxy = Some(ProxyConfig { url, matcher: None });
  343. self
  344. }
  345. /// Set a proxy URL with a host pattern matcher (reqwest only)
  346. #[cfg(feature = "reqwest")]
  347. pub fn proxy_with_matcher(mut self, url: url::Url, pattern: &str) -> Response<Self> {
  348. let matcher = regex::Regex::new(pattern)
  349. .map_err(|e| HttpError::Proxy(format!("Invalid proxy pattern: {}", e)))?;
  350. self.proxy = Some(ProxyConfig {
  351. url,
  352. matcher: Some(matcher),
  353. });
  354. Ok(self)
  355. }
  356. /// Accept invalid TLS certificates (bitreq only)
  357. #[cfg(feature = "bitreq")]
  358. pub fn danger_accept_invalid_certs(mut self, accept: bool) -> Self {
  359. self.accept_invalid_certs = accept;
  360. self
  361. }
  362. /// Set a proxy URL (bitreq only)
  363. #[cfg(feature = "bitreq")]
  364. pub fn proxy(mut self, url: url::Url) -> Self {
  365. self.proxy = Some(ProxyConfig { url, matcher: None });
  366. self
  367. }
  368. /// Set a proxy URL with a host pattern matcher (bitreq only)
  369. #[cfg(feature = "bitreq")]
  370. pub fn proxy_with_matcher(mut self, url: url::Url, pattern: &str) -> Response<Self> {
  371. let matcher = regex::Regex::new(pattern)
  372. .map_err(|e| HttpError::Proxy(format!("Invalid proxy pattern: {}", e)))?;
  373. self.proxy = Some(ProxyConfig {
  374. url,
  375. matcher: Some(matcher),
  376. });
  377. Ok(self)
  378. }
  379. /// Build the HTTP client
  380. pub fn build(self) -> Response<HttpClient> {
  381. #[cfg(feature = "reqwest")]
  382. {
  383. let mut builder = reqwest::Client::builder();
  384. if let Some(proxy) = self.proxy {
  385. let proxy_url = proxy.url;
  386. if let Some(matcher) = proxy.matcher {
  387. let custom_proxy = reqwest::Proxy::custom(move |url| {
  388. url.host_str().and_then(|host| {
  389. if matcher.is_match(host) {
  390. Some(proxy_url.clone())
  391. } else {
  392. None
  393. }
  394. })
  395. });
  396. builder = builder.proxy(custom_proxy);
  397. } else {
  398. let proxy = reqwest::Proxy::all(proxy_url)
  399. .map_err(|e| HttpError::Proxy(e.to_string()))?;
  400. builder = builder.proxy(proxy);
  401. }
  402. }
  403. let client = builder
  404. .build()
  405. .map_err(|e| HttpError::Build(e.to_string()))?;
  406. Ok(HttpClient { inner: client })
  407. }
  408. #[cfg(feature = "bitreq")]
  409. {
  410. // Return error if danger_accept_invalid_certs was set
  411. if self.accept_invalid_certs {
  412. return Err(HttpError::Build(
  413. "danger_accept_invalid_certs is not supported".to_string(),
  414. ));
  415. }
  416. Ok(HttpClient {
  417. inner: Arc::new(bitreq::Client::new(10)), // Default capacity of 10
  418. proxy_config: self.proxy,
  419. })
  420. }
  421. }
  422. }
  423. /// Convenience function for simple GET requests
  424. pub async fn fetch<R: DeserializeOwned>(url: &str) -> Response<R> {
  425. HttpClient::new().fetch(url).await
  426. }
  427. #[cfg(test)]
  428. mod tests {
  429. use super::*;
  430. #[test]
  431. fn test_client_new() {
  432. let client = HttpClient::new();
  433. // Client should be constructable without panicking
  434. let _ = format!("{:?}", client);
  435. }
  436. #[test]
  437. fn test_client_default() {
  438. let client = HttpClient::default();
  439. // Default should produce a valid client
  440. let _ = format!("{:?}", client);
  441. }
  442. #[test]
  443. fn test_builder_returns_builder() {
  444. let builder = HttpClient::builder();
  445. let _ = format!("{:?}", builder);
  446. }
  447. #[test]
  448. fn test_builder_build() {
  449. let result = HttpClientBuilder::default().build();
  450. assert!(result.is_ok());
  451. }
  452. #[cfg(feature = "reqwest")]
  453. #[test]
  454. fn test_from_reqwest() {
  455. let reqwest_client = reqwest::Client::new();
  456. let client = HttpClient::from_reqwest(reqwest_client);
  457. let _ = format!("{:?}", client);
  458. }
  459. #[cfg(feature = "bitreq")]
  460. mod bitreq_tests {
  461. use super::*;
  462. #[test]
  463. fn test_builder_accept_invalid_certs_returns_error() {
  464. let result = HttpClientBuilder::default()
  465. .danger_accept_invalid_certs(true)
  466. .build();
  467. assert!(result.is_err());
  468. if let Err(HttpError::Build(msg)) = result {
  469. assert!(msg.contains("danger_accept_invalid_certs"));
  470. } else {
  471. panic!("Expected HttpError::Build");
  472. }
  473. }
  474. #[test]
  475. fn test_builder_accept_invalid_certs_false_ok() {
  476. let result = HttpClientBuilder::default()
  477. .danger_accept_invalid_certs(false)
  478. .build();
  479. assert!(result.is_ok());
  480. }
  481. #[test]
  482. fn test_builder_proxy() {
  483. let proxy_url = url::Url::parse("http://localhost:8080").expect("Valid proxy URL");
  484. let result = HttpClientBuilder::default().proxy(proxy_url).build();
  485. assert!(result.is_ok());
  486. }
  487. #[test]
  488. fn test_builder_proxy_with_valid_matcher() {
  489. let proxy_url = url::Url::parse("http://localhost:8080").expect("Valid proxy URL");
  490. let result =
  491. HttpClientBuilder::default().proxy_with_matcher(proxy_url, r".*\.example\.com$");
  492. assert!(result.is_ok());
  493. let builder = result.expect("Valid matcher should succeed");
  494. let client_result = builder.build();
  495. assert!(client_result.is_ok());
  496. }
  497. #[test]
  498. fn test_builder_proxy_with_invalid_matcher() {
  499. let proxy_url = url::Url::parse("http://localhost:8080").expect("Valid proxy URL");
  500. // Invalid regex pattern (unclosed bracket)
  501. let result = HttpClientBuilder::default().proxy_with_matcher(proxy_url, r"[invalid");
  502. assert!(result.is_err());
  503. if let Err(HttpError::Proxy(msg)) = result {
  504. assert!(msg.contains("Invalid proxy pattern"));
  505. } else {
  506. panic!("Expected HttpError::Proxy");
  507. }
  508. }
  509. }
  510. }