Files

470 lines
16 KiB
Markdown
Raw Permalink 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.
# libhttp — C++20 HTTP / WebSocket 网络库
## 1. 模块概览
`libhttp` 是 `micro_servers` 项目的基础网络通信层,封装了 HTTP/HTTPS 服务器、HTTP 客户端(基于 libcurl)、WebSocket 服务器和 WebSocket 客户端四大功能。
以 C++20 标准编写,编译为动态库 `libus_http.so` 和静态库 `libus_http_static.a`,命名空间 `pmc::net`。
| 属性 | 值 |
|----------|-----------------------------|
| 版本 | 0.0.1 |
| 语言标准 | C++20 |
| 许可证 | MIT |
| 框架名 | `us` |
| 库名 | `libus_http` |
| 命名空间 | `pmc::net` |
**项目角色**:`libhttp` 被 `gateway` 模块和 `core` 模块直接依赖,承担所有入站请求的接收、路由、SSL 卸载和 WebSocket 长连接管理。
---
## 2. 文件清单
```
libhttp/
├── .gitignore # 构建产物忽略规则
├── CMakeLists.txt # CMake 构建 (v3.12+)
├── LICENSE # MIT License
├── Makefile # GNU Make 构建
├── _set.sh # 一键编译安装脚本
├── inc/us/
│ ├── HttpClient.hpp # HTTP 客户端 (header-only)
│ ├── HttpServer.hpp # HTTP/HTTPS 服务器声明
│ └── detail/
│ └── HttpServerImpl.hpp # Session/Listener 内部共享定义
└── src/
├── HttpServer.cpp # 核心生命周期/SSL 初始化
├── HttpServer_Listener.cpp # TCP 连接接受器
├── HttpServer_Routes.cpp # 路由注册/匹配/通配符
├── HttpServer_Request.cpp # 请求分发/静态文件/CORS/HTTP 读写
├── HttpServer_Ws.cpp # WebSocket 路由注册/客户端管理
└── HttpServer_WsSession.cpp# WebSocket 会话/代理桥接
```
---
## 3. 外部依赖
| 依赖 | 用途 | 备注 |
|------------------|-----------------------------------|---------------------------|
| Boost 1.91+ | Asio (I/O)、Beast (HTTP/WS)、Algorithms | 默认 `BOOST_ROOT=C:/local/boost_1_91_0`,可通过环境变量覆盖 |
| OpenSSL | TLS/SSL 加密传输 | 通过 Boost.Asio.SSL 调用 |
| libcurl | HTTP 客户端请求 | 仅 `HttpClient.hpp` 依赖 |
| pthread (Unix) | 多线程 | CMake 自动链接 |
| ws2_32 (Windows) | Winsock | CMake 自动链接 |
---
## 4. 构建
### 4.1 GNU Make
```bash
# 编译 (仅 HttpServer.o → libus_http.so)
make
# 清理
make clear
# 安装到 /lib 和 /usr/include/us/
make install
# 卸载
make uninstall
```
> **注意**:HttpServer 拆分为 6 个 TU,统一编译进 `libus_http.so`;WebSocket 能力已内置于 HttpServer(单端口同时服务 HTTP + WebSocket),客户端通过 `HttpClient` 提供,无独立 WebSocketServer/WebSocketClient 类。
### 4.2 CMake
```bash
mkdir build && cd build
cmake .. -DBOOST_ROOT=/path/to/boost
cmake --build .
cmake --install . --prefix /usr/local
```
CMake 会同时产出 `libus_http.so` (服务端) 和 `libus_httpclient.so` (客户端)。
### 4.3 一键脚本
```bash
bash _set.sh # make clear → make → make install
```
---
## 5. API 参考
### 5.1 HttpServer — HTTP/HTTPS 服务器
**头文件**: `<us/HttpServer.hpp>`
**实现**: `src/HttpServer.cpp`
#### 5.1.1 类型别名
```cpp
using HttpRequestHandler = std::function<http::response<http::string_body>(
const http::request<http::string_body>& req,
const std::unordered_map<std::string, std::string>& params)>;
using MiddlewareHandler = std::function<bool(
const http::request<http::string_body>& req,
http::response<http::string_body>& res,
const std::unordered_map<std::string, std::string>& params)>;
```
- **`HttpRequestHandler`**: 路由处理函数,接收 Boost.Beast 请求对象和已解析的查询参数,返回 HTTP 响应。
- **`MiddlewareHandler`**: 中间件函数,返回 `false` 表示拦截(短路后续处理),`true` 表示放行。
#### 5.1.2 构造函数
| 签名 | 说明 |
|----------------------------------------------------------------------------------------|------------------------|
| `HttpServer(address, port, threads = 1)` | 纯 HTTP 模式 |
| `HttpServer(address, port, cert_file, key_file, threads = 1)` | HTTPS 模式(自动启用 SSL) |
- `address`: 监听地址,如 `"0.0.0.0"`
- `port`: 监听端口
- `threads`: 工作线程数(即 `io_context` 线程池大小)
- `cert_file` / `key_file`: PEM 格式证书和私钥路径
#### 5.1.3 路由注册
```cpp
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 options(const std::string& path, HttpRequestHandler handler);
void use(MiddlewareHandler middleware);
```
- 路径支持通配符:`*` (任意字符) 和 `?` (单个字符),自动编译为 `std::regex`(大小写不敏感)。
- 匹配优先级:精确匹配 > 通配符匹配。
- 请求方法字符串对应:`"GET"`, `"POST"`, `"PUT"`, `"DELETE"`, `"OPTIONS"`。
#### 5.1.4 中间件
```cpp
void use(MiddlewareHandler middleware);
```
中间件在路由匹配之前执行,按注册顺序依次调用。任意中间件返回 `false` 时,请求处理结束,响应直接返回客户端。
#### 5.1.5 静态文件服务
```cpp
void setStaticDirectory(const std::string& path);
```
设置静态资源根目录。当请求路径未匹配任何路由时,自动尝试从该目录读取文件。支持 MIME 类型自动识别:`.html`, `.css`, `.js`, `.json`, `.png`, `.jpg/.jpeg`。
#### 5.1.6 OPTIONS 预检请求
当收到 `OPTIONS` 请求时:
1. 优先查找自定义 `OPTIONS` 路由处理器
2. 若未注册自定义处理器且请求包含 `Origin` 头,自动生成 CORS 响应
- 自动检测该路径下已注册的方法(GET/POST/PUT/DELETE/OPTIONS)
- 返回 `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`, `Access-Control-Max-Age`
#### 5.1.7 SSL 配置
```cpp
void setPrivateKeyPassword(const std::string& password);
```
- SSL 上下文使用 TLS 协议,禁用 SSLv2/SSLv3
- 仅允许 ECDHE/DHE 密钥交换的强密码套件:`ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS`
- 支持 PEM 加密私钥密码
#### 5.1.8 生命周期
```cpp
void start(); // 启动服务器(非阻塞,线程池跑 io_context)
void stop(); // 停止服务器(关闭所有会话,join 线程)
void run(); // 启动并在当前线程运行 io_context(阻塞)
bool isRunning() const;
```
#### 5.1.9 调试
```cpp
void showStatus() const; // 打印服务器状态和已注册路由
```
内嵌类 `HttpServer::debug` 提供 `print_all_header_fields()` 静态方法,打印请求的全部头字段。
#### 5.1.10 内部架构
```
HttpServer
├── Listener (内部类)
│ └── acceptor_ (tcp::acceptor)
│ └── async_accept → Session 实例
└── Session (内部类,enable_shared_from_this)
├── HTTP: socket_ (tcp::socket)
├── HTTPS: ssl_stream_ (asio::ssl::stream<tcp::socket>)
├── strand_ 保证线程安全
└── 读取 → handleRequest() → 写入 → [keep-alive 则继续读取]
```
---
### 5.2 HttpClient — HTTP 客户端
**头文件**: `<us/HttpClient.hpp>` (header-only,无独立 .cpp)
**依赖**: libcurl
#### 5.2.1 枚举 `HttpMethod`
```cpp
enum class HttpMethod { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS };
```
#### 5.2.2 公共方法
```cpp
HttpClient(); // 初始化 CURL 句柄,失败抛 runtime_error
~HttpClient(); // 清理 CURL 和 header 链表
void set_header(const std::string& header);
void clear_headers();
const std::map<std::string, std::string>& get_response_headers() const;
long get_response_code() const;
```
流式请求(通过回调函数接收响应体,保留原始字节):
```cpp
void request_stream_sync(HttpMethod method, const std::string& url,
const std::string& data,
std::function<void(const std::string&)> callback);
// 便捷方法
void get_stream_sync(const std::string& url, std::function<void(const std::string&)> callback);
void post_stream_sync(const std::string& url, const std::string& data,
std::function<void(const std::string&)> callback);
void put_stream_sync(const std::string& url, const std::string& data,
std::function<void(const std::string&)> callback);
void delete_stream_sync(const std::string& url, std::function<void(const std::string&)> callback);
```
**特点**:
- 同步阻塞调用
- 响应体通过 `CURLOPT_WRITEFUNCTION` 直接追加到 `std::string`,**保留所有换行符和特殊字符**
- 超时设置:连接超时 10 秒,总超时 30 秒
- 每次请求调用 `curl_easy_reset` 重置状态
---
### 5.3 WebSocket 支持
WebSocket 能力已内置于 `HttpServer`(单端口同时服务 HTTP + WebSocket),通过 `ws()`/`ws_proxy()` 注册路由;客户端通过 `HttpClient` 的 `ws_*` 方法提供(`using WebSocketClient = HttpClient`)。原独立 `WebSocketServer`/`WebSocketClient` 类已删除(未构建的历史遗留,见安全审计 P3-1)。
---
## 6. 使用示例
### 6.1 基本 HTTP 服务器
```cpp
#include <us/HttpServer.hpp>
#include <iostream>
int main() {
pmc::net::HttpServer server("0.0.0.0", 8080, 4);
server.get("/hello", [](const auto& req, const auto& params) {
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::content_type, "text/plain");
res.body() = "Hello, world!";
res.prepare_payload();
return res;
});
server.post("/echo", [](const auto& req, const auto& params) {
http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::content_type, "application/json");
res.body() = req.body();
res.prepare_payload();
return res;
});
server.start(); // 非阻塞启动
std::cout << "Server running on port " << server.getPort() << std::endl;
server.isRunning(); // 阻塞直到手动 stop()
}
```
### 6.2 HTTPS 服务器
```cpp
pmc::net::HttpServer server("0.0.0.0", 443,
"/etc/ssl/certs/server.crt",
"/etc/ssl/private/server.key",
4);
server.setPrivateKeyPassword("mysecret");
server.start();
```
### 6.3 中间件 + 静态文件
```cpp
pmc::net::HttpServer server("0.0.0.0", 8080);
// 日志中间件
server.use([](const auto& req, auto&, const auto&) {
std::cout << "[" << req.method_string() << "] " << req.target() << std::endl;
return true; // 继续处理
});
// 认证中间件
server.use([](const auto& req, auto& res, const auto&) {
auto auth = req.find(http::field::authorization);
if (auth == req.end()) {
res.result(http::status::unauthorized);
res.body() = "Unauthorized";
res.prepare_payload();
return false; // 拦截
}
return true;
});
server.setStaticDirectory("/var/www/html");
server.start();
```
### 6.4 HTTP 客户端
```cpp
pmc::net::HttpClient client;
client.set_header("Authorization: Bearer token123");
client.set_header("Content-Type: application/json");
client.post_stream_sync("https://api.example.com/data",
R"({"key":"value"})",
[](const std::string& body) {
std::cout << "Response: " << body << std::endl;
});
long status = client.get_response_code();
auto& headers = client.get_response_headers();
```
### 6.5 WebSocket 服务器
```cpp
pmc::net::HttpServer server("0.0.0.0", 9000, 4);
server.onWsConnect([](const std::string& clientId, const std::string& addr) {
std::cout << "Client connected: " << clientId << " from " << addr << std::endl;
});
server.ws("/chat", [](const std::string& clientId,
const std::string& message,
auto sendCallback) {
std::cout << "[" << clientId << "]: " << message << std::endl;
sendCallback("echo: " + message);
});
server.onWsDisconnect([](const std::string& clientId) {
std::cout << "Client disconnected: " << clientId << std::endl;
});
server.start();
```
### 6.6 WebSocket 客户端
```cpp
pmc::net::HttpClient ws_client;
if (ws_client.ws_connect("ws://localhost:9000/chat")) {
ws_client.ws_send("hello");
std::string reply = ws_client.ws_recv();
std::cout << "Received: " << reply << std::endl;
ws_client.ws_close();
}
```
---
## 7. 请求处理流程
```
客户端 → TCP 连接
├─ HTTP: Session::doRead()
└─ HTTPS: Session::doSSLHandshake() → Session::doRead()
↓
HttpServer::handleRequest(req)
├─ 解析 target_path + query params
├─ 执行中间件链 (MiddlewareHandler[])
│ └─ 任意中间件返回 false → 短路返回
├─ OPTIONS 请求?
│ ├─ 有自定义处理器 → 执行
│ └─ 有 Origin → 自动生成 CORS 响应
├─ 路由匹配 (精确匹配 → 通配符匹配)
│ └─ 命中 → 执行 HttpRequestHandler
├─ 路由未命中 + static_directory_ 非空
│ └─ 尝试读取静态文件 → 自动设 Content-Type
└─ 全部未命中 → 404
```
---
## 8. 设计要点
### 8.1 线程模型
- **HttpServer**: 每个工作线程独立运行 `io_context::run()`,所有异步操作通过 `strand` 序列化,同一 Session 的读写不会并发冲突
- **HttpClient**: 同步阻塞,无额外线程(HTTP 经 libcurl;WebSocket 经 Beast)
### 8.2 会话管理
- `HttpServer` 通过 `weak_ptr<Session>` 跟踪所有活跃会话,`stop()` 时遍历清理
### 8.3 发送队列
`HttpServer::Session` 的 `ws_send_queue_` 与代理队列 (`proxy_to_backend_queue_`/`proxy_to_client_queue_`) 采用 `std::deque` 消息队列,确保消息按序发送且不会因并发写入冲突。
---
## 9. 安全特性
| 特性 | 实现位置 |
|---------------------------|------------------------------|
| SSL/TLS 加密 | HttpServer SSL 构造函数 |
| 禁止 SSLv2/SSLv3 | `initSSLContext()` |
| 仅强密码套件 (ECDHE/DHE) | `initSSLContext()` 密码列表 |
| 禁用 NULL/MD5/DSS | `initSSLContext()` 密码列表 |
| 加密私钥密码支持 | `setPrivateKeyPassword()` |
| CORS 自动处理 | `handleRequest()` OPTIONS 分支 |
---
## 10. 与其他模块的关系
```
micro_servers/
├── gateway/ ──依赖──→ libhttp (HttpServer 接收外部请求)
├── core/ ──依赖──→ libhttp (HTTP 服务基础能力)
└── libhttp/ ──依赖──→ Boost, OpenSSL, libcurl
```
- **gateway** 使用 `pmc::net::HttpServer` 作为 API 网关入口,处理 JWT 认证、令牌刷新、请求转发
- **core** 模块使用 libhttp 提供的 HTTP 服务能力
---
## 11. 已知限制
1. **无 HTTP/2 支持**:仅 HTTP/1.1
2. **静态文件 MIME 有限**:仅支持 6 种常见类型
3. **无请求体大小限制**:依赖 Boost.Beast 默认配置(见安全审计 P1-1)
4. **HttpClient 仅同步**:无异步 HTTP 客户端实现
```