Compare commits
	
		
			27 Commits
		
	
	
		
			v8.0.0
			...
			Kumamon38-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d35818b688 | ||
| 
						 | 
					9936260711 | ||
| 
						 | 
					22fcdc0e2e | ||
| 
						 | 
					561eac816b | ||
| 
						 | 
					7256b3df65 | ||
| 
						 | 
					f4c771b745 | ||
| 
						 | 
					73ee18b093 | ||
| 
						 | 
					f502d3ca35 | ||
| 
						 | 
					9703f76386 | ||
| 
						 | 
					3ea7dbb637 | ||
| 
						 | 
					6beecc0aa8 | ||
| 
						 | 
					eee99ecfc9 | ||
| 
						 | 
					ed4063bd6a | ||
| 
						 | 
					3a9fe7c480 | ||
| 
						 | 
					2dfd141897 | ||
| 
						 | 
					f9abf3908f | ||
| 
						 | 
					679791dd63 | ||
| 
						 | 
					2b9b31ef4c | ||
| 
						 | 
					1f518aa95d | ||
| 
						 | 
					ec3896e61b | ||
| 
						 | 
					503826a762 | ||
| 
						 | 
					2eb3085d30 | ||
| 
						 | 
					3800978b3c | ||
| 
						 | 
					37c639387f | ||
| 
						 | 
					d4cdbe6141 | ||
| 
						 | 
					776227edcb | ||
| 
						 | 
					23384dcd6e | 
