mirror of
https://github.com/actix/actix-web.git
synced 2024-12-30 12:00:38 +00:00
add client ssl support
This commit is contained in:
parent
a02e0dfab6
commit
5cbaf3a1b8
6 changed files with 157 additions and 20 deletions
|
@ -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" }
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
name = "websocket"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
workspace = "../.."
|
||||
|
||||
[[bin]]
|
||||
name = "server"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -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 {
|
||||
|
|
|
@ -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)),
|
||||
|
|
Loading…
Reference in a new issue