| //! Simple HTTPS echo service based on hyper-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 core::task::{Context, Poll}; |
| use futures_util::{ |
| future::TryFutureExt, |
| stream::{Stream, StreamExt, TryStreamExt}, |
| }; |
| use hyper::service::{make_service_fn, service_fn}; |
| use hyper::{Body, Method, Request, Response, Server, StatusCode}; |
| use rustls::internal::pemfile; |
| use std::pin::Pin; |
| use std::vec::Vec; |
| use std::{env, fs, io, sync}; |
| use tokio::net::{TcpListener, TcpStream}; |
| use tokio_rustls::server::TlsStream; |
| 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<dyn std::error::Error + Send + Sync>> { |
| // First parameter is port number (optional, defaults to 1337) |
| let port = match env::args().nth(1) { |
| Some(ref p) => p.to_owned(), |
| None => "1337".to_owned(), |
| }; |
| let addr = format!("127.0.0.1:{}", port); |
| |
| // Build TLS configuration. |
| let tls_cfg = { |
| // Load public certificate. |
| let certs = load_certs("examples/sample.pem")?; |
| // Load private key. |
| let key = load_private_key("examples/sample.rsa")?; |
| // Do not use client certificate authentication. |
| let mut cfg = rustls::ServerConfig::new(rustls::NoClientAuth::new()); |
| // Select a certificate to use. |
| cfg.set_single_cert(certs, key) |
| .map_err(|e| error(format!("{}", e)))?; |
| // Configure ALPN to accept HTTP/2, HTTP/1.1 in that order. |
| cfg.set_protocols(&[b"h2".to_vec(), b"http/1.1".to_vec()]); |
| sync::Arc::new(cfg) |
| }; |
| |
| // Create a TCP listener via tokio. |
| let mut tcp = TcpListener::bind(&addr).await?; |
| let tls_acceptor = TlsAcceptor::from(tls_cfg); |
| // Prepare a long-running future stream to accept and serve cients. |
| let incoming_tls_stream = tcp |
| .incoming() |
| .map_err(|e| error(format!("Incoming failed: {:?}", e))) |
| .and_then(move |s| { |
| tls_acceptor.accept(s).map_err(|e| { |
| println!("[!] Voluntary server halt due to client-connection error..."); |
| // Errors could be handled here, instead of server aborting. |
| // Ok(None) |
| error(format!("TLS Error: {:?}", e)) |
| }) |
| }) |
| .boxed(); |
| |
| let service = make_service_fn(|_| async { Ok::<_, io::Error>(service_fn(echo)) }); |
| let server = Server::builder(HyperAcceptor { |
| acceptor: incoming_tls_stream, |
| }) |
| .serve(service); |
| |
| // Run the future, keep going until an error occurs. |
| println!("Starting to serve on https://{}.", addr); |
| server.await?; |
| Ok(()) |
| } |
| |
| struct HyperAcceptor<'a> { |
| acceptor: Pin<Box<dyn Stream<Item = Result<TlsStream<TcpStream>, io::Error>> + 'a>>, |
| } |
| |
| impl hyper::server::accept::Accept for HyperAcceptor<'_> { |
| type Conn = TlsStream<TcpStream>; |
| type Error = io::Error; |
| |
| fn poll_accept( |
| mut self: Pin<&mut Self>, |
| cx: &mut Context, |
| ) -> Poll<Option<Result<Self::Conn, Self::Error>>> { |
| Pin::new(&mut self.acceptor).poll_next(cx) |
| } |
| } |
| |
| // Custom echo service, handling two different routes and a |
| // catch-all 404 responder. |
| async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { |
| let mut response = Response::new(Body::empty()); |
| match (req.method(), req.uri().path()) { |
| // Help route. |
| (&Method::GET, "/") => { |
| *response.body_mut() = Body::from("Try POST /echo\n"); |
| } |
| // Echo service route. |
| (&Method::POST, "/echo") => { |
| *response.body_mut() = req.into_body(); |
| } |
| // Catch-all 404. |
| _ => { |
| *response.status_mut() = StatusCode::NOT_FOUND; |
| } |
| }; |
| Ok(response) |
| } |
| |
| // Load public certificate from file. |
| fn load_certs(filename: &str) -> io::Result<Vec<rustls::Certificate>> { |
| // 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. |
| pemfile::certs(&mut reader).map_err(|_| error("failed to load certificate".into())) |
| } |
| |
| // Load private key from file. |
| fn load_private_key(filename: &str) -> io::Result<rustls::PrivateKey> { |
| // 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. |
| let keys = pemfile::rsa_private_keys(&mut reader) |
| .map_err(|_| error("failed to load private key".into()))?; |
| if keys.len() != 1 { |
| return Err(error("expected a single private key".into())); |
| } |
| Ok(keys[0].clone()) |
| } |