mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-20 10:40:58 +00:00
read() collapsed every failure into Fail and marked the connection closed with it, so a read timeout could not be used to get control back and send on the same connection -- it killed the connection instead. The information was already there and thrown away: SocketStream::read records Error::Timeout, and read_websocket_frame flattened it into a bool. ReadResult gains Timeout, reported only when the timeout elapsed on a frame boundary with nothing consumed, which is the only case where the stream can be read again. Every multi-byte field now loops until it has its bytes, which also fixes a frame header straddling the read buffer's boundary failing the frame: Stream::read is allowed to return less than asked for, and only the payload was reading in a loop. ws::WebSocket::set_read_timeout() lets a server handler bound its own reads, and WebSocketClient::set_read_timeout() now reaches an already-open connection instead of only seeding the next connect(). The read timeout macro splits in two. A client waits forever by default -- a read timeout is the caller's tool for taking back control, not a liveness check, which is ping/pong's job -- while a server keeps the 300s that reclaims a worker from a peer gone quiet. Defining the old name still sets both. Also record a reason on the two WebSocketSSLStream::read failure paths that returned -1 without one, so get_error() cannot report a previous call's timeout, and make SocketStream's read timeout atomic now that it can be changed while a read is in flight.
85 lines
3.4 KiB
Markdown
85 lines
3.4 KiB
Markdown
---
|
|
title: "W06. Set Timeouts"
|
|
order: 57
|
|
status: "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
|
|
|
|
```cpp
|
|
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.
|
|
|
|
```cpp
|
|
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:
|
|
|
|
```cpp
|
|
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.
|
|
|
|
> Unresponsive-peer detection via Ping/Pong is a separate mechanism. See [W02. Set a WebSocket Heartbeat](../w02-websocket-ping) for details.
|
|
|
|
## How this differs from `Client`
|
|
|
|
For `Client`'s timeout configuration, see [C12. Set Timeouts](../c12-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()`.
|