Compare commits
	
		
			110 Commits
		
	
	
		
			v2.0.0
			...
			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 | ||
| 
						 | 
					12f6cd878d | ||
| 
						 | 
					9aacebbbaf | ||
| 
						 | 
					701c3745c2 | ||
| 
						 | 
					a41d08343c | ||
| 
						 | 
					156288b17b | ||
| 
						 | 
					6467f98241 | ||
| 
						 | 
					b24e4334f6 | ||
| 
						 | 
					bf8abcbf4a | ||
| 
						 | 
					bb484414b1 | ||
| 
						 | 
					fc75b13fae | ||
| 
						 | 
					78f59b4207 | ||
| 
						 | 
					7c5567db56 | ||
| 
						 | 
					ed0e23e8a5 | ||
| 
						 | 
					7ecaf1f982 | ||
| 
						 | 
					d0a41f3894 | ||
| 
						 | 
					57562b234f | ||
| 
						 | 
					469d127d61 | ||
| 
						 | 
					d6e9b61c8e | ||
| 
						 | 
					7fb1b65ddd | ||
| 
						 | 
					77c7fdc636 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1 +1,2 @@
 | 
			
		||||
build
 | 
			
		||||
*.pyc
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 @@
 | 
			
		||||
1.5.3
 | 
			
		||||
