mirror of
https://github.com/actix/actix-extras.git
synced 2024-12-01 02:44:37 +01:00
add http client connector service
This commit is contained in:
parent
b25b083866
commit
537144f0b9
26
Cargo.toml
26
Cargo.toml
@ -34,16 +34,26 @@ session = ["cookie/secure"]
|
|||||||
|
|
||||||
cell = ["actix-net/cell"]
|
cell = ["actix-net/cell"]
|
||||||
|
|
||||||
|
# tls
|
||||||
|
tls = ["native-tls", "actix-net/tls"]
|
||||||
|
|
||||||
|
# openssl
|
||||||
|
ssl = ["openssl", "actix-net/ssl"]
|
||||||
|
|
||||||
|
# rustls
|
||||||
|
rust-tls = ["rustls", "actix-net/rust-tls"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.7.5"
|
actix = "0.7.5"
|
||||||
actix-net = "0.2.0"
|
#actix-net = "0.2.0"
|
||||||
#actix-net = { git="https://github.com/actix/actix-net.git" }
|
actix-net = { git="https://github.com/actix/actix-net.git" }
|
||||||
|
|
||||||
base64 = "0.9"
|
base64 = "0.9"
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
http = "0.1.8"
|
http = "0.1.8"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
failure = "0.1.2"
|
failure = "0.1.2"
|
||||||
|
indexmap = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
rand = "0.5"
|
rand = "0.5"
|
||||||
@ -61,6 +71,7 @@ url = { version="1.7", features=["query_encoding"] }
|
|||||||
|
|
||||||
# io
|
# io
|
||||||
net2 = "0.2"
|
net2 = "0.2"
|
||||||
|
slab = "0.4"
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
byteorder = "1.2"
|
byteorder = "1.2"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
@ -70,6 +81,17 @@ tokio-io = "0.1"
|
|||||||
tokio-tcp = "0.1"
|
tokio-tcp = "0.1"
|
||||||
tokio-timer = "0.2"
|
tokio-timer = "0.2"
|
||||||
tokio-current-thread = "0.1"
|
tokio-current-thread = "0.1"
|
||||||
|
trust-dns-proto = "0.5.0"
|
||||||
|
trust-dns-resolver = "0.10.0"
|
||||||
|
|
||||||
|
# native-tls
|
||||||
|
native-tls = { version="0.2", optional = true }
|
||||||
|
|
||||||
|
# openssl
|
||||||
|
openssl = { version="0.10", optional = true }
|
||||||
|
|
||||||
|
#rustls
|
||||||
|
rustls = { version = "^0.14", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-web = "0.7"
|
actix-web = "0.7"
|
||||||
|
80
src/client/connect.rs
Normal file
80
src/client/connect.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use actix_net::connector::RequestPort;
|
||||||
|
use actix_net::resolver::RequestHost;
|
||||||
|
use http::uri::Uri;
|
||||||
|
use http::{Error as HttpError, HttpTryFrom};
|
||||||
|
|
||||||
|
use super::error::{ConnectorError, InvalidUrlKind};
|
||||||
|
use super::pool::Key;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// `Connect` type represents a message that can be sent to
|
||||||
|
/// `Connector` with a connection request.
|
||||||
|
pub struct Connect {
|
||||||
|
pub(crate) uri: Uri,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connect {
|
||||||
|
/// Construct `Uri` instance and create `Connect` message.
|
||||||
|
pub fn new<U>(uri: U) -> Result<Connect, HttpError>
|
||||||
|
where
|
||||||
|
Uri: HttpTryFrom<U>,
|
||||||
|
{
|
||||||
|
Ok(Connect {
|
||||||
|
uri: Uri::try_from(uri).map_err(|e| e.into())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create `Connect` message for specified `Uri`
|
||||||
|
pub fn with(uri: Uri) -> Connect {
|
||||||
|
Connect { uri }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_secure(&self) -> bool {
|
||||||
|
if let Some(scheme) = self.uri.scheme_part() {
|
||||||
|
scheme.as_str() == "https"
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn key(&self) -> Key {
|
||||||
|
self.uri.authority_part().unwrap().clone().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn validate(&self) -> Result<(), ConnectorError> {
|
||||||
|
if self.uri.host().is_none() {
|
||||||
|
Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingHost))
|
||||||
|
} else if self.uri.scheme_part().is_none() {
|
||||||
|
Err(ConnectorError::InvalidUrl(InvalidUrlKind::MissingScheme))
|
||||||
|
} else if let Some(scheme) = self.uri.scheme_part() {
|
||||||
|
match scheme.as_str() {
|
||||||
|
"http" | "ws" | "https" | "wss" => Ok(()),
|
||||||
|
_ => Err(ConnectorError::InvalidUrl(InvalidUrlKind::UnknownScheme)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestHost for Connect {
|
||||||
|
fn host(&self) -> &str {
|
||||||
|
&self.uri.host().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestPort for Connect {
|
||||||
|
fn port(&self) -> u16 {
|
||||||
|
if let Some(port) = self.uri.port() {
|
||||||
|
port
|
||||||
|
} else if let Some(scheme) = self.uri.scheme_part() {
|
||||||
|
match scheme.as_str() {
|
||||||
|
"http" | "ws" => 80,
|
||||||
|
"https" | "wss" => 443,
|
||||||
|
_ => 80,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
src/client/connection.rs
Normal file
79
src/client/connection.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
use std::{fmt, io, time};
|
||||||
|
|
||||||
|
use futures::Poll;
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
use super::pool::Acquired;
|
||||||
|
|
||||||
|
/// HTTP client connection
|
||||||
|
pub struct Connection<T: AsyncRead + AsyncWrite + 'static> {
|
||||||
|
io: T,
|
||||||
|
created: time::Instant,
|
||||||
|
pool: Option<Acquired<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for Connection<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + fmt::Debug + 'static,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Connection {:?}", self.io)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite + 'static> Connection<T> {
|
||||||
|
pub(crate) fn new(io: T, created: time::Instant, pool: Acquired<T>) -> Self {
|
||||||
|
Connection {
|
||||||
|
io,
|
||||||
|
created,
|
||||||
|
pool: Some(pool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Raw IO stream
|
||||||
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.io
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close connection
|
||||||
|
pub fn close(mut self) {
|
||||||
|
if let Some(mut pool) = self.pool.take() {
|
||||||
|
pool.close(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Release this connection to the connection pool
|
||||||
|
pub fn release(mut self) {
|
||||||
|
if let Some(mut pool) = self.pool.take() {
|
||||||
|
pool.release(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_inner(self) -> (T, time::Instant) {
|
||||||
|
(self.io, self.created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite + 'static> io::Read for Connection<T> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.io.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite + 'static> AsyncRead for Connection<T> {}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite + 'static> io::Write for Connection<T> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.io.write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.io.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncRead + AsyncWrite + 'static> AsyncWrite for Connection<T> {
|
||||||
|
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||||
|
self.io.shutdown()
|
||||||
|
}
|
||||||
|
}
|
500
src/client/connector.rs
Normal file
500
src/client/connector.rs
Normal file
@ -0,0 +1,500 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
use std::{fmt, io};
|
||||||
|
|
||||||
|
use actix_net::connector::TcpConnector;
|
||||||
|
use actix_net::resolver::Resolver;
|
||||||
|
use actix_net::service::{Service, ServiceExt};
|
||||||
|
use actix_net::timeout::{TimeoutError, TimeoutService};
|
||||||
|
use futures::future::Either;
|
||||||
|
use futures::Poll;
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
|
||||||
|
|
||||||
|
use super::connect::Connect;
|
||||||
|
use super::connection::Connection;
|
||||||
|
use super::error::ConnectorError;
|
||||||
|
use super::pool::ConnectionPool;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssl")]
|
||||||
|
use actix_net::ssl::OpensslConnector;
|
||||||
|
#[cfg(feature = "ssl")]
|
||||||
|
use openssl::ssl::{SslConnector, SslMethod};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ssl"))]
|
||||||
|
type SslConnector = ();
|
||||||
|
|
||||||
|
/// Http client connector builde instance.
|
||||||
|
/// `Connector` type uses builder-like pattern for connector service construction.
|
||||||
|
pub struct Connector {
|
||||||
|
resolver: Resolver<Connect>,
|
||||||
|
timeout: Duration,
|
||||||
|
conn_lifetime: Duration,
|
||||||
|
conn_keep_alive: Duration,
|
||||||
|
disconnect_timeout: Duration,
|
||||||
|
limit: usize,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
connector: SslConnector,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Connector {
|
||||||
|
fn default() -> Connector {
|
||||||
|
let connector = {
|
||||||
|
#[cfg(feature = "ssl")]
|
||||||
|
{
|
||||||
|
SslConnector::builder(SslMethod::tls()).unwrap().build()
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "ssl"))]
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Connector {
|
||||||
|
connector,
|
||||||
|
resolver: Resolver::default(),
|
||||||
|
timeout: Duration::from_secs(1),
|
||||||
|
conn_lifetime: Duration::from_secs(75),
|
||||||
|
conn_keep_alive: Duration::from_secs(15),
|
||||||
|
disconnect_timeout: Duration::from_millis(3000),
|
||||||
|
limit: 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connector {
|
||||||
|
/// Use custom resolver configuration.
|
||||||
|
pub fn resolver_config(mut self, cfg: ResolverConfig, opts: ResolverOpts) -> Self {
|
||||||
|
self.resolver = Resolver::new(cfg, opts);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection timeout, i.e. max time to connect to remote host including dns name resolution.
|
||||||
|
/// Set to 1 second by default.
|
||||||
|
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||||
|
self.timeout = timeout;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssl")]
|
||||||
|
/// Use custom `SslConnector` instance.
|
||||||
|
pub fn ssl(mut self, connector: SslConnector) -> Self {
|
||||||
|
self.connector = connector;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set total number of simultaneous connections per type of scheme.
|
||||||
|
///
|
||||||
|
/// If limit is 0, the connector has no limit.
|
||||||
|
/// The default limit size is 100.
|
||||||
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
|
self.limit = limit;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set keep-alive period for opened connection.
|
||||||
|
///
|
||||||
|
/// Keep-alive period is the period between connection usage. If
|
||||||
|
/// the delay between repeated usages of the same connection
|
||||||
|
/// exceeds this period, the connection is closed.
|
||||||
|
/// Default keep-alive period is 15 seconds.
|
||||||
|
pub fn conn_keep_alive(mut self, dur: Duration) -> Self {
|
||||||
|
self.conn_keep_alive = dur;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set max lifetime period for connection.
|
||||||
|
///
|
||||||
|
/// Connection lifetime is max lifetime of any opened connection
|
||||||
|
/// until it is closed regardless of keep-alive period.
|
||||||
|
/// Default lifetime period is 75 seconds.
|
||||||
|
pub fn conn_lifetime(mut self, dur: Duration) -> Self {
|
||||||
|
self.conn_lifetime = dur;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set server connection disconnect timeout in milliseconds.
|
||||||
|
///
|
||||||
|
/// Defines a timeout for disconnect connection. If a disconnect procedure does not complete
|
||||||
|
/// within this time, the socket get dropped. This timeout affects only secure connections.
|
||||||
|
///
|
||||||
|
/// To disable timeout set value to 0.
|
||||||
|
///
|
||||||
|
/// By default disconnect timeout is set to 3000 milliseconds.
|
||||||
|
pub fn disconnect_timeout(mut self, dur: Duration) -> Self {
|
||||||
|
self.disconnect_timeout = dur;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish configuration process and create connector service.
|
||||||
|
pub fn service(
|
||||||
|
self,
|
||||||
|
) -> impl Service<
|
||||||
|
Request = Connect,
|
||||||
|
Response = impl AsyncRead + AsyncWrite + fmt::Debug,
|
||||||
|
Error = ConnectorError,
|
||||||
|
> + Clone {
|
||||||
|
#[cfg(not(feature = "ssl"))]
|
||||||
|
{
|
||||||
|
let connector = TimeoutService::new(
|
||||||
|
self.timeout,
|
||||||
|
self.resolver
|
||||||
|
.map_err(ConnectorError::from)
|
||||||
|
.and_then(TcpConnector::default().from_err()),
|
||||||
|
).map_err(|e| match e {
|
||||||
|
TimeoutError::Service(e) => e,
|
||||||
|
TimeoutError::Timeout => ConnectorError::Timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
connect_impl::InnerConnector {
|
||||||
|
tcp_pool: ConnectionPool::new(
|
||||||
|
connector,
|
||||||
|
self.conn_lifetime,
|
||||||
|
self.conn_keep_alive,
|
||||||
|
None,
|
||||||
|
self.limit,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ssl")]
|
||||||
|
{
|
||||||
|
let ssl_service = TimeoutService::new(
|
||||||
|
self.timeout,
|
||||||
|
self.resolver
|
||||||
|
.clone()
|
||||||
|
.map_err(ConnectorError::from)
|
||||||
|
.and_then(TcpConnector::default().from_err())
|
||||||
|
.and_then(
|
||||||
|
OpensslConnector::service(self.connector)
|
||||||
|
.map_err(ConnectorError::SslError),
|
||||||
|
),
|
||||||
|
).map_err(|e| match e {
|
||||||
|
TimeoutError::Service(e) => e,
|
||||||
|
TimeoutError::Timeout => ConnectorError::Timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
let tcp_service = TimeoutService::new(
|
||||||
|
self.timeout,
|
||||||
|
self.resolver
|
||||||
|
.map_err(ConnectorError::from)
|
||||||
|
.and_then(TcpConnector::default().from_err()),
|
||||||
|
).map_err(|e| match e {
|
||||||
|
TimeoutError::Service(e) => e,
|
||||||
|
TimeoutError::Timeout => ConnectorError::Timeout,
|
||||||
|
});
|
||||||
|
|
||||||
|
connect_impl::InnerConnector {
|
||||||
|
tcp_pool: ConnectionPool::new(
|
||||||
|
tcp_service,
|
||||||
|
self.conn_lifetime,
|
||||||
|
self.conn_keep_alive,
|
||||||
|
None,
|
||||||
|
self.limit,
|
||||||
|
),
|
||||||
|
ssl_pool: ConnectionPool::new(
|
||||||
|
ssl_service,
|
||||||
|
self.conn_lifetime,
|
||||||
|
self.conn_keep_alive,
|
||||||
|
Some(self.disconnect_timeout),
|
||||||
|
self.limit,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ssl"))]
|
||||||
|
mod connect_impl {
|
||||||
|
use super::*;
|
||||||
|
use futures::future::{err, FutureResult};
|
||||||
|
|
||||||
|
pub(crate) struct InnerConnector<T, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io), Error = ConnectorError>,
|
||||||
|
{
|
||||||
|
pub(crate) tcp_pool: ConnectionPool<T, Io>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Io> Clone for InnerConnector<T, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io), Error = ConnectorError>
|
||||||
|
+ Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
InnerConnector {
|
||||||
|
tcp_pool: self.tcp_pool.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Io> Service for InnerConnector<T, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io), Error = ConnectorError>,
|
||||||
|
{
|
||||||
|
type Request = Connect;
|
||||||
|
type Response = Connection<Io>;
|
||||||
|
type Error = ConnectorError;
|
||||||
|
type Future = Either<
|
||||||
|
<ConnectionPool<T, Io> as Service>::Future,
|
||||||
|
FutureResult<Connection<Io>, ConnectorError>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||||
|
self.tcp_pool.poll_ready()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||||
|
if req.is_secure() {
|
||||||
|
Either::B(err(ConnectorError::SslIsNotSupported))
|
||||||
|
} else if let Err(e) = req.validate() {
|
||||||
|
Either::B(err(e))
|
||||||
|
} else {
|
||||||
|
Either::A(self.tcp_pool.call(req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssl")]
|
||||||
|
mod connect_impl {
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use futures::future::{err, FutureResult};
|
||||||
|
use futures::{Async, Future, Poll};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(crate) struct InnerConnector<T1, T2, Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Io2: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T1: Service<
|
||||||
|
Request = Connect,
|
||||||
|
Response = (Connect, Io1),
|
||||||
|
Error = ConnectorError,
|
||||||
|
>,
|
||||||
|
T2: Service<
|
||||||
|
Request = Connect,
|
||||||
|
Response = (Connect, Io2),
|
||||||
|
Error = ConnectorError,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
pub(crate) tcp_pool: ConnectionPool<T1, Io1>,
|
||||||
|
pub(crate) ssl_pool: ConnectionPool<T2, Io2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T1, T2, Io1, Io2> Clone for InnerConnector<T1, T2, Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Io2: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T1: Service<
|
||||||
|
Request = Connect,
|
||||||
|
Response = (Connect, Io1),
|
||||||
|
Error = ConnectorError,
|
||||||
|
> + Clone,
|
||||||
|
T2: Service<
|
||||||
|
Request = Connect,
|
||||||
|
Response = (Connect, Io2),
|
||||||
|
Error = ConnectorError,
|
||||||
|
> + Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
InnerConnector {
|
||||||
|
tcp_pool: self.tcp_pool.clone(),
|
||||||
|
ssl_pool: self.ssl_pool.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T1, T2, Io1, Io2> Service for InnerConnector<T1, T2, Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Io2: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T1: Service<
|
||||||
|
Request = Connect,
|
||||||
|
Response = (Connect, Io1),
|
||||||
|
Error = ConnectorError,
|
||||||
|
>,
|
||||||
|
T2: Service<
|
||||||
|
Request = Connect,
|
||||||
|
Response = (Connect, Io2),
|
||||||
|
Error = ConnectorError,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
type Request = Connect;
|
||||||
|
type Response = IoEither<Connection<Io1>, Connection<Io2>>;
|
||||||
|
type Error = ConnectorError;
|
||||||
|
type Future = Either<
|
||||||
|
FutureResult<Self::Response, Self::Error>,
|
||||||
|
Either<
|
||||||
|
InnerConnectorResponseA<T1, Io1, Io2>,
|
||||||
|
InnerConnectorResponseB<T2, Io1, Io2>,
|
||||||
|
>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||||
|
self.tcp_pool.poll_ready()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||||
|
if let Err(e) = req.validate() {
|
||||||
|
Either::A(err(e))
|
||||||
|
} else if req.is_secure() {
|
||||||
|
Either::B(Either::A(InnerConnectorResponseA {
|
||||||
|
fut: self.tcp_pool.call(req),
|
||||||
|
_t: PhantomData,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Either::B(Either::B(InnerConnectorResponseB {
|
||||||
|
fut: self.ssl_pool.call(req),
|
||||||
|
_t: PhantomData,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct InnerConnectorResponseA<T, Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io1), Error = ConnectorError>,
|
||||||
|
{
|
||||||
|
fut: <ConnectionPool<T, Io1> as Service>::Future,
|
||||||
|
_t: PhantomData<Io2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Io1, Io2> Future for InnerConnectorResponseA<T, Io1, Io2>
|
||||||
|
where
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io1), Error = ConnectorError>,
|
||||||
|
Io1: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Io2: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
type Item = IoEither<Connection<Io1>, Connection<Io2>>;
|
||||||
|
type Error = ConnectorError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match self.fut.poll()? {
|
||||||
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
|
Async::Ready(res) => Ok(Async::Ready(IoEither::A(res))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct InnerConnectorResponseB<T, Io1, Io2>
|
||||||
|
where
|
||||||
|
Io2: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io2), Error = ConnectorError>,
|
||||||
|
{
|
||||||
|
fut: <ConnectionPool<T, Io2> as Service>::Future,
|
||||||
|
_t: PhantomData<Io1>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Io1, Io2> Future for InnerConnectorResponseB<T, Io1, Io2>
|
||||||
|
where
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io2), Error = ConnectorError>,
|
||||||
|
Io1: AsyncRead + AsyncWrite + 'static,
|
||||||
|
Io2: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
type Item = IoEither<Connection<Io1>, Connection<Io2>>;
|
||||||
|
type Error = ConnectorError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match self.fut.poll()? {
|
||||||
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
|
Async::Ready(res) => Ok(Async::Ready(IoEither::B(res))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum IoEither<Io1, Io2> {
|
||||||
|
A(Io1),
|
||||||
|
B(Io2),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io1, Io2> io::Read for IoEither<Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: io::Read,
|
||||||
|
Io2: io::Read,
|
||||||
|
{
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self {
|
||||||
|
IoEither::A(ref mut io) => io.read(buf),
|
||||||
|
IoEither::B(ref mut io) => io.read(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io1, Io2> AsyncRead for IoEither<Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: AsyncRead,
|
||||||
|
Io2: AsyncRead,
|
||||||
|
{
|
||||||
|
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
|
||||||
|
match self {
|
||||||
|
IoEither::A(ref io) => io.prepare_uninitialized_buffer(buf),
|
||||||
|
IoEither::B(ref io) => io.prepare_uninitialized_buffer(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io1, Io2> AsyncWrite for IoEither<Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: AsyncWrite,
|
||||||
|
Io2: AsyncWrite,
|
||||||
|
{
|
||||||
|
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||||
|
match self {
|
||||||
|
IoEither::A(ref mut io) => io.shutdown(),
|
||||||
|
IoEither::B(ref mut io) => io.shutdown(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_write(&mut self, buf: &[u8]) -> Poll<usize, io::Error> {
|
||||||
|
match self {
|
||||||
|
IoEither::A(ref mut io) => io.poll_write(buf),
|
||||||
|
IoEither::B(ref mut io) => io.poll_write(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(&mut self) -> Poll<(), io::Error> {
|
||||||
|
match self {
|
||||||
|
IoEither::A(ref mut io) => io.poll_flush(),
|
||||||
|
IoEither::B(ref mut io) => io.poll_flush(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io1, Io2> io::Write for IoEither<Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: io::Write,
|
||||||
|
Io2: io::Write,
|
||||||
|
{
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
match self {
|
||||||
|
IoEither::A(ref mut io) => io.flush(),
|
||||||
|
IoEither::B(ref mut io) => io.flush(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
match self {
|
||||||
|
IoEither::A(ref mut io) => io.write(buf),
|
||||||
|
IoEither::B(ref mut io) => io.write(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io1, Io2> fmt::Debug for IoEither<Io1, Io2>
|
||||||
|
where
|
||||||
|
Io1: fmt::Debug,
|
||||||
|
Io2: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
IoEither::A(ref io) => io.fmt(fmt),
|
||||||
|
IoEither::B(ref io) => io.fmt(fmt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/client/error.rs
Normal file
77
src/client/error.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
|
use trust_dns_resolver::error::ResolveError;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssl")]
|
||||||
|
use openssl::ssl::Error as SslError;
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "tls",
|
||||||
|
not(any(feature = "ssl", feature = "rust-tls"))
|
||||||
|
))]
|
||||||
|
use native_tls::Error as SslError;
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "rust-tls",
|
||||||
|
not(any(feature = "tls", feature = "ssl"))
|
||||||
|
))]
|
||||||
|
use std::io::Error as SslError;
|
||||||
|
|
||||||
|
/// A set of errors that can occur while connecting to an HTTP host
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum ConnectorError {
|
||||||
|
/// Invalid URL
|
||||||
|
#[fail(display = "Invalid URL")]
|
||||||
|
InvalidUrl(InvalidUrlKind),
|
||||||
|
|
||||||
|
/// SSL feature is not enabled
|
||||||
|
#[fail(display = "SSL is not supported")]
|
||||||
|
SslIsNotSupported,
|
||||||
|
|
||||||
|
/// SSL error
|
||||||
|
#[cfg(any(feature = "tls", feature = "ssl", feature = "rust-tls"))]
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
SslError(#[cause] SslError),
|
||||||
|
|
||||||
|
/// Failed to resolve the hostname
|
||||||
|
#[fail(display = "Failed resolving hostname: {}", _0)]
|
||||||
|
Resolver(ResolveError),
|
||||||
|
|
||||||
|
/// No dns records
|
||||||
|
#[fail(display = "No dns records found for the input")]
|
||||||
|
NoRecords,
|
||||||
|
|
||||||
|
/// Connecting took too long
|
||||||
|
#[fail(display = "Timeout out while establishing connection")]
|
||||||
|
Timeout,
|
||||||
|
|
||||||
|
/// Connector has been disconnected
|
||||||
|
#[fail(display = "Internal error: connector has been disconnected")]
|
||||||
|
Disconnected,
|
||||||
|
|
||||||
|
/// Connection io error
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
IoError(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum InvalidUrlKind {
|
||||||
|
#[fail(display = "Missing url scheme")]
|
||||||
|
MissingScheme,
|
||||||
|
#[fail(display = "Unknown url scheme")]
|
||||||
|
UnknownScheme,
|
||||||
|
#[fail(display = "Missing host name")]
|
||||||
|
MissingHost,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for ConnectorError {
|
||||||
|
fn from(err: io::Error) -> ConnectorError {
|
||||||
|
ConnectorError::IoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResolveError> for ConnectorError {
|
||||||
|
fn from(err: ResolveError) -> ConnectorError {
|
||||||
|
ConnectorError::Resolver(err)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,14 @@
|
|||||||
//! Http client api
|
//! Http client api
|
||||||
|
mod connect;
|
||||||
|
mod connection;
|
||||||
|
mod connector;
|
||||||
|
mod error;
|
||||||
|
mod pool;
|
||||||
mod request;
|
mod request;
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
|
pub use self::connect::Connect;
|
||||||
|
pub use self::connector::Connector;
|
||||||
|
pub use self::error::{ConnectorError, InvalidUrlKind};
|
||||||
pub use self::request::{ClientRequest, ClientRequestBuilder};
|
pub use self::request::{ClientRequest, ClientRequestBuilder};
|
||||||
pub use self::response::ClientResponse;
|
pub use self::response::ClientResponse;
|
||||||
|
579
src/client/pool.rs
Normal file
579
src/client/pool.rs
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use std::io;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use actix_net::service::Service;
|
||||||
|
use futures::future::{ok, Either, FutureResult};
|
||||||
|
use futures::sync::oneshot;
|
||||||
|
use futures::task::AtomicTask;
|
||||||
|
use futures::{Async, Future, Poll};
|
||||||
|
use http::uri::Authority;
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
use slab::Slab;
|
||||||
|
use tokio_current_thread::spawn;
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use tokio_timer::{sleep, Delay};
|
||||||
|
|
||||||
|
use super::connect::Connect;
|
||||||
|
use super::connection::Connection;
|
||||||
|
use super::error::ConnectorError;
|
||||||
|
|
||||||
|
#[derive(Hash, Eq, PartialEq, Clone, Debug)]
|
||||||
|
pub(crate) struct Key {
|
||||||
|
authority: Authority,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Authority> for Key {
|
||||||
|
fn from(authority: Authority) -> Key {
|
||||||
|
Key { authority }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AvailableConnection<T> {
|
||||||
|
io: T,
|
||||||
|
used: Instant,
|
||||||
|
created: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connections pool
|
||||||
|
pub(crate) struct ConnectionPool<T, Io: AsyncRead + AsyncWrite + 'static>(
|
||||||
|
T,
|
||||||
|
Rc<RefCell<Inner<Io>>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<T, Io> ConnectionPool<T, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io), Error = ConnectorError>,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(
|
||||||
|
connector: T,
|
||||||
|
conn_lifetime: Duration,
|
||||||
|
conn_keep_alive: Duration,
|
||||||
|
disconnect_timeout: Option<Duration>,
|
||||||
|
limit: usize,
|
||||||
|
) -> Self {
|
||||||
|
ConnectionPool(
|
||||||
|
connector,
|
||||||
|
Rc::new(RefCell::new(Inner {
|
||||||
|
conn_lifetime,
|
||||||
|
conn_keep_alive,
|
||||||
|
disconnect_timeout,
|
||||||
|
limit,
|
||||||
|
acquired: 0,
|
||||||
|
waiters: Slab::new(),
|
||||||
|
waiters_queue: IndexSet::new(),
|
||||||
|
available: HashMap::new(),
|
||||||
|
task: AtomicTask::new(),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Io> Clone for ConnectionPool<T, Io>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
ConnectionPool(self.0.clone(), self.1.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Io> Service for ConnectionPool<T, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io), Error = ConnectorError>,
|
||||||
|
{
|
||||||
|
type Request = Connect;
|
||||||
|
type Response = Connection<Io>;
|
||||||
|
type Error = ConnectorError;
|
||||||
|
type Future = Either<
|
||||||
|
FutureResult<Connection<Io>, ConnectorError>,
|
||||||
|
Either<WaitForConnection<Io>, OpenConnection<T::Future, Io>>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||||
|
self.0.poll_ready()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||||
|
let key = req.key();
|
||||||
|
|
||||||
|
// acquire connection
|
||||||
|
match self.1.as_ref().borrow_mut().acquire(&key) {
|
||||||
|
Acquire::Acquired(io, created) => {
|
||||||
|
// use existing connection
|
||||||
|
Either::A(ok(Connection::new(
|
||||||
|
io,
|
||||||
|
created,
|
||||||
|
Acquired(key, Some(self.1.clone())),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
Acquire::NotAvailable => {
|
||||||
|
// connection is not available, wait
|
||||||
|
let (rx, token) = self.1.as_ref().borrow_mut().wait_for(req);
|
||||||
|
Either::B(Either::A(WaitForConnection {
|
||||||
|
rx,
|
||||||
|
key,
|
||||||
|
token,
|
||||||
|
inner: Some(self.1.clone()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Acquire::Available => {
|
||||||
|
// open new connection
|
||||||
|
Either::B(Either::B(OpenConnection::new(
|
||||||
|
key,
|
||||||
|
self.1.clone(),
|
||||||
|
self.0.call(req),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct WaitForConnection<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
key: Key,
|
||||||
|
token: usize,
|
||||||
|
rx: oneshot::Receiver<Result<Connection<Io>, ConnectorError>>,
|
||||||
|
inner: Option<Rc<RefCell<Inner<Io>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Drop for WaitForConnection<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(i) = self.inner.take() {
|
||||||
|
let mut inner = i.as_ref().borrow_mut();
|
||||||
|
inner.release_waiter(&self.key, self.token);
|
||||||
|
inner.check_availibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Future for WaitForConnection<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
type Item = Connection<Io>;
|
||||||
|
type Error = ConnectorError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match self.rx.poll() {
|
||||||
|
Ok(Async::Ready(item)) => match item {
|
||||||
|
Err(err) => Err(err),
|
||||||
|
Ok(conn) => {
|
||||||
|
let _ = self.inner.take();
|
||||||
|
Ok(Async::Ready(conn))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||||
|
Err(_) => {
|
||||||
|
let _ = self.inner.take();
|
||||||
|
Err(ConnectorError::Disconnected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct OpenConnection<F, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fut: F,
|
||||||
|
key: Key,
|
||||||
|
inner: Option<Rc<RefCell<Inner<Io>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, Io> OpenConnection<F, Io>
|
||||||
|
where
|
||||||
|
F: Future<Item = (Connect, Io), Error = ConnectorError>,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fn new(key: Key, inner: Rc<RefCell<Inner<Io>>>, fut: F) -> Self {
|
||||||
|
OpenConnection {
|
||||||
|
key,
|
||||||
|
fut,
|
||||||
|
inner: Some(inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, Io> Drop for OpenConnection<F, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(inner) = self.inner.take() {
|
||||||
|
let mut inner = inner.as_ref().borrow_mut();
|
||||||
|
inner.release();
|
||||||
|
inner.check_availibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, Io> Future for OpenConnection<F, Io>
|
||||||
|
where
|
||||||
|
F: Future<Item = (Connect, Io), Error = ConnectorError>,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
type Item = Connection<Io>;
|
||||||
|
type Error = ConnectorError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match self.fut.poll() {
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
Ok(Async::Ready((_, io))) => {
|
||||||
|
let _ = self.inner.take();
|
||||||
|
Ok(Async::Ready(Connection::new(
|
||||||
|
io,
|
||||||
|
Instant::now(),
|
||||||
|
Acquired(self.key.clone(), self.inner.clone()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OpenWaitingConnection<F, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fut: F,
|
||||||
|
key: Key,
|
||||||
|
rx: Option<oneshot::Sender<Result<Connection<Io>, ConnectorError>>>,
|
||||||
|
inner: Option<Rc<RefCell<Inner<Io>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, Io> OpenWaitingConnection<F, Io>
|
||||||
|
where
|
||||||
|
F: Future<Item = (Connect, Io), Error = ConnectorError> + 'static,
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fn spawn(
|
||||||
|
key: Key,
|
||||||
|
rx: oneshot::Sender<Result<Connection<Io>, ConnectorError>>,
|
||||||
|
inner: Rc<RefCell<Inner<Io>>>,
|
||||||
|
fut: F,
|
||||||
|
) {
|
||||||
|
spawn(OpenWaitingConnection {
|
||||||
|
key,
|
||||||
|
fut,
|
||||||
|
rx: Some(rx),
|
||||||
|
inner: Some(inner),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, Io> Drop for OpenWaitingConnection<F, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(inner) = self.inner.take() {
|
||||||
|
let mut inner = inner.as_ref().borrow_mut();
|
||||||
|
inner.release();
|
||||||
|
inner.check_availibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, Io> Future for OpenWaitingConnection<F, Io>
|
||||||
|
where
|
||||||
|
F: Future<Item = (Connect, Io), Error = ConnectorError>,
|
||||||
|
Io: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
type Item = ();
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match self.fut.poll() {
|
||||||
|
Err(err) => {
|
||||||
|
let _ = self.inner.take();
|
||||||
|
if let Some(rx) = self.rx.take() {
|
||||||
|
let _ = rx.send(Err(err));
|
||||||
|
}
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
Ok(Async::Ready((_, io))) => {
|
||||||
|
let _ = self.inner.take();
|
||||||
|
if let Some(rx) = self.rx.take() {
|
||||||
|
let _ = rx.send(Ok(Connection::new(
|
||||||
|
io,
|
||||||
|
Instant::now(),
|
||||||
|
Acquired(self.key.clone(), self.inner.clone()),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Acquire<T> {
|
||||||
|
Acquired(T, Instant),
|
||||||
|
Available,
|
||||||
|
NotAvailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Inner<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
conn_lifetime: Duration,
|
||||||
|
conn_keep_alive: Duration,
|
||||||
|
disconnect_timeout: Option<Duration>,
|
||||||
|
limit: usize,
|
||||||
|
acquired: usize,
|
||||||
|
available: HashMap<Key, VecDeque<AvailableConnection<Io>>>,
|
||||||
|
waiters: Slab<(
|
||||||
|
Connect,
|
||||||
|
oneshot::Sender<Result<Connection<Io>, ConnectorError>>,
|
||||||
|
)>,
|
||||||
|
waiters_queue: IndexSet<(Key, usize)>,
|
||||||
|
task: AtomicTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Io> Inner<Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
/// connection is not available, wait
|
||||||
|
fn wait_for(
|
||||||
|
&mut self,
|
||||||
|
connect: Connect,
|
||||||
|
) -> (
|
||||||
|
oneshot::Receiver<Result<Connection<Io>, ConnectorError>>,
|
||||||
|
usize,
|
||||||
|
) {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let key = connect.key();
|
||||||
|
let entry = self.waiters.vacant_entry();
|
||||||
|
let token = entry.key();
|
||||||
|
entry.insert((connect, tx));
|
||||||
|
assert!(!self.waiters_queue.insert((key, token)));
|
||||||
|
(rx, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_waiter(&mut self, key: &Key, token: usize) {
|
||||||
|
self.waiters.remove(token);
|
||||||
|
self.waiters_queue.remove(&(key.clone(), token));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acquire(&mut self, key: &Key) -> Acquire<Io> {
|
||||||
|
// check limits
|
||||||
|
if self.limit > 0 && self.acquired >= self.limit {
|
||||||
|
return Acquire::NotAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reserve();
|
||||||
|
|
||||||
|
// check if open connection is available
|
||||||
|
// cleanup stale connections at the same time
|
||||||
|
if let Some(ref mut connections) = self.available.get_mut(key) {
|
||||||
|
let now = Instant::now();
|
||||||
|
while let Some(conn) = connections.pop_back() {
|
||||||
|
// check if it still usable
|
||||||
|
if (now - conn.used) > self.conn_keep_alive
|
||||||
|
|| (now - conn.created) > self.conn_lifetime
|
||||||
|
{
|
||||||
|
if let Some(timeout) = self.disconnect_timeout {
|
||||||
|
spawn(CloseConnection::new(conn.io, timeout))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut io = conn.io;
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
match io.read(&mut buf) {
|
||||||
|
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (),
|
||||||
|
Ok(n) if n > 0 => {
|
||||||
|
if let Some(timeout) = self.disconnect_timeout {
|
||||||
|
spawn(CloseConnection::new(io, timeout))
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Ok(_) | Err(_) => continue,
|
||||||
|
}
|
||||||
|
return Acquire::Acquired(io, conn.created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Acquire::Available
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve(&mut self) {
|
||||||
|
self.acquired += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release(&mut self) {
|
||||||
|
self.acquired -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_conn(&mut self, key: &Key, io: Io, created: Instant) {
|
||||||
|
self.acquired -= 1;
|
||||||
|
self.available
|
||||||
|
.entry(key.clone())
|
||||||
|
.or_insert_with(VecDeque::new)
|
||||||
|
.push_back(AvailableConnection {
|
||||||
|
io,
|
||||||
|
created,
|
||||||
|
used: Instant::now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_close(&mut self, io: Io) {
|
||||||
|
self.acquired -= 1;
|
||||||
|
if let Some(timeout) = self.disconnect_timeout {
|
||||||
|
spawn(CloseConnection::new(io, timeout))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_availibility(&self) {
|
||||||
|
if !self.waiters_queue.is_empty() && self.acquired < self.limit {
|
||||||
|
self.task.notify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConnectorPoolSupport<T, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
connector: T,
|
||||||
|
inner: Rc<RefCell<Inner<Io>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Io> Future for ConnectorPoolSupport<T, Io>
|
||||||
|
where
|
||||||
|
Io: AsyncRead + AsyncWrite + 'static,
|
||||||
|
T: Service<Request = Connect, Response = (Connect, Io), Error = ConnectorError>,
|
||||||
|
T::Future: 'static,
|
||||||
|
{
|
||||||
|
type Item = ();
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
let mut inner = self.inner.as_ref().borrow_mut();
|
||||||
|
inner.task.register();
|
||||||
|
|
||||||
|
// check waiters
|
||||||
|
loop {
|
||||||
|
let (key, token) = {
|
||||||
|
if let Some((key, token)) = inner.waiters_queue.get_index(0) {
|
||||||
|
(key.clone(), *token)
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match inner.acquire(&key) {
|
||||||
|
Acquire::NotAvailable => break,
|
||||||
|
Acquire::Acquired(io, created) => {
|
||||||
|
let (_, tx) = inner.waiters.remove(token);
|
||||||
|
if let Err(conn) = tx.send(Ok(Connection::new(
|
||||||
|
io,
|
||||||
|
created,
|
||||||
|
Acquired(key.clone(), Some(self.inner.clone())),
|
||||||
|
))) {
|
||||||
|
let (io, created) = conn.unwrap().into_inner();
|
||||||
|
inner.release_conn(&key, io, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Acquire::Available => {
|
||||||
|
let (connect, tx) = inner.waiters.remove(token);
|
||||||
|
OpenWaitingConnection::spawn(
|
||||||
|
key.clone(),
|
||||||
|
tx,
|
||||||
|
self.inner.clone(),
|
||||||
|
self.connector.call(connect),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = inner.waiters_queue.swap_remove_index(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CloseConnection<T> {
|
||||||
|
io: T,
|
||||||
|
timeout: Delay,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> CloseConnection<T>
|
||||||
|
where
|
||||||
|
T: AsyncWrite,
|
||||||
|
{
|
||||||
|
fn new(io: T, timeout: Duration) -> Self {
|
||||||
|
CloseConnection {
|
||||||
|
io,
|
||||||
|
timeout: sleep(timeout),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Future for CloseConnection<T>
|
||||||
|
where
|
||||||
|
T: AsyncWrite,
|
||||||
|
{
|
||||||
|
type Item = ();
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<(), ()> {
|
||||||
|
match self.timeout.poll() {
|
||||||
|
Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())),
|
||||||
|
Ok(Async::NotReady) => match self.io.shutdown() {
|
||||||
|
Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())),
|
||||||
|
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Acquired<T: AsyncRead + AsyncWrite + 'static>(
|
||||||
|
Key,
|
||||||
|
Option<Rc<RefCell<Inner<T>>>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<T> Acquired<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
pub(crate) fn close(&mut self, conn: Connection<T>) {
|
||||||
|
if let Some(inner) = self.1.take() {
|
||||||
|
let (io, _) = conn.into_inner();
|
||||||
|
inner.as_ref().borrow_mut().release_close(io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn release(&mut self, conn: Connection<T>) {
|
||||||
|
if let Some(inner) = self.1.take() {
|
||||||
|
let (io, created) = conn.into_inner();
|
||||||
|
inner
|
||||||
|
.as_ref()
|
||||||
|
.borrow_mut()
|
||||||
|
.release_conn(&self.0, io, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Drop for Acquired<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(inner) = self.1.take() {
|
||||||
|
inner.as_ref().borrow_mut().release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -83,6 +83,7 @@ extern crate cookie;
|
|||||||
extern crate encoding;
|
extern crate encoding;
|
||||||
extern crate http as modhttp;
|
extern crate http as modhttp;
|
||||||
extern crate httparse;
|
extern crate httparse;
|
||||||
|
extern crate indexmap;
|
||||||
extern crate mime;
|
extern crate mime;
|
||||||
extern crate net2;
|
extern crate net2;
|
||||||
extern crate percent_encoding;
|
extern crate percent_encoding;
|
||||||
@ -90,18 +91,24 @@ extern crate rand;
|
|||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate serde_urlencoded;
|
extern crate serde_urlencoded;
|
||||||
|
extern crate slab;
|
||||||
extern crate tokio;
|
extern crate tokio;
|
||||||
extern crate tokio_codec;
|
extern crate tokio_codec;
|
||||||
extern crate tokio_current_thread;
|
extern crate tokio_current_thread;
|
||||||
extern crate tokio_io;
|
extern crate tokio_io;
|
||||||
extern crate tokio_tcp;
|
extern crate tokio_tcp;
|
||||||
extern crate tokio_timer;
|
extern crate tokio_timer;
|
||||||
|
extern crate trust_dns_proto;
|
||||||
|
extern crate trust_dns_resolver;
|
||||||
extern crate url as urlcrate;
|
extern crate url as urlcrate;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssl")]
|
||||||
|
extern crate openssl;
|
||||||
|
|
||||||
mod body;
|
mod body;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
mod config;
|
mod config;
|
||||||
|
Loading…
Reference in New Issue
Block a user