From 6bb00b6788fa453e64f514220e1dedac553cea17 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 1 May 2019 11:29:50 -0700 Subject: [PATCH] close with params --- Dockerfile | 2 +- ixwebsocket/IXWebSocket.cpp | 5 +- ixwebsocket/IXWebSocket.h | 6 +- test/CMakeLists.txt | 3 + test/IXWebSocketCloseTest.cpp | 407 ++++++++++++++++++++++++++++++++++ 5 files changed, 419 insertions(+), 4 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 0449e694..22fd4372 100644 --- a/ixwebsocket/IXWebSocket.cpp +++ b/ixwebsocket/IXWebSocket.cpp @@ -212,9 +212,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 1dda6f66..811fc0ef 100644 --- a/ixwebsocket/IXWebSocket.h +++ b/ixwebsocket/IXWebSocket.h @@ -112,7 +112,11 @@ namespace ix WebSocketSendInfo sendText(const std::string& text, const OnProgressCallback& onProgressCallback = nullptr); WebSocketSendInfo ping(const std::string& text); - void close(); + + // A close frame can provide a code and a reason + // FIXME: use constants + 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 f4a74388..a2ae8d85 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,6 +38,9 @@ set (SOURCES IXWebSocketPingTest.cpp IXWebSocketTestConnectionDisconnection.cpp IXUrlParserTest.cpp + IXWebSocketCloseTest.cpp + IXWebSocketServerTest.cpp + IXWebSocketPingTest.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..9b4d14e2 --- /dev/null +++ b/test/IXWebSocketCloseTest.cpp @@ -0,0 +1,407 @@ +/* + * 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::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.close(code, reason); + _webSocket.stop(); + } + + 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::WebSocketMessageType::Open) + { + log("client connected"); + + _webSocket.disableAutomaticReconnection(); + } + else if (messageType == ix::WebSocketMessageType::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::WebSocketMessageType::Error) + { + ss << "Error ! " << error.reason; + log(ss.str()); + + _webSocket.disableAutomaticReconnection(); + } + else if (messageType == ix::WebSocketMessageType::Pong) + { + ss << "Received pong message " << str; + log(ss.str()); + } + else if (messageType == ix::WebSocketMessageType::Ping) + { + ss << "Received ping message " << str; + log(ss.str()); + } + else if (messageType == ix::WebSocketMessageType::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::WebSocketMessageType::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::WebSocketMessageType::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(); + } +}