use std::fs;
use std::time::Duration;
use mini_static::Server;
use tempfile::TempDir;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
const H2_FRAME_TYPE_SETTINGS: u8 = 0x04;
const READ_DEADLINE: Duration = Duration::from_secs(5);
async fn serve_fixture() -> (u16, TempDir, mini_static::ServerHandle) {
let root = TempDir::new().unwrap();
fs::write(root.path().join("index.html"), b"<html>hello</html>").unwrap();
let server = Server::new(root.path()).unwrap();
let (port, handle) = server.run_ephemeral().await.unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;
(port, root, handle)
}
#[tokio::test]
async fn an_http2_preface_never_negotiates_an_http2_session() {
let (port, _root, _handle) = serve_fixture().await;
let mut stream = TcpStream::connect(format!("127.0.0.1:{port}"))
.await
.unwrap();
stream.write_all(H2_PREFACE).await.unwrap();
let mut response = Vec::new();
let read = tokio::time::timeout(READ_DEADLINE, stream.read_to_end(&mut response)).await;
assert!(
read.is_ok(),
"connection stayed open past {READ_DEADLINE:?} after the h2 preface — the server \
accepted it and is waiting for frames; bytes so far: {response:?}"
);
read.unwrap().unwrap();
assert!(
response.get(3) != Some(&H2_FRAME_TYPE_SETTINGS),
"server answered the h2 preface with a SETTINGS frame: {response:?}"
);
assert!(
response.is_empty() || response.starts_with(b"HTTP/1."),
"expected an HTTP/1.x reply or a closed connection, got: {}",
String::from_utf8_lossy(&response)
);
}
#[tokio::test]
async fn an_http1_request_on_the_same_listener_still_serves() {
let (port, _root, _handle) = serve_fixture().await;
let mut stream = TcpStream::connect(format!("127.0.0.1:{port}"))
.await
.unwrap();
stream
.write_all(b"GET /index.html HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let mut response = Vec::new();
tokio::time::timeout(READ_DEADLINE, stream.read_to_end(&mut response))
.await
.expect("server did not close the connection despite `Connection: close`")
.unwrap();
let response = String::from_utf8_lossy(&response);
assert!(
response.starts_with("HTTP/1.1 200"),
"expected a served HTTP/1.1 response, got: {response}"
);
}