173 lines
5.5 KiB
Rust
173 lines
5.5 KiB
Rust
mod admin_examples;
|
|
mod db;
|
|
mod embed;
|
|
mod howto;
|
|
mod state;
|
|
|
|
use crate::state::AppState;
|
|
use admin_examples::file_repository;
|
|
use admin_examples::static_repository;
|
|
use axum::{
|
|
body::Bytes,
|
|
extract::MatchedPath,
|
|
http::{HeaderMap, Request},
|
|
response::Response,
|
|
};
|
|
use axum::{
|
|
extract::State, handler::HandlerWithoutStateExt, response::IntoResponse, routing::get, Router,
|
|
};
|
|
use dotenvy::dotenv;
|
|
use log::info;
|
|
use rear::admin;
|
|
use rear::service::{handlers, templates};
|
|
use std::env;
|
|
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use tokio::net::TcpListener;
|
|
use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer};
|
|
use tracing::{info_span, Span};
|
|
|
|
async fn home(templates: State<templates::Templates>) -> impl IntoResponse {
|
|
templates.render_html("index.html", ())
|
|
}
|
|
|
|
async fn hello_world(templates: State<templates::Templates>) -> impl IntoResponse {
|
|
templates.render_html("hello_world.html", ())
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
// Load environment
|
|
dotenv().ok();
|
|
env_logger::init();
|
|
info!("Miniweb starting...");
|
|
|
|
// Database Configuration.
|
|
let db_connection = db::establish_connection().await;
|
|
|
|
// Prepare Application State Members
|
|
let tmpl = templates::Templates::initialize().expect("Template Engine could not be loaded.");
|
|
let mut admin = admin::state::AdminRegistry::new("admin");
|
|
|
|
// Register Admin Apps
|
|
static_repository::register(&mut admin);
|
|
//user_repository::register(&mut admin, db_connection);
|
|
file_repository::register(&mut admin, "static/admin");
|
|
|
|
// Create global Application State.
|
|
let state: AppState = AppState {
|
|
templates: tmpl,
|
|
admin: Arc::new(admin),
|
|
};
|
|
|
|
// Application Route
|
|
let app = Router::new()
|
|
.route("/", get(home))
|
|
.route("/hello", get(hello_world))
|
|
//.merge(admin_router)
|
|
.nest("/admin", admin::routes())
|
|
.nest("/howto", howto::routes())
|
|
.route_service("/static/*file", embed::static_handler.into_service())
|
|
.fallback(handlers::not_found_handler)
|
|
.with_state(state)
|
|
.layer(
|
|
TraceLayer::new_for_http()
|
|
.make_span_with(|request: &Request<_>| {
|
|
// Log the matched route's path (with placeholders not filled in).
|
|
// Use request.uri() or OriginalUri if you want the real path.
|
|
let matched_path = request
|
|
.extensions()
|
|
.get::<MatchedPath>()
|
|
.map(MatchedPath::as_str);
|
|
info_span!(
|
|
"http_request",
|
|
method = ?request.method(),
|
|
uri = ?request.uri(),
|
|
matched_path,
|
|
some_other_field = tracing::field::Empty,
|
|
)
|
|
})
|
|
.on_request(|_request: &Request<_>, _span: &Span| {
|
|
// You can use `_span.record("some_other_field", value)` in one of these
|
|
// closures to attach a value to the initially empty field in the info_span
|
|
// created above.
|
|
info!("Request: {:?}", _request);
|
|
})
|
|
.on_response(|_response: &Response, _latency: Duration, _span: &Span| {
|
|
// ...
|
|
info!("Response: {:?}", _response);
|
|
})
|
|
.on_body_chunk(|_chunk: &Bytes, _latency: Duration, _span: &Span| {
|
|
// ...
|
|
})
|
|
.on_eos(
|
|
|_trailers: Option<&HeaderMap>, _stream_duration: Duration, _span: &Span| {
|
|
// ...
|
|
},
|
|
)
|
|
.on_failure(
|
|
|_error: ServerErrorsFailureClass, _latency: Duration, _span: &Span| {
|
|
// ...
|
|
},
|
|
),
|
|
);
|
|
|
|
// Run Server
|
|
let app_host: std::net::IpAddr = env::var("APP_HOST")
|
|
.unwrap_or("0.0.0.0".to_string())
|
|
.parse()
|
|
.expect("IP Address expected in APP_HOST");
|
|
let app_port: u16 = env::var("APP_PORT")
|
|
.unwrap_or("3000".to_string())
|
|
.parse()
|
|
.expect("Port expected in APP_PORT");
|
|
|
|
// the listen_addr is the address we bind to. This might be multiple domains, like 0.0.0.0
|
|
let listen_addr = SocketAddr::from((app_host, app_port));
|
|
// the server addr is a concrete address the user can connect to.
|
|
let server_addr = if app_host.is_unspecified() {
|
|
SocketAddr::from(([127, 0, 0, 1], app_port))
|
|
} else {
|
|
listen_addr.clone()
|
|
};
|
|
|
|
info!("listening on {}", listen_addr);
|
|
info!("admin on: http://{}/admin", server_addr);
|
|
|
|
let listener = TcpListener::bind(&listen_addr)
|
|
.await
|
|
.expect("Could not bind TCP Listener.");
|
|
|
|
axum::serve(listener, app.into_make_service())
|
|
.with_graceful_shutdown(shutdown_signal())
|
|
.await
|
|
.expect("Could not start serving Axum");
|
|
}
|
|
|
|
async fn shutdown_signal() {
|
|
let ctrl_c = async {
|
|
tokio::signal::ctrl_c()
|
|
.await
|
|
.expect("failed to install Ctrl+C handler");
|
|
};
|
|
|
|
#[cfg(unix)]
|
|
let terminate = async {
|
|
tokio::signal::unix::signal(tokio::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 => {},
|
|
}
|
|
|
|
info!("shutting down...");
|
|
}
|