tricot/src/http.rs

76 lines
2 KiB
Rust

use std::net::SocketAddr;
use std::sync::Arc;
use anyhow::Result;
use log::*;
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,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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<Body>| {
let consul = consul.clone();
handle(req, consul)
}))
}
});
info!("Listening on http://{}", bind_addr);
let server = Server::bind(&bind_addr).serve(make_svc);
server.await?;
Ok(())
}
async fn handle(req: Request<Body>, consul: Arc<Consul>) -> Result<Response<Body>> {
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(""))?)
}
}