Skip to content

Commit 3a6080b

Browse files
committed
fix(client): coerce HTTP_2 requests to HTTP_11
Closes #1770
1 parent 2b0a5ea commit 3a6080b

File tree

3 files changed

+99
-14
lines changed

3 files changed

+99
-14
lines changed

src/client/mod.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,14 @@ where C: Connect + Sync + 'static,
236236
match req.version() {
237237
Version::HTTP_11 => (),
238238
Version::HTTP_10 => if is_http_connect {
239-
debug!("CONNECT is not allowed for HTTP/1.0");
239+
warn!("CONNECT is not allowed for HTTP/1.0");
240240
return ResponseFuture::new(Box::new(future::err(::Error::new_user_unsupported_request_method())));
241241
},
242-
other => if self.config.ver != Ver::Http2 {
243-
error!("Request has unsupported version \"{:?}\"", other);
244-
return ResponseFuture::new(Box::new(future::err(::Error::new_user_unsupported_version())));
245-
}
242+
other_h2 @ Version::HTTP_2 => if self.config.ver != Ver::Http2 {
243+
return ResponseFuture::error_version(other_h2);
244+
},
245+
// completely unsupported HTTP version (like HTTP/0.9)!
246+
other => return ResponseFuture::error_version(other),
246247
};
247248

248249
let domain = match extract_domain(req.uri_mut(), is_http_connect) {
@@ -591,6 +592,11 @@ impl ResponseFuture {
591592
inner: fut,
592593
}
593594
}
595+
596+
fn error_version(ver: Version) -> Self {
597+
warn!("Request has unsupported version \"{:?}\"", ver);
598+
ResponseFuture::new(Box::new(future::err(::Error::new_user_unsupported_version())))
599+
}
594600
}
595601

596602
impl fmt::Debug for ResponseFuture {

src/proto/h1/role.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ impl Http1Transaction for Server {
271271
warn!("response with HTTP2 version coerced to HTTP/1.1");
272272
extend(dst, b"HTTP/1.1 ");
273273
},
274-
_ => unreachable!(),
274+
other => panic!("unexpected response version: {:?}", other),
275275
}
276276

277277
extend(dst, msg.head.subject.as_str().as_bytes());
@@ -667,7 +667,11 @@ impl Http1Transaction for Client {
667667
match msg.head.version {
668668
Version::HTTP_10 => extend(dst, b"HTTP/1.0"),
669669
Version::HTTP_11 => extend(dst, b"HTTP/1.1"),
670-
_ => unreachable!(),
670+
Version::HTTP_2 => {
671+
warn!("request with HTTP2 version coerced to HTTP/1.1");
672+
extend(dst, b"HTTP/1.1");
673+
},
674+
other => panic!("unexpected request version: {:?}", other),
671675
}
672676
extend(dst, b"\r\n");
673677

tests/client.rs

+82-7
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,6 @@ macro_rules! test {
3939
request: {$(
4040
$c_req_prop:ident: $c_req_val: tt,
4141
)*},
42-
/*
43-
method: $client_method:ident,
44-
url: $client_url:expr,
45-
headers: { $($request_header_name:expr => $request_header_val:expr,)* },
46-
body: $request_body:expr,
47-
},
48-
*/
4942

5043
response:
5144
status: $client_status:ident,
@@ -299,6 +292,10 @@ macro_rules! __client_req_prop {
299292
$req_builder.method(Method::$method);
300293
});
301294

295+
($req_builder:ident, $body:ident, $addr:ident, version: $version:ident) => ({
296+
$req_builder.version(hyper::Version::$version);
297+
});
298+
302299
($req_builder:ident, $body:ident, $addr:ident, url: $url:expr) => ({
303300
$req_builder.uri(format!($url, addr=$addr));
304301
});
@@ -724,6 +721,38 @@ test! {
724721
body: None,
725722
}
726723

724+
test! {
725+
name: client_h1_rejects_http2,
726+
727+
server:
728+
expected: "won't get here {addr}",
729+
reply: "won't reply",
730+
731+
client:
732+
request: {
733+
method: GET,
734+
url: "http://{addr}/",
735+
version: HTTP_2,
736+
},
737+
error: |err| err.to_string() == "request has unsupported HTTP version",
738+
}
739+
740+
test! {
741+
name: client_always_rejects_http09,
742+
743+
server:
744+
expected: "won't get here {addr}",
745+
reply: "won't reply",
746+
747+
client:
748+
request: {
749+
method: GET,
750+
url: "http://{addr}/",
751+
version: HTTP_09,
752+
},
753+
error: |err| err.to_string() == "request has unsupported HTTP version",
754+
}
755+
727756
mod dispatch_impl {
728757
use super::*;
729758
use std::io::{self, Read, Write};
@@ -1804,6 +1833,52 @@ mod conn {
18041833
rt.block_on(res.join(rx).map(|r| r.0)).unwrap();
18051834
}
18061835

1836+
#[test]
1837+
fn http1_conn_coerces_http2_request() {
1838+
let server = TcpListener::bind("127.0.0.1:0").unwrap();
1839+
let addr = server.local_addr().unwrap();
1840+
let mut rt = Runtime::new().unwrap();
1841+
1842+
let (tx1, rx1) = oneshot::channel();
1843+
1844+
thread::spawn(move || {
1845+
let mut sock = server.accept().unwrap().0;
1846+
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
1847+
sock.set_write_timeout(Some(Duration::from_secs(5))).unwrap();
1848+
let mut buf = [0; 4096];
1849+
let n = sock.read(&mut buf).expect("read 1");
1850+
1851+
// Not HTTP/2, nor panicked
1852+
let expected = "GET /a HTTP/1.1\r\n\r\n";
1853+
assert_eq!(s(&buf[..n]), expected);
1854+
1855+
sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").unwrap();
1856+
let _ = tx1.send(());
1857+
});
1858+
1859+
let tcp = rt.block_on(tcp_connect(&addr)).unwrap();
1860+
1861+
let (mut client, conn) = rt.block_on(conn::handshake(tcp)).unwrap();
1862+
1863+
rt.spawn(conn.map(|_| ()).map_err(|e| panic!("conn error: {}", e)));
1864+
1865+
let req = Request::builder()
1866+
.uri("/a")
1867+
.version(hyper::Version::HTTP_2)
1868+
.body(Default::default())
1869+
.unwrap();
1870+
1871+
let res = client.send_request(req).and_then(move |res| {
1872+
assert_eq!(res.status(), hyper::StatusCode::OK);
1873+
res.into_body().concat2()
1874+
});
1875+
let rx = rx1.expect("thread panicked");
1876+
1877+
let timeout = Delay::new(Duration::from_millis(200));
1878+
let rx = rx.and_then(move |_| timeout.expect("timeout"));
1879+
rt.block_on(res.join(rx).map(|r| r.0)).unwrap();
1880+
}
1881+
18071882
#[test]
18081883
fn pipeline() {
18091884
let server = TcpListener::bind("127.0.0.1:0").unwrap();

0 commit comments

Comments
 (0)