feat(lib): switch to non-blocking (asynchronous) IO

BREAKING CHANGE: This breaks a lot of the Client and Server APIs.
  Check the documentation for how Handlers can be used for asynchronous
  events.
This commit is contained in:
Sean McArthur
2016-05-03 20:45:43 -07:00
parent 1ec56fe6b6
commit d35992d019
65 changed files with 5599 additions and 5023 deletions

205
tests/client.rs Normal file
View File

@@ -0,0 +1,205 @@
#![deny(warnings)]
extern crate hyper;
use std::io::{self, Read, Write};
use std::net::TcpListener;
use std::sync::mpsc;
use std::time::Duration;
use hyper::client::{Handler, Request, Response, HttpConnector};
use hyper::header;
use hyper::{Method, StatusCode, Next, Encoder, Decoder};
use hyper::net::HttpStream;
fn s(bytes: &[u8]) -> &str {
::std::str::from_utf8(bytes.as_ref()).unwrap()
}
#[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());
read(&self.opts)
}
fn on_request_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
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 {
method: Method,
read_timeout: Option<Duration>,
}
impl Default for Opts {
fn default() -> Opts {
Opts {
method: Method::Get,
read_timeout: None,
}
}
}
fn opts() -> Opts {
Opts::default()
}
impl Opts {
fn method(mut self, method: Method) -> Opts {
self.method = method;
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),
}
}
#[test]
fn client_get() {
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().method(Method::Get));
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 mut buf = [0; 4096];
let n = inc.read(&mut buf).unwrap();
let expected = format!("GET / HTTP/1.1\r\nHost: {}\r\n\r\n", addr);
assert_eq!(s(&buf[..n]), expected);
inc.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").unwrap();
if let Msg::Head(head) = res.recv().unwrap() {
assert_eq!(head.status(), &StatusCode::Ok);
assert_eq!(head.headers().get(), Some(&header::ContentLength(0)));
} else {
panic!("we lost the head!");
}
//drop(inc);
assert!(res.recv().is_err());
}
#[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)
}
}

379
tests/server.rs Normal file
View File

