Bring the READMEs and the docs pages (en and ja) in line with the current header: - Fix the client sample that does not compile, the Transfer-Encoding statement for default headers, the verifier callback comment, the thread pool spawn rule, the sanitize_filename description, and the inverted Accept-Encoding sentence. - Describe recent behaviour: TLS errors behind Read/Write failures, Host validation, Connection: close for a body without length, content_reader rules, refused CONNECT, WebSocketClient proxies, CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT, the SSE event size limit, and the payload limit of the Stream API. - Complete the API references of WebSocket, WebSocketClient and the stream:: helpers, and the SSE reconnect rules. - Fix cookbook pages: keychain loading on every backend, received Close status, a multipart sample that trusted the client's file name, an echo sample that sent binary as text, SNI, pre-request handler scope, stop() with WebSocket handlers, and the thread pool notes. - Restore the mutex in the LLM app's Chapter 5 code.
3.3 KiB
title, order, status
| title | order | status |
|---|---|---|
| W01. Implement a WebSocket Echo Server and Client | 52 | draft |
WebSocket is a protocol for two-way messaging between client and server. cpp-httplib provides APIs for both sides. Let's start with the simplest example: an echo server.
Server: echo server
#include <httplib.h>
int main() {
httplib::Server svr;
svr.WebSocket("/echo", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
std::string msg;
while (ws.is_open()) {
auto result = ws.read(msg);
if (result == httplib::ws::ReadResult::Fail) {
break;
}
// echo back what we received, in the same frame type
if (result == httplib::ws::ReadResult::Binary) {
ws.send(msg.data(), msg.size());
} else {
ws.send(msg);
}
}
});
svr.listen("0.0.0.0", 8080);
}
Register a WebSocket handler with svr.WebSocket(). By the time the handler runs, the WebSocket handshake is already complete. Inside the loop, just ws.read() and ws.send() to get a working echo. A binary message goes back through the data() + size() overload so it stays binary (more on that below).
The read() return value is a ReadResult enum:
ReadResult::Text: received a text messageReadResult::Binary: received a binary messageReadResult::Fail: error, or connection closedReadResult::Timeout: a read timeout you set withset_read_timeout()elapsed with nothing received; the connection is still open. The compile-time default timeout closes the connection and is reported asFailinstead — see W06. Set Timeouts
Client: talk to the echo server
#include <httplib.h>
int main() {
httplib::ws::WebSocketClient cli("ws://localhost:8080/echo");
if (!cli.connect()) {
std::cerr << "failed to connect" << std::endl;
return 1;
}
cli.send("Hello, WebSocket!");
std::string msg;
if (cli.read(msg) != httplib::ws::ReadResult::Fail) {
std::cout << "received: " << msg << std::endl;
}
cli.close();
}
Use a ws:// (plain) or wss:// (TLS) URL. Call connect() to do the handshake, then send() and read() work the same as on the server side.
Text vs. binary
send() has two overloads that let you choose the frame type.
ws.send("Hello"); // text frame
ws.send(binary_data, binary_data_size); // binary frame
The std::string overload sends as text; the const char* + size overload sends as binary. A bit subtle, but once you know it, it's intuitive. See W04. Send and receive binary frames for details.
Thread pool implications
A WebSocket handler holds its worker thread for the entire life of the connection — one connection per thread. The default pool already grows under load, up to 4× its base thread count. For many concurrent clients, raise that maximum.
svr.new_task_queue = [] {
return new httplib::ThreadPool(8, 128);
};
See S21. Configure the thread pool.
Note: To run WebSocket over HTTPS, use
httplib::SSLServerinstead ofhttplib::Server— the sameWebSocket()handler just works. On the client side, use awss://URL. For CA and client certificate configuration, see W05. Configure TLS for wss:// Connections.