feat(http1): Add higher-level HTTP upgrade support to Client and Server (#1563)
- Adds `Body::on_upgrade()` that returns an `OnUpgrade` future.
- Adds `hyper::upgrade` module containing types for dealing with
upgrades.
- Adds `server::conn::Connection::with_upgrades()` method to enable
these upgrades when using lower-level API (because of a missing
`Send` bound on the transport generic).
- Client connections are automatically enabled.
- Optimizes request parsing, to make up for extra work to look for
upgrade requests.
- Returns a smaller `DecodedLength` type instead of the fatter
`Decoder`, which should also allow a couple fewer branches.
- Removes the `Decode::Ignore` wrapper enum, and instead ignoring
1xx responses is handled directly in the response parsing code.
Ref #1563
Closes #1395
This commit is contained in:
@@ -596,33 +596,6 @@ test! {
|
||||
body: None,
|
||||
}
|
||||
|
||||
|
||||
test! {
|
||||
name: client_101_upgrade,
|
||||
|
||||
server:
|
||||
expected: "\
|
||||
GET /upgrade HTTP/1.1\r\n\
|
||||
host: {addr}\r\n\
|
||||
\r\n\
|
||||
",
|
||||
reply: "\
|
||||
HTTP/1.1 101 Switching Protocols\r\n\
|
||||
Upgrade: websocket\r\n\
|
||||
Connection: upgrade\r\n\
|
||||
\r\n\
|
||||
",
|
||||
|
||||
client:
|
||||
request:
|
||||
method: GET,
|
||||
url: "http://{addr}/upgrade",
|
||||
headers: {},
|
||||
body: None,
|
||||
error: |err| err.to_string() == "unsupported protocol upgrade",
|
||||
|
||||
}
|
||||
|
||||
test! {
|
||||
name: client_connect_method,
|
||||
|
||||
@@ -1277,6 +1250,68 @@ mod dispatch_impl {
|
||||
res.join(rx).map(|r| r.0).wait().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_upgrade() {
|
||||
use tokio_io::io::{read_to_end, write_all};
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let server = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = server.local_addr().unwrap();
|
||||
let runtime = Runtime::new().unwrap();
|
||||
let handle = runtime.reactor();
|
||||
|
||||
let connector = DebugConnector::new(&handle);
|
||||
|
||||
let client = Client::builder()
|
||||
.executor(runtime.executor())
|
||||
.build(connector);
|
||||
|
||||
let (tx1, rx1) = 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 101 Switching Protocols\r\n\
|
||||
Upgrade: foobar\r\n\
|
||||
\r\n\
|
||||
foobar=ready\
|
||||
").unwrap();
|
||||
let _ = tx1.send(());
|
||||
|
||||
let n = sock.read(&mut buf).expect("read 2");
|
||||
assert_eq!(&buf[..n], b"foo=bar");
|
||||
sock.write_all(b"bar=foo").expect("write 2");
|
||||
});
|
||||
|
||||
let rx = rx1.expect("thread panicked");
|
||||
|
||||
let req = Request::builder()
|
||||
.method("GET")
|
||||
.uri(&*format!("http://{}/up", addr))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let res = client.request(req);
|
||||
let res = res.join(rx).map(|r| r.0).wait().unwrap();
|
||||
|
||||
assert_eq!(res.status(), 101);
|
||||
let upgraded = res
|
||||
.into_body()
|
||||
.on_upgrade()
|
||||
.wait()
|
||||
.expect("on_upgrade");
|
||||
|
||||
let parts = upgraded.downcast::<DebugStream>().unwrap();
|
||||
assert_eq!(s(&parts.read_buf), "foobar=ready");
|
||||
|
||||
let io = parts.io;
|
||||
let io = write_all(io, b"foo=bar").wait().unwrap().0;
|
||||
let vec = read_to_end(io, vec![]).wait().unwrap().1;
|
||||
assert_eq!(vec, b"bar=foo");
|
||||
}
|
||||
|
||||
|
||||
struct DebugConnector {
|
||||
http: HttpConnector,
|
||||
|
||||
140
tests/server.rs
140
tests/server.rs
@@ -24,7 +24,7 @@ use futures::future::{self, FutureResult, Either};
|
||||
use futures::sync::oneshot;
|
||||
use futures_timer::Delay;
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::net::{TcpListener, TcpStream as TkTcpStream};
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::reactor::Handle;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
@@ -33,7 +33,7 @@ use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use hyper::client::Client;
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::{service_fn, Service};
|
||||
use hyper::service::{service_fn, service_fn_ok, Service};
|
||||
|
||||
fn tcp_bind(addr: &SocketAddr, handle: &Handle) -> ::tokio::io::Result<TcpListener> {
|
||||
let std_listener = StdTcpListener::bind(addr).unwrap();
|
||||
@@ -1270,6 +1270,142 @@ fn http_connect() {
|
||||
assert_eq!(vec, b"bar=foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrades_new() {
|
||||
use tokio_io::io::{read_to_end, write_all};
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap(), &rt.reactor()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let (read_101_tx, read_101_rx) = oneshot::channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut tcp = connect(&addr);
|
||||
tcp.write_all(b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Upgrade: foobar\r\n\
|
||||
Connection: upgrade\r\n\
|
||||
\r\n\
|
||||
eagerly optimistic\
|
||||
").expect("write 1");
|
||||
let mut buf = [0; 256];
|
||||
tcp.read(&mut buf).expect("read 1");
|
||||
|
||||
let expected = "HTTP/1.1 101 Switching Protocols\r\n";
|
||||
assert_eq!(s(&buf[..expected.len()]), expected);
|
||||
let _ = read_101_tx.send(());
|
||||
|
||||
let n = tcp.read(&mut buf).expect("read 2");
|
||||
assert_eq!(s(&buf[..n]), "foo=bar");
|
||||
tcp.write_all(b"bar=foo").expect("write 2");
|
||||
});
|
||||
|
||||
let (upgrades_tx, upgrades_rx) = mpsc::channel();
|
||||
let svc = service_fn_ok(move |req: Request<Body>| {
|
||||
let on_upgrade = req
|
||||
.into_body()
|
||||
.on_upgrade();
|
||||
let _ = upgrades_tx.send(on_upgrade);
|
||||
Response::builder()
|
||||
.status(101)
|
||||
.header("upgrade", "foobar")
|
||||
.body(hyper::Body::empty())
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let fut = listener.incoming()
|
||||
.into_future()
|
||||
.map_err(|_| -> hyper::Error { unreachable!() })
|
||||
.and_then(move |(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::new()
|
||||
.serve_connection(socket, svc)
|
||||
.with_upgrades()
|
||||
});
|
||||
|
||||
rt.block_on(fut).unwrap();
|
||||
let on_upgrade = upgrades_rx.recv().unwrap();
|
||||
|
||||
// wait so that we don't write until other side saw 101 response
|
||||
rt.block_on(read_101_rx).unwrap();
|
||||
|
||||
let upgraded = rt.block_on(on_upgrade).unwrap();
|
||||
let parts = upgraded.downcast::<TkTcpStream>().unwrap();
|
||||
let io = parts.io;
|
||||
assert_eq!(parts.read_buf, "eagerly optimistic");
|
||||
|
||||
let io = rt.block_on(write_all(io, b"foo=bar")).unwrap().0;
|
||||
let vec = rt.block_on(read_to_end(io, vec![])).unwrap().1;
|
||||
assert_eq!(s(&vec), "bar=foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_connect_new() {
|
||||
use tokio_io::io::{read_to_end, write_all};
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap(), &rt.reactor()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let (read_200_tx, read_200_rx) = oneshot::channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut tcp = connect(&addr);
|
||||
tcp.write_all(b"\
|
||||
CONNECT localhost HTTP/1.1\r\n\
|
||||
\r\n\
|
||||
eagerly optimistic\
|
||||
").expect("write 1");
|
||||
let mut buf = [0; 256];
|
||||
tcp.read(&mut buf).expect("read 1");
|
||||
|
||||
let expected = "HTTP/1.1 200 OK\r\n";
|
||||
assert_eq!(s(&buf[..expected.len()]), expected);
|
||||
let _ = read_200_tx.send(());
|
||||
|
||||
let n = tcp.read(&mut buf).expect("read 2");
|
||||
assert_eq!(s(&buf[..n]), "foo=bar");
|
||||
tcp.write_all(b"bar=foo").expect("write 2");
|
||||
});
|
||||
|
||||
let (upgrades_tx, upgrades_rx) = mpsc::channel();
|
||||
let svc = service_fn_ok(move |req: Request<Body>| {
|
||||
let on_upgrade = req
|
||||
.into_body()
|
||||
.on_upgrade();
|
||||
let _ = upgrades_tx.send(on_upgrade);
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.body(hyper::Body::empty())
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let fut = listener.incoming()
|
||||
.into_future()
|
||||
.map_err(|_| -> hyper::Error { unreachable!() })
|
||||
.and_then(move |(item, _incoming)| {
|
||||
let socket = item.unwrap();
|
||||
Http::new()
|
||||
.serve_connection(socket, svc)
|
||||
.with_upgrades()
|
||||
});
|
||||
|
||||
rt.block_on(fut).unwrap();
|
||||
let on_upgrade = upgrades_rx.recv().unwrap();
|
||||
|
||||
// wait so that we don't write until other side saw 200
|
||||
rt.block_on(read_200_rx).unwrap();
|
||||
|
||||
let upgraded = rt.block_on(on_upgrade).unwrap();
|
||||
let parts = upgraded.downcast::<TkTcpStream>().unwrap();
|
||||
let io = parts.io;
|
||||
assert_eq!(parts.read_buf, "eagerly optimistic");
|
||||
|
||||
let io = rt.block_on(write_all(io, b"foo=bar")).unwrap().0;
|
||||
let vec = rt.block_on(read_to_end(io, vec![])).unwrap().1;
|
||||
assert_eq!(s(&vec), "bar=foo");
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn parse_errors_send_4xx_response() {
|
||||
let runtime = Runtime::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user