Compare commits
27 Commits
feature/no
...
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 |
@@ -1 +0,0 @@
|
|||||||
docker/Dockerfile.fedora
|
|
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
FROM fedora:30 as build
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 fedora:30 as runtime
|
||||||
|
|
||||||
|
RUN yum install -y libtsan
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Now run in usermode
|
||||||
|
USER app
|
||||||
|
WORKDIR /home/app
|
||||||
|
|
||||||
|
COPY --chown=app:app ws/snake/appsConfig.json .
|
||||||
|
COPY --chown=app:app ws/cobraMetricsSample.json .
|
||||||
|
|
||||||
|
ENTRYPOINT ["ws"]
|
||||||
|
CMD ["--help"]
|
@@ -1,52 +0,0 @@
|
|||||||
# Build time
|
|
||||||
FROM debian:buster as build
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get -y install 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 ["make"]
|
|
||||||
|
|
||||||
# Runtime
|
|
||||||
FROM debian:buster 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"]
|
|
||||||
|
|
||||||
# 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
|
|
||||||
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
|
|
||||||
|
|
||||||
# Now run in usermode
|
|
||||||
USER app
|
|
||||||
WORKDIR /home/app
|
|
||||||
|
|
||||||
COPY --chown=app:app ws/snake/appsConfig.json .
|
|
||||||
COPY --chown=app:app ws/cobraMetricsSample.json .
|
|
||||||
|
|
||||||
ENTRYPOINT ["ws"]
|
|
||||||
CMD ["--help"]
|
|
@@ -1,42 +0,0 @@
|
|||||||
FROM fedora:30 as build
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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 fedora:30 as runtime
|
|
||||||
|
|
||||||
RUN yum install -y libtsan
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# Now run in usermode
|
|
||||||
USER app
|
|
||||||
WORKDIR /home/app
|
|
||||||
|
|
||||||
COPY --chown=app:app ws/snake/appsConfig.json .
|
|
||||||
COPY --chown=app:app ws/cobraMetricsSample.json .
|
|
||||||
|
|
||||||
ENTRYPOINT ["ws"]
|
|
||||||
CMD ["--help"]
|
|
@@ -1,24 +0,0 @@
|
|||||||
# Build time
|
|
||||||
FROM ubuntu:xenial as build
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get -y install 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
|
|
||||||
RUN apt-get -y install python
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
|
||||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
|
||||||
|
|
||||||
# RUN ["make"]
|
|
||||||
RUN ["make", "test"]
|
|
@@ -17,26 +17,28 @@ namespace ix
|
|||||||
std::atomic<uint64_t> DNSLookup::_nextId(0);
|
std::atomic<uint64_t> DNSLookup::_nextId(0);
|
||||||
std::set<uint64_t> DNSLookup::_activeJobs;
|
std::set<uint64_t> DNSLookup::_activeJobs;
|
||||||
std::mutex DNSLookup::_activeJobsMutex;
|
std::mutex DNSLookup::_activeJobsMutex;
|
||||||
|
std::mutex DNSLookup::_resMutex;
|
||||||
|
|
||||||
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
|
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
|
||||||
|
_hostname(hostname),
|
||||||
_port(port),
|
_port(port),
|
||||||
_wait(wait),
|
_wait(wait),
|
||||||
_res(nullptr),
|
_res(nullptr),
|
||||||
_done(false),
|
_done(false),
|
||||||
_id(_nextId++)
|
_id(_nextId++)
|
||||||
{
|
{
|
||||||
setHostname(hostname);
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
DNSLookup::~DNSLookup()
|
DNSLookup::~DNSLookup()
|
||||||
{
|
{
|
||||||
// Remove this job from the active jobs list
|
// Remove this job from the active jobs list
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
std::unique_lock<std::mutex> lock(_activeJobsMutex);
|
||||||
_activeJobs.erase(_id);
|
_activeJobs.erase(_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want hostname to be copied, not passed as a const reference
|
// we want hostname to be copied, not passed as a const reference
|
||||||
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
|
struct addrinfo* DNSLookup::getAddrInfo(std::string hostname,
|
||||||
int port,
|
int port,
|
||||||
std::string& errMsg)
|
std::string& errMsg)
|
||||||
{
|
{
|
||||||
@@ -79,7 +81,7 @@ namespace ix
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getAddrInfo(getHostname(), _port, errMsg);
|
return getAddrInfo(_hostname, _port, errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
|
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
|
||||||
@@ -97,7 +99,7 @@ namespace ix
|
|||||||
|
|
||||||
// Record job in the active Job set
|
// Record job in the active Job set
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
std::unique_lock<std::mutex> lock(_activeJobsMutex);
|
||||||
_activeJobs.insert(_id);
|
_activeJobs.insert(_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +107,7 @@ namespace ix
|
|||||||
// Good resource on thread forced termination
|
// Good resource on thread forced termination
|
||||||
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
||||||
//
|
//
|
||||||
_thread = std::thread(&DNSLookup::run, this, _id, getHostname(), _port);
|
_thread = std::thread(&DNSLookup::run, this, _id, _hostname, _port);
|
||||||
_thread.detach();
|
_thread.detach();
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
||||||
@@ -135,8 +137,13 @@ namespace ix
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
errMsg = getErrMsg();
|
if (!_errMsg.empty())
|
||||||
return getRes();
|
{
|
||||||
|
errMsg = _errMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> rlock(_resMutex);
|
||||||
|
return _res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
|
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
|
||||||
@@ -148,55 +155,21 @@ namespace ix
|
|||||||
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
||||||
|
|
||||||
// if this isn't an active job, and the control thread is gone
|
// if this isn't an active job, and the control thread is gone
|
||||||
// there is nothing to do, and we don't want to touch the defunct
|
// there is not thing to do, and we don't want to touch the defunct
|
||||||
// object data structure such as _errMsg or _condition
|
// object data structure such as _errMsg or _condition
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
std::unique_lock<std::mutex> lock(_activeJobsMutex);
|
||||||
if (_activeJobs.count(id) == 0)
|
if (_activeJobs.count(id) == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy result into the member variables
|
// Copy result into the member variables
|
||||||
setRes(res);
|
{
|
||||||
setErrMsg(errMsg);
|
std::unique_lock<std::mutex> rlock(_resMutex);
|
||||||
|
_res = res;
|
||||||
|
}
|
||||||
|
_errMsg = errMsg;
|
||||||
_condition.notify_one();
|
_condition.notify_one();
|
||||||
_done = true;
|
_done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSLookup::setHostname(const std::string& hostname)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_hostnameMutex);
|
|
||||||
_hostname = hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& DNSLookup::getHostname()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_hostnameMutex);
|
|
||||||
return _hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DNSLookup::setErrMsg(const std::string& errMsg)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
|
||||||
_errMsg = errMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& DNSLookup::getErrMsg()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
|
||||||
return _errMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DNSLookup::setRes(struct addrinfo* addr)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_resMutex);
|
|
||||||
_res = addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::getRes()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_resMutex);
|
|
||||||
return _res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -39,32 +39,18 @@ namespace ix
|
|||||||
struct addrinfo* resolveBlocking(std::string& errMsg,
|
struct addrinfo* resolveBlocking(std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
|
|
||||||
static struct addrinfo* getAddrInfo(const std::string& hostname,
|
static struct addrinfo* getAddrInfo(std::string hostname,
|
||||||
int port,
|
int port,
|
||||||
std::string& errMsg);
|
std::string& errMsg);
|
||||||
|
|
||||||
void run(uint64_t id, const std::string& hostname, int port); // thread runner
|
void run(uint64_t id, const std::string& hostname, int port); // thread runner
|
||||||
|
|
||||||
void setHostname(const std::string& hostname);
|
|
||||||
const std::string& getHostname();
|
|
||||||
|
|
||||||
void setErrMsg(const std::string& errMsg);
|
|
||||||
const std::string& getErrMsg();
|
|
||||||
|
|
||||||
void setRes(struct addrinfo* addr);
|
|
||||||
struct addrinfo* getRes();
|
|
||||||
|
|
||||||
std::string _hostname;
|
std::string _hostname;
|
||||||
std::mutex _hostnameMutex;
|
|
||||||
int _port;
|
int _port;
|
||||||
|
|
||||||
int64_t _wait;
|
int64_t _wait;
|
||||||
|
|
||||||
struct addrinfo* _res;
|
|
||||||
std::mutex _resMutex;
|
|
||||||
|
|
||||||
std::string _errMsg;
|
std::string _errMsg;
|
||||||
std::mutex _errMsgMutex;
|
struct addrinfo* _res;
|
||||||
|
static std::mutex _resMutex;
|
||||||
|
|
||||||
std::atomic<bool> _done;
|
std::atomic<bool> _done;
|
||||||
std::thread _thread;
|
std::thread _thread;
|
||||||
|
@@ -216,9 +216,14 @@ namespace ix
|
|||||||
return getReadyState() == WebSocket_ReadyState_Closing;
|
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()
|
void WebSocket::reconnectPerpetuallyIfDisconnected()
|
||||||
@@ -229,27 +234,6 @@ namespace ix
|
|||||||
using millis = std::chrono::duration<double, std::milli>;
|
using millis = std::chrono::duration<double, std::milli>;
|
||||||
millis duration;
|
millis duration;
|
||||||
|
|
||||||
// Try to connect only once when we don't have automaticReconnection setup
|
|
||||||
if (!isConnected() && !isClosing() && !_stop && !_automaticReconnection)
|
|
||||||
{
|
|
||||||
status = connect(_handshakeTimeoutSecs);
|
|
||||||
|
|
||||||
if (!status.success)
|
|
||||||
{
|
|
||||||
duration = millis(calculateRetryWaitMilliseconds(retries++));
|
|
||||||
|
|
||||||
connectErr.retries = retries;
|
|
||||||
connectErr.wait_time = duration.count();
|
|
||||||
connectErr.reason = status.errorStr;
|
|
||||||
connectErr.http_status = status.http_status;
|
|
||||||
_onMessageCallback(WebSocket_MessageType_Error, "", 0,
|
|
||||||
connectErr, WebSocketOpenInfo(),
|
|
||||||
WebSocketCloseInfo());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Otherwise try to reconnect perpertually
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
|
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
|
||||||
@@ -259,7 +243,7 @@ namespace ix
|
|||||||
|
|
||||||
status = connect(_handshakeTimeoutSecs);
|
status = connect(_handshakeTimeoutSecs);
|
||||||
|
|
||||||
if (!status.success)
|
if (!status.success && !_stop)
|
||||||
{
|
{
|
||||||
duration = millis(calculateRetryWaitMilliseconds(retries++));
|
duration = millis(calculateRetryWaitMilliseconds(retries++));
|
||||||
|
|
||||||
@@ -271,15 +255,10 @@ namespace ix
|
|||||||
connectErr, WebSocketOpenInfo(),
|
connectErr, WebSocketOpenInfo(),
|
||||||
WebSocketCloseInfo());
|
WebSocketCloseInfo());
|
||||||
|
|
||||||
// Only sleep if we aren't in the middle of stopping
|
|
||||||
if (!_stop)
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(duration);
|
std::this_thread::sleep_for(duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::run()
|
void WebSocket::run()
|
||||||
{
|
{
|
||||||
@@ -287,20 +266,17 @@ namespace ix
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (_stop) return;
|
if (_stop && !isClosing()) return;
|
||||||
|
|
||||||
// 1. Make sure we are always connected
|
// 1. Make sure we are always connected
|
||||||
reconnectPerpetuallyIfDisconnected();
|
reconnectPerpetuallyIfDisconnected();
|
||||||
|
|
||||||
if (_stop) return;
|
|
||||||
|
|
||||||
// 2. Poll to see if there's any new data available
|
// 2. Poll to see if there's any new data available
|
||||||
_ws.poll();
|
WebSocketTransport::PollPostTreatment pollPostTreatment = _ws.poll();
|
||||||
|
|
||||||
if (_stop) return;
|
|
||||||
|
|
||||||
// 3. Dispatch the incoming messages
|
// 3. Dispatch the incoming messages
|
||||||
_ws.dispatch(
|
_ws.dispatch(
|
||||||
|
pollPostTreatment,
|
||||||
[this](const std::string& msg,
|
[this](const std::string& msg,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
bool decompressionError,
|
bool decompressionError,
|
||||||
@@ -342,8 +318,8 @@ namespace ix
|
|||||||
|
|
||||||
// 4. In blocking mode, getting out of this function is triggered by
|
// 4. In blocking mode, getting out of this function is triggered by
|
||||||
// an explicit disconnection from the callback, or by the remote end
|
// an explicit disconnection from the callback, or by the remote end
|
||||||
// closing the connection, ie isConnected() == false.
|
// closing the connection, ie isConnectedOrClosing() == false.
|
||||||
if (!isConnected() && !_automaticReconnection) return;
|
if (!_thread.joinable() && !isConnectedOrClosing() && !_automaticReconnection) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -111,7 +111,8 @@ namespace ix
|
|||||||
WebSocketSendInfo sendText(const std::string& text,
|
WebSocketSendInfo sendText(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo ping(const std::string& text);
|
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);
|
void setOnMessageCallback(const OnMessageCallback& callback);
|
||||||
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
||||||
@@ -136,6 +137,7 @@ namespace ix
|
|||||||
|
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
bool isClosing() const;
|
bool isClosing() const;
|
||||||
|
bool isConnectedOrClosing() const;
|
||||||
void reconnectPerpetuallyIfDisconnected();
|
void reconnectPerpetuallyIfDisconnected();
|
||||||
std::string readyStateToString(ReadyState readyState);
|
std::string readyStateToString(ReadyState readyState);
|
||||||
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
|
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
|
||||||
@@ -154,7 +156,6 @@ namespace ix
|
|||||||
static OnTrafficTrackerCallback _onTrafficTrackerCallback;
|
static OnTrafficTrackerCallback _onTrafficTrackerCallback;
|
||||||
|
|
||||||
std::atomic<bool> _stop;
|
std::atomic<bool> _stop;
|
||||||
std::atomic<bool> _backgroundThreadRunning;
|
|
||||||
std::atomic<bool> _automaticReconnection;
|
std::atomic<bool> _automaticReconnection;
|
||||||
std::thread _thread;
|
std::thread _thread;
|
||||||
std::mutex _writeMutex;
|
std::mutex _writeMutex;
|
||||||
|
@@ -72,7 +72,9 @@ namespace ix
|
|||||||
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
||||||
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
|
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
|
||||||
const bool WebSocketTransport::kDefaultEnablePong(true);
|
const bool WebSocketTransport::kDefaultEnablePong(true);
|
||||||
|
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(200);
|
||||||
constexpr size_t WebSocketTransport::kChunkSize;
|
constexpr size_t WebSocketTransport::kChunkSize;
|
||||||
|
|
||||||
const uint16_t WebSocketTransport::kInternalErrorCode(1011);
|
const uint16_t WebSocketTransport::kInternalErrorCode(1011);
|
||||||
const uint16_t WebSocketTransport::kAbnormalCloseCode(1006);
|
const uint16_t WebSocketTransport::kAbnormalCloseCode(1006);
|
||||||
const uint16_t WebSocketTransport::kProtocolErrorCode(1002);
|
const uint16_t WebSocketTransport::kProtocolErrorCode(1002);
|
||||||
@@ -90,6 +92,7 @@ namespace ix
|
|||||||
_closeRemote(false),
|
_closeRemote(false),
|
||||||
_enablePerMessageDeflate(false),
|
_enablePerMessageDeflate(false),
|
||||||
_requestInitCancellation(false),
|
_requestInitCancellation(false),
|
||||||
|
_closingTimePoint(std::chrono::steady_clock::now()),
|
||||||
_enablePong(kDefaultEnablePong),
|
_enablePong(kDefaultEnablePong),
|
||||||
_pingIntervalSecs(kDefaultPingIntervalSecs),
|
_pingIntervalSecs(kDefaultPingIntervalSecs),
|
||||||
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
|
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
|
||||||
@@ -246,9 +249,19 @@ namespace ix
|
|||||||
return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs);
|
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)
|
if (_readyState == OPEN)
|
||||||
{
|
{
|
||||||
@@ -296,24 +309,19 @@ namespace ix
|
|||||||
{
|
{
|
||||||
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
||||||
|
|
||||||
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
if (ret < 0 && _readyState != CLOSING && (_socket->getErrno() == EWOULDBLOCK ||
|
||||||
_socket->getErrno() == EAGAIN))
|
_socket->getErrno() == EAGAIN))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (ret <= 0)
|
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();
|
_socket->close();
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
return CHECK_OR_RAISE_ABNORMAL_CLOSE_AFTER_DISPATCH;
|
||||||
_closeCode = kAbnormalCloseCode;
|
|
||||||
_closeReason = kAbnormalCloseMessage;
|
|
||||||
_closeWireSize = 0;
|
|
||||||
_closeRemote = true;
|
|
||||||
}
|
|
||||||
setReadyState(CLOSED);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -332,12 +340,15 @@ namespace ix
|
|||||||
_socket->close();
|
_socket->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid a race condition where we get stuck in select
|
if (_readyState == CLOSING && closingDelayExceeded())
|
||||||
// while closing.
|
|
||||||
if (_readyState == CLOSING)
|
|
||||||
{
|
{
|
||||||
|
_rxbuf.clear();
|
||||||
|
// close code and reason were set when calling close()
|
||||||
_socket->close();
|
_socket->close();
|
||||||
|
setReadyState(CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketTransport::isSendBufferEmpty() const
|
bool WebSocketTransport::isSendBufferEmpty() const
|
||||||
@@ -399,12 +410,13 @@ namespace ix
|
|||||||
// | Payload Data continued ... |
|
// | Payload Data continued ... |
|
||||||
// +---------------------------------------------------------------+
|
// +---------------------------------------------------------------+
|
||||||
//
|
//
|
||||||
void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback)
|
void WebSocketTransport::dispatch(WebSocketTransport::PollPostTreatment pollPostTreatment,
|
||||||
|
const OnMessageCallback& onMessageCallback)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
wsheader_type ws;
|
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
|
const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume
|
||||||
ws.fin = (data[0] & 0x80) == 0x80;
|
ws.fin = (data[0] & 0x80) == 0x80;
|
||||||
ws.rsv1 = (data[0] & 0x40) == 0x40;
|
ws.rsv1 = (data[0] & 0x40) == 0x40;
|
||||||
@@ -412,7 +424,7 @@ namespace ix
|
|||||||
ws.mask = (data[1] & 0x80) == 0x80;
|
ws.mask = (data[1] & 0x80) == 0x80;
|
||||||
ws.N0 = (data[1] & 0x7f);
|
ws.N0 = (data[1] & 0x7f);
|
||||||
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
|
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:
|
// Calculate payload length:
|
||||||
@@ -554,9 +566,25 @@ namespace ix
|
|||||||
std::string reason(_rxbuf.begin()+ws.header_size + 2,
|
std::string reason(_rxbuf.begin()+ws.header_size + 2,
|
||||||
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
||||||
|
|
||||||
bool remote = true;
|
|
||||||
|
|
||||||
close(code, reason, _rxbuf.size(), remote);
|
// 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);
|
||||||
|
|
||||||
|
_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
|
else
|
||||||
{
|
{
|
||||||
@@ -569,6 +597,25 @@ namespace ix
|
|||||||
_rxbuf.erase(_rxbuf.begin(),
|
_rxbuf.erase(_rxbuf.begin(),
|
||||||
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
|
_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
|
std::string WebSocketTransport::getMergedChunks() const
|
||||||
@@ -863,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:
|
// See list of close events here:
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||||
|
|
||||||
@@ -881,11 +925,11 @@ namespace ix
|
|||||||
|
|
||||||
bool compress = false;
|
bool compress = false;
|
||||||
sendData(wsheader_type::CLOSE, closure, compress);
|
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();
|
_socket->close();
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||||
_closeCode = code;
|
_closeCode = code;
|
||||||
@@ -893,10 +937,33 @@ namespace ix
|
|||||||
_closeWireSize = closeWireSize;
|
_closeWireSize = closeWireSize;
|
||||||
_closeRemote = remote;
|
_closeRemote = remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
setReadyState(CLOSED);
|
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
|
size_t WebSocketTransport::bufferedAmount() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_txbufMutex);
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
||||||
|
@@ -56,6 +56,12 @@ namespace ix
|
|||||||
FRAGMENT
|
FRAGMENT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum PollPostTreatment
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
CHECK_OR_RAISE_ABNORMAL_CLOSE_AFTER_DISPATCH
|
||||||
|
};
|
||||||
|
|
||||||
using OnMessageCallback = std::function<void(const std::string&,
|
using OnMessageCallback = std::function<void(const std::string&,
|
||||||
size_t,
|
size_t,
|
||||||
bool,
|
bool,
|
||||||
@@ -78,7 +84,7 @@ namespace ix
|
|||||||
WebSocketInitResult connectToSocket(int fd, // Server
|
WebSocketInitResult connectToSocket(int fd, // Server
|
||||||
int timeoutSecs);
|
int timeoutSecs);
|
||||||
|
|
||||||
void poll();
|
PollPostTreatment poll();
|
||||||
WebSocketSendInfo sendBinary(const std::string& message,
|
WebSocketSendInfo sendBinary(const std::string& message,
|
||||||
const OnProgressCallback& onProgressCallback);
|
const OnProgressCallback& onProgressCallback);
|
||||||
WebSocketSendInfo sendText(const std::string& message,
|
WebSocketSendInfo sendText(const std::string& message,
|
||||||
@@ -93,7 +99,8 @@ namespace ix
|
|||||||
ReadyStateValues getReadyState() const;
|
ReadyStateValues getReadyState() const;
|
||||||
void setReadyState(ReadyStateValues readyStateValue);
|
void setReadyState(ReadyStateValues readyStateValue);
|
||||||
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
||||||
void dispatch(const OnMessageCallback& onMessageCallback);
|
void dispatch(PollPostTreatment pollPostTreatment,
|
||||||
|
const OnMessageCallback& onMessageCallback);
|
||||||
size_t bufferedAmount() const;
|
size_t bufferedAmount() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -119,7 +126,7 @@ namespace ix
|
|||||||
|
|
||||||
// Tells whether we should mask the data we send.
|
// Tells whether we should mask the data we send.
|
||||||
// client should mask but server should not
|
// client should mask but server should not
|
||||||
std::atomic<bool> _useMask;
|
bool _useMask;
|
||||||
|
|
||||||
// Buffer for reading from our socket. That buffer is never resized.
|
// Buffer for reading from our socket. That buffer is never resized.
|
||||||
std::vector<uint8_t> _readbuf;
|
std::vector<uint8_t> _readbuf;
|
||||||
@@ -163,6 +170,10 @@ namespace ix
|
|||||||
// Used to cancel dns lookup + socket connect + http upgrade
|
// Used to cancel dns lookup + socket connect + http upgrade
|
||||||
std::atomic<bool> _requestInitCancellation;
|
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
|
// Constants for dealing with closing conneections
|
||||||
static const uint16_t kInternalErrorCode;
|
static const uint16_t kInternalErrorCode;
|
||||||
static const uint16_t kAbnormalCloseCode;
|
static const uint16_t kAbnormalCloseCode;
|
||||||
@@ -201,6 +212,16 @@ namespace ix
|
|||||||
// No PONG data was received through the socket for longer than ping timeout delay
|
// No PONG data was received through the socket for longer than ping timeout delay
|
||||||
bool pingTimeoutExceeded();
|
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();
|
void sendOnSocket();
|
||||||
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
||||||
const std::string& message,
|
const std::string& message,
|
||||||
|
@@ -8,8 +8,8 @@ project (ixwebsocket_unittest)
|
|||||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
||||||
find_package(Sanitizers)
|
find_package(Sanitizers)
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
# set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ set (SOURCES
|
|||||||
# Some unittest don't work on windows yet
|
# Some unittest don't work on windows yet
|
||||||
if (NOT WIN32)
|
if (NOT WIN32)
|
||||||
list(APPEND SOURCES
|
list(APPEND SOURCES
|
||||||
|
IXWebSocketCloseTest.cpp
|
||||||
IXWebSocketServerTest.cpp
|
IXWebSocketServerTest.cpp
|
||||||
IXWebSocketPingTest.cpp
|
IXWebSocketPingTest.cpp
|
||||||
IXWebSocketPingTimeoutTest.cpp
|
IXWebSocketPingTimeoutTest.cpp
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@@ -251,8 +251,8 @@ def executeJob(job):
|
|||||||
sys.stderr.write('.')
|
sys.stderr.write('.')
|
||||||
# print('Executing ' + job['cmd'] + '...')
|
# print('Executing ' + job['cmd'] + '...')
|
||||||
|
|
||||||
# 2 minutes of timeout for a single test
|
# 10 minutes of timeout for a single test, cf PR #42
|
||||||
timeout = 2 * 60
|
timeout = 10 * 60
|
||||||
command = Command(job['cmd'])
|
command = Command(job['cmd'])
|
||||||
timedout, ret = command.run(timeout)
|
timedout, ret = command.run(timeout)
|
||||||
|
|
||||||
|
@@ -58,7 +58,6 @@ int main(int argc, char** argv)
|
|||||||
bool compress = false;
|
bool compress = false;
|
||||||
bool strict = false;
|
bool strict = false;
|
||||||
bool stress = false;
|
bool stress = false;
|
||||||
bool disableAutomaticReconnection = false;
|
|
||||||
int port = 8080;
|
int port = 8080;
|
||||||
int redisPort = 6379;
|
int redisPort = 6379;
|
||||||
int statsdPort = 8125;
|
int statsdPort = 8125;
|
||||||
@@ -88,7 +87,6 @@ int main(int argc, char** argv)
|
|||||||
|
|
||||||
CLI::App* connectApp = app.add_subcommand("connect", "Connect to a remote server");
|
CLI::App* connectApp = app.add_subcommand("connect", "Connect to a remote server");
|
||||||
connectApp->add_option("url", url, "Connection url")->required();
|
connectApp->add_option("url", url, "Connection url")->required();
|
||||||
connectApp->add_flag("-d", disableAutomaticReconnection, "Disable Automatic Reconnection");
|
|
||||||
|
|
||||||
CLI::App* chatApp = app.add_subcommand("chat", "Group chat");
|
CLI::App* chatApp = app.add_subcommand("chat", "Group chat");
|
||||||
chatApp->add_option("url", url, "Connection url")->required();
|
chatApp->add_option("url", url, "Connection url")->required();
|
||||||
@@ -220,7 +218,7 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
else if (app.got_subcommand("connect"))
|
else if (app.got_subcommand("connect"))
|
||||||
{
|
{
|
||||||
ret = ix::ws_connect_main(url, disableAutomaticReconnection);
|
ret = ix::ws_connect_main(url);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("chat"))
|
else if (app.got_subcommand("chat"))
|
||||||
{
|
{
|
||||||
|
2
ws/ws.h
2
ws/ws.h
@@ -31,7 +31,7 @@ namespace ix
|
|||||||
int ws_chat_main(const std::string& url,
|
int ws_chat_main(const std::string& url,
|
||||||
const std::string& user);
|
const std::string& user);
|
||||||
|
|
||||||
int ws_connect_main(const std::string& url, bool disableAutomaticReconnection);
|
int ws_connect_main(const std::string& url);
|
||||||
|
|
||||||
int ws_receive_main(const std::string& url,
|
int ws_receive_main(const std::string& url,
|
||||||
bool enablePerMessageDeflate,
|
bool enablePerMessageDeflate,
|
||||||
|
@@ -14,8 +14,7 @@ namespace ix
|
|||||||
class WebSocketConnect
|
class WebSocketConnect
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WebSocketConnect(const std::string& _url,
|
WebSocketConnect(const std::string& _url);
|
||||||
bool disableAutomaticReconnection);
|
|
||||||
|
|
||||||
void subscribe(const std::string& channel);
|
void subscribe(const std::string& channel);
|
||||||
void start();
|
void start();
|
||||||
@@ -30,17 +29,10 @@ namespace ix
|
|||||||
void log(const std::string& msg);
|
void log(const std::string& msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
WebSocketConnect::WebSocketConnect(const std::string& url,
|
WebSocketConnect::WebSocketConnect(const std::string& url) :
|
||||||
bool disableAutomaticReconnection) :
|
|
||||||
_url(url)
|
_url(url)
|
||||||
{
|
{
|
||||||
if (disableAutomaticReconnection)
|
;
|
||||||
{
|
|
||||||
std::cout << "Disabling automatic reconnection with "
|
|
||||||
"_webSocket.disableAutomaticReconnection()"
|
|
||||||
" not supported yet" << std::endl;
|
|
||||||
_webSocket.disableAutomaticReconnection();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketConnect::log(const std::string& msg)
|
void WebSocketConnect::log(const std::string& msg)
|
||||||
@@ -121,10 +113,10 @@ namespace ix
|
|||||||
_webSocket.send(text);
|
_webSocket.send(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ws_connect_main(const std::string& url, bool disableAutomaticReconnection)
|
void interactiveMain(const std::string& url)
|
||||||
{
|
{
|
||||||
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
|
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
|
||||||
WebSocketConnect webSocketChat(url, disableAutomaticReconnection);
|
WebSocketConnect webSocketChat(url);
|
||||||
webSocketChat.start();
|
webSocketChat.start();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
@@ -157,7 +149,11 @@ namespace ix
|
|||||||
|
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
webSocketChat.stop();
|
webSocketChat.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ws_connect_main(const std::string& url)
|
||||||
|
{
|
||||||
|
interactiveMain(url);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user