add client::Builder to configure Clients

This commit is contained in:
Sean McArthur
2017-09-11 14:25:32 -07:00
parent d0afe30ab3
commit ed472f109c
5 changed files with 86 additions and 50 deletions

View File

@@ -4,21 +4,20 @@ use frame::Reason::*;
use proto::{self, Connection, WindowSize}; use proto::{self, Connection, WindowSize};
use bytes::{Bytes, IntoBuf}; use bytes::{Bytes, IntoBuf};
use futures::{AndThen, Async, AsyncSink, Future, MapErr, Poll, Sink}; use futures::{Async, Future, MapErr, Poll};
use http::{HeaderMap, Request, Response}; use http::{HeaderMap, Request, Response};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_io::io::WriteAll; use tokio_io::io::WriteAll;
use std::fmt; use std::fmt;
use std::io::Error as IoError; use std::io;
use std::marker::PhantomData;
/// In progress H2 connection binding /// In progress H2 connection binding
pub struct Handshake<T: AsyncRead + AsyncWrite, B: IntoBuf = Bytes> { pub struct Handshake<T: AsyncRead + AsyncWrite, B: IntoBuf = Bytes> {
inner: AndThen< inner: MapErr<WriteAll<T, &'static [u8]>, fn(io::Error) -> ::Error>,
MapErr<WriteAll<T, &'static [u8]>, fn(IoError) -> ::Error>, settings: Settings,
Result<Client<T, B>, ::Error>, _marker: PhantomData<B>,
fn((T, &'static [u8])) -> Result<Client<T, B>, ::Error>,
>,
} }
/// Marker type indicating a client peer /// Marker type indicating a client peer
@@ -36,6 +35,12 @@ pub struct Body<B: IntoBuf> {
inner: proto::StreamRef<B::Buf, Peer>, inner: proto::StreamRef<B::Buf, Peer>,
} }
/// Build a Client.
#[derive(Debug, Default)]
pub struct Builder {
settings: Settings,
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Peer; pub(crate) struct Peer;
@@ -43,16 +48,7 @@ impl<T> Client<T, Bytes>
where where
T: AsyncRead + AsyncWrite, T: AsyncRead + AsyncWrite,
{ {
pub fn handshake(io: T) -> Handshake<T, Bytes> {
Client::handshake2(io)
}
}
impl<T, B> Client<T, B>
where
T: AsyncRead + AsyncWrite,
B: IntoBuf,
{
/// Bind an H2 client connection. /// Bind an H2 client connection.
/// ///
/// Returns a future which resolves to the connection value once the H2 /// Returns a future which resolves to the connection value once the H2
@@ -60,41 +56,35 @@ where
/// ///
/// It's important to note that this does not **flush** the outbound /// It's important to note that this does not **flush** the outbound
/// settings to the wire. /// settings to the wire.
pub fn handshake2(io: T) -> Handshake<T, B> { pub fn handshake(io: T) -> Handshake<T, Bytes> {
Builder::default().handshake(io)
}
}
impl Client<(), Bytes> {
/// Creates a Client Builder to customize a Client before binding.
pub fn builder() -> Builder {
Builder::default()
}
}
impl<T, B> Client<T, B>
where T: AsyncRead + AsyncWrite,
B: IntoBuf
{
fn handshake2(io: T, settings: Settings) -> Handshake<T, B> {
use tokio_io::io; use tokio_io::io;
debug!("binding client connection"); debug!("binding client connection");
let bind: fn((T, &'static [u8]))
-> Result<Client<T, B>, ::Error> = |(io, _)| {
debug!("client connection bound");
// Create the codec
let mut codec = Codec::new(io);
// Create the initial SETTINGS frame
let settings = Settings::default();
// Send initial settings frame
match codec.start_send(settings.into()) {
Ok(AsyncSink::Ready) => {
let connection = Connection::new(codec);
Ok(Client {
connection,
})
},
Ok(_) => unreachable!(),
Err(e) => Err(::Error::from(e)),
}
};
let msg: &'static [u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; let msg: &'static [u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
let handshake = io::write_all(io, msg) let handshake = io::write_all(io, msg)
.map_err(::Error::from as fn(IoError) -> ::Error) .map_err(::Error::from as _);
.and_then(bind);
Handshake { Handshake {
inner: handshake, inner: handshake,
settings: settings,
_marker: PhantomData,
} }
} }
@@ -172,6 +162,30 @@ where
} }
} }
// ===== impl Builder =====
impl Builder {
/// Set the initial window size of the remote peer.
pub fn initial_window_size(&mut self, size: u32) -> &mut Self {
self.settings.set_initial_window_size(Some(size));
self
}
/// Bind an H2 client connection.
///
/// Returns a future which resolves to the connection value once the H2
/// handshake has been completed.
///
/// It's important to note that this does not **flush** the outbound
/// settings to the wire.
pub fn handshake<T, B>(&self, io: T) -> Handshake<T, B>
where T: AsyncRead + AsyncWrite,
B: IntoBuf
{
Client::handshake2(io, self.settings.clone())
}
}
// ===== impl Handshake ===== // ===== impl Handshake =====
impl<T, B: IntoBuf> Future for Handshake<T, B> impl<T, B: IntoBuf> Future for Handshake<T, B>
@@ -182,7 +196,21 @@ where
type Error = ::Error; type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.inner.poll() let (io, _) = try_ready!(self.inner.poll());
debug!("client connection bound");
// Create the codec
let mut codec = Codec::new(io);
// Send initial settings frame
codec.buffer(self.settings.clone().into())
.expect("invalid SETTINGS frame");
let connection = Connection::new(codec, &self.settings);
Ok(Async::Ready(Client {
connection,
}))
} }
} }

