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,