1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 16:02:59 +01:00

add http client connector service

This commit is contained in:
Nikolay Kim 2018-11-11 23:12:54 -08:00
parent b25b083866
commit 537144f0b9
8 changed files with 1354 additions and 2 deletions

View File

@ -34,16 +34,26 @@ session = ["cookie/secure"]
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]
actix = "0.7.5"
actix-net = "0.2.0"
#actix-net = { git="https://github.com/actix/actix-net.git" }
#actix-net = "0.2.0"
actix-net = { git="https://github.com/actix/actix-net.git" }
base64 = "0.9"
bitflags = "1.0"
http = "0.1.8"
httparse = "1.3"
failure = "0.1.2"
indexmap = "1.0"
log = "0.4"
mime = "0.3"
rand = "0.5"
@ -61,6 +71,7 @@ url = { version="1.7", features=["query_encoding"] }
# io
net2 = "0.2"
slab = "0.4"
bytes = "0.4"
byteorder = "1.2"
futures = "0.1"
@ -70,6 +81,17 @@ tokio-io = "0.1"
tokio-tcp = "0.1"
tokio-timer = "0.2"
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]
actix-web = "0.7"

80
src/client/connect.rs Normal file
View 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
View 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
View 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
View 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)
}
}

View File

@ -1,6 +1,14 @@
//! Http client api
mod connect;
mod connection;
mod connector;
mod error;
mod pool;
mod request;
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::response::ClientResponse;

579
src/client/pool.rs Normal file
View 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();
}
}
}

View File

@ -83,6 +83,7 @@ extern crate cookie;
extern crate encoding;
extern crate http as modhttp;
extern crate httparse;
extern crate indexmap;
extern crate mime;
extern crate net2;
extern crate percent_encoding;
@ -90,18 +91,24 @@ extern crate rand;
extern crate serde;
extern crate serde_json;
extern crate serde_urlencoded;
extern crate slab;
extern crate tokio;
extern crate tokio_codec;
extern crate tokio_current_thread;
extern crate tokio_io;
extern crate tokio_tcp;
extern crate tokio_timer;
extern crate trust_dns_proto;
extern crate trust_dns_resolver;
extern crate url as urlcrate;
#[cfg(test)]
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "ssl")]
extern crate openssl;
mod body;
pub mod client;
mod config;