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:
354
tests/client.rs
354
tests/client.rs
@@ -1,189 +1,27 @@
|
||||
#![deny(warnings)]
|
||||
extern crate hyper;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::TcpListener;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use hyper::client::{Handler, Request, Response, HttpConnector};
|
||||
use hyper::{Method, StatusCode, Next, Encoder, Decoder};
|
||||
use hyper::header::Headers;
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::client::{Client, Request, HttpConnector};
|
||||
use hyper::{Method, StatusCode};
|
||||
|
||||
fn s(bytes: &[u8]) -> &str {
|
||||
::std::str::from_utf8(bytes.as_ref()).unwrap()
|
||||
use futures::Future;
|
||||
use futures::sync::oneshot;
|
||||
|
||||
use tokio_core::reactor::{Core, Handle};
|
||||
|
||||
fn client(handle: &Handle) -> Client<HttpConnector> {
|
||||
Client::new(handle)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestHandler {
|
||||
opts: Opts,
|
||||
tx: mpsc::Sender<Msg>
|
||||
}
|
||||
|
||||
impl TestHandler {
|
||||
fn new(opts: Opts) -> (TestHandler, mpsc::Receiver<Msg>) {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
(TestHandler {
|
||||
opts: opts,
|
||||
tx: tx
|
||||
}, rx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Msg {
|
||||
Head(Response),
|
||||
Chunk(Vec<u8>),
|
||||
Error(hyper::Error),
|
||||
}
|
||||
|
||||
fn read(opts: &Opts) -> Next {
|
||||
if let Some(timeout) = opts.read_timeout {
|
||||
Next::read().timeout(timeout)
|
||||
} else {
|
||||
Next::read()
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<HttpStream> for TestHandler {
|
||||
fn on_request(&mut self, req: &mut Request) -> Next {
|
||||
req.set_method(self.opts.method.clone());
|
||||
req.headers_mut().extend(self.opts.headers.iter());
|
||||
if self.opts.body.is_some() {
|
||||
Next::write()
|
||||
} else {
|
||||
read(&self.opts)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_request_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
if let Some(ref mut body) = self.opts.body {
|
||||
let n = encoder.write(body).unwrap();
|
||||
*body = &body[n..];
|
||||
|
||||
if !body.is_empty() {
|
||||
return Next::write()
|
||||
}
|
||||
}
|
||||
encoder.close();
|
||||
read(&self.opts)
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: Response) -> Next {
|
||||
use hyper::header;
|
||||
// server responses can include a body until eof, if not size is specified
|
||||
let mut has_body = true;
|
||||
if let Some(len) = res.headers().get::<header::ContentLength>() {
|
||||
if **len == 0 {
|
||||
has_body = false;
|
||||
}
|
||||
}
|
||||
self.tx.send(Msg::Head(res)).unwrap();
|
||||
if has_body {
|
||||
read(&self.opts)
|
||||
} else {
|
||||
Next::end()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
let mut v = vec![0; 512];
|
||||
match decoder.read(&mut v) {
|
||||
Ok(n) => {
|
||||
v.truncate(n);
|
||||
self.tx.send(Msg::Chunk(v)).unwrap();
|
||||
if n == 0 {
|
||||
Next::end()
|
||||
} else {
|
||||
read(&self.opts)
|
||||
}
|
||||
},
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::WouldBlock => read(&self.opts),
|
||||
_ => panic!("io read error: {:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_error(&mut self, err: hyper::Error) -> Next {
|
||||
self.tx.send(Msg::Error(err)).unwrap();
|
||||
Next::remove()
|
||||
}
|
||||
}
|
||||
|
||||
struct Client {
|
||||
client: Option<hyper::Client<TestHandler>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Opts {
|
||||
body: Option<&'static [u8]>,
|
||||
method: Method,
|
||||
headers: Headers,
|
||||
read_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Default for Opts {
|
||||
fn default() -> Opts {
|
||||
Opts {
|
||||
body: None,
|
||||
method: Method::Get,
|
||||
headers: Headers::new(),
|
||||
read_timeout: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn opts() -> Opts {
|
||||
Opts::default()
|
||||
}
|
||||
|
||||
impl Opts {
|
||||
fn method(mut self, method: Method) -> Opts {
|
||||
self.method = method;
|
||||
self
|
||||
}
|
||||
|
||||
fn header<H: ::hyper::header::Header>(mut self, header: H) -> Opts {
|
||||
self.headers.set(header);
|
||||
self
|
||||
}
|
||||
|
||||
fn body(mut self, body: Option<&'static [u8]>) -> Opts {
|
||||
self.body = body;
|
||||
self
|
||||
}
|
||||
|
||||
fn read_timeout(mut self, timeout: Duration) -> Opts {
|
||||
self.read_timeout = Some(timeout);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
fn request<U>(&self, url: U, opts: Opts) -> mpsc::Receiver<Msg>
|
||||
where U: AsRef<str> {
|
||||
let (handler, rx) = TestHandler::new(opts);
|
||||
self.client.as_ref().unwrap()
|
||||
.request(url.as_ref().parse().unwrap(), handler).unwrap();
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Client {
|
||||
fn drop(&mut self) {
|
||||
self.client.take().map(|c| c.close());
|
||||
}
|
||||
}
|
||||
|
||||
fn client() -> Client {
|
||||
let c = hyper::Client::<TestHandler>::configure()
|
||||
.connector(HttpConnector::default())
|
||||
.build().unwrap();
|
||||
Client {
|
||||
client: Some(c),
|
||||
}
|
||||
fn s(buf: &[u8]) -> &str {
|
||||
::std::str::from_utf8(buf).unwrap()
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
@@ -206,43 +44,50 @@ macro_rules! test {
|
||||
) => (
|
||||
#[test]
|
||||
fn $name() {
|
||||
#[allow(unused)]
|
||||
#![allow(unused)]
|
||||
use hyper::header::*;
|
||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = server.local_addr().unwrap();
|
||||
let client = client();
|
||||
let opts = opts()
|
||||
.method(Method::$client_method)
|
||||
.body($request_body);
|
||||
let mut core = Core::new().unwrap();
|
||||
let client = client(&core.handle());
|
||||
let mut req = Request::new(Method::$client_method, format!($client_url, addr=addr).parse().unwrap());
|
||||
$(
|
||||
let opts = opts.header($request_headers);
|
||||
req.headers_mut().set($request_headers);
|
||||
)*
|
||||
let res = client.request(format!($client_url, addr=addr), opts);
|
||||
|
||||
let mut inc = server.accept().unwrap().0;
|
||||
inc.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
inc.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
let expected = format!($server_expected, addr=addr);
|
||||
let mut buf = [0; 4096];
|
||||
let mut n = 0;
|
||||
while n < buf.len() && n < expected.len() {
|
||||
n += inc.read(&mut buf[n..]).unwrap();
|
||||
if let Some(body) = $request_body {
|
||||
let body: &'static str = body;
|
||||
req.set_body(body);
|
||||
}
|
||||
assert_eq!(s(&buf[..n]), expected);
|
||||
let res = client.request(req);
|
||||
|
||||
inc.write_all($server_reply.as_ref()).unwrap();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
if let Msg::Head(head) = res.recv().unwrap() {
|
||||
assert_eq!(head.status(), &StatusCode::$client_status);
|
||||
$(
|
||||
assert_eq!(head.headers().get(), Some(&$response_headers));
|
||||
)*
|
||||
} else {
|
||||
panic!("we lost the head!");
|
||||
}
|
||||
//drop(inc);
|
||||
thread::spawn(move || {
|
||||
let mut inc = server.accept().unwrap().0;
|
||||
inc.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
inc.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
let expected = format!($server_expected, addr=addr);
|
||||
let mut buf = [0; 4096];
|
||||
let mut n = 0;
|
||||
while n < buf.len() && n < expected.len() {
|
||||
n += inc.read(&mut buf[n..]).unwrap();
|
||||
}
|
||||
assert_eq!(s(&buf[..n]), expected);
|
||||
|
||||
assert!(res.recv().is_err());
|
||||
inc.write_all($server_reply.as_ref()).unwrap();
|
||||
tx.complete(());
|
||||
});
|
||||
|
||||
let rx = rx.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
|
||||
let work = res.join(rx).map(|r| r.0);
|
||||
|
||||
let res = core.run(work).unwrap();
|
||||
assert_eq!(res.status(), &StatusCode::$client_status);
|
||||
$(
|
||||
assert_eq!(res.headers().get(), Some(&$response_headers));
|
||||
)*
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -311,7 +156,7 @@ test! {
|
||||
headers: [
|
||||
ContentLength(7),
|
||||
],
|
||||
body: Some(b"foo bar"),
|
||||
body: Some("foo bar"),
|
||||
response:
|
||||
status: Ok,
|
||||
headers: [],
|
||||
@@ -340,51 +185,86 @@ test! {
|
||||
headers: [
|
||||
TransferEncoding::chunked(),
|
||||
],
|
||||
body: Some(b"foo bar baz"),
|
||||
body: Some("foo bar baz"),
|
||||
response:
|
||||
status: Ok,
|
||||
headers: [],
|
||||
body: None,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_read_timeout() {
|
||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = server.local_addr().unwrap();
|
||||
let client = client();
|
||||
let res = client.request(format!("http://{}/", addr), opts().read_timeout(Duration::from_secs(3)));
|
||||
|
||||
let mut inc = server.accept().unwrap().0;
|
||||
let mut buf = [0; 4096];
|
||||
inc.read(&mut buf).unwrap();
|
||||
|
||||
match res.recv() {
|
||||
Ok(Msg::Error(hyper::Error::Timeout)) => (),
|
||||
other => panic!("expected timeout, actual: {:?}", other)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_keep_alive() {
|
||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = server.local_addr().unwrap();
|
||||
let client = client();
|
||||
let res = client.request(format!("http://{}/a", addr), opts());
|
||||
let mut core = Core::new().unwrap();
|
||||
let client = client(&core.handle());
|
||||
|
||||
let mut sock = server.accept().unwrap().0;
|
||||
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
let mut buf = [0; 4096];
|
||||
sock.read(&mut buf).expect("read 1");
|
||||
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 1");
|
||||
|
||||
while let Ok(_) = res.recv() {}
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
thread::spawn(move || {
|
||||
let mut sock = server.accept().unwrap().0;
|
||||
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
let mut buf = [0; 4096];
|
||||
sock.read(&mut buf).expect("read 1");
|
||||
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 1");
|
||||
tx1.complete(());
|
||||
|
||||
let res = client.request(format!("http://{}/b", addr), opts());
|
||||
sock.read(&mut buf).expect("read 2");
|
||||
let second_get = b"GET /b HTTP/1.1\r\n";
|
||||
assert_eq!(&buf[..second_get.len()], second_get);
|
||||
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 2");
|
||||
sock.read(&mut buf).expect("read 2");
|
||||
let second_get = b"GET /b HTTP/1.1\r\n";
|
||||
assert_eq!(&buf[..second_get.len()], second_get);
|
||||
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 2");
|
||||
tx2.complete(());
|
||||
});
|
||||
|
||||
while let Ok(_) = res.recv() {}
|
||||
|
||||
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
let res = client.get(format!("http://{}/a", addr).parse().unwrap());
|
||||
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||
|
||||
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
let res = client.get(format!("http://{}/b", addr).parse().unwrap());
|
||||
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn client_pooled_socket_disconnected() {
|
||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = server.local_addr().unwrap();
|
||||
let mut core = Core::new().unwrap();
|
||||
let client = client(&core.handle());
|
||||
|
||||
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
thread::spawn(move || {
|
||||
let mut sock = server.accept().unwrap().0;
|
||||
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
let mut buf = [0; 4096];
|
||||
sock.read(&mut buf).expect("read 1");
|
||||
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 1");
|
||||
drop(sock);
|
||||
tx1.complete(());
|
||||
|
||||
let mut sock = server.accept().unwrap().0;
|
||||
sock.read(&mut buf).expect("read 2");
|
||||
let second_get = b"GET /b HTTP/1.1\r\n";
|
||||
assert_eq!(&buf[..second_get.len()], second_get);
|
||||
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").expect("write 2");
|
||||
tx2.complete(());
|
||||
});
|
||||
|
||||
|
||||
|
||||
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
let res = client.get(format!("http://{}/a", addr).parse().unwrap());
|
||||
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||
|
||||
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
|
||||
let res = client.get(format!("http://{}/b", addr).parse().unwrap());
|
||||
core.run(res.join(rx).map(|r| r.0)).unwrap();
|
||||
}
|
||||
|
||||
314
tests/server.rs
314
tests/server.rs
@@ -1,30 +1,28 @@
|
||||
#![deny(warnings)]
|
||||
extern crate hyper;
|
||||
extern crate futures;
|
||||
extern crate spmc;
|
||||
|
||||
use futures::Future;
|
||||
use futures::stream::Stream;
|
||||
|
||||
use std::net::{TcpStream, SocketAddr};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use hyper::{Next, Encoder, Decoder};
|
||||
use hyper::net::{HttpListener, HttpStream};
|
||||
use hyper::server::{Server, Handler, Request, Response};
|
||||
use hyper::server::{Server, Request, Response, Service, NewService};
|
||||
|
||||
struct Serve {
|
||||
listening: Option<hyper::server::Listening>,
|
||||
msg_rx: mpsc::Receiver<Msg>,
|
||||
reply_tx: mpsc::Sender<Reply>,
|
||||
reply_tx: spmc::Sender<Reply>,
|
||||
spawn_rx: mpsc::Receiver<()>,
|
||||
}
|
||||
|
||||
impl Serve {
|
||||
fn addrs(&self) -> &[SocketAddr] {
|
||||
self.listening.as_ref().unwrap().addrs()
|
||||
}
|
||||
|
||||
fn addr(&self) -> &SocketAddr {
|
||||
let addrs = self.addrs();
|
||||
assert!(addrs.len() == 1);
|
||||
&addrs[0]
|
||||
self.listening.as_ref().unwrap().addr()
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -49,7 +47,7 @@ impl Serve {
|
||||
}
|
||||
|
||||
struct ReplyBuilder<'a> {
|
||||
tx: &'a mpsc::Sender<Reply>,
|
||||
tx: &'a spmc::Sender<Reply>,
|
||||
}
|
||||
|
||||
impl<'a> ReplyBuilder<'a> {
|
||||
@@ -73,16 +71,18 @@ impl<'a> ReplyBuilder<'a> {
|
||||
impl Drop for Serve {
|
||||
fn drop(&mut self) {
|
||||
self.listening.take().unwrap().close();
|
||||
self.spawn_rx.recv().expect("server thread should shutdown cleanly");
|
||||
}
|
||||
}
|
||||
|
||||
struct TestHandler {
|
||||
#[derive(Clone)]
|
||||
struct TestService {
|
||||
tx: mpsc::Sender<Msg>,
|
||||
reply: Vec<Reply>,
|
||||
peeked: Option<Vec<u8>>,
|
||||
timeout: Option<Duration>,
|
||||
reply: spmc::Receiver<Reply>,
|
||||
_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Reply {
|
||||
Status(hyper::StatusCode),
|
||||
Headers(hyper::Headers),
|
||||
@@ -94,72 +94,57 @@ enum Msg {
|
||||
Chunk(Vec<u8>),
|
||||
}
|
||||
|
||||
impl TestHandler {
|
||||
fn next(&self, next: Next) -> Next {
|
||||
if let Some(dur) = self.timeout {
|
||||
next.timeout(dur)
|
||||
} else {
|
||||
next
|
||||
}
|
||||
impl NewService for TestService {
|
||||
type Request = Request;
|
||||
type Response = Response;
|
||||
type Error = hyper::Error;
|
||||
|
||||
type Instance = TestService;
|
||||
|
||||
fn new_service(&self) -> std::io::Result<TestService> {
|
||||
Ok(self.clone())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
impl Handler<HttpStream> for TestHandler {
|
||||
fn on_request(&mut self, _req: Request<HttpStream>) -> Next {
|
||||
//self.tx.send(Msg::Head(req)).unwrap();
|
||||
self.next(Next::read())
|
||||
impl Service for TestService {
|
||||
type Request = Request;
|
||||
type Response = Response;
|
||||
type Error = hyper::Error;
|
||||
type Future = Box<Future<Item=Response, Error=hyper::Error>>;
|
||||
fn call(&self, req: Request) -> Self::Future {
|
||||
let tx = self.tx.clone();
|
||||
let replies = self.reply.clone();
|
||||
req.body().for_each(move |chunk| {
|
||||
tx.send(Msg::Chunk(chunk.to_vec())).unwrap();
|
||||
Ok(())
|
||||
}).map(move |_| {
|
||||
let mut res = Response::new();
|
||||
while let Ok(reply) = replies.try_recv() {
|
||||
match reply {
|
||||
Reply::Status(s) => {
|
||||
res.set_status(s);
|
||||
},
|
||||
Reply::Headers(headers) => {
|
||||
*res.headers_mut() = headers;
|
||||
},
|
||||
Reply::Body(body) => {
|
||||
res.set_body(body);
|
||||
},
|
||||
}
|
||||
}
|
||||
res
|
||||
}).boxed()
|
||||
}
|
||||
|
||||
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
|
||||
let mut vec = vec![0; 1024];
|
||||
match decoder.read(&mut vec) {
|
||||
Ok(0) => {
|
||||
self.next(Next::write())
|
||||
}
|
||||
Ok(n) => {
|
||||
vec.truncate(n);
|
||||
self.tx.send(Msg::Chunk(vec)).unwrap();
|
||||
self.next(Next::read())
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::WouldBlock => self.next(Next::read()),
|
||||
_ => panic!("test error: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response(&mut self, res: &mut Response) -> Next {
|
||||
for reply in self.reply.drain(..) {
|
||||
match reply {
|
||||
Reply::Status(s) => {
|
||||
res.set_status(s);
|
||||
},
|
||||
Reply::Headers(headers) => {
|
||||
use std::iter::Extend;
|
||||
res.headers_mut().extend(headers.iter());
|
||||
},
|
||||
Reply::Body(body) => {
|
||||
self.peeked = Some(body);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if self.peeked.is_some() {
|
||||
self.next(Next::write())
|
||||
} else {
|
||||
self.next(Next::end())
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
match self.peeked {
|
||||
Some(ref body) => {
|
||||
encoder.write(body).unwrap();
|
||||
self.next(Next::end())
|
||||
},
|
||||
None => self.next(Next::end())
|
||||
}
|
||||
}
|
||||
fn connect(addr: &SocketAddr) -> TcpStream {
|
||||
let req = TcpStream::connect(addr).unwrap();
|
||||
req.set_read_timeout(Some(Duration::from_secs(1))).unwrap();
|
||||
req.set_write_timeout(Some(Duration::from_secs(1))).unwrap();
|
||||
req
|
||||
}
|
||||
|
||||
fn serve() -> Serve {
|
||||
@@ -167,45 +152,37 @@ fn serve() -> Serve {
|
||||
}
|
||||
|
||||
fn serve_with_timeout(dur: Option<Duration>) -> Serve {
|
||||
serve_n_with_timeout(1, dur)
|
||||
}
|
||||
|
||||
fn serve_n(n: u32) -> Serve {
|
||||
serve_n_with_timeout(n, None)
|
||||
}
|
||||
|
||||
fn serve_n_with_timeout(n: u32, dur: Option<Duration>) -> Serve {
|
||||
use std::thread;
|
||||
|
||||
let (thread_tx, thread_rx) = mpsc::channel();
|
||||
let (spawn_tx, spawn_rx) = mpsc::channel();
|
||||
let (msg_tx, msg_rx) = mpsc::channel();
|
||||
let (reply_tx, reply_rx) = mpsc::channel();
|
||||
let (reply_tx, reply_rx) = spmc::channel();
|
||||
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
let listeners = (0..n).map(|_| HttpListener::bind(&addr).unwrap());
|
||||
let (listening, server) = Server::new(listeners)
|
||||
.handle(move |_| {
|
||||
let mut replies = Vec::new();
|
||||
while let Ok(reply) = reply_rx.try_recv() {
|
||||
replies.push(reply);
|
||||
}
|
||||
TestHandler {
|
||||
tx: msg_tx.clone(),
|
||||
timeout: dur,
|
||||
reply: replies,
|
||||
peeked: None,
|
||||
}
|
||||
}).unwrap();
|
||||
|
||||
|
||||
let thread_name = format!("test-server-{}: {:?}", listening, dur);
|
||||
let thread_name = format!("test-server-{:?}", dur);
|
||||
thread::Builder::new().name(thread_name).spawn(move || {
|
||||
let (listening, server) = Server::standalone(move |tokio| {
|
||||
Server::http(&addr, tokio).unwrap()
|
||||
.handle(TestService {
|
||||
tx: msg_tx.clone(),
|
||||
_timeout: dur,
|
||||
reply: reply_rx,
|
||||
}, tokio)
|
||||
}).unwrap();
|
||||
thread_tx.send(listening).unwrap();
|
||||
server.run();
|
||||
spawn_tx.send(()).unwrap();
|
||||
}).unwrap();
|
||||
|
||||
let listening = thread_rx.recv().unwrap();
|
||||
|
||||
Serve {
|
||||
listening: Some(listening),
|
||||
msg_rx: msg_rx,
|
||||
reply_tx: reply_tx,
|
||||
spawn_rx: spawn_rx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,10 +190,12 @@ fn serve_n_with_timeout(n: u32, dur: Option<Duration>) -> Serve {
|
||||
fn server_get_should_ignore_body() {
|
||||
let server = serve();
|
||||
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
let mut req = connect(server.addr());
|
||||
// Connection: close = don't try to parse the body as a new request
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n
|
||||
\r\n\
|
||||
I shouldn't be read.\r\n\
|
||||
").unwrap();
|
||||
@@ -228,7 +207,7 @@ fn server_get_should_ignore_body() {
|
||||
#[test]
|
||||
fn server_get_with_body() {
|
||||
let server = serve();
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
@@ -244,17 +223,18 @@ fn server_get_with_body() {
|
||||
|
||||
#[test]
|
||||
fn server_get_fixed_response() {
|
||||
|
||||
let foo_bar = b"foo bar baz";
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.status(hyper::Ok)
|
||||
.header(hyper::header::ContentLength(foo_bar.len() as u64))
|
||||
.body(foo_bar);
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
let mut body = String::new();
|
||||
@@ -272,7 +252,7 @@ fn server_get_chunked_response() {
|
||||
.status(hyper::Ok)
|
||||
.header(hyper::header::TransferEncoding::chunked())
|
||||
.body(foo_bar);
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
@@ -286,10 +266,67 @@ fn server_get_chunked_response() {
|
||||
assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_get_chunked_response_with_ka() {
|
||||
let foo_bar = b"foo bar baz";
|
||||
let foo_bar_chunk = b"\r\nfoo bar baz\r\n0\r\n\r\n";
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.status(hyper::Ok)
|
||||
.header(hyper::header::TransferEncoding::chunked())
|
||||
.body(foo_bar);
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: keep-alive\r\n\
|
||||
\r\n\
|
||||
").expect("writing 1");
|
||||
|
||||
let mut buf = [0; 1024 * 4];
|
||||
let mut ntotal = 0;
|
||||
loop {
|
||||
let n = req.read(&mut buf[ntotal..]).expect("reading 1");
|
||||
ntotal = ntotal + n;
|
||||
assert!(ntotal < buf.len());
|
||||
if &buf[ntotal - foo_bar_chunk.len()..ntotal] == foo_bar_chunk {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// try again!
|
||||
|
||||
let quux = b"zar quux";
|
||||
server.reply()
|
||||
.status(hyper::Ok)
|
||||
.header(hyper::header::ContentLength(quux.len() as u64))
|
||||
.body(quux);
|
||||
req.write_all(b"\
|
||||
GET /quux HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
").expect("writing 2");
|
||||
|
||||
let mut buf = [0; 1024 * 8];
|
||||
loop {
|
||||
let n = req.read(&mut buf[..]).expect("reading 2");
|
||||
assert!(n > 0, "n = {}", n);
|
||||
if n < buf.len() && n > 0 {
|
||||
if &buf[n - quux.len()..n] == quux {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn server_post_with_chunked_body() {
|
||||
let server = serve();
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
POST / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
@@ -302,56 +339,31 @@ fn server_post_with_chunked_body() {
|
||||
2\r\n\
|
||||
rt\r\n\
|
||||
0\r\n\
|
||||
\r\n
|
||||
\r\n\
|
||||
").unwrap();
|
||||
req.read(&mut [0; 256]).unwrap();
|
||||
|
||||
assert_eq!(server.body(), b"qwert");
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn server_empty_response() {
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.status(hyper::Ok);
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n
|
||||
\r\n\
|
||||
").unwrap();
|
||||
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
|
||||
assert_eq!(response, "foo");
|
||||
assert!(!response.contains("Transfer-Encoding: chunked\r\n"));
|
||||
|
||||
let mut lines = response.lines();
|
||||
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||
|
||||
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
assert_eq!(lines.next(), None);
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn server_empty_response_chunked() {
|
||||
let server = serve();
|
||||
|
||||
server.reply()
|
||||
.status(hyper::Ok)
|
||||
.body("");
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Content-Length: 0\r\n
|
||||
Connection: close\r\n
|
||||
\r\n\
|
||||
").unwrap();
|
||||
|
||||
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
|
||||
@@ -369,49 +381,44 @@ fn server_empty_response_chunked() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_empty_response_chunked_without_calling_write() {
|
||||
fn server_empty_response_chunked_without_body_should_set_content_length() {
|
||||
extern crate pretty_env_logger;
|
||||
let _ = pretty_env_logger::init();
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.status(hyper::Ok)
|
||||
.header(hyper::header::TransferEncoding::chunked());
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
").unwrap();
|
||||
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
|
||||
assert!(response.contains("Transfer-Encoding: chunked\r\n"));
|
||||
assert!(!response.contains("Transfer-Encoding: chunked\r\n"));
|
||||
assert!(response.contains("Content-Length: 0\r\n"));
|
||||
|
||||
let mut lines = response.lines();
|
||||
assert_eq!(lines.next(), Some("HTTP/1.1 200 OK"));
|
||||
|
||||
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
// 0\r\n\r\n
|
||||
assert_eq!(lines.next(), Some("0"));
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
assert_eq!(lines.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_keep_alive() {
|
||||
extern crate env_logger;
|
||||
env_logger::init().unwrap();
|
||||
|
||||
let foo_bar = b"foo bar baz";
|
||||
let server = serve();
|
||||
server.reply()
|
||||
.status(hyper::Ok)
|
||||
.header(hyper::header::ContentLength(foo_bar.len() as u64))
|
||||
.body(foo_bar);
|
||||
let mut req = TcpStream::connect(server.addr()).unwrap();
|
||||
req.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
req.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
@@ -426,7 +433,6 @@ fn server_keep_alive() {
|
||||
if &buf[n - foo_bar.len()..n] == foo_bar {
|
||||
break;
|
||||
} else {
|
||||
println!("{:?}", ::std::str::from_utf8(&buf[..n]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,6 +463,7 @@ fn server_keep_alive() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn server_get_with_body_three_listeners() {
|
||||
let server = serve_n(3);
|
||||
@@ -479,3 +486,4 @@ fn server_get_with_body_three_listeners() {
|
||||
assert_eq!(server.body(), comparison);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user