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}