Skip to content

Commit fa0dac8

Browse files
committed
🐛 Fix(CI): Resolve Pingora 0.6.0 build failures 🏗️
- 🗑️ Removed deprecated resolve_tls_ctx from proxy - 🔄 Switched pingclair binary to OpenSSL backend 🔐 - ✨ Implemented DynamicCertResolver with TlsAccept trait - 🛠️ Fixed Cargo dependencies (futures, async-trait) - ✅ Verified local build passes
1 parent 13c1792 commit fa0dac8

5 files changed

Lines changed: 194 additions & 32 deletions

File tree

Cargo.lock

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

implementation_plan.md.resolved

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Pingclair 项目里程碑与优化计划 🦀
2+
3+
> **愿景**: 打造一个像 Caddy 一样易用,且拥有 Pingora 级性能的现代化 Rust Web 服务器。
4+
5+
---
6+
7+
## ✅ 阶段一:核心构建与性能优化 (P0-P1) - 已完成
8+
9+
### 🚀 性能基座
10+
- [x] **正则表达式预编译**: 在 `Router` 中预处理各种匹配模式,避免请求时即时解析。
11+
- [x] **无锁/低锁并发**: 全面从 `std::sync` 迁移到 `parking_lot`。
12+
- [x] **静态资源优化**: 支持预压缩检测 (`.br`, `.gz`, `.zst`) 与零拷贝大文件流。
13+
- [x] **内存分配器**: 在 Linux 下启用 `jemalloc` 优化长时堆分配。
14+
15+
### 🛡️ 核心功能集
16+
- [x] **全功能反向代理**: 负载均衡 (RR, Hash, etc.)、健康检查、TLS 终止。
17+
- [x] **自动 HTTPS**: 集成 ACME (Let's Encrypt/ZeroSSL) 一键证书管理。
18+
- [x] **中间件生态**: Rate Limiting (令牌桶)、BasicAuth、Rewrite、Headers 过滤。
19+
- [x] **HTTP/3 支持**: 基于 QUIC 的高性能传输。
20+
21+
---
22+
23+
## ✅ 阶段二:部署自动化与 UX 飞跃 - 已完成 (当前版本)
24+
25+
### 📦 工业级部署 (`scripts/`)
26+
- [x] **[install.sh](file:///Users/zhaoyikai/code/pingclair/scripts/install.sh)**: 实现 Ubuntu/Debian 自动化一键部署,脚本含架构自适应、权限加固 (`setcap`) 与 FHS 规范。
27+
- [x] **[uninstall.sh](file:///Users/zhaoyikai/code/pingclair/scripts/uninstall.sh)**: 安全卸载,干净清理用户与数据。
28+
- [x] **Systemd 深度集成**: 提供标准化服务单元,支持 CLI 快捷管理。
29+
30+
### 🔄 平滑重载与稳定性
31+
- [x] **SIGHUP 动态重载**: 实现 `PingclairProxy::update_config`,支持无需停机的配置热加载。
32+
- [x] **隔离的异步运行时**: 解决 Pingora 与背景任务 (H3/Signals) 的运行时冲突。
33+
- [x] **CI 构建加固**: 修复 AArch64 交叉编译工具链,清理跨平台警告。
34+
35+
### 🎨 极致用户体验
36+
- [x] **Premium 默认页**: 现代化 **Futuristic Dark Mode** 落地页,支持 **中英双语** 🌐。
37+
- [x] **全注释示例配置**: [Pingclairfile.example](file:///Users/zhaoyikai/code/pingclair/examples/Pingclairfile.example) 指南,让新手一眼读懂。
38+
- [x] **品牌透传**: 响应中全局注入 `Server: Pingclair` 标识。
39+
40+
---
41+
42+
## 🛠️ 阶段三:工业级特性下沉 (P2-P3) - 未来方向
43+
44+
### ⚙️ 极致性能
45+
- [ ] **io_uring 事件驱动**: 进一步提升 Linux 下的网络吞吐。
46+
- [ ] **CPU 亲和性**: 软硬结合,减少上下文切换开销。
47+
48+
### 🌐 协议补全
49+
- [ ] **gRPC 代理**: 支持现代微服务间的通信透传。
50+
- [ ] **On-Demand TLS**: 实现动态域名实时颁发证书。
51+
52+
### 📊 生态建设
53+
- [ ] **Visual Dashboard**: 提供 Web UI 实时查看流量、QPS 与上游健康度。
54+
- [ ] **插件系统**: 完善 WebAssembly 插件接口,让用户编写自定义逻辑。
55+
56+
---
57+
58+
**最后审计**: 2026-01-27 23:15 🚀
59+
**状态**: 核心里程碑全部达成,进入稳定维护期。✨

pingclair-proxy/src/server.rs

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use pingora_core::upstreams::peer::HttpPeer;
1010
use pingora_core::Result as PingoraResult;
1111
use pingora_proxy::{ProxyHttp, Session};
1212
use pingora_http::{RequestHeader, ResponseHeader};
13-
use pingora_core::protocols::tls::TlsAcceptor;
13+
//
1414
use std::sync::Arc;
1515
use std::collections::HashMap;
1616
use parking_lot::RwLock;
@@ -462,36 +462,10 @@ impl ProxyHttp for PingclairProxy {
462462
RequestCtx::default()
463463
}
464464

465+
/*
466+
// Removed in Pingora 0.6: TLS resolution is handled by listeners, not the proxy trait.
465467
/// Resolve TLS certificate for SNI
466-
async fn resolve_tls_ctx(
467-
&self,
468-
session: &mut Session,
469-
_ctx: &mut Self::CTX,
470-
) -> pingora_core::Result<Option<TlsAcceptor>> {
471-
if let Some(tls_manager) = &self.tls_manager {
472-
let sni = session.req_header().headers.get("Host")
473-
.and_then(|v| v.to_str().ok())
474-
.unwrap_or("")
475-
.split(':')
476-
.next()
477-
.unwrap_or("");
478-
479-
if sni.is_empty() {
480-
return Ok(None);
481-
}
482-
483-
if let Some(cert) = tls_manager.resolve_cert(sni).await {
484-
let acceptor = TlsAcceptor::from_certified_key(cert)
485-
.map_err(|e| pingora_core::Error::because(
486-
pingora_core::ErrorType::TLSHandshakeFailure,
487-
"Failed to create TlsAcceptor",
488-
e
489-
))?;
490-
return Ok(Some(acceptor));
491-
}
492-
}
493-
Ok(None)
494-
}
468+
*/
495469

496470
/// Request filter (Handle static files and early return)
497471
async fn request_filter(&self, session: &mut Session, ctx: &mut Self::CTX) -> pingora_core::Result<bool> {

pingclair/Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,23 @@ pingclair-tls = { path = "../pingclair-tls" }
2121
pingclair-api = { path = "../pingclair-api" }
2222
pingclair-plugin = { path = "../pingclair-plugin" }
2323

24-
pingora = { workspace = true, features = ["proxy"] }
24+
pingora = { workspace = true, features = ["proxy", "openssl"] }
25+
openssl = { version = "0.10", features = ["v110"] }
26+
2527
pingora-core.workspace = true
2628
pingora-proxy.workspace = true
29+
rustls.workspace = true
30+
2731
clap.workspace = true
32+
async-trait.workspace = true
33+
2834
tokio.workspace = true
2935
tracing.workspace = true
3036
tracing-subscriber.workspace = true
3137
anyhow.workspace = true
3238
parking_lot = "0.12"
39+
futures = "0.3"
40+
3341

3442
[target.'cfg(target_os = "linux")'.dependencies]
3543
tikv-jemallocator = "0.6"

pingclair/src/main.rs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,74 @@
44
55
use clap::{Parser, Subcommand};
66
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
7+
use std::sync::Arc;
8+
use pingora_core::listeners::tls::TlsSettings;
9+
use pingora_core::listeners::TlsAccept;
10+
use pingora_core::protocols::tls::TlsRef;
11+
use pingclair_tls::manager::TlsManager;
12+
use openssl::ssl::NameType;
13+
use openssl::x509::X509;
14+
use openssl::pkey::PKey;
715

816
#[cfg(target_os = "linux")]
917
#[global_allocator]
1018
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
1119

20+
/// Resolves certificates dynamically using TlsManager
21+
struct DynamicCertResolver(Arc<TlsManager>);
22+
23+
// Manual Debug because TlsManager might not implement it
24+
impl std::fmt::Debug for DynamicCertResolver {
25+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26+
f.debug_struct("DynamicCertResolver").finish()
27+
}
28+
}
29+
30+
#[async_trait::async_trait]
31+
impl TlsAccept for DynamicCertResolver {
32+
async fn certificate_callback(&self, ssl: &mut TlsRef) {
33+
// Get SNI
34+
let sni = ssl.servername(NameType::HOST_NAME).unwrap_or("").to_string();
35+
if sni.is_empty() {
36+
return;
37+
}
38+
39+
tracing::debug!("🔐 Resolving cert for SNI: {}", sni);
40+
41+
if let Some((cert_pem, key_pem)) = self.0.resolve_pem(&sni).await {
42+
// Parse PEM
43+
// Note: In real production code, caching parsed X509/PKey might be better than parsing every handshake.
44+
// pingclair-tls caches rustls keys, but not openssl ones.
45+
// Optimization TODO: Cache OpenSSL objects in TlsManager.
46+
47+
let x509 = match X509::from_pem(cert_pem.as_bytes()) {
48+
Ok(c) => c,
49+
Err(e) => {
50+
tracing::error!("Failed to parse cert PEM: {}", e);
51+
return;
52+
}
53+
};
54+
55+
let pkey = match PKey::private_key_from_pem(key_pem.as_bytes()) {
56+
Ok(k) => k,
57+
Err(e) => {
58+
tracing::error!("Failed to parse key PEM: {}", e);
59+
return;
60+
}
61+
};
62+
63+
// Set into SSL
64+
if let Err(e) = ssl.set_certificate(&x509) {
65+
tracing::error!("Failed to set certificate: {}", e);
66+
}
67+
if let Err(e) = ssl.set_private_key(&pkey) {
68+
tracing::error!("Failed to set private key: {}", e);
69+
}
70+
71+
}
72+
}
73+
}
74+
1275
/// Pingclair - Modern web server inspired by Caddy, powered by Pingora
1376
#[derive(Parser)]
1477
#[command(name = "pingclair")]
@@ -350,7 +413,24 @@ fn run_server(config_path: String, config: pingclair_core::config::PingclairConf
350413
);
351414

352415
let mut service = proxy_service;
353-
service.add_tcp(addr);
416+
417+
// Determine if this is an HTTPS port
418+
if addr.ends_with(":443") || addr.ends_with(":8443") {
419+
// Setup TLS with dynamic resolver (OpenSSL)
420+
// Use TlsSettings::with_callbacks
421+
let acceptor = DynamicCertResolver(tls_manager.clone());
422+
match TlsSettings::with_callbacks(Box::new(acceptor)) {
423+
Ok(tls_settings) => {
424+
service.add_tls_with_settings(addr, None, tls_settings);
425+
tracing::info!(" 🔒 HTTPS/TLS enabled on {}", addr);
426+
}
427+
Err(e) => {
428+
tracing::error!("Failed to create TlsSettings: {}", e);
429+
}
430+
}
431+
} else {
432+
service.add_tcp(addr);
433+
}
354434
server.add_service(service);
355435

356436
// Check if this port should also support HTTP/3

0 commit comments

Comments
 (0)