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