From 7f1e70329c3ecad489593675d94c2cc63d7e29e2 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Fri, 10 May 2019 20:47:13 -0700 Subject: [PATCH] close and stop with code and reason + docker = ubuntu xenial --- Dockerfile | 2 +- ixwebsocket/IXWebSocket.cpp | 10 +- ixwebsocket/IXWebSocket.h | 8 +- test/CMakeLists.txt | 1 + test/IXWebSocketCloseTest.cpp | 406 ++++++++++++++++++++++++++++++++++ 5 files changed, 420 insertions(+), 7 deletions(-) create mode 100644 test/IXWebSocketCloseTest.cpp diff --git a/Dockerfile b/Dockerfile index d81c7222..cd238cbd 120000 --- a/Dockerfile +++ b/Dockerfile @@ -1 +1 @@ -docker/Dockerfile.fedora \ No newline at end of file +docker/Dockerfile.ubuntu_xenial \ No newline at end of file diff --git a/ixwebsocket/IXWebSocket.cpp b/ixwebsocket/IXWebSocket.cpp index 1146e00d..c4a537ca 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -142,9 +142,10 @@ namespace ix _thread = std::thread(&WebSocket::run, this); } - void WebSocket::stop() + void WebSocket::stop(uint16_t code, + const std::string& reason) { - close(); + close(code, reason); if (_thread.joinable()) { @@ -212,9 +213,10 @@ namespace ix return getReadyState() == ReadyState::Closing; } - void WebSocket::close() + void WebSocket::close(uint16_t code, + const std::string& reason) { - _ws.close(); + _ws.close(code, reason); } void WebSocket::checkConnection(bool firstConnectionAttempt) diff --git a/ixwebsocket/IXWebSocket.h b/ixwebsocket/IXWebSocket.h index 4682fdb7..850e089c 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -99,8 +99,10 @@ namespace ix // Run asynchronously, by calling start and stop. void start(); + // stop is synchronous - void stop(); + void stop(uint16_t code = 1000, + const std::string& reason = "Normal closure"); // Run in blocking mode, by connecting first manually, and then calling run. WebSocketInitResult connect(int timeoutSecs); @@ -112,7 +114,9 @@ namespace ix WebSocketSendInfo sendText(const std::string& text, const OnProgressCallback& onProgressCallback = nullptr); WebSocketSendInfo ping(const std::string& text); - void close(); + + void close(uint16_t code = 1000, + const std::string& reason = "Normal closure"); void setOnMessageCallback(const OnMessageCallback& callback); static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0ec26b46..e3c58ea0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,6 +37,7 @@ set (SOURCES IXWebSocketTestConnectionDisconnection.cpp IXUrlParserTest.cpp IXWebSocketServerTest.cpp + IXWebSocketCloseTest.cpp ) # Some unittest don't work on windows yet diff --git a/test/IXWebSocketCloseTest.cpp b/test/IXWebSocketCloseTest.cpp new file mode 100644 index 00000000..219463e1 --- /dev/null +++ b/test/IXWebSocketCloseTest.cpp @@ -0,0 +1,406 @@ +/* + * IXWebSocketCloseTest.cpp + * Author: Alexandre Konieczny + * Copyright (c) 2019 Machine Zone. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "IXTest.h" + +#include "catch.hpp" + +using namespace ix; + +namespace +{ + class WebSocketClient + { + public: + WebSocketClient(int port); + + void subscribe(const std::string& channel); + void start(); + void stop(); + void stop(uint16_t code, const std::string& reason); + bool isReady() const; + void sendMessage(const std::string& text); + + uint16_t getCloseCode(); + const std::string& getCloseReason(); + bool getCloseRemote(); + + private: + ix::WebSocket _webSocket; + int _port; + + mutable std::mutex _mutexCloseData; + uint16_t _closeCode; + std::string _closeReason; + bool _closeRemote; + }; + + WebSocketClient::WebSocketClient(int port) + : _port(port) + , _closeCode(0) + , _closeReason(std::string("")) + , _closeRemote(false) + { + ; + } + + bool WebSocketClient::isReady() const + { + return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open; + } + + uint16_t WebSocketClient::getCloseCode() + { + std::lock_guard lck(_mutexCloseData); + + return _closeCode; + } + + const std::string& WebSocketClient::getCloseReason() + { + std::lock_guard lck(_mutexCloseData); + + return _closeReason; + } + + bool WebSocketClient::getCloseRemote() + { + std::lock_guard lck(_mutexCloseData); + + return _closeRemote; + } + + void WebSocketClient::stop() + { + _webSocket.stop(); + } + + void WebSocketClient::stop(uint16_t code, const std::string& reason) + { + _webSocket.stop(code, reason); + } + + void WebSocketClient::start() + { + std::string url; + { + std::stringstream ss; + ss << "ws://localhost:" + << _port + << "/"; + + url = ss.str(); + } + + _webSocket.setUrl(url); + + std::stringstream ss; + log(std::string("Connecting to url: ") + url); + + _webSocket.setOnMessageCallback( + [this](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) + { + std::stringstream ss; + if (messageType == ix::WebSocket_MessageType_Open) + { + log("client connected"); + + _webSocket.disableAutomaticReconnection(); + } + else if (messageType == ix::WebSocket_MessageType_Close) + { + log("client disconnected"); + + std::lock_guard lck(_mutexCloseData); + + _closeCode = closeInfo.code; + _closeReason = std::string(closeInfo.reason); + _closeRemote = closeInfo.remote; + + _webSocket.disableAutomaticReconnection(); + } + else if (messageType == ix::WebSocket_MessageType_Error) + { + ss << "Error ! " << error.reason; + log(ss.str()); + + _webSocket.disableAutomaticReconnection(); + } + else if (messageType == ix::WebSocket_MessageType_Pong) + { + ss << "Received pong message " << str; + log(ss.str()); + } + else if (messageType == ix::WebSocket_MessageType_Ping) + { + ss << "Received ping message " << str; + log(ss.str()); + } + else if (messageType == ix::WebSocket_MessageType_Message) + { + ss << "Received message " << str; + log(ss.str()); + } + else + { + ss << "Invalid ix::WebSocketMessageType"; + log(ss.str()); + } + }); + + _webSocket.start(); + } + + void WebSocketClient::sendMessage(const std::string& text) + { + _webSocket.send(text); + } + + bool startServer(ix::WebSocketServer& server, + uint16_t& receivedCloseCode, + std::string& receivedCloseReason, + bool& receivedCloseRemote, + std::mutex& mutexWrite) + { + // A dev/null server + server.setOnConnectionCallback( + [&server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](std::shared_ptr webSocket, + std::shared_ptr connectionState) + { + webSocket->setOnMessageCallback( + [webSocket, connectionState, &server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](ix::WebSocketMessageType messageType, + const std::string& str, + size_t wireSize, + const ix::WebSocketErrorInfo& error, + const ix::WebSocketOpenInfo& openInfo, + const ix::WebSocketCloseInfo& closeInfo) + { + if (messageType == ix::WebSocket_MessageType_Open) + { + Logger() << "New server connection"; + Logger() << "id: " << connectionState->getId(); + Logger() << "Uri: " << openInfo.uri; + Logger() << "Headers:"; + for (auto it : openInfo.headers) + { + Logger() << it.first << ": " << it.second; + } + } + else if (messageType == ix::WebSocket_MessageType_Close) + { + log("Server closed connection"); + + //Logger() << closeInfo.code; + //Logger() << closeInfo.reason; + //Logger() << closeInfo.remote; + + std::lock_guard lck(mutexWrite); + + receivedCloseCode = closeInfo.code; + receivedCloseReason = std::string(closeInfo.reason); + receivedCloseRemote = closeInfo.remote; + } + } + ); + } + ); + + auto res = server.listen(); + if (!res.first) + { + log(res.second); + return false; + } + + server.start(); + return true; + } +} + +TEST_CASE("Websocket_client_close_default", "[close]") +{ + SECTION("Make sure that close code and reason was used and sent to server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(100); + + webSocketClient.stop(); + + ix::msleep(200); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 1000); + REQUIRE(webSocketClient.getCloseReason() == "Normal closure"); + REQUIRE(webSocketClient.getCloseRemote() == false); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 1000); + REQUIRE(serverReceivedCloseReason == "Normal closure"); + REQUIRE(serverReceivedCloseRemote == true); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_client_close_params_given", "[close]") +{ + SECTION("Make sure that close code and reason was used and sent to server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(100); + + webSocketClient.stop(4000, "My reason"); + + ix::msleep(500); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 4000); + REQUIRE(webSocketClient.getCloseReason() == "My reason"); + REQUIRE(webSocketClient.getCloseRemote() == false); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 4000); + REQUIRE(serverReceivedCloseReason == "My reason"); + REQUIRE(serverReceivedCloseRemote == true); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +} + +TEST_CASE("Websocket_server_close", "[close]") +{ + SECTION("Make sure that close code and reason was read from server.") + { + ix::setupWebSocketTrafficTrackerCallback(); + + int port = getFreePort(); + ix::WebSocketServer server(port); + + uint16_t serverReceivedCloseCode(0); + bool serverReceivedCloseRemote(false); + std::string serverReceivedCloseReason(""); + std::mutex mutexWrite; + + REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite)); + + std::string session = ix::generateSessionId(); + WebSocketClient webSocketClient(port); + + webSocketClient.start(); + + // Wait for all chat instance to be ready + while (true) + { + if (webSocketClient.isReady()) break; + ix::msleep(10); + } + + REQUIRE(server.getClients().size() == 1); + + ix::msleep(200); + + server.stop(); + + ix::msleep(500); + + // ensure client close is the same as values given + REQUIRE(webSocketClient.getCloseCode() == 1000); + REQUIRE(webSocketClient.getCloseReason() == "Normal closure"); + REQUIRE(webSocketClient.getCloseRemote() == true); + + { + std::lock_guard lck(mutexWrite); + + // Here we read the code/reason received by the server, and ensure that remote is true + REQUIRE(serverReceivedCloseCode == 1000); + REQUIRE(serverReceivedCloseReason == "Normal closure"); + REQUIRE(serverReceivedCloseRemote == false); + } + + // Give us 1000ms for the server to notice that clients went away + ix::msleep(1000); + REQUIRE(server.getClients().size() == 0); + + ix::reportWebSocketTraffic(); + } +}