API changes for 2.0

* Remove Request::build
* All mutations on Request follow builder pattern

The previous `build()` on request was necessary because mutating
functions did not follow a proper builder pattern (taking `&mut self`
instead of `mut self`). With a proper builder pattern, the need for
`.build()` goes away.

* All Request body and call methods consume self

Anything which "executes" the request will now consume the `Request`
to produce a `Result<Response>`.

* Move all config from request to agent builder

Timeouts, redirect config, proxy settings and TLS config are now on
`AgentBuilder`.

* Rename max_pool_connections -> max_idle_connections
* Rename max_pool_connections_per_host ->  max_idle_connections_per_host

Consistent internal and external naming.

* Introduce new AgentConfig for static config created by builder.

`Agent` can be seen as having two parts. Static config and a mutable
shared state between all states. The static config goes into
`AgentConfig` and the mutable shared state into `AgentState`.

* Replace all use of `Default` for `new`.

Deriving or implementing `Default` makes for a secondary instantiation
API.  It is useful in some cases, but gets very confusing when there
is both `new` _and_ a `Default`. It's especially devious for derived
values where a reasonable default is not `0`, `false` or `None`.

* Remove feature native_tls, we want only native rustls.

This feature made for very clunky handling throughout the code. From a
security point of view, it's better to stick with one single TLS API.
Rustls recently got an official audit (very positive).

https://github.com/ctz/rustls/tree/master/audit

Rustls deliberately omits support for older, insecure TLS such as TLS
1.1 or RC4. This might be a problem for a user of ureq, but on balance
not considered important enough to keep native_tls.

* Remove auth and support for basic auth.

The API just wasn't enough. A future reintroduction should at least
also provide a `Bearer` mechanism and possibly more.

* Rename jar -> cookie_store
* Rename jar -> cookie_tin

Just make some field names sync up with the type.

* Drop "cookies" as default feature

The need for handling cookies is probably rare, let's not enable it by
default.

* Change all feature checks for "cookie" to "cookies"

The outward facing feature is "cookies" and I think it's better form
that the code uses the official feature name instead of the optional
library "cookies".

* Keep `set` on Agent level as well as AgentBuilder.

The idea is that an auth exchange might result in a header that need
to be set _after_ the agent has been built.
This commit is contained in:
Martin Algesten
2020-10-25 11:08:50 +01:00
parent 703ca41960
commit 1369c32351
24 changed files with 398 additions and 647 deletions

View File

@@ -9,8 +9,8 @@ use crate::Proxy;
use url::Url;
const DEFAULT_MAX_IDLE_CONNECTIONS: usize = 100;
const DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST: usize = 1;
pub const DEFAULT_MAX_IDLE_CONNECTIONS: usize = 100;
pub const DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST: usize = 1;
/// Holder of recycled connections.
///
@@ -74,25 +74,23 @@ fn remove_last_match(list: &mut VecDeque<PoolKey>, key: &PoolKey) -> Option<Pool
}
}
impl Default for ConnectionPool {
fn default() -> Self {
Self {
max_idle_connections: DEFAULT_MAX_IDLE_CONNECTIONS,
max_idle_connections_per_host: DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
inner: Mutex::new(Inner {
recycle: HashMap::default(),
lru: VecDeque::default(),
}),
}
}
}
impl ConnectionPool {
pub(crate) fn new(max_idle_connections: usize, max_idle_connections_per_host: usize) -> Self {
#[cfg(test)]
pub(crate) fn new() -> Self {
Self::new_with_limits(
DEFAULT_MAX_IDLE_CONNECTIONS,
DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
)
}
pub(crate) fn new_with_limits(
max_idle_connections: usize,
max_idle_connections_per_host: usize,
) -> Self {
ConnectionPool {
inner: Mutex::new(Inner {
recycle: HashMap::default(),
lru: VecDeque::default(),
recycle: HashMap::new(),
lru: VecDeque::new(),
}),
max_idle_connections,
max_idle_connections_per_host,
@@ -105,7 +103,7 @@ impl ConnectionPool {
}
/// How the unit::connect tries to get a pooled connection.
pub fn try_get_connection(&self, url: &Url, proxy: &Option<Proxy>) -> Option<Stream> {
pub fn try_get_connection(&self, url: &Url, proxy: Option<Proxy>) -> Option<Stream> {
let key = PoolKey::new(url, proxy);
self.remove(&key)
}
@@ -211,13 +209,13 @@ impl fmt::Debug for PoolKey {
}
impl PoolKey {
fn new(url: &Url, proxy: &Option<Proxy>) -> Self {
fn new(url: &Url, proxy: Option<Proxy>) -> Self {
let port = url.port_or_known_default();
PoolKey {
scheme: url.scheme().to_string(),
hostname: url.host_str().unwrap_or("").to_string(),
port,
proxy: proxy.clone(),
proxy: proxy,
}
}
}
@@ -225,7 +223,7 @@ impl PoolKey {
#[test]
fn poolkey_new() {
// Test that PoolKey::new() does not panic on unrecognized schemes.
PoolKey::new(&Url::parse("zzz:///example.com").unwrap(), &None);
PoolKey::new(&Url::parse("zzz:///example.com").unwrap(), None);
}
#[test]
@@ -233,7 +231,7 @@ fn pool_connections_limit() {
// Test inserting connections with different keys into the pool,
// filling and draining it. The pool should evict earlier connections
// when the connection limit is reached.
let pool = ConnectionPool::default();
let pool = ConnectionPool::new();
let hostnames = (0..DEFAULT_MAX_IDLE_CONNECTIONS * 2).map(|i| format!("{}.example", i));
let poolkeys = hostnames.map(|hostname| PoolKey {
scheme: "https".to_string(),
@@ -258,7 +256,7 @@ fn pool_per_host_connections_limit() {
// Test inserting connections with the same key into the pool,
// filling and draining it. The pool should evict earlier connections
// when the per-host connection limit is reached.
let pool = ConnectionPool::default();
let pool = ConnectionPool::new();
let poolkey = PoolKey {
scheme: "https".to_string(),
hostname: "example.com".to_string(),
@@ -285,17 +283,17 @@ fn pool_per_host_connections_limit() {
fn pool_checks_proxy() {
// Test inserting different poolkeys with same address but different proxies.
// Each insertion should result in an additional entry in the pool.
let pool = ConnectionPool::default();
let pool = ConnectionPool::new();
let url = Url::parse("zzz:///example.com").unwrap();
pool.add(
PoolKey::new(&url, &None),
PoolKey::new(&url, None),
Stream::Cursor(std::io::Cursor::new(vec![])),
);
assert_eq!(pool.len(), 1);
pool.add(
PoolKey::new(&url, &Some(Proxy::new("localhost:9999").unwrap())),
PoolKey::new(&url, Some(Proxy::new("localhost:9999").unwrap())),
Stream::Cursor(std::io::Cursor::new(vec![])),
);
assert_eq!(pool.len(), 2);
@@ -303,7 +301,7 @@ fn pool_checks_proxy() {
pool.add(
PoolKey::new(
&url,
&Some(Proxy::new("user:password@localhost:9999").unwrap()),
Some(Proxy::new("user:password@localhost:9999").unwrap()),
),
Stream::Cursor(std::io::Cursor::new(vec![])),
);
@@ -343,7 +341,7 @@ impl<R: Read + Sized + Into<Stream>> PoolReturnRead<R> {
stream.reset()?;
// insert back into pool
let key = PoolKey::new(&unit.url, &unit.req.proxy);
let key = PoolKey::new(&unit.url, unit.req.proxy());
unit.req.agent.state.pool.add(key, stream);
}