ureq/
agent.rs

1use std::convert::TryFrom;
2use std::fmt;
3use std::sync::Arc;
4
5use http::{Method, Request, Response, Uri};
6use ureq_proto::BodyMode;
7
8use crate::body::Body;
9use crate::config::typestate::{AgentScope, HttpCrateScope};
10use crate::config::{Config, ConfigBuilder, RequestLevelConfig};
11use crate::http;
12use crate::middleware::MiddlewareNext;
13use crate::pool::ConnectionPool;
14use crate::request::ForceSendBody;
15use crate::resolver::{DefaultResolver, Resolver};
16use crate::send_body::AsSendBody;
17use crate::transport::{boxed_connector, Connector, DefaultConnector, Transport};
18use crate::unversioned::transport::{ConnectionDetails, RunConnector};
19use crate::{Error, RequestBuilder, SendBody};
20use crate::{WithBody, WithoutBody};
21
22/// Agents keep state between requests.
23///
24/// By default, no state, such as cookies, is kept between requests.
25/// But by creating an agent as entry point for the request, we
26/// can keep a state.
27///
28/// # Example
29///
30/// ```no_run
31/// let mut agent = ureq::agent();
32///
33/// agent
34///     .post("http://example.com/post/login")
35///     .send(b"my password")?;
36///
37/// let secret = agent
38///     .get("http://example.com/get/my-protected-page")
39///     .call()?
40///     .body_mut()
41///     .read_to_string()?;
42///
43///   println!("Secret is: {}", secret);
44/// # Ok::<_, ureq::Error>(())
45/// ```
46///
47/// # About threads and cloning
48///
49/// Agent uses inner [`Arc`]. Cloning an Agent results in an instance
50/// that shares the same underlying connection pool and other state.
51///
52/// The connection pool contains an inner [`Mutex`][std::sync::Mutex] which is (briefly)
53/// held when borrowing a pooled connection, or returning a connection to the pool.
54///
55/// All request functions in ureq have a signature similar to this:
56///
57/// ```
58/// # use ureq::{http, Body, AsSendBody, Error};
59/// fn run(request: http::Request<impl AsSendBody>) -> Result<http::Response<Body>, Error> {
60///     // <something>
61/// # todo!()
62/// }
63/// ```
64///
65/// It follows that:
66///
67/// * An Agent is borrowed for the duration of:
68///     1. Sending the request header ([`http::Request`])
69///     2. Sending the request body ([`SendBody`])
70///     3. Receiving the response header ([`http::Response`])
71/// * The [`Body`] of the response is not bound to the lifetime of the Agent.
72///
73/// A response [`Body`] can be streamed (for instance via [`Body::into_reader()`]). The [`Body`]
74/// implements [`Send`], which means it's possible to read the response body on another thread than
75/// the one that run the request. Behind the scenes, the [`Body`] retains the connection to the remote
76/// server and it is returned to the agent's pool, once the Body instance (or reader) is dropped.
77///
78/// There is an asymmetry in that sending a request body will borrow the Agent instance, while receiving
79/// the response body does not. This inconvenience is somewhat mitigated by that [`Agent::run()`] (or
80/// going via the methods such as [`Agent::get()`]), borrows `&self`, i.e. not exclusive `mut` borrows.
81///
82/// That cloning the agent shares the connection pool is considered a feature. It is often useful to
83/// retain a single pool for the entire process, while dispatching requests from different threads.
84/// And if we want separate pools, we can create multiple agents via one of the constructors
85/// (such as [`Agent::new_with_config()`]).
86///
87/// Note that both [`Config::clone()`] and [`Agent::clone()`] are  "cheap" meaning they should not
88/// incur any heap allocation.
89#[derive(Clone)]
90pub struct Agent {
91    pub(crate) config: Arc<Config>,
92    pub(crate) pool: Arc<ConnectionPool>,
93    pub(crate) resolver: Arc<dyn Resolver>,
94
95    #[cfg(feature = "cookies")]
96    pub(crate) jar: Arc<crate::cookies::SharedCookieJar>,
97
98    pub(crate) run_connector: Arc<RunConnector>,
99}
100
101impl Agent {
102    /// Creates an agent with defaults.
103    pub fn new_with_defaults() -> Self {
104        Self::with_parts_inner(
105            Config::default(),
106            Box::new(DefaultConnector::default()),
107            DefaultResolver::default(),
108        )
109    }
110
111    /// Creates an agent with config.
112    pub fn new_with_config(config: Config) -> Self {
113        Self::with_parts_inner(
114            config,
115            Box::new(DefaultConnector::default()),
116            DefaultResolver::default(),
117        )
118    }
119
120    /// Shortcut to reach a [`ConfigBuilder`]
121    ///
122    /// This is the same as doing [`Config::builder()`].
123    pub fn config_builder() -> ConfigBuilder<AgentScope> {
124        Config::builder()
125    }
126
127    /// Creates an agent with a bespoke transport and resolver.
128    ///
129    /// _This is low level API that isn't for regular use of ureq._
130    pub fn with_parts(config: Config, connector: impl Connector, resolver: impl Resolver) -> Self {
131        let boxed = boxed_connector(connector);
132        Self::with_parts_inner(config, boxed, resolver)
133    }
134
135    /// Inner helper to avoid additional boxing of the [`DefaultConnector`].
136    fn with_parts_inner(
137        config: Config,
138        connector: Box<dyn Connector<(), Out = Box<dyn Transport>>>,
139        resolver: impl Resolver,
140    ) -> Self {
141        let pool = Arc::new(ConnectionPool::new(connector, &config));
142
143        let run_connector = {
144            let pool = pool.clone();
145            Arc::new(move |details: &ConnectionDetails| pool.run_connector(details))
146        };
147
148        Agent {
149            config: Arc::new(config),
150            pool,
151            resolver: Arc::new(resolver),
152
153            #[cfg(feature = "cookies")]
154            jar: Arc::new(crate::cookies::SharedCookieJar::new()),
155
156            run_connector,
157        }
158    }
159
160    /// Access the shared cookie jar.
161    ///
162    /// Used to persist and manipulate the cookies. The jar is shared between
163    /// all clones of the same [`Agent`], meaning you must drop the CookieJar
164    /// before using the agent, or end up with a deadlock.
165    ///
166    /// ```rust
167    /// # #[cfg(feature = "json")]
168    /// # fn no_run() -> Result<(), ureq::Error> {
169    /// use std::io::Write;
170    /// use std::fs::File;
171    ///
172    /// let agent = ureq::agent();
173    ///
174    /// // Cookies set by www.google.com are stored in agent.
175    /// agent.get("https://www.google.com/").call()?;
176    ///
177    /// // Saves (persistent) cookies
178    /// let mut file = File::create("cookies.json")?;
179    /// let jar = agent.cookie_jar_lock();
180    ///
181    /// jar.save_json(&mut file)?;
182    ///
183    /// // Release the cookie jar to use agents again.
184    /// jar.release();
185    ///
186    /// # Ok(())
187    /// # }
188    /// ```
189    #[cfg(feature = "cookies")]
190    pub fn cookie_jar_lock(&self) -> crate::cookies::CookieJar<'_> {
191        self.jar.lock()
192    }
193
194    /// Run a [`http::Request<impl AsSendBody>`].
195    ///
196    /// Used to execute http crate [`http::Request`] directly on this agent.
197    ///
198    /// # Example
199    ///
200    /// ```
201    /// use ureq::{http, Agent};
202    ///
203    /// let agent: Agent = Agent::new_with_defaults();
204    ///
205    /// let mut request =
206    ///     http::Request::get("http://httpbin.org/get")
207    ///     .body(())?;
208    ///
209    /// let body = agent.run(request)?
210    ///     .body_mut()
211    ///     .read_to_string()?;
212    /// # Ok::<(), ureq::Error>(())
213    /// ```
214    pub fn run(&self, request: Request<impl AsSendBody>) -> Result<Response<Body>, Error> {
215        let (parts, mut body) = request.into_parts();
216        let mut body = body.as_body();
217        let mut request = Request::from_parts(parts, ());
218
219        // When using the http-crate API we cannot enforce the correctness of
220        // Method vs Body combos. This also solves a problem where we can't
221        // determine if a non-standard method is supposed to have a body such
222        // as for WebDAV PROPFIND.
223        let has_body = !matches!(body.body_mode(), Ok(BodyMode::NoBody));
224        if has_body {
225            request.extensions_mut().insert(ForceSendBody);
226        }
227
228        self.run_via_middleware(request, body)
229    }
230
231    pub(crate) fn run_via_middleware(
232        &self,
233        request: Request<()>,
234        body: SendBody,
235    ) -> Result<Response<Body>, Error> {
236        let (parts, _) = request.into_parts();
237        let request = http::Request::from_parts(parts, body);
238
239        let next = MiddlewareNext::new(self);
240        next.handle(request)
241    }
242
243    /// Get the config for this agent.
244    pub fn config(&self) -> &Config {
245        &self.config
246    }
247
248    /// Alter the configuration for an http crate request.
249    ///
250    /// Notice: It's an error to configure a [`http::Request`] using
251    /// one instance of [`Agent`] and run using another instance. The
252    /// library does not currently detect this situation, but it is
253    /// not considered a breaking change if this is enforced in
254    /// the future.
255    pub fn configure_request<S: AsSendBody>(
256        &self,
257        mut request: Request<S>,
258    ) -> ConfigBuilder<HttpCrateScope<S>> {
259        let exts = request.extensions_mut();
260
261        if exts.get::<RequestLevelConfig>().is_none() {
262            exts.insert(self.new_request_level_config());
263        }
264
265        ConfigBuilder(HttpCrateScope(request))
266    }
267
268    pub(crate) fn new_request_level_config(&self) -> RequestLevelConfig {
269        RequestLevelConfig(self.config.as_ref().clone())
270    }
271
272    /// Make a GET request using this agent.
273    #[must_use]
274    pub fn get<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
275    where
276        Uri: TryFrom<T>,
277        <Uri as TryFrom<T>>::Error: Into<http::Error>,
278    {
279        RequestBuilder::<WithoutBody>::new(self.clone(), Method::GET, uri)
280    }
281
282    /// Make a POST request using this agent.
283    #[must_use]
284    pub fn post<T>(&self, uri: T) -> RequestBuilder<WithBody>
285    where
286        Uri: TryFrom<T>,
287        <Uri as TryFrom<T>>::Error: Into<http::Error>,
288    {
289        RequestBuilder::<WithBody>::new(self.clone(), Method::POST, uri)
290    }
291
292    /// Make a PUT request using this agent.
293    #[must_use]
294    pub fn put<T>(&self, uri: T) -> RequestBuilder<WithBody>
295    where
296        Uri: TryFrom<T>,
297        <Uri as TryFrom<T>>::Error: Into<http::Error>,
298    {
299        RequestBuilder::<WithBody>::new(self.clone(), Method::PUT, uri)
300    }
301
302    /// Make a DELETE request using this agent.
303    #[must_use]
304    pub fn delete<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
305    where
306        Uri: TryFrom<T>,
307        <Uri as TryFrom<T>>::Error: Into<http::Error>,
308    {
309        RequestBuilder::<WithoutBody>::new(self.clone(), Method::DELETE, uri)
310    }
311
312    /// Make a HEAD request using this agent.
313    #[must_use]
314    pub fn head<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
315    where
316        Uri: TryFrom<T>,
317        <Uri as TryFrom<T>>::Error: Into<http::Error>,
318    {
319        RequestBuilder::<WithoutBody>::new(self.clone(), Method::HEAD, uri)
320    }
321
322    /// Make an OPTIONS request using this agent.
323    #[must_use]
324    pub fn options<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
325    where
326        Uri: TryFrom<T>,
327        <Uri as TryFrom<T>>::Error: Into<http::Error>,
328    {
329        RequestBuilder::<WithoutBody>::new(self.clone(), Method::OPTIONS, uri)
330    }
331
332    /// Make a CONNECT request using this agent.
333    #[must_use]
334    pub fn connect<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
335    where
336        Uri: TryFrom<T>,
337        <Uri as TryFrom<T>>::Error: Into<http::Error>,
338    {
339        RequestBuilder::<WithoutBody>::new(self.clone(), Method::CONNECT, uri)
340    }
341
342    /// Make a PATCH request using this agent.
343    #[must_use]
344    pub fn patch<T>(&self, uri: T) -> RequestBuilder<WithBody>
345    where
346        Uri: TryFrom<T>,
347        <Uri as TryFrom<T>>::Error: Into<http::Error>,
348    {
349        RequestBuilder::<WithBody>::new(self.clone(), Method::PATCH, uri)
350    }
351
352    /// Make a TRACE request using this agent.
353    #[must_use]
354    pub fn trace<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
355    where
356        Uri: TryFrom<T>,
357        <Uri as TryFrom<T>>::Error: Into<http::Error>,
358    {
359        RequestBuilder::<WithoutBody>::new(self.clone(), Method::TRACE, uri)
360    }
361}
362
363impl From<Config> for Agent {
364    fn from(value: Config) -> Self {
365        Agent::new_with_config(value)
366    }
367}
368
369impl fmt::Debug for Agent {
370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371        let mut dbg = f.debug_struct("Agent");
372
373        dbg.field("config", &self.config)
374            .field("pool", &self.pool)
375            .field("resolver", &self.resolver);
376
377        #[cfg(feature = "cookies")]
378        {
379            dbg.field("jar", &self.jar);
380        }
381
382        dbg.finish()
383    }
384}
385
386#[cfg(test)]
387impl Agent {
388    /// Exposed for testing the pool count.
389    pub fn pool_count(&self) -> usize {
390        self.pool.pool_count()
391    }
392}
393
394#[cfg(test)]
395mod test {
396    use super::*;
397    use assert_no_alloc::*;
398
399    #[test]
400    fn agent_clone_does_not_allocate() {
401        let a = Agent::new_with_defaults();
402        assert_no_alloc(|| a.clone());
403    }
404}