1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-10-03 08:41:55 +00:00

add client ssl support

This commit is contained in:
Nikolay Kim 2018-01-30 11:17:17 -08:00
parent a02e0dfab6
commit 5cbaf3a1b8
6 changed files with 157 additions and 20 deletions

View file

@ -12,4 +12,4 @@ path = "src/main.rs"
env_logger = "0.5"
actix = "^0.4.2"
actix-web = { path = "../../", features=["alpn"] }
openssl = { version="0.10", features = ["v110"] }
openssl = { version="0.10" }

View file

@ -2,6 +2,7 @@
name = "websocket"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "server"

View file

@ -6,19 +6,33 @@ use std::time::Duration;
use actix::{fut, Actor, ActorFuture, Arbiter, ArbiterService, Context,
Handler, Response, ResponseType, Supervised};
use actix::fut::WrapFuture;
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};
use http::Uri;
use http::{Uri, HttpTryFrom, Error as HttpError};
use futures::{Async, Future, Poll};
use tokio_core::reactor::Timeout;
use tokio_core::net::{TcpStream, TcpStreamNew};
use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature="alpn")]
use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode, Error as OpensslError};
#[cfg(feature="alpn")]
use tokio_openssl::SslConnectorExt;
use HAS_OPENSSL;
use server::IoStream;
#[derive(Debug)]
pub struct Connect(pub Uri);
impl Connect {
pub fn new<U>(uri: U) -> Result<Connect, HttpError> where Uri: HttpTryFrom<U> {
Ok(Connect(Uri::try_from(uri).map_err(|e| e.into())?))
}
}
impl ResponseType for Connect {
type Item = Connection;
type Error = ClientConnectorError;
@ -34,6 +48,11 @@ pub enum ClientConnectorError {
#[fail(display="SSL is not supported")]
SslIsNotSupported,
/// SSL error
#[cfg(feature="alpn")]
#[fail(display="{}", _0)]
SslError(OpensslError),
/// Connection error
#[fail(display = "{}", _0)]
Connector(ConnectorError),
@ -57,8 +76,9 @@ impl From<ConnectorError> for ClientConnectorError {
}
}
#[derive(Debug, Default)]
pub struct ClientConnector {
#[cfg(feature="alpn")]
connector: SslConnector,
}
impl Actor for ClientConnector {
@ -69,16 +89,86 @@ impl Supervised for ClientConnector {}
impl ArbiterService for ClientConnector {}
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({
/// conn.call_fut(
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connecto to host
/// .map_err(|_| ())
/// .and_then(|res| {
/// if let Ok(mut stream) = res {
/// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
/// }
/// # Arbiter::system().send(actix::msgs::SystemExit(0));
/// Ok(())
/// })
/// });
///
/// sys.run();
/// }
/// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector {
ClientConnector {
connector: connector
}
}
}
impl Handler<Connect> for ClientConnector {
type Result = Response<ClientConnector, Connect>;
fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result {
let uri = &msg.0;
// host name is required
if uri.host().is_none() {
return Self::reply(Err(ClientConnectorError::InvalidUrl))
}
// supported protocols
let proto = match uri.scheme_part() {
Some(scheme) => match Protocol::from(scheme.as_str()) {
Some(proto) => proto,
@ -87,20 +177,51 @@ impl Handler<Connect> for ClientConnector {
None => return Self::reply(Err(ClientConnectorError::InvalidUrl)),
};
// check ssl availability
if proto.is_secure() && !HAS_OPENSSL { //&& !HAS_TLS {
return Self::reply(Err(ClientConnectorError::SslIsNotSupported))
}
let host = uri.host().unwrap().to_owned();
let port = uri.port().unwrap_or_else(|| proto.port());
Self::async_reply(
Connector::from_registry()
.call(self, ResolveConnect::host_and_port(uri.host().unwrap(), port))
.call(self, ResolveConnect::host_and_port(&host, port))
.map_err(|_, _, _| ClientConnectorError::Disconnected)
.and_then(|res, _, _| match res {
Ok(stream) => fut::ok(Connection{stream: Box::new(stream)}),
Err(err) => fut::err(err.into())
.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)})
}
}
}
}))
}
}
#[derive(PartialEq, Hash, Debug)]
#[derive(PartialEq, Hash, Debug, Clone, Copy)]
enum Protocol {
Http,
Https,
@ -119,6 +240,13 @@ impl Protocol {
}
}
fn is_secure(&self) -> bool {
match *self {
Protocol::Https | Protocol::Wss => true,
_ => false,
}
}
fn port(&self) -> u16 {
match *self {
Protocol::Http | Protocol::Ws => 80,

View file

@ -8,3 +8,4 @@ pub(crate) use self::writer::HttpClientWriter;
pub use self::request::{ClientRequest, ClientRequestBuilder};
pub use self::response::ClientResponse;
pub use self::parser::{HttpResponseParser, HttpResponseParserError};
pub use self::connect::{Connect, Connection, ClientConnector, ClientConnectorError};

View file

@ -111,9 +111,7 @@ mod resource;
mod handler;
mod pipeline;
#[doc(hidden)]
pub mod client;
pub mod fs;
pub mod ws;
pub mod error;
@ -143,19 +141,15 @@ pub use http::{Method, StatusCode, Version};
#[cfg(feature="tls")]
pub use native_tls::Pkcs12;
#[doc(hidden)]
#[cfg(feature="openssl")]
pub use openssl::pkcs12::Pkcs12;
#[cfg(feature="openssl")]
pub(crate) const HAS_OPENSSL: bool = true;
#[cfg(not(feature="openssl"))]
pub(crate) const HAS_OPENSSL: bool = false;
#[cfg(feature="tls")]
pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature="tls"))]
pub(crate) const HAS_TLS: bool = false;
// #[cfg(feature="tls")]
// pub(crate) const HAS_TLS: bool = true;
// #[cfg(not(feature="tls"))]
// pub(crate) const HAS_TLS: bool = false;
pub mod headers {

View file

@ -99,21 +99,31 @@ pub struct WsClient {
http_err: Option<HttpError>,
origin: Option<HeaderValue>,
protocols: Option<String>,
conn: Address<ClientConnector>,
}
impl WsClient {
/// Create new websocket connection
pub fn new<S: AsRef<str>>(uri: S) -> WsClient {
WsClient::with_connector(uri, ClientConnector::from_registry())
}
/// Create new websocket connection with custom `ClientConnector`
pub fn with_connector<S: AsRef<str>>(uri: S, conn: Address<ClientConnector>) -> WsClient {
let mut cl = WsClient {
request: ClientRequest::build(),
err: None,
http_err: None,
origin: None,
protocols: None };
protocols: None,
conn: conn,
};
cl.request.uri(uri.as_ref());
cl
}
/// Set supported websocket protocols
pub fn protocols<U, V>(&mut self, protos: U) -> &mut Self
where U: IntoIterator<Item=V> + 'static,
V: AsRef<str>
@ -125,6 +135,7 @@ impl WsClient {
self
}
/// Set cookie for handshake request
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
self.request.cookie(cookie);
self
@ -141,6 +152,7 @@ impl WsClient {
self
}
/// Set request header
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where HeaderName: HttpTryFrom<K>, HeaderValue: HttpTryFrom<V>
{
@ -148,6 +160,7 @@ impl WsClient {
self
}
/// Connect to websocket server and do ws handshake
pub fn connect(&mut self) -> Result<Box<WsClientFuture>, WsClientError> {
if let Some(e) = self.err.take() {
return Err(e)
@ -183,7 +196,7 @@ impl WsClient {
// get connection and start handshake
Ok(Box::new(
ClientConnector::from_registry().call_fut(Connect(request.uri().clone()))
self.conn.call_fut(Connect(request.uri().clone()))
.map_err(|_| WsClientError::Disconnected)
.and_then(|res| match res {
Ok(stream) => Either::A(WsHandshake::new(stream, request)),