Skip to content

Commit

Permalink
fix(tls): Make TLS clients support HTTP/2 (#12530)
Browse files Browse the repository at this point in the history
`fetch()` and client-side websocket used to support HTTP/2, but this
regressed in #11491. This patch reenables it by explicitly adding `h2`
and `http/1.1` to the list of ALPN protocols on the HTTP and websocket
clients.
  • Loading branch information
Andreu Botella committed Oct 25, 2021
1 parent 2e888cc commit e39dace
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 16 deletions.
36 changes: 36 additions & 0 deletions cli/tests/unit/fetch_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1324,3 +1324,39 @@ unitTest(
}), TypeError);
},
);

unitTest(
{ permissions: { net: true, read: true } },
async function fetchSupportsHttp1Only() {
const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem");
const client = Deno.createHttpClient({ caCerts: [caCert] });
const res = await fetch("https://localhost:5546/http_version", { client });
assert(res.ok);
assertEquals(await res.text(), "HTTP/1.1");
client.close();
},
);

unitTest(
{ permissions: { net: true, read: true } },
async function fetchSupportsHttp2() {
const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem");
const client = Deno.createHttpClient({ caCerts: [caCert] });
const res = await fetch("https://localhost:5547/http_version", { client });
assert(res.ok);
assertEquals(await res.text(), "HTTP/2.0");
client.close();
},
);

unitTest(
{ permissions: { net: true, read: true } },
async function fetchPrefersHttp2() {
const caCert = await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem");
const client = Deno.createHttpClient({ caCerts: [caCert] });
const res = await fetch("https://localhost:5545/http_version", { client });
assert(res.ok);
assertEquals(await res.text(), "HTTP/2.0");
client.close();
},
);
2 changes: 2 additions & 0 deletions ext/tls/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ pub fn create_http_client(
.expect("invalid client key or certificate");
}

tls_config.alpn_protocols = vec!["h2".into(), "http/1.1".into()];

let mut headers = HeaderMap::new();
headers.insert(USER_AGENT, user_agent.parse().unwrap());
let mut builder = Client::builder()
Expand Down
2 changes: 1 addition & 1 deletion test_util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ async-stream = "0.3.2"
atty = "0.2.14"
base64 = "0.13.0"
futures = "0.3.16"
hyper = { version = "0.14.12", features = ["server", "http1", "runtime"] }
hyper = { version = "0.14.12", features = ["server", "http1", "http2", "runtime"] }
lazy_static = "1.4.0"
os_pipe = "0.9.2"
regex = "1.5.4"
Expand Down
156 changes: 141 additions & 15 deletions test_util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const TLS_CLIENT_AUTH_PORT: u16 = 4552;
const BASIC_AUTH_REDIRECT_PORT: u16 = 4554;
const TLS_PORT: u16 = 4557;
const HTTPS_PORT: u16 = 5545;
const H1_ONLY_PORT: u16 = 5546;
const H2_ONLY_PORT: u16 = 5547;
const HTTPS_CLIENT_AUTH_PORT: u16 = 5552;
const WS_PORT: u16 = 4242;
const WSS_PORT: u16 = 4243;
Expand Down Expand Up @@ -290,10 +292,22 @@ async fn run_ws_close_server(addr: &SocketAddr) {
}
}

enum SupportedHttpVersions {
All,
Http1Only,
Http2Only,
}
impl Default for SupportedHttpVersions {
fn default() -> SupportedHttpVersions {
SupportedHttpVersions::All
}
}

