Much work
This commit is contained in:
11
Cargo.toml
11
Cargo.toml
@@ -8,11 +8,11 @@ futures = "0.1"
|
|||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" }
|
tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" }
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
http = { path = "/Users/carllerche/Code/Oss/Tokio/tower/http" }
|
http = { git = "https://github.com/carllerche/http" }
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
log = "0.3.8"
|
log = "0.3.8"
|
||||||
# tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" }
|
|
||||||
fnv = "1.0.5"
|
fnv = "1.0.5"
|
||||||
|
ordermap = "0.2.10"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex = "0.2.0"
|
hex = "0.2.0"
|
||||||
@@ -21,3 +21,10 @@ serde = "1.0.0"
|
|||||||
serde_json = "1.0.0"
|
serde_json = "1.0.0"
|
||||||
quickcheck = "0.4.1"
|
quickcheck = "0.4.1"
|
||||||
rand = "0.3.15"
|
rand = "0.3.15"
|
||||||
|
|
||||||
|
# Akamai example
|
||||||
|
tokio-core = "0.1"
|
||||||
|
openssl = { version = "0.9.14", "features" = ["v102"] }
|
||||||
|
tokio-openssl = "0.1.3"
|
||||||
|
env_logger = "0.4.3"
|
||||||
|
io-dump = { path = "/Users/carllerche/Code/Oss/Tokio/util/io-dump" }
|
||||||
|
|||||||
107
src/client.rs
Normal file
107
src/client.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use {frame, proto, Frame, Peer, ConnectionError, StreamId};
|
||||||
|
|
||||||
|
use http;
|
||||||
|
|
||||||
|
use futures::{Future, Poll};
|
||||||
|
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
/// In progress H2 connection binding
|
||||||
|
pub struct Handshake<T> {
|
||||||
|
// TODO: unbox
|
||||||
|
inner: Box<Future<Item = Connection<T>, Error = ConnectionError>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marker type indicating a client peer
|
||||||
|
pub struct Client;
|
||||||
|
|
||||||
|
pub type Connection<T> = super::Connection<T, Client>;
|
||||||
|
|
||||||
|
/// Bind an H2 client connection.
|
||||||
|
///
|
||||||
|
/// Returns a future which resolves to the connection value once the H2
|
||||||
|
/// handshake has been completed.
|
||||||
|
pub fn bind<T>(io: T) -> Handshake<T>
|
||||||
|
where T: AsyncRead + AsyncWrite + 'static,
|
||||||
|
{
|
||||||
|
use tokio_io::io;
|
||||||
|
|
||||||
|
debug!("binding client connection");
|
||||||
|
|
||||||
|
let handshake = io::write_all(io, b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
|
||||||
|
.map(|(io, _)| {
|
||||||
|
debug!("client connection bound");
|
||||||
|
proto::new_connection(io)
|
||||||
|
})
|
||||||
|
.map_err(ConnectionError::from);
|
||||||
|
|
||||||
|
Handshake { inner: Box::new(handshake) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Peer for Client {
|
||||||
|
type Send = http::request::Head;
|
||||||
|
type Poll = http::response::Head;
|
||||||
|
|
||||||
|
fn check_initiating_id(id: StreamId) -> Result<(), ConnectionError> {
|
||||||
|
if id % 2 == 0 {
|
||||||
|
// Client stream identifiers must be odd
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Ensure the `id` doesn't overflow u31
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_send_message(
|
||||||
|
id: StreamId,
|
||||||
|
message: Self::Send,
|
||||||
|
body: bool) -> proto::SendMessage
|
||||||
|
{
|
||||||
|
use http::request::Head;
|
||||||
|
|
||||||
|
// Extract the components of the HTTP request
|
||||||
|
let Head { method, uri, headers, .. } = message;
|
||||||
|
|
||||||
|
// Build the set pseudo header set. All requests will include `method`
|
||||||
|
// and `path`.
|
||||||
|
let mut pseudo = frame::Pseudo::request(method, uri.path().into());
|
||||||
|
|
||||||
|
// If the URI includes a scheme component, add it to the pseudo headers
|
||||||
|
if let Some(scheme) = uri.scheme() {
|
||||||
|
pseudo.set_scheme(scheme.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the URI includes an authority component, add it to the pseudo
|
||||||
|
// headers
|
||||||
|
if let Some(authority) = uri.authority() {
|
||||||
|
pseudo.set_authority(authority.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the HEADERS frame
|
||||||
|
let mut frame = frame::Headers::new(id, pseudo, headers);
|
||||||
|
|
||||||
|
// TODO: Factor in trailers
|
||||||
|
if !body {
|
||||||
|
frame.set_end_stream();
|
||||||
|
} else {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the `SendMessage`
|
||||||
|
proto::SendMessage::new(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_poll_message(message: proto::PollMessage) -> Frame<Self::Poll> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Future for Handshake<T> {
|
||||||
|
type Item = Connection<T>;
|
||||||
|
type Error = ConnectionError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
self.inner.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ impl Head {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode<T: BufMut>(&self, payload_len: usize, dst: &mut T) {
|
pub fn encode<T: BufMut>(&self, payload_len: usize, dst: &mut T) {
|
||||||
debug_assert_eq!(self.encode_len(), dst.remaining_mut());
|
debug_assert!(self.encode_len() <= dst.remaining_mut());
|
||||||
debug_assert!(self.stream_id & STREAM_ID_MASK == 0);
|
debug_assert!(self.stream_id & STREAM_ID_MASK == 0);
|
||||||
|
|
||||||
dst.put_uint::<BigEndian>(payload_len as u64, 3);
|
dst.put_uint::<BigEndian>(payload_len as u64, 3);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub struct Headers {
|
|||||||
flags: HeadersFlag,
|
flags: HeadersFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub struct HeadersFlag(u8);
|
pub struct HeadersFlag(u8);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -108,6 +108,16 @@ const ALL: u8 = END_STREAM
|
|||||||
// ===== impl Headers =====
|
// ===== impl Headers =====
|
||||||
|
|
||||||
impl Headers {
|
impl Headers {
|
||||||
|
pub fn new(stream_id: StreamId, pseudo: Pseudo, fields: HeaderMap<HeaderValue>) -> Self {
|
||||||
|
Headers {
|
||||||
|
stream_id: stream_id,
|
||||||
|
stream_dep: None,
|
||||||
|
fields: fields,
|
||||||
|
pseudo: pseudo,
|
||||||
|
flags: HeadersFlag::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load(head: Head, src: &mut Cursor<Bytes>, decoder: &mut hpack::Decoder)
|
pub fn load(head: Head, src: &mut Cursor<Bytes>, decoder: &mut hpack::Decoder)
|
||||||
-> Result<Self, Error>
|
-> Result<Self, Error>
|
||||||
{
|
{
|
||||||
@@ -162,6 +172,10 @@ impl Headers {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_end_headers(&self) -> bool {
|
||||||
|
self.flags.is_end_headers()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn encode(self, encoder: &mut hpack::Encoder, dst: &mut BytesMut)
|
pub fn encode(self, encoder: &mut hpack::Encoder, dst: &mut BytesMut)
|
||||||
-> Option<Continuation>
|
-> Option<Continuation>
|
||||||
{
|
{
|
||||||
@@ -210,6 +224,28 @@ impl From<Headers> for Frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== impl Pseudo =====
|
||||||
|
|
||||||
|
impl Pseudo {
|
||||||
|
pub fn request(method: Method, path: ByteStr) -> Self {
|
||||||
|
Pseudo {
|
||||||
|
method: Some(method),
|
||||||
|
scheme: None,
|
||||||
|
authority: None,
|
||||||
|
path: Some(path),
|
||||||
|
status: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scheme(&mut self, scheme: ByteStr) {
|
||||||
|
self.scheme = Some(scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_authority(&mut self, authority: ByteStr) {
|
||||||
|
self.authority = Some(authority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== impl Iter =====
|
// ===== impl Iter =====
|
||||||
|
|
||||||
impl Iterator for Iter {
|
impl Iterator for Iter {
|
||||||
@@ -264,6 +300,10 @@ impl HeadersFlag {
|
|||||||
self.0 & END_STREAM == END_STREAM
|
self.0 & END_STREAM == END_STREAM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_end_stream(&mut self) {
|
||||||
|
self.0 |= END_STREAM
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_end_headers(&self) -> bool {
|
pub fn is_end_headers(&self) -> bool {
|
||||||
self.0 & END_HEADERS == END_HEADERS
|
self.0 & END_HEADERS == END_HEADERS
|
||||||
}
|
}
|
||||||
@@ -277,6 +317,13 @@ impl HeadersFlag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for HeadersFlag {
|
||||||
|
/// Returns a `HeadersFlag` value with `END_HEADERS` set.
|
||||||
|
fn default() -> Self {
|
||||||
|
HeadersFlag(END_HEADERS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<HeadersFlag> for u8 {
|
impl From<HeadersFlag> for u8 {
|
||||||
fn from(src: HeadersFlag) -> u8 {
|
fn from(src: HeadersFlag) -> u8 {
|
||||||
src.0
|
src.0
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ mod util;
|
|||||||
|
|
||||||
pub use self::data::Data;
|
pub use self::data::Data;
|
||||||
pub use self::head::{Head, Kind, StreamId};
|
pub use self::head::{Head, Kind, StreamId};
|
||||||
pub use self::headers::{Headers, PushPromise, Continuation};
|
pub use self::headers::{Headers, PushPromise, Continuation, Pseudo};
|
||||||
pub use self::settings::{Settings, SettingSet};
|
pub use self::settings::{Settings, SettingSet};
|
||||||
|
|
||||||
// Re-export some constants
|
// Re-export some constants
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ impl Settings {
|
|||||||
|
|
||||||
// Ensure the payload length is correct, each setting is 6 bytes long.
|
// Ensure the payload length is correct, each setting is 6 bytes long.
|
||||||
if payload.len() % 6 != 0 {
|
if payload.len() % 6 != 0 {
|
||||||
|
debug!("invalid settings payload length; len={:?}", payload.len());
|
||||||
return Err(Error::InvalidPayloadAckSettings);
|
return Err(Error::InvalidPayloadAckSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
43
src/lib.rs
43
src/lib.rs
@@ -13,14 +13,17 @@ extern crate http;
|
|||||||
// Buffer utilities
|
// Buffer utilities
|
||||||
extern crate bytes;
|
extern crate bytes;
|
||||||
|
|
||||||
// Hash function used for HPACK encoding
|
// Hash function used for HPACK encoding and tracking stream states.
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
|
|
||||||
|
extern crate ordermap;
|
||||||
|
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod hpack;
|
pub mod hpack;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
@@ -29,4 +32,42 @@ pub mod frame;
|
|||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use error::{ConnectionError, StreamError, Reason};
|
pub use error::{ConnectionError, StreamError, Reason};
|
||||||
|
pub use frame::{StreamId};
|
||||||
pub use proto::Connection;
|
pub use proto::Connection;
|
||||||
|
|
||||||
|
/// An H2 connection frame
|
||||||
|
pub enum Frame<T> {
|
||||||
|
Message {
|
||||||
|
id: StreamId,
|
||||||
|
message: T,
|
||||||
|
body: bool,
|
||||||
|
},
|
||||||
|
Body {
|
||||||
|
id: StreamId,
|
||||||
|
chunk: Option<()>,
|
||||||
|
},
|
||||||
|
Error {
|
||||||
|
id: StreamId,
|
||||||
|
error: (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Either a Client or a Server
|
||||||
|
pub trait Peer {
|
||||||
|
/// Message type sent into the transport
|
||||||
|
type Send;
|
||||||
|
|
||||||
|
/// Message type polled from the transport
|
||||||
|
type Poll;
|
||||||
|
|
||||||
|
fn check_initiating_id(id: StreamId) -> Result<(), ConnectionError>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn convert_send_message(
|
||||||
|
id: StreamId,
|
||||||
|
message: Self::Send,
|
||||||
|
body: bool) -> proto::SendMessage;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn convert_poll_message(message: proto::PollMessage) -> Frame<Self::Poll>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
use {frame, proto, ConnectionError};
|
use {frame, Frame, ConnectionError, Peer, StreamId};
|
||||||
|
use proto::{self, ReadySink, State};
|
||||||
|
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
use tokio_io::codec::length_delimited;
|
use tokio_io::codec::length_delimited;
|
||||||
|
|
||||||
|
use http;
|
||||||
|
|
||||||
use futures::*;
|
use futures::*;
|
||||||
|
|
||||||
pub struct Connection<T> {
|
use ordermap::OrderMap;
|
||||||
|
use fnv::FnvHasher;
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::hash::BuildHasherDefault;
|
||||||
|
|
||||||
|
/// An H2 connection
|
||||||
|
pub struct Connection<T, P> {
|
||||||
inner: Inner<T>,
|
inner: Inner<T>,
|
||||||
|
streams: StreamMap<State>,
|
||||||
|
peer: PhantomData<P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Inner<T> =
|
type Inner<T> =
|
||||||
@@ -15,72 +28,154 @@ type Inner<T> =
|
|||||||
proto::FramedRead<
|
proto::FramedRead<
|
||||||
length_delimited::FramedRead<T>>>>>;
|
length_delimited::FramedRead<T>>>>>;
|
||||||
|
|
||||||
impl<T: AsyncRead + AsyncWrite> Connection<T> {
|
type StreamMap<T> = OrderMap<StreamId, T, BuildHasherDefault<FnvHasher>>;
|
||||||
pub fn new(io: T) -> Connection<T> {
|
|
||||||
// Delimit the frames
|
|
||||||
let framed_read = length_delimited::Builder::new()
|
|
||||||
.big_endian()
|
|
||||||
.length_field_length(3)
|
|
||||||
.length_adjustment(6)
|
|
||||||
.num_skip(0) // Don't skip the header
|
|
||||||
.new_read(io);
|
|
||||||
|
|
||||||
// Map to `Frame` types
|
/// Returns a new `Connection` backed by the given `io`.
|
||||||
let framed_read = proto::FramedRead::new(framed_read);
|
pub fn new<T, P>(io: T) -> Connection<T, P>
|
||||||
|
where T: AsyncRead + AsyncWrite,
|
||||||
|
P: Peer,
|
||||||
|
{
|
||||||
|
|
||||||
// Frame encoder
|
// Delimit the frames
|
||||||
let mut framed = proto::FramedWrite::new(framed_read);
|
let framed_read = length_delimited::Builder::new()
|
||||||
|
.big_endian()
|
||||||
|
.length_field_length(3)
|
||||||
|
.length_adjustment(9)
|
||||||
|
.num_skip(0) // Don't skip the header
|
||||||
|
.new_read(io);
|
||||||
|
|
||||||
// Ok, so this is a **little** hacky, but it works for now.
|
// Map to `Frame` types
|
||||||
//
|
let framed_read = proto::FramedRead::new(framed_read);
|
||||||
// The ping/pong behavior SHOULD be given highest priority (6.7).
|
|
||||||
// However, the connection handshake requires the settings frame to be
|
|
||||||
// sent as the very first one. This needs special handling because
|
|
||||||
// otherwise there is a race condition where the peer could send its
|
|
||||||
// settings frame followed immediately by a Ping, in which case, we
|
|
||||||
// don't want to accidentally send the pong before finishing the
|
|
||||||
// connection hand shake.
|
|
||||||
//
|
|
||||||
// So, to ensure correct ordering, we write the settings frame here
|
|
||||||
// before fully constructing the connection struct. Technically, `Async`
|
|
||||||
// operations should not be performed in `new` because this might not
|
|
||||||
// happen on a task, however we have full control of the I/O and we know
|
|
||||||
// that the settings frame will get buffered and not actually perform an
|
|
||||||
// I/O op.
|
|
||||||
let initial_settings = frame::SettingSet::default();
|
|
||||||
let frame = frame::Settings::new(initial_settings.clone());
|
|
||||||
assert!(framed.start_send(frame.into()).unwrap().is_ready());
|
|
||||||
|
|
||||||
// Add ping/pong handler
|
// Frame encoder
|
||||||
let ping_pong = proto::PingPong::new(framed);
|
let mut framed = proto::FramedWrite::new(framed_read);
|
||||||
|
|
||||||
// Add settings handler
|
// Ok, so this is a **little** hacky, but it works for now.
|
||||||
let connection = proto::Settings::new(ping_pong, initial_settings);
|
//
|
||||||
|
// The ping/pong behavior SHOULD be given highest priority (6.7).
|
||||||
|
// However, the connection handshake requires the settings frame to be
|
||||||
|
// sent as the very first one. This needs special handling because
|
||||||
|
// otherwise there is a race condition where the peer could send its
|
||||||
|
// settings frame followed immediately by a Ping, in which case, we
|
||||||
|
// don't want to accidentally send the pong before finishing the
|
||||||
|
// connection hand shake.
|
||||||
|
//
|
||||||
|
// So, to ensure correct ordering, we write the settings frame here
|
||||||
|
// before fully constructing the connection struct. Technically, `Async`
|
||||||
|
// operations should not be performed in `new` because this might not
|
||||||
|
// happen on a task, however we have full control of the I/O and we know
|
||||||
|
// that the settings frame will get buffered and not actually perform an
|
||||||
|
// I/O op.
|
||||||
|
let initial_settings = frame::SettingSet::default();
|
||||||
|
let frame = frame::Settings::new(initial_settings.clone());
|
||||||
|
assert!(framed.start_send(frame.into()).unwrap().is_ready());
|
||||||
|
|
||||||
Connection {
|
// Add ping/pong handler
|
||||||
inner: connection,
|
let ping_pong = proto::PingPong::new(framed);
|
||||||
|
|
||||||
|
// Add settings handler
|
||||||
|
let connection = proto::Settings::new(ping_pong, initial_settings);
|
||||||
|
|
||||||
|
Connection {
|
||||||
|
inner: connection,
|
||||||
|
streams: StreamMap::default(),
|
||||||
|
peer: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, P> Connection<T, P>
|
||||||
|
where T: AsyncRead + AsyncWrite,
|
||||||
|
P: Peer,
|
||||||
|
{
|
||||||
|
/// Completes when the connection has terminated
|
||||||
|
pub fn poll_shutdown(&mut self) -> Poll<(), ConnectionError> {
|
||||||
|
try_ready!(self.poll_complete());
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, P> Stream for Connection<T, P>
|
||||||
|
where T: AsyncRead + AsyncWrite,
|
||||||
|
P: Peer,
|
||||||
|
{
|
||||||
|
type Item = Frame<P::Poll>;
|
||||||
|
type Error = ConnectionError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Option<Self::Item>, ConnectionError> {
|
||||||
|
use frame::Frame::*;
|
||||||
|
|
||||||
|
match try_ready!(self.inner.poll()) {
|
||||||
|
Some(Headers(v)) => unimplemented!(),
|
||||||
|
Some(frame) => panic!("unexpected frame; frame={:?}", frame),
|
||||||
|
None => return Ok(Async::Ready(None)),
|
||||||
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsyncRead + AsyncWrite> Stream for Connection<T> {
|
impl<T, P> Sink for Connection<T, P>
|
||||||
type Item = frame::Frame;
|
where T: AsyncRead + AsyncWrite,
|
||||||
type Error = ConnectionError;
|
P: Peer,
|
||||||
|
{
|
||||||
fn poll(&mut self) -> Poll<Option<frame::Frame>, ConnectionError> {
|
type SinkItem = Frame<P::Send>;
|
||||||
self.inner.poll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsyncRead + AsyncWrite> Sink for Connection<T> {
|
|
||||||
type SinkItem = frame::Frame;
|
|
||||||
type SinkError = ConnectionError;
|
type SinkError = ConnectionError;
|
||||||
|
|
||||||
fn start_send(&mut self, item: frame::Frame) -> StartSend<frame::Frame, ConnectionError> {
|
fn start_send(&mut self, item: Self::SinkItem)
|
||||||
self.inner.start_send(item)
|
-> StartSend<Self::SinkItem, Self::SinkError>
|
||||||
|
{
|
||||||
|
// First ensure that the upstream can process a new item
|
||||||
|
if !try!(self.poll_ready()).is_ready() {
|
||||||
|
return Ok(AsyncSink::NotReady(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
match item {
|
||||||
|
Frame::Message { id, message, body } => {
|
||||||
|
// Ensure ID is valid
|
||||||
|
try!(P::check_initiating_id(id));
|
||||||
|
|
||||||
|
// TODO: Ensure available capacity for a new stream
|
||||||
|
// This won't be as simple as self.streams.len() as closed
|
||||||
|
// connections should not be factored.
|
||||||
|
|
||||||
|
// Transition the stream state, creating a new entry if needed
|
||||||
|
try!(self.streams.entry(id)
|
||||||
|
.or_insert(State::default())
|
||||||
|
.send_headers());
|
||||||
|
|
||||||
|
let message = P::convert_send_message(id, message, body);
|
||||||
|
|
||||||
|
// TODO: Handle trailers and all that jazz
|
||||||
|
|
||||||
|
// We already ensured that the upstream can handle the frame, so
|
||||||
|
// panic if it gets rejected.
|
||||||
|
let res = try!(self.inner.start_send(frame::Frame::Headers(message.frame)));
|
||||||
|
|
||||||
|
// This is a one-way conversion. By checking `poll_ready` first,
|
||||||
|
// it's already been determined that the inner `Sink` can accept
|
||||||
|
// the item. If the item is rejected, then there is a bug.
|
||||||
|
assert!(res.is_ready());
|
||||||
|
|
||||||
|
Ok(AsyncSink::Ready)
|
||||||
|
}
|
||||||
|
Frame::Body { id, chunk } => {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
Frame::Error { id, error } => {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_complete(&mut self) -> Poll<(), ConnectionError> {
|
fn poll_complete(&mut self) -> Poll<(), ConnectionError> {
|
||||||
self.inner.poll_complete()
|
self.inner.poll_complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, P> ReadySink for Connection<T, P>
|
||||||
|
where T: AsyncRead + AsyncWrite,
|
||||||
|
P: Peer,
|
||||||
|
{
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), Self::SinkError> {
|
||||||
|
self.inner.poll_ready()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ impl<T> FramedRead<T> {
|
|||||||
|
|
||||||
// TODO: Change to drain: carllerche/bytes#130
|
// TODO: Change to drain: carllerche/bytes#130
|
||||||
let frame = try!(frame::Headers::load(head, &mut buf, &mut self.hpack));
|
let frame = try!(frame::Headers::load(head, &mut buf, &mut self.hpack));
|
||||||
|
|
||||||
|
if !frame.is_end_headers() {
|
||||||
|
// Wait for continuation frames
|
||||||
|
self.partial = Some(Partial::Headers(frame));
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
frame.into()
|
frame.into()
|
||||||
}
|
}
|
||||||
Kind::Priority => unimplemented!(),
|
Kind::Priority => unimplemented!(),
|
||||||
@@ -88,6 +95,7 @@ impl<T> Stream for FramedRead<T>
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(frame) = try!(self.decode_frame(bytes)) {
|
if let Some(frame) = try!(self.decode_frame(bytes)) {
|
||||||
|
debug!("poll; frame={:?}", frame);
|
||||||
return Ok(Async::Ready(Some(frame)));
|
return Ok(Async::Ready(Some(frame)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use {ConnectionError, Reason};
|
use {hpack, ConnectionError, Reason};
|
||||||
use frame::{self, Frame, Error};
|
use frame::{self, Frame, Error};
|
||||||
use hpack;
|
use proto::ReadySink;
|
||||||
|
|
||||||
use futures::*;
|
use futures::*;
|
||||||
use tokio_io::AsyncWrite;
|
use tokio_io::AsyncWrite;
|
||||||
@@ -67,7 +67,7 @@ impl<T: AsyncWrite> FramedWrite<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.next.is_none() && self.buf.has_remaining()
|
self.next.is_none() && !self.buf.has_remaining()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame_len(&self, data: &frame::Data) -> usize {
|
fn frame_len(&self, data: &frame::Data) -> usize {
|
||||||
@@ -80,13 +80,10 @@ impl<T: AsyncWrite> Sink for FramedWrite<T> {
|
|||||||
type SinkError = ConnectionError;
|
type SinkError = ConnectionError;
|
||||||
|
|
||||||
fn start_send(&mut self, item: Frame) -> StartSend<Frame, ConnectionError> {
|
fn start_send(&mut self, item: Frame) -> StartSend<Frame, ConnectionError> {
|
||||||
if self.has_capacity() {
|
debug!("start_send; frame={:?}", item);
|
||||||
// Try flushing
|
|
||||||
try!(self.poll_complete());
|
|
||||||
|
|
||||||
if self.has_capacity() {
|
if !try!(self.poll_ready()).is_ready() {
|
||||||
return Ok(AsyncSink::NotReady(item));
|
return Ok(AsyncSink::NotReady(item));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match item {
|
match item {
|
||||||
@@ -117,6 +114,7 @@ impl<T: AsyncWrite> Sink for FramedWrite<T> {
|
|||||||
}
|
}
|
||||||
Frame::Settings(v) => {
|
Frame::Settings(v) => {
|
||||||
v.encode(self.buf.get_mut());
|
v.encode(self.buf.get_mut());
|
||||||
|
trace!("encoded settings; rem={:?}", self.buf.remaining());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +122,8 @@ impl<T: AsyncWrite> Sink for FramedWrite<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn poll_complete(&mut self) -> Poll<(), ConnectionError> {
|
fn poll_complete(&mut self) -> Poll<(), ConnectionError> {
|
||||||
|
trace!("FramedWrite::poll_complete");
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
match self.next {
|
match self.next {
|
||||||
Some(Next::Data { .. }) => unimplemented!(),
|
Some(Next::Data { .. }) => unimplemented!(),
|
||||||
@@ -132,9 +132,14 @@ impl<T: AsyncWrite> Sink for FramedWrite<T> {
|
|||||||
|
|
||||||
// As long as there is data to write, try to write it!
|
// As long as there is data to write, try to write it!
|
||||||
while !self.is_empty() {
|
while !self.is_empty() {
|
||||||
|
trace!("writing buffer; next={:?}; rem={:?}", self.next, self.buf.remaining());
|
||||||
try_ready!(self.inner.write_buf(&mut self.buf));
|
try_ready!(self.inner.write_buf(&mut self.buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace!("flushing buffer");
|
||||||
|
// Flush the upstream
|
||||||
|
try_nb!(self.inner.flush());
|
||||||
|
|
||||||
// Clear internal buffer
|
// Clear internal buffer
|
||||||
self.buf.set_position(0);
|
self.buf.set_position(0);
|
||||||
self.buf.get_mut().clear();
|
self.buf.get_mut().clear();
|
||||||
@@ -148,6 +153,21 @@ impl<T: AsyncWrite> Sink for FramedWrite<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncWrite> ReadySink for FramedWrite<T> {
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), Self::SinkError> {
|
||||||
|
if !self.has_capacity() {
|
||||||
|
// Try flushing
|
||||||
|
try!(self.poll_complete());
|
||||||
|
|
||||||
|
if !self.has_capacity() {
|
||||||
|
return Ok(Async::NotReady);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Stream> Stream for FramedWrite<T> {
|
impl<T: Stream> Stream for FramedWrite<T> {
|
||||||
type Item = T::Item;
|
type Item = T::Item;
|
||||||
type Error = T::Error;
|
type Error = T::Error;
|
||||||
|
|||||||
@@ -2,10 +2,34 @@ mod connection;
|
|||||||
mod framed_read;
|
mod framed_read;
|
||||||
mod framed_write;
|
mod framed_write;
|
||||||
mod ping_pong;
|
mod ping_pong;
|
||||||
|
mod ready;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
mod state;
|
||||||
|
|
||||||
pub use self::connection::Connection;
|
pub use self::connection::{Connection, new as new_connection};
|
||||||
pub use self::framed_read::FramedRead;
|
pub use self::framed_read::FramedRead;
|
||||||
pub use self::framed_write::FramedWrite;
|
pub use self::framed_write::FramedWrite;
|
||||||
pub use self::ping_pong::PingPong;
|
pub use self::ping_pong::PingPong;
|
||||||
|
pub use self::ready::ReadySink;
|
||||||
pub use self::settings::Settings;
|
pub use self::settings::Settings;
|
||||||
|
pub use self::state::State;
|
||||||
|
|
||||||
|
use frame;
|
||||||
|
|
||||||
|
/// A request or response issued by the current process.
|
||||||
|
pub struct SendMessage {
|
||||||
|
frame: frame::Headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A request or response received by the current process.
|
||||||
|
pub struct PollMessage {
|
||||||
|
frame: frame::Headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendMessage {
|
||||||
|
pub fn new(frame: frame::Headers) -> Self {
|
||||||
|
SendMessage {
|
||||||
|
frame: frame,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use ConnectionError;
|
use ConnectionError;
|
||||||
use frame::Frame;
|
use frame::Frame;
|
||||||
|
use proto::ReadySink;
|
||||||
|
|
||||||
use futures::*;
|
use futures::*;
|
||||||
|
|
||||||
@@ -45,3 +46,13 @@ impl<T> Sink for PingPong<T>
|
|||||||
self.inner.poll_complete()
|
self.inner.poll_complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> ReadySink for PingPong<T>
|
||||||
|
where T: Stream<Item = Frame, Error = ConnectionError>,
|
||||||
|
T: Sink<SinkItem = Frame, SinkError = ConnectionError>,
|
||||||
|
T: ReadySink,
|
||||||
|
{
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), ConnectionError> {
|
||||||
|
self.inner.poll_ready()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
5
src/proto/ready.rs
Normal file
5
src/proto/ready.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
use futures::{Sink, Poll};
|
||||||
|
|
||||||
|
pub trait ReadySink: Sink {
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), Self::SinkError>;
|
||||||
|
}
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
use ConnectionError;
|
use ConnectionError;
|
||||||
use frame::{self, Frame};
|
use frame::{self, Frame};
|
||||||
|
use proto::ReadySink;
|
||||||
|
|
||||||
use futures::*;
|
use futures::*;
|
||||||
|
|
||||||
pub struct Settings<T> {
|
pub struct Settings<T> {
|
||||||
|
// Upstream transport
|
||||||
inner: T,
|
inner: T,
|
||||||
|
|
||||||
// Our settings
|
// Our settings
|
||||||
local: frame::SettingSet,
|
local: frame::SettingSet,
|
||||||
|
|
||||||
// Peer settings
|
// Peer settings
|
||||||
remote: frame::SettingSet,
|
remote: frame::SettingSet,
|
||||||
|
|
||||||
// Number of acks remaining to send to the peer
|
// Number of acks remaining to send to the peer
|
||||||
remaining_acks: usize,
|
remaining_acks: usize,
|
||||||
|
|
||||||
// True when the local settings must be flushed to the remote
|
// True when the local settings must be flushed to the remote
|
||||||
is_dirty: bool,
|
is_dirty: bool,
|
||||||
}
|
}
|
||||||
@@ -30,17 +36,13 @@ impl<T> Settings<T>
|
|||||||
local: local,
|
local: local,
|
||||||
remote: frame::SettingSet::default(),
|
remote: frame::SettingSet::default(),
|
||||||
remaining_acks: 0,
|
remaining_acks: 0,
|
||||||
is_dirty: false,
|
is_dirty: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_pending_sends(&self) -> bool {
|
|
||||||
self.is_dirty || self.remaining_acks > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_send_pending(&mut self) -> Poll<(), ConnectionError> {
|
fn try_send_pending(&mut self) -> Poll<(), ConnectionError> {
|
||||||
if self.is_dirty {
|
if self.is_dirty {
|
||||||
let frame = frame::Settings::new(self.local.clone()).into();
|
let frame = frame::Settings::new(self.local.clone());
|
||||||
try_ready!(self.try_send(frame));
|
try_ready!(self.try_send(frame));
|
||||||
|
|
||||||
self.is_dirty = false;
|
self.is_dirty = false;
|
||||||
@@ -56,8 +58,8 @@ impl<T> Settings<T>
|
|||||||
Ok(Async::Ready(()))
|
Ok(Async::Ready(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_send(&mut self, item: frame::Frame) -> Poll<(), ConnectionError> {
|
fn try_send(&mut self, item: frame::Settings) -> Poll<(), ConnectionError> {
|
||||||
if let AsyncSink::NotReady(_) = try!(self.inner.start_send(item)) {
|
if let AsyncSink::NotReady(_) = try!(self.inner.start_send(item.into())) {
|
||||||
// Ensure that call to `poll_complete` guarantee is called to satisfied
|
// Ensure that call to `poll_complete` guarantee is called to satisfied
|
||||||
try!(self.inner.poll_complete());
|
try!(self.inner.poll_complete());
|
||||||
|
|
||||||
@@ -110,3 +112,17 @@ impl<T> Sink for Settings<T>
|
|||||||
self.inner.close()
|
self.inner.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> ReadySink for Settings<T>
|
||||||
|
where T: Stream<Item = Frame, Error = ConnectionError>,
|
||||||
|
T: Sink<SinkItem = Frame, SinkError = ConnectionError>,
|
||||||
|
T: ReadySink,
|
||||||
|
{
|
||||||
|
fn poll_ready(&mut self) -> Poll<(), ConnectionError> {
|
||||||
|
if try!(self.try_send_pending()).is_ready() {
|
||||||
|
return self.inner.poll_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
72
src/proto/state.rs
Normal file
72
src/proto/state.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
use ConnectionError;
|
||||||
|
|
||||||
|
/// Represents the state of an H2 stream
|
||||||
|
///
|
||||||
|
/// ```not_rust
|
||||||
|
/// +--------+
|
||||||
|
/// send PP | | recv PP
|
||||||
|
/// ,--------| idle |--------.
|
||||||
|
/// / | | \
|
||||||
|
/// v +--------+ v
|
||||||
|
/// +----------+ | +----------+
|
||||||
|
/// | | | send H / | |
|
||||||
|
/// ,------| reserved | | recv H | reserved |------.
|
||||||
|
/// | | (local) | | | (remote) | |
|
||||||
|
/// | +----------+ v +----------+ |
|
||||||
|
/// | | +--------+ | |
|
||||||
|
/// | | recv ES | | send ES | |
|
||||||
|
/// | send H | ,-------| open |-------. | recv H |
|
||||||
|
/// | | / | | \ | |
|
||||||
|
/// | v v +--------+ v v |
|
||||||
|
/// | +----------+ | +----------+ |
|
||||||
|
/// | | half | | | half | |
|
||||||
|
/// | | closed | | send R / | closed | |
|
||||||
|
/// | | (remote) | | recv R | (local) | |
|
||||||
|
/// | +----------+ | +----------+ |
|
||||||
|
/// | | | | |
|
||||||
|
/// | | send ES / | recv ES / | |
|
||||||
|
/// | | send R / v send R / | |
|
||||||
|
/// | | recv R +--------+ recv R | |
|
||||||
|
/// | send R / `----------->| |<-----------' send R / |
|
||||||
|
/// | recv R | closed | recv R |
|
||||||
|
/// `----------------------->| |<----------------------'
|
||||||
|
/// +--------+
|
||||||
|
///
|
||||||
|
/// send: endpoint sends this frame
|
||||||
|
/// recv: endpoint receives this frame
|
||||||
|
///
|
||||||
|
/// H: HEADERS frame (with implied CONTINUATIONs)
|
||||||
|
/// PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
|
||||||
|
/// ES: END_STREAM flag
|
||||||
|
/// R: RST_STREAM frame
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum State {
|
||||||
|
Idle,
|
||||||
|
ReservedLocal,
|
||||||
|
ReservedRemote,
|
||||||
|
Open,
|
||||||
|
HalfClosedLocal,
|
||||||
|
HalfClosedRemote,
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// Transition the state to represent headers being sent.
|
||||||
|
///
|
||||||
|
/// Returns an error if this is an invalid state transition.
|
||||||
|
pub fn send_headers(&mut self) -> Result<(), ConnectionError> {
|
||||||
|
if *self != State::Idle {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
*self = State::Open;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> State {
|
||||||
|
State::Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user