use std::net::SocketAddr; use std::sync::Arc; use anyhow::Result; use log::*; use futures::future::Future; use http::uri::Authority; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Request, Response, Server, StatusCode, Uri}; use crate::consul::Consul; const CHALLENGE_PREFIX: &str = "/.well-known/acme-challenge/"; pub async fn serve_http( bind_addr: SocketAddr, consul: Consul, shutdown_signal: impl Future, ) -> Result<()> { let consul = Arc::new(consul); // For every connection, we must make a `Service` to handle all // incoming HTTP requests on said connection. let make_svc = make_service_fn(|_conn| { let consul = consul.clone(); // This is the `Service` that will handle the connection. // `service_fn` is a helper to convert a function that // returns a Response into a `Service`. async move { Ok::<_, anyhow::Error>(service_fn(move |req: Request| { let consul = consul.clone(); handle(req, consul) })) } }); info!("Listening on http://{}", bind_addr); let server = Server::bind(&bind_addr) .serve(make_svc) .with_graceful_shutdown(shutdown_signal); server.await?; Ok(()) } async fn handle(req: Request, consul: Arc) -> Result> { let path = req.uri().path(); info!("HTTP request {}", path); if let Some(token) = path.strip_prefix(CHALLENGE_PREFIX) { let response = consul.kv_get(&format!("challenge/{}", token)).await?; match response { Some(r) => Ok(Response::new(Body::from(r))), None => Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from(""))?), } } else { // Redirect to HTTPS let uri2 = req.uri().clone(); let mut parts = uri2.into_parts(); let host = req .headers() .get("Host") .map(|h| h.to_str()) .ok_or_else(|| anyhow!("Missing host header"))?? .to_string(); parts.authority = Some(Authority::from_maybe_shared(host)?); parts.scheme = Some("https".parse().unwrap()); let uri2 = Uri::from_parts(parts)?; Ok(Response::builder() .status(StatusCode::MOVED_PERMANENTLY) .header("Location", uri2.to_string()) .body(Body::from(""))?) } }