1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-25 08:22:43 +01:00
actix-extras/src/client/connector.rs

313 lines
9.1 KiB
Rust
Raw Normal View History

2018-01-30 08:01:20 +01:00
#![allow(unused_imports, dead_code)]
use std::{io, time};
use std::net::{SocketAddr, Shutdown};
use std::collections::VecDeque;
use std::time::Duration;
2018-02-09 02:13:56 +01:00
use actix::{fut, Actor, ActorFuture, Arbiter, Context,
2018-02-12 21:17:30 +01:00
Handler, Message, ActorResponse, Supervised};
2018-02-09 02:13:56 +01:00
use actix::registry::ArbiterService;
2018-01-30 20:17:17 +01:00
use actix::fut::WrapFuture;
2018-01-30 08:01:20 +01:00
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};
2018-01-30 20:17:17 +01:00
use http::{Uri, HttpTryFrom, Error as HttpError};
2018-01-30 08:01:20 +01:00
use futures::{Async, Future, Poll};
use tokio_core::reactor::Timeout;
use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::{AsyncRead, AsyncWrite};
2018-01-30 20:17:17 +01:00
#[cfg(feature="alpn")]
use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode, Error as OpensslError};
#[cfg(feature="alpn")]
use tokio_openssl::SslConnectorExt;
use HAS_OPENSSL;
2018-01-30 08:01:20 +01:00
use server::IoStream;
2018-01-30 20:17:17 +01:00
2018-01-30 08:01:20 +01:00
#[derive(Debug)]
/// `Connect` type represents message that can be send to `ClientConnector`
/// with connection request.
2018-01-30 08:01:20 +01:00
pub struct Connect(pub Uri);
2018-01-30 20:17:17 +01:00
impl Connect {
/// Create `Connect` message for specified `Uri`
2018-01-30 20:17:17 +01:00
pub fn new<U>(uri: U) -> Result<Connect, HttpError> where Uri: HttpTryFrom<U> {
Ok(Connect(Uri::try_from(uri).map_err(|e| e.into())?))
}
}
2018-02-12 21:17:30 +01:00
impl Message for Connect {
type Result = Result<Connection, ClientConnectorError>;
2018-01-30 08:01:20 +01:00
}
/// A set of errors that can occur during connecting to a http host
2018-01-30 08:01:20 +01:00
#[derive(Fail, Debug)]
pub enum ClientConnectorError {
/// Invalid url
#[fail(display="Invalid url")]
InvalidUrl,
/// SSL feature is not enabled
#[fail(display="SSL is not supported")]
SslIsNotSupported,
2018-01-30 20:17:17 +01:00
/// SSL error
#[cfg(feature="alpn")]
#[fail(display="{}", _0)]
SslError(OpensslError),
2018-01-30 08:01:20 +01:00
/// Connection error
#[fail(display = "{}", _0)]
Connector(ConnectorError),
/// 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),
}
impl From<ConnectorError> for ClientConnectorError {
fn from(err: ConnectorError) -> ClientConnectorError {
ClientConnectorError::Connector(err)
}
}
pub struct ClientConnector {
2018-01-30 20:17:17 +01:00
#[cfg(feature="alpn")]
connector: SslConnector,
2018-01-30 08:01:20 +01:00
}
impl Actor for ClientConnector {
type Context = Context<ClientConnector>;
}
impl Supervised for ClientConnector {}
impl ArbiterService for ClientConnector {}
2018-01-30 20:17:17 +01:00
impl Default for ClientConnector {
fn default() -> ClientConnector {
#[cfg(feature="alpn")]
{
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
ClientConnector {
connector: builder.build()
}
}
#[cfg(not(feature="alpn"))]
ClientConnector {}
}
}
impl ClientConnector {
#[cfg(feature="alpn")]
/// Create `ClientConnector` actor with custom `SslConnector` instance.
///
/// By default `ClientConnector` uses very simple ssl configuration.
/// With `with_connector` method it is possible to use custom `SslConnector`
/// object.
///
/// ```rust
/// # #![cfg(feature="alpn")]
/// # extern crate actix;
/// # extern crate actix_web;
/// # extern crate futures;
/// # use futures::Future;
/// # use std::io::Write;
/// extern crate openssl;
/// use actix::prelude::*;
/// use actix_web::client::{Connect, ClientConnector};
///
/// use openssl::ssl::{SslMethod, SslConnector};
///
/// fn main() {
/// let sys = System::new("test");
///
/// // Start `ClientConnector` with custom `SslConnector`
/// let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build();
/// let conn: Address<_> = ClientConnector::with_connector(ssl_conn).start();
///
/// Arbiter::handle().spawn({
2018-02-13 16:50:49 +01:00
/// conn.send(
2018-01-31 18:28:53 +01:00
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
2018-01-30 20:17:17 +01:00
/// .map_err(|_| ())
/// .and_then(|res| {
/// if let Ok(mut stream) = res {
/// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
/// }
2018-02-13 07:56:47 +01:00
/// # Arbiter::system().do_send(actix::msgs::SystemExit(0));
2018-01-30 20:17:17 +01:00
/// Ok(())
/// })
/// });
///
/// sys.run();
/// }
/// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector {
ClientConnector {
connector: connector
}
}
}
2018-01-30 08:01:20 +01:00
impl Handler<Connect> for ClientConnector {
2018-02-12 21:17:30 +01:00
type Result = ActorResponse<ClientConnector, Connection, ClientConnectorError>;
2018-01-30 08:01:20 +01:00
fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result {
let uri = &msg.0;
2018-01-30 20:17:17 +01:00
// host name is required
2018-01-30 08:01:20 +01:00
if uri.host().is_none() {
2018-02-12 21:17:30 +01:00
return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl))
2018-01-30 08:01:20 +01:00
}
2018-01-30 20:17:17 +01:00
// supported protocols
2018-01-30 08:01:20 +01:00
let proto = match uri.scheme_part() {
Some(scheme) => match Protocol::from(scheme.as_str()) {
Some(proto) => proto,
2018-02-12 21:17:30 +01:00
None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)),
2018-01-30 08:01:20 +01:00
},
2018-02-12 21:17:30 +01:00
None => return ActorResponse::reply(Err(ClientConnectorError::InvalidUrl)),
2018-01-30 08:01:20 +01:00
};
2018-01-30 20:17:17 +01:00
// check ssl availability
if proto.is_secure() && !HAS_OPENSSL { //&& !HAS_TLS {
2018-02-12 21:17:30 +01:00
return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported))
2018-01-30 20:17:17 +01:00
}
let host = uri.host().unwrap().to_owned();
2018-01-30 08:01:20 +01:00
let port = uri.port().unwrap_or_else(|| proto.port());
2018-02-12 21:17:30 +01:00
ActorResponse::async(
2018-01-30 08:01:20 +01:00
Connector::from_registry()
2018-02-13 07:56:47 +01:00
.send(ResolveConnect::host_and_port(&host, port))
2018-02-13 01:08:04 +01:00
.into_actor(self)
2018-01-30 08:01:20 +01:00
.map_err(|_, _, _| ClientConnectorError::Disconnected)
2018-01-30 20:17:17 +01:00
.and_then(move |res, _act, _| {
#[cfg(feature="alpn")]
match res {
Err(err) => fut::Either::B(fut::err(err.into())),
Ok(stream) => {
if proto.is_secure() {
fut::Either::A(
_act.connector.connect_async(&host, stream)
.map_err(|e| ClientConnectorError::SslError(e))
.map(|stream| Connection{stream: Box::new(stream)})
.into_actor(_act))
} else {
fut::Either::B(fut::ok(Connection{stream: Box::new(stream)}))
}
}
}
#[cfg(not(feature="alpn"))]
match res {
Err(err) => fut::err(err.into()),
Ok(stream) => {
if proto.is_secure() {
fut::err(ClientConnectorError::SslIsNotSupported)
} else {
fut::ok(Connection{stream: Box::new(stream)})
}
}
}
2018-01-30 08:01:20 +01:00
}))
}
}
2018-01-30 20:17:17 +01:00
#[derive(PartialEq, Hash, Debug, Clone, Copy)]
2018-01-30 08:01:20 +01:00
enum Protocol {
Http,
Https,
Ws,
Wss,
}
impl Protocol {
fn from(s: &str) -> Option<Protocol> {
match s {
"http" => Some(Protocol::Http),
"https" => Some(Protocol::Https),
"ws" => Some(Protocol::Ws),
"wss" => Some(Protocol::Wss),
_ => None,
}
}
2018-01-30 20:17:17 +01:00
fn is_secure(&self) -> bool {
match *self {
Protocol::Https | Protocol::Wss => true,
_ => false,
}
}
2018-01-30 08:01:20 +01:00
fn port(&self) -> u16 {
match *self {
Protocol::Http | Protocol::Ws => 80,
Protocol::Https | Protocol::Wss => 443
}
}
}
pub struct Connection {
stream: Box<IoStream>,
}
impl Connection {
pub fn stream(&mut self) -> &mut IoStream {
&mut *self.stream
}
}
impl IoStream for Connection {
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
IoStream::shutdown(&mut *self.stream, how)
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
IoStream::set_nodelay(&mut *self.stream, nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
IoStream::set_linger(&mut *self.stream, dur)
}
}
impl io::Read for Connection {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stream.read(buf)
}
}
impl AsyncRead for Connection {}
impl io::Write for Connection {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stream.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stream.flush()
}
}
impl AsyncWrite for Connection {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.stream.shutdown()
}
}