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:
Sean McArthur
2018-06-14 13:39:29 -07:00
committed by GitHub
parent 1c3fbfd6bf
commit fea29b29e2
26 changed files with 1271 additions and 574 deletions

View File

@@ -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,

View File

@@ -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();