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}