1 | use std::fmt; |
2 | use std::ops::Deref; |
3 | use std::sync::Arc; |
4 | use std::time::Duration; |
5 | use url::Url; |
6 | |
7 | use crate::middleware::Middleware; |
8 | use crate::pool::ConnectionPool; |
9 | use crate::proxy::Proxy; |
10 | use crate::request::Request; |
11 | use crate::resolve::{ArcResolver, StdResolver}; |
12 | use crate::stream::TlsConnector; |
13 | |
14 | #[cfg (feature = "cookies" )] |
15 | use { |
16 | crate::cookies::{CookieStoreGuard, CookieTin}, |
17 | cookie_store::CookieStore, |
18 | }; |
19 | |
20 | /// Strategy for keeping `authorization` headers during redirects. |
21 | /// |
22 | /// `Never` is the default strategy and never preserves `authorization` header in redirects. |
23 | /// `SameHost` send the authorization header in redirects only if the host of the redirect is |
24 | /// the same of the previous request, and both use the same scheme (or switch to a more secure one, i.e |
25 | /// we can redirect from `http` to `https`, but not the reverse). |
26 | #[derive (Debug, Clone, PartialEq, Eq)] |
27 | #[non_exhaustive ] |
28 | pub enum RedirectAuthHeaders { |
29 | /// Never preserve the `authorization` header on redirect. This is the default. |
30 | Never, |
31 | /// Preserve the `authorization` header when the redirect is to the same host. Both hosts must use |
32 | /// the same scheme (or switch to a more secure one, i.e we can redirect from `http` to `https`, |
33 | /// but not the reverse). |
34 | SameHost, |
35 | } |
36 | |
37 | /// Accumulates options towards building an [Agent]. |
38 | pub struct AgentBuilder { |
39 | config: AgentConfig, |
40 | try_proxy_from_env: bool, |
41 | max_idle_connections: usize, |
42 | max_idle_connections_per_host: usize, |
43 | /// Cookies saved between requests. |
44 | /// Invariant: All cookies must have a nonempty domain and path. |
45 | #[cfg (feature = "cookies" )] |
46 | cookie_store: Option<CookieStore>, |
47 | resolver: ArcResolver, |
48 | middleware: Vec<Box<dyn Middleware>>, |
49 | } |
50 | |
51 | #[derive (Clone)] |
52 | pub(crate) struct TlsConfig(Arc<dyn TlsConnector>); |
53 | |
54 | impl fmt::Debug for TlsConfig { |
55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
56 | f.debug_struct(name:"TlsConfig" ).finish() |
57 | } |
58 | } |
59 | |
60 | impl Deref for TlsConfig { |
61 | type Target = Arc<dyn TlsConnector>; |
62 | |
63 | fn deref(&self) -> &Self::Target { |
64 | &self.0 |
65 | } |
66 | } |
67 | |
68 | /// Config as built by AgentBuilder and then static for the lifetime of the Agent. |
69 | #[derive (Clone, Debug)] |
70 | pub(crate) struct AgentConfig { |
71 | pub proxy: Option<Proxy>, |
72 | pub timeout_connect: Option<Duration>, |
73 | pub timeout_read: Option<Duration>, |
74 | pub timeout_write: Option<Duration>, |
75 | pub timeout: Option<Duration>, |
76 | pub https_only: bool, |
77 | pub no_delay: bool, |
78 | pub redirects: u32, |
79 | pub redirect_auth_headers: RedirectAuthHeaders, |
80 | pub user_agent: String, |
81 | pub tls_config: TlsConfig, |
82 | } |
83 | |
84 | /// Agents keep state between requests. |
85 | /// |
86 | /// By default, no state, such as cookies, is kept between requests. |
87 | /// But by creating an agent as entry point for the request, we |
88 | /// can keep a state. |
89 | /// |
90 | /// ``` |
91 | /// # fn main() -> Result<(), ureq::Error> { |
92 | /// # ureq::is_test(true); |
93 | /// let mut agent = ureq::agent(); |
94 | /// |
95 | /// agent |
96 | /// .post("http://example.com/post/login" ) |
97 | /// .call()?; |
98 | /// |
99 | /// let secret = agent |
100 | /// .get("http://example.com/get/my-protected-page" ) |
101 | /// .call()? |
102 | /// .into_string()?; |
103 | /// |
104 | /// println!("Secret is: {}" , secret); |
105 | /// # Ok(()) |
106 | /// # } |
107 | /// ``` |
108 | /// |
109 | /// Agent uses an inner Arc, so cloning an Agent results in an instance |
110 | /// that shares the same underlying connection pool and other state. |
111 | #[derive (Debug, Clone)] |
112 | pub struct Agent { |
113 | pub(crate) config: Arc<AgentConfig>, |
114 | /// Reused agent state for repeated requests from this agent. |
115 | pub(crate) state: Arc<AgentState>, |
116 | } |
117 | |
118 | /// Container of the state |
119 | /// |
120 | /// *Internal API*. |
121 | pub(crate) struct AgentState { |
122 | /// Reused connections between requests. |
123 | pub(crate) pool: ConnectionPool, |
124 | /// Cookies saved between requests. |
125 | /// Invariant: All cookies must have a nonempty domain and path. |
126 | #[cfg (feature = "cookies" )] |
127 | pub(crate) cookie_tin: CookieTin, |
128 | pub(crate) resolver: ArcResolver, |
129 | pub(crate) middleware: Vec<Box<dyn Middleware>>, |
130 | } |
131 | |
132 | impl Agent { |
133 | /// Creates an Agent with default settings. |
134 | /// |
135 | /// Same as `AgentBuilder::new().build()`. |
136 | pub fn new() -> Self { |
137 | AgentBuilder::new().build() |
138 | } |
139 | |
140 | /// Make a request with the HTTP verb as a parameter. |
141 | /// |
142 | /// This allows making requests with verbs that don't have a dedicated |
143 | /// method. |
144 | /// |
145 | /// If you've got an already-parsed [Url], try [request_url][Agent::request_url]. |
146 | /// |
147 | /// ``` |
148 | /// # fn main() -> Result<(), ureq::Error> { |
149 | /// # ureq::is_test(true); |
150 | /// use ureq::Response; |
151 | /// let agent = ureq::agent(); |
152 | /// |
153 | /// let resp: Response = agent |
154 | /// .request("OPTIONS" , "http://example.com/" ) |
155 | /// .call()?; |
156 | /// # Ok(()) |
157 | /// # } |
158 | /// ``` |
159 | pub fn request(&self, method: &str, path: &str) -> Request { |
160 | Request::new(self.clone(), method.into(), path.into()) |
161 | } |
162 | |
163 | /// Make a request using an already-parsed [Url]. |
164 | /// |
165 | /// This is useful if you've got a parsed Url from some other source, or if |
166 | /// you want to parse the URL and then modify it before making the request. |
167 | /// If you'd just like to pass a String or a `&str`, try [request][Agent::request]. |
168 | /// |
169 | /// ``` |
170 | /// # fn main() -> Result<(), ureq::Error> { |
171 | /// # ureq::is_test(true); |
172 | /// use {url::Url, ureq::Response}; |
173 | /// let agent = ureq::agent(); |
174 | /// |
175 | /// let mut url: Url = "http://example.com/some-page" .parse()?; |
176 | /// url.set_path("/get/robots.txt" ); |
177 | /// let resp: Response = agent |
178 | /// .request_url("GET" , &url) |
179 | /// .call()?; |
180 | /// # Ok(()) |
181 | /// # } |
182 | /// ``` |
183 | pub fn request_url(&self, method: &str, url: &Url) -> Request { |
184 | Request::new(self.clone(), method.into(), url.to_string()) |
185 | } |
186 | |
187 | /// Make a GET request from this agent. |
188 | pub fn get(&self, path: &str) -> Request { |
189 | self.request("GET" , path) |
190 | } |
191 | |
192 | /// Make a HEAD request from this agent. |
193 | pub fn head(&self, path: &str) -> Request { |
194 | self.request("HEAD" , path) |
195 | } |
196 | |
197 | /// Make a PATCH request from this agent. |
198 | pub fn patch(&self, path: &str) -> Request { |
199 | self.request("PATCH" , path) |
200 | } |
201 | |
202 | /// Make a POST request from this agent. |
203 | pub fn post(&self, path: &str) -> Request { |
204 | self.request("POST" , path) |
205 | } |
206 | |
207 | /// Make a PUT request from this agent. |
208 | pub fn put(&self, path: &str) -> Request { |
209 | self.request("PUT" , path) |
210 | } |
211 | |
212 | /// Make a DELETE request from this agent. |
213 | pub fn delete(&self, path: &str) -> Request { |
214 | self.request("DELETE" , path) |
215 | } |
216 | |
217 | /// Read access to the cookie store. |
218 | /// |
219 | /// Used to persist the cookies to an external writer. |
220 | /// |
221 | /// ```no_run |
222 | /// use std::io::Write; |
223 | /// use std::fs::File; |
224 | /// |
225 | /// # fn main() -> Result<(), ureq::Error> { |
226 | /// # ureq::is_test(true); |
227 | /// let agent = ureq::agent(); |
228 | /// |
229 | /// // Cookies set by www.google.com are stored in agent. |
230 | /// agent.get("https://www.google.com/").call()?; |
231 | /// |
232 | /// // Saves (persistent) cookies |
233 | /// let mut file = File::create("cookies.json")?; |
234 | /// agent.cookie_store().save_json(&mut file).unwrap(); |
235 | /// # Ok(()) |
236 | /// # } |
237 | /// ``` |
238 | #[cfg (feature = "cookies" )] |
239 | pub fn cookie_store(&self) -> CookieStoreGuard<'_> { |
240 | self.state.cookie_tin.read_lock() |
241 | } |
242 | |
243 | pub(crate) fn weak_state(&self) -> std::sync::Weak<AgentState> { |
244 | Arc::downgrade(&self.state) |
245 | } |
246 | } |
247 | |
248 | const DEFAULT_MAX_IDLE_CONNECTIONS: usize = 100; |
249 | const DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST: usize = 1; |
250 | |
251 | impl AgentBuilder { |
252 | pub fn new() -> Self { |
253 | AgentBuilder { |
254 | config: AgentConfig { |
255 | proxy: None, |
256 | timeout_connect: Some(Duration::from_secs(30)), |
257 | timeout_read: None, |
258 | timeout_write: None, |
259 | timeout: None, |
260 | https_only: false, |
261 | no_delay: true, |
262 | redirects: 5, |
263 | redirect_auth_headers: RedirectAuthHeaders::Never, |
264 | user_agent: format!("ureq/ {}" , env!("CARGO_PKG_VERSION" )), |
265 | tls_config: TlsConfig(crate::default_tls_config()), |
266 | }, |
267 | #[cfg (feature = "proxy-from-env" )] |
268 | try_proxy_from_env: true, |
269 | #[cfg (not(feature = "proxy-from-env" ))] |
270 | try_proxy_from_env: false, |
271 | max_idle_connections: DEFAULT_MAX_IDLE_CONNECTIONS, |
272 | max_idle_connections_per_host: DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST, |
273 | resolver: StdResolver.into(), |
274 | #[cfg (feature = "cookies" )] |
275 | cookie_store: None, |
276 | middleware: vec![], |
277 | } |
278 | } |
279 | |
280 | /// Create a new agent. |
281 | // Note: This could take &self as the first argument, allowing one |
282 | // AgentBuilder to be used multiple times, except CookieStore does |
283 | // not implement clone, so we have to give ownership to the newly |
284 | // built Agent. |
285 | pub fn build(mut self) -> Agent { |
286 | if self.config.proxy.is_none() && self.try_proxy_from_env { |
287 | if let Some(proxy) = Proxy::try_from_system() { |
288 | self.config.proxy = Some(proxy); |
289 | } |
290 | } |
291 | Agent { |
292 | config: Arc::new(self.config), |
293 | state: Arc::new(AgentState { |
294 | pool: ConnectionPool::new_with_limits( |
295 | self.max_idle_connections, |
296 | self.max_idle_connections_per_host, |
297 | ), |
298 | #[cfg (feature = "cookies" )] |
299 | cookie_tin: CookieTin::new(self.cookie_store.unwrap_or_else(CookieStore::default)), |
300 | resolver: self.resolver, |
301 | middleware: self.middleware, |
302 | }), |
303 | } |
304 | } |
305 | |
306 | /// Set the proxy server to use for all connections from this Agent. |
307 | /// |
308 | /// Example: |
309 | /// ``` |
310 | /// # fn main() -> Result<(), ureq::Error> { |
311 | /// # ureq::is_test(true); |
312 | /// let proxy = ureq::Proxy::new("user:password@cool.proxy:9090" )?; |
313 | /// let agent = ureq::AgentBuilder::new() |
314 | /// .proxy(proxy) |
315 | /// .build(); |
316 | /// # Ok(()) |
317 | /// # } |
318 | /// ``` |
319 | /// |
320 | /// Adding a proxy will disable `try_proxy_from_env`. |
321 | pub fn proxy(mut self, proxy: Proxy) -> Self { |
322 | self.config.proxy = Some(proxy); |
323 | self |
324 | } |
325 | |
326 | /// Attempt to detect proxy settings from the environment, i.e. HTTP_PROXY |
327 | /// |
328 | /// The default is `false` without the `proxy-from-env` feature flag, i.e. |
329 | /// not detecting proxy from env, due to the potential security risk and to |
330 | /// maintain backward compatibility. |
331 | /// |
332 | /// If the `proxy` is set on the builder, this setting has no effect. |
333 | pub fn try_proxy_from_env(mut self, do_try: bool) -> Self { |
334 | self.try_proxy_from_env = do_try; |
335 | self |
336 | } |
337 | |
338 | /// Enforce the client to only perform HTTPS requests. |
339 | /// This setting also makes the client refuse HTTPS to HTTP redirects. |
340 | /// Default is false |
341 | /// |
342 | /// Example: |
343 | /// ``` |
344 | /// let agent = ureq::AgentBuilder::new() |
345 | /// .https_only(true) |
346 | /// .build(); |
347 | /// ``` |
348 | pub fn https_only(mut self, enforce: bool) -> Self { |
349 | self.config.https_only = enforce; |
350 | self |
351 | } |
352 | |
353 | /// Sets the maximum number of connections allowed in the connection pool. |
354 | /// By default, this is set to 100. Setting this to zero would disable |
355 | /// connection pooling. |
356 | /// |
357 | /// ``` |
358 | /// let agent = ureq::AgentBuilder::new() |
359 | /// .max_idle_connections(200) |
360 | /// .build(); |
361 | /// ``` |
362 | pub fn max_idle_connections(mut self, max: usize) -> Self { |
363 | self.max_idle_connections = max; |
364 | self |
365 | } |
366 | |
367 | /// Sets the maximum number of connections per host to keep in the |
368 | /// connection pool. By default, this is set to 1. Setting this to zero |
369 | /// would disable connection pooling. |
370 | /// |
371 | /// ``` |
372 | /// let agent = ureq::AgentBuilder::new() |
373 | /// .max_idle_connections_per_host(200) |
374 | /// .build(); |
375 | /// ``` |
376 | pub fn max_idle_connections_per_host(mut self, max: usize) -> Self { |
377 | self.max_idle_connections_per_host = max; |
378 | self |
379 | } |
380 | |
381 | /// Configures a custom resolver to be used by this agent. By default, |
382 | /// address-resolution is done by std::net::ToSocketAddrs. This allows you |
383 | /// to override that resolution with your own alternative. Useful for |
384 | /// testing and special-cases like DNS-based load balancing. |
385 | /// |
386 | /// A `Fn(&str) -> io::Result<Vec<SocketAddr>>` is a valid resolver, |
387 | /// passing a closure is a simple way to override. Note that you might need |
388 | /// explicit type `&str` on the closure argument for type inference to |
389 | /// succeed. |
390 | /// ``` |
391 | /// use std::net::ToSocketAddrs; |
392 | /// |
393 | /// let mut agent = ureq::AgentBuilder::new() |
394 | /// .resolver(|addr: &str| match addr { |
395 | /// "example.com" => Ok(vec![([127,0,0,1], 8096).into()]), |
396 | /// addr => addr.to_socket_addrs().map(Iterator::collect), |
397 | /// }) |
398 | /// .build(); |
399 | /// ``` |
400 | pub fn resolver(mut self, resolver: impl crate::Resolver + 'static) -> Self { |
401 | self.resolver = resolver.into(); |
402 | self |
403 | } |
404 | |
405 | /// Timeout for the socket connection to be successful. |
406 | /// If both this and `.timeout()` are both set, `.timeout_connect()` |
407 | /// takes precedence. |
408 | /// |
409 | /// The default is 30 seconds. |
410 | /// |
411 | /// ``` |
412 | /// use std::time::Duration; |
413 | /// # fn main() -> Result<(), ureq::Error> { |
414 | /// # ureq::is_test(true); |
415 | /// let agent = ureq::builder() |
416 | /// .timeout_connect(Duration::from_secs(1)) |
417 | /// .build(); |
418 | /// let result = agent.get("http://httpbin.org/delay/20" ).call(); |
419 | /// # Ok(()) |
420 | /// # } |
421 | /// ``` |
422 | pub fn timeout_connect(mut self, timeout: Duration) -> Self { |
423 | self.config.timeout_connect = Some(timeout); |
424 | self |
425 | } |
426 | |
427 | /// Timeout for the individual reads of the socket. |
428 | /// If both this and `.timeout()` are both set, `.timeout()` |
429 | /// takes precedence. |
430 | /// |
431 | /// The default is no timeout. In other words, requests may block forever on reads by default. |
432 | /// |
433 | /// ``` |
434 | /// use std::time::Duration; |
435 | /// # fn main() -> Result<(), ureq::Error> { |
436 | /// # ureq::is_test(true); |
437 | /// let agent = ureq::builder() |
438 | /// .timeout_read(Duration::from_secs(1)) |
439 | /// .build(); |
440 | /// let result = agent.get("http://httpbin.org/delay/20" ).call(); |
441 | /// # Ok(()) |
442 | /// # } |
443 | /// ``` |
444 | pub fn timeout_read(mut self, timeout: Duration) -> Self { |
445 | self.config.timeout_read = Some(timeout); |
446 | self |
447 | } |
448 | |
449 | /// Timeout for the individual writes to the socket. |
450 | /// If both this and `.timeout()` are both set, `.timeout()` |
451 | /// takes precedence. |
452 | /// |
453 | /// The default is no timeout. In other words, requests may block forever on writes by default. |
454 | /// |
455 | /// ``` |
456 | /// use std::time::Duration; |
457 | /// # fn main() -> Result<(), ureq::Error> { |
458 | /// # ureq::is_test(true); |
459 | /// let agent = ureq::builder() |
460 | /// .timeout_read(Duration::from_secs(1)) |
461 | /// .build(); |
462 | /// let result = agent.get("http://httpbin.org/delay/20" ).call(); |
463 | /// # Ok(()) |
464 | /// # } |
465 | /// ``` |
466 | pub fn timeout_write(mut self, timeout: Duration) -> Self { |
467 | self.config.timeout_write = Some(timeout); |
468 | self |
469 | } |
470 | |
471 | /// Timeout for the overall request, including DNS resolution, connection |
472 | /// time, redirects, and reading the response body. Slow DNS resolution |
473 | /// may cause a request to exceed the timeout, because the DNS request |
474 | /// cannot be interrupted with the available APIs. |
475 | /// |
476 | /// This takes precedence over `.timeout_read()` and `.timeout_write()`, but |
477 | /// not `.timeout_connect()`. |
478 | /// |
479 | /// ``` |
480 | /// # fn main() -> Result<(), ureq::Error> { |
481 | /// # ureq::is_test(true); |
482 | /// // wait max 1 second for whole request to complete. |
483 | /// let agent = ureq::builder() |
484 | /// .timeout(std::time::Duration::from_secs(1)) |
485 | /// .build(); |
486 | /// let result = agent.get("http://httpbin.org/delay/20" ).call(); |
487 | /// # Ok(()) |
488 | /// # } |
489 | /// ``` |
490 | pub fn timeout(mut self, timeout: Duration) -> Self { |
491 | self.config.timeout = Some(timeout); |
492 | self |
493 | } |
494 | |
495 | /// Whether no_delay will be set on the tcp socket. |
496 | /// Setting this to true disables Nagle's algorithm. |
497 | /// |
498 | /// Defaults to true. |
499 | /// |
500 | /// ``` |
501 | /// # fn main() -> Result<(), ureq::Error> { |
502 | /// # ureq::is_test(true); |
503 | /// let agent = ureq::builder() |
504 | /// .no_delay(false) |
505 | /// .build(); |
506 | /// let result = agent.get("http://httpbin.org/get" ).call(); |
507 | /// # Ok(()) |
508 | /// # } |
509 | /// ``` |
510 | pub fn no_delay(mut self, no_delay: bool) -> Self { |
511 | self.config.no_delay = no_delay; |
512 | self |
513 | } |
514 | |
515 | /// How many redirects to follow. |
516 | /// |
517 | /// Defaults to `5`. Set to `0` to avoid redirects and instead |
518 | /// get a response object with the 3xx status code. |
519 | /// |
520 | /// If the redirect count hits this limit (and it's > 0), TooManyRedirects is returned. |
521 | /// |
522 | /// WARNING: for 307 and 308 redirects, this value is ignored for methods that have a body. |
523 | /// You must handle 307 redirects yourself when sending a PUT, POST, PATCH, or DELETE request. |
524 | /// |
525 | /// ```no_run |
526 | /// # fn main() -> Result<(), ureq::Error> { |
527 | /// # ureq::is_test(true); |
528 | /// let result = ureq::builder() |
529 | /// .redirects(1) |
530 | /// .build() |
531 | /// # ; |
532 | /// # let result = ureq::agent() |
533 | /// .get("http://httpbin.org/status/301" ) |
534 | /// .call()?; |
535 | /// assert_ne!(result.status(), 301); |
536 | /// |
537 | /// let result = ureq::post("http://httpbin.org/status/307" ) |
538 | /// .send_bytes(b"some data" )?; |
539 | /// assert_eq!(result.status(), 307); |
540 | /// # Ok(()) |
541 | /// # } |
542 | /// ``` |
543 | pub fn redirects(mut self, n: u32) -> Self { |
544 | self.config.redirects = n; |
545 | self |
546 | } |
547 | |
548 | /// Set the strategy for propagation of authorization headers in redirects. |
549 | /// |
550 | /// Defaults to [`RedirectAuthHeaders::Never`]. |
551 | /// |
552 | pub fn redirect_auth_headers(mut self, v: RedirectAuthHeaders) -> Self { |
553 | self.config.redirect_auth_headers = v; |
554 | self |
555 | } |
556 | |
557 | /// The user-agent header to associate with all requests from this agent by default. |
558 | /// |
559 | /// Defaults to `ureq/[VERSION]`. You can override the user-agent on an individual request by |
560 | /// setting the `User-Agent` header when constructing the request. |
561 | /// |
562 | /// ```no_run |
563 | /// # #[cfg (feature = "json" )] |
564 | /// # fn main() -> Result<(), ureq::Error> { |
565 | /// # ureq::is_test(true); |
566 | /// let agent = ureq::builder() |
567 | /// .user_agent("ferris/1.0" ) |
568 | /// .build(); |
569 | /// |
570 | /// // Uses agent's header |
571 | /// let result: serde_json::Value = |
572 | /// agent.get("http://httpbin.org/headers" ).call()?.into_json()?; |
573 | /// assert_eq!(&result["headers" ]["User-Agent" ], "ferris/1.0" ); |
574 | /// |
575 | /// // Overrides user-agent set on the agent |
576 | /// let result: serde_json::Value = agent.get("http://httpbin.org/headers" ) |
577 | /// .set("User-Agent" , "super-ferris/2.0" ) |
578 | /// .call()?.into_json()?; |
579 | /// assert_eq!(&result["headers" ]["User-Agent" ], "super-ferris/2.0" ); |
580 | /// # Ok(()) |
581 | /// # } |
582 | /// # #[cfg (not(feature = "json" ))] |
583 | /// # fn main() {} |
584 | /// ``` |
585 | pub fn user_agent(mut self, user_agent: &str) -> Self { |
586 | self.config.user_agent = user_agent.into(); |
587 | self |
588 | } |
589 | |
590 | /// Configure TLS options for rustls to use when making HTTPS connections from this Agent. |
591 | /// |
592 | /// This overrides any previous call to tls_config or tls_connector. |
593 | /// |
594 | /// ``` |
595 | /// # fn main() -> Result<(), ureq::Error> { |
596 | /// # ureq::is_test(true); |
597 | /// use std::sync::Arc; |
598 | /// let mut root_store = rustls::RootCertStore { |
599 | /// roots: webpki_roots::TLS_SERVER_ROOTS.iter().cloned().collect(), |
600 | /// }; |
601 | /// |
602 | /// let tls_config = rustls::ClientConfig::builder() |
603 | /// .with_root_certificates(root_store) |
604 | /// .with_no_client_auth(); |
605 | /// let agent = ureq::builder() |
606 | /// .tls_config(Arc::new(tls_config)) |
607 | /// .build(); |
608 | /// # Ok(()) |
609 | /// # } |
610 | #[cfg (feature = "tls" )] |
611 | pub fn tls_config(mut self, tls_config: Arc<rustls::ClientConfig>) -> Self { |
612 | self.config.tls_config = TlsConfig(Arc::new(tls_config)); |
613 | self |
614 | } |
615 | |
616 | /// Configure TLS options for a backend other than rustls. The parameter can be a |
617 | /// any type which implements the [`TlsConnector`] trait. If you enable the native-tls |
618 | /// feature, we provide `impl TlsConnector for native_tls::TlsConnector` so you can pass |
619 | /// [`Arc<native_tls::TlsConnector>`](https://docs.rs/native-tls/0.2.7/native_tls/struct.TlsConnector.html). |
620 | /// |
621 | /// This overrides any previous call to tls_config or tls_connector. |
622 | /// |
623 | /// ``` |
624 | /// # fn main() -> Result<(), Box<dyn std::error::Error>> { |
625 | /// # ureq::is_test(true); |
626 | /// use std::sync::Arc; |
627 | /// # #[cfg (feature = "native-tls" )] |
628 | /// let tls_connector = Arc::new(native_tls::TlsConnector::new()?); |
629 | /// # #[cfg (feature = "native-tls" )] |
630 | /// let agent = ureq::builder() |
631 | /// .tls_connector(tls_connector.clone()) |
632 | /// .build(); |
633 | /// # Ok(()) |
634 | /// # } |
635 | /// ``` |
636 | pub fn tls_connector<T: TlsConnector + 'static>(mut self, tls_config: Arc<T>) -> Self { |
637 | self.config.tls_config = TlsConfig(tls_config); |
638 | self |
639 | } |
640 | |
641 | /// Provide the cookie store to be used for all requests using this agent. |
642 | /// |
643 | /// This is useful in two cases. First when there is a need to persist cookies |
644 | /// to some backing store, and second when there's a need to prepare the agent |
645 | /// with some pre-existing cookies. |
646 | /// |
647 | /// Example |
648 | /// ```no_run |
649 | /// # fn main() -> Result<(), ureq::Error> { |
650 | /// # ureq::is_test(true); |
651 | /// use cookie_store::CookieStore; |
652 | /// use std::fs::File; |
653 | /// use std::io::BufReader; |
654 | /// let file = File::open("cookies.json")?; |
655 | /// let read = BufReader::new(file); |
656 | /// |
657 | /// // Read persisted cookies from cookies.json |
658 | /// let my_store = CookieStore::load_json(read).unwrap(); |
659 | /// |
660 | /// // Cookies will be used for requests done through agent. |
661 | /// let agent = ureq::builder() |
662 | /// .cookie_store(my_store) |
663 | /// .build(); |
664 | /// # Ok(()) |
665 | /// # } |
666 | /// ``` |
667 | #[cfg (feature = "cookies" )] |
668 | pub fn cookie_store(mut self, cookie_store: CookieStore) -> Self { |
669 | self.cookie_store = Some(cookie_store); |
670 | self |
671 | } |
672 | |
673 | /// Add middleware handler to this agent. |
674 | /// |
675 | /// All requests made by the agent will use this middleware. Middleware is invoked |
676 | /// in the order they are added to the builder. |
677 | pub fn middleware(mut self, m: impl Middleware) -> Self { |
678 | self.middleware.push(Box::new(m)); |
679 | self |
680 | } |
681 | } |
682 | |
683 | #[cfg (feature = "tls" )] |
684 | #[derive (Clone)] |
685 | pub(crate) struct TLSClientConfig(pub(crate) Arc<rustls::ClientConfig>); |
686 | |
687 | #[cfg (feature = "tls" )] |
688 | impl fmt::Debug for TLSClientConfig { |
689 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
690 | f.debug_struct(name:"TLSClientConfig" ).finish() |
691 | } |
692 | } |
693 | |
694 | impl fmt::Debug for AgentBuilder { |
695 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
696 | f&mut DebugStruct<'_, '_>.debug_struct("AgentBuilder" ) |
697 | .field("config" , &self.config) |
698 | .field("max_idle_connections" , &self.max_idle_connections) |
699 | .field( |
700 | "max_idle_connections_per_host" , |
701 | &self.max_idle_connections_per_host, |
702 | ) |
703 | .field(name:"resolver" , &self.resolver) |
704 | // self.cookies missing because it's feature flagged. |
705 | // self.middleware missing because we don't want to force Debug on Middleware trait. |
706 | .finish_non_exhaustive() |
707 | } |
708 | } |
709 | |
710 | impl fmt::Debug for AgentState { |
711 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
712 | f&mut DebugStruct<'_, '_>.debug_struct("AgentState" ) |
713 | .field("pool" , &self.pool) |
714 | .field(name:"resolver" , &self.resolver) |
715 | // self.cookie_tin missing because it's feature flagged. |
716 | // self.middleware missing because we don't want to force Debug on Middleware trait. |
717 | .finish_non_exhaustive() |
718 | } |
719 | } |
720 | |
721 | #[cfg (test)] |
722 | mod tests { |
723 | use super::*; |
724 | |
725 | ///////////////////// AGENT TESTS ////////////////////////////// |
726 | |
727 | #[test ] |
728 | fn agent_implements_send_and_sync() { |
729 | let _agent: Box<dyn Send> = Box::new(AgentBuilder::new().build()); |
730 | let _agent: Box<dyn Sync> = Box::new(AgentBuilder::new().build()); |
731 | } |
732 | |
733 | #[test ] |
734 | fn agent_config_debug() { |
735 | let agent = AgentBuilder::new().build(); |
736 | let debug_format = format!(" {:?}" , agent); |
737 | assert!(debug_format.contains("Agent" )); |
738 | assert!(debug_format.contains("config:" )); |
739 | assert!(debug_format.contains("proxy:" )); |
740 | assert!(debug_format.contains("timeout_connect:" )); |
741 | assert!(debug_format.contains("timeout_read:" )); |
742 | assert!(debug_format.contains("timeout_write:" )); |
743 | assert!(debug_format.contains("timeout:" )); |
744 | assert!(debug_format.contains("https_only:" )); |
745 | assert!(debug_format.contains("no_delay:" )); |
746 | assert!(debug_format.contains("redirects:" )); |
747 | assert!(debug_format.contains("redirect_auth_headers:" )); |
748 | assert!(debug_format.contains("user_agent:" )); |
749 | assert!(debug_format.contains("tls_config:" )); |
750 | assert!(debug_format.contains("state:" )); |
751 | assert!(debug_format.contains("pool:" )); |
752 | } |
753 | } |
754 | |