//! Simple HTTPS echo service based on hyper_util and rustls //! //! First parameter is the mandatory port to use. //! Certificate and private key are hardcoded to sample files. //! hyper will automatically use HTTP/2 if a client starts talking HTTP/2, //! otherwise HTTP/1.1 will be used. use std::net::{Ipv4Addr, SocketAddr}; use std::sync::Arc; use std::{env, fs, io}; use http::{Method, Request, Response, StatusCode}; use http_body_util::{BodyExt, Full}; use hyper::body::{Bytes, Incoming}; use hyper::service::service_fn; use hyper_util::rt::{TokioExecutor, TokioIo}; use hyper_util::server::conn::auto::Builder; use pki_types::{CertificateDer, PrivateKeyDer}; use rustls::ServerConfig; use tokio::net::TcpListener; use tokio_rustls::TlsAcceptor; fn main() { // Serve an echo service over HTTPS, with proper error handling. if let Err(e) = run_server() { eprintln!("FAILED: {}", e); std::process::exit(1); } } fn error(err: String) -> io::Error { io::Error::new(io::ErrorKind::Other, err) } #[tokio::main] async fn run_server() -> Result<(), Box> { // Set a process wide default crypto provider. #[cfg(feature = "ring")] let _ = rustls::crypto::ring::default_provider().install_default(); #[cfg(feature = "aws-lc-rs")] let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); // First parameter is port number (optional, defaults to 1337) let port = match env::args().nth(1) { Some(ref p) => p.parse()?, None => 1337, }; let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port); // Load public certificate. let certs = load_certs("examples/sample.pem")?; // Load private key. let key = load_private_key("examples/sample.rsa")?; println!("Starting to serve on https://{}", addr); // Create a TCP listener via tokio. let incoming = TcpListener::bind(&addr).await?; // Build TLS configuration. let mut server_config = ServerConfig::builder() .with_no_client_auth() .with_single_cert(certs, key) .map_err(|e| error(e.to_string()))?; server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; let tls_acceptor = TlsAcceptor::from(Arc::new(server_config)); let service = service_fn(echo); loop { let (tcp_stream, _remote_addr) = incoming.accept().await?; let tls_acceptor = tls_acceptor.clone(); tokio::spawn(async move { let tls_stream = match tls_acceptor.accept(tcp_stream).await { Ok(tls_stream) => tls_stream, Err(err) => { eprintln!("failed to perform tls handshake: {err:#}"); return; } }; if let Err(err) = Builder::new(TokioExecutor::new()) .serve_connection(TokioIo::new(tls_stream), service) .await { eprintln!("failed to serve connection: {err:#}"); } }); } } // Custom echo service, handling two different routes and a // catch-all 404 responder. async fn echo(req: Request) -> Result>, hyper::Error> { let mut response = Response::new(Full::default()); match (req.method(), req.uri().path()) { // Help route. (&Method::GET, "/") => { *response.body_mut() = Full::from("Try POST /echo\n"); } // Echo service route. (&Method::POST, "/echo") => { *response.body_mut() = Full::from( req.into_body() .collect() .await? .to_bytes(), ); } // Catch-all 404. _ => { *response.status_mut() = StatusCode::NOT_FOUND; } }; Ok(response) } // Load public certificate from file. fn load_certs(filename: &str) -> io::Result>> { // Open certificate file. let certfile = fs::File::open(filename) .map_err(|e| error(format!("failed to open {}: {}", filename, e)))?; let mut reader = io::BufReader::new(certfile); // Load and return certificate. rustls_pemfile::certs(&mut reader).collect() } // Load private key from file. fn load_private_key(filename: &str) -> io::Result> { // Open keyfile. let keyfile = fs::File::open(filename) .map_err(|e| error(format!("failed to open {}: {}", filename, e)))?; let mut reader = io::BufReader::new(keyfile); // Load and return a single private key. rustls_pemfile::private_key(&mut reader).map(|key| key.unwrap()) }