Compare commits
	
		
			105 Commits
		
	
	
		
			feature/se
			...
			user/bserg
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 067c128474 | ||
|  | a127d9ef0d | ||
|  | 4a845e3cc4 | ||
|  | 633afa5bed | ||
|  | 49fd2a9e53 | ||
|  | 6264a8b41d | ||
|  | 3990d3bcbf | ||
|  | aa3f201ced | ||
|  | 83c261977d | ||
|  | 6ca28d96bf | ||
|  | c4a5647b62 | ||
|  | 720d5593a5 | ||
|  | 13fa325134 | ||
|  | 773cbb4907 | ||
|  | a696264b48 | ||
|  | b7db5f77fb | ||
|  | b11678e636 | ||
|  | f746070944 | ||
|  | 3323a51ab5 | ||
|  | 0e59927384 | ||
|  | 5c4840f129 | ||
|  | 9ac02323ad | ||
|  | cdbed26d1f | ||
|  | 23f171f34d | ||
|  | 20b625e483 | ||
|  | f1604c6460 | ||
|  | ba0e007c05 | ||
|  | 643e1bf20f | ||
|  | 24a32a0603 | ||
|  | c5caf32b77 | ||
|  | 09956d7500 | ||
|  | d91c896e46 | ||
|  | 042e6a22b8 | ||
|  | 14ec12d1f0 | ||
|  | 288b05a048 | ||
|  | 5af3096070 | ||
|  | 570fa01c04 | ||
|  | 2a69038c4c | ||
|  | 0ba127e447 | ||
|  | 7714bdf7e0 | ||
|  | 4e5e7ae50a | ||
|  | 5741b2f6c1 | ||
|  | 76172f92e9 | ||
|  | f8b547c028 | ||
|  | 7ccd9e1709 | ||
|  | 9217b27d40 | ||
|  | 819e9025b1 | ||
|  | 53ceab9f91 | ||
|  | a7ed4fe5c3 | ||
|  | 3190cd322d | ||
|  | dad2b64e15 | ||
|  | e527ab1613 | ||
|  | d7a0bc212d | ||
|  | aecd5e9c94 | ||
|  | e0edca43d5 | ||
|  | ce70d3d728 | ||
|  | d9be40a0de | ||
|  | e469f04c39 | ||
|  | 11774e6825 | ||
|  | 42bdfb51c3 | ||
|  | fd637bf1e1 | ||
|  | 8085e1416c | ||
|  | 671c9f805f | ||
|  | ace7a7ccae | ||
|  | 9c3bdf1a77 | ||
|  | f5242b3102 | ||
|  | f1272f059a | ||
|  | 91595ff4c2 | ||
|  | 3755d29a45 | ||
|  | c2b75399ae | ||
|  | a33ecd1338 | ||
|  | a7e29a9f36 | ||
|  | 02399dfa5c | ||
|  | aec2941bac | ||
|  | 9315eb5289 | ||
|  | 5b2b2ea7b0 | ||
|  | d90b634e80 | ||
|  | 6dd8cda074 | ||
|  | 701be31554 | ||
|  | 25eaf730bc | ||
|  | 4edb7447df | ||
|  | 5f3de60962 | ||
|  | 79c17aba49 | ||
|  | 80a90496d9 | ||
|  | bbca803840 | ||
|  | 160d3869a9 | ||
|  | afd8f64da8 | ||
|  | 6d2548b823 | ||
|  | 642356d353 | ||
|  | ba0fa36c2a | ||
|  | a41d08343c | ||
|  | 6467f98241 | ||
|  | b24e4334f6 | ||
|  | bf8abcbf4a | ||
|  | bb484414b1 | ||
|  | fc75b13fae | ||
|  | 78f59b4207 | ||
|  | 7c5567db56 | ||
|  | 7ecaf1f982 | ||
|  | d0a41f3894 | ||
|  | 57562b234f | ||
|  | 469d127d61 | ||
|  | d6e9b61c8e | ||
|  | 7fb1b65ddd | ||
|  | 77c7fdc636 | 
							
								
								
									
										47
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,17 +1,36 @@ | ||||
| language: cpp | ||||
| dist: xenial | ||||
|  | ||||
| compiler: | ||||
|   - gcc | ||||
|   - clang | ||||
| os: | ||||
|   - linux | ||||
|   - osx | ||||
| language: bash | ||||
|  | ||||
| matrix: | ||||
|   exclude: | ||||
|     # GCC fails on recent Travis OSX images. | ||||
|     - compiler: gcc | ||||
|       os: osx | ||||
|   include: | ||||
|     # macOS | ||||
|     - os: osx | ||||
|       compiler: clang | ||||
|       script: | ||||
|         - python test/run.py | ||||
|         - make ws | ||||
|  | ||||
| script: python test/run.py | ||||
|     # Linux | ||||
|     - os: linux | ||||
|       dist: xenial | ||||
|       script:  | ||||
|         - python test/run.py | ||||
|         - make ws | ||||
|       env: | ||||
|         - CC=gcc | ||||
|         - CXX=g++ | ||||
|  | ||||
|     # Clang + Linux disabled for now | ||||
|     # - os: linux | ||||
|     #   dist: xenial | ||||
|     #   script: python test/run.py | ||||
|     #   env: | ||||
|     #     - CC=clang  | ||||
|     #     - CXX=clang++ | ||||
|  | ||||
|     # Windows | ||||
|     - os: windows | ||||
|       env: | ||||
|         - CMAKE_PATH="/c/Program Files/CMake/bin" | ||||
|       script:  | ||||
|         - export PATH=$CMAKE_PATH:$PATH | ||||
|         - python test/run.py | ||||
|   | ||||
| @@ -41,6 +41,8 @@ set( IXWEBSOCKET_SOURCES | ||||
|     ixwebsocket/IXSelectInterrupt.cpp | ||||
|     ixwebsocket/IXSelectInterruptFactory.cpp | ||||
|     ixwebsocket/IXConnectionState.cpp | ||||
|     ixwebsocket/IXWebSocketCloseConstants.cpp | ||||
|     ixwebsocket/IXWebSocketMessageQueue.cpp | ||||
| ) | ||||
|  | ||||
| set( IXWEBSOCKET_HEADERS | ||||
| @@ -70,6 +72,8 @@ set( IXWEBSOCKET_HEADERS | ||||
|     ixwebsocket/IXSelectInterrupt.h | ||||
|     ixwebsocket/IXSelectInterruptFactory.h | ||||
|     ixwebsocket/IXConnectionState.h | ||||
|     ixwebsocket/IXWebSocketCloseConstants.h | ||||
|     ixwebsocket/IXWebSocketMessageQueue.h | ||||
| ) | ||||
|  | ||||
| if (UNIX) | ||||
| @@ -155,6 +159,6 @@ install(TARGETS ixwebsocket | ||||
|         PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/ | ||||
| ) | ||||
|  | ||||
| if (NOT WIN32) | ||||
| if (USE_WS) | ||||
|     add_subdirectory(ws) | ||||
| endif() | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| docker/Dockerfile.fedora | ||||
| docker/Dockerfile.ubuntu_xenial | ||||
| @@ -16,6 +16,7 @@ ENV PATH="${CMAKE_BIN_PATH}:${PATH}" | ||||
|  | ||||
| RUN yum install -y python | ||||
| RUN yum install -y libtsan | ||||
| RUN yum install -y zlib-devel | ||||
|  | ||||
| COPY . . | ||||
| # RUN ["make", "test"] | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  | ||||
| namespace ix | ||||
| { | ||||
|     enum class HttpErrorCode | ||||
|     enum class HttpErrorCode : int | ||||
|     { | ||||
|         Ok                       = 0, | ||||
|         CannotConnect            = 1, | ||||
|   | ||||
| @@ -129,7 +129,7 @@ namespace ix | ||||
|     } | ||||
|  | ||||
|     // Wake up from poll/select by writing to the pipe which is watched by select | ||||
|     bool Socket::wakeUpFromPoll(uint8_t wakeUpCode) | ||||
|     bool Socket::wakeUpFromPoll(uint64_t wakeUpCode) | ||||
|     { | ||||
|         return _selectInterrupt->notify(wakeUpCode); | ||||
|     } | ||||
|   | ||||
| @@ -57,7 +57,7 @@ namespace ix | ||||
|  | ||||
|         // Functions to check whether there is activity on the socket | ||||
|         PollResultType poll(int timeoutMs = kDefaultPollTimeout); | ||||
|         bool wakeUpFromPoll(uint8_t wakeUpCode); | ||||
|         bool wakeUpFromPoll(uint64_t wakeUpCode); | ||||
|  | ||||
|         PollResultType isReadyToWrite(int timeoutMs); | ||||
|         PollResultType isReadyToRead(int timeoutMs); | ||||
|   | ||||
| @@ -30,7 +30,9 @@ namespace ix | ||||
|         _host(host), | ||||
|         _backlog(backlog), | ||||
|         _maxConnections(maxConnections), | ||||
|         _serverFd(-1), | ||||
|         _stop(false), | ||||
|         _stopGc(false), | ||||
|         _connectionStateFactory(&ConnectionState::createConnectionState) | ||||
|     { | ||||
|  | ||||
| @@ -124,9 +126,15 @@ namespace ix | ||||
|  | ||||
|     void SocketServer::start() | ||||
|     { | ||||
|         if (_thread.joinable()) return; // we've already been started | ||||
|         if (!_thread.joinable()) | ||||
|         { | ||||
|             _thread = std::thread(&SocketServer::run, this); | ||||
|         } | ||||
|  | ||||
|         _thread = std::thread(&SocketServer::run, this); | ||||
|         if (!_gcThread.joinable()) | ||||
|         { | ||||
|             _gcThread = std::thread(&SocketServer::runGC, this); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void SocketServer::wait() | ||||
| @@ -142,21 +150,21 @@ namespace ix | ||||
|  | ||||
|     void SocketServer::stop() | ||||
|     { | ||||
|         while (true) | ||||
|         // Stop accepting connections, and close the 'accept' thread | ||||
|         if (_thread.joinable()) | ||||
|         { | ||||
|             if (closeTerminatedThreads()) break; | ||||
|  | ||||
|             // wait 10ms and try again later. | ||||
|             // we could have a timeout, but if we exit of here | ||||
|             // we leaked threads, it is quite bad. | ||||
|             std::this_thread::sleep_for(std::chrono::milliseconds(10)); | ||||
|             _stop = true; | ||||
|             _thread.join(); | ||||
|             _stop = false; | ||||
|         } | ||||
|  | ||||
|         if (!_thread.joinable()) return; // nothing to do | ||||
|  | ||||
|         _stop = true; | ||||
|         _thread.join(); | ||||
|         _stop = false; | ||||
|         // Join all threads and make sure that all connections are terminated | ||||
|         if (_gcThread.joinable()) | ||||
|         { | ||||
|             _stopGc = true; | ||||
|             _gcThread.join(); | ||||
|             _stopGc = false; | ||||
|         } | ||||
|  | ||||
|         _conditionVariable.notify_one(); | ||||
|         Socket::closeSocket(_serverFd); | ||||
| @@ -175,7 +183,7 @@ namespace ix | ||||
|     // field becomes true, and we can use that to know that we can join that thread | ||||
|     // and remove it from our _connectionsThreads data structure (a list). | ||||
|     // | ||||
|     bool SocketServer::closeTerminatedThreads() | ||||
|     void SocketServer::closeTerminatedThreads() | ||||
|     { | ||||
|         std::lock_guard<std::mutex> lock(_connectionsThreadsMutex); | ||||
|         auto it = _connectionsThreads.begin(); | ||||
| @@ -195,8 +203,6 @@ namespace ix | ||||
|             if (thread.joinable()) thread.join(); | ||||
|             it = _connectionsThreads.erase(it); | ||||
|         } | ||||
|  | ||||
|         return _connectionsThreads.empty(); | ||||
|     } | ||||
|  | ||||
|     void SocketServer::run() | ||||
| @@ -208,12 +214,6 @@ namespace ix | ||||
|         { | ||||
|             if (_stop) return; | ||||
|  | ||||
|             // Garbage collection to shutdown/join threads for closed connections. | ||||
|             // We could run this in its own thread, so that we dont need to accept | ||||
|             // a new connection to close a thread. | ||||
|             // We could also use a condition variable to be notify when we need to do this | ||||
|             closeTerminatedThreads(); | ||||
|  | ||||
|             // Use select to check whether a new connection is in progress | ||||
|             fd_set rfds; | ||||
|             struct timeval timeout; | ||||
| @@ -290,5 +290,30 @@ namespace ix | ||||
|                                 connectionState))); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     size_t SocketServer::getConnectionsThreadsCount() | ||||
|     { | ||||
|         std::lock_guard<std::mutex> lock(_connectionsThreadsMutex); | ||||
|         return _connectionsThreads.size(); | ||||
|     } | ||||
|  | ||||
|     void SocketServer::runGC() | ||||
|     { | ||||
|         for (;;) | ||||
|         { | ||||
|             // Garbage collection to shutdown/join threads for closed connections. | ||||
|             closeTerminatedThreads(); | ||||
|  | ||||
|             // We quit this thread if all connections are closed and we received | ||||
|             // a stop request by setting _stopGc to true. | ||||
|             if (_stopGc && getConnectionsThreadsCount() == 0) | ||||
|             { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // Sleep a little bit then keep cleaning up | ||||
|             std::this_thread::sleep_for(std::chrono::milliseconds(10)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -74,6 +74,12 @@ namespace ix | ||||
|         // background thread to wait for incoming connections | ||||
|         std::atomic<bool> _stop; | ||||
|         std::thread _thread; | ||||
|         void run(); | ||||
|  | ||||
|         // background thread to cleanup (join) terminated threads | ||||
|         std::atomic<bool> _stopGc; | ||||
|         std::thread _gcThread; | ||||
|         void runGC(); | ||||
|  | ||||
|         // the list of (connectionState, threads) for each connections | ||||
|         ConnectionThreads _connectionsThreads; | ||||
| @@ -87,13 +93,12 @@ namespace ix | ||||
|         // the factory to create ConnectionState objects | ||||
|         ConnectionStateFactory _connectionStateFactory; | ||||
|  | ||||
|         // Methods | ||||
|         void run(); | ||||
|         virtual void handleConnection(int fd, | ||||
|                                       std::shared_ptr<ConnectionState> connectionState) = 0; | ||||
|         virtual size_t getConnectedClientsCount() = 0; | ||||
|  | ||||
|         // Returns true if all connection threads are joined | ||||
|         bool closeTerminatedThreads(); | ||||
|         void closeTerminatedThreads(); | ||||
|         size_t getConnectionsThreadsCount(); | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| @@ -261,7 +263,7 @@ namespace ix | ||||
|                     connectErr.wait_time = duration.count(); | ||||
|                     connectErr.retries = retries; | ||||
|                 } | ||||
|                  | ||||
|  | ||||
|                 connectErr.reason      = status.errorStr; | ||||
|                 connectErr.http_status = status.http_status; | ||||
|  | ||||
| @@ -291,6 +293,9 @@ namespace ix | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // We can avoid to poll if we want to stop and are not closing | ||||
|             if (_stop && !isClosing()) break; | ||||
|  | ||||
|             // 2. Poll to see if there's any new data available | ||||
|             WebSocketTransport::PollResult pollResult = _ws.poll(); | ||||
|  | ||||
| @@ -458,6 +463,11 @@ namespace ix | ||||
|         _automaticReconnection = false; | ||||
|     } | ||||
|  | ||||
|     bool WebSocket::isAutomaticReconnectionEnabled() const | ||||
|     { | ||||
|         return _automaticReconnection; | ||||
|     } | ||||
|  | ||||
|     size_t WebSocket::bufferedAmount() const | ||||
|     { | ||||
|         return _ws.bufferedAmount(); | ||||
|   | ||||
| @@ -19,6 +19,7 @@ | ||||
| #include "IXWebSocketSendInfo.h" | ||||
| #include "IXWebSocketPerMessageDeflateOptions.h" | ||||
| #include "IXWebSocketHttpHeaders.h" | ||||
| #include "IXWebSocketCloseConstants.h" | ||||
| #include "IXProgressCallback.h" | ||||
|  | ||||
| namespace ix | ||||
| @@ -99,8 +100,10 @@ namespace ix | ||||
|  | ||||
|         // Run asynchronously, by calling start and stop. | ||||
|         void start(); | ||||
|  | ||||
|         // stop is synchronous | ||||
|         void stop(); | ||||
|         void stop(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, | ||||
|                   const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage); | ||||
|  | ||||
|         // Run in blocking mode, by connecting first manually, and then calling run. | ||||
|         WebSocketInitResult connect(int timeoutSecs); | ||||
| @@ -112,7 +115,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); | ||||
| @@ -130,6 +135,7 @@ namespace ix | ||||
|  | ||||
|         void enableAutomaticReconnection(); | ||||
|         void disableAutomaticReconnection(); | ||||
|         bool isAutomaticReconnectionEnabled() const; | ||||
|  | ||||
|     private: | ||||
|  | ||||
|   | ||||
							
								
								
									
										23
									
								
								ixwebsocket/IXWebSocketCloseConstants.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ixwebsocket/IXWebSocketCloseConstants.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| /* | ||||
|  *  IXWebSocketCloseConstants.cpp | ||||
|  *  Author: Benjamin Sergeant | ||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. | ||||
|  */ | ||||
|  | ||||
| #include "IXWebSocketCloseConstants.h" | ||||
|  | ||||
| namespace ix | ||||
| { | ||||
|     const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000); | ||||
|     const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011); | ||||
|     const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006); | ||||
|     const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002); | ||||
|     const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005); | ||||
|  | ||||
|     const std::string WebSocketCloseConstants::kNormalClosureMessage("Normal closure"); | ||||
|     const std::string WebSocketCloseConstants::kInternalErrorMessage("Internal error"); | ||||
|     const std::string WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure"); | ||||
|     const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout"); | ||||
|     const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error"); | ||||
|     const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code"); | ||||
| } | ||||
							
								
								
									
										29
									
								
								ixwebsocket/IXWebSocketCloseConstants.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								ixwebsocket/IXWebSocketCloseConstants.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| /* | ||||
|  *  IXWebSocketCloseConstants.h | ||||
|  *  Author: Benjamin Sergeant | ||||
|  *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <string> | ||||
|  | ||||
| namespace ix | ||||
| { | ||||
|     struct WebSocketCloseConstants | ||||
|     { | ||||
|         static const uint16_t kNormalClosureCode; | ||||
|         static const uint16_t kInternalErrorCode; | ||||
|         static const uint16_t kAbnormalCloseCode; | ||||
|         static const uint16_t kProtocolErrorCode; | ||||
|         static const uint16_t kNoStatusCodeErrorCode; | ||||
|  | ||||
|         static const std::string kNormalClosureMessage; | ||||
|         static const std::string kInternalErrorMessage; | ||||
|         static const std::string kAbnormalCloseMessage; | ||||
|         static const std::string kPingTimeoutMessage; | ||||
|         static const std::string kProtocolErrorMessage; | ||||
|         static const std::string kNoStatusCodeErrorMessage; | ||||
|     }; | ||||
| } | ||||
| @@ -242,7 +242,7 @@ namespace ix | ||||
|         } | ||||
|  | ||||
|         char output[29] = {}; | ||||
|         WebSocketHandshakeKeyGen::generate(secWebSocketKey.c_str(), output); | ||||
|         WebSocketHandshakeKeyGen::generate(secWebSocketKey, output); | ||||
|         if (std::string(output) != headers["sec-websocket-accept"]) | ||||
|         { | ||||
|             std::string errorMsg("Invalid Sec-WebSocket-Accept value"); | ||||
| @@ -348,7 +348,7 @@ namespace ix | ||||
|         } | ||||
|  | ||||
|         char output[29] = {}; | ||||
|         WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output); | ||||
|         WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output); | ||||
|  | ||||
|         std::stringstream ss; | ||||
|         ss << "HTTP/1.1 101 Switching Protocols\r\n"; | ||||
|   | ||||
							
								
								
									
										121
									
								
								ixwebsocket/IXWebSocketMessageQueue.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								ixwebsocket/IXWebSocketMessageQueue.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| /* | ||||
|  *  IXWebSocketMessageQueue.cpp | ||||
|  *  Author: Korchynskyi Dmytro | ||||
|  *  Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. | ||||
|  */ | ||||
|  | ||||
| #include "IXWebSocketMessageQueue.h" | ||||
|  | ||||
| namespace ix | ||||
| { | ||||
|  | ||||
|     WebSocketMessageQueue::WebSocketMessageQueue(WebSocket* websocket) | ||||
|     { | ||||
|         bindWebsocket(websocket); | ||||
|     } | ||||
|  | ||||
|     WebSocketMessageQueue::~WebSocketMessageQueue() | ||||
|     { | ||||
|         if (!_messages.empty()) | ||||
|         { | ||||
|             // not handled all messages | ||||
|         } | ||||
|  | ||||
|         bindWebsocket(nullptr); | ||||
|     } | ||||
|  | ||||
|     void WebSocketMessageQueue::bindWebsocket(WebSocket * websocket) | ||||
|     { | ||||
|         if (_websocket == websocket) return; | ||||
|  | ||||
|         // unbind old | ||||
|         if (_websocket) | ||||
|         { | ||||
|             // set dummy callback just to avoid crash | ||||
|             _websocket->setOnMessageCallback([]( | ||||
|                 WebSocketMessageType, | ||||
|                 const std::string&, | ||||
|                 size_t, | ||||
|                 const WebSocketErrorInfo&, | ||||
|                 const WebSocketOpenInfo&, | ||||
|                 const WebSocketCloseInfo&) | ||||
|             {}); | ||||
|         } | ||||
|  | ||||
|         _websocket = websocket; | ||||
|  | ||||
|         // bind new | ||||
|         if (_websocket) | ||||
|         { | ||||
|             _websocket->setOnMessageCallback([this]( | ||||
|                 WebSocketMessageType type, | ||||
|                 const std::string& str, | ||||
|                 size_t wireSize, | ||||
|                 const WebSocketErrorInfo& errorInfo, | ||||
|                 const WebSocketOpenInfo& openInfo, | ||||
|                 const WebSocketCloseInfo& closeInfo) | ||||
|             { | ||||
|                 MessagePtr message(new Message()); | ||||
|  | ||||
|                 message->type      = type; | ||||
|                 message->str       = str; | ||||
|                 message->wireSize  = wireSize; | ||||
|                 message->errorInfo = errorInfo; | ||||
|                 message->openInfo  = openInfo; | ||||
|                 message->closeInfo = closeInfo; | ||||
|  | ||||
|                 { | ||||
|                     std::lock_guard<std::mutex> lock(_messagesMutex); | ||||
|                     _messages.emplace_back(std::move(message)); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void WebSocketMessageQueue::setOnMessageCallback(const OnMessageCallback& callback) | ||||
|     { | ||||
|         _onMessageUserCallback = callback; | ||||
|     } | ||||
|  | ||||
|     void WebSocketMessageQueue::setOnMessageCallback(OnMessageCallback&& callback) | ||||
|     { | ||||
|         _onMessageUserCallback = std::move(callback); | ||||
|     } | ||||
|      | ||||
|     WebSocketMessageQueue::MessagePtr WebSocketMessageQueue::popMessage() | ||||
|     { | ||||
|         MessagePtr message; | ||||
|         std::lock_guard<std::mutex> lock(_messagesMutex); | ||||
|  | ||||
|         if (!_messages.empty()) | ||||
|         { | ||||
|             message = std::move(_messages.front()); | ||||
|             _messages.pop_front(); | ||||
|         } | ||||
|  | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     void WebSocketMessageQueue::poll(int count) | ||||
|     { | ||||
|         if (!_onMessageUserCallback) | ||||
|             return; | ||||
|  | ||||
|         MessagePtr message; | ||||
|  | ||||
|         while (count > 0 && (message = popMessage())) | ||||
|         { | ||||
|             _onMessageUserCallback( | ||||
|                 message->type, | ||||
|                 message->str, | ||||
|                 message->wireSize, | ||||
|                 message->errorInfo, | ||||
|                 message->openInfo, | ||||
|                 message->closeInfo | ||||
|             ); | ||||
|  | ||||
|             --count; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										53
									
								
								ixwebsocket/IXWebSocketMessageQueue.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								ixwebsocket/IXWebSocketMessageQueue.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| /* | ||||
|  *  IXWebSocketMessageQueue.h | ||||
|  *  Author: Korchynskyi Dmytro | ||||
|  *  Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "IXWebSocket.h" | ||||
| #include <thread> | ||||
| #include <list> | ||||
| #include <memory> | ||||
|  | ||||
| namespace ix | ||||
| { | ||||
|     // | ||||
|     // A helper class to dispatch websocket message callbacks in your thread. | ||||
|     // | ||||
|     class WebSocketMessageQueue | ||||
|     { | ||||
|     public: | ||||
|         WebSocketMessageQueue(WebSocket* websocket = nullptr); | ||||
|         ~WebSocketMessageQueue(); | ||||
|  | ||||
|         void bindWebsocket(WebSocket* websocket); | ||||
|  | ||||
|         void setOnMessageCallback(const OnMessageCallback& callback); | ||||
|         void setOnMessageCallback(OnMessageCallback&& callback); | ||||
|  | ||||
|         void poll(int count = 512); | ||||
|  | ||||
|     protected: | ||||
|         struct Message | ||||
|         { | ||||
|             WebSocketMessageType type; | ||||
|             std::string str; | ||||
|             size_t wireSize; | ||||
|             WebSocketErrorInfo errorInfo; | ||||
|             WebSocketOpenInfo openInfo; | ||||
|             WebSocketCloseInfo closeInfo; | ||||
|         }; | ||||
|  | ||||
|         using MessagePtr = std::shared_ptr<Message>; | ||||
|  | ||||
|         MessagePtr popMessage(); | ||||
|  | ||||
|     private: | ||||
|         WebSocket* _websocket = nullptr; | ||||
|         OnMessageCallback _onMessageUserCallback; | ||||
|         std::mutex _messagesMutex; | ||||
|         std::list<MessagePtr> _messages; | ||||
|     }; | ||||
| } | ||||
| @@ -71,24 +71,14 @@ namespace ix | ||||
|     const int WebSocketTransport::kDefaultPingIntervalSecs(-1); | ||||
|     const int WebSocketTransport::kDefaultPingTimeoutSecs(-1); | ||||
|     const bool WebSocketTransport::kDefaultEnablePong(true); | ||||
|     const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(200); | ||||
|     const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300); | ||||
|     constexpr size_t WebSocketTransport::kChunkSize; | ||||
|  | ||||
|     const uint16_t WebSocketTransport::kInternalErrorCode(1011); | ||||
|     const uint16_t WebSocketTransport::kAbnormalCloseCode(1006); | ||||
|     const uint16_t WebSocketTransport::kProtocolErrorCode(1002); | ||||
|     const uint16_t WebSocketTransport::kNoStatusCodeErrorCode(1005); | ||||
|     const std::string WebSocketTransport::kInternalErrorMessage("Internal error"); | ||||
|     const std::string WebSocketTransport::kAbnormalCloseMessage("Abnormal closure"); | ||||
|     const std::string WebSocketTransport::kPingTimeoutMessage("Ping timeout"); | ||||
|     const std::string WebSocketTransport::kProtocolErrorMessage("Protocol error"); | ||||
|     const std::string WebSocketTransport::kNoStatusCodeErrorMessage("No status code"); | ||||
|  | ||||
|     WebSocketTransport::WebSocketTransport() : | ||||
|         _useMask(true), | ||||
|         _readyState(ReadyState::CLOSED), | ||||
|         _closeCode(kInternalErrorCode), | ||||
|         _closeReason(kInternalErrorMessage), | ||||
|         _closeCode(WebSocketCloseConstants::kInternalErrorCode), | ||||
|         _closeReason(WebSocketCloseConstants::kInternalErrorMessage), | ||||
|         _closeWireSize(0), | ||||
|         _closeRemote(false), | ||||
|         _enablePerMessageDeflate(false), | ||||
| @@ -140,6 +130,8 @@ namespace ix | ||||
|     WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url, | ||||
|                                                          int timeoutSecs) | ||||
|     { | ||||
|         std::lock_guard<std::mutex> lock(_socketMutex); | ||||
|  | ||||
|         std::string protocol, host, path, query; | ||||
|         int port; | ||||
|  | ||||
| @@ -149,8 +141,8 @@ namespace ix | ||||
|                                        std::string("Could not parse URL ") + url); | ||||
|         } | ||||
|  | ||||
|         bool tls = protocol == "wss"; | ||||
|         std::string errorMsg; | ||||
|         bool tls = protocol == "wss"; | ||||
|         _socket = createSocket(tls, errorMsg); | ||||
|  | ||||
|         if (!_socket) | ||||
| @@ -176,6 +168,8 @@ namespace ix | ||||
|     // Server | ||||
|     WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs) | ||||
|     { | ||||
|         std::lock_guard<std::mutex> lock(_socketMutex); | ||||
|  | ||||
|         // Server should not mask the data it sends to the client | ||||
|         _useMask = false; | ||||
|  | ||||
| @@ -215,8 +209,8 @@ namespace ix | ||||
|         { | ||||
|             std::lock_guard<std::mutex> lock(_closeDataMutex); | ||||
|             _onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote); | ||||
|             _closeCode = kInternalErrorCode; | ||||
|             _closeReason = kInternalErrorMessage; | ||||
|             _closeCode = WebSocketCloseConstants::kInternalErrorCode; | ||||
|             _closeReason = WebSocketCloseConstants::kInternalErrorMessage; | ||||
|             _closeWireSize = 0; | ||||
|             _closeRemote = false; | ||||
|         } | ||||
| @@ -238,7 +232,7 @@ namespace ix | ||||
|         { | ||||
|             std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex); | ||||
|             _lastSendPingTimePoint = std::chrono::steady_clock::now(); | ||||
|         }  | ||||
|         } | ||||
|         { | ||||
|             std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex); | ||||
|             _lastReceivePongTimePoint = std::chrono::steady_clock::now(); | ||||
| @@ -286,7 +280,8 @@ namespace ix | ||||
|             // ping response (PONG) exceeds the maximum delay, then close the connection | ||||
|             if (pingTimeoutExceeded()) | ||||
|             { | ||||
|                 close(kInternalErrorCode, kPingTimeoutMessage); | ||||
|                 close(WebSocketCloseConstants::kInternalErrorCode, | ||||
|                       WebSocketCloseConstants::kPingTimeoutMessage); | ||||
|             } | ||||
|             // If ping is enabled and no ping has been sent for a duration | ||||
|             // exceeding our ping interval, send a ping to the server. | ||||
| @@ -297,7 +292,7 @@ namespace ix | ||||
|                 sendPing(ss.str()); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|  | ||||
|         // No timeout if state is not OPEN, otherwise computed | ||||
|         // pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set) | ||||
|         int lastingTimeoutDelayInMs = (_readyState != ReadyState::OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs; | ||||
| @@ -310,19 +305,30 @@ namespace ix | ||||
|             if (now >= _nextGCDTimePoint) | ||||
|             { | ||||
|                 _nextGCDTimePoint = now + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs); | ||||
|              | ||||
|  | ||||
|                 lastingTimeoutDelayInMs = _pingIntervalOrTimeoutGCDSecs * 1000; | ||||
|             } | ||||
|             else  | ||||
|             else | ||||
|             { | ||||
|                 lastingTimeoutDelayInMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(_nextGCDTimePoint - now).count(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| #ifdef _WIN32 | ||||
|         if (lastingTimeoutDelayInMs <= 0) lastingTimeoutDelayInMs = 20; | ||||
|         // Windows does not have select interrupt capabilities, so wait with a small timeout | ||||
|         if (lastingTimeoutDelayInMs <= 0) | ||||
|         { | ||||
|             lastingTimeoutDelayInMs = 20; | ||||
|         } | ||||
| #endif | ||||
|  | ||||
|         // If we are requesting a cancellation, pass in a positive and small timeout | ||||
|         // to never poll forever without a timeout. | ||||
|         if (_requestInitCancellation) | ||||
|         { | ||||
|             lastingTimeoutDelayInMs = 100; | ||||
|         } | ||||
|  | ||||
|         // poll the socket | ||||
|         PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs); | ||||
|  | ||||
| @@ -338,7 +344,7 @@ namespace ix | ||||
|  | ||||
|                 if (result == PollResultType::Error) | ||||
|                 { | ||||
|                     _socket->close(); | ||||
|                     closeSocket(); | ||||
|                     setReadyState(ReadyState::CLOSED); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -362,8 +368,8 @@ namespace ix | ||||
|                 { | ||||
|                     // if there are received data pending to be processed, then delay the abnormal closure | ||||
|                     // to after dispatch (other close code/reason could be read from the buffer) | ||||
|                      | ||||
|                     _socket->close(); | ||||
|  | ||||
|                     closeSocket(); | ||||
|  | ||||
|                     return PollResult::AbnormalClose; | ||||
|                 } | ||||
| @@ -377,18 +383,18 @@ namespace ix | ||||
|         } | ||||
|         else if (pollResult == PollResultType::Error) | ||||
|         { | ||||
|             _socket->close(); | ||||
|             closeSocket(); | ||||
|         } | ||||
|         else if (pollResult == PollResultType::CloseRequest) | ||||
|         { | ||||
|             _socket->close(); | ||||
|             closeSocket(); | ||||
|         } | ||||
|  | ||||
|         if (_readyState == ReadyState::CLOSING && closingDelayExceeded()) | ||||
|         { | ||||
|             _rxbuf.clear(); | ||||
|             // close code and reason were set when calling close() | ||||
|             _socket->close(); | ||||
|             closeSocket(); | ||||
|             setReadyState(ReadyState::CLOSED); | ||||
|         } | ||||
|  | ||||
| @@ -620,8 +626,8 @@ namespace ix | ||||
|                 else | ||||
|                 { | ||||
|                     // no close code received | ||||
|                     code = kNoStatusCodeErrorCode; | ||||
|                     reason = kNoStatusCodeErrorMessage; | ||||
|                     code = WebSocketCloseConstants::kNoStatusCodeErrorCode; | ||||
|                     reason = WebSocketCloseConstants::kNoStatusCodeErrorMessage; | ||||
|                 } | ||||
|  | ||||
|                 // We receive a CLOSE frame from remote and are NOT the ones who triggered the close | ||||
| @@ -655,8 +661,9 @@ namespace ix | ||||
|             else | ||||
|             { | ||||
|                 // Unexpected frame type | ||||
|  | ||||
|                 close(kProtocolErrorCode, kProtocolErrorMessage, _rxbuf.size()); | ||||
|                 close(WebSocketCloseConstants::kProtocolErrorCode, | ||||
|                       WebSocketCloseConstants::kProtocolErrorMessage, | ||||
|                       _rxbuf.size()); | ||||
|             } | ||||
|  | ||||
|             // Erase the message that has been processed from the input/read buffer | ||||
| @@ -673,13 +680,15 @@ namespace ix | ||||
|             // if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before) | ||||
|             if (_readyState == ReadyState::CLOSING) | ||||
|             { | ||||
|                 _socket->close(); | ||||
|                 closeSocket(); | ||||
|                 setReadyState(ReadyState::CLOSED); | ||||
|             } | ||||
|             // if we weren't closing, then close using abnormal close code and message  | ||||
|             // if we weren't closing, then close using abnormal close code and message | ||||
|             else if (_readyState != ReadyState::CLOSED) | ||||
|             { | ||||
|                 closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false); | ||||
|                 closeSocketAndSwitchToClosedState(WebSocketCloseConstants::kAbnormalCloseCode, | ||||
|                                                   WebSocketCloseConstants::kAbnormalCloseMessage, | ||||
|                                                   0, false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -739,7 +748,7 @@ namespace ix | ||||
|         bool compress, | ||||
|         const OnProgressCallback& onProgressCallback) | ||||
|     { | ||||
|         if (_readyState != ReadyState::OPEN) | ||||
|         if (_readyState != ReadyState::OPEN && _readyState != ReadyState::CLOSING) | ||||
|         { | ||||
|             return WebSocketSendInfo(); | ||||
|         } | ||||
| @@ -949,13 +958,19 @@ namespace ix | ||||
|                         _enablePerMessageDeflate, onProgressCallback); | ||||
|     } | ||||
|  | ||||
|     ssize_t WebSocketTransport::send() | ||||
|     { | ||||
|         std::lock_guard<std::mutex> lock(_socketMutex); | ||||
|         return _socket->send((char*)&_txbuf[0], _txbuf.size()); | ||||
|     } | ||||
|  | ||||
|     void WebSocketTransport::sendOnSocket() | ||||
|     { | ||||
|         std::lock_guard<std::mutex> lock(_txbufMutex); | ||||
|  | ||||
|         while (_txbuf.size()) | ||||
|         { | ||||
|             ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size()); | ||||
|             ssize_t ret = send(); | ||||
|  | ||||
|             if (ret < 0 && Socket::isWaitNeeded()) | ||||
|             { | ||||
| @@ -963,8 +978,7 @@ namespace ix | ||||
|             } | ||||
|             else if (ret <= 0) | ||||
|             { | ||||
|                 _socket->close(); | ||||
|  | ||||
|                 closeSocket(); | ||||
|                 setReadyState(ReadyState::CLOSED); | ||||
|                 break; | ||||
|             } | ||||
| @@ -980,7 +994,7 @@ namespace ix | ||||
|         bool compress = false; | ||||
|  | ||||
|         // if a status is set/was read | ||||
|         if (code != kNoStatusCodeErrorCode) | ||||
|         if (code != WebSocketCloseConstants::kNoStatusCodeErrorCode) | ||||
|         { | ||||
|             // See list of close events here: | ||||
|             // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent | ||||
| @@ -998,9 +1012,17 @@ namespace ix | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) | ||||
|     void WebSocketTransport::closeSocket() | ||||
|     { | ||||
|         std::lock_guard<std::mutex> lock(_socketMutex); | ||||
|         _socket->close(); | ||||
|     } | ||||
|  | ||||
|     void WebSocketTransport::closeSocketAndSwitchToClosedState( | ||||
|         uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) | ||||
|     { | ||||
|         closeSocket(); | ||||
|  | ||||
|         { | ||||
|             std::lock_guard<std::mutex> lock(_closeDataMutex); | ||||
|             _closeCode = code; | ||||
| @@ -1008,16 +1030,18 @@ namespace ix | ||||
|             _closeWireSize = closeWireSize; | ||||
|             _closeRemote = remote; | ||||
|         } | ||||
|  | ||||
|         setReadyState(ReadyState::CLOSED); | ||||
|         _requestInitCancellation = false; | ||||
|     } | ||||
|  | ||||
|     void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) | ||||
|     void WebSocketTransport::close( | ||||
|         uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) | ||||
|     { | ||||
|         _requestInitCancellation = true; | ||||
|  | ||||
|         if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return; | ||||
|  | ||||
|         sendCloseFrame(code, reason); | ||||
|          | ||||
|         { | ||||
|             std::lock_guard<std::mutex> lock(_closeDataMutex); | ||||
|             _closeCode = code; | ||||
| @@ -1031,6 +1055,7 @@ namespace ix | ||||
|         } | ||||
|         setReadyState(ReadyState::CLOSING); | ||||
|  | ||||
|         sendCloseFrame(code, reason); | ||||
|         // wake up the poll, but do not close yet | ||||
|         _socket->wakeUpFromPoll(Socket::kSendRequest); | ||||
|     } | ||||
|   | ||||
| @@ -25,6 +25,7 @@ | ||||
| #include "IXCancellationRequest.h" | ||||
| #include "IXWebSocketHandshake.h" | ||||
| #include "IXProgressCallback.h" | ||||
| #include "IXWebSocketCloseConstants.h" | ||||
|  | ||||
| namespace ix | ||||
| { | ||||
| @@ -91,11 +92,14 @@ namespace ix | ||||
|                                    const OnProgressCallback& onProgressCallback); | ||||
|         WebSocketSendInfo sendPing(const std::string& message); | ||||
|  | ||||
|         void close(uint16_t code = 1000, | ||||
|                    const std::string& reason = "Normal closure", | ||||
|         void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, | ||||
|                    const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage, | ||||
|                    size_t closeWireSize = 0, | ||||
|                    bool remote = false); | ||||
|  | ||||
|         void closeSocket(); | ||||
|         ssize_t send(); | ||||
|  | ||||
|         ReadyState getReadyState() const; | ||||
|         void setReadyState(ReadyState readyState); | ||||
|         void setOnCloseCallback(const OnCloseCallback& onCloseCallback); | ||||
| @@ -151,6 +155,7 @@ namespace ix | ||||
|  | ||||
|         // Underlying TCP socket | ||||
|         std::shared_ptr<Socket> _socket; | ||||
|         std::mutex _socketMutex; | ||||
|  | ||||
|         // Hold the state of the connection (OPEN, CLOSED, etc...) | ||||
|         std::atomic<ReadyState> _readyState; | ||||
| @@ -169,22 +174,11 @@ namespace ix | ||||
|  | ||||
|         // Used to cancel dns lookup + socket connect + http upgrade | ||||
|         std::atomic<bool> _requestInitCancellation; | ||||
|                | ||||
|  | ||||
|         mutable std::mutex _closingTimePointMutex; | ||||
|         std::chrono::time_point<std::chrono::steady_clock>_closingTimePoint; | ||||
|         static const int kClosingMaximumWaitingDelayInMs; | ||||
|  | ||||
|         // Constants for dealing with closing conneections | ||||
|         static const uint16_t kInternalErrorCode; | ||||
|         static const uint16_t kAbnormalCloseCode; | ||||
|         static const uint16_t kProtocolErrorCode; | ||||
|         static const uint16_t kNoStatusCodeErrorCode; | ||||
|         static const std::string kInternalErrorMessage; | ||||
|         static const std::string kAbnormalCloseMessage; | ||||
|         static const std::string kPingTimeoutMessage; | ||||
|         static const std::string kProtocolErrorMessage; | ||||
|         static const std::string kNoStatusCodeErrorMessage; | ||||
|  | ||||
|         // enable auto response to ping | ||||
|         std::atomic<bool> _enablePong; | ||||
|         static const bool kDefaultEnablePong; | ||||
|   | ||||
| @@ -1,21 +1,21 @@ | ||||
| /* | ||||
|  * Lightweight URL & URI parser (RFC 1738, RFC 3986) | ||||
|  * https://github.com/corporateshark/LUrlParser | ||||
|  *  | ||||
|  * | ||||
|  * The MIT License (MIT) | ||||
|  *  | ||||
|  * | ||||
|  * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) | ||||
|  *  | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  *  | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in all | ||||
|  * copies or substantial portions of the Software. | ||||
|  *  | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   | ||||
| @@ -1,21 +1,21 @@ | ||||
| /* | ||||
|  * Lightweight URL & URI parser (RFC 1738, RFC 3986) | ||||
|  * https://github.com/corporateshark/LUrlParser | ||||
|  *  | ||||
|  * | ||||
|  * The MIT License (MIT) | ||||
|  *  | ||||
|  * | ||||
|  * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) | ||||
|  *  | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  *  | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in all | ||||
|  * copies or substantial portions of the Software. | ||||
|  *  | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|   | ||||
| @@ -20,6 +20,8 @@ | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <cstddef> | ||||
| #include <string> | ||||
| #include <string.h> | ||||
|  | ||||
| class WebSocketHandshakeKeyGen { | ||||
|     template <int N, typename T> | ||||
| @@ -100,7 +102,12 @@ class WebSocketHandshakeKeyGen { | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     static inline void generate(const char input[24], char output[28]) { | ||||
|     static inline void generate(const std::string& inputStr, char output[28]) { | ||||
|  | ||||
|         char input[25] = {}; | ||||
|         strncpy(input, inputStr.c_str(), 25 - 1); | ||||
|         input[25 - 1] = '\0'; | ||||
|  | ||||
|         uint32_t b_output[5] = { | ||||
|             0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 | ||||
|         }; | ||||
|   | ||||
							
								
								
									
										10
									
								
								makefile
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								makefile
									
									
									
									
									
								
							| @@ -9,7 +9,10 @@ install: brew | ||||
| # on osx it is good practice to make /usr/local user writable | ||||
| # sudo chown -R `whoami`/staff /usr/local | ||||
| brew: | ||||
| 	mkdir -p build && (cd build ; cmake -DUSE_TLS=1 .. ; make -j install) | ||||
| 	mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j install) | ||||
|  | ||||
| ws: | ||||
| 	mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j) | ||||
|  | ||||
| uninstall: | ||||
| 	xargs rm -fv < build/install_manifest.txt | ||||
| @@ -48,8 +51,8 @@ test_server: | ||||
| test: | ||||
| 	python2.7 test/run.py | ||||
|  | ||||
| ws_test: all | ||||
| 	(cd ws ; bash test_ws.sh) | ||||
| ws_test: ws | ||||
| 	(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh) | ||||
|  | ||||
| # For the fork that is configured with appveyor | ||||
| rebase_upstream: | ||||
| @@ -64,3 +67,4 @@ install_cmake_for_linux: | ||||
|  | ||||
| .PHONY: test | ||||
| .PHONY: build | ||||
| .PHONY: ws | ||||
|   | ||||
| @@ -31,23 +31,34 @@ set (SOURCES | ||||
|   ../third_party/msgpack11/msgpack11.cpp | ||||
|   ../ws/ixcore/utils/IXCoreLogger.cpp | ||||
|  | ||||
|   IXDNSLookupTest.cpp | ||||
|   IXSocketTest.cpp | ||||
|   IXSocketConnectTest.cpp | ||||
|   IXWebSocketServerTest.cpp | ||||
|   IXWebSocketPingTest.cpp | ||||
|   IXWebSocketTestConnectionDisconnection.cpp | ||||
|   IXUrlParserTest.cpp | ||||
|   IXWebSocketServerTest.cpp | ||||
| ) | ||||
|  | ||||
| # Some unittest don't work on windows yet | ||||
| if (NOT WIN32) | ||||
| if (UNIX) | ||||
|   list(APPEND SOURCES | ||||
|     IXWebSocketPingTimeoutTest.cpp | ||||
|     IXDNSLookupTest.cpp | ||||
|     cmd_websocket_chat.cpp | ||||
|     IXWebSocketCloseTest.cpp | ||||
|     IXWebSocketPingTest.cpp | ||||
|     IXWebSocketPingTimeoutTest.cpp | ||||
|   ) | ||||
| endif() | ||||
|  | ||||
| # Some unittest fail for dubious reason on Ubuntu Xenial with TSAN | ||||
| if (MAC OR WIN32) | ||||
|   list(APPEND SOURCES | ||||
|     IXWebSocketMessageQTest.cpp | ||||
|   ) | ||||
| endif() | ||||
|  | ||||
| # Disable tests for now that are failing or not reliable | ||||
|  | ||||
| add_executable(ixwebsocket_unittest ${SOURCES}) | ||||
|  | ||||
| if (NOT WIN32) | ||||
|   | ||||
| @@ -17,8 +17,12 @@ TEST_CASE("socket_connect", "[net]") | ||||
| { | ||||
|     SECTION("Test connecting to a known hostname") | ||||
|     { | ||||
|         int port = getFreePort(); | ||||
|         ix::WebSocketServer server(port); | ||||
|         REQUIRE(startWebSocketEchoServer(server)); | ||||
|  | ||||
|         std::string errMsg; | ||||
|         int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return false; }); | ||||
|         int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return false; }); | ||||
|         std::cerr << "Error message: " << errMsg << std::endl; | ||||
|         REQUIRE(fd != -1); | ||||
|     } | ||||
| @@ -34,9 +38,13 @@ TEST_CASE("socket_connect", "[net]") | ||||
|  | ||||
|     SECTION("Test connecting to a good hostname, with cancellation") | ||||
|     { | ||||
|         int port = getFreePort(); | ||||
|         ix::WebSocketServer server(port); | ||||
|         REQUIRE(startWebSocketEchoServer(server)); | ||||
|  | ||||
|         std::string errMsg; | ||||
|         // The callback returning true means we are requesting cancellation | ||||
|         int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return true; }); | ||||
|         int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return true; }); | ||||
|         std::cerr << "Error message: " << errMsg << std::endl; | ||||
|         REQUIRE(fd == -1); | ||||
|     } | ||||
|   | ||||
| @@ -53,13 +53,17 @@ namespace ix | ||||
|  | ||||
| TEST_CASE("socket", "[socket]") | ||||
| { | ||||
|     SECTION("Connect to google HTTP server. Send GET request without header. Should return 200") | ||||
|     SECTION("Connect to a local websocket server over a free port. Send GET request without header. Should return 400") | ||||
|     { | ||||
|         // Start a server first which we'll hit with our socket code | ||||
|         int port = getFreePort(); | ||||
|         ix::WebSocketServer server(port); | ||||
|         REQUIRE(startWebSocketEchoServer(server)); | ||||
|  | ||||
|         std::string errMsg; | ||||
|         bool tls = false; | ||||
|         std::shared_ptr<Socket> socket = createSocket(tls, errMsg); | ||||
|         std::string host("www.google.com"); | ||||
|         int port = 80; | ||||
|         std::string host("127.0.0.1"); | ||||
|  | ||||
|         std::stringstream ss; | ||||
|         ss << "GET / HTTP/1.1\r\n"; | ||||
| @@ -67,14 +71,14 @@ TEST_CASE("socket", "[socket]") | ||||
|         ss << "\r\n"; | ||||
|         std::string request(ss.str()); | ||||
|  | ||||
|         int expectedStatus = 200; | ||||
|         int expectedStatus = 400; | ||||
|         int timeoutSecs = 3; | ||||
|  | ||||
|         testSocket(host, port, request, socket, expectedStatus, timeoutSecs); | ||||
|     } | ||||
|  | ||||
| #if defined(__APPLE__) || defined(__linux__) | ||||
|     SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200") | ||||
|     SECTION("Connect to google HTTPS server over port 443. Send GET request without header. Should return 200") | ||||
|     { | ||||
|         std::string errMsg; | ||||
|         bool tls = true; | ||||
|   | ||||
| @@ -170,4 +170,58 @@ namespace ix | ||||
|  | ||||
|         std::cout << prefix << ": " << s << " => " << ss.str() << std::endl; | ||||
|     } | ||||
|  | ||||
|     bool startWebSocketEchoServer(ix::WebSocketServer& server) | ||||
|     { | ||||
|         server.setOnConnectionCallback( | ||||
|             [&server](std::shared_ptr<ix::WebSocket> webSocket, | ||||
|                       std::shared_ptr<ConnectionState> connectionState) | ||||
|             { | ||||
|                 webSocket->setOnMessageCallback( | ||||
|                     [webSocket, connectionState, &server](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 connection"; | ||||
|                             Logger() << "Uri: " << openInfo.uri; | ||||
|                             Logger() << "Headers:"; | ||||
|                             for (auto it : openInfo.headers) | ||||
|                             { | ||||
|                                 Logger() << it.first << ": " << it.second; | ||||
|                             } | ||||
|                         } | ||||
|                         else if (messageType == ix::WebSocketMessageType::Close) | ||||
|                         { | ||||
|                             Logger() << "Closed connection"; | ||||
|                         } | ||||
|                         else if (messageType == ix::WebSocketMessageType::Message) | ||||
|                         { | ||||
|                             for (auto&& client : server.getClients()) | ||||
|                             { | ||||
|                                 if (client != webSocket) | ||||
|                                 { | ||||
|                                     client->send(str); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 ); | ||||
|             } | ||||
|         ); | ||||
|  | ||||
|         auto res = server.listen(); | ||||
|         if (!res.first) | ||||
|         { | ||||
|             Logger() << res.second; | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         server.start(); | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,8 @@ | ||||
| #include <sstream> | ||||
| #include <iostream> | ||||
| #include <mutex> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <ixwebsocket/IXWebSocketServer.h> | ||||
|  | ||||
| namespace ix | ||||
| { | ||||
| @@ -32,8 +34,9 @@ namespace ix | ||||
|             { | ||||
|                 std::lock_guard<std::mutex> lock(_mutex); | ||||
|  | ||||
|                 std::cerr << obj; | ||||
|                 std::cerr << std::endl; | ||||
|                 std::stringstream ss; | ||||
|                 ss << obj; | ||||
|                 spdlog::info(ss.str()); | ||||
|                 return *this; | ||||
|             } | ||||
|  | ||||
| @@ -44,4 +47,6 @@ namespace ix | ||||
|     void log(const std::string& msg); | ||||
|  | ||||
|     int getFreePort(); | ||||
|  | ||||
|     bool startWebSocketEchoServer(ix::WebSocketServer& server); | ||||
| } | ||||
|   | ||||
							
								
								
									
										456
									
								
								test/IXWebSocketCloseTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										456
									
								
								test/IXWebSocketCloseTest.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,456 @@ | ||||
| /* | ||||
|  *  IXWebSocketCloseTest.cpp | ||||
|  *  Author: Alexandre Konieczny | ||||
|  *  Copyright (c) 2019 Machine Zone. All rights reserved. | ||||
|  */ | ||||
|  | ||||
| #include <iostream> | ||||
| #include <sstream> | ||||
| #include <queue> | ||||
| #include <ixwebsocket/IXWebSocket.h> | ||||
| #include <ixwebsocket/IXWebSocketServer.h> | ||||
|  | ||||
| #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<std::mutex> lck(_mutexCloseData); | ||||
|  | ||||
|         return _closeCode; | ||||
|     } | ||||
|  | ||||
|     const std::string& WebSocketClient::getCloseReason() | ||||
|     { | ||||
|         std::lock_guard<std::mutex> lck(_mutexCloseData); | ||||
|  | ||||
|         return _closeReason; | ||||
|     } | ||||
|  | ||||
|     bool WebSocketClient::getCloseRemote() | ||||
|     { | ||||
|         std::lock_guard<std::mutex> 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); | ||||
|         _webSocket.disableAutomaticReconnection(); | ||||
|  | ||||
|         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"); | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Close) | ||||
|                 { | ||||
|                     std::stringstream ss; | ||||
|                     ss << "client disconnected(" | ||||
|                        << closeInfo.code | ||||
|                        << "," | ||||
|                        << closeInfo.reason | ||||
|                        << ")"; | ||||
|                     log(ss.str()); | ||||
|  | ||||
|                     std::lock_guard<std::mutex> lck(_mutexCloseData); | ||||
|  | ||||
|                     _closeCode = closeInfo.code; | ||||
|                     _closeReason = std::string(closeInfo.reason); | ||||
|                     _closeRemote = closeInfo.remote; | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Error) | ||||
|                 { | ||||
|                     ss << "Error ! " << error.reason; | ||||
|                     log(ss.str()); | ||||
|                 } | ||||
|                 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<ix::WebSocket> webSocket, | ||||
|                                              std::shared_ptr<ConnectionState> 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) | ||||
|                         { | ||||
|                             std::stringstream ss; | ||||
|                             ss << "Server closed connection(" | ||||
|                                << closeInfo.code | ||||
|                                << "," | ||||
|                                << closeInfo.reason | ||||
|                                << ")"; | ||||
|                             log(ss.str()); | ||||
|                              | ||||
|                             std::lock_guard<std::mutex> 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(500); | ||||
|  | ||||
|         webSocketClient.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() == false); | ||||
|  | ||||
|         { | ||||
|             std::lock_guard<std::mutex> 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(500); | ||||
|  | ||||
|         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<std::mutex> 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(500); | ||||
|  | ||||
|         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<std::mutex> 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(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| TEST_CASE("Websocket_server_close_immediatly", "[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(); | ||||
|  | ||||
|         server.stop(); | ||||
|  | ||||
|         ix::msleep(500); | ||||
|  | ||||
|         // ensure client close hasn't been called | ||||
|         REQUIRE(webSocketClient.getCloseCode() == 0); | ||||
|         REQUIRE(webSocketClient.getCloseReason() == ""); | ||||
|         REQUIRE(webSocketClient.getCloseRemote() == false); | ||||
|  | ||||
|         { | ||||
|             std::lock_guard<std::mutex> lck(mutexWrite); | ||||
|  | ||||
|             // Here we ensure that the code/reason wasn't received by the server | ||||
|             REQUIRE(serverReceivedCloseCode == 0); | ||||
|             REQUIRE(serverReceivedCloseReason == ""); | ||||
|             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(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										193
									
								
								test/IXWebSocketMessageQTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								test/IXWebSocketMessageQTest.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | ||||
| /* | ||||
|  *  IXWebSocketMessageQTest.cpp | ||||
|  *  Author: Korchynskyi Dmytro | ||||
|  *  Copyright (c) 2019 Machine Zone. All rights reserved. | ||||
|  */ | ||||
|  | ||||
| #include <ixwebsocket/IXWebSocket.h> | ||||
| #include <ixwebsocket/IXWebSocketServer.h> | ||||
| #include <ixwebsocket/IXWebSocketMessageQueue.h> | ||||
|  | ||||
| #include "IXTest.h" | ||||
| #include "catch.hpp" | ||||
| #include <thread> | ||||
|  | ||||
| using namespace ix; | ||||
|  | ||||
| namespace | ||||
| { | ||||
|     bool startServer(ix::WebSocketServer& server) | ||||
|     { | ||||
|         server.setOnConnectionCallback( | ||||
|             [&server](std::shared_ptr<ix::WebSocket> webSocket, | ||||
|                 std::shared_ptr<ConnectionState> connectionState) | ||||
|         { | ||||
|             webSocket->setOnMessageCallback( | ||||
|                 [connectionState, &server](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 connection"; | ||||
|                     connectionState->computeId(); | ||||
|                     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) | ||||
|                 { | ||||
|                     Logger() << "Closed connection"; | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Message) | ||||
|                 { | ||||
|                     Logger() << "Message received: " << str; | ||||
|  | ||||
|                     for (auto&& client : server.getClients()) | ||||
|                     { | ||||
|                         client->send(str); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             ); | ||||
|         } | ||||
|         ); | ||||
|  | ||||
|         auto res = server.listen(); | ||||
|         if (!res.first) | ||||
|         { | ||||
|             Logger() << res.second; | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         server.start(); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     class MsgQTestClient | ||||
|     { | ||||
|     public: | ||||
|         MsgQTestClient() | ||||
|         { | ||||
|             msgQ.bindWebsocket(&ws); | ||||
|  | ||||
|             msgQ.setOnMessageCallback([this](WebSocketMessageType messageType, | ||||
|                 const std::string & str, | ||||
|                 size_t wireSize, | ||||
|                 const WebSocketErrorInfo & error, | ||||
|                 const WebSocketOpenInfo & openInfo, | ||||
|                 const WebSocketCloseInfo & closeInfo) | ||||
|             { | ||||
|                 REQUIRE(mainThreadId == std::this_thread::get_id()); | ||||
|  | ||||
|                 std::stringstream ss; | ||||
|                 if (messageType == WebSocketMessageType::Open) | ||||
|                 { | ||||
|                     log("client connected"); | ||||
|                     sendNextMessage(); | ||||
|                 } | ||||
|                 else if (messageType == WebSocketMessageType::Close) | ||||
|                 { | ||||
|                     log("client disconnected"); | ||||
|                 } | ||||
|                 else if (messageType == WebSocketMessageType::Error) | ||||
|                 { | ||||
|                     ss << "Error ! " << error.reason; | ||||
|                     log(ss.str()); | ||||
|                     testDone = true; | ||||
|                 } | ||||
|                 else if (messageType == WebSocketMessageType::Pong) | ||||
|                 { | ||||
|                     ss << "Received pong message " << str; | ||||
|                     log(ss.str()); | ||||
|                 } | ||||
|                 else if (messageType == WebSocketMessageType::Ping) | ||||
|                 { | ||||
|                     ss << "Received ping message " << str; | ||||
|                     log(ss.str()); | ||||
|                 } | ||||
|                 else if (messageType == WebSocketMessageType::Message) | ||||
|                 { | ||||
|                     REQUIRE(str.compare("Hey dude!") == 0); | ||||
|                     ++receivedCount; | ||||
|                     ss << "Received message " << str; | ||||
|                     log(ss.str()); | ||||
|                     sendNextMessage(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     ss << "Invalid WebSocketMessageType"; | ||||
|                     log(ss.str()); | ||||
|                     testDone = true; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         void sendNextMessage() | ||||
|         { | ||||
|             if (receivedCount >= 3) | ||||
|             { | ||||
|                 testDone = true; | ||||
|                 succeeded = true; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 auto info = ws.sendText("Hey dude!"); | ||||
|                 if (info.success) | ||||
|                     log("sent message"); | ||||
|                 else | ||||
|                     log("send failed"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         void run(const std::string& url) | ||||
|         { | ||||
|             mainThreadId = std::this_thread::get_id(); | ||||
|             testDone = false; | ||||
|             receivedCount = 0; | ||||
|  | ||||
|             ws.setUrl(url); | ||||
|             ws.start(); | ||||
|  | ||||
|             while (!testDone) | ||||
|             { | ||||
|                 msgQ.poll(); | ||||
|                 msleep(50); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         bool isSucceeded() const { return succeeded; } | ||||
|  | ||||
|     private: | ||||
|         WebSocket ws; | ||||
|         WebSocketMessageQueue msgQ; | ||||
|         bool testDone = false; | ||||
|         uint32_t receivedCount = 0; | ||||
|         std::thread::id mainThreadId; | ||||
|         bool succeeded = false; | ||||
|     }; | ||||
| } | ||||
|  | ||||
| TEST_CASE("Websocket_message_queue", "[websocket_message_q]") | ||||
| { | ||||
|     SECTION("Send several messages") | ||||
|     { | ||||
|         int port = getFreePort(); | ||||
|         WebSocketServer server(port); | ||||
|         REQUIRE(startServer(server)); | ||||
|  | ||||
|         MsgQTestClient testClient; | ||||
|         testClient.run("ws://127.0.0.1:" + std::to_string(port)); | ||||
|         REQUIRE(testClient.isSucceeded()); | ||||
|  | ||||
|         server.stop(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -225,8 +225,8 @@ TEST_CASE("Websocket_ping_no_data_sent_setPingInterval", "[setPingInterval]") | ||||
|         // -> expected ping messages == 2 as 2100 seconds, 1 ping sent every second | ||||
|         REQUIRE(serverReceivedPingMessages == 2); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         ix::reportWebSocketTraffic(); | ||||
| @@ -272,8 +272,8 @@ TEST_CASE("Websocket_ping_data_sent_setPingInterval", "[setPingInterval]") | ||||
|         // -> expected ping messages == 3 as 900+900+1300 = 3100 seconds, 1 ping sent every second | ||||
|         REQUIRE(serverReceivedPingMessages == 3); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         ix::reportWebSocketTraffic(); | ||||
| @@ -325,8 +325,8 @@ TEST_CASE("Websocket_ping_data_sent_setPingInterval_half_full", "[setPingInterva | ||||
|  | ||||
|         webSocketClient.stop(); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         ix::reportWebSocketTraffic(); | ||||
| @@ -377,8 +377,8 @@ TEST_CASE("Websocket_ping_data_sent_setPingInterval_full", "[setPingInterval]") | ||||
|  | ||||
|         webSocketClient.stop(); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         ix::reportWebSocketTraffic(); | ||||
| @@ -422,8 +422,8 @@ TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod | ||||
|         // -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second | ||||
|         REQUIRE(serverReceivedPingMessages == 1); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         ix::reportWebSocketTraffic(); | ||||
| @@ -472,8 +472,8 @@ TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]") | ||||
|         // -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second | ||||
|         REQUIRE(serverReceivedPingMessages == 2); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         ix::reportWebSocketTraffic(); | ||||
|   | ||||
| @@ -259,8 +259,8 @@ TEST_CASE("Websocket_ping_timeout_not_checked", "[setPingTimeout]") | ||||
|  | ||||
|         webSocketClient.stop(); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         // Ensure client close was not by ping timeout | ||||
| @@ -298,7 +298,7 @@ TEST_CASE("Websocket_ping_no_timeout", "[setPingTimeout]") | ||||
|  | ||||
|         REQUIRE(server.getClients().size() == 1); | ||||
|  | ||||
|         ix::msleep(1100); | ||||
|         ix::msleep(1200); | ||||
|  | ||||
|         // Here we test ping timeout, no timeout | ||||
|         REQUIRE(serverReceivedPingMessages == 1); | ||||
| @@ -312,8 +312,8 @@ TEST_CASE("Websocket_ping_no_timeout", "[setPingTimeout]") | ||||
|  | ||||
|         webSocketClient.stop(); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         // Ensure client close was not by ping timeout | ||||
| @@ -359,12 +359,13 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]") | ||||
|         REQUIRE(webSocketClient.isClosed() == false); | ||||
|         REQUIRE(webSocketClient.closedDueToPingTimeout() == false); | ||||
|  | ||||
|         ix::msleep(200); | ||||
|         ix::msleep(300); | ||||
|  | ||||
|         // Here we test ping timeout, timeout | ||||
|         REQUIRE(serverReceivedPingMessages == 0); | ||||
|         REQUIRE(webSocketClient.getReceivedPongMessages() == 0); | ||||
|         // Ensure client close was not by ping timeout | ||||
|         // Ensure client close was by ping timeout | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(webSocketClient.isClosed() == true); | ||||
|         REQUIRE(webSocketClient.closedDueToPingTimeout() == true); | ||||
|  | ||||
| @@ -415,7 +416,8 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]") | ||||
|         // Here we test ping timeout, timeout | ||||
|         REQUIRE(serverReceivedPingMessages == 1); | ||||
|         REQUIRE(webSocketClient.getReceivedPongMessages() == 0); | ||||
|         // Ensure client close was not by ping timeout | ||||
|         // Ensure client close was by ping timeout | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(webSocketClient.isClosed() == true); | ||||
|         REQUIRE(webSocketClient.closedDueToPingTimeout() == true); | ||||
|  | ||||
| @@ -427,7 +429,6 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]") | ||||
|     } | ||||
| } | ||||
|  | ||||
| #if 0 // this test fails on travis / commenting it out for now to get back to a green travis state | ||||
| TEST_CASE("Websocket_ping_long_timeout", "[setPingTimeout]") | ||||
| { | ||||
|     SECTION("Make sure that ping messages don't have responses (no PONG).") | ||||
| @@ -456,7 +457,7 @@ TEST_CASE("Websocket_ping_long_timeout", "[setPingTimeout]") | ||||
|  | ||||
|         REQUIRE(server.getClients().size() == 1); | ||||
|  | ||||
|         ix::msleep(5900); | ||||
|         ix::msleep(5800); | ||||
|  | ||||
|         // Here we test ping timeout, no timeout yet (2 ping sent at 2s and 4s) | ||||
|         REQUIRE(serverReceivedPingMessages == 2); | ||||
| @@ -466,7 +467,7 @@ TEST_CASE("Websocket_ping_long_timeout", "[setPingTimeout]") | ||||
|         REQUIRE(webSocketClient.isClosed() == false); | ||||
|         REQUIRE(webSocketClient.closedDueToPingTimeout() == false); | ||||
|  | ||||
|         ix::msleep(200); | ||||
|         ix::msleep(600); | ||||
|  | ||||
|         // Here we test ping timeout, timeout (at 6 seconds) | ||||
|         REQUIRE(serverReceivedPingMessages == 2); | ||||
| @@ -482,4 +483,3 @@ TEST_CASE("Websocket_ping_long_timeout", "[setPingTimeout]") | ||||
|         ix::reportWebSocketTraffic(); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
|   | ||||
| @@ -62,33 +62,33 @@ namespace | ||||
|                 std::stringstream ss; | ||||
|                 if (messageType == ix::WebSocketMessageType::Open) | ||||
|                 { | ||||
|                     log("cmd_websocket_satori_chat: connected !"); | ||||
|                     log("TestConnectionDisconnection: connected !"); | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Close) | ||||
|                 { | ||||
|                     log("cmd_websocket_satori_chat: disconnected !"); | ||||
|                     log("TestConnectionDisconnection: disconnected !"); | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Error) | ||||
|                 { | ||||
|                     ss << "cmd_websocket_satori_chat: Error! "; | ||||
|                     ss << "TestConnectionDisconnection: Error! "; | ||||
|                     ss << error.reason; | ||||
|                     log(ss.str()); | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Message) | ||||
|                 { | ||||
|                     log("cmd_websocket_satori_chat: received message.!"); | ||||
|                     log("TestConnectionDisconnection: received message.!"); | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Ping) | ||||
|                 { | ||||
|                     log("cmd_websocket_satori_chat: received ping message.!"); | ||||
|                     log("TestConnectionDisconnection: received ping message.!"); | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Pong) | ||||
|                 { | ||||
|                     log("cmd_websocket_satori_chat: received pong message.!"); | ||||
|                     log("TestConnectionDisconnection: received pong message.!"); | ||||
|                 } | ||||
|                 else if (messageType == ix::WebSocketMessageType::Fragment) | ||||
|                 { | ||||
|                     log("cmd_websocket_satori_chat: received fragment.!"); | ||||
|                     log("TestConnectionDisconnection: received fragment.!"); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
| @@ -96,6 +96,12 @@ namespace | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         _webSocket.enableAutomaticReconnection(); | ||||
|         REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == true); | ||||
|  | ||||
|         _webSocket.disableAutomaticReconnection(); | ||||
|         REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == false); | ||||
|  | ||||
|         // Start the connection | ||||
|         _webSocket.start(); | ||||
|     } | ||||
| @@ -123,26 +129,38 @@ TEST_CASE("websocket_connections", "[websocket]") | ||||
|     SECTION("Try to connect and disconnect with different timing, not enough time to succesfully connect") | ||||
|     { | ||||
|         IXWebSocketTestConnectionDisconnection test; | ||||
|         log(std::string("50 Runs")); | ||||
|  | ||||
|         for (int i = 0; i < 50; ++i) | ||||
|         { | ||||
|             log(std::string("Run: ") + std::to_string(i)); | ||||
|             test.start(WEBSOCKET_DOT_ORG_URL); | ||||
|  | ||||
|             log(std::string("Sleeping")); | ||||
|             ix::msleep(i); | ||||
|  | ||||
|             log(std::string("Stopping")); | ||||
|             test.stop(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // This test breaks on travis CI - Ubuntu Xenial + gcc + tsan | ||||
|     // We should fix this. | ||||
|     /*SECTION("Try to connect and disconnect with different timing, from not enough time to successfull connect") | ||||
|     SECTION("Try to connect and disconnect with different timing, from not enough time to successfull connect") | ||||
|     { | ||||
|         IXWebSocketTestConnectionDisconnection test; | ||||
|         log(std::string("20 Runs")); | ||||
|  | ||||
|         for (int i = 0; i < 20; ++i) | ||||
|         { | ||||
|             log(std::string("Run: ") + std::to_string(i)); | ||||
|             test.start(WEBSOCKET_DOT_ORG_URL); | ||||
|  | ||||
|             log(std::string("Sleeping")); | ||||
|             ix::msleep(i*50); | ||||
|  | ||||
|             log(std::string("Stopping")); | ||||
|             test.stop(); | ||||
|         } | ||||
|     }*/ | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -336,8 +336,8 @@ TEST_CASE("Websocket_chat", "[websocket_chat]") | ||||
|         REQUIRE(chatA.getReceivedMessages()[1] == "from B2"); | ||||
|         REQUIRE(chatA.getReceivedMessages()[2].size() == bigMessage.size()); | ||||
|  | ||||
|         // Give us 500ms for the server to notice that clients went away | ||||
|         ix::msleep(500); | ||||
|         // Give us 1000ms for the server to notice that clients went away | ||||
|         ix::msleep(1000); | ||||
|         REQUIRE(server.getClients().size() == 0); | ||||
|  | ||||
|         ix::reportWebSocketTraffic(); | ||||
|   | ||||
							
								
								
									
										52
									
								
								test/run.py
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								test/run.py
									
									
									
									
									
								
							| @@ -1,10 +1,4 @@ | ||||
| #!/usr/bin/env python2.7 | ||||
| ''' | ||||
| Windows notes: | ||||
|     generator = '-G"NMake Makefiles"' | ||||
|     make = 'nmake' | ||||
|     testBinary ='ixwebsocket_unittest.exe' | ||||
| ''' | ||||
|  | ||||
| from __future__ import print_function | ||||
|  | ||||
| @@ -103,7 +97,8 @@ def runCMake(sanitizer, buildDir): | ||||
|  | ||||
|     if platform.system() == 'Windows': | ||||
|         #generator = '"NMake Makefiles"' | ||||
|         generator = '"Visual Studio 16 2019"' | ||||
|         #generator = '"Visual Studio 16 2019"' | ||||
|         generator = '"Visual Studio 15 2017"' | ||||
|     else: | ||||
|         generator = '"Unix Makefiles"' | ||||
|  | ||||
| @@ -274,12 +269,12 @@ def executeJob(job): | ||||
|     return job | ||||
|  | ||||
|  | ||||
| def executeJobs(jobs): | ||||
| def executeJobs(jobs, cpuCount): | ||||
|     '''Execute a list of job concurrently on multiple CPU/cores''' | ||||
|  | ||||
|     poolSize = multiprocessing.cpu_count() | ||||
|     print('Using {} cores to execute the unittest'.format(cpuCount)) | ||||
|  | ||||
|     pool = multiprocessing.Pool(poolSize) | ||||
|     pool = multiprocessing.Pool(cpuCount) | ||||
|     results = pool.map(executeJob, jobs) | ||||
|     pool.close() | ||||
|     pool.join() | ||||
| @@ -351,26 +346,22 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime): | ||||
|         f.write(content.encode('utf-8')) | ||||
|  | ||||
|  | ||||
| def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLDB): | ||||
| def run(testName, buildDir, sanitizer, xmlOutput, | ||||
|         testRunName, buildOnly, useLLDB, cpuCount): | ||||
|     '''Main driver. Run cmake, compiles, execute and validate the testsuite.''' | ||||
|  | ||||
|     # gen build files with CMake | ||||
|     runCMake(sanitizer, buildDir) | ||||
|  | ||||
|     # build with make | ||||
|     #makeCmd = 'cmake --build ' | ||||
|     #jobs = '-j8' | ||||
|  | ||||
|     #if platform.system() == 'Windows': | ||||
|     #    makeCmd = 'nmake' | ||||
|  | ||||
|         # nmake does not have a -j option | ||||
|     #    jobs = '' | ||||
|  | ||||
|     #runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs)) | ||||
|  | ||||
|     # build with cmake | ||||
|     runCommand('cmake --build ' + buildDir) | ||||
|     if platform.system() == 'Linux': | ||||
|         # build with make -j | ||||
|         runCommand('make -C {} -j 2'.format(buildDir)) | ||||
|     elif platform.system() == 'Darwin': | ||||
|         # build with make | ||||
|         runCommand('make -C {} -j 8'.format(buildDir)) | ||||
|     else: | ||||
|         # build with cmake on recent | ||||
|         runCommand('cmake --build --parallel {}'.format(buildDir)) | ||||
|  | ||||
|     if buildOnly: | ||||
|         return | ||||
| @@ -415,7 +406,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD | ||||
|         }) | ||||
|  | ||||
|     start = time.time() | ||||
|     results = executeJobs(jobs) | ||||
|     results = executeJobs(jobs, cpuCount) | ||||
|     runTime = time.time() - start | ||||
|     generateXmlOutput(results, xmlOutput, testRunName, runTime) | ||||
|  | ||||
| @@ -465,9 +456,16 @@ def main(): | ||||
|                         help='Run the test through lldb.') | ||||
|     parser.add_argument('--run_name', '-n', | ||||
|                         help='Name of the test run.') | ||||
|     parser.add_argument('--cpu_count', '-j', type=int, default=multiprocessing.cpu_count(), | ||||
|                         help='Number of cpus to use for running the tests.') | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Windows does not play nice with multiple files opened by different processes | ||||
|     # "The process cannot access the file because it is being used by another process" | ||||
|     if platform.system() == 'Windows': | ||||
|         args.cpu_count = 1 | ||||
|  | ||||
|     # Default sanitizer is tsan | ||||
|     sanitizer = args.sanitizer | ||||
|  | ||||
| @@ -510,7 +508,7 @@ def main(): | ||||
|         args.lldb = False | ||||
|  | ||||
|     return run(args.test, buildDir, sanitizer, xmlOutput,  | ||||
|                testRunName, args.build_only, args.lldb) | ||||
|                testRunName, args.build_only, args.lldb, args.cpu_count) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   | ||||
							
								
								
									
										638
									
								
								third_party/spdlog/README.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										638
									
								
								third_party/spdlog/README.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,319 +1,319 @@ | ||||
| # spdlog | ||||
|  | ||||
| Very fast, header only, C++ logging library. [](https://travis-ci.org/gabime/spdlog)  [](https://ci.appveyor.com/project/gabime/spdlog) | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Install | ||||
| #### Just copy the headers: | ||||
|  | ||||
| * Copy the source [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler. | ||||
|  | ||||
| #### Or use your favorite package manager: | ||||
|  | ||||
| * Ubuntu: `apt-get install libspdlog-dev` | ||||
| * Homebrew: `brew install spdlog` | ||||
| * FreeBSD:  `cd /usr/ports/devel/spdlog/ && make install clean` | ||||
| * Fedora: `yum install spdlog` | ||||
| * Gentoo: `emerge dev-libs/spdlog` | ||||
| * Arch Linux: `yaourt -S spdlog-git` | ||||
| * vcpkg: `vcpkg install spdlog` | ||||
|   | ||||
|  | ||||
| ## Platforms | ||||
|  * Linux, FreeBSD, OpenBSD, Solaris, AIX | ||||
|  * Windows (msvc 2013+, cygwin) | ||||
|  * macOS (clang 3.5+) | ||||
|  * Android | ||||
|  | ||||
| ## Features | ||||
| * Very fast (see [benchmarks](#benchmarks) below). | ||||
| * Headers only, just copy and use. | ||||
| * Feature rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library. | ||||
| * Fast asynchronous mode (optional) | ||||
| * [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. | ||||
| * Multi/Single threaded loggers. | ||||
| * Various log targets: | ||||
|     * Rotating log files. | ||||
|     * Daily log files. | ||||
|     * Console logging (colors supported). | ||||
|     * syslog. | ||||
|     * Windows debugger (```OutputDebugString(..)```) | ||||
|     * Easily extendable with custom log targets  (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). | ||||
| * Severity based filtering - threshold levels can be modified in runtime as well as in compile time. | ||||
| * Binary data logging. | ||||
|  | ||||
|  | ||||
| ## Benchmarks | ||||
|  | ||||
| Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz | ||||
|  | ||||
| #### Synchronous mode | ||||
| ``` | ||||
| ******************************************************************************* | ||||
| Single thread, 1,000,000 iterations | ||||
| ******************************************************************************* | ||||
| basic_st...             Elapsed: 0.181652       5,505,042/sec | ||||
| rotating_st...          Elapsed: 0.181781       5,501,117/sec | ||||
| daily_st...             Elapsed: 0.187595       5,330,630/sec | ||||
| null_st...              Elapsed: 0.0504704      19,813,602/sec | ||||
| ******************************************************************************* | ||||
| 10 threads sharing same logger, 1,000,000 iterations | ||||
| ******************************************************************************* | ||||
| basic_mt...             Elapsed: 0.616035       1,623,284/sec | ||||
| rotating_mt...          Elapsed: 0.620344       1,612,008/sec | ||||
| daily_mt...             Elapsed: 0.648353       1,542,369/sec | ||||
| null_mt...              Elapsed: 0.151972       6,580,166/sec | ||||
| ```  | ||||
| #### Asynchronous mode | ||||
| ``` | ||||
| ******************************************************************************* | ||||
| 10 threads sharing same logger, 1,000,000 iterations  | ||||
| ******************************************************************************* | ||||
| async...                Elapsed: 0.350066       2,856,606/sec | ||||
| async...                Elapsed: 0.314865       3,175,960/sec | ||||
| async...                Elapsed: 0.349851       2,858,358/sec | ||||
| ``` | ||||
|  | ||||
| ## Usage samples | ||||
|  | ||||
| #### Basic usage | ||||
| ```c++ | ||||
| #include "spdlog/spdlog.h" | ||||
| int main()  | ||||
| { | ||||
|     spdlog::info("Welcome to spdlog!"); | ||||
|     spdlog::error("Some error message with arg: {}", 1); | ||||
|      | ||||
|     spdlog::warn("Easy padding in numbers like {:08d}", 12); | ||||
|     spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42); | ||||
|     spdlog::info("Support for floats {:03.2f}", 1.23456); | ||||
|     spdlog::info("Positional args are {1} {0}..", "too", "supported"); | ||||
|     spdlog::info("{:<30}", "left aligned"); | ||||
|      | ||||
|     spdlog::set_level(spdlog::level::debug); // Set global log level to debug | ||||
|     spdlog::debug("This message should be displayed..");     | ||||
|      | ||||
|     // change log pattern | ||||
|     spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); | ||||
|      | ||||
|     // Compile time log levels | ||||
|     // define SPDLOG_ACTIVE_LEVEL to desired level | ||||
|     SPDLOG_TRACE("Some trace message with param {}", {}); | ||||
|     SPDLOG_DEBUG("Some debug message"); | ||||
|          | ||||
| } | ||||
| ``` | ||||
| #### create stdout/stderr logger object | ||||
| ```c++ | ||||
| #include "spdlog/spdlog.h" | ||||
| #include "spdlog/sinks/stdout_color_sinks.h" | ||||
| void stdout_example() | ||||
| { | ||||
|     // create color multi threaded logger | ||||
|     auto console = spdlog::stdout_color_mt("console");     | ||||
|     auto err_logger = spdlog::stderr_color_mt("stderr");     | ||||
|     spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); | ||||
| } | ||||
| ``` | ||||
| --- | ||||
| #### Basic file logger | ||||
| ```c++ | ||||
| #include "spdlog/sinks/basic_file_sink.h" | ||||
| void basic_logfile_example() | ||||
| { | ||||
|     try  | ||||
|     { | ||||
|         auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); | ||||
|     } | ||||
|     catch (const spdlog::spdlog_ex &ex) | ||||
|     { | ||||
|         std::cout << "Log init failed: " << ex.what() << std::endl; | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| --- | ||||
| #### Rotating files | ||||
| ```c++ | ||||
| #include "spdlog/sinks/rotating_file_sink.h" | ||||
| void rotating_example() | ||||
| { | ||||
|     // Create a file rotating logger with 5mb size max and 3 rotated files | ||||
|     auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Daily files | ||||
| ```c++ | ||||
|  | ||||
| #include "spdlog/sinks/daily_file_sink.h" | ||||
| void daily_example() | ||||
| { | ||||
|     // Create a daily logger - a new file is created every day on 2:30am | ||||
|     auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Cloning loggers  | ||||
| ```c++ | ||||
| // clone a logger and give it new name. | ||||
| // Useful for creating subsystem loggers from some "root" logger | ||||
| void clone_example() | ||||
| { | ||||
|     auto network_logger = spdlog::get("root")->clone("network"); | ||||
|     network_logger->info("Logging network stuff.."); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Periodic flush | ||||
| ```c++ | ||||
| // periodically flush all *registered* loggers every 3 seconds: | ||||
| // warning: only use if all your loggers are thread safe! | ||||
| spdlog::flush_every(std::chrono::seconds(3)); | ||||
|  | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Binary logging | ||||
| ```c++ | ||||
| // log binary data as hex. | ||||
| // many types of std::container<char> types can be used. | ||||
| // ranges are supported too. | ||||
| // format flags: | ||||
| // {:X} - print in uppercase. | ||||
| // {:s} - don't separate each byte with space. | ||||
| // {:p} - don't print the position on each line start. | ||||
| // {:n} - don't split the output to lines. | ||||
|  | ||||
| #include "spdlog/fmt/bin_to_hex.h" | ||||
|  | ||||
| void binary_example() | ||||
| { | ||||
|     auto console = spdlog::get("console"); | ||||
|     std::array<char, 80> buf; | ||||
|     console->info("Binary example: {}", spdlog::to_hex(buf)); | ||||
|     console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10)); | ||||
|     // more examples: | ||||
|     // logger->info("uppercase: {:X}", spdlog::to_hex(buf)); | ||||
|     // logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf)); | ||||
|     // logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf)); | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Logger with multi sinks - each with different format and log level | ||||
| ```c++ | ||||
|  | ||||
| // create logger with 2 targets with different log levels and formats. | ||||
| // the console will show only warnings or errors, while the file will log all. | ||||
| void multi_sink_example() | ||||
| { | ||||
|     auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); | ||||
|     console_sink->set_level(spdlog::level::warn); | ||||
|     console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); | ||||
|  | ||||
|     auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true); | ||||
|     file_sink->set_level(spdlog::level::trace); | ||||
|  | ||||
|     spdlog::logger logger("multi_sink", {console_sink, file_sink}); | ||||
|     logger.set_level(spdlog::level::debug); | ||||
|     logger.warn("this should appear in both console and file"); | ||||
|     logger.info("this message should not appear in the console, only in the file"); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Asynchronous logging | ||||
| ```c++ | ||||
| #include "spdlog/async.h" | ||||
| #include "spdlog/sinks/basic_file_sink.h" | ||||
| void async_example() | ||||
| { | ||||
|     // default thread pool settings can be modified *before* creating the async logger: | ||||
|     // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. | ||||
|     auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt"); | ||||
|     // alternatively: | ||||
|     // auto async_file = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt");    | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Asynchronous logger with multi sinks   | ||||
| ```c++ | ||||
| #include "spdlog/sinks/stdout_color_sinks.h" | ||||
| #include "spdlog/sinks/rotating_file_sink.h" | ||||
|  | ||||
| void multi_sink_example2() | ||||
| { | ||||
|     spdlog::init_thread_pool(8192, 1); | ||||
|     auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt >(); | ||||
|     auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("mylog.txt", 1024*1024*10, 3); | ||||
|     std::vector<spdlog::sink_ptr> sinks {stdout_sink, rotating_sink}; | ||||
|     auto logger = std::make_shared<spdlog::async_logger>("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); | ||||
|     spdlog::register_logger(logger); | ||||
| } | ||||
| ``` | ||||
|   | ||||
| --- | ||||
| #### User defined types | ||||
| ```c++ | ||||
| // user defined types logging by implementing operator<< | ||||
| #include "spdlog/fmt/ostr.h" // must be included | ||||
| struct my_type | ||||
| { | ||||
|     int i; | ||||
|     template<typename OStream> | ||||
|     friend OStream &operator<<(OStream &os, const my_type &c) | ||||
|     { | ||||
|         return os << "[my_type i=" << c.i << "]"; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| void user_defined_example() | ||||
| { | ||||
|     spdlog::get("console")->info("user defined type: {}", my_type{14}); | ||||
| } | ||||
|  | ||||
| ``` | ||||
| --- | ||||
| #### Custom error handler | ||||
| ```c++ | ||||
| void err_handler_example() | ||||
| { | ||||
|     // can be set globally or per logger(logger->set_error_handler(..)) | ||||
|     spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); }); | ||||
|     spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); | ||||
| } | ||||
|  | ||||
| ``` | ||||
| --- | ||||
| #### syslog  | ||||
| ```c++ | ||||
| #include "spdlog/sinks/syslog_sink.h" | ||||
| void syslog_example() | ||||
| { | ||||
|     std::string ident = "spdlog-example"; | ||||
|     auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); | ||||
|     syslog_logger->warn("This is warning that will end up in syslog."); | ||||
| } | ||||
| ``` | ||||
| --- | ||||
| #### Android example  | ||||
| ```c++ | ||||
| #include "spdlog/sinks/android_sink.h" | ||||
| void android_example() | ||||
| { | ||||
|     std::string tag = "spdlog-android"; | ||||
|     auto android_logger = spdlog::android_logger("android", tag); | ||||
|     android_logger->critical("Use \"adb shell logcat\" to view this message."); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Documentation | ||||
| Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages. | ||||
| # spdlog | ||||
|  | ||||
| Very fast, header only, C++ logging library. [](https://travis-ci.org/gabime/spdlog)  [](https://ci.appveyor.com/project/gabime/spdlog) | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Install | ||||
| #### Just copy the headers: | ||||
|  | ||||
| * Copy the source [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler. | ||||
|  | ||||
| #### Or use your favorite package manager: | ||||
|  | ||||
| * Ubuntu: `apt-get install libspdlog-dev` | ||||
| * Homebrew: `brew install spdlog` | ||||
| * FreeBSD:  `cd /usr/ports/devel/spdlog/ && make install clean` | ||||
| * Fedora: `yum install spdlog` | ||||
| * Gentoo: `emerge dev-libs/spdlog` | ||||
| * Arch Linux: `yaourt -S spdlog-git` | ||||
| * vcpkg: `vcpkg install spdlog` | ||||
|  | ||||
|  | ||||
| ## Platforms | ||||
|  * Linux, FreeBSD, OpenBSD, Solaris, AIX | ||||
|  * Windows (msvc 2013+, cygwin) | ||||
|  * macOS (clang 3.5+) | ||||
|  * Android | ||||
|  | ||||
| ## Features | ||||
| * Very fast (see [benchmarks](#benchmarks) below). | ||||
| * Headers only, just copy and use. | ||||
| * Feature rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library. | ||||
| * Fast asynchronous mode (optional) | ||||
| * [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. | ||||
| * Multi/Single threaded loggers. | ||||
| * Various log targets: | ||||
|     * Rotating log files. | ||||
|     * Daily log files. | ||||
|     * Console logging (colors supported). | ||||
|     * syslog. | ||||
|     * Windows debugger (```OutputDebugString(..)```) | ||||
|     * Easily extendable with custom log targets  (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). | ||||
| * Severity based filtering - threshold levels can be modified in runtime as well as in compile time. | ||||
| * Binary data logging. | ||||
|  | ||||
|  | ||||
| ## Benchmarks | ||||
|  | ||||
| Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz | ||||
|  | ||||
| #### Synchronous mode | ||||
| ``` | ||||
| ******************************************************************************* | ||||
| Single thread, 1,000,000 iterations | ||||
| ******************************************************************************* | ||||
| basic_st...             Elapsed: 0.181652       5,505,042/sec | ||||
| rotating_st...          Elapsed: 0.181781       5,501,117/sec | ||||
| daily_st...             Elapsed: 0.187595       5,330,630/sec | ||||
| null_st...              Elapsed: 0.0504704      19,813,602/sec | ||||
| ******************************************************************************* | ||||
| 10 threads sharing same logger, 1,000,000 iterations | ||||
| ******************************************************************************* | ||||
| basic_mt...             Elapsed: 0.616035       1,623,284/sec | ||||
| rotating_mt...          Elapsed: 0.620344       1,612,008/sec | ||||
| daily_mt...             Elapsed: 0.648353       1,542,369/sec | ||||
| null_mt...              Elapsed: 0.151972       6,580,166/sec | ||||
| ``` | ||||
| #### Asynchronous mode | ||||
| ``` | ||||
| ******************************************************************************* | ||||
| 10 threads sharing same logger, 1,000,000 iterations | ||||
| ******************************************************************************* | ||||
| async...                Elapsed: 0.350066       2,856,606/sec | ||||
| async...                Elapsed: 0.314865       3,175,960/sec | ||||
| async...                Elapsed: 0.349851       2,858,358/sec | ||||
| ``` | ||||
|  | ||||
| ## Usage samples | ||||
|  | ||||
| #### Basic usage | ||||
| ```c++ | ||||
| #include "spdlog/spdlog.h" | ||||
| int main() | ||||
| { | ||||
|     spdlog::info("Welcome to spdlog!"); | ||||
|     spdlog::error("Some error message with arg: {}", 1); | ||||
|  | ||||
|     spdlog::warn("Easy padding in numbers like {:08d}", 12); | ||||
|     spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42); | ||||
|     spdlog::info("Support for floats {:03.2f}", 1.23456); | ||||
|     spdlog::info("Positional args are {1} {0}..", "too", "supported"); | ||||
|     spdlog::info("{:<30}", "left aligned"); | ||||
|  | ||||
|     spdlog::set_level(spdlog::level::debug); // Set global log level to debug | ||||
|     spdlog::debug("This message should be displayed.."); | ||||
|  | ||||
|     // change log pattern | ||||
|     spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); | ||||
|  | ||||
|     // Compile time log levels | ||||
|     // define SPDLOG_ACTIVE_LEVEL to desired level | ||||
|     SPDLOG_TRACE("Some trace message with param {}", {}); | ||||
|     SPDLOG_DEBUG("Some debug message"); | ||||
|  | ||||
| } | ||||
| ``` | ||||
| #### create stdout/stderr logger object | ||||
| ```c++ | ||||
| #include "spdlog/spdlog.h" | ||||
| #include "spdlog/sinks/stdout_color_sinks.h" | ||||
| void stdout_example() | ||||
| { | ||||
|     // create color multi threaded logger | ||||
|     auto console = spdlog::stdout_color_mt("console"); | ||||
|     auto err_logger = spdlog::stderr_color_mt("stderr"); | ||||
|     spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); | ||||
| } | ||||
| ``` | ||||
| --- | ||||
| #### Basic file logger | ||||
| ```c++ | ||||
| #include "spdlog/sinks/basic_file_sink.h" | ||||
| void basic_logfile_example() | ||||
| { | ||||
|     try | ||||
|     { | ||||
|         auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); | ||||
|     } | ||||
|     catch (const spdlog::spdlog_ex &ex) | ||||
|     { | ||||
|         std::cout << "Log init failed: " << ex.what() << std::endl; | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| --- | ||||
| #### Rotating files | ||||
| ```c++ | ||||
| #include "spdlog/sinks/rotating_file_sink.h" | ||||
| void rotating_example() | ||||
| { | ||||
|     // Create a file rotating logger with 5mb size max and 3 rotated files | ||||
|     auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Daily files | ||||
| ```c++ | ||||
|  | ||||
| #include "spdlog/sinks/daily_file_sink.h" | ||||
| void daily_example() | ||||
| { | ||||
|     // Create a daily logger - a new file is created every day on 2:30am | ||||
|     auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Cloning loggers | ||||
| ```c++ | ||||
| // clone a logger and give it new name. | ||||
| // Useful for creating subsystem loggers from some "root" logger | ||||
| void clone_example() | ||||
| { | ||||
|     auto network_logger = spdlog::get("root")->clone("network"); | ||||
|     network_logger->info("Logging network stuff.."); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Periodic flush | ||||
| ```c++ | ||||
| // periodically flush all *registered* loggers every 3 seconds: | ||||
| // warning: only use if all your loggers are thread safe! | ||||
| spdlog::flush_every(std::chrono::seconds(3)); | ||||
|  | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Binary logging | ||||
| ```c++ | ||||
| // log binary data as hex. | ||||
| // many types of std::container<char> types can be used. | ||||
| // ranges are supported too. | ||||
| // format flags: | ||||
| // {:X} - print in uppercase. | ||||
| // {:s} - don't separate each byte with space. | ||||
| // {:p} - don't print the position on each line start. | ||||
| // {:n} - don't split the output to lines. | ||||
|  | ||||
| #include "spdlog/fmt/bin_to_hex.h" | ||||
|  | ||||
| void binary_example() | ||||
| { | ||||
|     auto console = spdlog::get("console"); | ||||
|     std::array<char, 80> buf; | ||||
|     console->info("Binary example: {}", spdlog::to_hex(buf)); | ||||
|     console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10)); | ||||
|     // more examples: | ||||
|     // logger->info("uppercase: {:X}", spdlog::to_hex(buf)); | ||||
|     // logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf)); | ||||
|     // logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf)); | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Logger with multi sinks - each with different format and log level | ||||
| ```c++ | ||||
|  | ||||
| // create logger with 2 targets with different log levels and formats. | ||||
| // the console will show only warnings or errors, while the file will log all. | ||||
| void multi_sink_example() | ||||
| { | ||||
|     auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); | ||||
|     console_sink->set_level(spdlog::level::warn); | ||||
|     console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); | ||||
|  | ||||
|     auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true); | ||||
|     file_sink->set_level(spdlog::level::trace); | ||||
|  | ||||
|     spdlog::logger logger("multi_sink", {console_sink, file_sink}); | ||||
|     logger.set_level(spdlog::level::debug); | ||||
|     logger.warn("this should appear in both console and file"); | ||||
|     logger.info("this message should not appear in the console, only in the file"); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Asynchronous logging | ||||
| ```c++ | ||||
| #include "spdlog/async.h" | ||||
| #include "spdlog/sinks/basic_file_sink.h" | ||||
| void async_example() | ||||
| { | ||||
|     // default thread pool settings can be modified *before* creating the async logger: | ||||
|     // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. | ||||
|     auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt"); | ||||
|     // alternatively: | ||||
|     // auto async_file = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt"); | ||||
| } | ||||
|  | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### Asynchronous logger with multi sinks | ||||
| ```c++ | ||||
| #include "spdlog/sinks/stdout_color_sinks.h" | ||||
| #include "spdlog/sinks/rotating_file_sink.h" | ||||
|  | ||||
| void multi_sink_example2() | ||||
| { | ||||
|     spdlog::init_thread_pool(8192, 1); | ||||
|     auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt >(); | ||||
|     auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("mylog.txt", 1024*1024*10, 3); | ||||
|     std::vector<spdlog::sink_ptr> sinks {stdout_sink, rotating_sink}; | ||||
|     auto logger = std::make_shared<spdlog::async_logger>("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); | ||||
|     spdlog::register_logger(logger); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
| #### User defined types | ||||
| ```c++ | ||||
| // user defined types logging by implementing operator<< | ||||
| #include "spdlog/fmt/ostr.h" // must be included | ||||
| struct my_type | ||||
| { | ||||
|     int i; | ||||
|     template<typename OStream> | ||||
|     friend OStream &operator<<(OStream &os, const my_type &c) | ||||
|     { | ||||
|         return os << "[my_type i=" << c.i << "]"; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| void user_defined_example() | ||||
| { | ||||
|     spdlog::get("console")->info("user defined type: {}", my_type{14}); | ||||
| } | ||||
|  | ||||
| ``` | ||||
| --- | ||||
| #### Custom error handler | ||||
| ```c++ | ||||
| void err_handler_example() | ||||
| { | ||||
|     // can be set globally or per logger(logger->set_error_handler(..)) | ||||
|     spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); }); | ||||
|     spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); | ||||
| } | ||||
|  | ||||
| ``` | ||||
| --- | ||||
| #### syslog | ||||
| ```c++ | ||||
| #include "spdlog/sinks/syslog_sink.h" | ||||
| void syslog_example() | ||||
| { | ||||
|     std::string ident = "spdlog-example"; | ||||
|     auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); | ||||
|     syslog_logger->warn("This is warning that will end up in syslog."); | ||||
| } | ||||
| ``` | ||||
| --- | ||||
| #### Android example | ||||
| ```c++ | ||||
| #include "spdlog/sinks/android_sink.h" | ||||
| void android_example() | ||||
| { | ||||
|     std::string tag = "spdlog-android"; | ||||
|     auto android_logger = spdlog::android_logger("android", tag); | ||||
|     android_logger->critical("Use \"adb shell logcat\" to view this message."); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Documentation | ||||
| Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages. | ||||
|   | ||||
| @@ -713,7 +713,7 @@ typedef basic_format_args<wprintf_context> wprintf_args; | ||||
| /** | ||||
|   \rst | ||||
|   Constructs an `~fmt::format_arg_store` object that contains references to | ||||
|   arguments and can be implicitly converted to `~fmt::printf_args`.  | ||||
|   arguments and can be implicitly converted to `~fmt::printf_args`. | ||||
|   \endrst | ||||
|  */ | ||||
| template<typename... Args> | ||||
| @@ -723,7 +723,7 @@ inline format_arg_store<printf_context, Args...> | ||||
| /** | ||||
|   \rst | ||||
|   Constructs an `~fmt::format_arg_store` object that contains references to | ||||
|   arguments and can be implicitly converted to `~fmt::wprintf_args`.  | ||||
|   arguments and can be implicitly converted to `~fmt::wprintf_args`. | ||||
|   \endrst | ||||
|  */ | ||||
| template<typename... Args> | ||||
|   | ||||
| @@ -159,7 +159,7 @@ void for_each(index_sequence<Is...>, Tuple &&tup, F &&f) FMT_NOEXCEPT { | ||||
| } | ||||
|  | ||||
| template <class T> | ||||
| FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value>  | ||||
| FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> | ||||
| get_indexes(T const &) { return {}; } | ||||
|  | ||||
| template <class Tuple, class F> | ||||
| @@ -169,14 +169,14 @@ void for_each(Tuple &&tup, F &&f) { | ||||
| } | ||||
|  | ||||
| template<typename Arg> | ||||
| FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,  | ||||
| FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, | ||||
|   typename std::enable_if< | ||||
|     !is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) { | ||||
|   return add_space ? " {}" : "{}"; | ||||
| } | ||||
|  | ||||
| template<typename Arg> | ||||
| FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,  | ||||
| FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, | ||||
|   typename std::enable_if< | ||||
|     is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) { | ||||
|   return add_space ? " \"{}\"" : "\"{}\""; | ||||
| @@ -205,7 +205,7 @@ struct is_tuple_like { | ||||
| }; | ||||
|  | ||||
| template <typename TupleT, typename Char> | ||||
| struct formatter<TupleT, Char,  | ||||
| struct formatter<TupleT, Char, | ||||
|     typename std::enable_if<fmt::is_tuple_like<TupleT>::value>::type> { | ||||
| private: | ||||
|   // C++11 generic lambda for format() | ||||
|   | ||||
							
								
								
									
										2
									
								
								third_party/spdlog/include/spdlog/tweakme.h
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								third_party/spdlog/include/spdlog/tweakme.h
									
									
									
									
										vendored
									
									
								
							| @@ -142,4 +142,4 @@ | ||||
| // Defaults to __FUNCTION__ (should work on all compilers) if not defined. | ||||
| // | ||||
| // #define SPDLOG_FUNCTION __PRETTY_FUNCTION__ | ||||
| /////////////////////////////////////////////////////////////////////////////// | ||||
| /////////////////////////////////////////////////////////////////////////////// | ||||
|   | ||||
							
								
								
									
										2
									
								
								third_party/spdlog/tests/main.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								third_party/spdlog/tests/main.cpp
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,2 @@ | ||||
| #define CATCH_CONFIG_MAIN | ||||
| #include "catch.hpp" | ||||
| #include "catch.hpp" | ||||
|   | ||||
							
								
								
									
										2
									
								
								third_party/spdlog/tests/test_mpmc_q.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								third_party/spdlog/tests/test_mpmc_q.cpp
									
									
									
									
										vendored
									
									
								
							| @@ -104,4 +104,4 @@ TEST_CASE("full_queue", "[mpmc_blocking_q]") | ||||
|     int item = -1; | ||||
|     q.dequeue_for(item, milliseconds(0)); | ||||
|     REQUIRE(item == 123456); | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								third_party/spdlog/tests/utils.h
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								third_party/spdlog/tests/utils.h
									
									
									
									
										vendored
									
									
								
							| @@ -13,4 +13,4 @@ std::size_t count_lines(const std::string &filename); | ||||
|  | ||||
| std::size_t get_filesize(const std::string &filename); | ||||
|  | ||||
| bool ends_with(std::string const &value, std::string const &ending); | ||||
| bool ends_with(std::string const &value, std::string const &ending); | ||||
|   | ||||
| @@ -61,4 +61,4 @@ sleep 2 | ||||
| kill `cat /tmp/ws_test/pidfile.transfer` | ||||
| kill `cat /tmp/ws_test/pidfile.receive` | ||||
| kill `cat /tmp/ws_test/pidfile.send` | ||||
|  | ||||
| exit 0 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user