2.0.0
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SelectInterruptEventFd : public SelectInterrupt {
 | 
			
		||||
    class SelectInterruptEventFd final : public SelectInterrupt {
 | 
			
		||||
    public:
 | 
			
		||||
        SelectInterruptEventFd();
 | 
			
		||||
        virtual ~SelectInterruptEventFd();
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SelectInterruptPipe : public SelectInterrupt {
 | 
			
		||||
    class SelectInterruptPipe final : public SelectInterrupt {
 | 
			
		||||
    public:
 | 
			
		||||
        SelectInterruptPipe();
 | 
			
		||||
        virtual ~SelectInterruptPipe();
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        struct timeval timeout;
 | 
			
		||||
        timeout.tv_sec = timeoutMs / 1000;
 | 
			
		||||
        timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
 | 
			
		||||
        timeout.tv_usec = 1000 * (timeoutMs % 1000);
 | 
			
		||||
 | 
			
		||||
        // Compute the highest fd.
 | 
			
		||||
        int sockfd = _sockfd;
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SocketAppleSSL : public Socket
 | 
			
		||||
    class SocketAppleSSL final : public Socket
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        SocketAppleSSL(int fd = -1);
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SocketOpenSSL : public Socket
 | 
			
		||||
    class SocketOpenSSL final : public Socket
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        SocketOpenSSL(int fd = -1);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SocketSChannel : public Socket
 | 
			
		||||
    class SocketSChannel final : public Socket
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        SocketSChannel();
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
@@ -223,7 +225,6 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        uint32_t retries = 0;
 | 
			
		||||
        millis duration;
 | 
			
		||||
        ix::WebSocketInitResult status;
 | 
			
		||||
 | 
			
		||||
        // Try to connect perpertually
 | 
			
		||||
        while (true)
 | 
			
		||||
@@ -249,7 +250,7 @@ namespace ix
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Try to connect synchronously
 | 
			
		||||
            status = connect(_handshakeTimeoutSecs);
 | 
			
		||||
            ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
 | 
			
		||||
 | 
			
		||||
            if (!status.success)
 | 
			
		||||
            {
 | 
			
		||||
@@ -292,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();
 | 
			
		||||
 | 
			
		||||
@@ -459,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;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -23,7 +23,7 @@ namespace ix
 | 
			
		||||
    using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
 | 
			
		||||
                                                    std::shared_ptr<ConnectionState>)>;
 | 
			
		||||
 | 
			
		||||
    class WebSocketServer : public SocketServer {
 | 
			
		||||
    class WebSocketServer final : public SocketServer {
 | 
			
		||||
    public:
 | 
			
		||||
        WebSocketServer(int port = SocketServer::kDefaultPort,
 | 
			
		||||
                        const std::string& host = SocketServer::kDefaultHost,
 | 
			
		||||
 
 | 
			
		||||
@@ -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),
 | 
			
		||||
@@ -134,17 +124,14 @@ namespace ix
 | 
			
		||||
        {
 | 
			
		||||
            _pingIntervalOrTimeoutGCDSecs = pingIntervalSecs;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (_pingIntervalOrTimeoutGCDSecs > 0)
 | 
			
		||||
        {
 | 
			
		||||
            _nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Client
 | 
			
		||||
    WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url,
 | 
			
		||||
                                                         int timeoutSecs)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<std::mutex> lock(_socketMutex);
 | 
			
		||||
 | 
			
		||||
        std::string protocol, host, path, query;
 | 
			
		||||
        int port;
 | 
			
		||||
 | 
			
		||||
@@ -154,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)
 | 
			
		||||
@@ -181,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;
 | 
			
		||||
 | 
			
		||||
@@ -220,11 +209,15 @@ 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;
 | 
			
		||||
        }
 | 
			
		||||
        else if (readyState == ReadyState::OPEN)
 | 
			
		||||
        {
 | 
			
		||||
            initTimePointsAndGCDAfterConnect();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _readyState = readyState;
 | 
			
		||||
    }
 | 
			
		||||
@@ -234,6 +227,23 @@ namespace ix
 | 
			
		||||
        _onCloseCallback = onCloseCallback;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocketTransport::initTimePointsAndGCDAfterConnect()
 | 
			
		||||
    {
 | 
			
		||||
        {
 | 
			
		||||
            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();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (_pingIntervalOrTimeoutGCDSecs > 0)
 | 
			
		||||
        {
 | 
			
		||||
            _nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Only consider send PING time points for that computation.
 | 
			
		||||
    bool WebSocketTransport::pingIntervalExceeded()
 | 
			
		||||
    {
 | 
			
		||||
@@ -270,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.
 | 
			
		||||
@@ -303,6 +314,21 @@ namespace ix
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
        // 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);
 | 
			
		||||
 | 
			
		||||
@@ -318,7 +344,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
                if (result == PollResultType::Error)
 | 
			
		||||
                {
 | 
			
		||||
                    _socket->close();
 | 
			
		||||
                    closeSocket();
 | 
			
		||||
                    setReadyState(ReadyState::CLOSED);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
@@ -343,7 +369,7 @@ 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;
 | 
			
		||||
                }
 | 
			
		||||
@@ -357,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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -600,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
 | 
			
		||||
@@ -635,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
 | 
			
		||||
@@ -653,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
 | 
			
		||||
            else if (_readyState != ReadyState::CLOSED)
 | 
			
		||||
            {
 | 
			
		||||
                closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false);
 | 
			
		||||
                closeSocketAndSwitchToClosedState(WebSocketCloseConstants::kAbnormalCloseCode,
 | 
			
		||||
                                                  WebSocketCloseConstants::kAbnormalCloseMessage,
 | 
			
		||||
                                                  0, false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -719,7 +748,7 @@ namespace ix
 | 
			
		||||
        bool compress,
 | 
			
		||||
        const OnProgressCallback& onProgressCallback)
 | 
			
		||||
    {
 | 
			
		||||
        if (_readyState != ReadyState::OPEN)
 | 
			
		||||
        if (_readyState != ReadyState::OPEN && _readyState != ReadyState::CLOSING)
 | 
			
		||||
        {
 | 
			
		||||
            return WebSocketSendInfo();
 | 
			
		||||
        }
 | 
			
		||||
@@ -929,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())
 | 
			
		||||
            {
 | 
			
		||||
@@ -943,8 +978,7 @@ namespace ix
 | 
			
		||||
            }
 | 
			
		||||
            else if (ret <= 0)
 | 
			
		||||
            {
 | 
			
		||||
                _socket->close();
 | 
			
		||||
 | 
			
		||||
                closeSocket();
 | 
			
		||||
                setReadyState(ReadyState::CLOSED);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
@@ -960,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
 | 
			
		||||
@@ -978,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;
 | 
			
		||||
@@ -988,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;
 | 
			
		||||
@@ -1011,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;
 | 
			
		||||
@@ -174,17 +179,6 @@ namespace ix
 | 
			
		||||
        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;
 | 
			
		||||
@@ -220,6 +214,8 @@ namespace ix
 | 
			
		||||
        // after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion
 | 
			
		||||
        bool closingDelayExceeded();
 | 
			
		||||
 | 
			
		||||
        void initTimePointsAndGCDAfterConnect();
 | 
			
		||||
 | 
			
		||||
        void sendCloseFrame(uint16_t code, const std::string& reason);
 | 
			
		||||
 | 
			
		||||
        void closeSocketAndSwitchToClosedState(uint16_t code,
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								test/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								test/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -7,3 +7,4 @@ node_modules
 | 
			
		||||
ixwebsocket
 | 
			
		||||
Makefile
 | 
			
		||||
build
 | 
			
		||||
ixwebsocket_unittest.xml
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,11 @@
 | 
			
		||||
cmake_minimum_required (VERSION 3.4.1)
 | 
			
		||||
project (ixwebsocket_unittest)
 | 
			
		||||
 | 
			
		||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
 | 
			
		||||
find_package(Sanitizers)
 | 
			
		||||
 | 
			
		||||
set (CMAKE_CXX_STANDARD 14)
 | 
			
		||||
 | 
			
		||||
if (NOT WIN32)
 | 
			
		||||
  set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
 | 
			
		||||
  find_package(Sanitizers)
 | 
			
		||||
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
 | 
			
		||||
  set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
 | 
			
		||||
  option(USE_TLS "Add TLS support" ON)
 | 
			
		||||
@@ -32,25 +31,39 @@ 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})
 | 
			
		||||
add_sanitizers(ixwebsocket_unittest)
 | 
			
		||||
 | 
			
		||||
if (NOT WIN32)
 | 
			
		||||
  add_sanitizers(ixwebsocket_unittest)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if (APPLE AND USE_TLS)
 | 
			
		||||
    target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
@@ -413,17 +413,17 @@ TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod
 | 
			
		||||
 | 
			
		||||
        REQUIRE(server.getClients().size() == 1);
 | 
			
		||||
 | 
			
		||||
        ix::msleep(1850);
 | 
			
		||||
        ix::msleep(1900);
 | 
			
		||||
 | 
			
		||||
        webSocketClient.stop();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Here we test ping interval
 | 
			
		||||
        // -> expected ping messages == 1 as 1850 seconds, 1 ping sent every second
 | 
			
		||||
        // -> 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();
 | 
			
		||||
@@ -460,7 +460,7 @@ TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
 | 
			
		||||
        webSocketClient.sendMessage("hello world");
 | 
			
		||||
        ix::msleep(900);
 | 
			
		||||
        webSocketClient.sendMessage("hello world");
 | 
			
		||||
        ix::msleep(900);
 | 
			
		||||
        ix::msleep(1100);
 | 
			
		||||
 | 
			
		||||
        webSocketClient.stop();
 | 
			
		||||
 | 
			
		||||
@@ -469,11 +469,11 @@ TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
 | 
			
		||||
 | 
			
		||||
        // Here we test ping interval
 | 
			
		||||
        // client has sent data, but ping should have been sent no matter what
 | 
			
		||||
        // -> expected ping messages == 2 as 900+900+900 = 2700 seconds, 1 ping sent every second
 | 
			
		||||
        // -> 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
 | 
			
		||||
@@ -350,7 +350,7 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]")
 | 
			
		||||
 | 
			
		||||
        REQUIRE(server.getClients().size() == 1);
 | 
			
		||||
 | 
			
		||||
        ix::msleep(2700);
 | 
			
		||||
        ix::msleep(2900);
 | 
			
		||||
 | 
			
		||||
        // Here we test ping timeout, no timeout yet
 | 
			
		||||
        REQUIRE(serverReceivedPingMessages == 0);
 | 
			
		||||
@@ -359,12 +359,13 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]")
 | 
			
		||||
        REQUIRE(webSocketClient.isClosed() == false);
 | 
			
		||||
        REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
 | 
			
		||||
 | 
			
		||||
        ix::msleep(400);
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
@@ -410,12 +411,13 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]")
 | 
			
		||||
        REQUIRE(serverReceivedPingMessages == 1);
 | 
			
		||||
        REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
 | 
			
		||||
 | 
			
		||||
        ix::msleep(1000);
 | 
			
		||||
        ix::msleep(1100);
 | 
			
		||||
 | 
			
		||||
        // 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();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										137
									
								
								test/run.py
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								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
 | 
			
		||||
 | 
			
		||||
@@ -28,9 +22,9 @@ try:
 | 
			
		||||
except ImportError:
 | 
			
		||||
    hasClick = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DEFAULT_EXE = 'ixwebsocket_unittest'
 | 
			
		||||
 | 
			
		||||
BUILD_TYPE = 'Debug'
 | 
			
		||||
XML_OUTPUT_FILE = 'ixwebsocket_unittest.xml'
 | 
			
		||||
TEST_EXE_PATH = None
 | 
			
		||||
 | 
			
		||||
class Command(object):
 | 
			
		||||
    """Run system commands with timeout
 | 
			
		||||
@@ -65,7 +59,7 @@ class Command(object):
 | 
			
		||||
            return True, self.process.returncode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def runCommand(cmd, assertOnFailure=True, timeout=None):
 | 
			
		||||
def runCommand(cmd, abortOnFailure=True, timeout=None):
 | 
			
		||||
    '''Small wrapper to run a command and make sure it succeed'''
 | 
			
		||||
 | 
			
		||||
    if timeout is None:
 | 
			
		||||
@@ -73,16 +67,13 @@ def runCommand(cmd, assertOnFailure=True, timeout=None):
 | 
			
		||||
 | 
			
		||||
    print('\nRunning', cmd)
 | 
			
		||||
    command = Command(cmd)
 | 
			
		||||
    timedout, ret = command.run(timeout)
 | 
			
		||||
    succeed, ret = command.run(timeout)
 | 
			
		||||
 | 
			
		||||
    if timedout:
 | 
			
		||||
        print('Unittest timed out')
 | 
			
		||||
 | 
			
		||||
    msg = 'cmd {} failed with error code {}'.format(cmd, ret)
 | 
			
		||||
    if ret != 0:
 | 
			
		||||
    if not succeed or ret != 0:
 | 
			
		||||
        msg = 'cmd {}\nfailed with error code {}'.format(cmd, ret)
 | 
			
		||||
        print(msg)
 | 
			
		||||
        if assertOnFailure:
 | 
			
		||||
            assert False
 | 
			
		||||
        if abortOnFailure:
 | 
			
		||||
            sys.exit(-1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def runCMake(sanitizer, buildDir):
 | 
			
		||||
@@ -91,12 +82,6 @@ def runCMake(sanitizer, buildDir):
 | 
			
		||||
    (remove build sub-folder).
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
    # CMake installed via Self Service ends up here.
 | 
			
		||||
    cmake_executable = '/Applications/CMake.app/Contents/bin/cmake'
 | 
			
		||||
 | 
			
		||||
    if not os.path.exists(cmake_executable):
 | 
			
		||||
        cmake_executable = 'cmake'
 | 
			
		||||
 | 
			
		||||
    sanitizersFlags = {
 | 
			
		||||
        'asan': '-DSANITIZE_ADDRESS=On',
 | 
			
		||||
        'ubsan': '-DSANITIZE_UNDEFINED=On',
 | 
			
		||||
@@ -110,19 +95,23 @@ def runCMake(sanitizer, buildDir):
 | 
			
		||||
    if not os.path.exists(cmakeExecutable):
 | 
			
		||||
        cmakeExecutable = 'cmake'
 | 
			
		||||
 | 
			
		||||
    generator = '"Unix Makefiles"'
 | 
			
		||||
    if platform.system() == 'Windows':
 | 
			
		||||
        generator = '"NMake Makefiles"'
 | 
			
		||||
        #generator = '"NMake Makefiles"'
 | 
			
		||||
        #generator = '"Visual Studio 16 2019"'
 | 
			
		||||
        generator = '"Visual Studio 15 2017"'
 | 
			
		||||
    else:
 | 
			
		||||
        generator = '"Unix Makefiles"'
 | 
			
		||||
 | 
			
		||||
    fmt = '''
 | 
			
		||||
{cmakeExecutable} -H. \
 | 
			
		||||
    CMAKE_BUILD_TYPE = BUILD_TYPE
 | 
			
		||||
 | 
			
		||||
    fmt = '{cmakeExecutable} -H. \
 | 
			
		||||
    {sanitizerFlag} \
 | 
			
		||||
    -B{buildDir} \
 | 
			
		||||
    -DCMAKE_BUILD_TYPE=Debug \
 | 
			
		||||
    -B"{buildDir}" \
 | 
			
		||||
    -DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
 | 
			
		||||
    -DUSE_TLS=1 \
 | 
			
		||||
    -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
 | 
			
		||||
    -G{generator}
 | 
			
		||||
'''
 | 
			
		||||
    -G{generator}'
 | 
			
		||||
 | 
			
		||||
    cmakeCmd = fmt.format(**locals())
 | 
			
		||||
    runCommand(cmakeCmd)
 | 
			
		||||
 | 
			
		||||
@@ -133,10 +122,10 @@ def runTest(args, buildDir, xmlOutput, testRunName):
 | 
			
		||||
    if args is None:
 | 
			
		||||
        args = ''
 | 
			
		||||
 | 
			
		||||
    fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"'
 | 
			
		||||
    testCommand = fmt.format(**locals())
 | 
			
		||||
    testCommand = '{} -o {} -n "{}" -r junit "{}"'.format(TEST_EXE_PATH, xmlOutput, testRunName, args)
 | 
			
		||||
 | 
			
		||||
    runCommand(testCommand,
 | 
			
		||||
               assertOnFailure=False)
 | 
			
		||||
               abortOnFailure=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validateTestSuite(xmlOutput):
 | 
			
		||||
@@ -280,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()
 | 
			
		||||
@@ -296,8 +285,7 @@ def executeJobs(jobs):
 | 
			
		||||
def computeAllTestNames(buildDir):
 | 
			
		||||
    '''Compute all test case names, by executing the unittest in a custom mode'''
 | 
			
		||||
 | 
			
		||||
    executable = os.path.join(buildDir, DEFAULT_EXE)
 | 
			
		||||
    cmd = '"{}" --list-test-names-only'.format(executable)
 | 
			
		||||
    cmd = '"{}" --list-test-names-only'.format(TEST_EXE_PATH)
 | 
			
		||||
    names = os.popen(cmd).read().splitlines()
 | 
			
		||||
    names.sort()  # Sort test names for execution determinism
 | 
			
		||||
    return names
 | 
			
		||||
@@ -344,7 +332,7 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime):
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        systemOut = ET.Element('system-out')
 | 
			
		||||
        systemOut.text = result['output'].decode('utf-8')
 | 
			
		||||
        systemOut.text = result['output'].decode('utf-8', 'ignore')
 | 
			
		||||
        testCase.append(systemOut)
 | 
			
		||||
 | 
			
		||||
        if not result['success']:
 | 
			
		||||
@@ -358,23 +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 = 'make'
 | 
			
		||||
    jobs = '-j8'
 | 
			
		||||
 | 
			
		||||
    if platform.system() == 'Windows':
 | 
			
		||||
        makeCmd = 'nmake'
 | 
			
		||||
 | 
			
		||||
        # nmake does not have a -j option
 | 
			
		||||
        jobs = ''
 | 
			
		||||
 | 
			
		||||
    runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs))
 | 
			
		||||
    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
 | 
			
		||||
@@ -409,12 +396,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        # testName can contains spaces, so we enclose them in double quotes
 | 
			
		||||
        executable = os.path.join(buildDir, DEFAULT_EXE)
 | 
			
		||||
 | 
			
		||||
        if platform.system() == 'Windows':
 | 
			
		||||
            executable += '.exe'
 | 
			
		||||
 | 
			
		||||
        cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, executable, testName, outputPath)
 | 
			
		||||
        cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, TEST_EXE_PATH, testName, outputPath)
 | 
			
		||||
 | 
			
		||||
        jobs.append({
 | 
			
		||||
            'name': testName,
 | 
			
		||||
@@ -424,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)
 | 
			
		||||
 | 
			
		||||
@@ -454,8 +436,6 @@ def main():
 | 
			
		||||
    if not os.path.exists(buildDir):
 | 
			
		||||
        os.makedirs(buildDir)
 | 
			
		||||
 | 
			
		||||
    defaultOutput = DEFAULT_EXE + '.xml'
 | 
			
		||||
 | 
			
		||||
    parser = argparse.ArgumentParser(description='Build and Run the engine unittest')
 | 
			
		||||
 | 
			
		||||
    sanitizers = ['tsan', 'asan', 'ubsan', 'none']
 | 
			
		||||
@@ -476,19 +456,41 @@ 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
 | 
			
		||||
    if args.sanitizer is None:
 | 
			
		||||
 | 
			
		||||
    if args.no_sanitizer:
 | 
			
		||||
        sanitizer = 'none'
 | 
			
		||||
    elif args.sanitizer is None:
 | 
			
		||||
        sanitizer = 'tsan'
 | 
			
		||||
 | 
			
		||||
    # Sanitizers display lots of strange errors on Linux on CI,
 | 
			
		||||
    # which looks like false positives
 | 
			
		||||
    if platform.system() != 'Darwin':
 | 
			
		||||
        sanitizer = 'none'
 | 
			
		||||
 | 
			
		||||
    defaultRunName = 'ixengine_{}_{}'.format(platform.system(), sanitizer)
 | 
			
		||||
 | 
			
		||||
    xmlOutput = args.output or defaultOutput
 | 
			
		||||
    xmlOutput = args.output or XML_OUTPUT_FILE
 | 
			
		||||
    testRunName = args.run_name or os.getenv('IXENGINE_TEST_RUN_NAME') or defaultRunName
 | 
			
		||||
 | 
			
		||||
    global TEST_EXE_PATH
 | 
			
		||||
 | 
			
		||||
    if platform.system() == 'Windows':
 | 
			
		||||
        TEST_EXE_PATH = os.path.join(buildDir, BUILD_TYPE, 'ixwebsocket_unittest.exe')
 | 
			
		||||
    else:
 | 
			
		||||
        TEST_EXE_PATH = os.path.join(buildDir, 'ixwebsocket_unittest')
 | 
			
		||||
 | 
			
		||||
    if args.list:
 | 
			
		||||
        # catch2 exit with a different error code when requesting the list of files
 | 
			
		||||
        try:
 | 
			
		||||
@@ -505,13 +507,8 @@ def main():
 | 
			
		||||
        print('LLDB is only supported on Apple at this point')
 | 
			
		||||
        args.lldb = False
 | 
			
		||||
 | 
			
		||||
    # Sanitizers display lots of strange errors on Linux on CI,
 | 
			
		||||
    # which looks like false positives
 | 
			
		||||
    if platform.system() != 'Darwin':
 | 
			
		||||
        sanitizer = 'none'
 | 
			
		||||
 | 
			
		||||
    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__':
 | 
			
		||||
 
 | 
			
		||||
@@ -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