headless_lms_server/programs/
start_server.rs1use 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 dev::ServiceRequest,
14 middleware::Logger,
15};
16use dotenvy::dotenv;
17use listenfd::ListenFd;
18use secrecy::ExposeSecret;
19use std::time::Duration;
20
21pub async fn main() -> anyhow::Result<()> {
23 dotenv().ok();
24 setup_tracing()?;
25
26 let runtime_config = ServerRuntimeConfig::try_from_env()?;
27 let private_cookie_key = runtime_config.private_cookie_key.clone();
28 let test_mode = runtime_config.test_mode;
29 let allow_no_https_for_development = runtime_config.allow_no_https_for_development;
30 let host = runtime_config.host.clone();
31 let port = runtime_config.port.clone();
32 set_server_runtime_config(runtime_config.clone())?;
33
34 if test_mode {
35 info!("***********************************");
36 info!("* Starting backend in test mode *");
37 info!("***********************************");
38 }
39 let server_config = ServerConfigBuilder::from_runtime_config(&runtime_config)
40 .await
41 .expect("Failed to create server config builder from runtime config")
42 .build()
43 .await
44 .expect("Failed to create server config");
45 let mut server = HttpServer::new(move || {
46 let server_config = server_config.clone();
47 App::new()
48 .configure(move |config| config::configure(config, server_config))
49 .wrap(
50 SessionMiddleware::builder(
51 CookieSessionStore::default(),
52 Key::from(private_cookie_key.expose_secret().as_bytes()),
53 )
54 .cookie_name("session".to_string())
55 .cookie_secure(!allow_no_https_for_development)
56 .cookie_same_site(SameSite::Strict) .cookie_http_only(true) .cookie_path("/api".to_string()) .cookie_content_security(CookieContentSecurity::Private)
60 .session_lifecycle(SessionLifecycle::PersistentSession(
61 PersistentSession::default()
62 .session_ttl(actix_web::cookie::time::Duration::days(100))
63 .session_ttl_extension_policy(TtlExtensionPolicy::OnEveryRequest),
64 ))
65 .build(),
66 )
67 .wrap(
68 Logger::new(
69 "Completed %{request_line}xi %s %b bytes - %D ms, request_id=%{request-id}o",
70 )
71 .custom_request_replace("request_line", redacted_request_line),
72 )
73 })
74 .keep_alive(Duration::from_secs(75));
77
78 let mut listenfd = ListenFd::from_env();
80 server = match listenfd.take_tcp_listener(0)? {
81 Some(listener) => server.listen(listener)?,
82 None => {
83 let bind_address = format!("{}:{}", host, port);
84 info!("Binding to address: {}", bind_address);
85 server.bind(bind_address)?
86 }
87 };
88
89 info!("Starting server.");
90 server.run().await?;
91
92 Ok(())
93}
94
95fn redacted_request_line(req: &ServiceRequest) -> String {
99 let mut path = String::with_capacity(req.path().len());
100 let mut is_token_segment = false;
101 for (i, segment) in req.path().split('/').enumerate() {
102 if i > 0 {
103 path.push('/');
104 }
105 path.push_str(if is_token_segment { "{token}" } else { segment });
106 is_token_segment = segment == "student-number-verifications";
108 }
109 let query = req.query_string();
110 if !query.is_empty() {
111 path.push('?');
112 for (i, pair) in query.split('&').enumerate() {
113 if i > 0 {
114 path.push('&');
115 }
116 path.push_str(pair.split('=').next().unwrap_or_default());
117 }
118 }
119 format!("{} {} {:?}", req.method(), path, req.version())
120}