@@ -26,6 +26,7 @@ set( IXWEBSOCKET_SOURCES
 | 
			
		||||
    ixwebsocket/IXSocketFactory.cpp
 | 
			
		||||
    ixwebsocket/IXDNSLookup.cpp
 | 
			
		||||
    ixwebsocket/IXCancellationRequest.cpp
 | 
			
		||||
    ixwebsocket/IXNetSystem.cpp
 | 
			
		||||
    ixwebsocket/IXWebSocket.cpp
 | 
			
		||||
    ixwebsocket/IXWebSocketServer.cpp
 | 
			
		||||
    ixwebsocket/IXWebSocketTransport.cpp
 | 
			
		||||
@@ -49,6 +50,7 @@ set( IXWEBSOCKET_HEADERS
 | 
			
		||||
    ixwebsocket/IXSetThreadName.h
 | 
			
		||||
    ixwebsocket/IXDNSLookup.h
 | 
			
		||||
    ixwebsocket/IXCancellationRequest.h
 | 
			
		||||
    ixwebsocket/IXNetSystem.h
 | 
			
		||||
    ixwebsocket/IXProgressCallback.h
 | 
			
		||||
    ixwebsocket/IXWebSocket.h
 | 
			
		||||
    ixwebsocket/IXWebSocketServer.h
 | 
			
		||||
@@ -137,6 +139,11 @@ set( IXWEBSOCKET_INCLUDE_DIRS
 | 
			
		||||
    .
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
 | 
			
		||||
    # Build with Multiple Processes
 | 
			
		||||
    target_compile_options(ixwebsocket PRIVATE /MP)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
 | 
			
		||||
 | 
			
		||||
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
1.4.2
 | 
			
		||||
1.4.3
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,42 +1,32 @@
 | 
			
		||||
# Build time
 | 
			
		||||
FROM debian:buster as build
 | 
			
		||||
FROM fedora:30 as build
 | 
			
		||||
 | 
			
		||||
ENV DEBIAN_FRONTEND noninteractive
 | 
			
		||||
RUN apt-get update 
 | 
			
		||||
RUN apt-get -y install wget 
 | 
			
		||||
RUN yum install -y gcc-g++
 | 
			
		||||
RUN yum install -y cmake
 | 
			
		||||
RUN yum install -y make
 | 
			
		||||
RUN yum install -y openssl-devel
 | 
			
		||||
 | 
			
		||||
RUN yum install -y wget
 | 
			
		||||
RUN mkdir -p /tmp/cmake
 | 
			
		||||
WORKDIR /tmp/cmake
 | 
			
		||||
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz 
 | 
			
		||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
 | 
			
		||||
 | 
			
		||||
RUN apt-get -y install g++ 
 | 
			
		||||
RUN apt-get -y install libssl-dev
 | 
			
		||||
RUN apt-get -y install libz-dev
 | 
			
		||||
RUN apt-get -y install make
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
 | 
			
		||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
 | 
			
		||||
 | 
			
		||||
RUN yum install -y python
 | 
			
		||||
RUN yum install -y libtsan
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
# RUN ["make", "test"]
 | 
			
		||||
RUN ["make"]
 | 
			
		||||
 | 
			
		||||
# Runtime
 | 
			
		||||
FROM debian:buster as runtime
 | 
			
		||||
FROM fedora:30 as runtime
 | 
			
		||||
 | 
			
		||||
ENV DEBIAN_FRONTEND noninteractive
 | 
			
		||||
RUN apt-get update 
 | 
			
		||||
# Runtime 
 | 
			
		||||
RUN apt-get install -y libssl1.1 
 | 
			
		||||
RUN apt-get install -y ca-certificates
 | 
			
		||||
RUN ["update-ca-certificates"]
 | 
			
		||||
RUN yum install -y libtsan
 | 
			
		||||
 | 
			
		||||
# Debugging
 | 
			
		||||
RUN apt-get install -y strace
 | 
			
		||||
RUN apt-get install -y procps
 | 
			
		||||
RUN apt-get install -y htop
 | 
			
		||||
 | 
			
		||||
RUN adduser --disabled-password --gecos '' app
 | 
			
		||||
RUN groupadd app && useradd -g app app 
 | 
			
		||||
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
 | 
			
		||||
RUN chmod +x /usr/local/bin/ws
 | 
			
		||||
RUN ldd /usr/local/bin/ws
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ namespace ix
 | 
			
		||||
        _done(false),
 | 
			
		||||
        _id(_nextId++)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DNSLookup::~DNSLookup()
 | 
			
		||||
@@ -137,6 +137,11 @@ namespace ix
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!_errMsg.empty())
 | 
			
		||||
        {
 | 
			
		||||
            errMsg = _errMsg;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::unique_lock<std::mutex> rlock(_resMutex);
 | 
			
		||||
        return _res;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								ixwebsocket/IXNetSystem.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								ixwebsocket/IXNetSystem.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXNetSystem.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "IXNetSystem.h"
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    bool initNetSystem()
 | 
			
		||||
    {
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
        WORD wVersionRequested;
 | 
			
		||||
        WSADATA wsaData;
 | 
			
		||||
        int err;
 | 
			
		||||
 | 
			
		||||
        /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
 | 
			
		||||
        wVersionRequested = MAKEWORD(2, 2);
 | 
			
		||||
 | 
			
		||||
        err = WSAStartup(wVersionRequested, &wsaData);
 | 
			
		||||
 | 
			
		||||
        return err == 0;
 | 
			
		||||
#else
 | 
			
		||||
        return true;
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool uninitNetSystem()
 | 
			
		||||
    {
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
        int err = WSACleanup();
 | 
			
		||||
 | 
			
		||||
        return err == 0;
 | 
			
		||||
#else
 | 
			
		||||
        return true;
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,3 +23,9 @@
 | 
			
		||||
# include <sys/time.h>
 | 
			
		||||
# include <unistd.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    bool initNetSystem();
 | 
			
		||||
    bool uninitNetSystem();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,10 +57,10 @@ namespace ix
 | 
			
		||||
        SocketConnect::configure(fd);
 | 
			
		||||
 | 
			
		||||
        if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
 | 
			
		||||
            && errno != EINPROGRESS)
 | 
			
		||||
            && errno != EINPROGRESS && errno != 0)
 | 
			
		||||
        {
 | 
			
		||||
            closeSocket(fd);
 | 
			
		||||
            errMsg = strerror(errno);
 | 
			
		||||
            closeSocket(fd);
 | 
			
		||||
            return -1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@
 | 
			
		||||
# include <ws2def.h>
 | 
			
		||||
# include <WS2tcpip.h>
 | 
			
		||||
# include <schannel.h>
 | 
			
		||||
//# include <sslsock.h>
 | 
			
		||||
# include <io.h>
 | 
			
		||||
 | 
			
		||||
#define WIN32_LEAN_AND_MEAN
 | 
			
		||||
 
 | 
			
		||||
@@ -216,9 +216,14 @@ namespace ix
 | 
			
		||||
        return getReadyState() == WebSocket_ReadyState_Closing;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocket::close()
 | 
			
		||||
    bool WebSocket::isConnectedOrClosing() const
 | 
			
		||||
    {
 | 
			
		||||
        _ws.close();
 | 
			
		||||
        return isConnected() || isClosing();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocket::close(uint16_t code, const std::string& reason)
 | 
			
		||||
    {
 | 
			
		||||
        _ws.close(code, reason);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocket::reconnectPerpetuallyIfDisconnected()
 | 
			
		||||
@@ -257,24 +262,21 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    void WebSocket::run()
 | 
			
		||||
    {
 | 
			
		||||
        setThreadName(_url);
 | 
			
		||||
        setThreadName(getUrl());
 | 
			
		||||
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            if (_stop) return;
 | 
			
		||||
            if (_stop && !isClosing()) return;
 | 
			
		||||
 | 
			
		||||
            // 1. Make sure we are always connected
 | 
			
		||||
            reconnectPerpetuallyIfDisconnected();
 | 
			
		||||
 | 
			
		||||
            if (_stop) return;
 | 
			
		||||
 | 
			
		||||
            // 2. Poll to see if there's any new data available
 | 
			
		||||
            _ws.poll();
 | 
			
		||||
 | 
			
		||||
            if (_stop) return;
 | 
			
		||||
            WebSocketTransport::PollPostTreatment pollPostTreatment = _ws.poll();
 | 
			
		||||
 | 
			
		||||
            // 3. Dispatch the incoming messages
 | 
			
		||||
            _ws.dispatch(
 | 
			
		||||
                pollPostTreatment,
 | 
			
		||||
                [this](const std::string& msg,
 | 
			
		||||
                       size_t wireSize,
 | 
			
		||||
                       bool decompressionError,
 | 
			
		||||
@@ -316,8 +318,8 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
            // 4. In blocking mode, getting out of this function is triggered by
 | 
			
		||||
            //    an explicit disconnection from the callback, or by the remote end
 | 
			
		||||
            //    closing the connection, ie isConnected() == false.
 | 
			
		||||
            if (!_thread.joinable() && !isConnected() && !_automaticReconnection) return;
 | 
			
		||||
            //    closing the connection, ie isConnectedOrClosing() == false.
 | 
			
		||||
            if (!_thread.joinable() && !isConnectedOrClosing() && !_automaticReconnection) return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -111,7 +111,8 @@ 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);
 | 
			
		||||
@@ -136,6 +137,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        bool isConnected() const;
 | 
			
		||||
        bool isClosing() const;
 | 
			
		||||
        bool isConnectedOrClosing() const;
 | 
			
		||||
        void reconnectPerpetuallyIfDisconnected();
 | 
			
		||||
        std::string readyStateToString(ReadyState readyState);
 | 
			
		||||
        static void invokeTrafficTrackerCallback(size_t size, bool incoming);
 | 
			
		||||
 
 | 
			
		||||
@@ -51,15 +51,19 @@
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int greatestCommonDivisor (int a, int b) {
 | 
			
		||||
  while (b != 0)
 | 
			
		||||
  {
 | 
			
		||||
    int t = b;
 | 
			
		||||
    b = a % b;
 | 
			
		||||
    a = t;
 | 
			
		||||
  }
 | 
			
		||||
namespace
 | 
			
		||||
{
 | 
			
		||||
    int greatestCommonDivisor(int a, int b)
 | 
			
		||||
    {
 | 
			
		||||
        while (b != 0)
 | 
			
		||||
        {
 | 
			
		||||
            int t = b;
 | 
			
		||||
            b = a % b;
 | 
			
		||||
            a = t;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
  return a;
 | 
			
		||||
        return a;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
@@ -68,7 +72,9 @@ namespace ix
 | 
			
		||||
    const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
 | 
			
		||||
    const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
 | 
			
		||||
    const bool WebSocketTransport::kDefaultEnablePong(true);
 | 
			
		||||
    const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(200);
 | 
			
		||||
    constexpr size_t WebSocketTransport::kChunkSize;
 | 
			
		||||
 | 
			
		||||
    const uint16_t WebSocketTransport::kInternalErrorCode(1011);
 | 
			
		||||
    const uint16_t WebSocketTransport::kAbnormalCloseCode(1006);
 | 
			
		||||
    const uint16_t WebSocketTransport::kProtocolErrorCode(1002);
 | 
			
		||||
@@ -86,6 +92,7 @@ namespace ix
 | 
			
		||||
        _closeRemote(false),
 | 
			
		||||
        _enablePerMessageDeflate(false),
 | 
			
		||||
        _requestInitCancellation(false),
 | 
			
		||||
        _closingTimePoint(std::chrono::steady_clock::now()),
 | 
			
		||||
        _enablePong(kDefaultEnablePong),
 | 
			
		||||
        _pingIntervalSecs(kDefaultPingIntervalSecs),
 | 
			
		||||
        _pingTimeoutSecs(kDefaultPingTimeoutSecs),
 | 
			
		||||
@@ -242,9 +249,19 @@ namespace ix
 | 
			
		||||
        return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocketTransport::poll()
 | 
			
		||||
    bool WebSocketTransport::closingDelayExceeded()
 | 
			
		||||
    {
 | 
			
		||||
        PollResultType pollResult = _socket->poll(_pingIntervalOrTimeoutGCDSecs);
 | 
			
		||||
        std::lock_guard<std::mutex> lock(_closingTimePointMutex);
 | 
			
		||||
        auto now = std::chrono::steady_clock::now();
 | 
			
		||||
        return now - _closingTimePoint > std::chrono::milliseconds(kClosingMaximumWaitingDelayInMs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WebSocketTransport::PollPostTreatment WebSocketTransport::poll()
 | 
			
		||||
    {
 | 
			
		||||
        // we need to have no timeout if state is CLOSING
 | 
			
		||||
        int timeoutDelaySecs = (_readyState == CLOSING) ? 0 : _pingIntervalOrTimeoutGCDSecs;
 | 
			
		||||
 | 
			
		||||
        PollResultType pollResult = _socket->poll(timeoutDelaySecs);
 | 
			
		||||
 | 
			
		||||
        if (_readyState == OPEN)
 | 
			
		||||
        {
 | 
			
		||||
@@ -292,24 +309,19 @@ namespace ix
 | 
			
		||||
            {
 | 
			
		||||
                ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
 | 
			
		||||
 | 
			
		||||
                if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
 | 
			
		||||
                                _socket->getErrno() == EAGAIN))
 | 
			
		||||
                if (ret < 0 && _readyState != CLOSING && (_socket->getErrno() == EWOULDBLOCK ||
 | 
			
		||||
                                                          _socket->getErrno() == EAGAIN))
 | 
			
		||||
                {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                else if (ret <= 0)
 | 
			
		||||
                {
 | 
			
		||||
                    _rxbuf.clear();
 | 
			
		||||
                    // 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();
 | 
			
		||||
                    {
 | 
			
		||||
                        std::lock_guard<std::mutex> lock(_closeDataMutex);
 | 
			
		||||
                        _closeCode = kAbnormalCloseCode;
 | 
			
		||||
                        _closeReason = kAbnormalCloseMessage;
 | 
			
		||||
                        _closeWireSize = 0;
 | 
			
		||||
                        _closeRemote = true;
 | 
			
		||||
                    }
 | 
			
		||||
                    setReadyState(CLOSED);
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                    return CHECK_OR_RAISE_ABNORMAL_CLOSE_AFTER_DISPATCH;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
@@ -328,12 +340,15 @@ namespace ix
 | 
			
		||||
            _socket->close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Avoid a race condition where we get stuck in select
 | 
			
		||||
        // while closing.
 | 
			
		||||
        if (_readyState == CLOSING)
 | 
			
		||||
        if (_readyState == CLOSING && closingDelayExceeded())
 | 
			
		||||
        {
 | 
			
		||||
            _rxbuf.clear();
 | 
			
		||||
            // close code and reason were set when calling close()
 | 
			
		||||
            _socket->close();
 | 
			
		||||
            setReadyState(CLOSED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return NONE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool WebSocketTransport::isSendBufferEmpty() const
 | 
			
		||||
@@ -395,12 +410,13 @@ namespace ix
 | 
			
		||||
    // |                     Payload Data continued ...                |
 | 
			
		||||
    // +---------------------------------------------------------------+
 | 
			
		||||
    //
 | 
			
		||||
    void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback)
 | 
			
		||||
    void WebSocketTransport::dispatch(WebSocketTransport::PollPostTreatment pollPostTreatment,
 | 
			
		||||
                                      const OnMessageCallback& onMessageCallback)
 | 
			
		||||
    {
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            wsheader_type ws;
 | 
			
		||||
            if (_rxbuf.size() < 2) return; /* Need at least 2 */
 | 
			
		||||
            if (_rxbuf.size() < 2) break; /* Need at least 2 */
 | 
			
		||||
            const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume
 | 
			
		||||
            ws.fin = (data[0] & 0x80) == 0x80;
 | 
			
		||||
            ws.rsv1 = (data[0] & 0x40) == 0x40;
 | 
			
		||||
@@ -408,7 +424,7 @@ namespace ix
 | 
			
		||||
            ws.mask = (data[1] & 0x80) == 0x80;
 | 
			
		||||
            ws.N0 = (data[1] & 0x7f);
 | 
			
		||||
            ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
 | 
			
		||||
            if (_rxbuf.size() < ws.header_size) return; /* Need: ws.header_size - _rxbuf.size() */
 | 
			
		||||
            if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */
 | 
			
		||||
 | 
			
		||||
            //
 | 
			
		||||
            // Calculate payload length:
 | 
			
		||||
@@ -550,9 +566,25 @@ namespace ix
 | 
			
		||||
                std::string reason(_rxbuf.begin()+ws.header_size + 2,
 | 
			
		||||
                                   _rxbuf.begin()+ws.header_size + (size_t) ws.N);
 | 
			
		||||
                
 | 
			
		||||
                bool remote = true;
 | 
			
		||||
                
 | 
			
		||||
                // We receive a CLOSE frame from remote and are NOT the ones who triggered the close
 | 
			
		||||
                if (_readyState != CLOSING)
 | 
			
		||||
                {
 | 
			
		||||
                    // send back the CLOSE frame
 | 
			
		||||
                    sendCloseFrame(code, reason);
 | 
			
		||||
 | 
			
		||||
                close(code, reason, _rxbuf.size(), remote);
 | 
			
		||||
                    _socket->wakeUpFromPoll(Socket::kCloseRequest);
 | 
			
		||||
                    
 | 
			
		||||
                    bool remote = true;
 | 
			
		||||
                    closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
 | 
			
		||||
                }
 | 
			
		||||
                // we got the CLOSE frame answer from our close, so we can close the connection if
 | 
			
		||||
                // the code/reason are the same
 | 
			
		||||
                else if (_closeCode == code && _closeReason == reason)
 | 
			
		||||
                {
 | 
			
		||||
                    bool remote = false;
 | 
			
		||||
                    closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
@@ -565,6 +597,25 @@ namespace ix
 | 
			
		||||
            _rxbuf.erase(_rxbuf.begin(),
 | 
			
		||||
                         _rxbuf.begin() + ws.header_size + (size_t) ws.N);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in
 | 
			
		||||
        // the received and processed data then close the connection
 | 
			
		||||
        if (pollPostTreatment == CHECK_OR_RAISE_ABNORMAL_CLOSE_AFTER_DISPATCH)
 | 
			
		||||
        {
 | 
			
		||||
            _rxbuf.clear();
 | 
			
		||||
 | 
			
		||||
            // if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before)
 | 
			
		||||
            if (_readyState == CLOSING)
 | 
			
		||||
            {
 | 
			
		||||
                _socket->close();
 | 
			
		||||
                setReadyState(CLOSED);
 | 
			
		||||
            }
 | 
			
		||||
            // if we weren't closing, then close using abnormal close code and message 
 | 
			
		||||
            else if (_readyState != CLOSED)
 | 
			
		||||
            {
 | 
			
		||||
                closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string WebSocketTransport::getMergedChunks() const
 | 
			
		||||
@@ -859,12 +910,9 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
 | 
			
		||||
 | 
			
		||||
    void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason)
 | 
			
		||||
    {
 | 
			
		||||
        _requestInitCancellation = true;
 | 
			
		||||
 | 
			
		||||
        if (_readyState == CLOSING || _readyState == CLOSED) return;
 | 
			
		||||
 | 
			
		||||
        // See list of close events here:
 | 
			
		||||
        // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
 | 
			
		||||
 | 
			
		||||
@@ -877,11 +925,11 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        bool compress = false;
 | 
			
		||||
        sendData(wsheader_type::CLOSE, closure, compress);
 | 
			
		||||
        setReadyState(CLOSING);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        _socket->wakeUpFromPoll(Socket::kCloseRequest);
 | 
			
		||||
    void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
 | 
			
		||||
    {
 | 
			
		||||
        _socket->close();
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            std::lock_guard<std::mutex> lock(_closeDataMutex);
 | 
			
		||||
            _closeCode = code;
 | 
			
		||||
@@ -889,10 +937,33 @@ namespace ix
 | 
			
		||||
            _closeWireSize = closeWireSize;
 | 
			
		||||
            _closeRemote = remote;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setReadyState(CLOSED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
 | 
			
		||||
    {
 | 
			
		||||
        _requestInitCancellation = true;
 | 
			
		||||
 | 
			
		||||
        if (_readyState == CLOSING || _readyState == CLOSED) return;
 | 
			
		||||
 | 
			
		||||
        sendCloseFrame(code, reason);
 | 
			
		||||
        {
 | 
			
		||||
            std::lock_guard<std::mutex> lock(_closeDataMutex);
 | 
			
		||||
            _closeCode = code;
 | 
			
		||||
            _closeReason = reason;
 | 
			
		||||
            _closeWireSize = closeWireSize;
 | 
			
		||||
            _closeRemote = remote;
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            std::lock_guard<std::mutex> lock(_closingTimePointMutex);
 | 
			
		||||
            _closingTimePoint = std::chrono::steady_clock::now();
 | 
			
		||||
        }
 | 
			
		||||
        setReadyState(CLOSING);
 | 
			
		||||
 | 
			
		||||
        // wake up the poll, but do not close yet
 | 
			
		||||
        _socket->wakeUpFromPoll(Socket::kSendRequest);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t WebSocketTransport::bufferedAmount() const
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<std::mutex> lock(_txbufMutex);
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,12 @@ namespace ix
 | 
			
		||||
            FRAGMENT
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        enum PollPostTreatment
 | 
			
		||||
        {
 | 
			
		||||
            NONE,
 | 
			
		||||
            CHECK_OR_RAISE_ABNORMAL_CLOSE_AFTER_DISPATCH
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        using OnMessageCallback = std::function<void(const std::string&,
 | 
			
		||||
                                                     size_t,
 | 
			
		||||
                                                     bool,
 | 
			
		||||
@@ -78,7 +84,7 @@ namespace ix
 | 
			
		||||
        WebSocketInitResult connectToSocket(int fd,              // Server
 | 
			
		||||
                                            int timeoutSecs);
 | 
			
		||||
 | 
			
		||||
        void poll();
 | 
			
		||||
        PollPostTreatment poll();
 | 
			
		||||
        WebSocketSendInfo sendBinary(const std::string& message,
 | 
			
		||||
                                     const OnProgressCallback& onProgressCallback);
 | 
			
		||||
        WebSocketSendInfo sendText(const std::string& message,
 | 
			
		||||
@@ -93,7 +99,8 @@ namespace ix
 | 
			
		||||
        ReadyStateValues getReadyState() const;
 | 
			
		||||
        void setReadyState(ReadyStateValues readyStateValue);
 | 
			
		||||
        void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
 | 
			
		||||
        void dispatch(const OnMessageCallback& onMessageCallback);
 | 
			
		||||
        void dispatch(PollPostTreatment pollPostTreatment,
 | 
			
		||||
                      const OnMessageCallback& onMessageCallback);
 | 
			
		||||
        size_t bufferedAmount() const;
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
@@ -162,6 +169,10 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        // Used to cancel dns lookup + socket connect + http upgrade
 | 
			
		||||
        std::atomic<bool> _requestInitCancellation;
 | 
			
		||||
              
 | 
			
		||||
        mutable std::mutex _closingTimePointMutex;
 | 
			
		||||
        std::chrono::time_point<std::chrono::steady_clock>_closingTimePoint;
 | 
			
		||||
        static const int kClosingMaximumWaitingDelayInMs;
 | 
			
		||||
 | 
			
		||||
        // Constants for dealing with closing conneections
 | 
			
		||||
        static const uint16_t kInternalErrorCode;
 | 
			
		||||
@@ -201,6 +212,16 @@ namespace ix
 | 
			
		||||
        // No PONG data was received through the socket for longer than ping timeout delay
 | 
			
		||||
        bool pingTimeoutExceeded();
 | 
			
		||||
 | 
			
		||||
        // after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion
 | 
			
		||||
        bool closingDelayExceeded();
 | 
			
		||||
 | 
			
		||||
        void sendCloseFrame(uint16_t code, const std::string& reason);
 | 
			
		||||
 | 
			
		||||
        void closeSocketAndSwitchToClosedState(uint16_t code,
 | 
			
		||||
                                               const std::string& reason,
 | 
			
		||||
                                               size_t closeWireSize,
 | 
			
		||||
                                               bool remote);
 | 
			
		||||
 | 
			
		||||
        void sendOnSocket();
 | 
			
		||||
        WebSocketSendInfo sendData(wsheader_type::opcode_type type,
 | 
			
		||||
                                   const std::string& message,
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,9 @@ project (ixwebsocket_unittest)
 | 
			
		||||
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")
 | 
			
		||||
 | 
			
		||||
set (CMAKE_CXX_STANDARD 14)
 | 
			
		||||
 | 
			
		||||
if (NOT WIN32)
 | 
			
		||||
@@ -35,6 +38,7 @@ set (SOURCES
 | 
			
		||||
# Some unittest don't work on windows yet
 | 
			
		||||
if (NOT WIN32)
 | 
			
		||||
  list(APPEND SOURCES 
 | 
			
		||||
    IXWebSocketCloseTest.cpp
 | 
			
		||||
    IXWebSocketServerTest.cpp
 | 
			
		||||
    IXWebSocketPingTest.cpp
 | 
			
		||||
    IXWebSocketPingTimeoutTest.cpp
 | 
			
		||||
 
 | 
			
		||||
@@ -113,7 +113,7 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        struct sockaddr_in sa; // server address information
 | 
			
		||||
        socklen_t len;
 | 
			
		||||
        socklen_t len = sizeof(sa);
 | 
			
		||||
        if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
 | 
			
		||||
        {
 | 
			
		||||
            log("Cannot compute a free port. getsockname error.");
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,5 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    void log(const std::string& msg);
 | 
			
		||||
 | 
			
		||||
    bool computeFreePorts(int count);
 | 
			
		||||
    int getFreePort();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										407
									
								
								test/IXWebSocketCloseTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										407
									
								
								test/IXWebSocketCloseTest.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,407 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  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::WebSocket_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.close(code, reason);
 | 
			
		||||
        _webSocket.stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocketClient::start()
 | 
			
		||||
    {
 | 
			
		||||
        std::string url;
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "ws://localhost:"
 | 
			
		||||
               << _port
 | 
			
		||||
               << "/";
 | 
			
		||||
 | 
			
		||||
            url = ss.str();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _webSocket.setUrl(url);
 | 
			
		||||
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        log(std::string("Connecting to url: ") + url);
 | 
			
		||||
 | 
			
		||||
        _webSocket.setOnMessageCallback(
 | 
			
		||||
            [this](ix::WebSocketMessageType messageType,
 | 
			
		||||
               const std::string& str,
 | 
			
		||||
               size_t wireSize,
 | 
			
		||||
               const ix::WebSocketErrorInfo& error,
 | 
			
		||||
               const ix::WebSocketOpenInfo& openInfo,
 | 
			
		||||
                   const ix::WebSocketCloseInfo& closeInfo)
 | 
			
		||||
            {
 | 
			
		||||
                std::stringstream ss;
 | 
			
		||||
                if (messageType == ix::WebSocket_MessageType_Open)
 | 
			
		||||
                {
 | 
			
		||||
                    log("client connected");
 | 
			
		||||
 | 
			
		||||
                    _webSocket.disableAutomaticReconnection();
 | 
			
		||||
                }
 | 
			
		||||
                else if (messageType == ix::WebSocket_MessageType_Close)
 | 
			
		||||
                {
 | 
			
		||||
                    log("client disconnected");
 | 
			
		||||
 | 
			
		||||
                    std::lock_guard<std::mutex> lck(_mutexCloseData);
 | 
			
		||||
                    
 | 
			
		||||
                    _closeCode = closeInfo.code;
 | 
			
		||||
                    _closeReason = std::string(closeInfo.reason);
 | 
			
		||||
                    _closeRemote = closeInfo.remote;
 | 
			
		||||
                    
 | 
			
		||||
                    _webSocket.disableAutomaticReconnection();
 | 
			
		||||
                }
 | 
			
		||||
                else if (messageType == ix::WebSocket_MessageType_Error)
 | 
			
		||||
                {
 | 
			
		||||
                    ss << "Error ! " << error.reason;
 | 
			
		||||
                    log(ss.str());
 | 
			
		||||
 | 
			
		||||
                    _webSocket.disableAutomaticReconnection();
 | 
			
		||||
                }
 | 
			
		||||
                else if (messageType == ix::WebSocket_MessageType_Pong)
 | 
			
		||||
                {
 | 
			
		||||
                    ss << "Received pong message " << str;
 | 
			
		||||
                    log(ss.str());
 | 
			
		||||
                }
 | 
			
		||||
                else if (messageType == ix::WebSocket_MessageType_Ping)
 | 
			
		||||
                {
 | 
			
		||||
                    ss << "Received ping message " << str;
 | 
			
		||||
                    log(ss.str());
 | 
			
		||||
                }
 | 
			
		||||
                else if (messageType == ix::WebSocket_MessageType_Message)
 | 
			
		||||
                {
 | 
			
		||||
                    ss << "Received message " << str;
 | 
			
		||||
                    log(ss.str());
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    ss << "Invalid ix::WebSocketMessageType";
 | 
			
		||||
                    log(ss.str());
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        _webSocket.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocketClient::sendMessage(const std::string& text)
 | 
			
		||||
    {
 | 
			
		||||
        _webSocket.send(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool startServer(ix::WebSocketServer& server,
 | 
			
		||||
                     uint16_t& receivedCloseCode,
 | 
			
		||||
                     std::string& receivedCloseReason,
 | 
			
		||||
                     bool& receivedCloseRemote,
 | 
			
		||||
                     std::mutex& mutexWrite)
 | 
			
		||||
    {
 | 
			
		||||
        // A dev/null server
 | 
			
		||||
        server.setOnConnectionCallback(
 | 
			
		||||
            [&server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](std::shared_ptr<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::WebSocket_MessageType_Open)
 | 
			
		||||
                        {
 | 
			
		||||
                            Logger() << "New server connection";
 | 
			
		||||
                            Logger() << "id: " << connectionState->getId();
 | 
			
		||||
                            Logger() << "Uri: " << openInfo.uri;
 | 
			
		||||
                            Logger() << "Headers:";
 | 
			
		||||
                            for (auto it : openInfo.headers)
 | 
			
		||||
                            {
 | 
			
		||||
                                Logger() << it.first << ": " << it.second;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else if (messageType == ix::WebSocket_MessageType_Close)
 | 
			
		||||
                        {
 | 
			
		||||
                            log("Server closed connection");
 | 
			
		||||
 | 
			
		||||
                            //Logger() << closeInfo.code;
 | 
			
		||||
                            //Logger() << closeInfo.reason;
 | 
			
		||||
                            //Logger() << closeInfo.remote;
 | 
			
		||||
 | 
			
		||||
                            std::lock_guard<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(100);
 | 
			
		||||
 | 
			
		||||
        webSocketClient.stop();
 | 
			
		||||
 | 
			
		||||
        ix::msleep(200);
 | 
			
		||||
 | 
			
		||||
        // ensure client close is the same as values given
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseCode() == 1000);
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseReason() == "Normal closure");
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseRemote() == false);
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            std::lock_guard<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(100);
 | 
			
		||||
 | 
			
		||||
        webSocketClient.stop(4000, "My reason");
 | 
			
		||||
 | 
			
		||||
        ix::msleep(500);
 | 
			
		||||
 | 
			
		||||
        // ensure client close is the same as values given
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseCode() == 4000);
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseReason() == "My reason");
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseRemote() == false);
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            std::lock_guard<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(200);
 | 
			
		||||
 | 
			
		||||
        server.stop();
 | 
			
		||||
 | 
			
		||||
        ix::msleep(500);
 | 
			
		||||
 | 
			
		||||
        // ensure client close is the same as values given
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseCode() == 1000);
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseReason() == "Normal closure");
 | 
			
		||||
        REQUIRE(webSocketClient.getCloseRemote() == true);
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            std::lock_guard<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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -432,6 +432,7 @@ 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).")
 | 
			
		||||
@@ -486,3 +487,4 @@ TEST_CASE("Websocket_ping_long_timeout", "[setPingTimeout]")
 | 
			
		||||
        ix::reportWebSocketTraffic();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -251,8 +251,8 @@ def executeJob(job):
 | 
			
		||||
    sys.stderr.write('.')
 | 
			
		||||
    # print('Executing ' + job['cmd'] + '...')
 | 
			
		||||
 | 
			
		||||
    # 2 minutes of timeout for a single test
 | 
			
		||||
    timeout = 2 * 60
 | 
			
		||||
    # 10 minutes of timeout for a single test, cf PR #42
 | 
			
		||||
    timeout = 10 * 60
 | 
			
		||||
    command = Command(job['cmd'])
 | 
			
		||||
    timedout, ret = command.run(timeout)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,14 @@
 | 
			
		||||
#define CATCH_CONFIG_RUNNER
 | 
			
		||||
#include "catch.hpp"
 | 
			
		||||
 | 
			
		||||
#include <ixwebsocket/IXNetSystem.h>
 | 
			
		||||
 | 
			
		||||
int main(int argc, char* argv[])
 | 
			
		||||
{
 | 
			
		||||
    ix::initNetSystem();
 | 
			
		||||
 | 
			
		||||
    int result = Catch::Session().run(argc, argv);
 | 
			
		||||
 | 
			
		||||
    ix::uninitNetSystem();
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,6 @@ protected:
 | 
			
		||||
    const uint64_t max_batching_size = 32768;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}; // end namespace
 | 
			
		||||
} // end namespace
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -205,20 +205,18 @@ namespace ix
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void CobraConnection::configure(const std::string& appkey,
 | 
			
		||||
                                     const std::string& endpoint,
 | 
			
		||||
                                     const std::string& rolename,
 | 
			
		||||
                                     const std::string& rolesecret,
 | 
			
		||||
                                     WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions)
 | 
			
		||||
                                    const std::string& endpoint,
 | 
			
		||||
                                    const std::string& rolename,
 | 
			
		||||
                                    const std::string& rolesecret,
 | 
			
		||||
                                    const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions)
 | 
			
		||||
    {
 | 
			
		||||
        _appkey = appkey;
 | 
			
		||||
        _endpoint = endpoint;
 | 
			
		||||
        _role_name = rolename;
 | 
			
		||||
        _role_secret = rolesecret;
 | 
			
		||||
        _roleName = rolename;
 | 
			
		||||
        _roleSecret = rolesecret;
 | 
			
		||||
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << _endpoint;
 | 
			
		||||
        ss << endpoint;
 | 
			
		||||
        ss << "/v2?appkey=";
 | 
			
		||||
        ss << _appkey;
 | 
			
		||||
        ss << appkey;
 | 
			
		||||
 | 
			
		||||
        std::string url = ss.str();
 | 
			
		||||
        _webSocket->setUrl(url);
 | 
			
		||||
@@ -242,7 +240,7 @@ namespace ix
 | 
			
		||||
    bool CobraConnection::sendHandshakeMessage()
 | 
			
		||||
    {
 | 
			
		||||
        Json::Value data;
 | 
			
		||||
        data["role"] = _role_name;
 | 
			
		||||
        data["role"] = _roleName;
 | 
			
		||||
 | 
			
		||||
        Json::Value body;
 | 
			
		||||
        body["data"] = data;
 | 
			
		||||
@@ -304,7 +302,7 @@ namespace ix
 | 
			
		||||
    bool CobraConnection::sendAuthMessage(const std::string& nonce)
 | 
			
		||||
    {
 | 
			
		||||
        Json::Value credentials;
 | 
			
		||||
        credentials["hash"] = hmac(nonce, _role_secret);
 | 
			
		||||
        credentials["hash"] = hmac(nonce, _roleSecret);
 | 
			
		||||
 | 
			
		||||
        Json::Value body;
 | 
			
		||||
        body["credentials"] = credentials;
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ namespace ix
 | 
			
		||||
                       const std::string& endpoint,
 | 
			
		||||
                       const std::string& rolename,
 | 
			
		||||
                       const std::string& rolesecret,
 | 
			
		||||
                       WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions);
 | 
			
		||||
                       const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions);
 | 
			
		||||
 | 
			
		||||
        static void setTrafficTrackerCallback(const TrafficTrackerCallback& callback);
 | 
			
		||||
 | 
			
		||||
@@ -135,10 +135,8 @@ namespace ix
 | 
			
		||||
        std::unique_ptr<WebSocket> _webSocket;
 | 
			
		||||
 | 
			
		||||
        /// Configuration data
 | 
			
		||||
        std::string _appkey;
 | 
			
		||||
        std::string _endpoint;
 | 
			
		||||
        std::string _role_name;
 | 
			
		||||
        std::string _role_secret;
 | 
			
		||||
        std::string _roleName;
 | 
			
		||||
        std::string _roleSecret;
 | 
			
		||||
        std::atomic<CobraConnectionPublishMode> _publishMode;
 | 
			
		||||
 | 
			
		||||
        // Can be set on control+background thread, protecting with an atomic
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										73
									
								
								ws/ws.cpp
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								ws/ws.cpp
									
									
									
									
									
								
							@@ -21,9 +21,12 @@
 | 
			
		||||
 | 
			
		||||
#include <cli11/CLI11.hpp>
 | 
			
		||||
#include <ixwebsocket/IXSocket.h>
 | 
			
		||||
#include <ixwebsocket/IXNetSystem.h>
 | 
			
		||||
 | 
			
		||||
int main(int argc, char** argv)
 | 
			
		||||
{
 | 
			
		||||
    ix::initNetSystem();
 | 
			
		||||
 | 
			
		||||
    CLI::App app{"ws is a websocket tool"};
 | 
			
		||||
    app.require_subcommand();
 | 
			
		||||
 | 
			
		||||
@@ -199,88 +202,90 @@ int main(int argc, char** argv)
 | 
			
		||||
        f.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int ret = 1;
 | 
			
		||||
    if (app.got_subcommand("transfer"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_transfer_main(port, hostname);
 | 
			
		||||
        ret = ix::ws_transfer_main(port, hostname);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("send"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_send_main(url, path);
 | 
			
		||||
        ret = ix::ws_send_main(url, path);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("receive"))
 | 
			
		||||
    {
 | 
			
		||||
        bool enablePerMessageDeflate = false;
 | 
			
		||||
        return ix::ws_receive_main(url, enablePerMessageDeflate, delayMs);
 | 
			
		||||
        ret = ix::ws_receive_main(url, enablePerMessageDeflate, delayMs);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("connect"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_connect_main(url);
 | 
			
		||||
        ret = ix::ws_connect_main(url);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("chat"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_chat_main(url, user);
 | 
			
		||||
        ret = ix::ws_chat_main(url, user);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("echo_server"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_echo_server_main(port, hostname);
 | 
			
		||||
        ret = ix::ws_echo_server_main(port, hostname);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("broadcast_server"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_broadcast_server_main(port, hostname);
 | 
			
		||||
        ret = ix::ws_broadcast_server_main(port, hostname);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("ping"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_ping_pong_main(url);
 | 
			
		||||
        ret = ix::ws_ping_pong_main(url);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("curl"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_http_client_main(url, headers, data, headersOnly,
 | 
			
		||||
                                       connectTimeOut, transferTimeout,
 | 
			
		||||
                                       followRedirects, maxRedirects, verbose,
 | 
			
		||||
                                       save, output, compress);
 | 
			
		||||
        ret = ix::ws_http_client_main(url, headers, data, headersOnly,
 | 
			
		||||
                                      connectTimeOut, transferTimeout,
 | 
			
		||||
                                      followRedirects, maxRedirects, verbose,
 | 
			
		||||
                                      save, output, compress);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("redis_publish"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_redis_publish_main(hostname, redisPort, password,
 | 
			
		||||
                                         channel, message, count);
 | 
			
		||||
        ret = ix::ws_redis_publish_main(hostname, redisPort, password,
 | 
			
		||||
                                        channel, message, count);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("redis_subscribe"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_redis_subscribe_main(hostname, redisPort, password, channel, verbose);
 | 
			
		||||
        ret = ix::ws_redis_subscribe_main(hostname, redisPort, password, channel, verbose);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("cobra_subscribe"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_cobra_subscribe_main(appkey, endpoint,
 | 
			
		||||
                                           rolename, rolesecret,
 | 
			
		||||
                                           channel);
 | 
			
		||||
        ret = ix::ws_cobra_subscribe_main(appkey, endpoint,
 | 
			
		||||
                                          rolename, rolesecret,
 | 
			
		||||
                                          channel);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("cobra_publish"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_cobra_publish_main(appkey, endpoint,
 | 
			
		||||
                                         rolename, rolesecret,
 | 
			
		||||
                                         channel, path, stress);
 | 
			
		||||
        ret = ix::ws_cobra_publish_main(appkey, endpoint,
 | 
			
		||||
                                        rolename, rolesecret,
 | 
			
		||||
                                        channel, path, stress);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("cobra_to_statsd"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_cobra_to_statsd_main(appkey, endpoint,
 | 
			
		||||
                                           rolename, rolesecret,
 | 
			
		||||
                                           channel, hostname, statsdPort,
 | 
			
		||||
                                           prefix, fields, verbose);
 | 
			
		||||
        ret = ix::ws_cobra_to_statsd_main(appkey, endpoint,
 | 
			
		||||
                                          rolename, rolesecret,
 | 
			
		||||
                                          channel, hostname, statsdPort,
 | 
			
		||||
                                          prefix, fields, verbose);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("cobra_to_sentry"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_cobra_to_sentry_main(appkey, endpoint,
 | 
			
		||||
                                           rolename, rolesecret,
 | 
			
		||||
                                           channel, dsn,
 | 
			
		||||
                                           verbose, strict, jobs);
 | 
			
		||||
        ret = ix::ws_cobra_to_sentry_main(appkey, endpoint,
 | 
			
		||||
                                          rolename, rolesecret,
 | 
			
		||||
                                          channel, dsn,
 | 
			
		||||
                                          verbose, strict, jobs);
 | 
			
		||||
    }
 | 
			
		||||
    else if (app.got_subcommand("snake"))
 | 
			
		||||
    {
 | 
			
		||||
        return ix::ws_snake_main(port, hostname,
 | 
			
		||||
                                 redisHosts, redisPort,
 | 
			
		||||
                                 redisPassword, verbose,
 | 
			
		||||
                                 appsConfigPath);
 | 
			
		||||
        ret = ix::ws_snake_main(port, hostname,
 | 
			
		||||
                                redisHosts, redisPort,
 | 
			
		||||
                                redisPassword, verbose,
 | 
			
		||||
                                appsConfigPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
    ix::uninitNetSystem();
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user