aboutsummaryrefslogtreecommitdiff
path: root/src/http.rs
blob: 973e77f7c8c6c4d69fb0e6c0e16a6e75d2af994a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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<Output = ()>,
) -> 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<Body>| {
				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<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(""))?)
	}
}