feat(libhttp): integrate WebSocket client into HttpClient

Merge WebSocket client functionality into HttpClient using Boost.Beast,
enabling a single client instance to handle both HTTP (libcurl) and
WebSocket (ws:// and wss://) protocols. Backward-compatible type aliases
(WebSocketClient, WebSocketClientBase) preserve existing code.

Changes:
- Add synchronous WebSocket client: ws_connect/ws_send/ws_recv/ws_close
- Support ws:// (plain TCP) and wss:// (TLS with SNI) connections
- Layered stream architecture with beast::tcp_stream for timeout support
- Binary message support via ws_send_binary()
- Update Makefile to link libcurl (-lcurl)
- Add README.md documenting unified HttpServer + HttpClient API
This commit is contained in:
qing committed 2026-08-01 04:59:47 +08:00
1 parent 5edb67b46d
commit 9815ff44af
3 files changed
+504 -20

No files matched your search

+231
View File
@@ -0,0 +1,231 @@
# libhttp - UniServe HTTP/WebSocket Library
C++20 network library for the UniServe microservice framework, providing unified HTTP/HTTPS server and HTTP/WebSocket client capabilities built on Boost.Beast and libcurl.
## Features
### HttpServer
- **HTTP/HTTPS Server**: Asynchronous HTTP 1.1 server with TLS support
- **WebSocket Integration**: Single port can serve both HTTP(S) and WebSocket(S) endpoints
- **Routing**: Exact path matching + wildcard (`*`) routing
- **Middleware**: `use()` for middleware chains (CORS, auth, logging, etc.)
- **Static Files**: `setStaticDirectory()` for serving static files
- **Thread Pool**: Multi-threaded I/O with `asio::strand` for thread safety
- **Client Management**: WebSocket client tracking, broadcast, and targeted send
### HttpClient
- **HTTP Client**: Synchronous HTTP requests via libcurl (GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS)
- **WebSocket Client**: Synchronous WebSocket client via Boost.Beast
- **Dual Protocol**: Same client instance handles both HTTP and WebSocket
- **ws:// and wss://**: Supports both plain and TLS-encrypted WebSocket connections
- **Binary Support**: Text and binary message sending
- **Backward Compatible**: Original API preserved; `WebSocketClient`/`WebSocketClientBase` are aliases for `HttpClient`
## Dependencies
- C++20 compiler
- Boost.Beast / Boost.Asio (header-only)
- OpenSSL (for TLS)
- libcurl (for HTTP client)
- pthread
## Build
```bash
make # builds build/libus_http.so
make install # installs to /usr/lib and /usr/include/us (requires root)
```
Makefile links: `-lssl -lcrypto -lcurl -lpthread` with Homebrew paths for macOS.
## Quick Start
### HTTP + WebSocket Server
```cpp
#include <us/HttpServer.hpp>
using namespace pmc::net;
int main() {
// HTTP server on port 8080
HttpServer server("0.0.0.0", 8080, 4); // 4 threads
// HTTP GET route
server.get("/hello", [](const auto& req, const auto& params) {
http::response<http::string_body> res;
res.result(http::status::ok);
res.set(http::field::content_type, "text/plain");
res.body() = "Hello, World!";
res.prepare_payload();
return res;
});
// WebSocket route
server.ws("/ws", [](const std::string& clientId,
const std::string& message,
std::function<void(const std::string&)> send) {
send("Echo: " + message);
});
server.onWsConnect([](const std::string& id, const std::string& addr) {
std::cout << "Client " << id << " connected from " << addr << std::endl;
});
server.onWsDisconnect([](const std::string& id) {
std::cout << "Client " << id << " disconnected" << std::endl;
});
server.start();
// server.run(); // blocks with ioc.run()
return 0;
}
```
### HTTP + WebSocket Client
```cpp
#include <us/HttpClient.hpp>
using namespace pmc::net;
// HTTP request
HttpClient http;
http.set_header("Content-Type: application/json");
http.post_stream_sync("http://localhost:8080/api/data", "{}",
[](const std::string& resp) {
std::cout << "Response: " << resp << std::endl;
});
// WebSocket connection
HttpClient ws;
if (ws.ws_connect("ws://localhost:8080/ws")) {
ws.ws_send("Hello WebSocket!");
std::string reply = ws.ws_recv();
std::cout << "Got: " << reply << std::endl;
ws.ws_close();
}
// WSS (TLS) connection
HttpClient wss;
wss.ws_connect("wss://example.com/socket");
```
### HTTPS Server
```cpp
HttpServer secure("0.0.0.0", 443, "/path/to/cert.pem", "/path/to/key.pem", 4);
secure.get("/", handler);
secure.start();
```
## API Reference
### HttpServer
**Constructors**
```cpp
HttpServer(const std::string& address, unsigned short port, unsigned int threads = 1);
HttpServer(const std::string& address, unsigned short port,
const std::string& cert_file, const std::string& key_file,
unsigned int threads = 1);
```
**HTTP Routes**
```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);
void setStaticDirectory(const std::string& path);
```
**WebSocket Routes**
```cpp
void ws(const std::string& path, WebSocketMessageHandler handler);
void onWsConnect(WebSocketConnectHandler handler);
void onWsDisconnect(WebSocketDisconnectHandler handler);
bool sendToWsClient(const std::string& clientId, const std::string& message);
size_t broadcastWs(const std::string& message);
void disconnectWsClient(const std::string& clientId);
size_t getWsClientCount() const;
```
**Lifecycle**
```cpp
void start(); // non-blocking, spawns worker threads
void run(); // blocking (calls start() then ioc.run())
void stop();
bool isRunning() const;
```
### HttpClient
**HTTP Methods**
```cpp
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;
void get_stream_sync(const std::string& url, ResponseCallback cb);
void post_stream_sync(const std::string& url, const std::string& data, ResponseCallback cb);
void put_stream_sync(const std::string& url, const std::string& data, ResponseCallback cb);
void delete_stream_sync(const std::string& url, ResponseCallback cb);
void request_stream_sync(HttpMethod method, const std::string& url,
const std::string& data, ResponseCallback cb);
```
**WebSocket Methods**
```cpp
bool ws_connect(const std::string& url, int timeout_ms = 5000);
bool ws_connect(const std::string& host, const std::string& port,
const std::string& path = "/", bool ssl = false, int timeout_ms = 5000);
bool ws_send(const std::string& message);
bool ws_send_binary(const void* data, size_t size);
std::string ws_recv(int timeout_ms = 0);
bool ws_is_connected() const;
void ws_close();
std::string ws_get_host() const;
std::string ws_get_port() const;
std::string ws_get_path() const;
```
### Type Definitions
```cpp
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>&)>;
using WebSocketMessageHandler = std::function<void(
const std::string& clientId,
const std::string& message,
std::function<void(const std::string&)> sendCallback)>;
using WebSocketConnectHandler = std::function<void(
const std::string& clientId,
const std::string& remoteAddress)>;
using WebSocketDisconnectHandler = std::function<void(
const std::string& clientId)>;
```
## Backward Compatibility
The following type aliases are provided for existing code that used the standalone WebSocket client:
```cpp
using WebSocketClient = HttpClient;
using WebSocketClientBase = HttpClient;
```
## Version History
- **v3.0.0**: Merged WebSocketServer into HttpServer and WebSocketClient into HttpClient. Single-port HTTP+WebSocket support.
- **v2.x**: Separate HttpServer, WebSocketServer, and HttpClient modules.