mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-17 10:59:39 +00:00
Enhance WebSocket support with unresponsive-peer detection and documentation updates
- Added `set_websocket_max_missed_pongs` method to configure unresponsive-peer detection. - Updated README and documentation to clarify WebSocket limitations and features. - Introduced tests for detecting non-responsive peers and ensuring responsive peers do not trigger timeouts.
This commit is contained in:
1 parent
b4eec3ee77
commit
6bdd657713
6 files changed
+199
-12
No files matched your search
@@ -205,6 +205,10 @@
|
||||
#define CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND 30
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS
|
||||
#define CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Headers
|
||||
*/
|
||||
@@ -1720,6 +1724,8 @@ public:
|
||||
Server &set_websocket_ping_interval(
|
||||
const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
Server &set_websocket_max_missed_pongs(int count);
|
||||
|
||||
bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
|
||||
int bind_to_any_port(const std::string &host, int socket_flags = 0);
|
||||
bool listen_after_bind();
|
||||
@@ -1756,6 +1762,7 @@ protected:
|
||||
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
|
||||
time_t websocket_ping_interval_sec_ =
|
||||
CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND;
|
||||
int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;
|
||||
|
||||
private:
|
||||
using Handlers =
|
||||
@@ -3728,17 +3735,21 @@ private:
|
||||
|
||||
WebSocket(
|
||||
Stream &strm, const Request &req, bool is_server,
|
||||
time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND)
|
||||
time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
|
||||
int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
|
||||
: strm_(strm), req_(req), is_server_(is_server),
|
||||
ping_interval_sec_(ping_interval_sec) {
|
||||
ping_interval_sec_(ping_interval_sec),
|
||||
max_missed_pongs_(max_missed_pongs) {
|
||||
start_heartbeat();
|
||||
}
|
||||
|
||||
WebSocket(
|
||||
std::unique_ptr<Stream> &&owned_strm, const Request &req, bool is_server,
|
||||
time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND)
|
||||
time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
|
||||
int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
|
||||
: strm_(*owned_strm), owned_strm_(std::move(owned_strm)), req_(req),
|
||||
is_server_(is_server), ping_interval_sec_(ping_interval_sec) {
|
||||
is_server_(is_server), ping_interval_sec_(ping_interval_sec),
|
||||
max_missed_pongs_(max_missed_pongs) {
|
||||
start_heartbeat();
|
||||
}
|
||||
|
||||
@@ -3750,6 +3761,8 @@ private:
|
||||
Request req_;
|
||||
bool is_server_;
|
||||
time_t ping_interval_sec_;
|
||||
int max_missed_pongs_;
|
||||
int unacked_pings_ = 0;
|
||||
std::atomic<bool> closed_{false};
|
||||
std::mutex write_mutex_;
|
||||
std::thread ping_thread_;
|
||||
@@ -3779,6 +3792,7 @@ public:
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
void set_websocket_ping_interval(time_t sec);
|
||||
void set_websocket_max_missed_pongs(int count);
|
||||
void set_tcp_nodelay(bool on);
|
||||
void set_address_family(int family);
|
||||
void set_ipv6_v6only(bool on);
|
||||
@@ -3810,6 +3824,7 @@ private:
|
||||
time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
|
||||
time_t websocket_ping_interval_sec_ =
|
||||
CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND;
|
||||
int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
|
||||
@@ -10912,6 +10927,11 @@ inline Server &Server::set_payload_max_length(size_t length) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_websocket_max_missed_pongs(int count) {
|
||||
websocket_max_missed_pongs_ = count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_websocket_ping_interval(time_t sec) {
|
||||
websocket_ping_interval_sec_ = sec;
|
||||
return *this;
|
||||
@@ -12050,7 +12070,8 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
{
|
||||
// Use WebSocket-specific read timeout instead of HTTP timeout
|
||||
strm.set_read_timeout(CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND, 0);
|
||||
ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_);
|
||||
ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_,
|
||||
websocket_max_missed_pongs_);
|
||||
entry.handler(req, ws);
|
||||
}
|
||||
return true;
|
||||
@@ -19700,7 +19721,11 @@ inline ReadResult WebSocket::read(std::string &msg) {
|
||||
payload.size(), true, !is_server_);
|
||||
continue;
|
||||
}
|
||||
case Opcode::Pong: continue;
|
||||
case Opcode::Pong: {
|
||||
std::lock_guard<std::mutex> lock(ping_mutex_);
|
||||
unacked_pings_ = 0;
|
||||
continue;
|
||||
}
|
||||
case Opcode::Close: {
|
||||
if (!closed_.exchange(true)) {
|
||||
// Echo close frame back
|
||||
@@ -19734,7 +19759,11 @@ inline ReadResult WebSocket::read(std::string &msg) {
|
||||
true, !is_server_);
|
||||
continue;
|
||||
}
|
||||
if (cont_opcode == Opcode::Pong) { continue; }
|
||||
if (cont_opcode == Opcode::Pong) {
|
||||
std::lock_guard<std::mutex> lock(ping_mutex_);
|
||||
unacked_pings_ = 0;
|
||||
continue;
|
||||
}
|
||||
if (cont_opcode == Opcode::Close) {
|
||||
if (!closed_.exchange(true)) {
|
||||
std::lock_guard<std::mutex> lock(write_mutex_);
|
||||
@@ -19822,12 +19851,22 @@ inline void WebSocket::start_heartbeat() {
|
||||
while (!closed_) {
|
||||
ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_));
|
||||
if (closed_) { break; }
|
||||
// If the peer has failed to respond to the previous pings, give up.
|
||||
// RFC 6455 does not define a pong-timeout mechanism; this is an
|
||||
// opt-in liveness check controlled by max_missed_pongs_.
|
||||
if (max_missed_pongs_ > 0 && unacked_pings_ >= max_missed_pongs_) {
|
||||
lock.unlock();
|
||||
close(CloseStatus::GoingAway, "pong timeout");
|
||||
return;
|
||||
}
|
||||
lock.unlock();
|
||||
if (!send_frame(Opcode::Ping, nullptr, 0)) {
|
||||
lock.lock();
|
||||
closed_ = true;
|
||||
break;
|
||||
}
|
||||
lock.lock();
|
||||
unacked_pings_++;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -19955,8 +19994,9 @@ inline bool WebSocketClient::connect() {
|
||||
Request req;
|
||||
req.method = "GET";
|
||||
req.path = path_;
|
||||
ws_ = std::unique_ptr<WebSocket>(
|
||||
new WebSocket(std::move(strm), req, false, websocket_ping_interval_sec_));
|
||||
ws_ = std::unique_ptr<WebSocket>(new WebSocket(std::move(strm), req, false,
|
||||
websocket_ping_interval_sec_,
|
||||
websocket_max_missed_pongs_));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -20000,6 +20040,10 @@ inline void WebSocketClient::set_websocket_ping_interval(time_t sec) {
|
||||
websocket_ping_interval_sec_ = sec;
|
||||
}
|
||||
|
||||
inline void WebSocketClient::set_websocket_max_missed_pongs(int count) {
|
||||
websocket_max_missed_pongs_ = count;
|
||||
}
|
||||
|
||||
inline void WebSocketClient::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
|
||||
|
||||
inline void WebSocketClient::set_address_family(int family) {
|
||||
|
||||
Reference in new issue
Block a user