@@ -0,0 +1,379 @@
#![deny(warnings)]
extern crate hyper;
use std::net::{TcpStream, SocketAddr};
use std::io::{self, Read, Write};
use std::sync::mpsc;
use std::time::Duration;
use hyper::{Next, Encoder, Decoder};
use hyper::net::HttpStream;
use hyper::server::{Server, Handler, Request, Response};
struct Serve {
listening: Option<hyper::server::Listening>,
msg_rx: mpsc::Receiver<Msg>,
reply_tx: mpsc::Sender<Reply>,
}
impl Serve {
fn addr(&self) -> &SocketAddr {
self.listening.as_ref().unwrap().addr()
}
/*
fn head(&self) -> Request {
unimplemented!()
}
*/
fn body(&self) -> Vec<u8> {
let mut buf = vec![];
while let Ok(Msg::Chunk(msg)) = self.msg_rx.try_recv() {
buf.extend(&msg);
}
buf
}
fn reply(&self) -> ReplyBuilder {
ReplyBuilder {
tx: &self.reply_tx
}
}
}
struct ReplyBuilder<'a> {
tx: &'a mpsc::Sender<Reply>,
}
impl<'a> ReplyBuilder<'a> {
fn status(self, status: hyper::StatusCode) -> Self {
self.tx.send(Reply::Status(status)).unwrap();
self
}
fn header<H: hyper::header::Header>(self, header: H) -> Self {
let mut headers = hyper::Headers::new();
headers.set(header);
self.tx.send(Reply::Headers(headers)).unwrap();
self
}
fn body<T: AsRef<[u8]>>(self, body: T) {
self.tx.send(Reply::Body(body.as_ref().into())).unwrap();
}
}
impl Drop for Serve {
fn drop(&mut self) {
self.listening.take().unwrap().close();
}
}
struct TestHandler {
tx: mpsc::Sender<Msg>,
rx: mpsc::Receiver<Reply>,
peeked: Option<Vec<u8>>,
timeout: Option<Duration>,
}
enum Reply {
Status(hyper::StatusCode),
Headers(hyper::Headers),
Body(Vec<u8>),
}
enum Msg {
//Head(Request),
Chunk(Vec<u8>),
}
impl TestHandler {
fn next(&self, next: Next) -> Next {
if let Some(dur) = self.timeout {
next.timeout(dur)
} else {
next
}
}
}
impl Handler<HttpStream> for TestHandler {
fn on_request(&mut self, _req: Request) -> Next {
//self.tx.send(Msg::Head(req)).unwrap();
self.next(Next::read())
}
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 {
loop {
match self.rx.try_recv() {
Ok(Reply::Status(s)) => {
res.set_status(s);
},
Ok(Reply::Headers(headers)) => {
use std::iter::Extend;
res.headers_mut().extend(headers.iter());
},
Ok(Reply::Body(body)) => {
self.peeked = Some(body);
},
Err(..) => {
return 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 serve() -> Serve {
serve_with_timeout(None)
}
fn serve_with_timeout(dur: Option<Duration>) -> Serve {
use std::thread;
let (msg_tx, msg_rx) = mpsc::channel();
let (reply_tx, reply_rx) = mpsc::channel();
let mut reply_rx = Some(reply_rx);
let (listening, server) = Server::http(&"127.0.0.1:0".parse().unwrap()).unwrap()
.handle(move |_| TestHandler {
tx: msg_tx.clone(),
timeout: dur,
rx: reply_rx.take().unwrap(),
peeked: None,
}).unwrap();
let thread_name = format!("test-server-{}: {:?}", listening.addr(), dur);
thread::Builder::new().name(thread_name).spawn(move || {
server.run();
}).unwrap();
Serve {
listening: Some(listening),
msg_rx: msg_rx,
reply_tx: reply_tx,
}
}
#[test]
fn server_get_should_ignore_body() {
let server = serve();
let mut req = TcpStream::connect(server.addr()).unwrap();
req.write_all(b"\
GET / HTTP/1.1\r\n\
Host: example.domain\r\n\
\r\n\
I shouldn't be read.\r\n\
").unwrap();
req.read(&mut [0; 256]).unwrap();
assert_eq!(server.body(), b"");
}
#[test]
fn server_get_with_body() {
let server = serve();
let mut req = TcpStream::connect(server.addr()).unwrap();
req.write_all(b"\
GET / HTTP/1.1\r\n\
Host: example.domain\r\n\
Content-Length: 19\r\n\
\r\n\
I'm a good request.\r\n\
").unwrap();
req.read(&mut [0; 256]).unwrap();
// note: doesnt include trailing \r\n, cause Content-Length wasn't 21
assert_eq!(server.body(), b"I'm a good request.");
}
#[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();
req.write_all(b"\
GET / HTTP/1.1\r\n\
Host: example.domain\r\n\
Connection: close\r\n
\r\n\
").unwrap();
let mut body = String::new();
req.read_to_string(&mut body).unwrap();
let n = body.find("\r\n\r\n").unwrap() + 4;
assert_eq!(&body[n..], "foo bar baz");
}
#[test]
fn server_get_chunked_response() {
let foo_bar = b"foo bar baz";
let server = serve();
server.reply()
.status(hyper::Ok)
.header(hyper::header::TransferEncoding::chunked())
.body(foo_bar);
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 body = String::new();
req.read_to_string(&mut body).unwrap();
let n = body.find("\r\n\r\n").unwrap() + 4;
assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n");
}
#[test]
fn server_post_with_chunked_body() {
let server = serve();
let mut req = TcpStream::connect(server.addr()).unwrap();
req.write_all(b"\
POST / HTTP/1.1\r\n\
Host: example.domain\r\n\
Transfer-Encoding: chunked\r\n\
\r\n\
1\r\n\
q\r\n\
2\r\n\
we\r\n\
2\r\n\
rt\r\n\
0\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();
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!(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(""));
// 0\r\n\r\n
assert_eq!(lines.next(), Some("0"));
assert_eq!(lines.next(), Some(""));
assert_eq!(lines.next(), None);
}
#[test]
fn server_empty_response_chunked_without_calling_write() {
let server = serve();
server.reply()
.status(hyper::Ok)
.header(hyper::header::TransferEncoding::chunked());
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!(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(""));
// 0\r\n\r\n
assert_eq!(lines.next(), Some("0"));
assert_eq!(lines.next(), Some(""));
assert_eq!(lines.next(), None);
}