Files
blackbird/libhttp/src/HttpServer.cpp
T
qing e815f0ec40 fix(gateway,libhttp): 修复进程无法退出bug,完成P0安全问题修复
安全修复(P0):
- 修复4处错误消息泄露内部步骤问题,替换为用户友好文案
- 修正拼写错误 "Registe" -> "Registration"
- 修正注册失败HTTP状态码 400 -> 500
- 所有P0严重问题100%修复,安全评级提升至A-

稳定性修复(关键):
- 修复信号处理非async-signal-safe问题:仅设置原子标志,杜绝死锁
- 新增SIGPIPE忽略处理,防止写关闭socket时进程意外终止
- 修复Listener::doAccept()关闭后无限递归导致CPU 100%空转bug
- 新增Listener::close()方法,stop()先关闭acceptor再停止ioc
- 改进stop()流程:acceptor->WS客户端->ioc->worker线程 有序关闭
- 新增双重超时保护:stop() 8秒、日志flush 3秒,超时强制exit保证进程绝不挂死
- 服务器事件循环改为独立线程运行,主线程负责信号检测和关闭协调

文档:
- 新增CHANGELOG.md更新日志
- 更新gateway安全审计文档,添加稳定性修复记录
- 更新docs/INDEX.md文档索引
2026-08-07 23:07:35 +08:00

