turntable 0.0.1

Turntable is unpkg copy
Documentation
#[macro_use]
pub(crate) mod macros;

mod errors;
mod handlers;
mod middlewares;
mod models;
mod utils;

use std::time::Duration;

use middlewares::{
  FindEntry, ValidateFilename, ValidatePackageName, ValidatePackagePathname, ValidatePackageVersion,
};
use poem::{
  endpoint::{BoxEndpoint, StaticFileEndpoint},
  get,
  http::header,
  listener::TcpListener,
  middleware::{Compression, Cors, SetHeader, Tracing},
  web::CompressionLevel,
  EndpointExt, Route,
};
use tokio::signal;

#[poem::async_trait]
pub trait ListenPort {
  async fn listen(self, port: u16) -> anyhow::Result<()>;
}

pub struct Server {
  ep: BoxEndpoint<'static>,
}

impl Server {
  pub fn new() -> Self {
    Self::default()
  }
}

impl Default for Server {
  fn default() -> Self {
    let ep = get(handlers::handle_pkg_pathname)
      .with(FindEntry)
      .with(ValidateFilename)
      .with(ValidatePackageVersion)
      .with(ValidatePackageName)
      .with(ValidatePackagePathname);

    let ep = Route::new()
      .at("/*pkg", ep)
      .at(
        "/favicon.ico",
        StaticFileEndpoint::new("./assets/favicon.ico")
          .with(SetHeader::new().overriding(header::CACHE_CONTROL, "public, max-age=31536000")),
      )
      .at(
        "/robots.txt",
        StaticFileEndpoint::new("./assets/robots.txt")
          .with(SetHeader::new().overriding(header::CACHE_CONTROL, "public, max-age=31536000")),
      )
      .with(Tracing)
      .with(Compression::new().with_quality(CompressionLevel::Fastest))
      .with(Cors::new());

    Self { ep: ep.boxed() }
  }
}

#[poem::async_trait]
impl ListenPort for Server {
  async fn listen(self, port: u16) -> anyhow::Result<()> {
    let addr = format!("0.0.0.0:{port}");
    let server = poem::Server::new(TcpListener::bind(&addr));
    server
      .run_with_graceful_shutdown(self.ep, shutdown_signal(), Some(Duration::from_secs(5)))
      .await?;
    Ok(())
  }
}

async fn shutdown_signal() {
  let ctrl_c = async {
    signal::ctrl_c()
      .await
      .expect("failed to install Ctrl+C handler")
  };

  #[cfg(unix)]
  let terminate = async {
    signal::unix::signal(signal::unix::SignalKind::terminate())
      .expect("failed to install signal handler")
      .recv()
      .await;
  };

  #[cfg(not(unix))]
  let terminate = std::future::pending::<()>();

  tokio::select! {
      _ = ctrl_c => {},
      _ = terminate => {},
  }

  tracing::info!("signal received, starting graceful shutdown");
}