View File

@@ -66,6 +66,10 @@ impl Settings {
self.initial_window_size self.initial_window_size
} }
pub fn set_initial_window_size(&mut self, size: Option<u32>) {
self.initial_window_size = size;
}
pub fn max_concurrent_streams(&self) -> Option<u32> { pub fn max_concurrent_streams(&self) -> Option<u32> {
self.max_concurrent_streams self.max_concurrent_streams
} }

View File

@@ -58,13 +58,15 @@ where
P: Peer, P: Peer,
B: IntoBuf, B: IntoBuf,
{ {
pub fn new(codec: Codec<T, Prioritized<B::Buf>>) -> Connection<T, P, B> { pub fn new(codec: Codec<T, Prioritized<B::Buf>>, settings: &frame::Settings) -> Connection<T, P, B> {
// TODO: Actually configure // TODO: Actually configure
let streams = Streams::new(streams::Config { let streams = Streams::new(streams::Config {
max_remote_initiated: None, max_remote_initiated: None,
init_remote_window_sz: DEFAULT_INITIAL_WINDOW_SIZE, init_remote_window_sz: DEFAULT_INITIAL_WINDOW_SIZE,
max_local_initiated: None, max_local_initiated: None,
init_local_window_sz: DEFAULT_INITIAL_WINDOW_SIZE, init_local_window_sz: settings
.initial_window_size()
.unwrap_or(DEFAULT_INITIAL_WINDOW_SIZE),
}); });
Connection { Connection {

View File

@@ -1,5 +1,5 @@
use codec::{Codec, RecvError}; use codec::{Codec, RecvError};
use frame::{self, Reason, StreamId}; use frame::{self, Reason, Settings, StreamId};
use frame::Reason::*; use frame::Reason::*;
use proto::{self, Connection, WindowSize}; use proto::{self, Connection, WindowSize};
@@ -82,19 +82,18 @@ where
let mut codec = Codec::new(io); let mut codec = Codec::new(io);
// Create the initial SETTINGS frame // Create the initial SETTINGS frame
let settings = frame::Settings::default(); let settings = Settings::default();
// Send initial settings frame // Send initial settings frame
codec codec
.buffer(settings.into()) .buffer(settings.into())
.ok()
.expect("invalid SETTINGS frame"); .expect("invalid SETTINGS frame");
// Flush pending settings frame and then wait for the client preface // Flush pending settings frame and then wait for the client preface
let handshake = Flush::new(codec) let handshake = Flush::new(codec)
.and_then(ReadPreface::new) .and_then(ReadPreface::new)
.map(move |codec| { .map(move |codec| {
let connection = Connection::new(codec); let connection = Connection::new(codec, &Settings::default());
Server { Server {
connection, connection,
} }

View File

@@ -62,7 +62,10 @@ fn send_recv_data() {
]) ])
.build(); .build();
let mut h2 = Client::handshake2(mock).wait().unwrap(); let mut h2 = Client::builder()
.handshake(mock)
.wait()
.unwrap();
let request = Request::builder() let request = Request::builder()
.method(Method::POST) .method(Method::POST)