resymo_agent/common/
http.rs

1use actix_http::Request;
2use actix_service::IntoServiceFactory;
3use actix_web::body::MessageBody;
4use actix_web::dev::{AppConfig, Response, Service, ServiceFactory};
5use actix_web::*;
6use anyhow::bail;
7use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
8use std::fmt;
9use std::net::{IpAddr, SocketAddr};
10use std::path::PathBuf;
11use std::str::FromStr;
12
13#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, clap::Args, schemars::JsonSchema)]
14pub struct Options {
15    /// Bind host
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    #[arg(long, env)]
18    bind_host: Option<String>,
19
20    /// Bind port
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    #[arg(long, env)]
23    bind_port: Option<u16>,
24
25    /// A TLS certificate
26    #[cfg(feature = "openssl")]
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    #[arg(long, env)]
29    tls_certificate: Option<PathBuf>,
30
31    /// A TLS key
32    #[cfg(feature = "openssl")]
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    #[arg(long, env)]
35    tls_key: Option<PathBuf>,
36}
37
38pub struct Defaults {
39    pub port: u16,
40    pub host: IpAddr,
41}
42
43pub async fn run_server<F, I, S, B>(
44    options: Options,
45    defaults: Defaults,
46    factory: F,
47) -> anyhow::Result<()>
48where
49    F: Fn() -> I + Send + Clone + 'static,
50    I: IntoServiceFactory<S, Request>,
51
52    S: ServiceFactory<Request, Config = AppConfig> + 'static,
53    S::Error: Into<Error> + 'static,
54    S::InitError: fmt::Debug,
55    S::Response: Into<Response<B>> + 'static,
56    <S::Service as Service<Request>>::Future: 'static,
57    S::Service: 'static,
58
59    B: MessageBody + 'static,
60{
61    let bind_addr = SocketAddr::new(
62        options
63            .bind_host
64            .as_deref()
65            .map(IpAddr::from_str)
66            .transpose()?
67            .unwrap_or(defaults.host),
68        options.bind_port.unwrap_or(defaults.port),
69    );
70
71    log::info!("  Binding on: {}", bind_addr);
72    log::info!(
73        "  TLS - key: {}",
74        options
75            .tls_key
76            .as_ref()
77            .map(|p| p.display().to_string())
78            .unwrap_or_else(|| "<none>".to_string())
79    );
80    log::info!(
81        "  TLS - certificate: {}",
82        options
83            .tls_certificate
84            .as_ref()
85            .map(|p| p.display().to_string())
86            .unwrap_or_else(|| "<none>".to_string())
87    );
88
89    let server = HttpServer::new(factory);
90
91    let server = match (options.tls_key, options.tls_certificate) {
92        (Some(key), Some(cert)) => {
93            #[cfg(feature = "openssl")]
94            {
95                let mut acceptor = SslAcceptor::mozilla_modern_v5(SslMethod::tls_server())?;
96                acceptor.set_certificate_chain_file(cert)?;
97                acceptor.set_private_key_file(key, SslFiletype::PEM)?;
98                server.bind_openssl(bind_addr, acceptor)?.run()
99            }
100        }
101        (None, None) => server.bind(bind_addr)?.run(),
102        _ => {
103            bail!("Enabling TLS requires both --tls-key and --tls-certificate");
104        }
105    };
106
107    server.await?;
108
109    Ok(())
110}