feat(lib): redesign API to use Futures and Tokio

There are many changes involved with this, but let's just talk about
user-facing changes.

- Creating a `Client` and `Server` now needs a Tokio `Core` event loop
to attach to.
- `Request` and `Response` both no longer implement the
`std::io::{Read,Write}` traits, but instead represent their bodies as a
`futures::Stream` of items, where each item is a `Chunk`.
- The `Client.request` method now takes a `Request`, instead of being
used as a builder, and returns a `Future` that resolves to `Response`.
- The `Handler` trait for servers is no more, and instead the Tokio
`Service` trait is used. This allows interoperability with generic
middleware.

BREAKING CHANGE: A big sweeping set of breaking changes.
This commit is contained in:
Sean McArthur
2016-11-17 17:31:42 -08:00
parent e23689122a
commit 2d2d5574a6
43 changed files with 2775 additions and 5033 deletions

View File

@@ -1,15 +1,12 @@
use std::borrow::Cow;
use std::cmp;
use std::io::{self, Write};
use http::internal::{AtomicWrite, WriteBuf};
use http::io::AtomicWrite;
/// Encoders to handle different Transfer-Encodings.
#[derive(Debug, Clone)]
pub struct Encoder {
kind: Kind,
prefix: Prefix,
is_closed: bool,
}
#[derive(Debug, PartialEq, Clone)]
@@ -26,27 +23,16 @@ impl Encoder {
pub fn chunked() -> Encoder {
Encoder {
kind: Kind::Chunked(Chunked::Init),
prefix: Prefix(None),
is_closed: false,
}
}
pub fn length(len: u64) -> Encoder {
Encoder {
kind: Kind::Length(len),
prefix: Prefix(None),
is_closed: false,
}
}
pub fn prefix(&mut self, prefix: WriteBuf<Vec<u8>>) {
self.prefix.0 = Some(prefix);
}
pub fn is_eof(&self) -> bool {
if self.prefix.0.is_some() {
return false;
}
match self.kind {
Kind::Length(0) |
Kind::Chunked(Chunked::End) => true,
@@ -54,71 +40,26 @@ impl Encoder {
}
}
/// User has called `encoder.close()` in a `Handler`.
pub fn is_closed(&self) -> bool {
self.is_closed
}
pub fn close(&mut self) {
self.is_closed = true;
}
pub fn finish(self) -> Option<WriteBuf<Cow<'static, [u8]>>> {
let trailer = self.trailer();
let buf = self.prefix.0;
match (buf, trailer) {
(Some(mut buf), Some(trailer)) => {
buf.bytes.extend_from_slice(trailer);
Some(WriteBuf {
bytes: Cow::Owned(buf.bytes),
pos: buf.pos,
})
},
(Some(buf), None) => Some(WriteBuf {
bytes: Cow::Owned(buf.bytes),
pos: buf.pos
}),
(None, Some(trailer)) => {
Some(WriteBuf {
bytes: Cow::Borrowed(trailer),
pos: 0,
})
},
(None, None) => None
}
}
fn trailer(&self) -> Option<&'static [u8]> {
match self.kind {
Kind::Chunked(Chunked::Init) => {
Some(b"0\r\n\r\n")
}
_ => None
}
}
pub fn encode<W: AtomicWrite>(&mut self, w: &mut W, msg: &[u8]) -> io::Result<usize> {
match self.kind {
Kind::Chunked(ref mut chunked) => {
chunked.encode(w, &mut self.prefix, msg)
chunked.encode(w, msg)
},
Kind::Length(ref mut remaining) => {
let mut n = {
let n = {
let max = cmp::min(*remaining as usize, msg.len());
trace!("sized write, len = {}", max);
let slice = &msg[..max];
let prefix = self.prefix.0.as_ref().map(|buf| &buf.bytes[buf.pos..]).unwrap_or(b"");
try!(w.write_atomic(&[prefix, slice]))
try!(w.write_atomic(&[slice]))
};
n = self.prefix.update(n);
if n == 0 {
return Err(io::Error::new(io::ErrorKind::WouldBlock, "would block"));
}
*remaining -= n as u64;
trace!("sized write complete, remaining = {}", remaining);
Ok(n)
},
}
@@ -138,7 +79,7 @@ enum Chunked {
}
impl Chunked {
fn encode<W: AtomicWrite>(&mut self, w: &mut W, prefix: &mut Prefix, msg: &[u8]) -> io::Result<usize> {
fn encode<W: AtomicWrite>(&mut self, w: &mut W, msg: &[u8]) -> io::Result<usize> {
match *self {
Chunked::Init => {
let mut size = ChunkSize {
@@ -158,28 +99,24 @@ impl Chunked {
let pieces = match *self {
Chunked::Init => unreachable!("Chunked::Init should have become Chunked::Size"),
Chunked::Size(ref size) => [
prefix.0.as_ref().map(|buf| &buf.bytes[buf.pos..]).unwrap_or(b""),
&size.bytes[size.pos.into() .. size.len.into()],
&b"\r\n"[..],
msg,
&b"\r\n"[..],
],
Chunked::SizeCr => [
&b""[..],
&b""[..],
&b"\r\n"[..],
msg,
&b"\r\n"[..],
],
Chunked::SizeLf => [
&b""[..],
&b""[..],
&b"\n"[..],
msg,
&b"\r\n"[..],
],
Chunked::Body(pos) => [
&b""[..],
&b""[..],
&b""[..],
&msg[pos..],
@@ -189,14 +126,12 @@ impl Chunked {
&b""[..],
&b""[..],
&b""[..],
&b""[..],
&b"\r\n"[..],
],
Chunked::BodyLf => [
&b""[..],
&b""[..],
&b""[..],
&b""[..],
&b"\n"[..],
],
Chunked::End => unreachable!("Chunked::End shouldn't write more")
@@ -204,9 +139,6 @@ impl Chunked {
try!(w.write_atomic(&pieces))
};
if n > 0 {
n = prefix.update(n);
}
while n > 0 {
match *self {
Chunked::Init => unreachable!("Chunked::Init should have become Chunked::Size"),
@@ -321,30 +253,10 @@ impl io::Write for ChunkSize {
}
}
#[derive(Debug, Clone)]
struct Prefix(Option<WriteBuf<Vec<u8>>>);
impl Prefix {
fn update(&mut self, n: usize) -> usize {
if let Some(mut buf) = self.0.take() {
if buf.bytes.len() - buf.pos > n {
buf.pos += n;
self.0 = Some(buf);
0
} else {
let nbuf = buf.bytes.len() - buf.pos;
n - nbuf
}
} else {
n
}
}
}
#[cfg(test)]
mod tests {
use super::Encoder;
use mock::{Async, Buf};
use mock::{AsyncIo, Buf};
#[test]
fn test_chunked_encode_sync() {
@@ -359,7 +271,7 @@ mod tests {
#[test]
fn test_chunked_encode_async() {
let mut dst = Async::new(Buf::new(), 7);
let mut dst = AsyncIo::new(Buf::new(), 7);
let mut encoder = Encoder::chunked();
assert!(encoder.encode(&mut dst, b"foo bar").is_err());