Skip to content

Commit f1bb957

Browse files
authored
fix: reduce expensive clones of BaseSettings (#186)
Use `Arc`'s clone-on-write capabilities to only clone data when absolutely necessary.
1 parent 0b4b33c commit f1bb957

File tree

4 files changed

+179
-103
lines changed

4 files changed

+179
-103
lines changed

src/request/builder.rs

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::borrow::Borrow;
22
use std::convert::{From, TryInto};
33
use std::fs;
44
use std::str;
5+
use std::sync::Arc;
56
use std::time::Duration;
67

78
use base64::Engine;
@@ -38,7 +39,10 @@ pub struct RequestBuilder<B = body::Empty> {
3839
url: Url,
3940
method: Method,
4041
body: B,
41-
base_settings: BaseSettings,
42+
// Headers are always modified by [Self::try_prepare], it's cheaper to keep them
43+
// separate than to clone all of BaseSettings through [Arc::make_mut].
44+
headers: HeaderMap,
45+
base_settings: Arc<BaseSettings>,
4246
}
4347

4448
impl RequestBuilder {
@@ -61,17 +65,17 @@ impl RequestBuilder {
6165
where
6266
U: AsRef<str>,
6367
{
64-
Self::try_with_settings(method, base_url, BaseSettings::default())
68+
Self::try_with_settings(method, base_url, Arc::new(BaseSettings::default()))
6569
}
6670

67-
pub(crate) fn with_settings<U>(method: Method, base_url: U, base_settings: BaseSettings) -> Self
71+
pub(crate) fn with_settings<U>(method: Method, base_url: U, base_settings: Arc<BaseSettings>) -> Self
6872
where
6973
U: AsRef<str>,
7074
{
7175
Self::try_with_settings(method, base_url, base_settings).expect("invalid url or method")
7276
}
7377

74-
pub(crate) fn try_with_settings<U>(method: Method, base_url: U, base_settings: BaseSettings) -> Result<Self>
78+
pub(crate) fn try_with_settings<U>(method: Method, base_url: U, base_settings: Arc<BaseSettings>) -> Result<Self>
7579
where
7680
U: AsRef<str>,
7781
{
@@ -85,6 +89,7 @@ impl RequestBuilder {
8589
url,
8690
method,
8791
body: body::Empty,
92+
headers: base_settings.headers.clone(),
8893
base_settings,
8994
})
9095
}
@@ -161,6 +166,7 @@ impl<B> RequestBuilder<B> {
161166
url: self.url,
162167
method: self.method,
163168
body,
169+
headers: self.headers,
164170
base_settings: self.base_settings,
165171
}
166172
}
@@ -169,8 +175,7 @@ impl<B> RequestBuilder<B> {
169175
///
170176
/// If the `Content-Type` header is unset, it will be set to `text/plain` and the charset to UTF-8.
171177
pub fn text<B1: AsRef<str>>(mut self, body: B1) -> RequestBuilder<body::Text<B1>> {
172-
self.base_settings
173-
.headers
178+
self.headers
174179
.entry(http::header::CONTENT_TYPE)
175180
.or_insert(HeaderValue::from_static("text/plain; charset=utf-8"));
176181
self.body(body::Text(body))
@@ -180,8 +185,7 @@ impl<B> RequestBuilder<B> {
180185
///
181186
/// If the `Content-Type` header is unset, it will be set to `application/octet-stream`.
182187
pub fn bytes<B1: AsRef<[u8]>>(mut self, body: B1) -> RequestBuilder<body::Bytes<B1>> {
183-
self.base_settings
184-
.headers
188+
self.headers
185189
.entry(http::header::CONTENT_TYPE)
186190
.or_insert(HeaderValue::from_static("application/octet-stream"));
187191
self.body(body::Bytes(body))
@@ -191,8 +195,7 @@ impl<B> RequestBuilder<B> {
191195
///
192196
/// If the `Content-Type` header is unset, it will be set to `application/octet-stream`.
193197
pub fn file(mut self, body: fs::File) -> RequestBuilder<body::File> {
194-
self.base_settings
195-
.headers
198+
self.headers
196199
.entry(http::header::CONTENT_TYPE)
197200
.or_insert(HeaderValue::from_static("application/octet-stream"));
198201
self.body(body::File(body))
@@ -204,8 +207,7 @@ impl<B> RequestBuilder<B> {
204207
#[cfg(feature = "json")]
205208
pub fn json<T: serde::Serialize>(mut self, value: &T) -> Result<RequestBuilder<body::Bytes<Vec<u8>>>> {
206209
let body = serde_json::to_vec(value)?;
207-
self.base_settings
208-
.headers
210+
self.headers
209211
.entry(http::header::CONTENT_TYPE)
210212
.or_insert(HeaderValue::from_static("application/json; charset=utf-8"));
211213
Ok(self.body(body::Bytes(body)))
@@ -216,8 +218,7 @@ impl<B> RequestBuilder<B> {
216218
/// If the `Content-Type` header is unset, it will be set to `application/json` and the charset to UTF-8.
217219
#[cfg(feature = "json")]
218220
pub fn json_streaming<T: serde::Serialize>(mut self, value: T) -> RequestBuilder<body::Json<T>> {
219-
self.base_settings
220-
.headers
221+
self.headers
221222
.entry(http::header::CONTENT_TYPE)
222223
.or_insert(HeaderValue::from_static("application/json; charset=utf-8"));
223224
self.body(body::Json(value))
@@ -229,13 +230,22 @@ impl<B> RequestBuilder<B> {
229230
#[cfg(feature = "form")]
230231
pub fn form<T: serde::Serialize>(mut self, value: &T) -> Result<RequestBuilder<body::Bytes<Vec<u8>>>> {
231232
let body = serde_urlencoded::to_string(value)?.into_bytes();
232-
self.base_settings
233-
.headers
233+
self.headers
234234
.entry(http::header::CONTENT_TYPE)
235235
.or_insert(HeaderValue::from_static("application/x-www-form-urlencoded"));
236236
Ok(self.body(body::Bytes(body)))
237237
}
238238

239+
/// Get a mutable reference to headers.
240+
pub fn headers(&self) -> &HeaderMap {
241+
&self.headers
242+
}
243+
244+
/// Get a mutable reference to headers.
245+
pub fn headers_mut(&mut self) -> &mut HeaderMap {
246+
&mut self.headers
247+
}
248+
239249
//
240250
// Settings
241251
//
@@ -281,7 +291,7 @@ impl<B> RequestBuilder<B> {
281291
V: TryInto<HeaderValue>,
282292
Error: From<V::Error>,
283293
{
284-
header_insert(&mut self.base_settings.headers, header, value)?;
294+
header_insert(&mut self.headers, header, value)?;
285295
Ok(self)
286296
}
287297

@@ -294,68 +304,63 @@ impl<B> RequestBuilder<B> {
294304
V: TryInto<HeaderValue>,
295305
Error: From<V::Error>,
296306
{
297-
header_append(&mut self.base_settings.headers, header, value)?;
307+
header_append(&mut self.headers, header, value)?;
298308
Ok(self)
299309
}
300310

301311
/// Set the maximum number of headers accepted in responses to this request.
302312
///
303313
/// The default is 100.
304314
pub fn max_headers(mut self, max_headers: usize) -> Self {
305-
self.base_settings.max_headers = max_headers;
315+
self.base_settings.set_max_headers(max_headers);
306316
self
307317
}
308318

309-
/// Get a mutable reference to headers.
310-
pub fn headers_mut(&mut self) -> &mut HeaderMap {
311-
&mut self.base_settings.headers
312-
}
313-
314319
/// Set the maximum number of redirections this request can perform.
315320
///
316321
/// The default is 5.
317322
pub fn max_redirections(mut self, max_redirections: u32) -> Self {
318-
self.base_settings.max_redirections = max_redirections;
323+
self.base_settings.set_max_redirections(max_redirections);
319324
self
320325
}
321326

322327
/// Sets if this request should follow redirects, 3xx codes.
323328
///
324329
/// This value defaults to true.
325330
pub fn follow_redirects(mut self, follow_redirects: bool) -> Self {
326-
self.base_settings.follow_redirects = follow_redirects;
331+
self.base_settings.set_follow_redirects(follow_redirects);
327332
self
328333
}
329334

330335
/// Sets a connect timeout for this request.
331336
///
332337
/// The default is 30 seconds.
333-
pub fn connect_timeout(mut self, duration: Duration) -> Self {
334-
self.base_settings.connect_timeout = duration;
338+
pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
339+
self.base_settings.set_connect_timeout(connect_timeout);
335340
self
336341
}
337342

338343
/// Sets a read timeout for this request.
339344
///
340345
/// The default is 30 seconds.
341-
pub fn read_timeout(mut self, duration: Duration) -> Self {
342-
self.base_settings.read_timeout = duration;
346+
pub fn read_timeout(mut self, read_timeout: Duration) -> Self {
347+
self.base_settings.set_read_tmeout(read_timeout);
343348
self
344349
}
345350

346351
/// Sets a timeout for the whole request.
347352
///
348353
/// Applies after a TCP connection is established. Defaults to no timeout.
349-
pub fn timeout(mut self, duration: Duration) -> Self {
350-
self.base_settings.timeout = Some(duration);
354+
pub fn timeout(mut self, timeout: Duration) -> Self {
355+
self.base_settings.set_timeout(Some(timeout));
351356
self
352357
}
353358

354359
/// Sets the proxy settigns for this request.
355360
///
356361
/// If left untouched, the defaults are to use system proxy settings found in environment variables.
357-
pub fn proxy_settings(mut self, settings: ProxySettings) -> Self {
358-
self.base_settings.proxy_settings = settings;
362+
pub fn proxy_settings(mut self, proxy_settings: ProxySettings) -> Self {
363+
self.base_settings.set_proxy_settings(proxy_settings);
359364
self
360365
}
361366

@@ -365,7 +370,7 @@ impl<B> RequestBuilder<B> {
365370
/// This value defaults to `None`, in which case ISO-8859-1 is used.
366371
#[cfg(feature = "charsets")]
367372
pub fn default_charset(mut self, default_charset: Option<Charset>) -> Self {
368-
self.base_settings.default_charset = default_charset;
373+
self.base_settings.set_default_charset(default_charset);
369374
self
370375
}
371376

@@ -375,7 +380,7 @@ impl<B> RequestBuilder<B> {
375380
/// compression, the server might choose not to compress the content.
376381
#[cfg(feature = "flate2")]
377382
pub fn allow_compression(mut self, allow_compression: bool) -> Self {
378-
self.base_settings.allow_compression = allow_compression;
383+
self.base_settings.set_allow_compression(allow_compression);
379384
self
380385
}
381386

@@ -391,7 +396,7 @@ impl<B> RequestBuilder<B> {
391396
/// If you are using self signed certificates, it is much safer to add their root CA
392397
/// to the list of trusted root CAs by your system.
393398
pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> Self {
394-
self.base_settings.accept_invalid_certs = accept_invalid_certs;
399+
self.base_settings.set_accept_invalid_certs(accept_invalid_certs);
395400
self
396401
}
397402

@@ -403,13 +408,14 @@ impl<B> RequestBuilder<B> {
403408
/// Use this setting with care. This will accept TLS certificates that do not match
404409
/// the hostname.
405410
pub fn danger_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
406-
self.base_settings.accept_invalid_hostnames = accept_invalid_hostnames;
411+
self.base_settings
412+
.set_accept_invalid_hostnames(accept_invalid_hostnames);
407413
self
408414
}
409415

410416
/// Adds a root certificate that will be trusted.
411417
pub fn add_root_certificate(mut self, cert: Certificate) -> Self {
412-
self.base_settings.root_certificates.0.push(cert);
418+
self.base_settings.add_root_certificate(cert);
413419
self
414420
}
415421
}
@@ -429,27 +435,29 @@ impl<B: Body> RequestBuilder<B> {
429435
url: self.url,
430436
method: self.method,
431437
body: self.body,
438+
headers: self.headers,
432439
base_settings: self.base_settings,
433440
};
434-
435-
header_insert(&mut prepped.base_settings.headers, CONNECTION, "close")?;
436441
prepped.set_compression()?;
442+
let headers = &mut prepped.headers;
443+
444+
header_insert(headers, CONNECTION, "close")?;
437445
match prepped.body.kind()? {
438446
BodyKind::Empty => (),
439447
BodyKind::KnownLength(len) => {
440-
header_insert(&mut prepped.base_settings.headers, CONTENT_LENGTH, len)?;
448+
header_insert(headers, CONTENT_LENGTH, len)?;
441449
}
442450
BodyKind::Chunked => {
443-
header_insert(&mut prepped.base_settings.headers, TRANSFER_ENCODING, "chunked")?;
451+
header_insert(headers, TRANSFER_ENCODING, "chunked")?;
444452
}
445453
}
446454

447455
if let Some(typ) = prepped.body.content_type()? {
448-
header_insert(&mut prepped.base_settings.headers, CONTENT_TYPE, typ)?;
456+
header_insert(headers, CONTENT_TYPE, typ)?;
449457
}
450458

451-
header_insert_if_missing(&mut prepped.base_settings.headers, ACCEPT, "*/*")?;
452-
header_insert_if_missing(&mut prepped.base_settings.headers, USER_AGENT, DEFAULT_USER_AGENT)?;
459+
header_insert_if_missing(headers, ACCEPT, "*/*")?;
460+
header_insert_if_missing(headers, USER_AGENT, DEFAULT_USER_AGENT)?;
453461

454462
Ok(prepped)
455463
}
@@ -489,7 +497,7 @@ impl<B> RequestInspector<'_, B> {
489497

490498
/// Acess the current headers
491499
pub fn headers(&self) -> &HeaderMap {
492-
&self.0.base_settings.headers
500+
&self.0.headers
493501
}
494502
}
495503

0 commit comments

Comments
 (0)