139 lines
4.6 KiB
Rust
139 lines
4.6 KiB
Rust
//! 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<dyn std::error::Error + Send + Sync>> {
|
|
// 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<Incoming>) -> Result<Response<Full<Bytes>>, 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<Vec<CertificateDer<'static>>> {
|
|
// 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<PrivateKeyDer<'static>> {
|
|
// 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())
|
|
}
|