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:
@@ -277,7 +277,7 @@ mod tests {
|
||||
use std::io::Write;
|
||||
use super::Decoder;
|
||||
use super::ChunkedState;
|
||||
use mock::Async;
|
||||
use mock::AsyncIo;
|
||||
|
||||
#[test]
|
||||
fn test_read_chunk_size() {
|
||||
@@ -422,7 +422,7 @@ mod tests {
|
||||
-> String {
|
||||
let content_len = content.len();
|
||||
let mock_buf = io::Cursor::new(content.clone());
|
||||
let mut ins = Async::new(mock_buf, block_at);
|
||||
let mut ins = AsyncIo::new(mock_buf, block_at);
|
||||
let mut outs = vec![];
|
||||
loop {
|
||||
let mut buf = vec![0; read_buffer_size];
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
/*
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use url::Url;
|
||||
use tick;
|
||||
use time::now_utc;
|
||||
|
||||
use header::{self, Headers};
|
||||
use http::{self, conn};
|
||||
use method::Method;
|
||||
use net::{Fresh, Streaming};
|
||||
use status::StatusCode;
|
||||
use version::HttpVersion;
|
||||
*/
|
||||
|
||||
pub use self::decode::Decoder;
|
||||
pub use self::encode::Encoder;
|
||||
|
||||
@@ -23,7 +5,7 @@ pub use self::parse::parse;
|
||||
|
||||
mod decode;
|
||||
mod encode;
|
||||
mod parse;
|
||||
pub mod parse;
|
||||
|
||||
/*
|
||||
fn should_have_response_body(method: &Method, status: u16) -> bool {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::fmt::{self, Write};
|
||||
use httparse;
|
||||
|
||||
use header::{self, Headers, ContentLength, TransferEncoding};
|
||||
use http::{MessageHead, RawStatus, Http1Message, ParseResult, ServerMessage, ClientMessage, RequestLine};
|
||||
use http::{MessageHead, RawStatus, Http1Transaction, ParseResult, ServerTransaction, ClientTransaction, RequestLine};
|
||||
use http::h1::{Encoder, Decoder};
|
||||
use method::Method;
|
||||
use status::StatusCode;
|
||||
@@ -13,17 +13,15 @@ use version::HttpVersion::{Http10, Http11};
|
||||
const MAX_HEADERS: usize = 100;
|
||||
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||
|
||||
pub fn parse<T: Http1Message<Incoming=I>, I>(buf: &[u8]) -> ParseResult<I> {
|
||||
pub fn parse<T: Http1Transaction<Incoming=I>, I>(buf: &[u8]) -> ParseResult<I> {
|
||||
if buf.len() == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
trace!("parse({:?})", buf);
|
||||
<T as Http1Message>::parse(buf)
|
||||
<T as Http1Transaction>::parse(buf)
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl Http1Message for ServerMessage {
|
||||
impl Http1Transaction for ServerTransaction {
|
||||
type Incoming = RequestLine;
|
||||
type Outgoing = StatusCode;
|
||||
|
||||
@@ -60,7 +58,7 @@ impl Http1Message for ServerMessage {
|
||||
}
|
||||
|
||||
|
||||
fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||
fn encode(head: &mut MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||
use ::header;
|
||||
trace!("writing head: {:?}", head);
|
||||
|
||||
@@ -103,9 +101,14 @@ impl Http1Message for ServerMessage {
|
||||
}
|
||||
body
|
||||
}
|
||||
|
||||
fn should_set_length(_head: &MessageHead<Self::Outgoing>) -> bool {
|
||||
//TODO: pass method, check if method == HEAD
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Http1Message for ClientMessage {
|
||||
impl Http1Transaction for ClientTransaction {
|
||||
type Incoming = RawStatus;
|
||||
type Outgoing = RequestLine;
|
||||
|
||||
@@ -162,7 +165,7 @@ impl Http1Message for ClientMessage {
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||
fn encode(head: &mut MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder {
|
||||
trace!("writing head: {:?}", head);
|
||||
|
||||
|
||||
@@ -203,6 +206,14 @@ impl Http1Message for ClientMessage {
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
|
||||
fn should_set_length(head: &MessageHead<Self::Outgoing>) -> bool {
|
||||
match &head.subject.0 {
|
||||
&Method::Get | &Method::Head => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FastWrite<'a>(&'a mut Vec<u8>);
|
||||
@@ -238,17 +249,17 @@ mod tests {
|
||||
#[test]
|
||||
fn test_parse_request() {
|
||||
let raw = b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n";
|
||||
parse::<http::ServerMessage, _>(raw).unwrap();
|
||||
parse::<http::ServerTransaction, _>(raw).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_raw_status() {
|
||||
let raw = b"HTTP/1.1 200 OK\r\n\r\n";
|
||||
let (res, _) = parse::<http::ClientMessage, _>(raw).unwrap().unwrap();
|
||||
let (res, _) = parse::<http::ClientTransaction, _>(raw).unwrap().unwrap();
|
||||
assert_eq!(res.subject.1, "OK");
|
||||
|
||||
let raw = b"HTTP/1.1 200 Howdy\r\n\r\n";
|
||||
let (res, _) = parse::<http::ClientMessage, _>(raw).unwrap().unwrap();
|
||||
let (res, _) = parse::<http::ClientTransaction, _>(raw).unwrap().unwrap();
|
||||
assert_eq!(res.subject.1, "Howdy");
|
||||
}
|
||||
|
||||
@@ -260,7 +271,7 @@ mod tests {
|
||||
fn bench_parse_incoming(b: &mut Bencher) {
|
||||
let raw = b"GET /echo HTTP/1.1\r\nHost: hyper.rs\r\n\r\n";
|
||||
b.iter(|| {
|
||||
parse::<http::ServerMessage, _>(raw).unwrap()
|
||||
parse::<http::ServerTransaction, _>(raw).unwrap()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user