zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use actix_request_identifier::{IdReuse, RequestIdentifier};
use actix_web::middleware::from_fn;
use actix_web::{middleware, web, App, HttpServer};

use crate::core::auth0::cookie_session::{self, CookieSessionSettings};
use crate::core::middlewares;
use crate::core::middlewares::force_keep_alive::force_keep_alive;
use crate::core::url_dispatch;
use crate::core::websocat::echo::server::EchoServer;
use crate::sitepages;
use crate::websocket::devstreams;

pub async fn run<F>(
    ac: crate::server::AppContext,
    server_name: &'static str,
    cfg: F,
) -> Result<(), anyhow::Error>
where
    F: Fn(&mut web::ServiceConfig, web::Data<crate::server::AppContext>) + Send + Clone + 'static,
{
    let configuration = ac.config();

    // data context
    let context = web::Data::new(ac);

    let address = format!("{}:{}", "0.0.0.0", configuration.application_port);
    let address_clone = address.clone();
    let protocal = configuration.protocal.clone();

    let upstreams = configuration.upstreams.clone();
    let cookie_session_settings = CookieSessionSettings::get(&configuration.cookie_session);

    // consumers
    // use crate::services::kline_service;
    // kline_service::consumer::consumers1(redis_pubsub_async.clone(), es_client.clone());

    // websocket
    let devstreams = web::Data::new(devstreams::AppState::build());
    // let echo_server = web::Data::new(echo::ctx::EchoContext::build(context.clone()));
    // let webchat_server = web::Data::new(webchat::ctx::WebChatContext::build(context.clone()));

    // websocket actorless echo
    let (echo_server, echo_connection_tx) = EchoServer::new(context.clone()).await;
    let actorless_echo_server = tokio::spawn(echo_server.run());

    // // websocket actorless chat
    // let (chat_server, chat_server_handler_tx) = ChatServer::new(context.clone());
    // let chat_server = tokio::spawn(chat_server.run());

    // graphql
    let graphql_data = crate::graphql::build(context.clone());

    let reqwest_client = reqwest::Client::default();

    let new_app = move || {
        let mut app = App::new()
            .wrap(middleware::Compress::default())
            // .wrap(actix_middleware_etag::Etag::default())
            .configure(url_dispatch::static_router)
            .configure(url_dispatch::beautifier_js_router)
            .configure(url_dispatch::favicon_router)
            .wrap(cookie_session::session_cookie_store(&cookie_session_settings))
            .wrap(cookie_session::flash_message(&cookie_session_settings).clone())
            .wrap(crate::utils::cors())
            .wrap(crate::utils::normalize_path_config())
            // .wrap(crate::utils::access_limiter())
            .wrap(from_fn(force_keep_alive))
            .wrap(RequestIdentifier::with_uuid().use_incoming_id(IdReuse::UseIncoming))
            .wrap(middlewares::access_filter::Logger::get())
            .wrap(crate::utils::default_headers()) // ..
            ;

        app = app
            // To re-use the same HTTP client across multiple requests in actix-web we need to store a copy of it in the application context
            // we will then be able to retrieve a reference to Client in our request handlers using an extractor (e.g. actix_web::web::Data).
            .app_data(web::Data::new(reqwest_client.clone()))
            // config upstream client
            .app_data(web::Data::new(awc::Client::builder().finish()))
            .app_data(web::Data::new(upstreams.clone()))
            // Get a pointer copy and attach it to the application state
            .app_data(context.tpl.clone())
            .app_data(context.clone()) // application context
            .app_data(devstreams.clone())
            // .app_data(echo_server.clone())
            // .app_data(webchat_server.clone())
            .app_data(web::Data::new(echo_connection_tx.clone()))
            // .app_data(web::Data::new(chat_server_handler_tx.clone()))
            .app_data(web::Data::new(graphql_data.clone())) // graphql
            // ..
            .app_data(crate::utils::path_variable_error_handler()) // path variable error handle
            // .app_data(crate::utils::validate_path_variable()) // PathVariable 参数验证
            // query string error handle
            // .app_data(crate::utils::validate_qs_query_string())
            // .app_data(crate::utils::validate_query_string())
            // ..
            .app_data(crate::utils::post_json_error_handler()) // post json error handle
            .app_data(crate::utils::post_form_error_handler()) // post form error handle
            .app_data(crate::utils::multipart_form_error_handler()) // post multipart error handle
            // .app_data(crate::utils::validate_json_body()) // JsonBody 验证
            // .app_data(crate::utils::validate_form_data()) // FormData 参数验证
            .app_data(web::PayloadConfig::default().limit(10 * 1024 * 1024)) // 将限制设置为10MB
            // ..
            .app_data(crate::utils::query_error_handler());

        // register routers
        app = app
            .configure(|c| {
                crate::server::serv::config(
                    c,
                    configuration.use_developer,
                    context.clone(),
                    upstreams.clone(),
                )
            })
            .configure(|wc| cfg(wc, context.clone()));

        // set default router
        app = app.default_service(actix_web::web::route().to(sitepages::default::not_found));

        print_line(server_name, &protocal, &(address_clone));

        app
    };

    let server = HttpServer::new(new_app)
        .backlog(configuration.backlog_size)
        .workers(if configuration.worker_count == 0 {
            std::thread::available_parallelism()?.get()
        } else {
            configuration.worker_count
        })
        .shutdown_timeout(5)
        .keep_alive(std::time::Duration::from_secs(configuration.keepalive_time));

    let bind_result = if configuration.protocal == "http" {
        server.bind(&address)
    } else {
        server.bind_openssl(
            &address,
            crate::commons::tls_builder((&configuration.https_key, &configuration.https_cert)),
        )
    };

    match bind_result {
        Ok(svr) => {
            let http_server = svr.run();

            tokio::try_join!(
                http_server,
                async move { actorless_echo_server.await.unwrap() },
                // async move { chat_server.await.unwrap() },
            )?;

            Ok(())
        }
        Err(e) => {
            #[rustfmt::skip]
            log::info!("🔥 Couldn't start the server at port {}, error={}", &address, e );
            Err(anyhow::anyhow!(e))
        }
    }
}

fn print_line(server_name: &str, protocal: &str, address: &str) {
    let line = format!(
        "{:>21} {}",
        "Congratulations!", "Your server startup and running ~"
    );

    if !crate::core::cacheable::exists60s(&line) {
        log::info!("{:>20}: {}", "Server Name", server_name);
        log::info!("{:>20}: {}://{}", "Http Address", protocal, address);

        log::info!(
            "{:>20}: {}",
            "Temporary Folder",
            crate::commons::temp_folder(server_name)
        );

        log::info!(
            "{:>20}: {}:00",
            "Datetime Offset",
            crate::commons::get_date_offsets()
        );

        log::info!("{}", line);

        crate::core::cacheable::put60s(&line, "");
    }
}