Skip to main content

tdm_server_rust/server/
mod.rs

1//! HTTP 服务启动模块 (Server Bootstrap)
2//!
3//! 支持明文 HTTP/1.1 和 TLS + ALPN HTTP/2 两种启动模式。
4//! 使用 `axum::serve`(明文)或 `axum-server`(TLS)。
5//!
6//! ## 两种模式
7//!
8//! | 配置 | 协议 | 端口 | 用途 |
9//! |------|------|------|------|
10//! | `ssl_enabled = false` | HTTP/1.1 | `server.port` | 本地开发、反向代理后端 |
11//! | `ssl_enabled = true` | HTTPS + HTTP/2 | `server.port` | 直连生产环境 |
12//!
13//! ## TLS 证书
14//!
15//! - **生产**: 从 `server.tls_cert` / `server.tls_key` 路径加载 PEM 文件
16//! - **dev**: 证书不存在时自动生成 localhost 自签证书(rcgen)
17//! - **加密后端**: aws-lc-rs(rustls 的 FIPS 兼容加密库)
18
19use anyhow::{Context, Result};
20use axum::Router;
21use rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair, SanType};
22use std::fs;
23use std::net::SocketAddr;
24use std::path::{Path, PathBuf};
25use crate::config::ServerConfig;
26
27/// 解析 TLS 文件路径
28///
29/// 相对路径基于 config 目录解析,绝对路径直接使用。
30fn resolve_tls_path(config_dir: &Path, relative: &str) -> PathBuf {
31    let path = PathBuf::from(relative);
32    if path.is_absolute() {
33        path
34    } else {
35        config_dir.join(path)
36    }
37}
38
39/// dev 环境缺少证书时自动生成 localhost 自签 PEM 证书
40///
41/// 生成 RSA 密钥对 + X.509 自签名证书,SAN 包含 `localhost` 和 `127.0.0.1`。
42/// 证书和私钥分别写入 `tls_cert` 和 `tls_key` 指定的路径。
43///
44/// # Errors
45///
46/// 仅在文件系统写入失败时返回错误。
47fn ensure_dev_tls_pem(cert_path: &Path, key_path: &Path) -> Result<()> {
48    if cert_path.is_file() && key_path.is_file() {
49        return Ok(());
50    }
51    if let Some(parent) = cert_path.parent() {
52        fs::create_dir_all(parent).with_context(|| format!("创建证书目录失败: {parent:?}"))?;
53    }
54
55    let mut params = CertificateParams::new(vec!["localhost".into()])
56        .context("构建证书参数失败")?;
57    params.distinguished_name = DistinguishedName::new();
58    params
59        .distinguished_name
60        .push(DnType::CommonName, "localhost");
61    params.subject_alt_names = vec![
62        SanType::DnsName("localhost".try_into()?),
63        SanType::IpAddress(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)),
64    ];
65
66    let key_pair = KeyPair::generate().context("生成 TLS 密钥对失败")?;
67    let cert = params.self_signed(&key_pair).context("签发 dev 自签证书失败")?;
68
69    fs::write(cert_path, cert.pem()).with_context(|| format!("写入证书失败: {cert_path:?}"))?;
70    fs::write(key_path, key_pair.serialize_pem()).with_context(|| {
71        format!("写入私钥失败: {key_path:?}")
72    })?;
73    tracing::info!(
74        "已生成 dev 自签 TLS 证书 cert={cert_path:?} key={key_path:?}"
75    );
76    Ok(())
77}
78
79/// 安装 rustls 默认加密后端(aws-lc-rs)
80///
81/// 进程内仅需调用一次,重复调用无副作用。
82fn install_rustls_provider() {
83    let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
84}
85
86/// 启动 HTTP 服务
87///
88/// 根据 `server.ssl_enabled` 选择 HTTP/1.1 或 HTTPS + HTTP/2 模式。
89///
90/// # 参数
91///
92/// - `addr`: 监听地址,格式 `"host:port"`
93/// - `app`: axum Router(含所有路由和中间件)
94/// - `server`: 服务端配置(SSL 开关和证书路径)
95/// - `config_dir`: 配置目录(用于解析相对路径证书)
96/// - `profile`: 当前运行 profile("dev" / "dev-h2" / "pro")
97///
98/// # Panics
99///
100/// 不 panic。所有启动失败均通过 `anyhow::Result` 返回。
101///
102/// # 优雅关闭
103///
104/// axum/axum-server 自动处理 SIGTERM 信号,不丢失正在处理的请求。
105pub async fn serve(
106    addr: &str,
107    app: Router,
108    server: &ServerConfig,
109    config_dir: &Path,
110    profile: &str,
111) -> Result<()> {
112    if server.ssl_enabled {
113        install_rustls_provider();
114        let cert_rel = server
115            .tls_cert
116            .as_deref()
117            .context("ssl_enabled=true 时需配置 server.tls_cert")?;
118        let key_rel = server
119            .tls_key
120            .as_deref()
121            .context("ssl_enabled=true 时需配置 server.tls_key")?;
122        let cert_path = resolve_tls_path(config_dir, cert_rel);
123        let key_path = resolve_tls_path(config_dir, key_rel);
124
125        if profile == "dev" || profile == "dev-h2" {
126            ensure_dev_tls_pem(&cert_path, &key_path)?;
127        }
128
129        let rustls_config = axum_server::tls_rustls::RustlsConfig::from_pem_file(&cert_path, &key_path)
130            .await
131            .with_context(|| format!("加载 TLS 证书失败 cert={cert_path:?}"))?;
132
133        let socket_addr: SocketAddr = addr.parse().context("解析监听地址失败")?;
134        tracing::info!("HTTPS/HTTP2 监听 {addr}");
135        axum_server::bind_rustls(socket_addr, rustls_config)
136            .serve(app.into_make_service())
137            .await
138            .context("HTTPS 服务异常退出")?;
139    } else {
140        let listener = tokio::net::TcpListener::bind(addr)
141            .await
142            .with_context(|| format!("绑定端口失败: {addr}"))?;
143        tracing::info!("HTTP/1.1 监听 {addr}");
144        axum::serve(listener, app)
145            .await
146            .context("HTTP 服务异常退出")?;
147    }
148    Ok(())
149}