Skip to main content

headless_lms_server/programs/
start_server.rs

1use crate::{
2    config::{self, ServerConfigBuilder, ServerRuntimeConfig, set_server_runtime_config},
3    setup_tracing,
4};
5use actix_session::{
6    SessionMiddleware,
7    config::{CookieContentSecurity, PersistentSession, SessionLifecycle, TtlExtensionPolicy},
8    storage::CookieSessionStore,
9};
10use actix_web::{
11    App, HttpServer,
12    cookie::{Key, SameSite},
13    middleware::Logger,
14};
15use dotenvy::dotenv;
16use listenfd::ListenFd;
17use rustls::crypto::ring;
18use secrecy::ExposeSecret;
19
20/// The entrypoint to the server.
21pub async fn main() -> anyhow::Result<()> {
22    dotenv().ok();
23    setup_tracing()?;
24
25    // Required by rustls 0.23 so kube-client can build TLS configs.
26    ring::default_provider()
27        .install_default()
28        .expect("failed to install rustls ring crypto provider");
29
30    let runtime_config = ServerRuntimeConfig::try_from_env()?;
31    let private_cookie_key = runtime_config.private_cookie_key.clone();
32    let test_mode = runtime_config.test_mode;
33    let allow_no_https_for_development = runtime_config.allow_no_https_for_development;
34    let host = runtime_config.host.clone();
35    let port = runtime_config.port.clone();
36    set_server_runtime_config(runtime_config.clone())?;
37
38    if test_mode {
39        info!("***********************************");
40        info!("*  Starting backend in test mode  *");
41        info!("***********************************");
42    }
43    let server_config = ServerConfigBuilder::from_runtime_config(&runtime_config)
44        .await
45        .expect("Failed to create server config builder from runtime config")
46        .build()
47        .await
48        .expect("Failed to create server config");
49    let mut server = HttpServer::new(move || {
50        let server_config = server_config.clone();
51        App::new()
52            .configure(move |config| config::configure(config, server_config))
53            .wrap(
54                SessionMiddleware::builder(
55                    CookieSessionStore::default(),
56                    Key::from(private_cookie_key.expose_secret().as_bytes()),
57                )
58                .cookie_name("session".to_string())
59                .cookie_secure(!allow_no_https_for_development)
60                .cookie_same_site(SameSite::Strict) // Default api can only be accessed from the main website. Public api will be less strict on this.
61                .cookie_http_only(true) // Cookie is inaccessible from javascript for security
62                .cookie_path("/api".to_string()) // browser won't send the cookie unless this path exists in the request url
63                .cookie_content_security(CookieContentSecurity::Private)
64                .session_lifecycle(SessionLifecycle::PersistentSession(
65                    PersistentSession::default()
66                        .session_ttl(actix_web::cookie::time::Duration::days(100))
67                        .session_ttl_extension_policy(TtlExtensionPolicy::OnEveryRequest),
68                ))
69                .build(),
70            )
71            .wrap(Logger::new(
72                "Completed %r %s %b bytes - %D ms, request_id=%{request-id}o",
73            ))
74    });
75
76    // this will enable us to keep application running during recompile: systemfd --no-pid -s http::5000 -- cargo watch -x run
77    let mut listenfd = ListenFd::from_env();
78    server = match listenfd.take_tcp_listener(0)? {
79        Some(listener) => server.listen(listener)?,
80        None => {
81            let bind_address = format!("{}:{}", host, port);
82            info!("Binding to address: {}", bind_address);
83            server.bind(bind_address)?
84        }
85    };
86
87    info!("Starting server.");
88    server.run().await?;
89
90    Ok(())
91}