atomic_server_lib/
serve.rs

1use actix_cors::Cors;
2use actix_web::{middleware, web, HttpServer};
3
4use crate::errors::AtomicServerResult;
5
6/// Clears and rebuilds the Store & Search indexes
7fn rebuild_indexes(appstate: &crate::appstate::AppState) -> AtomicServerResult<()> {
8    let appstate_clone = appstate.clone();
9
10    actix_web::rt::spawn(async move {
11        appstate_clone
12            .store
13            .clear_index()
14            .expect("Failed to clear value index");
15        appstate_clone
16            .store
17            .build_index(true)
18            .expect("Failed to build value index");
19    });
20
21    tracing::info!("Removing existing search index...");
22    appstate
23        .search_state
24        .writer
25        .write()
26        .expect("Could not get a lock on search writer")
27        .delete_all_documents()?;
28    appstate.search_state.add_all_resources(&appstate.store)?;
29    Ok(())
30}
31
32// Increase the maximum payload size (for POSTing a body, for example) to 50MB
33const PAYLOAD_MAX: usize = 50_242_880;
34
35/// Start the server
36pub async fn serve(config: crate::config::Config) -> AtomicServerResult<()> {
37    println!("Atomic-server {} \nUse --help for instructions. Visit https://docs.atomicdata.dev and https://github.com/atomicdata-dev/atomic-server for more info.", env!("CARGO_PKG_VERSION"));
38    let tracing_chrome_flush_guard = crate::trace::init_tracing(&config);
39
40    // Setup the database and more
41    let appstate = crate::appstate::AppState::init(config.clone())?;
42
43    // Start async processes
44    if config.opts.rebuild_indexes {
45        rebuild_indexes(&appstate)?;
46    }
47
48    let server = HttpServer::new(move || {
49        let cors = Cors::permissive();
50
51        actix_web::App::new()
52            .app_data(web::PayloadConfig::new(PAYLOAD_MAX))
53            .app_data(web::Data::new(appstate.clone()))
54            .wrap(cors)
55            .wrap(tracing_actix_web::TracingLogger::default())
56            .wrap(middleware::Compress::default())
57            // Here are the actual handlers / endpoints
58            .configure(crate::routes::config_routes)
59            .default_service(web::to(|| {
60                tracing::error!("Wrong route, should not happen with normal requests");
61                actix_web::HttpResponse::NotFound()
62            }))
63            .app_data(
64                web::JsonConfig::default()
65                    // register error_handler for JSON extractors.
66                    .error_handler(crate::jsonerrors::json_error_handler),
67            )
68    });
69
70    let message = format!("{}\n\nVisit {}\n\n", BANNER, config.server_url);
71
72    if config.opts.https {
73        if cfg!(feature = "https") {
74            #[cfg(feature = "https")]
75            {
76                // If there is no certificate file, or the certs are too old, start HTTPS initialization
77                {
78                    if crate::https::should_renew_certs_check(&config)? {
79                        crate::https::request_cert(&config).await?;
80                    }
81                }
82                let https_config = crate::https::get_https_config(&config)
83                    .expect("HTTPS TLS Configuration with Let's Encrypt failed.");
84                let endpoint = format!("{}:{}", config.opts.ip, config.opts.port_https);
85                tracing::info!("Binding HTTPS server to endpoint {}", endpoint);
86                println!("{}", message);
87                server
88                    .bind_rustls(&endpoint, https_config)
89                    .map_err(|e| format!("Cannot bind to endpoint {}: {}", &endpoint, e))?
90                    .shutdown_timeout(TIMEOUT)
91                    .run()
92                    .await?;
93            }
94        } else {
95            return Err("The HTTPS feature has been disabled for this build. Please compile atomic-server with the HTTP feature. `cargo install atomic-server`".into());
96        }
97    } else {
98        let endpoint = format!("{}:{}", config.opts.ip, config.opts.port);
99        tracing::info!("Binding HTTP server to endpoint {}", endpoint);
100        println!("{}", message);
101        server
102            .bind(&format!("{}:{}", config.opts.ip, config.opts.port))
103            .map_err(|e| format!("Cannot bind to endpoint {}: {}", &endpoint, e))?
104            .shutdown_timeout(TIMEOUT)
105            .run()
106            .await?;
107    }
108
109    tracing::info!("Cleaning up");
110    // Cleanup, runs when server is stopped
111    // Note that more cleanup code is in Appstate::exit
112    if let Some(guard) = tracing_chrome_flush_guard {
113        guard.flush()
114    }
115
116    tracing::info!("Server stopped");
117    Ok(())
118}
119
120/// Amount of seconds before server shuts down connections after SIGTERM signal
121const TIMEOUT: u64 = 15;
122
123const BANNER: &str = r#"
124         __                  _
125  ____ _/ /_____  ____ ___  (_)____      ________  ______   _____  _____
126 / __ `/ __/ __ \/ __ `__ \/ / ___/_____/ ___/ _ \/ ___/ | / / _ \/ ___/
127/ /_/ / /_/ /_/ / / / / / / / /__/_____(__  )  __/ /   | |/ /  __/ /
128\__,_/\__/\____/_/ /_/ /_/_/\___/     /____/\___/_/    |___/\___/_/
129"#;