1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-06-26 18:37:41 +02:00

initial work on client connector

This commit is contained in:
Nikolay Kim
2018-01-29 23:01:20 -08:00
parent 5cc3bba5cc
commit a02e0dfab6
8 changed files with 238 additions and 185 deletions

View File

@ -1,4 +1,5 @@
//! Http client request
#![allow(unused_imports, dead_code)]
use std::{io, str};
use std::rc::Rc;
use std::time::Duration;
@ -12,9 +13,11 @@ use http::{HttpTryFrom, StatusCode, Error as HttpError};
use http::header::{self, HeaderName, HeaderValue};
use sha1::Sha1;
use futures::{Async, Future, Poll, Stream};
// use futures::unsync::oneshot;
use futures::future::{Either, err as FutErr};
use tokio_core::net::TcpStream;
use actix::prelude::*;
use body::Binary;
use error::UrlParseError;
use server::shared::SharedBytes;
@ -22,14 +25,14 @@ use server::shared::SharedBytes;
use server::{utils, IoStream};
use client::{ClientRequest, ClientRequestBuilder,
HttpResponseParser, HttpResponseParserError, HttpClientWriter};
use client::connect::{Connect, Connection, ClientConnector, ClientConnectorError};
use super::Message;
use super::proto::{CloseCode, OpCode};
use super::frame::Frame;
use super::connect::{TcpConnector, TcpConnectorError};
pub type WsClientFuture<T> =
Future<Item=(WsClientReader<T>, WsClientWriter<T>), Error=WsClientError>;
pub type WsClientFuture =
Future<Item=(WsClientReader, WsClientWriter), Error=WsClientError>;
/// Websockt client error
@ -52,7 +55,7 @@ pub enum WsClientError {
#[fail(display="Response parsing error")]
ResponseParseError(HttpResponseParserError),
#[fail(display="{}", _0)]
Connection(TcpConnectorError),
Connector(ClientConnectorError),
#[fail(display="{}", _0)]
Io(io::Error),
#[fail(display="Disconnected")]
@ -71,9 +74,9 @@ impl From<UrlParseError> for WsClientError {
}
}
impl From<TcpConnectorError> for WsClientError {
fn from(err: TcpConnectorError) -> WsClientError {
WsClientError::Connection(err)
impl From<ClientConnectorError> for WsClientError {
fn from(err: ClientConnectorError) -> WsClientError {
WsClientError::Connector(err)
}
}
@ -145,7 +148,7 @@ impl WsClient {
self
}
pub fn connect(&mut self) -> Result<Box<WsClientFuture<TcpStream>>, WsClientError> {
pub fn connect(&mut self) -> Result<Box<WsClientFuture>, WsClientError> {
if let Some(e) = self.err.take() {
return Err(e)
}
@ -178,19 +181,20 @@ impl WsClient {
return Err(WsClientError::InvalidUrl);
}
let connect = TcpConnector::new(
request.uri().host().unwrap(),
request.uri().port().unwrap_or(80), Duration::from_secs(5));
// get connection and start handshake
Ok(Box::new(
connect
.from_err()
.and_then(move |stream| WsHandshake::new(stream, request))))
ClientConnector::from_registry().call_fut(Connect(request.uri().clone()))
.map_err(|_| WsClientError::Disconnected)
.and_then(|res| match res {
Ok(stream) => Either::A(WsHandshake::new(stream, request)),
Err(err) => Either::B(FutErr(err.into())),
})
))
}
}
struct WsInner<T> {
stream: T,
struct WsInner {
conn: Connection,
writer: HttpClientWriter,
parser: HttpResponseParser,
parser_buf: BytesMut,
@ -198,15 +202,15 @@ struct WsInner<T> {
error_sent: bool,
}
struct WsHandshake<T> {
inner: Option<WsInner<T>>,
struct WsHandshake {
inner: Option<WsInner>,
request: ClientRequest,
sent: bool,
key: String,
}
impl<T: IoStream> WsHandshake<T> {
fn new(stream: T, mut request: ClientRequest) -> WsHandshake<T> {
impl WsHandshake {
fn new(conn: Connection, mut request: ClientRequest) -> WsHandshake {
// Generate a random key for the `Sec-WebSocket-Key` header.
// a base64-encoded (see Section 4 of [RFC4648]) value that,
// when decoded, is 16 bytes in length (RFC 6455)
@ -218,7 +222,7 @@ impl<T: IoStream> WsHandshake<T> {
HeaderValue::try_from(key.as_str()).unwrap());
let inner = WsInner {
stream: stream,
conn: conn,
writer: HttpClientWriter::new(SharedBytes::default()),
parser: HttpResponseParser::default(),
parser_buf: BytesMut::new(),
@ -235,8 +239,8 @@ impl<T: IoStream> WsHandshake<T> {
}
}
impl<T: IoStream> Future for WsHandshake<T> {
type Item = (WsClientReader<T>, WsClientWriter<T>);
impl Future for WsHandshake {
type Item = (WsClientReader, WsClientWriter);
type Error = WsClientError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@ -246,11 +250,11 @@ impl<T: IoStream> Future for WsHandshake<T> {
self.sent = true;
inner.writer.start(&mut self.request);
}
if let Err(err) = inner.writer.poll_completed(&mut inner.stream, false) {
if let Err(err) = inner.writer.poll_completed(&mut inner.conn, false) {
return Err(err.into())
}
match inner.parser.parse(&mut inner.stream, &mut inner.parser_buf) {
match inner.parser.parse(&mut inner.conn, &mut inner.parser_buf) {
Ok(Async::Ready(resp)) => {
// verify response
if resp.status() != StatusCode::SWITCHING_PROTOCOLS {
@ -311,22 +315,22 @@ impl<T: IoStream> Future for WsHandshake<T> {
}
struct Inner<T> {
inner: WsInner<T>,
struct Inner {
inner: WsInner,
}
pub struct WsClientReader<T> {
inner: Rc<UnsafeCell<Inner<T>>>
pub struct WsClientReader {
inner: Rc<UnsafeCell<Inner>>
}
impl<T> WsClientReader<T> {
impl WsClientReader {
#[inline]
fn as_mut(&mut self) -> &mut Inner<T> {
fn as_mut(&mut self) -> &mut Inner {
unsafe{ &mut *self.inner.get() }
}
}
impl<T: IoStream> Stream for WsClientReader<T> {
impl Stream for WsClientReader {
type Item = Message;
type Error = WsClientError;
@ -334,7 +338,7 @@ impl<T: IoStream> Stream for WsClientReader<T> {
let inner = self.as_mut();
let mut done = false;
match utils::read_from_io(&mut inner.inner.stream, &mut inner.inner.parser_buf) {
match utils::read_from_io(&mut inner.inner.conn, &mut inner.inner.parser_buf) {
Ok(Async::Ready(0)) => {
done = true;
inner.inner.closed = true;
@ -345,7 +349,7 @@ impl<T: IoStream> Stream for WsClientReader<T> {
}
// write
let _ = inner.inner.writer.poll_completed(&mut inner.inner.stream, false);
let _ = inner.inner.writer.poll_completed(&mut inner.inner.conn, false);
// read
match Frame::parse(&mut inner.inner.parser_buf) {
@ -406,18 +410,18 @@ impl<T: IoStream> Stream for WsClientReader<T> {
}
}
pub struct WsClientWriter<T> {
inner: Rc<UnsafeCell<Inner<T>>>
pub struct WsClientWriter {
inner: Rc<UnsafeCell<Inner>>
}
impl<T: IoStream> WsClientWriter<T> {
impl WsClientWriter {
#[inline]
fn as_mut(&mut self) -> &mut Inner<T> {
fn as_mut(&mut self) -> &mut Inner {
unsafe{ &mut *self.inner.get() }
}
}
impl<T: IoStream> WsClientWriter<T> {
impl WsClientWriter {
/// Write payload
#[inline]

View File

@ -1,139 +0,0 @@
use std::io;
use std::net::SocketAddr;
use std::collections::VecDeque;
use std::time::Duration;
use actix::Arbiter;
use trust_dns_resolver::ResolverFuture;
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use trust_dns_resolver::lookup_ip::LookupIpFuture;
use futures::{Async, Future, Poll};
use tokio_core::reactor::Timeout;
use tokio_core::net::{TcpStream, TcpStreamNew};
#[derive(Fail, Debug)]
pub enum TcpConnectorError {
/// Failed to resolve the hostname
#[fail(display = "Failed resolving hostname: {}", _0)]
Dns(String),
/// Address is invalid
#[fail(display = "Invalid input: {}", _0)]
InvalidInput(&'static str),
/// Connecting took too long
#[fail(display = "Timeout out while establishing connection")]
Timeout,
/// Connection io error
#[fail(display = "{}", _0)]
IoError(io::Error),
}
pub struct TcpConnector {
lookup: Option<LookupIpFuture>,
port: u16,
ips: VecDeque<SocketAddr>,
error: Option<TcpConnectorError>,
timeout: Timeout,
stream: Option<TcpStreamNew>,
}
impl TcpConnector {
pub fn new<S: AsRef<str>>(addr: S, port: u16, timeout: Duration) -> TcpConnector {
// try to parse as a regular SocketAddr first
if let Ok(addr) = addr.as_ref().parse() {
let mut ips = VecDeque::new();
ips.push_back(addr);
TcpConnector {
lookup: None,
port: port,
ips: ips,
error: None,
stream: None,
timeout: Timeout::new(timeout, Arbiter::handle()).unwrap() }
} else {
// we need to do dns resolution
let resolve = match ResolverFuture::from_system_conf(Arbiter::handle()) {
Ok(resolve) => resolve,
Err(err) => {
warn!("Can not create system dns resolver: {}", err);
ResolverFuture::new(
ResolverConfig::default(),
ResolverOpts::default(),
Arbiter::handle())
}
};
TcpConnector {
lookup: Some(resolve.lookup_ip(addr.as_ref())),
port: port,
ips: VecDeque::new(),
error: None,
stream: None,
timeout: Timeout::new(timeout, Arbiter::handle()).unwrap() }
}
}
}
impl Future for TcpConnector {
type Item = TcpStream;
type Error = TcpConnectorError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(err) = self.error.take() {
Err(err)
} else {
// timeout
if let Ok(Async::Ready(_)) = self.timeout.poll() {
return Err(TcpConnectorError::Timeout)
}
// lookip ips
if let Some(mut lookup) = self.lookup.take() {
match lookup.poll() {
Ok(Async::NotReady) => {
self.lookup = Some(lookup);
return Ok(Async::NotReady)
},
Ok(Async::Ready(ips)) => {
let port = self.port;
let ips = ips.iter().map(|ip| SocketAddr::new(ip, port));
self.ips.extend(ips);
if self.ips.is_empty() {
return Err(TcpConnectorError::Dns(
"Expect at least one A dns record".to_owned()))
}
},
Err(err) => return Err(TcpConnectorError::Dns(format!("{}", err))),
}
}
// connect
loop {
if let Some(mut new) = self.stream.take() {
match new.poll() {
Ok(Async::Ready(sock)) =>
return Ok(Async::Ready(sock)),
Ok(Async::NotReady) => {
self.stream = Some(new);
return Ok(Async::NotReady)
},
Err(err) => {
if self.ips.is_empty() {
return Err(TcpConnectorError::IoError(err))
}
}
}
}
// try to connect
let addr = self.ips.pop_front().unwrap();
self.stream = Some(TcpStream::connect(&addr, Arbiter::handle()));
}
}
}
}

View File

@ -61,8 +61,6 @@ mod context;
mod mask;
mod client;
mod connect;
use self::frame::Frame;
use self::proto::{hash_key, OpCode};
pub use self::proto::CloseCode;