feat(client): provide tower::Service support for clients (#1915)
This commit is contained in:
committed by
Sean McArthur
parent
eef407d60e
commit
eee2a72879
@@ -27,7 +27,7 @@ futures-core-preview = { version = "=0.3.0-alpha.18" }
|
|||||||
futures-channel-preview = { version = "=0.3.0-alpha.18" }
|
futures-channel-preview = { version = "=0.3.0-alpha.18" }
|
||||||
futures-util-preview = { version = "=0.3.0-alpha.18" }
|
futures-util-preview = { version = "=0.3.0-alpha.18" }
|
||||||
http = "0.1.15"
|
http = "0.1.15"
|
||||||
http-body = { git = "https://github.com/hyperium/http-body" }
|
http-body = "0.2.0-alpha.1"
|
||||||
httparse = "1.0"
|
httparse = "1.0"
|
||||||
h2 = { git = "https://github.com/hyperium/h2" }
|
h2 = { git = "https://github.com/hyperium/h2" }
|
||||||
iovec = "0.1"
|
iovec = "0.1"
|
||||||
@@ -38,6 +38,7 @@ pin-utils = "=0.1.0-alpha.4"
|
|||||||
time = "0.1"
|
time = "0.1"
|
||||||
tokio = { version = "=0.2.0-alpha.4", optional = true, default-features = false, features = ["rt-full"] }
|
tokio = { version = "=0.2.0-alpha.4", optional = true, default-features = false, features = ["rt-full"] }
|
||||||
tower-service = "=0.3.0-alpha.1"
|
tower-service = "=0.3.0-alpha.1"
|
||||||
|
tower-make = { version = "0.1.0-alpha.2", features = ['io'] }
|
||||||
tokio-executor = { version = "=0.2.0-alpha.4", features = ["blocking"] }
|
tokio-executor = { version = "=0.2.0-alpha.4", features = ["blocking"] }
|
||||||
tokio-io = "=0.2.0-alpha.4"
|
tokio-io = "=0.2.0-alpha.4"
|
||||||
tokio-sync = "=0.2.0-alpha.4"
|
tokio-sync = "=0.2.0-alpha.4"
|
||||||
|
|||||||
26
examples/tower_client.rs
Normal file
26
examples/tower_client.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
use hyper::client::service::{Connect, Service, MakeService};
|
||||||
|
use hyper::client::conn::Builder;
|
||||||
|
use hyper::client::connect::HttpConnector;
|
||||||
|
use hyper::{Body, Request};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
let mut mk_svc = Connect::new(HttpConnector::new(), Builder::new());
|
||||||
|
|
||||||
|
let uri = "http://127.0.0.1:8080".parse::<http::Uri>()?;
|
||||||
|
|
||||||
|
|
||||||
|
let mut svc = mk_svc.make_service(uri.clone()).await?;
|
||||||
|
|
||||||
|
let body = Body::empty();
|
||||||
|
|
||||||
|
let req = Request::get(uri).body(body)?;
|
||||||
|
let res = svc.call(req).await?;
|
||||||
|
|
||||||
|
println!("RESPONSE={:?}", res);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ use std::sync::Arc;
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{self, Either, FutureExt as _};
|
use futures_util::future::{self, Either, FutureExt as _};
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
use crate::body::Payload;
|
use crate::body::Payload;
|
||||||
use crate::common::{Exec, Future, Pin, Poll, task};
|
use crate::common::{Exec, Future, Pin, Poll, task};
|
||||||
@@ -242,18 +243,21 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO(0.12.0): when we change from tokio-service to tower.
|
impl<B> Service<Request<B>> for SendRequest<B>
|
||||||
impl<T, B> Service for SendRequest<T, B> {
|
where
|
||||||
type Request = Request<B>;
|
B: Payload + 'static, {
|
||||||
type Response = Response;
|
type Response = Response<Body>;
|
||||||
type Error = ::Error;
|
type Error = crate::Error;
|
||||||
type Future = ResponseFuture;
|
type Future = ResponseFuture;
|
||||||
|
|
||||||
fn call(&self, req: Self::Request) -> Self::Future {
|
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||||
|
self.send_request(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
impl<B> fmt::Debug for SendRequest<B> {
|
impl<B> fmt::Debug for SendRequest<B> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use std::mem;
|
|||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use http::uri::Scheme;
|
use http::uri::{Scheme, Uri};
|
||||||
|
use futures_util::{TryFutureExt, FutureExt};
|
||||||
use net2::TcpBuilder;
|
use net2::TcpBuilder;
|
||||||
use tokio_net::driver::Handle;
|
use tokio_net::driver::Handle;
|
||||||
use tokio_net::tcp::TcpStream;
|
use tokio_net::tcp::TcpStream;
|
||||||
@@ -248,6 +249,63 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R> tower_service::Service<Uri> for HttpConnector<R>
|
||||||
|
where
|
||||||
|
R: Resolve + Clone + Send + Sync + 'static,
|
||||||
|
R::Future: Send,
|
||||||
|
{
|
||||||
|
type Response = TcpStream;
|
||||||
|
type Error = io::Error;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Ok(()).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, dst: Uri) -> Self::Future {
|
||||||
|
// TODO: return error here
|
||||||
|
let dst = Destination::try_from_uri(dst).unwrap();
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Http::connect; scheme={}, host={}, port={:?}",
|
||||||
|
dst.scheme(),
|
||||||
|
dst.host(),
|
||||||
|
dst.port(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.enforce_http {
|
||||||
|
if dst.uri.scheme_part() != Some(&Scheme::HTTP) {
|
||||||
|
return invalid_url::<R>(InvalidUrl::NotHttp, &self.handle).map_ok(|(s, _)| s).boxed();
|
||||||
|
}
|
||||||
|
} else if dst.uri.scheme_part().is_none() {
|
||||||
|
return invalid_url::<R>(InvalidUrl::MissingScheme, &self.handle).map_ok(|(s, _)| s).boxed();
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = match dst.uri.host() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return invalid_url::<R>(InvalidUrl::MissingAuthority, &self.handle).map_ok(|(s, _)| s).boxed(),
|
||||||
|
};
|
||||||
|
let port = match dst.uri.port_part() {
|
||||||
|
Some(port) => port.as_u16(),
|
||||||
|
None => if dst.uri.scheme_part() == Some(&Scheme::HTTPS) { 443 } else { 80 },
|
||||||
|
};
|
||||||
|
|
||||||
|
let fut = HttpConnecting {
|
||||||
|
state: State::Lazy(self.resolver.clone(), host.into(), self.local_address),
|
||||||
|
handle: self.handle.clone(),
|
||||||
|
happy_eyeballs_timeout: self.happy_eyeballs_timeout,
|
||||||
|
keep_alive_timeout: self.keep_alive_timeout,
|
||||||
|
nodelay: self.nodelay,
|
||||||
|
port,
|
||||||
|
reuse_address: self.reuse_address,
|
||||||
|
send_buffer_size: self.send_buffer_size,
|
||||||
|
recv_buffer_size: self.recv_buffer_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
fut.map_ok(|(s, _)| s).boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HttpInfo {
|
impl HttpInfo {
|
||||||
/// Get the remote address of the transport used.
|
/// Get the remote address of the transport used.
|
||||||
pub fn remote_addr(&self) -> SocketAddr {
|
pub fn remote_addr(&self) -> SocketAddr {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ pub mod conn;
|
|||||||
pub mod connect;
|
pub mod connect;
|
||||||
pub(crate) mod dispatch;
|
pub(crate) mod dispatch;
|
||||||
mod pool;
|
mod pool;
|
||||||
|
pub mod service;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
|||||||
85
src/client/service.rs
Normal file
85
src/client/service.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//! Utilities used to interact with the Tower ecosystem.
|
||||||
|
//!
|
||||||
|
//! This module provides exports of `Service`, `MakeService` and `Connect` which
|
||||||
|
//! all provide hook-ins into the Tower ecosystem.
|
||||||
|
|
||||||
|
use super::conn::{SendRequest, Builder};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use crate::{common::{Poll, task, Pin}, body::Payload};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use tower_make::MakeConnection;
|
||||||
|
|
||||||
|
pub use tower_service::Service;
|
||||||
|
pub use tower_make::MakeService;
|
||||||
|
|
||||||
|
/// Creates a connection via `SendRequest`.
|
||||||
|
///
|
||||||
|
/// This accepts a `hyper::client::conn::Builder` and provides
|
||||||
|
/// a `MakeService` implementation to create connections from some
|
||||||
|
/// target `T`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Connect<C, B, T> {
|
||||||
|
inner: C,
|
||||||
|
builder: Builder,
|
||||||
|
_pd: PhantomData<fn(T, B)>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, B, T> Connect<C, B, T> {
|
||||||
|
/// Create a new `Connect` with some inner connector `C` and a connection
|
||||||
|
/// builder.
|
||||||
|
pub fn new(inner: C, builder: Builder) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
builder,
|
||||||
|
_pd: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, B, T> Service<T> for Connect<C, B, T>
|
||||||
|
where
|
||||||
|
C: MakeConnection<T>,
|
||||||
|
C::Connection: Unpin + Send + 'static,
|
||||||
|
C::Future: Send + 'static,
|
||||||
|
C::Error: Into<Box<dyn StdError + Send + Sync>> + Send,
|
||||||
|
B: Payload + Unpin + 'static,
|
||||||
|
B::Data: Unpin,
|
||||||
|
{
|
||||||
|
type Response = SendRequest<B>;
|
||||||
|
type Error = crate::Error;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.inner.poll_ready(cx).map_err(|e| crate::Error::new(crate::error::Kind::Connect).with(e.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: T) -> Self::Future {
|
||||||
|
let builder = self.builder.clone();
|
||||||
|
let io = self.inner.make_connection(req);
|
||||||
|
|
||||||
|
let fut = async move {
|
||||||
|
match io.await {
|
||||||
|
Ok(io) => {
|
||||||
|
match builder.handshake(io).await {
|
||||||
|
Ok((sr, conn)) => {
|
||||||
|
builder.exec.execute(async move {
|
||||||
|
if let Err(e) = conn.await {
|
||||||
|
debug!("connection error: {:?}", e);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
Ok(sr)
|
||||||
|
},
|
||||||
|
Err(e) => Err(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
let err = crate::Error::new(crate::error::Kind::Connect).with(e.into());
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::pin(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user