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"
|
env_logger = "0.5"
|
||||||
actix = "^0.4.2"
|
actix = "^0.4.2"
|
||||||
actix-web = { path = "../../", features=["alpn"] }
|
actix-web = { path = "../../", features=["alpn"] }
|
||||||
openssl = { version="0.10", features = ["v110"] }
|
openssl = { version="0.10" }
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
name = "websocket"
|
name = "websocket"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "server"
|
name = "server"
|
||||||
|
|
|
@ -6,19 +6,33 @@ use std::time::Duration;
|
||||||
|
|
||||||
use actix::{fut, Actor, ActorFuture, Arbiter, ArbiterService, Context,
|
use actix::{fut, Actor, ActorFuture, Arbiter, ArbiterService, Context,
|
||||||
Handler, Response, ResponseType, Supervised};
|
Handler, Response, ResponseType, Supervised};
|
||||||
|
use actix::fut::WrapFuture;
|
||||||
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};
|
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};
|
||||||
|
|
||||||
use http::Uri;
|
use http::{Uri, HttpTryFrom, Error as HttpError};
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
use tokio_core::reactor::Timeout;
|
use tokio_core::reactor::Timeout;
|
||||||
use tokio_core::net::{TcpStream, TcpStreamNew};
|
use tokio_core::net::{TcpStream, TcpStreamNew};
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
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;
|
use server::IoStream;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Connect(pub Uri);
|
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 {
|
impl ResponseType for Connect {
|
||||||
type Item = Connection;
|
type Item = Connection;
|
||||||
type Error = ClientConnectorError;
|
type Error = ClientConnectorError;
|
||||||
|
@ -34,6 +48,11 @@ pub enum ClientConnectorError {
|
||||||
#[fail(display="SSL is not supported")]
|
#[fail(display="SSL is not supported")]
|
||||||
SslIsNotSupported,
|
SslIsNotSupported,
|
||||||
|
|
||||||
|
/// SSL error
|
||||||
|
#[cfg(feature="alpn")]
|
||||||
|
#[fail(display="{}", _0)]
|
||||||
|
SslError(OpensslError),
|
||||||
|
|
||||||
/// Connection error
|
/// Connection error
|
||||||
#[fail(display = "{}", _0)]
|
#[fail(display = "{}", _0)]
|
||||||
Connector(ConnectorError),
|
Connector(ConnectorError),
|
||||||
|
@ -57,8 +76,9 @@ impl From<ConnectorError> for ClientConnectorError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct ClientConnector {
|
pub struct ClientConnector {
|
||||||
|
#[cfg(feature="alpn")]
|
||||||
|
connector: SslConnector,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for ClientConnector {
|
impl Actor for ClientConnector {
|
||||||
|
@ -69,16 +89,86 @@ impl Supervised for ClientConnector {}
|
||||||
|
|
||||||
impl ArbiterService 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 {
|
impl Handler<Connect> for ClientConnector {
|
||||||
type Result = Response<ClientConnector, Connect>;
|
type Result = Response<ClientConnector, Connect>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: Connect, _: &mut Self::Context) -> Self::Result {
|
||||||
let uri = &msg.0;
|
let uri = &msg.0;
|
||||||
|
|
||||||
|
// host name is required
|
||||||
if uri.host().is_none() {
|
if uri.host().is_none() {
|
||||||
return Self::reply(Err(ClientConnectorError::InvalidUrl))
|
return Self::reply(Err(ClientConnectorError::InvalidUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// supported protocols
|
||||||
let proto = match uri.scheme_part() {
|
let proto = match uri.scheme_part() {
|
||||||
Some(scheme) => match Protocol::from(scheme.as_str()) {
|
Some(scheme) => match Protocol::from(scheme.as_str()) {
|
||||||
Some(proto) => proto,
|
Some(proto) => proto,
|
||||||
|
@ -87,20 +177,51 @@ impl Handler<Connect> for ClientConnector {
|
||||||
None => return Self::reply(Err(ClientConnectorError::InvalidUrl)),
|
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());
|
let port = uri.port().unwrap_or_else(|| proto.port());
|
||||||
|
|
||||||
Self::async_reply(
|
Self::async_reply(
|
||||||
Connector::from_registry()
|
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)
|
.map_err(|_, _, _| ClientConnectorError::Disconnected)
|
||||||
.and_then(|res, _, _| match res {
|
.and_then(move |res, _act, _| {
|
||||||
Ok(stream) => fut::ok(Connection{stream: Box::new(stream)}),
|
#[cfg(feature="alpn")]
|
||||||
Err(err) => fut::err(err.into())
|
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 {
|
enum Protocol {
|
||||||
Http,
|
Http,
|
||||||
Https,
|
Https,
|
||||||
|
@ -119,6 +240,13 @@ impl Protocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_secure(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Protocol::Https | Protocol::Wss => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn port(&self) -> u16 {
|
fn port(&self) -> u16 {
|
||||||
match *self {
|
match *self {
|
||||||
Protocol::Http | Protocol::Ws => 80,
|
Protocol::Http | Protocol::Ws => 80,
|
||||||
|
|
|
@ -8,3 +8,4 @@ pub(crate) use self::writer::HttpClientWriter;
|
||||||
pub use self::request::{ClientRequest, ClientRequestBuilder};
|
pub use self::request::{ClientRequest, ClientRequestBuilder};
|
||||||
pub use self::response::ClientResponse;
|
pub use self::response::ClientResponse;
|
||||||
pub use self::parser::{HttpResponseParser, HttpResponseParserError};
|
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 handler;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
@ -143,19 +141,15 @@ pub use http::{Method, StatusCode, Version};
|
||||||
#[cfg(feature="tls")]
|
#[cfg(feature="tls")]
|
||||||
pub use native_tls::Pkcs12;
|
pub use native_tls::Pkcs12;
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[cfg(feature="openssl")]
|
|
||||||
pub use openssl::pkcs12::Pkcs12;
|
|
||||||
|
|
||||||
#[cfg(feature="openssl")]
|
#[cfg(feature="openssl")]
|
||||||
pub(crate) const HAS_OPENSSL: bool = true;
|
pub(crate) const HAS_OPENSSL: bool = true;
|
||||||
#[cfg(not(feature="openssl"))]
|
#[cfg(not(feature="openssl"))]
|
||||||
pub(crate) const HAS_OPENSSL: bool = false;
|
pub(crate) const HAS_OPENSSL: bool = false;
|
||||||
|
|
||||||
#[cfg(feature="tls")]
|
// #[cfg(feature="tls")]
|
||||||
pub(crate) const HAS_TLS: bool = true;
|
// pub(crate) const HAS_TLS: bool = true;
|
||||||
#[cfg(not(feature="tls"))]
|
// #[cfg(not(feature="tls"))]
|
||||||
pub(crate) const HAS_TLS: bool = false;
|
// pub(crate) const HAS_TLS: bool = false;
|
||||||
|
|
||||||
|
|
||||||
pub mod headers {
|
pub mod headers {
|
||||||
|
|
|
@ -99,21 +99,31 @@ pub struct WsClient {
|
||||||
http_err: Option<HttpError>,
|
http_err: Option<HttpError>,
|
||||||
origin: Option<HeaderValue>,
|
origin: Option<HeaderValue>,
|
||||||
protocols: Option<String>,
|
protocols: Option<String>,
|
||||||
|
conn: Address<ClientConnector>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WsClient {
|
impl WsClient {
|
||||||
|
|
||||||
|
/// Create new websocket connection
|
||||||
pub fn new<S: AsRef<str>>(uri: S) -> WsClient {
|
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 {
|
let mut cl = WsClient {
|
||||||
request: ClientRequest::build(),
|
request: ClientRequest::build(),
|
||||||
err: None,
|
err: None,
|
||||||
http_err: None,
|
http_err: None,
|
||||||
origin: None,
|
origin: None,
|
||||||
protocols: None };
|
protocols: None,
|
||||||
|
conn: conn,
|
||||||
|
};
|
||||||
cl.request.uri(uri.as_ref());
|
cl.request.uri(uri.as_ref());
|
||||||
cl
|
cl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set supported websocket protocols
|
||||||
pub fn protocols<U, V>(&mut self, protos: U) -> &mut Self
|
pub fn protocols<U, V>(&mut self, protos: U) -> &mut Self
|
||||||
where U: IntoIterator<Item=V> + 'static,
|
where U: IntoIterator<Item=V> + 'static,
|
||||||
V: AsRef<str>
|
V: AsRef<str>
|
||||||
|
@ -125,6 +135,7 @@ impl WsClient {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set cookie for handshake request
|
||||||
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
|
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
|
||||||
self.request.cookie(cookie);
|
self.request.cookie(cookie);
|
||||||
self
|
self
|
||||||
|
@ -141,6 +152,7 @@ impl WsClient {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set request header
|
||||||
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
where HeaderName: HttpTryFrom<K>, HeaderValue: HttpTryFrom<V>
|
where HeaderName: HttpTryFrom<K>, HeaderValue: HttpTryFrom<V>
|
||||||
{
|
{
|
||||||
|
@ -148,6 +160,7 @@ impl WsClient {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Connect to websocket server and do ws handshake
|
||||||
pub fn connect(&mut self) -> Result<Box<WsClientFuture>, WsClientError> {
|
pub fn connect(&mut self) -> Result<Box<WsClientFuture>, WsClientError> {
|
||||||
if let Some(e) = self.err.take() {
|
if let Some(e) = self.err.take() {
|
||||||
return Err(e)
|
return Err(e)
|
||||||
|
@ -183,7 +196,7 @@ impl WsClient {
|
||||||
|
|
||||||
// get connection and start handshake
|
// get connection and start handshake
|
||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
ClientConnector::from_registry().call_fut(Connect(request.uri().clone()))
|
self.conn.call_fut(Connect(request.uri().clone()))
|
||||||
.map_err(|_| WsClientError::Disconnected)
|
.map_err(|_| WsClientError::Disconnected)
|
||||||
.and_then(|res| match res {
|
.and_then(|res| match res {
|
||||||
Ok(stream) => Either::A(WsHandshake::new(stream, request)),
|
Ok(stream) => Either::A(WsHandshake::new(stream, request)),
|
||||||
|
|
Loading…
Reference in a new issue