217 lines
8.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "us/detail/HttpServerImpl.hpp"
namespace pmc {
namespace net {
// ===================== HttpServer 核心生命周期 =====================
HttpServer::HttpServer(const std::string& address, unsigned short port, unsigned int threads)
: address_(address), port_(port), threads_(threads), ssl_enabled_(false),
ioc_(std::make_unique<asio::io_context>(threads)) {
if (!ioc_) throw CreateObjectFailed("Failed to create io_context");
}
HttpServer::HttpServer(const std::string& address, unsigned short port,
const std::string& cert_file, const std::string& key_file,
unsigned int threads)
: address_(address), port_(port), threads_(threads), ssl_enabled_(true),
cert_file_(cert_file), key_file_(key_file),
ioc_(std::make_unique<asio::io_context>(threads)) {
if (!ioc_) throw CreateObjectFailed("Failed to create io_context");
initSSLContext();
}
HttpServer::~HttpServer() { stop(); }
void HttpServer::setPrivateKeyPassword(const std::string& password) {
private_key_password_ = password;
}
void HttpServer::initSSLContext() {
if (!ssl_enabled_) return;
try {
ssl_ctx_ = std::make_unique<asio::ssl::context>(asio::ssl::context::tls);
ssl_ctx_->set_options(
asio::ssl::context::default_workarounds |
asio::ssl::context::no_sslv2 |
asio::ssl::context::no_sslv3 |
asio::ssl::context::single_dh_use);
SSL_CTX_set_cipher_list(ssl_ctx_->native_handle(),
"ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS");
if (!private_key_password_.empty()) {
ssl_ctx_->set_password_callback(
[this](std::size_t, asio::ssl::context::password_purpose) {
return private_key_password_;
});
}
std::cout << "[SSL] Loading certificate chain: " << cert_file_ << std::endl;
ssl_ctx_->use_certificate_chain_file(cert_file_);
std::cout << "[SSL] Loading private key: " << key_file_ << std::endl;
ssl_ctx_->use_private_key_file(key_file_, asio::ssl::context::pem);
std::cout << "[SSL] SSL context ready." << std::endl;
} catch (const std::exception& e) {
std::cerr << "[SSL] Init failed: " << e.what() << std::endl;
ERR_print_errors_fp(stderr);
throw SSLInitException("Failed to initialize SSL context");
}
}
void HttpServer::start() {
if (running_) return;
try {
auto addr = asio::ip::make_address(address_);
tcp::endpoint ep(addr, port_);
if (ssl_enabled_ && ssl_ctx_) {
listener_ = std::make_shared<Listener>(*ioc_, ep, *ssl_ctx_, this);
} else {
listener_ = std::make_shared<Listener>(*ioc_, ep, this);
}
listener_->run();
running_ = true;
for (unsigned i = 0; i < threads_; ++i) {
worker_threads_.emplace_back([this]() {
try { ioc_->run(); }
catch (const std::exception& e) {
std::cerr << "[Thread] Exception: " << e.what() << std::endl;
}
});
}
std::cout << (ssl_enabled_ ? "HTTPS" : "HTTP")
<< " Server started on " << address_ << ":" << port_
<< " with " << threads_ << " threads";
if (!ws_handlers_.empty()) {
std::cout << " (WebSocket enabled, " << ws_handlers_.size() << " WS route(s))";
}
std::cout << "\n";
} catch (const std::exception& e) {
std::cerr << "[Start] Failed: " << e.what() << std::endl;
running_ = false;
}
}
void HttpServer::stop() {
if (!running_) return;
running_ = false;
std::cerr << "[HttpServer] 正在停止服务器..." << std::endl;
// 1. 先关闭 acceptor,停止接受新连接
if (listener_) {
listener_->close();
}
// 2. 清空 WebSocket 客户端(触发它们的关闭回调)
{
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
ws_clients_.clear();
}
// 3. 清空 session 列表(weak_ptr,session 会在异步操作完成后自动析构)
closeAllSessions();
// 4. 停止 io_context 事件循环(取消所有挂起的异步操作)
if (ioc_) {
ioc_->stop();
}
// 5. 等待所有 worker 线程退出
for (auto& t : worker_threads_) {
if (t.joinable()) t.join();
}
worker_threads_.clear();
// 6. 重置 listener
listener_.reset();
std::cerr << "[HttpServer] " << (ssl_enabled_ ? "HTTPS" : "HTTP")
<< " Server stopped" << std::endl;
}
void HttpServer::run() {
start();
if (ioc_) ioc_->run();
}
bool HttpServer::isRunning() const { return running_; }
unsigned short HttpServer::getPort() const { return port_; }
std::string HttpServer::getAddress() const { return address_; }
bool HttpServer::isSSLEnabled() const { return ssl_enabled_; }
// ---- 会话管理 ----
void HttpServer::addSession(const std::shared_ptr<Session>& session) {
std::lock_guard<std::mutex> lock(sessions_mutex_);
sessions_.erase(std::remove_if(sessions_.begin(), sessions_.end(),
[](const std::weak_ptr<Session>& wp) { return wp.expired(); }), sessions_.end());
sessions_.push_back(session);
}
void HttpServer::removeSession(const Session* session) {
std::lock_guard<std::mutex> lock(sessions_mutex_);
sessions_.erase(std::remove_if(sessions_.begin(), sessions_.end(),
[session](const std::weak_ptr<Session>& wp) {
auto sp = wp.lock();
return !sp || sp.get() == session;
}), sessions_.end());
}
void HttpServer::closeAllSessions() {
std::lock_guard<std::mutex> lock(sessions_mutex_);
sessions_.clear();
}
// ---- 调试工具 ----
void HttpServer::debug::print_all_header_fields(const http::request<http::string_body>& req) {
for (auto const& field : req) {
std::cout << "Name: " << field.name_string()
<< ", Value: " << field.value() << std::endl;
}
}
void HttpServer::showStatus() const {
std::cout << "=== " << (ssl_enabled_ ? "HTTPS" : "HTTP") << " Server Status ===" << std::endl;
std::cout << "Protocol: " << (ssl_enabled_ ? "HTTPS (SSL/TLS)" : "HTTP") << std::endl;
std::cout << "Address: " << address_ << std::endl;
std::cout << "Port: " << port_ << std::endl;
std::cout << "Threads: " << threads_ << std::endl;
std::cout << "Running: " << (running_ ? "Yes" : "No") << std::endl;
if (ssl_enabled_) {
std::cout << "Certificate: " << cert_file_ << std::endl;
std::cout << "Private Key: " << key_file_ << std::endl;
}
std::cout << "Static Directory: " << (static_directory_.empty() ? "Not set" : static_directory_) << std::endl;
std::cout << "Registered Routes:" << std::endl;
std::cout << " GET: " << get_handlers_.size() << " handler(s)" << std::endl;
for (const auto& entry : get_handlers_)
std::cout << " GET " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
std::cout << " POST: " << post_handlers_.size() << " handler(s)" << std::endl;
for (const auto& entry : post_handlers_)
std::cout << " POST " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
std::cout << " PUT: " << put_handlers_.size() << " handler(s)" << std::endl;
for (const auto& entry : put_handlers_)
std::cout << " PUT " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
std::cout << " DELETE: " << delete_handlers_.size() << " handler(s)" << std::endl;
for (const auto& entry : delete_handlers_)
std::cout << " DELETE " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
std::cout << " OPTIONS: " << options_handlers_.size() << " handler(s)" << std::endl;
for (const auto& entry : options_handlers_)
std::cout << " OPTIONS " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
std::cout << " WebSocket: " << ws_handlers_.size() << " route(s)" << std::endl;
for (const auto& entry : ws_handlers_)
std::cout << " WS " << entry.path << std::endl;
std::cout << " WebSocket-Proxy: " << ws_proxy_handlers_.size() << " route(s)" << std::endl;
for (const auto& entry : ws_proxy_handlers_)
std::cout << " WS-PROXY " << entry.path << std::endl;
std::cout << " WebSocket clients: " << getWsClientCount() << std::endl;
std::cout << "Middlewares: " << middlewares_.size() << std::endl;
std::cout << "==========================" << std::endl;
}
} // namespace net
} // namespace pmc