pub mod commons;
pub mod config;
pub mod error;
pub mod errors;
pub mod mandelbrot;
pub mod middlewares;
pub mod simples;
pub mod wrapper;
pub mod request;
pub mod prelude;
pub mod templates;
pub mod flash;
use crate::prelude::*;
use lazy_static::lazy_static;
use actix_web::{dev, App, HttpServer, Responder, web::ServiceConfig };
use actix_session::{SessionMiddleware, storage::CookieSessionStore};
use clap::Parser;
use std::fmt;
use std::time::Duration;
use chrono::offset::Offset;
use chrono::Local;
use actix_extensible_rate_limit::{
backend::memory::InMemoryBackend, backend::SimpleInput, backend::SimpleInputFunctionBuilder,
backend::SimpleOutput, RateLimiter,
};
use crate::middlewares::access_filter;
#[derive(Parser, Debug)]
#[clap(
author = "keesh.zhang",
version,
about = "A powerful, pragmatic, and extremely fast web framework for Rust"
)]
pub struct Cli {
#[arg(long, short)]
profile: Option<String>,
#[arg(long, short)]
env_folder: Option<String>,
}
pub async fn favicon(_req: HttpRequest) -> std::io::Result<actix_files::NamedFile> {
actix_files::NamedFile::open(format!("{}/favicon.ico", std::env::var("static_folder").unwrap()))
}
pub async fn favicon_svg() -> impl Responder {
actix_files::NamedFile::open_async(format!("{}/favicon.svg", std::env::var("static_folder").unwrap()))
.await
.unwrap()
}
pub fn static_handler(config: &mut ServiceConfig) {
let static_path = std::env::var("static_folder").unwrap();
let fs = actix_files::Files::new("/static", static_path);
config.service(fs);
}
pub async fn info(request: HttpRequest) -> impl Responder {
request.text(200, "Hello, server is alive and kicking...")
}
pub async fn not_found() -> HttpResponse {
HttpResponse::build(actix_web::http::StatusCode::NOT_FOUND)
.content_type("text/html;charset=utf-8")
.body("<h1>404 - Page not found</h1>")
}
#[rustfmt::skip]
pub async fn run<F>(server_name: &'static str, cfg: F)
where
F: Fn(&mut actix_web::web::ServiceConfig) + Send + Clone + 'static,
{
let args = Cli::parse();
config::load_env(&args.env_folder.expect("未指定env_folder"), vec![&(".env.".to_owned() + &(args.profile.expect("未指定profile"))), ".env"]);
let server_config = config::load_config::<config::ServerConfig>();
log4rs::init_file(format!("{}/log4rs.yaml", server_config.resousrce_folder), Default::default()).expect("初始化日志配置异常");
let temp_foder = format!("{}", server_config.temp_folder);
std::env::set_var("RUST_LOG", "info");
std::env::set_var("RUST_MIN_STACK", server_config.min_stack_size.to_string());
let template_store = crate::templates::load();
let templates = template_store.templates.clone();
let signing_key = actix_web::cookie::Key::generate();
let new_app = move || {
App::new()
.wrap(wrapper::cors())
.wrap(wrapper::default_headers())
.wrap(wrapper::normalize_path_config())
.wrap(access_filter::Logger::get())
.wrap(
SessionMiddleware::builder(CookieSessionStore::default(), signing_key.clone())
.cookie_path("/".to_string())
.build(),
)
.app_data(templates.clone())
.app_data(wrapper::validate_json_body()) .app_data(wrapper::validate_query_string()) .app_data(wrapper::validate_path_variable()) .app_data(wrapper::validate_form_data()) .app_data(wrapper::multipart_form_config(52_428_800)) .app_data(wrapper::multipart_temp_folder_config(&commons::temp_upload_folder(server_name))) .app_data(wrapper::multipart_temp_folder_config(&temp_foder.clone())) .configure(|wc| config(wc, server_config.enable_simples))
.configure(|wc| cfg(wc))
.configure(static_handler)
.default_service(actix_web::web::route().to(not_found))
};
let server = HttpServer::new(new_app)
.backlog(server_config.backlog_size)
.workers(server_config.worker_count)
.keep_alive(std::time::Duration::from_secs(server_config.keepalive_time));
let bind_result = if server_config.protocal == "http" {
server.bind(format!("0.0.0.0:{}", server_config.server_port))
} else {
server.bind_openssl(format!("0.0.0.0:{}", server_config.server_port), tls_builder())
};
match bind_result {
Ok(svr) => {
log::info!("Congratulations! Your server '{}' will be running at {}://0.0.0.0:{}", server_name, server_config.protocal, server_config.server_port);
log::info!("Your temporary folder for uploader is: {}", &server_config.temp_folder);
log::info!("Current time offset is: {:+02}:00", Local::now().offset().fix().local_minus_utc() / 3600);
svr.run().await.expect("Failed to run server")
}
_ => log::info!("🔥 Couldn't start the server at port {}", server_config.server_port),
}
}
pub fn tls_builder() -> openssl::ssl::SslAcceptorBuilder {
let mut builder = openssl::ssl::SslAcceptor::mozilla_intermediate(openssl::ssl::SslMethod::tls()).expect("初始化ssl异常");
builder.set_private_key_file("key.pem", openssl::ssl::SslFiletype::PEM).expect("初始化ssl异常: key.pem");
builder.set_certificate_chain_file("cert.pem").expect("初始化ssl异常: cert.pem");
return builder;
}
pub fn access_limiter() -> RateLimiter<
InMemoryBackend,
SimpleOutput,
impl Fn(&dev::ServiceRequest) -> std::future::Ready<std::result::Result<SimpleInput, actix_web::Error>>,
> {
return RateLimiter::builder(
InMemoryBackend::builder().build(),
SimpleInputFunctionBuilder::new(Duration::from_secs(1), 1)
.real_ip_key()
.build(),
)
.add_headers()
.build();
}
pub type Result<T> = std::result::Result<T, crate::error::Error>;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct R<T> {
pub code: u16,
pub message: String,
pub data: Option<T>,
}
impl<T: serde::Serialize> R<T> {
pub fn ok(data: T) -> Self {
R::<T> {
code: 200,
message: "Ok".to_string(),
data: Some(data),
}
}
pub fn success(data: T, message: String) -> Self {
R::<T> {
code: 200,
message: message,
data: Some(data),
}
}
pub fn fail(code: u16) -> Self {
R::<T> {
code: code,
message: "failed".to_string(),
data: None,
}
}
pub fn failed(code: u16, message: String) -> Self {
R::<T> {
code: code,
message: message,
data: None,
}
}
}
impl<T> fmt::Display for R<T>
where
T: fmt::Display + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
lazy_static! {
pub static ref RS_CACHE: moka::sync::Cache<String, String> = moka::sync::Cache::builder()
.time_to_live(Duration::from_secs(30 * 60))
.time_to_idle(Duration::from_secs( 1 * 60))
.max_capacity(32 * 1024 * 1024)
.build();
}
pub fn reg_router<F, Args>(
cfg: &mut actix_web::web::ServiceConfig,
method: &str,
path: &str,
handler: F,
) where
F: actix_web::Handler<Args>,
Args: actix_web::FromRequest + 'static,
F::Output: actix_web::Responder + 'static,
{
let cache_key = format!("{}::{}", method.to_uppercase().trim(), path.to_string());
if RS_CACHE.get(&cache_key).is_none() {
log::info!(
"Reg-a-router: method={}, path={}, handler={}",
method.to_uppercase().trim(),
path,
commons::get_function_name(&handler)
);
RS_CACHE.insert(cache_key, path.to_string());
}
cfg.route(
path,
actix_web::Route::new()
.method(
actix_web::http::Method::from_bytes(method.to_uppercase().trim().as_bytes())
.unwrap(),
)
.to(handler),
);
}
#[rustfmt::skip]
pub fn config(cfg: &mut actix_web::web::ServiceConfig, enable_simples: bool) {
reg_router(cfg, "GET", "/favicon.ico", favicon);
reg_router(cfg, "GET", "/favicon.svg", favicon_svg);
reg_router(cfg, "GET", "/info", info);
reg_router(cfg, "GET", "/", simples::default_page);
reg_router(cfg, "GET", "/graphiql", simples::graphiql);
if enable_simples {
reg_router(cfg, "GET", "/simple1/{name}", simples::simple1);
reg_router(cfg, "POST", "/simple2/{project_id}", simples::simple2);
reg_router(cfg, "POST", "/simple3/{project_id}", simples::simple3);
reg_router(cfg, "POST", "/simple4", simples::simple4);
reg_router(cfg, "POST", "/simple5/{project_id}", simples::simple5);
reg_router(cfg, "get", "/simple6", simples::simple6);
reg_router(cfg, "get", "/simple/panic/{project_id}", simples::panic1);
reg_router(cfg, "POST", "/simple/upload_file", simples::save_files);
reg_router(cfg, "GET", "/simple/mandelbrot", simples::mandelbrot);
}
}