This commit is contained in:
qing committed 2026-07-20 11:12:29 +08:00
commit 70d467d052
97 files changed
+10386

No files matched your search

+200
View File
@@ -0,0 +1,200 @@
#pragma once
#include <string>
#include <functional>
#include <map>
#include <curl/curl.h>
namespace pmc::net {
enum class HttpMethod {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS
};
class HttpClient {
public:
HttpClient() {
curl_ = curl_easy_init();
if (!curl_) {
throw std::runtime_error("Failed to initialize CURL");
}
}
~HttpClient() {
if (curl_) {
curl_easy_cleanup(curl_);
}
if (headers_) {
curl_slist_free_all(headers_);
}
}
void set_header(const std::string& header) {
headers_ = curl_slist_append(headers_, header.c_str());
}
void clear_headers() {
if (headers_) {
curl_slist_free_all(headers_);
headers_ = nullptr;
}
}
const std::map<std::string, std::string>& get_response_headers() const {
return response_headers_;
}
long get_response_code() const {
return response_code_;
}
// ===== 修复:直接转发,不按行处理 =====
void request_stream_sync(
HttpMethod method,
const std::string& url,
const std::string& data,
std::function<void(const std::string&)> callback
) {
if (!curl_) {
throw std::runtime_error("CURL not initialized");
}
response_headers_.clear();
response_code_ = 0;
curl_easy_reset(curl_);
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
set_http_method(method, data);
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers_);
curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(curl_, CURLOPT_HEADERDATA, this);
// ===== 关键修复:直接收集原始数据,不按行处理 =====
std::string full_response;
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &full_response);
curl_easy_setopt(curl_, CURLOPT_TIMEOUT, 30L);
curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, 10L);
CURLcode res = curl_easy_perform(curl_);
if (res != CURLE_OK) {
throw std::runtime_error(std::string("CURL error: ") + curl_easy_strerror(res));
}
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &response_code_);
// ===== 完整转发所有数据(包括换行符) =====
if (!full_response.empty()) {
callback(full_response);
}
}
void get_stream_sync(const std::string& url, std::function<void(const std::string&)> callback) {
request_stream_sync(HttpMethod::GET, url, "", callback);
}
void post_stream_sync(const std::string& url, const std::string& data, std::function<void(const std::string&)> callback) {
request_stream_sync(HttpMethod::POST, url, data, callback);
}
void put_stream_sync(const std::string& url, const std::string& data, std::function<void(const std::string&)> callback) {
request_stream_sync(HttpMethod::PUT, url, data, callback);
}
void delete_stream_sync(const std::string& url, std::function<void(const std::string&)> callback) {
request_stream_sync(HttpMethod::DELETE, url, "", callback);
}
private:
CURL* curl_ = nullptr;
struct curl_slist* headers_ = nullptr;
std::map<std::string, std::string> response_headers_;
long response_code_ = 0;
void set_http_method(HttpMethod method, const std::string& data) {
switch (method) {
case HttpMethod::GET:
curl_easy_setopt(curl_, CURLOPT_HTTPGET, 1L);
break;
case HttpMethod::POST:
curl_easy_setopt(curl_, CURLOPT_POST, 1L);
if (!data.empty()) {
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data.c_str());
curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data.size());
}
break;
case HttpMethod::PUT:
curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "PUT");
if (!data.empty()) {
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data.c_str());
curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data.size());
}
break;
case HttpMethod::DELETE:
curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
case HttpMethod::PATCH:
curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "PATCH");
if (!data.empty()) {
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data.c_str());
curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data.size());
}
break;
case HttpMethod::HEAD:
curl_easy_setopt(curl_, CURLOPT_NOBODY, 1L);
break;
case HttpMethod::OPTIONS:
curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "OPTIONS");
break;
}
}
static size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata) {
size_t total_size = size * nitems;
HttpClient* client = static_cast<HttpClient*>(userdata);
std::string header_line(buffer, total_size);
size_t colon_pos = header_line.find(':');
if (colon_pos != std::string::npos) {
std::string key = header_line.substr(0, colon_pos);
std::string value = header_line.substr(colon_pos + 1);
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t\r\n") + 1);
if (!key.empty() && !value.empty()) {
client->response_headers_[key] = value;
}
}
return total_size;
}
// ===== 修复:直接写入原始数据,不按行处理 =====
static size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) {
size_t total_size = size * nmemb;
std::string* response = static_cast<std::string*>(userdata);
// 直接追加原始数据,保留所有换行符和特殊字符
response->append(ptr, total_size);
return total_size;
}
};
} // namespace pmc::net
+126
View File
@@ -0,0 +1,126 @@
#ifndef HTTPSERVER_HPP
#define HTTPSERVER_HPP
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <memory>
#include <string>
#include <functional>
#include <unordered_map>
#include <vector>
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <regex>
namespace pmc {
namespace net {
namespace beast = boost::beast;
namespace http = beast::http;
namespace asio = boost::asio;
using tcp = boost::asio::ip::tcp;
using HttpRequestHandler = std::function<http::response<http::string_body>(
const http::request<http::string_body>&,
const std::unordered_map<std::string, std::string>&)>;
using MiddlewareHandler = std::function<bool(
const http::request<http::string_body>&,
http::response<http::string_body>&,
const std::unordered_map<std::string, std::string>&)>;
struct RouteEntry {
std::string path;
std::regex pattern;
HttpRequestHandler handler;
bool is_wildcard;
};
class HttpServer {
public:
explicit HttpServer(const std::string& address, unsigned short port,
unsigned int threads = 1);
explicit HttpServer(const std::string& address, unsigned short port,
const std::string& cert_file, const std::string& key_file,
unsigned int threads = 1);
~HttpServer();
void start();
void stop();
void get(const std::string& path, HttpRequestHandler handler);
void post(const std::string& path, HttpRequestHandler handler);
void put(const std::string& path, HttpRequestHandler handler);
void del(const std::string& path, HttpRequestHandler handler);
void use(MiddlewareHandler middleware);
void setStaticDirectory(const std::string& path);
bool isRunning() const;
unsigned short getPort() const;
std::string getAddress() const;
bool isSSLEnabled() const;
void run();
void showStatus() const;
void setPrivateKeyPassword(const std::string& password);
class debug {
public:
static void print_all_header_fields(const http::request<http::string_body>& req);
};
private:
class Session;
class Listener;
std::string address_;
unsigned short port_;
unsigned int threads_;
std::atomic<bool> running_{false};
bool ssl_enabled_{false};
std::string cert_file_;
std::string key_file_;
std::string private_key_password_;
std::unique_ptr<asio::ssl::context> ssl_ctx_;
std::unique_ptr<asio::io_context> ioc_;
std::shared_ptr<Listener> listener_;
std::vector<std::thread> worker_threads_;
std::vector<RouteEntry> get_handlers_;
std::vector<RouteEntry> post_handlers_;
std::vector<RouteEntry> put_handlers_;
std::vector<RouteEntry> delete_handlers_;
std::vector<MiddlewareHandler> middlewares_;
std::string static_directory_;
std::vector<std::weak_ptr<Session>> sessions_;
std::mutex sessions_mutex_;
std::unordered_map<std::string, std::string> parseQueryParams(const std::string& query);
std::regex wildcardToRegex(const std::string& pattern);
bool matchPath(const std::string& path, const std::regex& pattern);
http::response<http::string_body> handleRequest(const http::request<http::string_body>& req);
HttpRequestHandler findRoute(const std::string& method, const std::string& path);
void addSession(const std::shared_ptr<Session>& session);
void removeSession(const Session* session);
void closeAllSessions();
void initSSLContext();
};
} // namespace net
} // namespace pmc
#endif // HTTPSERVER_HPP
+63
View File
@@ -0,0 +1,63 @@
#ifndef PMC_NET_WEBSOCKET_CLIENT_HPP
#define PMC_NET_WEBSOCKET_CLIENT_HPP
#include "WebSocketClientBase.hpp"
namespace pmc {
namespace net {
/**
* @brief Asynchronous WebSocket client based on Boost.Beast (non-SSL version)
*/
class WebSocketClient : public WebSocketClientBase {
public:
/**
* @brief Constructor
* @param host Server hostname or IP address
* @param port Server port number
* @param path WebSocket path (default "/")
*/
WebSocketClient(const std::string& host, const std::string& port, const std::string& path = "/");
/**
* @brief Destructor
*/
~WebSocketClient();
// Implement WebSocketClientBase interface
void setMessageCallback(MessageCallback callback) override;
void setErrorCallback(ErrorCallback callback) override;
void setConnectCallback(ConnectCallback callback) override;
void setCloseCallback(CloseCallback callback) override;
bool connect(int timeout_ms = 5000) override;
void disconnect() override;
bool isConnected() const override;
bool send(const std::string& message) override;
bool sendBinary(const void* data, size_t size) override;
void setAutoReconnect(bool enable, int interval_ms = 3000) override;
void setHeartbeatInterval(int interval_ms = 30000) override;
std::string getHost() const override;
std::string getPort() const override;
std::string getPath() const override;
// SSL/TLS related (non-SSL version returns default values)
bool isSecure() const override { return false; }
void setVerifyCertificate(bool verify) override { (void)verify;/* Ignored */ }
void setCertificateFile(const std::string& cert_file) override { (void)cert_file; /* Ignored */ }
void setPrivateKeyFile(const std::string& key_file) override { (void)key_file;/* Ignored */ }
void setCertificateAuthorityFile(const std::string& ca_file) override { (void)ca_file;/* Ignored */ }
private:
// Forward declaration of implementation class
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace net
} // namespace pmc
#endif // PMC_NET_WEBSOCKET_CLIENT_HPP
+61
View File
@@ -0,0 +1,61 @@
#ifndef PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
#define PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
#include <string>
#include <functional>
#include <memory>
namespace pmc {
namespace net {
/**
* @brief WebSocket client base class
*
* Defines the common interface for all WebSocket clients
*/
class WebSocketClientBase {
public:
// Callback function types
using MessageCallback = std::function<void(const std::string&)>;
using ErrorCallback = std::function<void(const std::string&)>;
using ConnectCallback = std::function<void(bool)>;
using CloseCallback = std::function<void()>;
virtual ~WebSocketClientBase() = default;
// Callback setters
virtual void setMessageCallback(MessageCallback callback) = 0;
virtual void setErrorCallback(ErrorCallback callback) = 0;
virtual void setConnectCallback(ConnectCallback callback) = 0;
virtual void setCloseCallback(CloseCallback callback) = 0;
// Connection management
virtual bool connect(int timeout_ms = 5000) = 0;
virtual void disconnect() = 0;
virtual bool isConnected() const = 0;
// Message sending
virtual bool send(const std::string& message) = 0;
virtual bool sendBinary(const void* data, size_t size) = 0;
// Configuration options
virtual void setAutoReconnect(bool enable, int interval_ms = 3000) = 0;
virtual void setHeartbeatInterval(int interval_ms = 30000) = 0;
// Information retrieval
virtual std::string getHost() const = 0;
virtual std::string getPort() const = 0;
virtual std::string getPath() const = 0;
// SSL/TLS related
virtual bool isSecure() const = 0;
virtual void setVerifyCertificate(bool verify) = 0;
virtual void setCertificateFile(const std::string& cert_file) = 0;
virtual void setPrivateKeyFile(const std::string& key_file) = 0;
virtual void setCertificateAuthorityFile(const std::string& ca_file) = 0;
};
} // namespace net
} // namespace pmc
#endif // PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
+176
View File
@@ -0,0 +1,176 @@
// WebSocketServer.hpp - WebSocket server header file
// Based on Boost.Beast and Boost.Asio
#ifndef PMC_NET_WEBSOCKETSERVER_HPP
#define PMC_NET_WEBSOCKETSERVER_HPP
#include <functional>
#include <string>
#include <memory>
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <vector>
#include <thread>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
namespace pmc::net {
/**
* @brief WebSocket connection handler type definition
*
* Handles message sending and receiving after WebSocket connection is established
*/
using WebSocketMessageHandler = std::function<void(
const std::string& clientId,
const std::string& message,
std::function<void(const std::string&)> sendCallback)>;
/**
* @brief WebSocket connection callback type definition
*/
using WebSocketConnectHandler = std::function<void(
const std::string& clientId,
const std::string& remoteAddress)>;
/**
* @brief WebSocket disconnection callback type definition
*/
using WebSocketDisconnectHandler = std::function<void(
const std::string& clientId)>;
/**
* @brief WebSocket server class
*
* WebSocket server implemented based on Boost.Beast and Boost.Asio
*/
class WebSocketServer {
public:
/**
* @brief Constructor
* @param port Listening port
* @param threads Number of threads (default 2)
*/
WebSocketServer(unsigned short port, unsigned int threads = 2);
/**
* @brief Destructor
*/
~WebSocketServer();
/**
* @brief Start the server
*/
void start();
/**
* @brief Stop the server
*/
void stop();
/**
* @brief Register message handler
* @param path Path (for routing)
* @param handler Message handler
*/
void onMessage(const std::string& path, WebSocketMessageHandler handler);
/**
* @brief Register connection handler
* @param handler Connection handler
*/
void onConnect(WebSocketConnectHandler handler);
/**
* @brief Register disconnection handler
* @param handler Disconnection handler
*/
void onDisconnect(WebSocketDisconnectHandler handler);
/**
* @brief Send message to a specific client
* @param clientId Client ID
* @param message Message content
* @return Whether sending was successful
*/
bool sendToClient(const std::string& clientId, const std::string& message);
/**
* @brief Broadcast message to all clients
* @param message Message content
* @return Number of clients that successfully received the message
*/
size_t broadcast(const std::string& message);
/**
* @brief Disconnect a specific client
* @param clientId Client ID
*/
void disconnectClient(const std::string& clientId);
/**
* @brief Get the number of currently connected clients
* @return Number of clients
*/
size_t getClientCount() const;
/**
* @brief Check if the server is running
* @return Running status
*/
bool isRunning() const { return running_; }
/**
* @brief Get the server listening port
* @return Port number
*/
unsigned short getPort() const { return port_; }
private:
// Internal Session class
class Session;
// Internal Listener class
class Listener;
// Internal implementation structure
struct Impl {
boost::asio::io_context ioc_;
std::shared_ptr<Listener> listener_;
std::vector<std::thread> threads_;
};
// Server configuration
unsigned short port_;
unsigned int threads_;
std::atomic<bool> running_{false};
// Internal implementation
std::unique_ptr<Impl> impl_;
// Generate client ID
std::string generateClientId() const;
// Add client
void addClient(const std::string& clientId, std::shared_ptr<Session> session);
// Remove client
void removeClient(const std::string& clientId);
// Get client session
std::shared_ptr<Session> getClient(const std::string& clientId);
// Callback functions
std::unordered_map<std::string, WebSocketMessageHandler> messageHandlers_;
WebSocketConnectHandler connectHandler_;
WebSocketDisconnectHandler disconnectHandler_;
// Client management
mutable std::mutex clientsMutex_;
std::unordered_map<std::string, std::shared_ptr<Session>> clients_;
};
} // namespace pmc::net
#endif // PMC_NET_WEBSOCKETSERVER_HPP