feat(libhttp): integrate WebSocket support into HttpServer
Merge WebSocketServer functionality into HttpServer so a single port can serve both HTTP(S) and WebSocket(S) endpoints while preserving full backward compatibility for existing HTTP interfaces. Key changes: - Add WebSocket route registration: ws(), onWsConnect(), onWsDisconnect() - Add client management: sendToWsClient(), broadcastWs(), disconnectWsClient() - Rewrite Session class using beast::tcp_stream for unified HTTP/WS handling - Support automatic ws:// and wss:// upgrades on HTTP/HTTPS ports - Thread-safe message queue with asio::post for cross-thread sends - Update Makefile to link OpenSSL and pthread for TLS/WebSocket support - Add test_server.cpp demonstrating HTTP + WebSocket mixed usage
This commit is contained in:
1 parent
e007990594
commit
ba9a77fe66
4 files changed
+580
-108
No files matched your search
+383
-103
@@ -5,6 +5,7 @@
|
||||
#include <iomanip>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <boost/beast/websocket/ssl.hpp>
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
@@ -29,25 +30,30 @@ public:
|
||||
|
||||
// ===================== Session =====================
|
||||
class HttpServer::Session : public std::enable_shared_from_this<Session> {
|
||||
using tcp_stream = beast::tcp_stream;
|
||||
using ssl_stream = asio::ssl::stream<tcp_stream&>;
|
||||
using ws_stream = websocket::stream<tcp_stream&>;
|
||||
using wss_stream = websocket::stream<ssl_stream&>;
|
||||
|
||||
public:
|
||||
// HTTP 构造
|
||||
Session(tcp::socket&& socket, HttpServer* server)
|
||||
: socket_(std::move(socket)), server_(server), ssl_enabled_(false),
|
||||
strand_(asio::make_strand(socket_.get_executor())) {}
|
||||
: stream_(std::move(socket)), server_(server), ssl_enabled_(false),
|
||||
is_websocket_(false), client_id_("") {}
|
||||
|
||||
// HTTPS 构造
|
||||
Session(tcp::socket&& socket, asio::ssl::context& ssl_ctx, HttpServer* server)
|
||||
: socket_(std::move(socket)), server_(server), ssl_enabled_(true),
|
||||
strand_(asio::make_strand(socket_.get_executor())) {
|
||||
ssl_stream_ = std::make_unique<asio::ssl::stream<tcp::socket>>(
|
||||
std::move(socket_), ssl_ctx);
|
||||
: stream_(std::move(socket)), server_(server), ssl_enabled_(true),
|
||||
is_websocket_(false), client_id_("") {
|
||||
ssl_stream_ = std::make_unique<ssl_stream>(stream_, ssl_ctx);
|
||||
}
|
||||
|
||||
~Session() { close(); }
|
||||
|
||||
void run() {
|
||||
if (ssl_enabled_) doSSLHandshake();
|
||||
else doRead();
|
||||
if (ssl_enabled_) {
|
||||
doSSLHandshake();
|
||||
} else {
|
||||
doRead();
|
||||
}
|
||||
}
|
||||
|
||||
void setRemoteInfo(const std::string& ip, unsigned short port) {
|
||||
@@ -55,16 +61,35 @@ public:
|
||||
remote_port_ = port;
|
||||
}
|
||||
|
||||
void wsSend(const std::string& message) {
|
||||
auto exec = stream_.get_executor();
|
||||
asio::post(exec, [self = shared_from_this(), message]() {
|
||||
self->ws_send_queue_.push_back(message);
|
||||
if (self->ws_send_queue_.size() == 1) {
|
||||
self->doWsWrite();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::string getClientId() const { return client_id_; }
|
||||
std::string getRemoteAddress() const { return remote_ip_ + ":" + std::to_string(remote_port_); }
|
||||
bool isWebSocket() const { return is_websocket_; }
|
||||
|
||||
private:
|
||||
void close() {
|
||||
beast::error_code ec;
|
||||
if (ssl_enabled_ && ssl_stream_) {
|
||||
ssl_stream_->shutdown(ec);
|
||||
ssl_stream_->lowest_layer().close(ec);
|
||||
} else if (socket_.is_open()) {
|
||||
socket_.shutdown(tcp::socket::shutdown_send, ec);
|
||||
socket_.close(ec);
|
||||
if (is_websocket_) {
|
||||
if (ssl_enabled_ && wss_stream_) {
|
||||
wss_stream_->close(websocket::close_code::normal, ec);
|
||||
} else if (ws_stream_) {
|
||||
ws_stream_->close(websocket::close_code::normal, ec);
|
||||
}
|
||||
}
|
||||
if (ssl_stream_) {
|
||||
ssl_stream_->shutdown(ec);
|
||||
}
|
||||
stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
|
||||
stream_.close();
|
||||
}
|
||||
|
||||
void doSSLHandshake() {
|
||||
@@ -74,39 +99,60 @@ private:
|
||||
|
||||
ssl_stream_->async_handshake(
|
||||
asio::ssl::stream_base::server,
|
||||
asio::bind_executor(strand_,
|
||||
[this, self](beast::error_code ec) {
|
||||
if (!ec) {
|
||||
std::cout << "[SSL] Handshake succeeded: "
|
||||
<< remote_ip_ << ":" << remote_port_ << std::endl;
|
||||
doRead();
|
||||
} else {
|
||||
std::cerr << "[SSL] Handshake error: "
|
||||
<< remote_ip_ << ":" << remote_port_
|
||||
<< " - " << ec.message()
|
||||
<< " (code=" << ec.value() << ")" << std::endl;
|
||||
ERR_print_errors_fp(stderr);
|
||||
}
|
||||
}));
|
||||
[self](beast::error_code ec) {
|
||||
if (ec) {
|
||||
std::cerr << "[SSL] Handshake error: "
|
||||
<< self->remote_ip_ << ":" << self->remote_port_
|
||||
<< " - " << ec.message()
|
||||
<< " (code=" << ec.value() << ")" << std::endl;
|
||||
ERR_print_errors_fp(stderr);
|
||||
return;
|
||||
}
|
||||
std::cout << "[SSL] Handshake succeeded: "
|
||||
<< self->remote_ip_ << ":" << self->remote_port_ << std::endl;
|
||||
self->doRead();
|
||||
});
|
||||
}
|
||||
|
||||
void doRead() {
|
||||
auto self = shared_from_this();
|
||||
request_ = {};
|
||||
buffer_.consume(buffer_.size());
|
||||
|
||||
if (ssl_enabled_ && ssl_stream_) {
|
||||
http::async_read(*ssl_stream_, buffer_, request_,
|
||||
asio::bind_executor(strand_,
|
||||
[this, self](beast::error_code ec, std::size_t) { handleRead(ec); }));
|
||||
[self](beast::error_code ec, std::size_t) {
|
||||
self->handleRead(ec);
|
||||
});
|
||||
} else {
|
||||
http::async_read(socket_, buffer_, request_,
|
||||
asio::bind_executor(strand_,
|
||||
[this, self](beast::error_code ec, std::size_t) { handleRead(ec); }));
|
||||
http::async_read(stream_, buffer_, request_,
|
||||
[self](beast::error_code ec, std::size_t) {
|
||||
self->handleRead(ec);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void handleRead(beast::error_code ec) {
|
||||
if (!ec) {
|
||||
if (isWebSocketUpgrade(request_)) {
|
||||
std::string path = std::string(request_.target());
|
||||
auto qpos = path.find('?');
|
||||
if (qpos != std::string::npos) path = path.substr(0, qpos);
|
||||
|
||||
auto ws_handler = server_->findWsRoute(path);
|
||||
if (ws_handler) {
|
||||
ws_handler_ = ws_handler;
|
||||
ws_path_ = path;
|
||||
if (ssl_enabled_) {
|
||||
doWssUpgrade();
|
||||
} else {
|
||||
doWsUpgrade();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto req_copy = std::move(request_);
|
||||
request_ = {};
|
||||
processRequest(std::move(req_copy));
|
||||
} else if (ec != http::error::end_of_stream) {
|
||||
std::cerr << "[Read] Error from " << remote_ip_ << ":" << remote_port_
|
||||
@@ -114,6 +160,154 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool isWebSocketUpgrade(const http::request<http::string_body>& req) {
|
||||
if (req.method() != http::verb::get) return false;
|
||||
|
||||
auto upgrade = req.find(http::field::upgrade);
|
||||
if (upgrade == req.end()) return false;
|
||||
|
||||
std::string upgrade_val(upgrade->value());
|
||||
boost::to_lower(upgrade_val);
|
||||
boost::trim(upgrade_val);
|
||||
if (upgrade_val != "websocket") return false;
|
||||
|
||||
auto connection = req.find(http::field::connection);
|
||||
if (connection == req.end()) return false;
|
||||
|
||||
std::string conn_val(connection->value());
|
||||
boost::to_lower(conn_val);
|
||||
return conn_val.find("upgrade") != std::string::npos;
|
||||
}
|
||||
|
||||
void doWsUpgrade() {
|
||||
auto self = shared_from_this();
|
||||
ws_stream_ = std::make_unique<ws_stream>(stream_);
|
||||
ws_stream_->text(true);
|
||||
|
||||
ws_stream_->async_accept(
|
||||
request_,
|
||||
[self](beast::error_code ec) {
|
||||
self->onWsAccept(ec);
|
||||
});
|
||||
}
|
||||
|
||||
void doWssUpgrade() {
|
||||
auto self = shared_from_this();
|
||||
wss_stream_ = std::make_unique<wss_stream>(*ssl_stream_);
|
||||
wss_stream_->text(true);
|
||||
|
||||
wss_stream_->async_accept(
|
||||
request_,
|
||||
[self](beast::error_code ec) {
|
||||
self->onWsAccept(ec);
|
||||
});
|
||||
}
|
||||
|
||||
void onWsAccept(beast::error_code ec) {
|
||||
if (ec) {
|
||||
std::cerr << "[WS] Accept error from " << remote_ip_ << ":" << remote_port_
|
||||
<< ": " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
is_websocket_ = true;
|
||||
client_id_ = server_->generateWsClientId();
|
||||
server_->addWsClient(client_id_, shared_from_this());
|
||||
|
||||
std::cout << "[WS] Client " << client_id_ << " connected from "
|
||||
<< remote_ip_ << ":" << remote_port_ << " at " << ws_path_ << std::endl;
|
||||
|
||||
if (server_->ws_connect_handler_) {
|
||||
server_->ws_connect_handler_(client_id_, getRemoteAddress());
|
||||
}
|
||||
|
||||
doWsRead();
|
||||
}
|
||||
|
||||
void doWsRead() {
|
||||
auto self = shared_from_this();
|
||||
ws_buffer_.consume(ws_buffer_.size());
|
||||
|
||||
if (ssl_enabled_ && wss_stream_) {
|
||||
wss_stream_->async_read(
|
||||
ws_buffer_,
|
||||
[self](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
self->onWsRead(ec, bytes_transferred);
|
||||
});
|
||||
} else if (ws_stream_) {
|
||||
ws_stream_->async_read(
|
||||
ws_buffer_,
|
||||
[self](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
self->onWsRead(ec, bytes_transferred);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onWsRead(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
if (ec == websocket::error::closed) {
|
||||
onWsClose();
|
||||
return;
|
||||
}
|
||||
if (ec) {
|
||||
std::cerr << "[WS] Read error from " << client_id_ << ": " << ec.message() << std::endl;
|
||||
onWsClose();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string message = beast::buffers_to_string(ws_buffer_.data());
|
||||
ws_buffer_.consume(bytes_transferred);
|
||||
|
||||
if (ws_handler_) {
|
||||
auto self = shared_from_this();
|
||||
ws_handler_(client_id_, message,
|
||||
[self](const std::string& response) {
|
||||
self->wsSend(response);
|
||||
});
|
||||
}
|
||||
|
||||
doWsRead();
|
||||
}
|
||||
|
||||
void doWsWrite() {
|
||||
if (ws_send_queue_.empty()) return;
|
||||
|
||||
auto self = shared_from_this();
|
||||
std::string& message = ws_send_queue_.front();
|
||||
|
||||
auto write_handler = [self](beast::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
std::cerr << "[WS] Write error to " << self->client_id_ << ": " << ec.message() << std::endl;
|
||||
self->onWsClose();
|
||||
return;
|
||||
}
|
||||
self->ws_send_queue_.pop_front();
|
||||
if (!self->ws_send_queue_.empty()) {
|
||||
self->doWsWrite();
|
||||
}
|
||||
};
|
||||
|
||||
if (ssl_enabled_ && wss_stream_) {
|
||||
wss_stream_->text(true);
|
||||
wss_stream_->async_write(asio::buffer(message), write_handler);
|
||||
} else if (ws_stream_) {
|
||||
ws_stream_->text(true);
|
||||
ws_stream_->async_write(asio::buffer(message), write_handler);
|
||||
}
|
||||
}
|
||||
|
||||
void onWsClose() {
|
||||
if (client_id_.empty()) return;
|
||||
std::cout << "[WS] Client " << client_id_ << " disconnected" << std::endl;
|
||||
|
||||
server_->removeWsClient(client_id_);
|
||||
|
||||
if (server_->ws_disconnect_handler_) {
|
||||
server_->ws_disconnect_handler_(client_id_);
|
||||
}
|
||||
client_id_.clear();
|
||||
is_websocket_ = false;
|
||||
}
|
||||
|
||||
void processRequest(http::request<http::string_body> req) {
|
||||
try {
|
||||
auto resp = std::make_shared<http::response<http::string_body>>(
|
||||
@@ -135,42 +329,49 @@ private:
|
||||
auto self = shared_from_this();
|
||||
if (ssl_enabled_ && ssl_stream_) {
|
||||
http::async_write(*ssl_stream_, *response,
|
||||
asio::bind_executor(strand_,
|
||||
[this, self, response](beast::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
std::cerr << "[Write] Error to " << remote_ip_ << ":" << remote_port_
|
||||
<< ": " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
if (!response->keep_alive()) close();
|
||||
else doRead();
|
||||
}));
|
||||
[self, response](beast::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
std::cerr << "[Write] Error to " << self->remote_ip_ << ":" << self->remote_port_
|
||||
<< ": " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
if (!response->keep_alive()) self->close();
|
||||
else self->doRead();
|
||||
});
|
||||
} else {
|
||||
http::async_write(socket_, *response,
|
||||
asio::bind_executor(strand_,
|
||||
[this, self, response](beast::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
std::cerr << "[Write] Error to " << remote_ip_ << ":" << remote_port_
|
||||
<< ": " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
if (!response->keep_alive()) close();
|
||||
else doRead();
|
||||
}));
|
||||
http::async_write(stream_, *response,
|
||||
[self, response](beast::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
std::cerr << "[Write] Error to " << self->remote_ip_ << ":" << self->remote_port_
|
||||
<< ": " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
if (!response->keep_alive()) self->close();
|
||||
else self->doRead();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tcp::socket socket_;
|
||||
std::unique_ptr<asio::ssl::stream<tcp::socket>> ssl_stream_;
|
||||
tcp_stream stream_;
|
||||
std::unique_ptr<ssl_stream> ssl_stream_;
|
||||
std::unique_ptr<ws_stream> ws_stream_;
|
||||
std::unique_ptr<wss_stream> wss_stream_;
|
||||
|
||||
beast::flat_buffer buffer_;
|
||||
beast::flat_buffer ws_buffer_;
|
||||
http::request<http::string_body> request_;
|
||||
|
||||
HttpServer* server_;
|
||||
bool ssl_enabled_;
|
||||
|
||||
asio::strand<asio::any_io_executor> strand_;
|
||||
|
||||
std::string remote_ip_ = "unknown";
|
||||
unsigned short remote_port_ = 0;
|
||||
|
||||
bool is_websocket_;
|
||||
std::string client_id_;
|
||||
std::string ws_path_;
|
||||
WebSocketMessageHandler ws_handler_;
|
||||
std::deque<std::string> ws_send_queue_;
|
||||
};
|
||||
|
||||
// ===================== Listener =====================
|
||||
@@ -204,8 +405,10 @@ private:
|
||||
}
|
||||
|
||||
void doAccept() {
|
||||
auto self = shared_from_this();
|
||||
acceptor_.async_accept(
|
||||
[this, self = shared_from_this()](beast::error_code ec, tcp::socket socket) {
|
||||
asio::make_strand(ioc_),
|
||||
[self](beast::error_code ec, tcp::socket socket) {
|
||||
if (!ec) {
|
||||
auto ep = socket.remote_endpoint(ec);
|
||||
std::string ip = ec ? "unknown" : ep.address().to_string();
|
||||
@@ -213,17 +416,18 @@ private:
|
||||
std::cout << "[Listener] New connection from " << ip << ":" << port << std::endl;
|
||||
|
||||
std::shared_ptr<Session> session;
|
||||
if (ssl_enabled_ && ssl_ctx_) {
|
||||
session = std::make_shared<Session>(std::move(socket), *ssl_ctx_, server_);
|
||||
if (self->ssl_enabled_ && self->ssl_ctx_) {
|
||||
session = std::make_shared<Session>(std::move(socket), *self->ssl_ctx_, self->server_);
|
||||
} else {
|
||||
session = std::make_shared<Session>(std::move(socket), server_);
|
||||
session = std::make_shared<Session>(std::move(socket), self->server_);
|
||||
}
|
||||
session->setRemoteInfo(ip, port);
|
||||
self->server_->addSession(session);
|
||||
session->run();
|
||||
} else {
|
||||
std::cerr << "[Listener] Accept error: " << ec.message() << std::endl;
|
||||
}
|
||||
doAccept();
|
||||
self->doAccept();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -236,7 +440,6 @@ private:
|
||||
|
||||
// ===================== 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)) {
|
||||
@@ -255,7 +458,6 @@ HttpServer::HttpServer(const std::string& address, unsigned short port,
|
||||
|
||||
HttpServer::~HttpServer() { stop(); }
|
||||
|
||||
// ---- SSL 初始化 ----
|
||||
void HttpServer::setPrivateKeyPassword(const std::string& password) {
|
||||
private_key_password_ = password;
|
||||
}
|
||||
@@ -294,7 +496,6 @@ void HttpServer::initSSLContext() {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 启动/停止 ----
|
||||
void HttpServer::start() {
|
||||
if (running_) return;
|
||||
try {
|
||||
@@ -318,7 +519,11 @@ void HttpServer::start() {
|
||||
}
|
||||
std::cout << (ssl_enabled_ ? "HTTPS" : "HTTP")
|
||||
<< " Server started on " << address_ << ":" << port_
|
||||
<< " with " << threads_ << " threads\n";
|
||||
<< " 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;
|
||||
@@ -328,6 +533,12 @@ void HttpServer::start() {
|
||||
void HttpServer::stop() {
|
||||
if (!running_) return;
|
||||
running_ = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
|
||||
ws_clients_.clear();
|
||||
}
|
||||
|
||||
closeAllSessions();
|
||||
if (ioc_) ioc_->stop();
|
||||
for (auto& t : worker_threads_) if (t.joinable()) t.join();
|
||||
@@ -341,13 +552,12 @@ void HttpServer::run() {
|
||||
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_; }
|
||||
|
||||
// ---- 路由注册 ----
|
||||
// ---- HTTP 路由注册 ----
|
||||
void HttpServer::get(const std::string& path, HttpRequestHandler handler) {
|
||||
RouteEntry entry;
|
||||
entry.path = path;
|
||||
@@ -388,7 +598,6 @@ void HttpServer::del(const std::string& path, HttpRequestHandler handler) {
|
||||
delete_handlers_.push_back(entry);
|
||||
}
|
||||
|
||||
// ---- 新增 OPTIONS 路由注册 ----
|
||||
void HttpServer::options(const std::string& path, HttpRequestHandler handler) {
|
||||
RouteEntry entry;
|
||||
entry.path = path;
|
||||
@@ -407,6 +616,62 @@ void HttpServer::setStaticDirectory(const std::string& p) {
|
||||
static_directory_ = p;
|
||||
}
|
||||
|
||||
// ---- WebSocket 路由注册 ----
|
||||
void HttpServer::ws(const std::string& path, WebSocketMessageHandler handler) {
|
||||
WsRouteEntry entry;
|
||||
entry.path = path;
|
||||
entry.handler = handler;
|
||||
ws_handlers_.push_back(entry);
|
||||
}
|
||||
|
||||
void HttpServer::onWsConnect(WebSocketConnectHandler handler) {
|
||||
ws_connect_handler_ = handler;
|
||||
}
|
||||
|
||||
void HttpServer::onWsDisconnect(WebSocketDisconnectHandler handler) {
|
||||
ws_disconnect_handler_ = handler;
|
||||
}
|
||||
|
||||
bool HttpServer::sendToWsClient(const std::string& clientId, const std::string& message) {
|
||||
std::shared_ptr<Session> session;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
|
||||
auto it = ws_clients_.find(clientId);
|
||||
if (it != ws_clients_.end()) {
|
||||
session = it->second;
|
||||
}
|
||||
}
|
||||
if (session) {
|
||||
session->wsSend(message);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t HttpServer::broadcastWs(const std::string& message) {
|
||||
std::vector<std::shared_ptr<Session>> clients;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
|
||||
for (auto& pair : ws_clients_) {
|
||||
clients.push_back(pair.second);
|
||||
}
|
||||
}
|
||||
for (auto& session : clients) {
|
||||
session->wsSend(message);
|
||||
}
|
||||
return clients.size();
|
||||
}
|
||||
|
||||
void HttpServer::disconnectWsClient(const std::string& clientId) {
|
||||
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
|
||||
ws_clients_.erase(clientId);
|
||||
}
|
||||
|
||||
size_t HttpServer::getWsClientCount() const {
|
||||
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
|
||||
return ws_clients_.size();
|
||||
}
|
||||
|
||||
// ---- 通配符转换 ----
|
||||
std::regex HttpServer::wildcardToRegex(const std::string& pattern) {
|
||||
std::string r;
|
||||
@@ -453,22 +718,48 @@ HttpRequestHandler HttpServer::findRoute(const std::string& method, const std::s
|
||||
else if (method == "OPTIONS") handlers = &options_handlers_;
|
||||
else return nullptr;
|
||||
|
||||
// 精确匹配优先
|
||||
for (const auto& e : *handlers) {
|
||||
if (!e.is_wildcard && e.path == path) return e.handler;
|
||||
}
|
||||
// 通配符匹配
|
||||
for (const auto& e : *handlers) {
|
||||
if (e.is_wildcard && matchPath(path, e.pattern)) return e.handler;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---- 新增辅助方法:检查路由是否存在 ----
|
||||
bool HttpServer::routeExists(const std::string& method, const std::string& path) {
|
||||
return findRoute(method, path) != nullptr;
|
||||
}
|
||||
|
||||
WebSocketMessageHandler HttpServer::findWsRoute(const std::string& path) {
|
||||
for (const auto& e : ws_handlers_) {
|
||||
if (e.path == path) return e.handler;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---- WebSocket 客户端管理 ----
|
||||
std::string HttpServer::generateWsClientId() {
|
||||
return "ws_client_" + std::to_string(++ws_client_counter_);
|
||||
}
|
||||
|
||||
void HttpServer::addWsClient(const std::string& clientId, std::shared_ptr<Session> session) {
|
||||
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
|
||||
ws_clients_[clientId] = session;
|
||||
}
|
||||
|
||||
void HttpServer::removeWsClient(const std::string& clientId) {
|
||||
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
|
||||
ws_clients_.erase(clientId);
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpServer::Session> HttpServer::getWsClient(const std::string& clientId) {
|
||||
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
|
||||
auto it = ws_clients_.find(clientId);
|
||||
if (it != ws_clients_.end()) return it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---- 请求处理核心 ----
|
||||
http::response<http::string_body> HttpServer::handleRequest(const http::request<http::string_body>& req) {
|
||||
std::string target_path(req.target().data(), req.target().size());
|
||||
@@ -486,7 +777,6 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
|
||||
res.set(http::field::content_type, "text/plain");
|
||||
res.keep_alive(req.keep_alive());
|
||||
|
||||
// 执行中间件
|
||||
bool middleware_handled = false;
|
||||
for (auto& m : middlewares_) {
|
||||
if (!m(req, res, params)) {
|
||||
@@ -500,10 +790,8 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
|
||||
}
|
||||
|
||||
std::string method = http::to_string(req.method());
|
||||
|
||||
// ========== 处理 OPTIONS 预检请求 ==========
|
||||
|
||||
if (method == "OPTIONS") {
|
||||
// 检查是否有自定义 OPTIONS 路由
|
||||
auto handler = findRoute("OPTIONS", target_path);
|
||||
if (handler) {
|
||||
try {
|
||||
@@ -516,34 +804,33 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// 自动生成 OPTIONS 响应
|
||||
|
||||
auto origin = req.find(http::field::origin);
|
||||
if (origin != req.end()) {
|
||||
// 构建允许的方法列表
|
||||
std::vector<std::string> all_methods = {"GET", "POST", "PUT", "DELETE", "OPTIONS"};
|
||||
std::vector<std::string> allowed_methods;
|
||||
|
||||
// 检查每个方法是否有对应的路由
|
||||
|
||||
for (const auto& m : all_methods) {
|
||||
if (m == "OPTIONS" || routeExists(m, target_path)) {
|
||||
allowed_methods.push_back(m);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed_methods.empty()) {
|
||||
|
||||
if (!allowed_methods.empty() || findWsRoute(target_path)) {
|
||||
if (allowed_methods.empty()) allowed_methods.push_back("GET");
|
||||
|
||||
std::string allow_methods;
|
||||
for (size_t i = 0; i < allowed_methods.size(); ++i) {
|
||||
if (i > 0) allow_methods += ", ";
|
||||
allow_methods += allowed_methods[i];
|
||||
}
|
||||
|
||||
|
||||
res.result(http::status::ok);
|
||||
res.set(http::field::allow, allow_methods);
|
||||
res.set(http::field::access_control_allow_origin,
|
||||
res.set(http::field::access_control_allow_origin,
|
||||
std::string(origin->value()));
|
||||
res.set(http::field::access_control_allow_methods, allow_methods);
|
||||
res.set(http::field::access_control_allow_headers,
|
||||
res.set(http::field::access_control_allow_headers,
|
||||
"Content-Type, Authorization, X-Requested-With, Accept, Origin");
|
||||
res.set(http::field::access_control_max_age, "86400");
|
||||
res.body() = "";
|
||||
@@ -552,19 +839,15 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// 没有 Origin 头或没有匹配的方法
|
||||
|
||||
res.result(http::status::not_found);
|
||||
res.body() = "404 Not Found\n";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
// ========== OPTIONS 处理结束 ==========
|
||||
|
||||
// 查找普通路由
|
||||
auto handler = findRoute(method, target_path);
|
||||
|
||||
// 静态文件
|
||||
if (!handler && !static_directory_.empty()) {
|
||||
std::string file_path = static_directory_ + target_path;
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
@@ -592,7 +875,6 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
|
||||
}
|
||||
}
|
||||
|
||||
// 路由处理
|
||||
if (handler) {
|
||||
try {
|
||||
return handler(req, params);
|
||||
@@ -605,14 +887,12 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
|
||||
}
|
||||
}
|
||||
|
||||
// 404
|
||||
res.result(http::status::not_found);
|
||||
res.body() = "404 Not Found\n";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
// ---- 状态显示 ----
|
||||
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;
|
||||
@@ -638,9 +918,13 @@ void HttpServer::showStatus() const {
|
||||
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; // 新增
|
||||
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 clients: " << getWsClientCount() << std::endl;
|
||||
std::cout << "Middlewares: " << middlewares_.size() << std::endl;
|
||||
std::cout << "==========================" << std::endl;
|
||||
}
|
||||
@@ -664,10 +948,6 @@ void HttpServer::removeSession(const Session* session) {
|
||||
|
||||
void HttpServer::closeAllSessions() {
|
||||
std::lock_guard<std::mutex> lock(sessions_mutex_);
|
||||
for (auto& weak : sessions_) {
|
||||
auto sp = weak.lock();
|
||||
if (sp) { /* 会话析构时自动关闭 socket */ }
|
||||
}
|
||||
sessions_.clear();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user