use crate::app::config::ServerSettings;
use crate::dependency_container::{LazyDependencyContainer, default_container};
use crate::handler::Req;
use crate::http::convert::incoming_to_req::IncomingToInternal;
use crate::router::HttpSvc;
use crate::router::Router;
use hyper::Error as HyperError;
use hyper_util::{
rt::{TokioExecutor, TokioIo},
server::conn::auto::Builder as AutoBuilder,
service::TowerToHyperService,
};
use tokio::io::Result as IoResult;
use tokio::net::TcpListener;
use tokio::signal;
use tokio::time::Duration;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
use tracing;
pub mod config;
pub struct Application {
settings: ServerSettings,
svc: HttpSvc<Req>,
dependencies: std::sync::Arc<LazyDependencyContainer>,
}
impl Application {
pub fn new<S: Send + Sync + 'static>(settings: ServerSettings, router: Router<S>) -> Self {
Self::with_dependencies(settings, router, default_container())
}
pub fn with_dependencies<S: Send + Sync + 'static>(
settings: ServerSettings,
router: Router<S>,
dependencies: std::sync::Arc<LazyDependencyContainer>,
) -> Self {
Self {
settings,
svc: router.into_tower_service_with_container(dependencies.clone()),
dependencies,
}
}
pub fn new_<S: Send + Sync + 'static>(router: Router<S>) -> Self {
Self::new(ServerSettings::from_global_settings(), router)
}
pub fn dependencies(&self) -> &std::sync::Arc<LazyDependencyContainer> {
&self.dependencies
}
pub async fn run(self) -> IoResult<()> {
self.dependencies
.prewarm_all()
.await
.map_err(std::io::Error::other)?;
let addr = format!("{}:{}", self.settings.host, self.settings.port);
let listener = TcpListener::bind(addr).await?;
let executor = TokioExecutor::new();
let service_handle = self.svc;
let tracker = TaskTracker::new();
let shutdown_token = CancellationToken::new();
tracing::info!("listening on {}:{}", self.settings.host, self.settings.port);
loop {
tokio::select! {
_ = shutdown_signal() => {
tracing::info!("shutdown signal received, terminating...");
shutdown_token.cancel();
break;
}
r = listener.accept() => {
let (stream, _) = match r {
Ok(pair) => pair,
Err(err) =>{
tracing::error!("failed to accept connection: {}", err);
continue;
}
};
let io = TokioIo::new(stream);
let service_with_conversion = IncomingToInternal {
inner: service_handle.clone(),
};
let hyper_service = TowerToHyperService::new(service_with_conversion);
let executor = executor.clone();
let shutdown_token = shutdown_token.clone();
tracker.spawn(async move {
let builder = AutoBuilder::new(executor);
let conn = builder.serve_connection_with_upgrades(io, hyper_service);
tokio::pin!(conn);
let res = tokio::select! {
r = conn.as_mut() => r,
_ = shutdown_token.cancelled() => {
conn.as_mut().graceful_shutdown();
conn.await
}
};
if let Err(err) = res {
if let Some(hyper_err) = err.downcast_ref::<HyperError>()
&& hyper_err.is_incomplete_message() {
return;
}
tracing::warn!(error = ?err, "failed to serve connection");
}
});
}
}
}
tracker.close();
tracing::info!(
"waiting for existing {} connections to close...",
tracker.len()
);
let timeout = Duration::from_secs(30);
match tokio::time::timeout(timeout, tracker.wait()).await {
Ok(_) => {
tracing::info!("all connections closed, shutdown complete.");
}
Err(_) => {
tracing::warn!(
"timeout ({:?}) reached, forcing shutdown with {} active connections.",
timeout,
tracker.len()
);
}
}
Ok(())
}
}
#[cfg(feature = "test")]
impl Application {
pub fn test_client(&mut self) -> crate::test::test_client::TestClient {
crate::test::test_client::TestClient::new(self.svc.clone())
}
}
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 => {},
}
}