From df7c4bb9f80b22b25298fdc8400d59f371e89179 Mon Sep 17 00:00:00 2001 From: Alex Hultman Date: Sun, 31 Jan 2021 12:53:17 +0000 Subject: [PATCH] Add option sendPingsAutomatically --- examples/EchoServer.cpp | 5 ++++- src/App.h | 6 ++++++ src/WebSocket.h | 4 +++- src/WebSocketContext.h | 17 +++++++++++++++-- src/WebSocketContextData.h | 29 +++++++++++++++++++++++++++-- src/WebSocketData.h | 1 + 6 files changed, 56 insertions(+), 6 deletions(-) diff --git a/examples/EchoServer.cpp b/examples/EchoServer.cpp index 49e921b..66376a4 100644 --- a/examples/EchoServer.cpp +++ b/examples/EchoServer.cpp @@ -21,8 +21,11 @@ int main() { /* Settings */ .compression = uWS::SHARED_COMPRESSOR, .maxPayloadLength = 16 * 1024, - .idleTimeout = 10, + .idleTimeout = 16, .maxBackpressure = 1 * 1024 * 1024, + .closeOnBackpressureLimit = false, + .resetIdleTimeoutOnSend = false, + .sendPingsAutomatically = true, /* Handlers */ .upgrade = nullptr, .open = [](auto */*ws*/) { diff --git a/src/App.h b/src/App.h index 9b7cf26..78175f6 100644 --- a/src/App.h +++ b/src/App.h @@ -139,8 +139,10 @@ public: unsigned int maxPayloadLength = 16 * 1024; unsigned int idleTimeout = 120; unsigned int maxBackpressure = 1 * 1024 * 1024; + // change these before release bool closeOnBackpressureLimit = false; bool resetIdleTimeoutOnSend = true; + bool sendPingsAutomatically = false; fu2::unique_function *, HttpRequest *, struct us_socket_context_t *)> upgrade = nullptr; fu2::unique_function *)> open = nullptr; fu2::unique_function *, std::string_view, uWS::OpCode)> message = nullptr; @@ -204,8 +206,12 @@ public: webSocketContext->getExt()->maxBackpressure = behavior.maxBackpressure; webSocketContext->getExt()->closeOnBackpressureLimit = behavior.closeOnBackpressureLimit; webSocketContext->getExt()->resetIdleTimeoutOnSend = behavior.resetIdleTimeoutOnSend; + webSocketContext->getExt()->sendPingsAutomatically = behavior.sendPingsAutomatically; webSocketContext->getExt()->compression = behavior.compression; + /* Calculate idleTimeoutCompnents */ + webSocketContext->getExt()->calculateIdleTimeoutCompnents(); + httpContext->onHttp("get", pattern, [webSocketContext, behavior = std::move(behavior)](auto *res, auto *req) mutable { /* If we have this header set, it's a websocket */ diff --git a/src/WebSocket.h b/src/WebSocket.h index 6a69ea5..3bf6f13 100644 --- a/src/WebSocket.h +++ b/src/WebSocket.h @@ -123,7 +123,9 @@ public: /* Every successful send resets the timeout */ if (webSocketContextData->resetIdleTimeoutOnSend) { - Super::timeout(webSocketContextData->idleTimeout); + Super::timeout(webSocketContextData->idleTimeoutComponents.first); + WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData(); + webSocketData->hasTimedOut = false; } /* Return success */ diff --git a/src/WebSocketContext.h b/src/WebSocketContext.h index 8cebb37..819ab5a 100644 --- a/src/WebSocketContext.h +++ b/src/WebSocketContext.h @@ -276,7 +276,8 @@ private: auto *asyncSocket = (AsyncSocket *) s; /* Every time we get data and not in shutdown state we simply reset the timeout */ - asyncSocket->timeout(webSocketContextData->idleTimeout); + asyncSocket->timeout(webSocketContextData->idleTimeoutComponents.first); + webSocketData->hasTimedOut = false; /* We always cork on data */ asyncSocket->cork(); @@ -325,7 +326,8 @@ private: /* Also reset timeout if we came here with 0 backpressure */ if (!backpressure || backpressure > asyncSocket->getBufferedAmount()) { auto *webSocketContextData = (WebSocketContextData *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s)); - asyncSocket->timeout(webSocketContextData->idleTimeout); + asyncSocket->timeout(webSocketContextData->idleTimeoutComponents.first); + webSocketData->hasTimedOut = false; } /* Are we in (WebSocket) shutdown mode? */ @@ -359,6 +361,17 @@ private: /* Handle socket timeouts, simply close them so to not confuse client with FIN */ us_socket_context_on_timeout(SSL, getSocketContext(), [](auto *s) { + auto *webSocketData = (WebSocketData *)(us_socket_ext(SSL, s)); + auto *webSocketContextData = (WebSocketContextData *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s)); + + if (webSocketContextData->sendPingsAutomatically && !webSocketData->hasTimedOut) { + webSocketData->hasTimedOut = true; + us_socket_timeout(SSL, s, webSocketContextData->idleTimeoutComponents.second); + /* Send ping without being corked */ + ((AsyncSocket *) s)->write("\x89\x00", 2); + return s; + } + /* Timeout is very simple; we just close it */ /* Warning: we happen to know forceClose will not use first parameter so pass nullptr here */ forceClose(nullptr, s, ERR_WEBSOCKET_TIMEOUT); diff --git a/src/WebSocketContextData.h b/src/WebSocketContextData.h index 99c2f07..ca04a54 100644 --- a/src/WebSocketContextData.h +++ b/src/WebSocketContextData.h @@ -66,10 +66,31 @@ public: size_t maxBackpressure = 0; bool closeOnBackpressureLimit; bool resetIdleTimeoutOnSend; + bool sendPingsAutomatically; + + /* These are calculated on creation */ + std::pair idleTimeoutComponents; /* Each websocket context has a topic tree for pub/sub */ TopicTree topicTree; + /* This is run once on start-up */ + void calculateIdleTimeoutCompnents() { + unsigned short margin = 4; + /* 4, 8 or 16 seconds margin based on idleTimeout */ + while ((int) idleTimeout - margin * 2 >= margin * 2 && margin < 16) { + margin *= 2; + } + /* We should have no margin if not using sendPingsAutomatically */ + if (!sendPingsAutomatically) { + margin = 0; + } + idleTimeoutComponents = { + idleTimeout - margin, + margin + }; + } + ~WebSocketContextData() { /* We must unregister any loop post handler here */ Loop::get()->removePostHandler(this); @@ -157,7 +178,9 @@ public: * ENTIRE SUCCESS - we need minor API changes to support correct checks */ if (!failed) { if (this->resetIdleTimeoutOnSend) { - asyncSocket->timeout(this->idleTimeout); + auto *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) asyncSocket); + webSocketData->hasTimedOut = false; + asyncSocket->timeout(this->idleTimeoutComponents.first); } } }); @@ -171,7 +194,9 @@ public: /* Again, this check should be more like DID WE PROGRESS rather than DID WE SUCCEED ENTIRELY */ if (!failed) { if (this->resetIdleTimeoutOnSend) { - asyncSocket->timeout(this->idleTimeout); + auto *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) asyncSocket); + webSocketData->hasTimedOut = false; + asyncSocket->timeout(this->idleTimeoutComponents.first); } } } diff --git a/src/WebSocketData.h b/src/WebSocketData.h index 5504860..feb94f8 100644 --- a/src/WebSocketData.h +++ b/src/WebSocketData.h @@ -37,6 +37,7 @@ private: std::string fragmentBuffer; unsigned int controlTipLength = 0; bool isShuttingDown = 0; + bool hasTimedOut = false; enum CompressionStatus : char { DISABLED, ENABLED,