Documentation
use std::{net::SocketAddr, sync::Arc};

use hyper::{Request, body::Incoming, service::service_fn};
use hyper_util::rt::{TokioExecutor, TokioIo};
use rustls::ServerConfig;
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;

use crate::{CertLoader, LoadCert, Result, Route, proxy};

pub async fn srv<D: LoadCert>(
  addr: SocketAddr,
  route: Arc<Route>,
  cert_loader: Arc<CertLoader<D>>,
) -> Result<()> {
  let listener = TcpListener::bind(addr).await?;
  let mut tls_config = ServerConfig::builder()
    .with_no_client_auth()
    .with_cert_resolver(cert_loader);
  tls_config.alpn_protocols = vec![b"h2".to_vec()];
  let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));

  loop {
    let (stream, _remote_addr) = listener.accept().await?;
    let tls_acceptor = tls_acceptor.clone();
    let route = route.clone();
    tokio::spawn(async move {
      let stream = match tls_acceptor.accept(stream).await {
        Ok(stream) => stream,
        Err(err) => {
          eprintln!("tls accept error: {}", err);
          return;
        }
      };

      let io = TokioIo::new(stream);
      let service = service_fn(move |req: Request<Incoming>| {
        let route = route.clone();
        async move { Ok::<_, hyper::Error>(proxy(req, route).await) }
      });

      let conn_builder = hyper::server::conn::http2::Builder::new(TokioExecutor::new());
      if let Err(err) = conn_builder.serve_connection(io, service).await {
        eprintln!("h2 error: {}", err);
      }
    });
  }
}