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
- WebSocket Reverse Proxy: Transparent bidirectional WebSocket proxy with handshake-time auth/callback (supports ws:// and wss:// backends)
- 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::strandfor 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/WebSocketClientBaseare aliases forHttpClient
Dependencies
- C++20 compiler
- Boost.Beast / Boost.Asio (header-only)
- OpenSSL (for TLS)
- libcurl (for HTTP client)
- pthread
Build
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
#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
#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
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
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
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
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;
WebSocket Reverse Proxy
void ws_proxy(const std::string& path, WsProxyHandshakeHandler handshake);
The WsProxyHandshakeHandler is called during the HTTP Upgrade handshake. It receives the upgrade request and must return the backend WebSocket URL (ws://host:port/path or wss://host:port/path) to bridge to. Returning an empty string rejects the connection (HTTP 403 is sent to the client). Proxy routes take priority over regular ws() routes on the same path.
Example — admin-only proxy:
server.ws_proxy("/ws/admin", [](const http::request<http::string_body>& req) -> std::string {
if (!isAdmin(req)) return ""; // reject non-admins
return "ws://127.0.0.1:9000/stream"; // bridge to backend
});
Features:
- Full-duplex async bridging (text and binary frames preserved)
- Write queues in both directions to maintain message ordering
- Automatic SNI for wss:// backends
- Graceful close propagation between client and backend
- Errors during backend connect/handshake close the client connection cleanly
Lifecycle
void start(); // non-blocking, spawns worker threads
void run(); // blocking (calls start() then ioc.run())
void stop();
bool isRunning() const;
HttpClient
HTTP Methods
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
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
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)>;
using WsProxyHandshakeHandler = std::function<std::string(
const http::request<http::string_body>& upgradeReq)>;
Backward Compatibility
The following type aliases are provided for existing code that used the standalone WebSocket client:
using WebSocketClient = HttpClient;
using WebSocketClientBase = HttpClient;
Version History
- v3.1.0: Added WebSocket reverse proxy (
ws_proxy()) with bidirectional async bridging, ws:// and wss:// backend support, handshake-time authorization callback. - v3.0.0: Merged WebSocketServer into HttpServer and WebSocketClient into HttpClient. Single-port HTTP+WebSocket support.
- v2.x: Separate HttpServer, WebSocketServer, and HttpClient modules.