Networking
The Networking module provides a complete set of networking primitives: TCP/UDP clients and servers, an HTTP client and server with Express-style routing, WebSocket client with auto-reconnect, multipart form-data parsing, and TLS support via pluggable backends.
Overview
All networking in xtils is event-driven and non-blocking, built on the TaskRunner event loop. Callbacks fire on the event loop thread, making the programming model simple and predictable — no data races by default.
#include <xtils/net/http_router.h>
#include <xtils/net/http_server.h>
#include <xtils/tasks/thread_task_runner.h>
auto runner = ThreadTaskRunner::CreateAndStart("server");
HttpRouter router;
router.Get("/api/hello", [](const HttpRequestContext& ctx, HttpResponse& res) {
res.Status(200).Json("{\"message\":\"Hello!\"}");
});
HttpServer server(runner.get(), &router);
server.Start("0.0.0.0", 8080);TCP Client
#include "xtils/net/tcp_client.h"Event Listener
class TcpClientEventListener {
public:
virtual void OnConnected(bool success) = 0;
virtual void OnDataReceived(const void* data, size_t len) = 0;
virtual void OnDisconnected() = 0;
};API
TcpClient(TaskRunner* runner, TcpClientEventListener* listener);
bool Connect(const std::string& address, uint16_t port);
bool ConnectToHost(const std::string& hostname, uint16_t port); // DNS resolution
void Disconnect();
bool Send(const void* data, size_t len);
bool SendString(const std::string& data);
bool IsConnected() const;
void SetKeepAlive(bool enable);
void SetNoDelay(bool enable);Example
class MyClient : public TcpClientEventListener {
public:
void OnConnected(bool success) override {
if (success) {
LogI("Connected!");
client_->SendString("HELLO\n");
}
}
void OnDataReceived(const void* data, size_t len) override {
std::string msg(static_cast<const char*>(data), len);
LogI("Received: %s", msg.c_str());
}
void OnDisconnected() override {
LogI("Disconnected");
}
TcpClient* client_;
};
MyClient listener;
TcpClient client(&runner, &listener);
listener.client_ = &client;
client.ConnectToHost("example.com", 9000);TCP Server
#include "xtils/net/tcp_server.h"Event Listener
class TcpServerEventListener {
public:
virtual void OnClientConnected(TcpServerConnection* conn) = 0;
virtual void OnDataReceived(TcpServerConnection* conn, const void* data, size_t len) = 0;
virtual void OnClientDisconnected(TcpServerConnection* conn) = 0;
};API
TcpServer(TaskRunner* runner, TcpServerEventListener* listener);
bool Start(const std::string& address, uint16_t port);
bool StartDualStack(uint16_t port); // IPv4 + IPv6
void Stop();
void Broadcast(const void* data, size_t len);
size_t GetConnectionCount() const;Example: Echo Server
class EchoServer : public TcpServerEventListener {
void OnClientConnected(TcpServerConnection* conn) override {
LogI("Client connected: %s", conn->GetRemoteAddress().c_str());
}
void OnDataReceived(TcpServerConnection* conn, const void* data, size_t len) override {
// Echo back
conn->Send(data, len);
}
void OnClientDisconnected(TcpServerConnection* conn) override {
LogI("Client disconnected");
}
};
EchoServer handler;
TcpServer server(&runner, &handler);
server.Start("0.0.0.0", 9000);UDP Client
#include "xtils/net/udp_client.h"
UdpClient(TaskRunner* runner, UdpClientEventListener* listener);
bool Open(const std::string& local_address = "", uint16_t local_port = 0);
bool SendTo(const std::string& server_addr, const void* data, size_t len);
bool Send(const void* data, size_t len);
void Close();
void SetBroadcast(bool enable);
bool JoinMulticastGroup(const std::string& group, const std::string& interface = "");UDP Server
#include "xtils/net/udp_server.h"
UdpServer(TaskRunner* runner, UdpServerEventListener* listener);
bool Start(const std::string& address, uint16_t port);
bool StartDualStack(uint16_t port);
bool SendTo(const std::string& client_addr, const void* data, size_t len);
void Broadcast(const void* data, size_t len);
void SetClientTimeout(uint32_t timeout_ms);HTTP Client
#include "xtils/net/http_client.h"Synchronous API
HttpClient client(&runner);
HttpResponse res = client.Get("https://api.example.com/data");
HttpResponse res = client.Post(url, body, "application/json");
HttpResponse res = client.PostJson(url, json_string);
HttpResponse res = client.PostForm(url, {{"key", "value"}});
HttpResponse res = client.PostMultipart(url, fields, files);
HttpResponse res = client.Request(custom_request);Asynchronous API
client.GetAsync(url, [](const HttpResponse& res) {
LogI("Status: %d, Body: %s", res.status_code, res.body.c_str());
});
client.PostJsonAsync(url, json, [](const HttpResponse& res) { /* ... */ });
client.PostMultipartAsync(url, fields, files, [](const HttpResponse& res) { /* ... */ });
client.RequestAsync(request, [](const HttpResponse& res) { /* ... */ });Configuration
client.SetTimeout(10000); // 10 second timeout
client.SetFollowRedirects(true, 5); // Follow up to 5 redirects
client.SetKeepAlive(true); // Reuse connections
client.SetVerifySSL(true); // Verify TLS certificates
client.SetSSLCertificate("/path/to/ca.pem"); // Custom CA bundle
client.SetCookie("session", "abc123", ".example.com");Example: REST Client
HttpClient client(&runner);
client.SetTimeout(5000);
client.SetFollowRedirects(true);
// GET with query parameters
auto res = client.Get("https://api.example.com/users?page=1&limit=10");
if (res.status_code == 200) {
auto users = Json::parse(res.body);
LogI("Got %d users", (int)users->as_array().size());
}
// POST JSON
Json payload;
payload["name"] = "Alice";
payload["email"] = "alice@example.com";
auto create_res = client.PostJson("https://api.example.com/users", payload.dump());HTTP Server (Low-Level)
#include "xtils/net/http_server.h"Request Handler Interface
class HttpRequestHandler {
public:
virtual void OnHttpRequest(const HttpRequest& request) = 0;
virtual void OnWebsocketMessage(const WebsocketMessage& msg) {}
virtual void OnHttpConnectionClosed(HttpServerConnection* conn) {}
};Server API
HttpServer(TaskRunner* runner, HttpRequestHandler* handler);
bool Start(const std::string& ip, uint16_t port);
void Stop();
void AddAllowedOrigin(const std::string& origin);Connection Methods
conn->SendResponse(http_code, headers, content, force_close);
conn->UpgradeToWebsocket(request);
conn->SendWebsocketMessage(data, len);
bool conn->SendFileStreaming(file_path, http_code, headers); // Chunked file deliveryHTTP Router (Express-Style)
The router provides familiar Express.js-style routing with URL parameters, query strings, middleware, and static file serving.
#include "xtils/net/http_router.h"Route Registration
HttpRouter router;
router.Get("/api/users", handler);
router.Post("/api/users", handler);
router.Put("/api/users/:id", handler);
router.Delete("/api/users/:id", handler);
router.Any("/api/*", handler); // Match any method, wildcard pathHandler Signature
void handler(const HttpRequestContext& ctx, HttpResponse& res) {
// URL parameters (from :param patterns)
auto id = ctx.GetParam("id");
// Query parameters
auto search = ctx.GetQuery("q");
auto page = ctx.GetQuery("page");
// Request body
auto body = ctx.GetBody();
// Headers
auto auth = ctx.GetHeader("Authorization");
// Multipart form data (lazy parsed)
auto& fields = ctx.GetMultipartFields();
auto& files = ctx.GetMultipartFiles();
// Send response
res.Status(200).Json("{\"ok\":true}");
res.Status(201).Text("Created");
res.Status(404).Html("<h1>Not Found</h1>");
}Middleware
// Global middleware (runs for all routes)
router.Use([](const HttpRequestContext& ctx, HttpResponse& res) -> bool {
LogI("%s %s", ctx.GetMethod().c_str(), ctx.GetPath().c_str());
return true; // Continue to next handler (false = abort)
});
// Path-scoped middleware
router.Use("/api", [](const HttpRequestContext& ctx, HttpResponse& res) -> bool {
auto token = ctx.GetHeader("Authorization");
if (token.empty()) {
res.Status(401).Json("{\"error\":\"unauthorized\"}");
return false; // Stop chain
}
return true;
});Static File Serving
// Serve ./public directory at /static path
router.Static("/static", "./public");
// Serve SPA (fallback to index.html)
router.Static("/", "./dist");Route Groups
auto api = router.Group("/api/v1");
api.Get("/users", listUsers);
api.Post("/users", createUser);
api.Get("/users/:id", getUser);
api.Put("/users/:id", updateUser);
api.Delete("/users/:id", deleteUser);CORS
router.EnableCors("*", "GET,POST,PUT,DELETE,OPTIONS");
// Or specific origins:
router.EnableCors("https://example.com", "GET,POST");WebSocket Client
#include "xtils/net/websocket_client.h"Event Listener
class WebSocketClientEventListener {
public:
virtual void OnWebSocketConnected(WebSocketClient* client) = 0;
virtual void OnWebSocketMessage(WebSocketClient* client, const WebSocketMessage& msg) = 0;
virtual void OnWebSocketClosed(WebSocketClient* client, uint16_t code, const std::string& reason) = 0;
virtual void OnWebSocketError(WebSocketClient* client, const std::string& error) = 0;
};API
WebSocketClient(TaskRunner* runner, WebSocketClientEventListener* listener = nullptr);
bool Connect(const std::string& url);
bool Connect(const std::string& url, const std::map<std::string, std::string>& headers);
bool Connect(const std::string& url, const std::map<std::string, std::string>& headers,
const std::vector<std::string>& protocols);
bool SendText(const std::string& text);
bool SendBinary(const void* data, size_t len);
bool SendPing(const std::string& data = "");
void Close(uint16_t code = 1000, const std::string& reason = "");Configuration
client.SetAutoReconnect(true, 3000); // Auto-reconnect with 3s delay
client.SetPingInterval(30000); // Send ping every 30s
client.SetMaxMessageSize(1024 * 1024); // 1MB max message
client.SetVerifySSL(true); // Verify TLS for wss://Example: WebSocket with Auto-Reconnect
class WsHandler : public WebSocketClientEventListener {
public:
void OnWebSocketConnected(WebSocketClient* client) override {
LogI("WS connected!");
client->SendText("{\"type\":\"subscribe\",\"channel\":\"updates\"}");
}
void OnWebSocketMessage(WebSocketClient* client, const WebSocketMessage& msg) override {
if (msg.is_text) {
auto json = Json::parse(msg.text);
LogI("Update: %s", json->dump().c_str());
}
}
void OnWebSocketClosed(WebSocketClient* client, uint16_t code, const std::string& reason) override {
LogW("WS closed: %d %s", code, reason.c_str());
}
void OnWebSocketError(WebSocketClient* client, const std::string& error) override {
LogE("WS error: %s", error.c_str());
}
};
WsHandler handler;
WebSocketClient ws(&runner, &handler);
ws.SetAutoReconnect(true, 5000);
ws.Connect("wss://api.example.com/ws");Multipart Parser
For handling file uploads and form data:
#include "xtils/net/http_multipart.h"
struct MultipartFormField {
std::string name;
std::string value;
};
struct MultipartFormFile {
std::string field_name; // Form field name
std::string filename; // Original filename
std::string content_type; // MIME type
std::string content; // File content (binary-safe)
};Direct Usage
auto boundary = MultipartParser::ExtractBoundary(content_type_header);
MultipartParser parser(body, boundary);
if (parser.Parse()) {
for (auto& field : parser.GetFields()) {
LogI("Field: %s = %s", field.name.c_str(), field.value.c_str());
}
for (auto& file : parser.GetFiles()) {
LogI("File: %s (%s, %zu bytes)", file.filename.c_str(),
file.content_type.c_str(), file.content.size());
}
}Via Router (Lazy Parsing)
router.Post("/upload", [](const HttpRequestContext& ctx, HttpResponse& res) {
auto& files = ctx.GetMultipartFiles(); // Parsed on first access
auto& fields = ctx.GetMultipartFields();
for (auto& file : files) {
save_file(file.filename, file.content);
}
res.Status(200).Json("{\"uploaded\":" + std::to_string(files.size()) + "}");
});TLS Factory
Backend-agnostic TLS support:
#include "xtils/net/transport/tls_factory.h"
// Create TLS context (cert/key configuration)
TlsContextPtr CreateTlsContext(const TlsCertConfig& cfg);
// Create TLS transport (wraps a plain TCP connection)
std::unique_ptr<Transport> CreateTlsTransport(TaskRunner* runner,
TransportEventListener* listener);The backend (OpenSSL or mbedTLS) is selected at compile time via TLS_BACKEND.
HTTP Utilities
#include "xtils/net/http_common.h"
// URL parsing
HttpUrl url = HttpUrl::Parse("https://example.com:8443/path?q=1");
// url.scheme = "https", url.host = "example.com", url.port = 8443, ...
// URL encoding
std::string encoded = HttpUtils::UrlEncode("hello world"); // "hello%20world"
std::string decoded = HttpUtils::UrlDecode("hello%20world");
// Form encoding
std::string form = HttpUtils::FormDataEncode({{"key", "value"}, {"foo", "bar"}});
// MIME types
std::string mime = HttpUtils::GetMimeType("json"); // "application/json"Putting It Together
A complete server with REST API, WebSocket, and static files:
#include <xtils/app/service.h>
#include <xtils/net/http_router.h>
#include <xtils/net/http_server.h>
#include <xtils/tasks/thread_task_runner.h>
#include <xtils/logging/logger.h>
using namespace xtils;
class WebService : public Service<WebService> {
public:
WebService() : Service("web") {}
void Init() override {
runner_ = ThreadTaskRunner::CreateAndStart("web_io");
router_ = std::make_unique<HttpRouter>();
// REST endpoints
router_->Get("/api/status", [this](const HttpRequestContext& ctx, HttpResponse& res) {
Json status;
status["uptime"] = GetUptime();
status["clients"] = ws_clients_;
res.Status(200).Json(status.dump());
});
// WebSocket endpoint for live updates
router_->Get("/ws", [this](const HttpRequestContext& ctx, HttpResponse& res) {
ws_clients_++;
res.Status(101); // Upgrade handled by server
});
// Static files
router_->Static("/", config.GetString("web_root").value_or("./public"));
router_->EnableCors("*", "GET,POST,OPTIONS");
server_ = std::make_unique<HttpServer>(runner_.get(), router_.get());
server_->Start("0.0.0.0", config.GetInt("port").value_or(8080));
}
void Deinit() override {
server_->Stop();
}
private:
ThreadTaskRunner runner_;
std::unique_ptr<HttpRouter> router_;
std::unique_ptr<HttpServer> server_;
int ws_clients_ = 0;
};Thread Safety
All network callbacks fire on the TaskRunner's thread (the thread that calls Run()). If you use ThreadTaskRunner, all callbacks for that server are serialized on its dedicated thread — no locks needed within handlers.