Files
cpp-httplib/docs-src/pages/en/cookbook/w06-websocket-timeouts.md
T
yhirose 6b8c3f5387 Report only a caller-set WebSocket read timeout as Timeout
199d7ee made read() return the new ReadResult::Timeout for every read
timeout and leave the connection open. The compile-time server default
(CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND, 300s) is always in
effect, so a handler written as `while (ws.read(msg))`, the form the
README's Quick Start uses, no longer ended when a peer went quiet:
Timeout is non-zero, so the loop ran its body again with the previous
message still in `msg`, and the worker the backstop is meant to reclaim
was never released. Nothing caught it because every test of the new
result set a timeout explicitly and checked the result by value, and the
heartbeat tests keep the connection alive with pings.

The two timeouts mean different things. One the caller sets through
set_read_timeout() is a request for control back, and is reported as
Timeout on a still-open connection. The compile-time default is a
backstop against a peer that has gone quiet, and elapsing it is now a
failure again: read() returns Fail and closes the connection, as it did
before 199d7ee. WebSocket tracks whether set_read_timeout() was called,
and WebSocketClient carries the same flag over to the WebSocket it
creates on connect().

Tests use the heartbeat binary, which compiles both defaults down to 3s:
a `while (ws.read(msg))` server handler runs its body once and exits
when the client falls silent, and a client that never set a timeout gets
Fail with the connection closed. The README and cookbook now say which
timeout produces Timeout.

Claude-Session: https://claude.ai/code/session_01EF5uZ1X2kaHhqJ8VgfjVaQ
2026-09-11 17:29:20 -04:00

3.7 KiB

title, order, status
title order status
W06. Set Timeouts 57 draft

ws::WebSocketClient has the same three kinds of timeouts as Client, with the same meaning.

Kind API Default
Connection set_connection_timeout 300s
Read set_read_timeout none — waits forever (CPPHTTPLIB_WEBSOCKET_CLIENT_READ_TIMEOUT_SECOND)
Write set_write_timeout 5s

Basic usage

httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");

ws.set_connection_timeout(5, 0);  // 5 seconds
ws.set_read_timeout(30, 0);       // 30 seconds
ws.set_write_timeout(10, 0);      // 10 seconds

if (ws.connect()) {
  ws.send("hello");
}

Set the connection and write timeouts before calling connect(). The read timeout can be changed at any time — setting it on an open connection takes effect on the next read().

Use std::chrono

Just like Client, there's an overload that takes a std::chrono duration directly.

using namespace std::chrono_literals;

ws.set_connection_timeout(5s);
ws.set_read_timeout(30s);
ws.set_write_timeout(10s);

What the read timeout means

set_read_timeout() applies to a single read() call. If no message arrives within that time, read() returns ReadResult::Timeout: the connection is still open and nothing was consumed, so you can send on it and read again. That is what separates it from ReadResult::Fail, which means the connection is gone.

This is what lets one thread own a connection in both directions:

using namespace std::chrono_literals;

ws.set_read_timeout(100ms);
std::string msg;
while (ws.is_open()) {
  auto r = ws.read(msg);
  if (r == httplib::ws::Timeout) {
    flush_outgoing(ws);  // nothing arrived — send whatever is queued
    continue;
  }
  if (r == httplib::ws::Fail) { break; }
  handle(msg);
}

Without a read timeout, read() blocks until a message arrives, so the thread holding the connection never gets to its writes.

Two things to know about Timeout:

  • It leaves msg untouched, and it is non-zero. So while (ws.read(msg)) is not usable once a read timeout is set — the loop would keep running with the previous message still in msg.
  • It is only reported on a message boundary. If the timeout elapses partway through a fragmented message, that message cannot be resumed and read() returns Fail.

For connections where long idle periods are normal — waiting on notifications, for example — either leave the read timeout unset, or treat Timeout as the no-op it is and keep looping.

On the server side

A handler's ws::WebSocket has set_read_timeout() too, and the pattern above is how a handler relays between connections instead of parking in read().

The server default is 300s (CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND) rather than "forever": it is a backstop that reclaims a worker from a peer that has gone silent, since a WebSocket handler holds its worker for the life of the connection.

Because it is a backstop rather than something the handler asked for, it does not surface as Timeout. When it elapses, read() returns Fail and closes the connection, so a handler written as while (ws.read(msg)) ends the way it always has. Only a timeout the handler set itself with set_read_timeout() comes back as Timeout.

Unresponsive-peer detection via Ping/Pong is a separate mechanism. See W02. Set a WebSocket Heartbeat for details.

How this differs from Client

For Client's timeout configuration, see C12. Set Timeouts. The behavior and API are nearly identical, but WebSocketClient has no equivalent to set_max_timeout() for capping the whole request — once connected, the connection stays open for as long as you keep calling read().