async fn get_tls_config(
cert: &str,
key: &str,
ca: &str,
http_versions: SupportedHttpVersions,
) -> io::Result<Arc<rustls::ServerConfig>> {
let cert_path = testdata_path().join(cert);
let key_path = testdata_path().join(key);
Expand Down Expand Up @@ -336,6 +350,15 @@ async fn get_tls_config(
let allow_client_auth =
rustls::AllowAnyAnonymousOrAuthenticatedClient::new(root_cert_store);
let mut config = rustls::ServerConfig::new(allow_client_auth);
match http_versions {
SupportedHttpVersions::All => {
config.set_protocols(&["h2".into(), "http/1.1".into()]);
}
SupportedHttpVersions::Http1Only => {}
SupportedHttpVersions::Http2Only => {
config.set_protocols(&["h2".into()]);
}
}
config
.set_single_cert(cert, key)
.map_err(|e| {
Expand All @@ -354,9 +377,10 @@ async fn run_wss_server(addr: &SocketAddr) {
let key_file = "tls/localhost.key";
let ca_cert_file = "tls/RootCA.pem";

let tls_config = get_tls_config(cert_file, key_file, ca_cert_file)
.await
.unwrap();
let tls_config =
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
.await
.unwrap();
let tls_acceptor = TlsAcceptor::from(tls_config);
let listener = TcpListener::bind(addr).await.unwrap();
println!("ready: wss"); // Eye catcher for HttpServerCount
Expand Down Expand Up @@ -396,9 +420,10 @@ async fn run_tls_client_auth_server() {
let cert_file = "tls/localhost.crt";
let key_file = "tls/localhost.key";
let ca_cert_file = "tls/RootCA.pem";
let tls_config = get_tls_config(cert_file, key_file, ca_cert_file)
.await
.unwrap();
let tls_config =
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
.await
.unwrap();
let tls_acceptor = TlsAcceptor::from(tls_config);

// Listen on ALL addresses that localhost can resolves to.
Expand Down Expand Up @@ -459,9 +484,10 @@ async fn run_tls_server() {
let cert_file = "tls/localhost.crt";
let key_file = "tls/localhost.key";
let ca_cert_file = "tls/RootCA.pem";
let tls_config = get_tls_config(cert_file, key_file, ca_cert_file)
.await
.unwrap();
let tls_config =
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
.await
.unwrap();
let tls_acceptor = TlsAcceptor::from(tls_config);

// Listen on ALL addresses that localhost can resolves to.
Expand Down Expand Up @@ -819,6 +845,10 @@ async fn main_server(req: Request<Body>) -> hyper::Result<Response<Body>> {
Ok(Response::new(Body::empty()))
}
}
(_, "/http_version") => {
let version = format!("{:?}", req.version());
Ok(Response::new(version.into()))
}
_ => {
let mut file_path = testdata_path();
file_path.push(&req.uri().path()[1..]);
Expand Down Expand Up @@ -959,9 +989,99 @@ async fn wrap_main_https_server() {
let cert_file = "tls/localhost.crt";
let key_file = "tls/localhost.key";
let ca_cert_file = "tls/RootCA.pem";
let tls_config = get_tls_config(cert_file, key_file, ca_cert_file)
.await
.unwrap();
let tls_config =
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
.await
.unwrap();
loop {
let tcp = TcpListener::bind(&main_server_https_addr)
.await
.expect("Cannot bind TCP");
println!("ready: https"); // Eye catcher for HttpServerCount
let tls_acceptor = TlsAcceptor::from(tls_config.clone());
// Prepare a long-running future stream to accept and serve cients.
let incoming_tls_stream = async_stream::stream! {
loop {
let (socket, _) = tcp.accept().await?;
let stream = tls_acceptor.accept(socket);
yield stream.await;
}
}
.boxed();

let main_server_https_svc = make_service_fn(|_| async {
Ok::<_, Infallible>(service_fn(main_server))
});
let main_server_https = Server::builder(HyperAcceptor {
acceptor: incoming_tls_stream,
})
.serve(main_server_https_svc);

//continue to prevent TLS error stopping the server
if main_server_https.await.is_err() {
continue;
}
}
}

async fn wrap_https_h1_only_server() {
let main_server_https_addr = SocketAddr::from(([127, 0, 0, 1], H1_ONLY_PORT));
let cert_file = "tls/localhost.crt";
let key_file = "tls/localhost.key";
let ca_cert_file = "tls/RootCA.pem";
let tls_config = get_tls_config(
cert_file,
key_file,
ca_cert_file,
SupportedHttpVersions::Http1Only,
)
.await
.unwrap();
loop {
let tcp = TcpListener::bind(&main_server_https_addr)
.await
.expect("Cannot bind TCP");
println!("ready: https"); // Eye catcher for HttpServerCount
let tls_acceptor = TlsAcceptor::from(tls_config.clone());
// Prepare a long-running future stream to accept and serve cients.
let incoming_tls_stream = async_stream::stream! {
loop {
let (socket, _) = tcp.accept().await?;
let stream = tls_acceptor.accept(socket);
yield stream.await;
}
}
.boxed();

let main_server_https_svc = make_service_fn(|_| async {
Ok::<_, Infallible>(service_fn(main_server))
});
let main_server_https = Server::builder(HyperAcceptor {
acceptor: incoming_tls_stream,
})
.http1_only(true)
.serve(main_server_https_svc);

//continue to prevent TLS error stopping the server
if main_server_https.await.is_err() {
continue;
}
}
}

async fn wrap_https_h2_only_server() {
let main_server_https_addr = SocketAddr::from(([127, 0, 0, 1], H2_ONLY_PORT));
let cert_file = "tls/localhost.crt";
let key_file = "tls/localhost.key";
let ca_cert_file = "tls/RootCA.pem";
let tls_config = get_tls_config(
cert_file,
key_file,
ca_cert_file,
SupportedHttpVersions::Http2Only,
)
.await
.unwrap();
loop {
let tcp = TcpListener::bind(&main_server_https_addr)
.await
Expand All @@ -984,6 +1104,7 @@ async fn wrap_main_https_server() {
let main_server_https = Server::builder(HyperAcceptor {
acceptor: incoming_tls_stream,
})
.http2_only(true)
.serve(main_server_https_svc);

//continue to prevent TLS error stopping the server
Expand All @@ -999,9 +1120,10 @@ async fn wrap_client_auth_https_server() {
let cert_file = "tls/localhost.crt";
let key_file = "tls/localhost.key";
let ca_cert_file = "tls/RootCA.pem";
let tls_config = get_tls_config(cert_file, key_file, ca_cert_file)
.await
.unwrap();
let tls_config =
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
.await
.unwrap();
loop {
let tcp = TcpListener::bind(&main_server_https_addr)
.await
Expand Down Expand Up @@ -1078,6 +1200,8 @@ pub async fn run_all_servers() {
let client_auth_server_https_fut = wrap_client_auth_https_server();
let main_server_fut = wrap_main_server();
let main_server_https_fut = wrap_main_https_server();
let h1_only_server_fut = wrap_https_h1_only_server();
let h2_only_server_fut = wrap_https_h2_only_server();

let mut server_fut = async {
futures::join!(
Expand All @@ -1096,6 +1220,8 @@ pub async fn run_all_servers() {
main_server_fut,
main_server_https_fut,
client_auth_server_https_fut,
h1_only_server_fut,
h2_only_server_fut
)
}
.boxed();
Expand Down

0 comments on commit e39dace

Please sign in to comment.