[go: up one dir, main page]

ureq/
agent.rs

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