#![allow(
missing_docs,
clippy::unwrap_used,
clippy::expect_used,
clippy::uninlined_format_args,
clippy::items_after_statements,
clippy::use_self,
clippy::semicolon_if_nothing_returned,
clippy::similar_names
)]
use bytes::Bytes;
use std::time::Duration;
use tachyon_web::{Router, Server, get, post};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
async fn free_loopback_addr() -> std::net::SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("bind ephemeral port");
let addr = listener.local_addr().expect("local addr");
drop(listener);
addr
}
#[test]
fn test_server_debug_and_config() {
let router = Router::new();
let server = Server::new(router).max_body_size(1024);
assert_eq!(server.max_body_size, 1024);
let dbg = format!("{:?}", server);
assert!(dbg.contains("Server"));
}
#[tokio::test]
#[cfg(feature = "tls")]
async fn test_start_all_invalid_address() {
let router = Router::new();
let server = Server::new(router);
let res = server
.start_all(
"999.999.999.999:9999",
None,
"cert".to_string(),
"key".to_string(),
)
.await;
assert!(res.is_err());
}
#[tokio::test(start_paused = true)]
async fn test_server_request_timeout() {
let router = Router::new().route("/", post(|_body: Bytes| async { "ok" }));
let server = Server::new(router);
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server_handle = tokio::spawn(async move {
let _ = server.serve_http(listener).await;
});
let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
let req_headers = "POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 10\r\n\r\n";
stream.write_all(req_headers.as_bytes()).await.unwrap();
stream.flush().await.unwrap();
tokio::time::advance(Duration::from_secs(32)).await;
let mut resp_bytes = vec![0; 512];
let n = stream.read(&mut resp_bytes).await.unwrap();
let resp_str = String::from_utf8_lossy(&resp_bytes[..n]);
assert!(resp_str.contains("408"), "response was: {resp_str}");
server_handle.abort();
}
#[tokio::test]
async fn test_server_ignores_unread_body_for_bodyless_handler() {
let router = Router::new().route("/", post(|| async { "ok" }));
let server = Server::new(router);
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server_handle = tokio::spawn(async move {
let _ = server.serve_http(listener).await;
});
let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
let req_headers = "POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 10\r\n\r\n";
stream.write_all(req_headers.as_bytes()).await.unwrap();
stream.flush().await.unwrap();
let resp_fut = async {
let mut resp_bytes = vec![0; 512];
let n = stream.read(&mut resp_bytes).await.unwrap();
String::from_utf8_lossy(&resp_bytes[..n]).into_owned()
};
let resp_str = tokio::time::timeout(Duration::from_secs(5), resp_fut)
.await
.expect("handler should respond promptly without waiting on the unread body");
assert!(resp_str.contains("200"), "response was: {resp_str}");
server_handle.abort();
}
#[tokio::test]
async fn test_start_http_addr() {
let addr = free_loopback_addr().await;
let router = Router::new().route("/", get(|| async { "ok-addr" }));
let server = Server::new(router);
let handle = tokio::spawn(async move {
let _ = server.start_http_addr(addr).await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
let res = reqwest::get(format!("http://{addr}/"))
.await
.expect("http request");
assert_eq!(res.status(), 200);
assert_eq!(res.text().await.expect("body"), "ok-addr");
handle.abort();
}
#[tokio::test]
async fn test_start_http_with_address_string() {
let addr = free_loopback_addr().await;
let router = Router::new().route("/", get(|| async { "ok-str" }));
let server = Server::new(router);
let addr_str = addr.to_string();
let handle = tokio::spawn(async move {
let _ = server.start_http(&addr_str).await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
let res = reqwest::get(format!("http://{addr}/"))
.await
.expect("http request");
assert_eq!(res.status(), 200);
assert_eq!(res.text().await.expect("body"), "ok-str");
handle.abort();
}
#[tokio::test]
async fn test_start_http_rejects_unparseable_address() {
let server = Server::new(Router::new());
let err = server
.start_http("this-is-not-a-socket-addr")
.await
.expect_err("unparseable bind address must fail fast, without binding anything");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
#[cfg(feature = "tls")]
#[tokio::test]
async fn test_start_https_with_config_addr() {
use tachyon_web::tls::generate_self_signed_cert;
let cert = generate_self_signed_cert(vec!["localhost".to_string()]).unwrap();
let mut server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert.cert_der], cert.key_der)
.unwrap();
server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let addr = free_loopback_addr().await;
let router = Router::new().route("/", get(|| async { "ok-https-addr" }));
let server = Server::new(router);
let handle = tokio::spawn(async move {
let _ = server
.start_https_with_config_addr(addr, server_config)
.await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let res = client
.get(format!("https://{addr}/"))
.send()
.await
.expect("https request");
assert_eq!(res.status(), 200);
assert_eq!(res.text().await.unwrap(), "ok-https-addr");
handle.abort();
}
#[cfg(feature = "tls")]
#[tokio::test]
async fn test_start_https_with_config_string() {
use tachyon_web::tls::generate_self_signed_cert;
let cert = generate_self_signed_cert(vec!["localhost".to_string()]).unwrap();
let mut server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert.cert_der], cert.key_der)
.unwrap();
server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
let addr = free_loopback_addr().await;
let addr_str = addr.to_string();
let router = Router::new().route("/", get(|| async { "ok-https-str" }));
let server = Server::new(router);
let handle = tokio::spawn(async move {
let _ = server
.start_https_with_config(&addr_str, server_config)
.await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let res = client
.get(format!("https://{addr}/"))
.send()
.await
.expect("https request");
assert_eq!(res.status(), 200);
assert_eq!(res.text().await.unwrap(), "ok-https-str");
handle.abort();
}
#[cfg(feature = "tls")]
#[tokio::test]
async fn test_start_https_with_config_rejects_unparseable_address() {
use tachyon_web::tls::generate_self_signed_cert;
let cert = generate_self_signed_cert(vec!["localhost".to_string()]).unwrap();
let server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert.cert_der], cert.key_der)
.unwrap();
let server = Server::new(Router::new());
let err = server
.start_https_with_config("this-is-not-a-socket-addr", server_config)
.await
.expect_err("unparseable bind address must fail fast, without binding anything");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
#[cfg(all(feature = "tls", feature = "http3"))]
#[tokio::test]
async fn test_start_https_and_h3_with_config() {
use tachyon_web::tls::generate_self_signed_cert;
let cert = generate_self_signed_cert(vec!["localhost".to_string()]).unwrap();
let server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert.cert_der], cert.key_der)
.unwrap();
let addr = free_loopback_addr().await;
let addr_str = addr.to_string();
let router = Router::new().route("/", get(|| async { "ok-h3-config" }));
let server = Server::new(router);
let handle = tokio::spawn(async move {
let _ = server
.start_https_and_h3_with_config(&addr_str, server_config)
.await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let res = client
.get(format!("https://{addr}/"))
.version(reqwest::Version::HTTP_2)
.send()
.await
.expect("https/2 request over the shared tls_addr");
assert_eq!(res.status(), 200);
assert_eq!(res.text().await.unwrap(), "ok-h3-config");
handle.abort();
}
#[tokio::test]
async fn test_free_serve_function() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let router = Router::new().route("/", get(|| async { "ok-serve-fn" }));
let handle = tokio::spawn(async move {
let _ = tachyon_web::serve(listener, router).await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
let res = reqwest::get(format!("http://{addr}/"))
.await
.expect("http request");
assert_eq!(res.status(), 200);
assert_eq!(res.text().await.expect("body"), "ok-serve-fn");
handle.abort();
}
#[cfg(feature = "tls")]
#[tokio::test]
async fn test_bind_rustls_https_server_serve() {
use tachyon_web::RustlsConfig;
use tachyon_web::tls::generate_self_signed_cert;
let cert = generate_self_signed_cert(vec!["localhost".to_string()]).unwrap();
let config = RustlsConfig::from_pem(cert.cert_pem.into_bytes(), cert.key_pem.into_bytes())
.await
.expect("build RustlsConfig from a valid self-signed cert");
let addr = free_loopback_addr().await;
let router = Router::new().route("/", get(|| async { "ok-bind-rustls" }));
let handle = tokio::spawn(async move {
let _ = tachyon_web::bind_rustls(addr, config).serve(router).await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let res = client
.get(format!("https://{addr}/"))
.send()
.await
.expect("https request");
assert_eq!(res.status(), 200);
assert_eq!(res.text().await.unwrap(), "ok-bind-rustls");
handle.abort();
}
#[cfg(all(feature = "tls", feature = "http3"))]
#[tokio::test]
async fn test_bind_rustls_https_server_serve_with_http3_enabled() {
use tachyon_web::RustlsConfig;
use tachyon_web::tls::generate_self_signed_cert;
let cert = generate_self_signed_cert(vec!["localhost".to_string()]).unwrap();
let config = RustlsConfig::from_pem(cert.cert_pem.into_bytes(), cert.key_pem.into_bytes())
.await
.expect("build RustlsConfig from a valid self-signed cert");
let addr = free_loopback_addr().await;
let router = Router::new().route("/", get(|| async { "ok-bind-rustls-h3" }));
let handle = tokio::spawn(async move {
let _ = tachyon_web::bind_rustls(addr, config)
.serve_http3(true)
.serve(router)
.await;
});
tokio::time::sleep(Duration::from_millis(300)).await;
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let res = client
.get(format!("https://{addr}/"))
.send()
.await
.expect("https request over the shared addr with http3 also enabled");
assert_eq!(res.status(), 200);
assert_eq!(res.text().await.unwrap(), "ok-bind-rustls-h3");
handle.abort();
}