Compare commits

..

1 Commits

Author SHA1 Message Date
Benjamin Sergeant
5e1a4541bf play with cpp_redis 2019-03-29 11:42:45 -07:00
391 changed files with 13030 additions and 82059 deletions

View File

@@ -1,3 +1 @@
build build
CMakeCache.txt
ws/CMakeCache.txt

View File

@@ -26,7 +26,6 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXSocketFactory.cpp ixwebsocket/IXSocketFactory.cpp
ixwebsocket/IXDNSLookup.cpp ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXCancellationRequest.cpp ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXNetSystem.cpp
ixwebsocket/IXWebSocket.cpp ixwebsocket/IXWebSocket.cpp
ixwebsocket/IXWebSocketServer.cpp ixwebsocket/IXWebSocketServer.cpp
ixwebsocket/IXWebSocketTransport.cpp ixwebsocket/IXWebSocketTransport.cpp
@@ -37,8 +36,8 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXWebSocketHttpHeaders.cpp ixwebsocket/IXWebSocketHttpHeaders.cpp
ixwebsocket/IXHttpClient.cpp ixwebsocket/IXHttpClient.cpp
ixwebsocket/IXUrlParser.cpp ixwebsocket/IXUrlParser.cpp
ixwebsocket/LUrlParser.cpp
ixwebsocket/IXSelectInterrupt.cpp ixwebsocket/IXSelectInterrupt.cpp
ixwebsocket/IXSelectInterruptPipe.cpp
ixwebsocket/IXSelectInterruptFactory.cpp ixwebsocket/IXSelectInterruptFactory.cpp
ixwebsocket/IXConnectionState.cpp ixwebsocket/IXConnectionState.cpp
) )
@@ -51,7 +50,6 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXSetThreadName.h ixwebsocket/IXSetThreadName.h
ixwebsocket/IXDNSLookup.h ixwebsocket/IXDNSLookup.h
ixwebsocket/IXCancellationRequest.h ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXNetSystem.h
ixwebsocket/IXProgressCallback.h ixwebsocket/IXProgressCallback.h
ixwebsocket/IXWebSocket.h ixwebsocket/IXWebSocket.h
ixwebsocket/IXWebSocketServer.h ixwebsocket/IXWebSocketServer.h
@@ -66,18 +64,12 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/libwshandshake.hpp ixwebsocket/libwshandshake.hpp
ixwebsocket/IXHttpClient.h ixwebsocket/IXHttpClient.h
ixwebsocket/IXUrlParser.h ixwebsocket/IXUrlParser.h
ixwebsocket/LUrlParser.h
ixwebsocket/IXSelectInterrupt.h ixwebsocket/IXSelectInterrupt.h
ixwebsocket/IXSelectInterruptPipe.h
ixwebsocket/IXSelectInterruptFactory.h ixwebsocket/IXSelectInterruptFactory.h
ixwebsocket/IXConnectionState.h ixwebsocket/IXConnectionState.h
) )
if (UNIX)
# Linux, Mac, iOS, Android
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.cpp )
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.h )
endif()
# Platform specific code # Platform specific code
if (APPLE) if (APPLE)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp) list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
@@ -89,7 +81,6 @@ else()
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h) list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
endif() endif()
set(USE_OPEN_SSL FALSE)
if (USE_TLS) if (USE_TLS)
add_definitions(-DIXWEBSOCKET_USE_TLS) add_definitions(-DIXWEBSOCKET_USE_TLS)
@@ -100,7 +91,6 @@ if (USE_TLS)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h) list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp) list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
else() else()
set(USE_OPEN_SSL TRUE)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h) list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp) list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
endif() endif()
@@ -111,50 +101,38 @@ add_library( ixwebsocket STATIC
${IXWEBSOCKET_HEADERS} ${IXWEBSOCKET_HEADERS}
) )
if (APPLE AND USE_TLS) # gcc/Linux needs -pthread
target_link_libraries(ixwebsocket "-framework foundation" "-framework security") find_package(Threads)
endif()
if (USE_OPEN_SSL) if(UNIX AND NOT APPLE)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
add_definitions(${OPENSSL_DEFINITIONS}) add_definitions(${OPENSSL_DEFINITIONS})
message(STATUS "OpenSSL: " ${OPENSSL_VERSION}) message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
include_directories(${OPENSSL_INCLUDE_DIR}) include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
endif() endif()
if (WIN32) if (WIN32)
add_subdirectory(third_party/zlib) get_filename_component(libz_path
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib) ${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/release_dynamic/x64/lib/zlib.lib
target_link_libraries(ixwebsocket zlibstatic wsock32 ws2_32) ABSOLUTE)
add_library(libz STATIC IMPORTED)
set_target_properties(libz PROPERTIES IMPORTED_LOCATION
${libz_path})
include_directories(${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/include)
target_link_libraries(ixwebsocket libz wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS) add_definitions(-D_CRT_SECURE_NO_WARNINGS)
else() else()
# gcc/Linux needs -pthread
find_package(Threads)
target_link_libraries(ixwebsocket target_link_libraries(ixwebsocket
z ${CMAKE_THREAD_LIBS_INIT}) z ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
endif() endif()
set( IXWEBSOCKET_INCLUDE_DIRS set( IXWEBSOCKET_INCLUDE_DIRS
. .
) ../../shared/OpenSSL/include)
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} ) target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}") add_subdirectory(ws)
add_subdirectory(third_party/cpp_redis)
install(TARGETS ixwebsocket
ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
)
if (NOT WIN32)
add_subdirectory(ws)
endif()

View File

@@ -1 +1 @@
1.4.3 1.3.2

View File

@@ -1 +1 @@
docker/Dockerfile.fedora Dockerfile.dev

31
Dockerfile.dev Normal file
View File

@@ -0,0 +1,31 @@
FROM debian:stretch
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install g++
RUN apt-get -y install libssl-dev
RUN apt-get -y install gdb
RUN apt-get -y install screen
RUN apt-get -y install procps
RUN apt-get -y install lsof
RUN apt-get -y install libz-dev
RUN apt-get -y install vim
RUN apt-get -y install make
RUN apt-get -y install cmake
RUN apt-get -y install curl
RUN apt-get -y install python
RUN apt-get -y install netcat
# debian strech cmake is too old for building with Docker
COPY makefile .
RUN ["make", "install_cmake_for_linux"]
COPY . .
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-rc4-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
# RUN ["make"]
EXPOSE 8765
CMD ["/ws/ws", "transfer", "--port", "8765", "--host", "0.0.0.0"]

View File

@@ -1,24 +1,30 @@
# Build time FROM debian:buster
FROM ubuntu:xenial as build
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update RUN apt-get update
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 wget RUN apt-get -y install wget
RUN mkdir -p /tmp/cmake RUN mkdir -p /tmp/cmake
WORKDIR /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 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 tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
RUN apt-get -y install g++ RUN adduser app
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 . . COPY . .
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}" ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
# RUN ["make"] RUN ["make"]
RUN ["make", "test"]
# Now run in usermode
USER app
EXPOSE 8765
CMD ["bash"]

View File

@@ -10,7 +10,8 @@
* iOS * iOS
* Linux * Linux
* Android * Android
* Windows (no TLS)
The code was made to compile once on Windows but support is currently broken on this platform.
## Examples ## Examples
@@ -34,8 +35,8 @@ webSocket.setOnMessageCallback(
const std::string& str, const std::string& str,
size_t wireSize, size_t wireSize,
const ix::WebSocketErrorInfo& error, const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo, const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketCloseInfo& closeInfo) const ix::WebSocketHttpHeaders& headers)
{ {
if (messageType == ix::WebSocket_MessageType_Message) if (messageType == ix::WebSocket_MessageType_Message)
{ {
@@ -186,36 +187,11 @@ auto downloadSize = std::get<6>(out);
## Build ## Build
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice. CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project.
``` There is a Dockerfile for running some code on Linux, and a unittest which can be executed by typing `make test`.
mkdir build # make a build dir so that you can build out of tree.
cd build
cmake ..
make -j
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
```
Headers and a static library will be installed to the target dir. You can build and install the ws command line tool with Homebrew.
A [conan](https://conan.io/) file is available at [conan-IXWebSocket](https://github.com/Zinnion/conan-IXWebSocket).
There is a unittest which can be executed by typing `make test`.
There is a Dockerfile for running some code on Linux. To use docker-compose you must make a docker container first.
```
$ make docker
...
$ docker compose up &
...
$ docker exec -it ixwebsocket_ws_1 bash
app@ca2340eb9106:~$ ws --help
ws is a websocket tool
...
```
Finally you can build and install the `ws command line tool` with Homebrew. The homebrew version might be slightly out of date.
``` ```
brew tap bsergean/IXWebSocket brew tap bsergean/IXWebSocket
@@ -316,8 +292,8 @@ webSocket.setOnMessageCallback(
const std::string& str, const std::string& str,
size_t wireSize, size_t wireSize,
const ix::WebSocketErrorInfo& error, const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo, const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketCloseInfo& closeInfo) const ix::WebSocketHttpHeaders& headers)
{ {
if (messageType == ix::WebSocket_MessageType_Open) if (messageType == ix::WebSocket_MessageType_Open)
{ {
@@ -353,8 +329,8 @@ webSocket.setOnMessageCallback(
const std::string& str, const std::string& str,
size_t wireSize, size_t wireSize,
const ix::WebSocketErrorInfo& error, const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo, const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketCloseInfo& closeInfo) const ix::WebSocketHttpHeaders& headers)
{ {
if (messageType == ix::WebSocket_MessageType_Error) if (messageType == ix::WebSocket_MessageType_Error)
{ {
@@ -393,8 +369,8 @@ webSocket.setOnMessageCallback(
const std::string& str, const std::string& str,
size_t wireSize, size_t wireSize,
const ix::WebSocketErrorInfo& error, const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo, const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketCloseInfo& closeInfo) const ix::WebSocketHttpHeaders& headers)
{ {
if (messageType == ix::WebSocket_MessageType_Ping || if (messageType == ix::WebSocket_MessageType_Ping ||
messageType == ix::WebSocket_MessageType_Pong) messageType == ix::WebSocket_MessageType_Pong)

View File

@@ -1,14 +1,10 @@
image: image:
- Visual Studio 2017 - Visual Studio 2017
- Ubuntu
install: install:
- ls -al - ls -al
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
- cd test - python test/run.py
- mkdir build
- cd build
- cmake -G"NMake Makefiles" ..
- nmake
- ixwebsocket_unittest.exe
build: off build: off

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -0,0 +1,2 @@
all:
(cd .. ; make docker && make docker_push)

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
doc/redis_conf_2019/neo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1,23 +1,11 @@
version: "3" version: "3"
services: services:
snake:
image: bsergean/ws:build
entrypoint: ws snake --port 8765 --host 0.0.0.0 --redis_hosts redis1
ports:
- "8765:8765"
networks:
- ws-net
depends_on:
- redis1
ws: ws:
security_opt:
- seccomp:unconfined
cap_add:
- SYS_PTRACE
stdin_open: true stdin_open: true
tty: true tty: true
image: bsergean/ws:build image: bsergean/ws:build
ports:
- "8765:8765"
entrypoint: bash entrypoint: bash
networks: networks:
- ws-net - ws-net

View File

@@ -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"]

View File

@@ -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"]

View File

@@ -10,7 +10,7 @@ namespace ix
{ {
std::atomic<uint64_t> ConnectionState::_globalId(0); std::atomic<uint64_t> ConnectionState::_globalId(0);
ConnectionState::ConnectionState() : _terminated(false) ConnectionState::ConnectionState()
{ {
computeId(); computeId();
} }
@@ -29,15 +29,5 @@ namespace ix
{ {
return std::make_shared<ConnectionState>(); return std::make_shared<ConnectionState>();
} }
bool ConnectionState::isTerminated() const
{
return _terminated;
}
void ConnectionState::setTerminated()
{
_terminated = true;
}
} }

View File

@@ -21,13 +21,9 @@ namespace ix
virtual void computeId(); virtual void computeId();
virtual const std::string& getId() const; virtual const std::string& getId() const;
void setTerminated();
bool isTerminated() const;
static std::shared_ptr<ConnectionState> createConnectionState(); static std::shared_ptr<ConnectionState> createConnectionState();
protected: protected:
std::atomic<bool> _terminated;
std::string _id; std::string _id;
static std::atomic<uint64_t> _globalId; static std::atomic<uint64_t> _globalId;

View File

@@ -19,19 +19,20 @@ namespace ix
std::mutex DNSLookup::_activeJobsMutex; std::mutex DNSLookup::_activeJobsMutex;
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);
} }
@@ -78,7 +79,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,
@@ -96,7 +97,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);
} }
@@ -104,7 +105,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);
@@ -134,8 +135,7 @@ namespace ix
return nullptr; return nullptr;
} }
errMsg = getErrMsg(); return _res;
return getRes();
} }
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
@@ -147,55 +147,18 @@ 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); _res = res;
setErrMsg(errMsg); _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;
}
} }

View File

@@ -45,26 +45,11 @@ namespace ix
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;
std::atomic<bool> _done; std::atomic<bool> _done;
std::thread _thread; std::thread _thread;

View File

@@ -47,8 +47,9 @@ namespace ix
std::string protocol, host, path, query; std::string protocol, host, path, query;
int port; int port;
bool websocket = false;
if (!UrlParser::parse(url, protocol, host, path, query, port)) if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
{ {
std::stringstream ss; std::stringstream ss;
ss << "Cannot parse url: " << url; ss << "Cannot parse url: " << url;

View File

@@ -1,39 +0,0 @@
/*
* IXNetSystem.cpp
* Author: Korchynskyi Dmytro
* 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
}
}

View File

@@ -23,9 +23,3 @@
# include <sys/time.h> # include <sys/time.h>
# include <unistd.h> # include <unistd.h>
#endif #endif
namespace ix
{
bool initNetSystem();
bool uninitNetSystem();
}

View File

@@ -21,10 +21,6 @@
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#ifdef min
#undef min
#endif
namespace ix namespace ix
{ {
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
@@ -45,14 +41,17 @@ namespace ix
close(); close();
} }
PollResultType Socket::poll(int timeoutSecs) void Socket::poll(const OnPollCallback& onPollCallback, int timeoutSecs)
{ {
if (_sockfd == -1) if (_sockfd == -1)
{ {
return PollResultType::Error; if (onPollCallback) onPollCallback(PollResultType::Error);
return;
} }
return isReadyToRead(1000 * timeoutSecs); PollResultType pollResult = isReadyToRead(1000 * timeoutSecs);
if (onPollCallback) onPollCallback(pollResult);
} }
PollResultType Socket::select(bool readyToRead, int timeoutMs) PollResultType Socket::select(bool readyToRead, int timeoutMs)
@@ -160,8 +159,6 @@ namespace ix
ssize_t Socket::send(char* buffer, size_t length) ssize_t Socket::send(char* buffer, size_t length)
{ {
std::lock_guard<std::mutex> lock(_socketMutex);
int flags = 0; int flags = 0;
#ifdef MSG_NOSIGNAL #ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL; flags = MSG_NOSIGNAL;
@@ -177,8 +174,6 @@ namespace ix
ssize_t Socket::recv(void* buffer, size_t length) ssize_t Socket::recv(void* buffer, size_t length)
{ {
std::lock_guard<std::mutex> lock(_socketMutex);
int flags = 0; int flags = 0;
#ifdef MSG_NOSIGNAL #ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL; flags = MSG_NOSIGNAL;
@@ -189,27 +184,11 @@ namespace ix
int Socket::getErrno() int Socket::getErrno()
{ {
int err;
#ifdef _WIN32 #ifdef _WIN32
err = WSAGetLastError(); return WSAGetLastError();
#else #else
err = errno; return errno;
#endif #endif
return err;
}
bool Socket::isWaitNeeded()
{
int err = getErrno();
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
{
return true;
}
return false;
} }
void Socket::closeSocket(int fd) void Socket::closeSocket(int fd)
@@ -244,7 +223,8 @@ namespace ix
return ret == len; return ret == len;
} }
// There is possibly something to be writen, try again // There is possibly something to be writen, try again
else if (ret < 0 && Socket::isWaitNeeded()) else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
{ {
continue; continue;
} }
@@ -272,7 +252,8 @@ namespace ix
return true; return true;
} }
// There is possibly something to be read, try again // There is possibly something to be read, try again
else if (ret < 0 && Socket::isWaitNeeded()) else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
{ {
// Wait with a 1ms timeout until the socket is ready to read. // Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping // This way we are not busy looping
@@ -331,12 +312,13 @@ namespace ix
size_t size = std::min(kChunkSize, length - output.size()); size_t size = std::min(kChunkSize, length - output.size());
ssize_t ret = recv((char*)&_readBuffer[0], size); ssize_t ret = recv((char*)&_readBuffer[0], size);
if (ret <= 0 && !Socket::isWaitNeeded()) if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
getErrno() != EAGAIN))
{ {
// Error // Error
return std::make_pair(false, std::string()); return std::make_pair(false, std::string());
} }
else else if (ret > 0)
{ {
output.insert(output.end(), output.insert(output.end(),
_readBuffer.begin(), _readBuffer.begin(),

View File

@@ -16,20 +16,6 @@
#ifdef _WIN32 #ifdef _WIN32
#include <BaseTsd.h> #include <BaseTsd.h>
typedef SSIZE_T ssize_t; typedef SSIZE_T ssize_t;
#undef EWOULDBLOCK
#undef EAGAIN
#undef EINPROGRESS
#undef EBADF
#undef EINVAL
// map to WSA error codes
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EAGAIN WSATRY_AGAIN
#define EINPROGRESS WSAEINPROGRESS
#define EBADF WSAEBADF
#define EINVAL WSAEINVAL
#endif #endif
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
@@ -51,12 +37,17 @@ namespace ix
class Socket { class Socket {
public: public:
using OnPollCallback = std::function<void(PollResultType)>;
Socket(int fd = -1); Socket(int fd = -1);
virtual ~Socket(); virtual ~Socket();
bool init(std::string& errorMsg); bool init(std::string& errorMsg);
void configure();
// Functions to check whether there is activity on the socket // Functions to check whether there is activity on the socket
PollResultType poll(int timeoutSecs = kDefaultPollTimeout); void poll(const OnPollCallback& onPollCallback,
int timeoutSecs = kDefaultPollTimeout);
bool wakeUpFromPoll(uint8_t wakeUpCode); bool wakeUpFromPoll(uint8_t wakeUpCode);
PollResultType isReadyToWrite(int timeoutMs); PollResultType isReadyToWrite(int timeoutMs);
@@ -88,14 +79,14 @@ namespace ix
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
static int getErrno(); static int getErrno();
static bool isWaitNeeded();
static void closeSocket(int fd);
// Used as special codes for pipe communication // Used as special codes for pipe communication
static const uint64_t kSendRequest; static const uint64_t kSendRequest;
static const uint64_t kCloseRequest; static const uint64_t kCloseRequest;
protected: protected:
void closeSocket(int fd);
std::atomic<int> _sockfd; std::atomic<int> _sockfd;
std::mutex _socketMutex; std::mutex _socketMutex;

View File

@@ -7,7 +7,6 @@
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include "IXDNSLookup.h" #include "IXDNSLookup.h"
#include "IXNetSystem.h" #include "IXNetSystem.h"
#include "IXSocket.h"
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
@@ -19,6 +18,18 @@
# include <linux/tcp.h> # include <linux/tcp.h>
#endif #endif
namespace
{
void closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
}
namespace ix namespace ix
{ {
// //
@@ -45,12 +56,11 @@ namespace ix
// block us for too long // block us for too long
SocketConnect::configure(fd); SocketConnect::configure(fd);
int res = ::connect(fd, address->ai_addr, address->ai_addrlen); if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
&& errno != EINPROGRESS)
if (res == -1 && !Socket::isWaitNeeded())
{ {
errMsg = strerror(Socket::getErrno()); closeSocket(fd);
Socket::closeSocket(fd); errMsg = strerror(errno);
return -1; return -1;
} }
@@ -58,17 +68,15 @@ namespace ix
{ {
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{ {
Socket::closeSocket(fd); closeSocket(fd);
errMsg = "Cancelled"; errMsg = "Cancelled";
return -1; return -1;
} }
// On Linux the timeout needs to be re-initialized everytime // Use select to check the status of the new connection
// http://man7.org/linux/man-pages/man2/select.2.html
struct timeval timeout; struct timeval timeout;
timeout.tv_sec = 0; timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout timeout.tv_usec = 10 * 1000; // 10ms timeout
fd_set wfds; fd_set wfds;
fd_set efds; fd_set efds;
@@ -77,13 +85,11 @@ namespace ix
FD_ZERO(&efds); FD_ZERO(&efds);
FD_SET(fd, &efds); FD_SET(fd, &efds);
// Use select to check the status of the new connection if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
res = select(fd + 1, nullptr, &wfds, &efds, &timeout); (errno == EBADF || errno == EINVAL))
if (res < 0 && (Socket::getErrno() == EBADF || Socket::getErrno() == EINVAL))
{ {
Socket::closeSocket(fd); closeSocket(fd);
errMsg = std::string("Connect error, select error: ") + strerror(Socket::getErrno()); errMsg = std::string("Connect error, select error: ") + strerror(errno);
return -1; return -1;
} }
@@ -104,7 +110,7 @@ namespace ix
optval != 0) optval != 0)
#endif #endif
{ {
Socket::closeSocket(fd); closeSocket(fd);
errMsg = strerror(optval); errMsg = strerror(optval);
return -1; return -1;
} }
@@ -115,7 +121,7 @@ namespace ix
} }
} }
Socket::closeSocket(fd); closeSocket(fd);
errMsg = "connect timed out after 60 seconds"; errMsg = "connect timed out after 60 seconds";
return -1; return -1;
} }

View File

@@ -6,20 +6,12 @@
#include "IXSocketFactory.h" #include "IXSocketFactory.h"
#ifdef IXWEBSOCKET_USE_TLS #if defined(__APPLE__) or defined(__linux__)
# ifdef __APPLE__ # ifdef __APPLE__
# include <ixwebsocket/IXSocketAppleSSL.h> # include <ixwebsocket/IXSocketAppleSSL.h>
# elif defined(_WIN32)
# include <ixwebsocket/IXSocketSChannel.h>
# else # else
# include <ixwebsocket/IXSocketOpenSSL.h> # include <ixwebsocket/IXSocketOpenSSL.h>
# endif # endif
#else
#include <ixwebsocket/IXSocket.h>
#endif #endif
namespace ix namespace ix
@@ -39,8 +31,6 @@ namespace ix
#ifdef IXWEBSOCKET_USE_TLS #ifdef IXWEBSOCKET_USE_TLS
# ifdef __APPLE__ # ifdef __APPLE__
socket = std::make_shared<SocketAppleSSL>(); socket = std::make_shared<SocketAppleSSL>();
# elif defined(_WIN32)
socket = std::make_shared<SocketSChannel>();
# else # else
socket = std::make_shared<SocketOpenSSL>(); socket = std::make_shared<SocketOpenSSL>();
# endif # endif

View File

@@ -8,7 +8,6 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <string>
namespace ix namespace ix
{ {

View File

@@ -342,7 +342,7 @@ namespace ix
ERR_clear_error(); ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte); ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, (int) write_result); int reason = SSL_get_error(_ssl_connection, write_result);
if (reason == SSL_ERROR_NONE) { if (reason == SSL_ERROR_NONE) {
nbyte -= write_result; nbyte -= write_result;
@@ -382,7 +382,7 @@ namespace ix
return read_result; return read_result;
} }
int reason = SSL_get_error(_ssl_connection, (int) read_result); int reason = SSL_get_error(_ssl_connection, read_result);
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{ {

View File

@@ -18,6 +18,7 @@
# include <ws2def.h> # include <ws2def.h>
# include <WS2tcpip.h> # include <WS2tcpip.h>
# include <schannel.h> # include <schannel.h>
# include <sslsock.h>
# include <io.h> # include <io.h>
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
@@ -74,7 +75,7 @@ namespace ix
int port, int port,
std::string& errMsg) std::string& errMsg)
{ {
return Socket::connect(host, port, errMsg, nullptr); return Socket::connect(host, port, errMsg);
} }
@@ -88,17 +89,17 @@ namespace ix
Socket::close(); Socket::close();
} }
ssize_t SocketSChannel::send(char* buf, size_t nbyte) int SocketSChannel::send(char* buf, size_t nbyte)
{ {
return Socket::send(buf, nbyte); return Socket::send(buf, nbyte);
} }
ssize_t SocketSChannel::send(const std::string& buffer) int SocketSChannel::send(const std::string& buffer)
{ {
return Socket::send(buffer); return Socket::send(buffer);
} }
ssize_t SocketSChannel::recv(void* buf, size_t nbyte) int SocketSChannel::recv(void* buf, size_t nbyte)
{ {
return Socket::recv(buf, nbyte); return Socket::recv(buf, nbyte);
} }

View File

@@ -24,9 +24,9 @@ namespace ix
// The important override // The important override
virtual void secureSocket() final; virtual void secureSocket() final;
virtual ssize_t send(char* buffer, size_t length) final; virtual int send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final; virtual int send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final; virtual int recv(void* buffer, size_t length) final;
private: private:
}; };

View File

@@ -13,7 +13,6 @@
#include <sstream> #include <sstream>
#include <future> #include <future>
#include <string.h> #include <string.h>
#include <assert.h>
namespace ix namespace ix
{ {
@@ -77,7 +76,7 @@ namespace ix
<< "at address " << _host << ":" << _port << "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno()); << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd); ::close(_serverFd);
return std::make_pair(false, ss.str()); return std::make_pair(false, ss.str());
} }
@@ -101,7 +100,7 @@ namespace ix
<< "at address " << _host << ":" << _port << "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno()); << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd); ::close(_serverFd);
return std::make_pair(false, ss.str()); return std::make_pair(false, ss.str());
} }
@@ -115,7 +114,7 @@ namespace ix
<< "at address " << _host << ":" << _port << "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno()); << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd); ::close(_serverFd);
return std::make_pair(false, ss.str()); return std::make_pair(false, ss.str());
} }
@@ -135,23 +134,8 @@ namespace ix
_conditionVariable.wait(lock); _conditionVariable.wait(lock);
} }
void SocketServer::stopAcceptingConnections()
{
_stop = true;
}
void SocketServer::stop() void SocketServer::stop()
{ {
while (true)
{
if (closeTerminatedThreads()) break;
// wait 10ms and try again later.
// we could have a timeout, but if we exit of here
// we leaked threads, it is quite bad.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
if (!_thread.joinable()) return; // nothing to do if (!_thread.joinable()) return; // nothing to do
_stop = true; _stop = true;
@@ -159,7 +143,7 @@ namespace ix
_stop = false; _stop = false;
_conditionVariable.notify_one(); _conditionVariable.notify_one();
Socket::closeSocket(_serverFd); ::close(_serverFd);
} }
void SocketServer::setConnectionStateFactory( void SocketServer::setConnectionStateFactory(
@@ -168,52 +152,18 @@ namespace ix
_connectionStateFactory = connectionStateFactory; _connectionStateFactory = connectionStateFactory;
} }
//
// join the threads for connections that have been closed
//
// When a connection is closed by a client, the connection state terminated
// field becomes true, and we can use that to know that we can join that thread
// and remove it from our _connectionsThreads data structure (a list).
//
bool SocketServer::closeTerminatedThreads()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
auto it = _connectionsThreads.begin();
auto itEnd = _connectionsThreads.end();
while (it != itEnd)
{
auto& connectionState = it->first;
auto& thread = it->second;
if (!connectionState->isTerminated())
{
++it;
continue;
}
if (thread.joinable()) thread.join();
it = _connectionsThreads.erase(it);
}
return _connectionsThreads.empty();
}
void SocketServer::run() void SocketServer::run()
{ {
// Set the socket to non blocking mode, so that accept calls are not blocking // Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd); SocketConnect::configure(_serverFd);
// Return value of std::async, ignored
std::future<void> f;
for (;;) for (;;)
{ {
if (_stop) return; if (_stop) return;
// Garbage collection to shutdown/join threads for closed connections.
// We could run this in its own thread, so that we dont need to accept
// a new connection to close a thread.
// We could also use a condition variable to be notify when we need to do this
closeTerminatedThreads();
// Use select to check whether a new connection is in progress // Use select to check whether a new connection is in progress
fd_set rfds; fd_set rfds;
struct timeval timeout; struct timeval timeout;
@@ -242,18 +192,17 @@ namespace ix
// Accept a connection. // Accept a connection.
struct sockaddr_in client; // client address information struct sockaddr_in client; // client address information
int clientFd; // socket connected to client int clientFd; // socket connected to client
socklen_t addressLen = sizeof(client); socklen_t addressLen = sizeof(socklen_t);
memset(&client, 0, sizeof(client)); memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0) if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
{ {
if (!Socket::isWaitNeeded()) if (Socket::getErrno() != EWOULDBLOCK)
{ {
// FIXME: that error should be propagated // FIXME: that error should be propagated
int err = Socket::getErrno();
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::run() error accepting connection: " ss << "SocketServer::run() error accepting connection: "
<< err << ", " << strerror(err); << strerror(Socket::getErrno());
logError(ss.str()); logError(ss.str());
} }
continue; continue;
@@ -267,7 +216,7 @@ namespace ix
<< "Not accepting connection"; << "Not accepting connection";
logError(ss.str()); logError(ss.str());
Socket::closeSocket(clientFd); ::close(clientFd);
continue; continue;
} }
@@ -278,16 +227,15 @@ namespace ix
connectionState = _connectionStateFactory(); connectionState = _connectionStateFactory();
} }
if (_stop) return;
// Launch the handleConnection work asynchronously in its own thread. // Launch the handleConnection work asynchronously in its own thread.
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex); //
_connectionsThreads.push_back(std::make_pair( // the destructor of a future returned by std::async blocks,
connectionState, // so we need to declare it outside of this loop
std::thread(&SocketServer::handleConnection, f = std::async(std::launch::async,
this, &SocketServer::handleConnection,
clientFd, this,
connectionState))); clientFd,
connectionState);
} }
} }
} }

View File

@@ -12,7 +12,6 @@
#include <string> #include <string>
#include <set> #include <set>
#include <thread> #include <thread>
#include <list>
#include <mutex> #include <mutex>
#include <functional> #include <functional>
#include <memory> #include <memory>
@@ -25,11 +24,6 @@ namespace ix
public: public:
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>; using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
// Each connection is handled by its own worker thread.
// We use a list as we only care about remove and append operations.
using ConnectionThreads = std::list<std::pair<std::shared_ptr<ConnectionState>,
std::thread>>;
SocketServer(int port = SocketServer::kDefaultPort, SocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost, const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog, int backlog = SocketServer::kDefaultTcpBacklog,
@@ -37,9 +31,6 @@ namespace ix
virtual ~SocketServer(); virtual ~SocketServer();
virtual void stop(); virtual void stop();
// It is possible to override ConnectionState through inheritance
// this method allows user to change the factory by returning an object
// that inherits from ConnectionState but has its own methods.
void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory); void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory);
const static int kDefaultPort; const static int kDefaultPort;
@@ -57,8 +48,6 @@ namespace ix
void logError(const std::string& str); void logError(const std::string& str);
void logInfo(const std::string& str); void logInfo(const std::string& str);
void stopAcceptingConnections();
private: private:
// Member variables // Member variables
int _port; int _port;
@@ -71,20 +60,13 @@ namespace ix
std::mutex _logMutex; std::mutex _logMutex;
// background thread to wait for incoming connections
std::atomic<bool> _stop; std::atomic<bool> _stop;
std::thread _thread; std::thread _thread;
// the list of (connectionState, threads) for each connections
ConnectionThreads _connectionsThreads;
std::mutex _connectionsThreadsMutex;
// used to have the main control thread for a server
// wait for a 'terminate' notification without busy polling
std::condition_variable _conditionVariable; std::condition_variable _conditionVariable;
std::mutex _conditionVariableMutex; std::mutex _conditionVariableMutex;
// the factory to create ConnectionState objects //
ConnectionStateFactory _connectionStateFactory; ConnectionStateFactory _connectionStateFactory;
// Methods // Methods
@@ -92,8 +74,5 @@ namespace ix
virtual void handleConnection(int fd, virtual void handleConnection(int fd,
std::shared_ptr<ConnectionState> connectionState) = 0; std::shared_ptr<ConnectionState> connectionState) = 0;
virtual size_t getConnectedClientsCount() = 0; virtual size_t getConnectedClientsCount() = 0;
// Returns true if all connection threads are joined
bool closeTerminatedThreads();
}; };
} }

View File

@@ -5,32 +5,43 @@
*/ */
#include "IXUrlParser.h" #include "IXUrlParser.h"
#include "LUrlParser.h"
#include <iostream> #include <iostream>
#include <sstream>
namespace ix namespace ix
{ {
//
// The only difference between those 2 regex is the protocol
//
std::regex UrlParser::_httpRegex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
std::regex UrlParser::_webSocketRegex("(ws|wss)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
bool UrlParser::parse(const std::string& url, bool UrlParser::parse(const std::string& url,
std::string& protocol, std::string& protocol,
std::string& host, std::string& host,
std::string& path, std::string& path,
std::string& query, std::string& query,
int& port) int& port,
bool websocket)
{ {
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url); std::cmatch what;
if (!regex_match(url.c_str(), what,
if (!res.IsValid()) websocket ? _webSocketRegex : _httpRegex))
{ {
return false; return false;
} }
protocol = res.m_Scheme; std::string portStr;
host = res.m_Host;
path = res.m_Path;
query = res.m_Query;
if (!res.GetPort(&port)) protocol = std::string(what[1].first, what[1].second);
host = std::string(what[2].first, what[2].second);
portStr = std::string(what[3].first, what[3].second);
path = std::string(what[4].first, what[4].second);
query = std::string(what[5].first, what[5].second);
if (portStr.empty())
{ {
if (protocol == "ws" || protocol == "http") if (protocol == "ws" || protocol == "http")
{ {
@@ -47,6 +58,12 @@ namespace ix
return false; return false;
} }
} }
else
{
std::stringstream ss;
ss << portStr;
ss >> port;
}
if (path.empty()) if (path.empty())
{ {
@@ -66,12 +83,12 @@ namespace ix
return true; return true;
} }
void UrlParser::printUrl(const std::string& url) void UrlParser::printUrl(const std::string& url, bool websocket)
{ {
std::string protocol, host, path, query; std::string protocol, host, path, query;
int port {0}; int port {0};
if (!parse(url, protocol, host, path, query, port)) if (!parse(url, protocol, host, path, query, port, websocket))
{ {
return; return;
} }

View File

@@ -7,6 +7,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include <regex>
namespace ix namespace ix
{ {
@@ -18,8 +19,13 @@ namespace ix
std::string& host, std::string& host,
std::string& path, std::string& path,
std::string& query, std::string& query,
int& port); int& port,
bool websocket);
static void printUrl(const std::string& url); static void printUrl(const std::string& url, bool websocket);
private:
static std::regex _httpRegex;
static std::regex _webSocketRegex;
}; };
} }

View File

@@ -31,25 +31,21 @@ namespace ix
{ {
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr; OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
const int WebSocket::kDefaultHandShakeTimeoutSecs(60); const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
const int WebSocket::kDefaultPingIntervalSecs(-1); const int WebSocket::kDefaultHeartBeatPeriod(-1);
const int WebSocket::kDefaultPingTimeoutSecs(-1);
const bool WebSocket::kDefaultEnablePong(true);
WebSocket::WebSocket() : WebSocket::WebSocket() :
_onMessageCallback(OnMessageCallback()), _onMessageCallback(OnMessageCallback()),
_stop(false), _stop(false),
_automaticReconnection(true), _automaticReconnection(true),
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs), _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
_enablePong(kDefaultEnablePong), _heartBeatPeriod(kDefaultHeartBeatPeriod)
_pingIntervalSecs(kDefaultPingIntervalSecs),
_pingTimeoutSecs(kDefaultPingTimeoutSecs)
{ {
_ws.setOnCloseCallback( _ws.setOnCloseCallback(
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) [this](uint16_t code, const std::string& reason, size_t wireSize)
{ {
_onMessageCallback(WebSocket_MessageType_Close, "", wireSize, _onMessageCallback(WebSocket_MessageType_Close, "", wireSize,
WebSocketErrorInfo(), WebSocketOpenInfo(), WebSocketErrorInfo(), WebSocketOpenInfo(),
WebSocketCloseInfo(code, reason, remote)); WebSocketCloseInfo(code, reason));
} }
); );
} }
@@ -83,52 +79,16 @@ namespace ix
return _perMessageDeflateOptions; return _perMessageDeflateOptions;
} }
void WebSocket::setHeartBeatPeriod(int heartBeatPeriodSecs) void WebSocket::setHeartBeatPeriod(int heartBeatPeriod)
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
_pingIntervalSecs = heartBeatPeriodSecs; _heartBeatPeriod = heartBeatPeriod;
} }
int WebSocket::getHeartBeatPeriod() const int WebSocket::getHeartBeatPeriod() const
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
return _pingIntervalSecs; return _heartBeatPeriod;
}
void WebSocket::setPingInterval(int pingIntervalSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingIntervalSecs = pingIntervalSecs;
}
int WebSocket::getPingInterval() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingIntervalSecs;
}
void WebSocket::setPingTimeout(int pingTimeoutSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingTimeoutSecs = pingTimeoutSecs;
}
int WebSocket::getPingTimeout() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingTimeoutSecs;
}
void WebSocket::enablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = true;
}
void WebSocket::disablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = false;
} }
void WebSocket::start() void WebSocket::start()
@@ -165,9 +125,7 @@ namespace ix
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions, _ws.configure(_perMessageDeflateOptions,
_enablePong, _heartBeatPeriod);
_pingIntervalSecs,
_pingTimeoutSecs);
} }
WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs); WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
@@ -187,10 +145,7 @@ namespace ix
{ {
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions, _ws.configure(_perMessageDeflateOptions, _heartBeatPeriod);
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
} }
WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs); WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
@@ -229,12 +184,16 @@ 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 while (true)
if (!isConnected() && !isClosing() && !_stop && !_automaticReconnection)
{ {
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
{
break;
}
status = connect(_handshakeTimeoutSecs); status = connect(_handshakeTimeoutSecs);
if (!status.success) if (!status.success && !_stop)
{ {
duration = millis(calculateRetryWaitMilliseconds(retries++)); duration = millis(calculateRetryWaitMilliseconds(retries++));
@@ -245,45 +204,15 @@ namespace ix
_onMessageCallback(WebSocket_MessageType_Error, "", 0, _onMessageCallback(WebSocket_MessageType_Error, "", 0,
connectErr, WebSocketOpenInfo(), connectErr, WebSocketOpenInfo(),
WebSocketCloseInfo()); WebSocketCloseInfo());
}
}
else
{
// Otherwise try to reconnect perpertually
while (true)
{
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
{
break;
}
status = connect(_handshakeTimeoutSecs); std::this_thread::sleep_for(duration);
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());
// Only sleep if we aren't in the middle of stopping
if (!_stop)
{
std::this_thread::sleep_for(duration);
}
}
} }
} }
} }
void WebSocket::run() void WebSocket::run()
{ {
setThreadName(getUrl()); setThreadName(_url);
while (true) while (true)
{ {
@@ -340,8 +269,10 @@ namespace ix
WebSocket::invokeTrafficTrackerCallback(msg.size(), true); WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
}); });
// If we aren't trying to reconnect automatically, exit if we aren't connected // 4. In blocking mode, getting out of this function is triggered by
if (!isConnected() && !_automaticReconnection) return; // an explicit disconnection from the callback, or by the remote end
// closing the connection, ie isConnected() == false.
if (!_thread.joinable() && !isConnected() && !_automaticReconnection) return;
} }
} }

View File

@@ -61,14 +61,11 @@ namespace ix
{ {
uint16_t code; uint16_t code;
std::string reason; std::string reason;
bool remote;
WebSocketCloseInfo(uint16_t c = 0, WebSocketCloseInfo(uint16_t c = 0,
const std::string& r = std::string(), const std::string& r = std::string())
bool rem = false)
: code(c) : code(c)
, reason(r) , reason(r)
, remote(rem)
{ {
; ;
} }
@@ -92,11 +89,7 @@ namespace ix
void setUrl(const std::string& url); void setUrl(const std::string& url);
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions); void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setHandshakeTimeout(int handshakeTimeoutSecs); void setHandshakeTimeout(int handshakeTimeoutSecs);
void setHeartBeatPeriod(int heartBeatPeriodSecs); void setHeartBeatPeriod(int heartBeatPeriod);
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
void setPingTimeout(int pingTimeoutSecs);
void enablePong();
void disablePong();
// Run asynchronously, by calling start and stop. // Run asynchronously, by calling start and stop.
void start(); void start();
@@ -121,8 +114,6 @@ namespace ix
const std::string& getUrl() const; const std::string& getUrl() const;
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const; const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
int getHeartBeatPeriod() const; int getHeartBeatPeriod() const;
int getPingInterval() const;
int getPingTimeout() const;
size_t bufferedAmount() const; size_t bufferedAmount() const;
void enableAutomaticReconnection(); void enableAutomaticReconnection();
@@ -161,15 +152,9 @@ namespace ix
std::atomic<int> _handshakeTimeoutSecs; std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs; static const int kDefaultHandShakeTimeoutSecs;
// enable or disable PONG frame response to received PING frame // Optional Heartbeat
bool _enablePong; int _heartBeatPeriod;
static const bool kDefaultEnablePong; static const int kDefaultHeartBeatPeriod;
// Optional ping and ping timeout
int _pingIntervalSecs;
int _pingTimeoutSecs;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
friend class WebSocketServer; friend class WebSocketServer;
}; };

View File

@@ -12,6 +12,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <regex>
#include <random> #include <random>
#include <algorithm> #include <algorithm>

View File

@@ -6,28 +6,12 @@
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXSocket.h" #include "IXSocket.h"
#include <algorithm>
#include <locale> #include <string>
#include <unordered_map>
namespace ix namespace ix
{ {
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char & c1, const unsigned char & c2) const
{
#ifdef _WIN32
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
#else
return std::tolower(c1) < std::tolower(c2);
#endif
}
bool CaseInsensitiveLess::operator()(const std::string & s1, const std::string & s2) const
{
return std::lexicographical_compare
(s1.begin(), s1.end(), // source range
s2.begin(), s2.end(), // dest range
NocaseCompare()); // comparison
}
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders( std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
std::shared_ptr<Socket> socket, std::shared_ptr<Socket> socket,
const CancellationRequest& isCancellationRequested) const CancellationRequest& isCancellationRequested)

View File

@@ -11,6 +11,7 @@
#include <string> #include <string>
#include <map> #include <map>
#include <memory> #include <memory>
#include <algorithm>
namespace ix namespace ix
{ {
@@ -21,10 +22,19 @@ namespace ix
// Case Insensitive compare_less binary function // Case Insensitive compare_less binary function
struct NocaseCompare struct NocaseCompare
{ {
bool operator() (const unsigned char& c1, const unsigned char& c2) const; bool operator() (const unsigned char& c1, const unsigned char& c2) const
{
return std::tolower(c1) < std::tolower(c2);
}
}; };
bool operator() (const std::string & s1, const std::string & s2) const; bool operator() (const std::string & s1, const std::string & s2) const
{
return std::lexicographical_compare
(s1.begin(), s1.end(), // source range
s2.begin(), s2.end(), // dest range
NocaseCompare()); // comparison
}
}; };
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>; using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;

View File

@@ -17,15 +17,13 @@
namespace ix namespace ix
{ {
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
const bool WebSocketServer::kDefaultEnablePong(true);
WebSocketServer::WebSocketServer(int port, WebSocketServer::WebSocketServer(int port,
const std::string& host, const std::string& host,
int backlog, int backlog,
size_t maxConnections, size_t maxConnections,
int handshakeTimeoutSecs) : SocketServer(port, host, backlog, maxConnections), int handshakeTimeoutSecs) : SocketServer(port, host, backlog, maxConnections),
_handshakeTimeoutSecs(handshakeTimeoutSecs), _handshakeTimeoutSecs(handshakeTimeoutSecs)
_enablePong(kDefaultEnablePong)
{ {
} }
@@ -37,8 +35,6 @@ namespace ix
void WebSocketServer::stop() void WebSocketServer::stop()
{ {
stopAcceptingConnections();
auto clients = getClients(); auto clients = getClients();
for (auto client : clients) for (auto client : clients)
{ {
@@ -48,16 +44,6 @@ namespace ix
SocketServer::stop(); SocketServer::stop();
} }
void WebSocketServer::enablePong()
{
_enablePong = true;
}
void WebSocketServer::disablePong()
{
_enablePong = false;
}
void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback) void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{ {
_onConnectionCallback = callback; _onConnectionCallback = callback;
@@ -72,11 +58,6 @@ namespace ix
webSocket->disableAutomaticReconnection(); webSocket->disableAutomaticReconnection();
if (_enablePong)
webSocket->enablePong();
else
webSocket->disablePong();
// Add this client to our client set // Add this client to our client set
{ {
std::lock_guard<std::mutex> lock(_clientsMutex); std::lock_guard<std::mutex> lock(_clientsMutex);
@@ -110,7 +91,6 @@ namespace ix
} }
logInfo("WebSocketServer::handleConnection() done"); logInfo("WebSocketServer::handleConnection() done");
connectionState->setTerminated();
} }
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients() std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()

View File

@@ -33,9 +33,6 @@ namespace ix
virtual ~WebSocketServer(); virtual ~WebSocketServer();
virtual void stop() final; virtual void stop() final;
void enablePong();
void disablePong();
void setOnConnectionCallback(const OnConnectionCallback& callback); void setOnConnectionCallback(const OnConnectionCallback& callback);
// Get all the connected clients // Get all the connected clients
@@ -44,7 +41,6 @@ namespace ix
private: private:
// Member variables // Member variables
int _handshakeTimeoutSecs; int _handshakeTimeoutSecs;
bool _enablePong;
OnConnectionCallback _onConnectionCallback; OnConnectionCallback _onConnectionCallback;
@@ -52,7 +48,6 @@ namespace ix
std::set<std::shared_ptr<WebSocket>> _clients; std::set<std::shared_ptr<WebSocket>> _clients;
const static int kDefaultHandShakeTimeoutSecs; const static int kDefaultHandShakeTimeoutSecs;
const static bool kDefaultEnablePong;
// Methods // Methods
virtual void handleConnection(int fd, virtual void handleConnection(int fd,

View File

@@ -51,51 +51,21 @@
#include <thread> #include <thread>
namespace
{
int greatestCommonDivisor(int a, int b)
{
while (b != 0)
{
int t = b;
b = a % b;
a = t;
}
return a;
}
}
namespace ix namespace ix
{ {
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat"); const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::heartbeat");
const int WebSocketTransport::kDefaultPingIntervalSecs(-1); const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
const bool WebSocketTransport::kDefaultEnablePong(true);
constexpr size_t WebSocketTransport::kChunkSize; constexpr size_t WebSocketTransport::kChunkSize;
const uint16_t WebSocketTransport::kInternalErrorCode(1011);
const uint16_t WebSocketTransport::kAbnormalCloseCode(1006);
const uint16_t WebSocketTransport::kProtocolErrorCode(1002);
const std::string WebSocketTransport::kInternalErrorMessage("Internal error");
const std::string WebSocketTransport::kAbnormalCloseMessage("Abnormal closure");
const std::string WebSocketTransport::kPingTimeoutMessage("Ping timeout");
const std::string WebSocketTransport::kProtocolErrorMessage("Protocol error");
WebSocketTransport::WebSocketTransport() : WebSocketTransport::WebSocketTransport() :
_useMask(true), _useMask(true),
_readyState(CLOSED), _readyState(CLOSED),
_closeCode(kInternalErrorCode), _closeCode(0),
_closeReason(kInternalErrorMessage),
_closeWireSize(0), _closeWireSize(0),
_closeRemote(false),
_enablePerMessageDeflate(false), _enablePerMessageDeflate(false),
_requestInitCancellation(false), _requestInitCancellation(false),
_enablePong(kDefaultEnablePong), _heartBeatPeriod(kDefaultHeartBeatPeriod),
_pingIntervalSecs(kDefaultPingIntervalSecs), _lastSendTimePoint(std::chrono::steady_clock::now())
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
_pingIntervalOrTimeoutGCDSecs(-1),
_lastSendPingTimePoint(std::chrono::steady_clock::now()),
_lastReceivePongTimePoint(std::chrono::steady_clock::now())
{ {
_readbuf.resize(kChunkSize); _readbuf.resize(kChunkSize);
} }
@@ -106,29 +76,11 @@ namespace ix
} }
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
bool enablePong, int heartBeatPeriod)
int pingIntervalSecs,
int pingTimeoutSecs)
{ {
_perMessageDeflateOptions = perMessageDeflateOptions; _perMessageDeflateOptions = perMessageDeflateOptions;
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled(); _enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
_enablePong = enablePong; _heartBeatPeriod = heartBeatPeriod;
_pingIntervalSecs = pingIntervalSecs;
_pingTimeoutSecs = pingTimeoutSecs;
if (pingIntervalSecs > 0 && pingTimeoutSecs > 0)
{
_pingIntervalOrTimeoutGCDSecs = greatestCommonDivisor(pingIntervalSecs,
pingTimeoutSecs);
}
else if (_pingTimeoutSecs > 0)
{
_pingIntervalOrTimeoutGCDSecs = pingTimeoutSecs;
}
else
{
_pingIntervalOrTimeoutGCDSecs = pingIntervalSecs;
}
} }
// Client // Client
@@ -137,8 +89,9 @@ namespace ix
{ {
std::string protocol, host, path, query; std::string protocol, host, path, query;
int port; int port;
bool websocket = true;
if (!UrlParser::parse(url, protocol, host, path, query, port)) if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
{ {
return WebSocketInitResult(false, 0, return WebSocketInitResult(false, 0,
std::string("Could not parse URL ") + url); std::string("Could not parse URL ") + url);
@@ -209,11 +162,10 @@ namespace ix
if (readyStateValue == CLOSED) if (readyStateValue == CLOSED)
{ {
std::lock_guard<std::mutex> lock(_closeDataMutex); std::lock_guard<std::mutex> lock(_closeDataMutex);
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote); _onCloseCallback(_closeCode, _closeReason, _closeWireSize);
_closeCode = kInternalErrorCode; _closeCode = 0;
_closeReason = kInternalErrorMessage; _closeReason = std::string();
_closeWireSize = 0; _closeWireSize = 0;
_closeRemote = false;
} }
_readyState = readyStateValue; _readyState = readyStateValue;
@@ -224,118 +176,95 @@ namespace ix
_onCloseCallback = onCloseCallback; _onCloseCallback = onCloseCallback;
} }
// Only consider send PING time points for that computation. // Only consider send time points for that computation.
bool WebSocketTransport::pingIntervalExceeded() // The receive time points is taken into account in Socket::poll (second parameter).
bool WebSocketTransport::heartBeatPeriodExceeded()
{ {
if (_pingIntervalSecs <= 0) std::lock_guard<std::mutex> lock(_lastSendTimePointMutex);
return false;
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs); return now - _lastSendTimePoint > std::chrono::seconds(_heartBeatPeriod);
}
bool WebSocketTransport::pingTimeoutExceeded()
{
if (_pingTimeoutSecs <= 0)
return false;
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
auto now = std::chrono::steady_clock::now();
return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs);
} }
void WebSocketTransport::poll() void WebSocketTransport::poll()
{ {
PollResultType pollResult = _socket->poll(_pingIntervalOrTimeoutGCDSecs); _socket->poll(
[this](PollResultType pollResult)
if (_readyState == OPEN)
{
// if (1) ping timeout is enabled and (2) duration since last received
// ping response (PONG) exceeds the maximum delay, then close the connection
if (pingTimeoutExceeded())
{ {
close(kInternalErrorCode, kPingTimeoutMessage); // If (1) heartbeat is enabled, and (2) no data was received or
} // send for a duration exceeding our heart-beat period, send a
// If ping is enabled and no ping has been sent for a duration // ping to the server.
// exceeding our ping interval, send a ping to the server. if (pollResult == PollResultType::Timeout &&
else if (pingIntervalExceeded()) heartBeatPeriodExceeded())
{
std::stringstream ss;
ss << kPingMessage << "::" << _pingIntervalSecs << "s";
sendPing(ss.str());
}
}
// Make sure we send all the buffered data
// there can be a lot of it for large messages.
if (pollResult == PollResultType::SendRequest)
{
while (!isSendBufferEmpty() && !_requestInitCancellation)
{
// Wait with a 10ms timeout until the socket is ready to write.
// This way we are not busy looping
PollResultType result = _socket->isReadyToWrite(10);
if (result == PollResultType::Error)
{ {
_socket->close(); std::stringstream ss;
setReadyState(CLOSED); ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
break; sendPing(ss.str());
} }
else if (result == PollResultType::ReadyForWrite) // Make sure we send all the buffered data
// there can be a lot of it for large messages.
else if (pollResult == PollResultType::SendRequest)
{ {
sendOnSocket(); while (!isSendBufferEmpty() && !_requestInitCancellation)
}
}
}
else if (pollResult == PollResultType::ReadyForRead)
{
while (true)
{
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
if (ret < 0 && Socket::isWaitNeeded())
{
break;
}
else if (ret <= 0)
{
_rxbuf.clear();
_socket->close();
{ {
std::lock_guard<std::mutex> lock(_closeDataMutex); // Wait with a 10ms timeout until the socket is ready to write.
_closeCode = kAbnormalCloseCode; // This way we are not busy looping
_closeReason = kAbnormalCloseMessage; PollResultType result = _socket->isReadyToWrite(10);
_closeWireSize = 0;
_closeRemote = true;
}
setReadyState(CLOSED);
break;
}
else
{
_rxbuf.insert(_rxbuf.end(),
_readbuf.begin(),
_readbuf.begin() + ret);
}
}
}
else if (pollResult == PollResultType::Error)
{
_socket->close();
}
else if (pollResult == PollResultType::CloseRequest)
{
_socket->close();
}
// Avoid a race condition where we get stuck in select if (result == PollResultType::Error)
// while closing. {
if (_readyState == CLOSING) _socket->close();
{ setReadyState(CLOSED);
_socket->close(); break;
} }
else if (result == PollResultType::ReadyForWrite)
{
sendOnSocket();
}
}
}
else if (pollResult == PollResultType::ReadyForRead)
{
while (true)
{
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
_socket->getErrno() == EAGAIN))
{
break;
}
else if (ret <= 0)
{
_rxbuf.clear();
_socket->close();
setReadyState(CLOSED);
break;
}
else
{
_rxbuf.insert(_rxbuf.end(),
_readbuf.begin(),
_readbuf.begin() + ret);
}
}
}
else if (pollResult == PollResultType::Error)
{
_socket->close();
}
else if (pollResult == PollResultType::CloseRequest)
{
_socket->close();
}
// Avoid a race condition where we get stuck in select
// while closing.
if (_readyState == CLOSING)
{
_socket->close();
}
},
_heartBeatPeriod);
} }
bool WebSocketTransport::isSendBufferEmpty() const bool WebSocketTransport::isSendBufferEmpty() const
@@ -515,16 +444,12 @@ namespace ix
else if (ws.opcode == wsheader_type::PING) else if (ws.opcode == wsheader_type::PING)
{ {
unmaskReceiveBuffer(ws); unmaskReceiveBuffer(ws);
std::string pingData(_rxbuf.begin()+ws.header_size, std::string pingData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N); _rxbuf.begin()+ws.header_size + (size_t) ws.N);
if (_enablePong) // Reply back right away
{ bool compress = false;
// Reply back right away sendData(wsheader_type::PONG, pingData, compress);
bool compress = false;
sendData(wsheader_type::PONG, pingData, compress);
}
emitMessage(PING, pingData, ws, onMessageCallback); emitMessage(PING, pingData, ws, onMessageCallback);
} }
@@ -534,9 +459,6 @@ namespace ix
std::string pongData(_rxbuf.begin()+ws.header_size, std::string pongData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N); _rxbuf.begin()+ws.header_size + (size_t) ws.N);
std::lock_guard<std::mutex> lck(_lastReceivePongTimePointMutex);
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
emitMessage(PONG, pongData, ws, onMessageCallback); emitMessage(PONG, pongData, ws, onMessageCallback);
} }
else if (ws.opcode == wsheader_type::CLOSE) else if (ws.opcode == wsheader_type::CLOSE)
@@ -550,17 +472,20 @@ namespace ix
// Get the reason. // Get the reason.
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 + 2 + (size_t) ws.N);
bool remote = true; {
std::lock_guard<std::mutex> lock(_closeDataMutex);
_closeCode = code;
_closeReason = reason;
_closeWireSize = _rxbuf.size();
}
close(code, reason, _rxbuf.size(), remote); close();
} }
else else
{ {
// Unexpected frame type close();
close(kProtocolErrorCode, kProtocolErrorMessage, _rxbuf.size());
} }
// Erase the message that has been processed from the input/read buffer // Erase the message that has been processed from the input/read buffer
@@ -719,7 +644,7 @@ namespace ix
std::string::const_iterator message_end, std::string::const_iterator message_end,
bool compress) bool compress)
{ {
uint64_t message_size = static_cast<uint64_t>(message_end - message_begin); auto message_size = message_end - message_begin;
unsigned x = getRandomUnsigned(); unsigned x = getRandomUnsigned();
uint8_t masking_key[4] = {}; uint8_t masking_key[4] = {};
@@ -805,15 +730,7 @@ namespace ix
WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message) WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
{ {
bool compress = false; bool compress = false;
WebSocketSendInfo info = sendData(wsheader_type::PING, message, compress); return sendData(wsheader_type::PING, message, compress);
if (info.success)
{
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
return info;
} }
WebSocketSendInfo WebSocketTransport::sendBinary( WebSocketSendInfo WebSocketTransport::sendBinary(
@@ -842,7 +759,8 @@ namespace ix
{ {
ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size()); ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
if (ret < 0 && Socket::isWaitNeeded()) if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
_socket->getErrno() == EAGAIN))
{ {
break; break;
} }
@@ -858,9 +776,12 @@ namespace ix
_txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret); _txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret);
} }
} }
std::lock_guard<std::mutex> lck(_lastSendTimePointMutex);
_lastSendTimePoint = std::chrono::steady_clock::now();
} }
void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote) void WebSocketTransport::close()
{ {
_requestInitCancellation = true; _requestInitCancellation = true;
@@ -868,29 +789,21 @@ namespace ix
// 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
// We use 1000: normal closure.
int codeLength = 2; //
std::string closure{(char)(code >> 8), (char)(code & 0xff)}; // >>> struct.pack('!H', 1000)
closure.resize(codeLength + reason.size()); // b'\x03\xe8'
//
// copy reason after code const std::string normalClosure = std::string("\x03\xe8");
closure.replace(codeLength, reason.size(), reason);
bool compress = false; bool compress = false;
sendData(wsheader_type::CLOSE, closure, compress); sendData(wsheader_type::CLOSE, normalClosure, compress);
setReadyState(CLOSING); setReadyState(CLOSING);
_socket->wakeUpFromPoll(Socket::kCloseRequest); _socket->wakeUpFromPoll(Socket::kCloseRequest);
_socket->close(); _socket->close();
{ _closeCode = 1000;
std::lock_guard<std::mutex> lock(_closeDataMutex); _closeReason = "Normal Closure";
_closeCode = code;
_closeReason = reason;
_closeWireSize = closeWireSize;
_closeRemote = remote;
}
setReadyState(CLOSED); setReadyState(CLOSED);
} }

View File

@@ -62,16 +62,13 @@ namespace ix
MessageKind)>; MessageKind)>;
using OnCloseCallback = std::function<void(uint16_t, using OnCloseCallback = std::function<void(uint16_t,
const std::string&, const std::string&,
size_t, size_t)>;
bool)>;
WebSocketTransport(); WebSocketTransport();
~WebSocketTransport(); ~WebSocketTransport();
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions, void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
bool enablePong, int heartBeatPeriod);
int pingIntervalSecs,
int pingTimeoutSecs);
WebSocketInitResult connectToUrl(const std::string& url, // Client WebSocketInitResult connectToUrl(const std::string& url, // Client
int timeoutSecs); int timeoutSecs);
@@ -84,12 +81,7 @@ namespace ix
WebSocketSendInfo sendText(const std::string& message, WebSocketSendInfo sendText(const std::string& message,
const OnProgressCallback& onProgressCallback); const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendPing(const std::string& message); WebSocketSendInfo sendPing(const std::string& message);
void close();
void close(uint16_t code = 1000,
const std::string& reason = "Normal closure",
size_t closeWireSize = 0,
bool remote = false);
ReadyStateValues getReadyState() const; ReadyStateValues getReadyState() const;
void setReadyState(ReadyStateValues readyStateValue); void setReadyState(ReadyStateValues readyStateValue);
void setOnCloseCallback(const OnCloseCallback& onCloseCallback); void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
@@ -119,7 +111,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;
@@ -152,7 +144,6 @@ namespace ix
uint16_t _closeCode; uint16_t _closeCode;
std::string _closeReason; std::string _closeReason;
size_t _closeWireSize; size_t _closeWireSize;
bool _closeRemote;
mutable std::mutex _closeDataMutex; mutable std::mutex _closeDataMutex;
// Data used for Per Message Deflate compression (with zlib) // Data used for Per Message Deflate compression (with zlib)
@@ -163,43 +154,15 @@ 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;
// Constants for dealing with closing conneections // Optional Heartbeat
static const uint16_t kInternalErrorCode; int _heartBeatPeriod;
static const uint16_t kAbnormalCloseCode; static const int kDefaultHeartBeatPeriod;
static const uint16_t kProtocolErrorCode; const static std::string kHeartBeatPingMessage;
static const std::string kInternalErrorMessage; mutable std::mutex _lastSendTimePointMutex;
static const std::string kAbnormalCloseMessage; std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
static const std::string kPingTimeoutMessage;
static const std::string kProtocolErrorMessage;
// enable auto response to ping // No data was send through the socket for longer than the heartbeat period
bool _enablePong; bool heartBeatPeriodExceeded();
static const bool kDefaultEnablePong;
// Optional ping and pong timeout
// if both ping interval and timeout are set (> 0),
// then use GCD of these value to wait for the lowest time
int _pingIntervalSecs;
int _pingTimeoutSecs;
int _pingIntervalOrTimeoutGCDSecs;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
static const std::string kPingMessage;
// We record when ping are being sent so that we can know when to send the next one
// We also record when pong are being sent as a reply to pings, to close the connections
// if no pong were received sufficiently fast.
mutable std::mutex _lastSendPingTimePointMutex;
mutable std::mutex _lastReceivePongTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock> _lastSendPingTimePoint;
std::chrono::time_point<std::chrono::steady_clock> _lastReceivePongTimePoint;
// If this function returns true, it is time to send a new ping
bool pingIntervalExceeded();
// No PONG data was received through the socket for longer than ping timeout delay
bool pingTimeoutExceeded();
void sendOnSocket(); void sendOnSocket();
WebSocketSendInfo sendData(wsheader_type::opcode_type type, WebSocketSendInfo sendData(wsheader_type::opcode_type type,

View File

@@ -1,265 +0,0 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "LUrlParser.h"
#include <algorithm>
#include <cstring>
#include <stdlib.h>
// check if the scheme name is valid
static bool IsSchemeValid( const std::string& SchemeName )
{
for ( auto c : SchemeName )
{
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
}
return true;
}
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
{
if ( !IsValid() ) { return false; }
int Port = atoi( m_Port.c_str() );
if ( Port <= 0 || Port > 65535 ) { return false; }
if ( OutPort ) { *OutPort = Port; }
return true;
}
// based on RFC 1738 and RFC 3986
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
{
LUrlParser::clParseURL Result;
const char* CurrentString = URL.c_str();
/*
* <scheme>:<scheme-specific-part>
* <scheme> := [a-z\+\-\.]+
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
*/
// try to read scheme
{
const char* LocalString = strchr( CurrentString, ':' );
if ( !LocalString )
{
return clParseURL( LUrlParserError_NoUrlCharacter );
}
// save the scheme name
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
if ( !IsSchemeValid( Result.m_Scheme ) )
{
return clParseURL( LUrlParserError_InvalidSchemeName );
}
// scheme should be lowercase
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
// skip ':'
CurrentString = LocalString+1;
}
/*
* //<user>:<password>@<host>:<port>/<url-path>
* any ":", "@" and "/" must be normalized
*/
// skip "//"
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
// check if the user name and password are specified
bool bHasUserName = false;
const char* LocalString = CurrentString;
while ( *LocalString )
{
if ( *LocalString == '@' )
{
// user name and password are specified
bHasUserName = true;
break;
}
else if ( *LocalString == '/' )
{
// end of <host>:<port> specification
bHasUserName = false;
break;
}
LocalString++;
}
// user name and password
LocalString = CurrentString;
if ( bHasUserName )
{
// read user name
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
// proceed with the current pointer
CurrentString = LocalString;
if ( *CurrentString == ':' )
{
// skip ':'
CurrentString++;
// read password
LocalString = CurrentString;
while ( *LocalString && *LocalString != '@' ) LocalString++;
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// skip '@'
if ( *CurrentString != '@' )
{
return clParseURL( LUrlParserError_NoAtSign );
}
CurrentString++;
}
bool bHasBracket = ( *CurrentString == '[' );
// go ahead, read the host name
LocalString = CurrentString;
while ( *LocalString )
{
if ( bHasBracket && *LocalString == ']' )
{
// end of IPv6 address
LocalString++;
break;
}
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
{
// port number is specified
break;
}
LocalString++;
}
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// is port number specified?
if ( *CurrentString == ':' )
{
CurrentString++;
// read port number
LocalString = CurrentString;
while ( *LocalString && *LocalString != '/' ) LocalString++;
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// end of string
if ( !*CurrentString )
{
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
// skip '/'
if ( *CurrentString != '/' )
{
return clParseURL( LUrlParserError_NoSlash );
}
CurrentString++;
// parse the path
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// check for query
if ( *CurrentString == '?' )
{
// skip '?'
CurrentString++;
// read query
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' ) LocalString++;
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// check for fragment
if ( *CurrentString == '#' )
{
// skip '#'
CurrentString++;
// read fragment
LocalString = CurrentString;
while ( *LocalString ) LocalString++;
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}

View File

@@ -1,78 +0,0 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <string>
namespace LUrlParser
{
enum LUrlParserError
{
LUrlParserError_Ok = 0,
LUrlParserError_Uninitialized = 1,
LUrlParserError_NoUrlCharacter = 2,
LUrlParserError_InvalidSchemeName = 3,
LUrlParserError_NoDoubleSlash = 4,
LUrlParserError_NoAtSign = 5,
LUrlParserError_UnexpectedEndOfLine = 6,
LUrlParserError_NoSlash = 7,
};
class clParseURL
{
public:
LUrlParserError m_ErrorCode;
std::string m_Scheme;
std::string m_Host;
std::string m_Port;
std::string m_Path;
std::string m_Query;
std::string m_Fragment;
std::string m_UserName;
std::string m_Password;
clParseURL()
: m_ErrorCode( LUrlParserError_Uninitialized )
{}
/// return 'true' if the parsing was successful
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
bool GetPort( int* OutPort ) const;
/// parse the URL
static clParseURL ParseURL( const std::string& URL );
private:
explicit clParseURL( LUrlParserError ErrorCode )
: m_ErrorCode( ErrorCode )
{}
};
} // namespace LUrlParser

View File

@@ -5,14 +5,8 @@ all: brew
install: brew install: brew
# Use -DCMAKE_INSTALL_PREFIX= to install into another location
# on osx it is good practice to make /usr/local user writable
# sudo chown -R `whoami`/staff /usr/local
brew: brew:
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 .. ; make -j install) mkdir -p build && (cd build ; cmake .. ; make -j install)
uninstall:
xargs rm -fv < build/install_manifest.txt
.PHONY: docker .PHONY: docker
@@ -31,12 +25,20 @@ docker_push:
docker push ${LATEST} docker push ${LATEST}
run: run:
docker run --cap-add sys_ptrace --entrypoint=bash -it bsergean/ws:build docker run --cap-add sys_ptrace -it ws:latest
# this is helpful to remove trailing whitespaces # this is helpful to remove trailing whitespaces
trail: trail:
sh third_party/remote_trailing_whitespaces.sh sh third_party/remote_trailing_whitespaces.sh
build:
(cd examples/satori_publisher ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/chat ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/ping_pong ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/ws_connect ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/echo_server ; mkdir -p build ; cd build ; cmake .. ; make)
(cd examples/broadcast_server ; mkdir -p build ; cd build ; cmake .. ; make)
# That target is used to start a node server, but isn't required as we have # That target is used to start a node server, but isn't required as we have
# a builtin C++ server started in the unittest now # a builtin C++ server started in the unittest now
test_server: test_server:
@@ -46,7 +48,7 @@ test_server:
# env TEST=Websocket_chat make test # env TEST=Websocket_chat make test
# env TEST=heartbeat make test # env TEST=heartbeat make test
test: test:
python2.7 test/run.py python test/run.py
ws_test: all ws_test: all
(cd ws ; bash test_ws.sh) (cd ws ; bash test_ws.sh)
@@ -60,7 +62,7 @@ rebase_upstream:
install_cmake_for_linux: install_cmake_for_linux:
mkdir -p /tmp/cmake mkdir -p /tmp/cmake
(cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-Linux-x86_64.tar.gz) (cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0-rc4/cmake-3.14.0-rc4-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-rc4-Linux-x86_64.tar.gz)
.PHONY: test .PHONY: test
.PHONY: build .PHONY: build

View File

@@ -11,8 +11,6 @@ find_package(Sanitizers)
set (CMAKE_CXX_STANDARD 14) set (CMAKE_CXX_STANDARD 14)
if (NOT WIN32) if (NOT WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
option(USE_TLS "Add TLS support" ON) option(USE_TLS "Add TLS support" ON)
endif() endif()
@@ -32,17 +30,15 @@ set (SOURCES
IXDNSLookupTest.cpp IXDNSLookupTest.cpp
IXSocketTest.cpp IXSocketTest.cpp
IXSocketConnectTest.cpp IXSocketConnectTest.cpp
IXWebSocketServerTest.cpp
IXWebSocketPingTest.cpp
IXWebSocketTestConnectionDisconnection.cpp
IXUrlParserTest.cpp
) )
# 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
IXWebSocketPingTimeoutTest.cpp IXWebSocketServerTest.cpp
IXWebSocketHeartBeatTest.cpp
cmd_websocket_chat.cpp cmd_websocket_chat.cpp
IXWebSocketTestConnectionDisconnection.cpp
) )
endif() endif()

File diff suppressed because it is too large Load Diff

View File

@@ -73,7 +73,7 @@ TEST_CASE("socket", "[socket]")
testSocket(host, port, request, socket, expectedStatus, timeoutSecs); testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
} }
#if defined(__APPLE__) || defined(__linux__) #if defined(__APPLE__) or defined(__linux__)
SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200") SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
{ {
std::string errMsg; std::string errMsg;

View File

@@ -17,8 +17,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <stack> #include <stack>
#include <iomanip> #include <iomanip>
#include <random>
namespace ix namespace ix
{ {
@@ -72,12 +70,10 @@ namespace ix
Logger() << msg; Logger() << msg;
} }
int getAnyFreePortRandom() int getAnyFreePortSimple()
{ {
std::random_device rd; static int defaultPort = 8090;
std::uniform_int_distribution<int> dist(1024 + 1, 65535); return defaultPort++;
return dist(rd);
} }
int getAnyFreePort() int getAnyFreePort()
@@ -87,7 +83,7 @@ namespace ix
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{ {
log("Cannot compute a free port. socket error."); log("Cannot compute a free port. socket error.");
return getAnyFreePortRandom(); return defaultPort;
} }
int enable = 1; int enable = 1;
@@ -95,7 +91,7 @@ namespace ix
(char*) &enable, sizeof(enable)) < 0) (char*) &enable, sizeof(enable)) < 0)
{ {
log("Cannot compute a free port. setsockopt error."); log("Cannot compute a free port. setsockopt error.");
return getAnyFreePortRandom(); return defaultPort;
} }
// Bind to port 0. This is the standard way to get a free port. // Bind to port 0. This is the standard way to get a free port.
@@ -108,22 +104,22 @@ namespace ix
{ {
log("Cannot compute a free port. bind error."); log("Cannot compute a free port. bind error.");
Socket::closeSocket(sockfd); ::close(sockfd);
return getAnyFreePortRandom(); return defaultPort;
} }
struct sockaddr_in sa; // server address information struct sockaddr_in sa; // server address information
socklen_t len = sizeof(sa); unsigned int len;
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0) if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
{ {
log("Cannot compute a free port. getsockname error."); log("Cannot compute a free port. getsockname error.");
Socket::closeSocket(sockfd); ::close(sockfd);
return getAnyFreePortRandom(); return defaultPort;
} }
int port = ntohs(sa.sin_port); int port = ntohs(sa.sin_port);
Socket::closeSocket(sockfd); ::close(sockfd);
return port; return port;
} }
@@ -134,7 +130,7 @@ namespace ix
{ {
#if defined(__has_feature) #if defined(__has_feature)
# if __has_feature(address_sanitizer) # if __has_feature(address_sanitizer)
int port = getAnyFreePortRandom(); int port = getAnyFreePortSimple();
# else # else
int port = getAnyFreePort(); int port = getAnyFreePort();
# endif # endif

View File

@@ -52,5 +52,6 @@ namespace ix
void log(const std::string& msg); void log(const std::string& msg);
bool computeFreePorts(int count);
int getFreePort(); int getFreePort();
} }

View File

@@ -1,108 +0,0 @@
/*
* IXSocketTest.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <ixwebsocket/IXUrlParser.h>
#include "IXTest.h"
#include "catch.hpp"
#include <string.h>
using namespace ix;
namespace ix
{
TEST_CASE("urlParser", "[urlParser]")
{
SECTION("http://google.com")
{
std::string url = "http://google.com";
std::string protocol, host, path, query;
int port;
bool res;
res = UrlParser::parse(url, protocol, host, path, query, port);
REQUIRE(res);
REQUIRE(protocol == "http");
REQUIRE(host == "google.com");
REQUIRE(path == "/");
REQUIRE(query == "");
REQUIRE(port == 80); // default port for http
}
SECTION("https://google.com")
{
std::string url = "https://google.com";
std::string protocol, host, path, query;
int port;
bool res;
res = UrlParser::parse(url, protocol, host, path, query, port);
REQUIRE(res);
REQUIRE(protocol == "https");
REQUIRE(host == "google.com");
REQUIRE(path == "/");
REQUIRE(query == "");
REQUIRE(port == 443); // default port for https
}
SECTION("ws://google.com")
{
std::string url = "ws://google.com";
std::string protocol, host, path, query;
int port;
bool res;
res = UrlParser::parse(url, protocol, host, path, query, port);
REQUIRE(res);
REQUIRE(protocol == "ws");
REQUIRE(host == "google.com");
REQUIRE(path == "/");
REQUIRE(query == "");
REQUIRE(port == 80); // default port for ws
}
SECTION("wss://google.com/ws?arg=value")
{
std::string url = "wss://google.com/ws?arg=value&arg2=value2";
std::string protocol, host, path, query;
int port;
bool res;
res = UrlParser::parse(url, protocol, host, path, query, port);
REQUIRE(res);
REQUIRE(protocol == "wss");
REQUIRE(host == "google.com");
REQUIRE(path == "/ws?arg=value&arg2=value2");
REQUIRE(query == "arg=value&arg2=value2");
REQUIRE(port == 443); // default port for wss
}
SECTION("real test")
{
std::string url = "ws://127.0.0.1:7350/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf";
std::string protocol, host, path, query;
int port;
bool res;
res = UrlParser::parse(url, protocol, host, path, query, port);
REQUIRE(res);
REQUIRE(protocol == "ws");
REQUIRE(host == "127.0.0.1");
REQUIRE(path == "/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf");
REQUIRE(query == "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf");
REQUIRE(port == 7350);
}
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* IXWebSocketPingTest.cpp * IXWebSocketHeartBeatTest.cpp
* Author: Benjamin Sergeant * Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved. * Copyright (c) 2019 Machine Zone. All rights reserved.
*/ */
@@ -21,8 +21,9 @@ namespace
class WebSocketClient class WebSocketClient
{ {
public: public:
WebSocketClient(int port, bool useHeartBeatMethod); WebSocketClient(int port);
void subscribe(const std::string& channel);
void start(); void start();
void stop(); void stop();
bool isReady() const; bool isReady() const;
@@ -31,12 +32,10 @@ namespace
private: private:
ix::WebSocket _webSocket; ix::WebSocket _webSocket;
int _port; int _port;
bool _useHeartBeatMethod;
}; };
WebSocketClient::WebSocketClient(int port, bool useHeartBeatMethod) WebSocketClient::WebSocketClient(int port)
: _port(port), : _port(port)
_useHeartBeatMethod(useHeartBeatMethod)
{ {
; ;
} }
@@ -56,7 +55,7 @@ namespace
std::string url; std::string url;
{ {
std::stringstream ss; std::stringstream ss;
ss << "ws://127.0.0.1:" ss << "ws://localhost:"
<< _port << _port
<< "/"; << "/";
@@ -66,11 +65,9 @@ namespace
_webSocket.setUrl(url); _webSocket.setUrl(url);
// The important bit for this test. // The important bit for this test.
// Set a 1 second heartbeat with the setter method to test // Set a 1 second heartbeat ; if no traffic is present on the connection for 1 second
if (_useHeartBeatMethod) // a ping message will be sent by the client.
_webSocket.setHeartBeatPeriod(1); _webSocket.setHeartBeatPeriod(1);
else
_webSocket.setPingInterval(1);
std::stringstream ss; std::stringstream ss;
log(std::string("Connecting to url: ") + url); log(std::string("Connecting to url: ") + url);
@@ -179,9 +176,9 @@ namespace
} }
} }
TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]") TEST_CASE("Websocket_heartbeat", "[heartbeat]")
{ {
SECTION("Make sure that ping messages are sent when no other data are sent.") SECTION("Make sure that ping messages are sent during heartbeat.")
{ {
ix::setupWebSocketTrafficTrackerCallback(); ix::setupWebSocketTrafficTrackerCallback();
@@ -191,168 +188,36 @@ TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod
REQUIRE(startServer(server, serverReceivedPingMessages)); REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId(); std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = true; WebSocketClient webSocketClientA(port);
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod); WebSocketClient webSocketClientB(port);
webSocketClient.start(); webSocketClientA.start();
webSocketClientB.start();
// Wait for all chat instance to be ready // Wait for all chat instance to be ready
while (true) while (true)
{ {
if (webSocketClient.isReady()) break; if (webSocketClientA.isReady() && webSocketClientB.isReady()) break;
ix::msleep(10); ix::msleep(10);
} }
REQUIRE(server.getClients().size() == 1); REQUIRE(server.getClients().size() == 2);
ix::msleep(1900); ix::msleep(900);
webSocketClientB.sendMessage("hello world");
ix::msleep(900);
webSocketClientB.sendMessage("hello world");
ix::msleep(900);
webSocketClient.stop(); webSocketClientA.stop();
webSocketClientB.stop();
// Here we test ping interval // Here we test heart beat period exceeded for clientA
// -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second // but it should not be exceeded for clientB which has sent data.
REQUIRE(serverReceivedPingMessages == 1); // -> expected ping messages == 2, but add a small buffer to make this more reliable.
REQUIRE(serverReceivedPingMessages >= 2);
// Give us 500ms for the server to notice that clients went away REQUIRE(serverReceivedPingMessages <= 4);
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
{
SECTION("Make sure that ping messages are sent, even if other messages are sent")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = true;
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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(900);
webSocketClient.sendMessage("hello world");
ix::msleep(900);
webSocketClient.sendMessage("hello world");
ix::msleep(1100);
webSocketClient.stop();
// Here we test ping interval
// client has sent data, but ping should have been sent no matter what
// -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 2);
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_ping_no_data_sent_setPingInterval", "[setPingInterval]")
{
SECTION("Make sure that ping messages are sent when no other data are sent.")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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(2100);
webSocketClient.stop();
// Here we test ping interval
// -> expected ping messages == 2 as 2100 seconds, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 2);
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_ping_data_sent_setPingInterval", "[setPingInterval]")
{
SECTION("Make sure that ping messages are sent, even if other messages are sent")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
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(900);
webSocketClient.sendMessage("hello world");
ix::msleep(900);
webSocketClient.sendMessage("hello world");
ix::msleep(1300);
webSocketClient.stop();
// without this sleep test fails on Windows
ix::msleep(100);
// Here we test ping interval
// client has sent data, but ping should have been sent no matter what
// -> expected ping messages == 3 as 900+900+1300 = 3100 seconds, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 3);
// Give us 500ms for the server to notice that clients went away // Give us 500ms for the server to notice that clients went away
ix::msleep(500); ix::msleep(500);

View File

@@ -1,489 +0,0 @@
/*
* IXWebSocketHeartBeatNoResponseAutoDisconnectTest.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, int pingInterval, int pingTimeout);
void start();
void stop();
bool isReady() const;
bool isClosed() const;
void sendMessage(const std::string& text);
int getReceivedPongMessages();
bool closedDueToPingTimeout();
private:
ix::WebSocket _webSocket;
int _port;
int _pingInterval;
int _pingTimeout;
std::atomic<int> _receivedPongMessages;
std::atomic<bool> _closedDueToPingTimeout;
};
WebSocketClient::WebSocketClient(int port, int pingInterval, int pingTimeout)
: _port(port),
_receivedPongMessages(0),
_closedDueToPingTimeout(false),
_pingInterval(pingInterval),
_pingTimeout(pingTimeout)
{
;
}
bool WebSocketClient::isReady() const
{
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
}
bool WebSocketClient::isClosed() const
{
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Closed;
}
void WebSocketClient::stop()
{
_webSocket.stop();
}
void WebSocketClient::start()
{
std::string url;
{
std::stringstream ss;
ss << "ws://127.0.0.1:"
<< _port
<< "/";
url = ss.str();
}
_webSocket.setUrl(url);
// The important bit for this test.
// Set a ping interval, and a ping timeout
_webSocket.setPingInterval(_pingInterval);
_webSocket.setPingTimeout(_pingTimeout);
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");
if (closeInfo.code == 1011)
{
_closedDueToPingTimeout = true;
}
_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)
{
_receivedPongMessages++;
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);
}
int WebSocketClient::getReceivedPongMessages()
{
return _receivedPongMessages;
}
bool WebSocketClient::closedDueToPingTimeout()
{
return _closedDueToPingTimeout;
}
bool startServer(ix::WebSocketServer& server, std::atomic<int>& receivedPingMessages, bool enablePong)
{
// A dev/null server
server.setOnConnectionCallback(
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server, &receivedPingMessages](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");
}
else if (messageType == ix::WebSocket_MessageType_Ping)
{
log("Server received a ping");
receivedPingMessages++;
}
}
);
}
);
if (!enablePong)
{
// USE this to prevent a pong answer, so the ping timeout is raised on client
server.disablePong();
}
auto res = server.listen();
if (!res.first)
{
log(res.second);
return false;
}
server.start();
return true;
}
}
TEST_CASE("Websocket_ping_timeout_not_checked", "[setPingTimeout]")
{
SECTION("Make sure that ping messages have a response (PONG).")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
bool enablePong = false; // Pong is disabled on Server
REQUIRE(startServer(server, serverReceivedPingMessages, enablePong));
std::string session = ix::generateSessionId();
int pingIntervalSecs = 1;
int pingTimeoutSecs = -1; // ping timeout not checked
WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs);
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(1100);
// Here we test ping timeout, no timeout
REQUIRE(serverReceivedPingMessages == 1);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
ix::msleep(1000);
// Here we test ping timeout, no timeout
REQUIRE(serverReceivedPingMessages == 2);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
webSocketClient.stop();
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
// Ensure client close was not by ping timeout
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_ping_no_timeout", "[setPingTimeout]")
{
SECTION("Make sure that ping messages have a response (PONG).")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
bool enablePong = true; // Pong is enabled on Server
REQUIRE(startServer(server, serverReceivedPingMessages, enablePong));
std::string session = ix::generateSessionId();
int pingIntervalSecs = 1;
int pingTimeoutSecs = 2;
WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs);
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(1100);
// Here we test ping timeout, no timeout
REQUIRE(serverReceivedPingMessages == 1);
REQUIRE(webSocketClient.getReceivedPongMessages() == 1);
ix::msleep(1000);
// Here we test ping timeout, no timeout
REQUIRE(serverReceivedPingMessages == 2);
REQUIRE(webSocketClient.getReceivedPongMessages() == 2);
webSocketClient.stop();
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
// Ensure client close was not by ping timeout
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]")
{
SECTION("Make sure that ping messages don't have responses (no PONG).")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
bool enablePong = false; // Pong is disabled on Server
REQUIRE(startServer(server, serverReceivedPingMessages, enablePong));
std::string session = ix::generateSessionId();
int pingIntervalSecs = -1; // no ping set
int pingTimeoutSecs = 3;
WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs);
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(2900);
// Here we test ping timeout, no timeout yet
REQUIRE(serverReceivedPingMessages == 0);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
REQUIRE(webSocketClient.isClosed() == false);
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
ix::msleep(200);
// Here we test ping timeout, timeout
REQUIRE(serverReceivedPingMessages == 0);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
// Ensure client close was not by ping timeout
REQUIRE(webSocketClient.isClosed() == true);
REQUIRE(webSocketClient.closedDueToPingTimeout() == true);
webSocketClient.stop();
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]")
{
SECTION("Make sure that ping messages don't have responses (no PONG).")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
bool enablePong = false; // Pong is disabled on Server
REQUIRE(startServer(server, serverReceivedPingMessages, enablePong));
std::string session = ix::generateSessionId();
int pingIntervalSecs = 1;
int pingTimeoutSecs = 2;
WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs);
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(1100);
// Here we test ping timeout, no timeout yet
REQUIRE(serverReceivedPingMessages == 1);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
ix::msleep(1000);
// Here we test ping timeout, timeout
REQUIRE(serverReceivedPingMessages == 1);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
// Ensure client close was not by ping timeout
REQUIRE(webSocketClient.isClosed() == true);
REQUIRE(webSocketClient.closedDueToPingTimeout() == true);
webSocketClient.stop();
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
#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).")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
bool enablePong = false; // Pong is disabled on Server
REQUIRE(startServer(server, serverReceivedPingMessages, enablePong));
std::string session = ix::generateSessionId();
int pingIntervalSecs = 2;
int pingTimeoutSecs = 6;
WebSocketClient webSocketClient(port, pingIntervalSecs, pingTimeoutSecs);
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(5900);
// Here we test ping timeout, no timeout yet (2 ping sent at 2s and 4s)
REQUIRE(serverReceivedPingMessages == 2);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
// Ensure client not closed
REQUIRE(webSocketClient.isClosed() == false);
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
ix::msleep(200);
// Here we test ping timeout, timeout (at 6 seconds)
REQUIRE(serverReceivedPingMessages == 2);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
// Ensure client close was not by ping timeout
REQUIRE(webSocketClient.isClosed() == true);
REQUIRE(webSocketClient.closedDueToPingTimeout() == true);
webSocketClient.stop();
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
#endif

View File

@@ -107,7 +107,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg; std::string errMsg;
bool tls = false; bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg); std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("127.0.0.1"); std::string host("localhost");
auto isCancellationRequested = []() -> bool auto isCancellationRequested = []() -> bool
{ {
return false; return false;
@@ -141,7 +141,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg; std::string errMsg;
bool tls = false; bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg); std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("127.0.0.1"); std::string host("localhost");
auto isCancellationRequested = []() -> bool auto isCancellationRequested = []() -> bool
{ {
return false; return false;
@@ -178,7 +178,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg; std::string errMsg;
bool tls = false; bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg); std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("127.0.0.1"); std::string host("localhost");
auto isCancellationRequested = []() -> bool auto isCancellationRequested = []() -> bool
{ {
return false; return false;
@@ -204,8 +204,9 @@ TEST_CASE("Websocket_server", "[websocket_server]")
// Give us 500ms for the server to notice that clients went away // Give us 500ms for the server to notice that clients went away
ix::msleep(500); ix::msleep(500);
server.stop();
REQUIRE(connectionId == "foobarConnectionId"); REQUIRE(connectionId == "foobarConnectionId");
server.stop();
REQUIRE(server.getClients().size() == 0); REQUIRE(server.getClients().size() == 0);
} }
} }

View File

@@ -100,7 +100,7 @@ namespace
std::string url; std::string url;
{ {
std::stringstream ss; std::stringstream ss;
ss << "ws://127.0.0.1:" ss << "ws://localhost:"
<< _port << _port
<< "/" << "/"
<< _user; << _user;

573
test/run.py Executable file → Normal file
View File

@@ -1,35 +1,9 @@
#!/usr/bin/env python2.7
'''
Windows notes:
generator = '-G"NMake Makefiles"'
make = 'nmake'
testBinary ='ixwebsocket_unittest.exe'
'''
from __future__ import print_function
import os import os
import sys
import platform import platform
import argparse import shutil
import multiprocessing
import tempfile
import time
import datetime
import threading
import subprocess import subprocess
import re import threading
import xml.etree.ElementTree as ET
from xml.dom import minidom
hasClick = True
try:
import click
except ImportError:
hasClick = False
DEFAULT_EXE = 'ixwebsocket_unittest'
class Command(object): class Command(object):
@@ -42,16 +16,12 @@ class Command(object):
self.cmd = cmd self.cmd = cmd
self.process = None self.process = None
def run_command(self): def run_command(self, capture = False):
self.process = subprocess.Popen(self.cmd, shell=True) self.process = subprocess.Popen(self.cmd, shell=True)
self.process.communicate() self.process.communicate()
def run(self, timeout=None): def run(self, timeout = 5 * 60):
'''5 minutes default timeout''' '''5 minutes default timeout'''
if timeout is None:
timeout = 5 * 60
thread = threading.Thread(target=self.run_command, args=()) thread = threading.Thread(target=self.run_command, args=())
thread.start() thread.start()
thread.join(timeout) thread.join(timeout)
@@ -65,454 +35,85 @@ class Command(object):
return True, self.process.returncode return True, self.process.returncode
def runCommand(cmd, assertOnFailure=True, timeout=None): osName = platform.system()
'''Small wrapper to run a command and make sure it succeed''' print('os name = {}'.format(osName))
if timeout is None: root = os.path.dirname(os.path.realpath(__file__))
timeout = 30 * 60 # 30 minute default timeout buildDir = os.path.join(root, 'build', osName)
print('\nRunning', cmd) if not os.path.exists(buildDir):
command = Command(cmd) os.makedirs(buildDir)
timedout, ret = command.run(timeout)
os.chdir(buildDir)
if timedout:
print('Unittest timed out') if osName == 'Windows':
generator = '-G"NMake Makefiles"'
msg = 'cmd {} failed with error code {}'.format(cmd, ret) make = 'nmake'
if ret != 0: testBinary ='ixwebsocket_unittest.exe'
print(msg) else:
if assertOnFailure: generator = ''
assert False make = 'make -j6'
testBinary ='./ixwebsocket_unittest'
def runCMake(sanitizer, buildDir): sanitizersFlags = {
'''Generate a makefile from CMake. 'asan': '-DSANITIZE_ADDRESS=On',
We do an out of dir build, so that cleaning up is easy 'ubsan': '-DSANITIZE_UNDEFINED=On',
(remove build sub-folder). 'tsan': '-DSANITIZE_THREAD=On',
''' 'none': ''
}
# CMake installed via Self Service ends up here. sanitizer = 'tsan'
cmake_executable = '/Applications/CMake.app/Contents/bin/cmake' if osName == 'Linux':
sanitizer = 'none'
if not os.path.exists(cmake_executable):
cmake_executable = 'cmake' sanitizerFlags = sanitizersFlags[sanitizer]
sanitizersFlags = { # if osName == 'Windows':
'asan': '-DSANITIZE_ADDRESS=On', # os.environ['CC'] = 'clang-cl'
'ubsan': '-DSANITIZE_UNDEFINED=On', # os.environ['CXX'] = 'clang-cl'
'tsan': '-DSANITIZE_THREAD=On',
'none': '' cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ../..'.format(generator, sanitizerFlags)
} print(cmakeCmd)
sanitizerFlag = sanitizersFlags.get(sanitizer, '') ret = os.system(cmakeCmd)
assert ret == 0, 'CMake failed, exiting'
# CMake installed via Self Service ends up here.
cmakeExecutable = '/Applications/CMake.app/Contents/bin/cmake' ret = os.system(make)
if not os.path.exists(cmakeExecutable): assert ret == 0, 'Make failed, exiting'
cmakeExecutable = 'cmake'
def findFiles(prefix):
generator = '"Unix Makefiles"' '''Find all files under a given directory'''
if platform.system() == 'Windows':
generator = '"NMake Makefiles"' paths = []
fmt = ''' for root, _, files in os.walk(prefix):
{cmakeExecutable} -H. \ for path in files:
{sanitizerFlag} \ fullPath = os.path.join(root, path)
-B{buildDir} \
-DCMAKE_BUILD_TYPE=Debug \ if os.path.islink(fullPath):
-DUSE_TLS=1 \ continue
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-G{generator} paths.append(fullPath)
'''
cmakeCmd = fmt.format(**locals()) return paths
runCommand(cmakeCmd)
#for path in findFiles('.'):
# print(path)
def runTest(args, buildDir, xmlOutput, testRunName):
'''Execute the unittest. # We need to copy the zlib DLL in the current work directory
''' shutil.copy(os.path.join(
if args is None: '..',
args = '' '..',
'..',
fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"' 'third_party',
testCommand = fmt.format(**locals()) 'ZLIB-Windows',
runCommand(testCommand, 'zlib-1.2.11_deploy_v140',
assertOnFailure=False) 'release_dynamic',
'x64',
'bin',
def validateTestSuite(xmlOutput): 'zlib.dll'), '.')
'''
Parse the output XML file to validate that all tests passed. # lldb = "lldb --batch -o 'run' -k 'thread backtrace all' -k 'quit 1'"
lldb = "" # Disabled for now
Assume that the XML file contains only one testsuite. testCommand = '{} {} {}'.format(lldb, testBinary, os.getenv('TEST', ''))
(which is true when generate by catch2) command = Command(testCommand)
''' timedout, ret = command.run()
tree = ET.parse(xmlOutput) assert ret == 0, 'Test command failed'
root = tree.getroot()
testSuite = root[0]
testSuiteAttributes = testSuite.attrib
tests = testSuiteAttributes['tests']
success = True
for testcase in testSuite:
if testcase.tag != 'testcase':
continue
testName = testcase.attrib['name']
systemOutput = None
for child in testcase:
if child.tag == 'system-out':
systemOutput = child.text
if child.tag == 'failure':
success = False
print("Testcase '{}' failed".format(testName))
print(' ', systemOutput)
return success, tests
def log(msg, color):
if hasClick:
click.secho(msg, fg=color)
else:
print(msg)
def isSuccessFullRun(output):
'''When being run from lldb, we cannot capture the exit code
so we have to parse the output which is produced in a
consistent way. Whenever we'll be on a recent enough version of lldb we
won't have to do this.
'''
pid = None
matchingPids = False
exitCode = -1
# 'Process 279 exited with status = 1 (0x00000001) ',
exitPattern = re.compile('^Process (?P<pid>[0-9]+) exited with status = (?P<exitCode>[0-9]+)')
# "Process 99232 launched: '/Users/bse...
launchedPattern = re.compile('^Process (?P<pid>[0-9]+) launched: ')
for line in output:
match = exitPattern.match(line)
if match:
exitCode = int(match.group('exitCode'))
pid = match.group('pid')
match = launchedPattern.match(line)
if match:
matchingPids = (pid == match.group('pid'))
return exitCode == 0 and matchingPids
def testLLDBOutput():
failedOutputWithCrashLines = [
' frame #15: 0x00007fff73f4d305 libsystem_pthread.dylib`_pthread_body + 126',
' frame #16: 0x00007fff73f5026f libsystem_pthread.dylib`_pthread_start + 70',
' frame #17: 0x00007fff73f4c415 libsystem_pthread.dylib`thread_start + 13',
'(lldb) quit 1'
]
failedOutputWithFailedUnittest = [
'===============================================================================',
'test cases: 1 | 0 passed | 1 failed', 'assertions: 15 | 14 passed | 1 failed',
'',
'Process 279 exited with status = 1 (0x00000001) ',
'',
"Process 279 launched: '/Users/bsergeant/src/foss/ixwebsocket/test/build/Darwin/ixwebsocket_unittest' (x86_64)"
]
successLines = [
'...',
'...',
'All tests passed (16 assertions in 1 test case)',
'',
'Process 99232 exited with status = 0 (0x00000000) ',
'',
"Process 99232 launched: '/Users/bsergeant/src/foss/ixwebsocket/test/build/Darwin/ixwebsocket_unittest' (x86_64)"
]
assert not isSuccessFullRun(failedOutputWithCrashLines)
assert not isSuccessFullRun(failedOutputWithFailedUnittest)
assert isSuccessFullRun(successLines)
def executeJob(job):
'''Execute a unittest and capture info about it (runtime, success, etc...)'''
start = time.time()
sys.stderr.write('.')
# print('Executing ' + job['cmd'] + '...')
# 2 minutes of timeout for a single test
timeout = 2 * 60
command = Command(job['cmd'])
timedout, ret = command.run(timeout)
job['exit_code'] = ret
job['success'] = ret == 0
job['runtime'] = time.time() - start
# Record unittest console output
job['output'] = ''
path = job['output_path']
if os.path.exists(path):
with open(path) as f:
output = f.read()
job['output'] = output
outputLines = output.splitlines()
if job['use_lldb']:
job['success'] = isSuccessFullRun(outputLines)
# Cleanup tmp file now that its content was read
os.unlink(path)
return job
def executeJobs(jobs):
'''Execute a list of job concurrently on multiple CPU/cores'''
poolSize = multiprocessing.cpu_count()
pool = multiprocessing.Pool(poolSize)
results = pool.map(executeJob, jobs)
pool.close()
pool.join()
return results
def computeAllTestNames(buildDir):
'''Compute all test case names, by executing the unittest in a custom mode'''
executable = os.path.join(buildDir, DEFAULT_EXE)
cmd = '"{}" --list-test-names-only'.format(executable)
names = os.popen(cmd).read().splitlines()
names.sort() # Sort test names for execution determinism
return names
def prettyPrintXML(root):
'''Pretty print an XML file. Default writer write it on a single line
which makes it hard for human to inspect.'''
serializedXml = ET.tostring(root, encoding='utf-8')
reparsed = minidom.parseString(serializedXml)
prettyPrinted = reparsed.toprettyxml(indent=" ")
return prettyPrinted
def generateXmlOutput(results, xmlOutput, testRunName, runTime):
'''Generate a junit compatible XML file
We prefer doing this ourself instead of letting Catch2 do it.
When the test is crashing (as has happened on Jenkins), an invalid file
with no trailer can be created which trigger an XML reading error in validateTestSuite.
Something like that:
```
<testsuite>
<foo>
```
'''
root = ET.Element('testsuites')
testSuite = ET.Element('testsuite', {
'name': testRunName,
'tests': str(len(results)),
'failures': str(sum(1 for result in results if not result['success'])),
'time': str(runTime),
'timestamp': datetime.datetime.utcnow().isoformat(),
})
root.append(testSuite)
for result in results:
testCase = ET.Element('testcase', {
'name': result['name'],
'time': str(result['runtime'])
})
systemOut = ET.Element('system-out')
systemOut.text = result['output'].decode('utf-8')
testCase.append(systemOut)
if not result['success']:
failure = ET.Element('failure')
testCase.append(failure)
testSuite.append(testCase)
with open(xmlOutput, 'w') as f:
content = prettyPrintXML(root)
f.write(content.encode('utf-8'))
def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLDB):
'''Main driver. Run cmake, compiles, execute and validate the testsuite.'''
# gen build files with CMake
runCMake(sanitizer, buildDir)
# build with make
makeCmd = 'make'
jobs = '-j8'
if platform.system() == 'Windows':
makeCmd = 'nmake'
# nmake does not have a -j option
jobs = ''
runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs))
if buildOnly:
return
# A specific test case can be provided on the command line
if testName:
testNames = [testName]
else:
# Default case
testNames = computeAllTestNames(buildDir)
# This should be empty. It is useful to have a blacklist during transitions
# We could add something for asan as well.
blackLists = {
'ubsan': []
}
blackList = blackLists.get(sanitizer, [])
# Run through LLDB to capture crashes
lldb = ''
if useLLDB:
lldb = "lldb --batch -o 'run' -k 'thread backtrace all' -k 'quit 1'"
# Jobs is a list of python dicts
jobs = []
for testName in testNames:
outputPath = tempfile.mktemp(suffix=testName + '.log')
if testName in blackList:
log('Skipping blacklisted test {}'.format(testName), 'yellow')
continue
# testName can contains spaces, so we enclose them in double quotes
executable = os.path.join(buildDir, DEFAULT_EXE)
if platform.system() == 'Windows':
executable += '.exe'
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, executable, testName, outputPath)
jobs.append({
'name': testName,
'cmd': cmd,
'output_path': outputPath,
'use_lldb': useLLDB
})
start = time.time()
results = executeJobs(jobs)
runTime = time.time() - start
generateXmlOutput(results, xmlOutput, testRunName, runTime)
# Validate and report results
print('\nParsing junit test result file: {}'.format(xmlOutput))
log('## Results', 'blue')
success, tests = validateTestSuite(xmlOutput)
if success:
label = 'tests' if int(tests) > 1 else 'test'
msg = 'All test passed (#{} {})'.format(tests, label)
color = 'green'
else:
msg = 'unittest failed'
color = 'red'
log(msg, color)
log('Execution time: %.2fs' % (runTime), 'blue')
sys.exit(0 if success else 1)
def main():
root = os.path.dirname(os.path.realpath(__file__))
os.chdir(root)
buildDir = os.path.join(root, 'build', platform.system())
if not os.path.exists(buildDir):
os.makedirs(buildDir)
defaultOutput = DEFAULT_EXE + '.xml'
parser = argparse.ArgumentParser(description='Build and Run the engine unittest')
sanitizers = ['tsan', 'asan', 'ubsan', 'none']
parser.add_argument('--sanitizer', choices=sanitizers,
help='Run a clang sanitizer.')
parser.add_argument('--test', '-t', help='Test name.')
parser.add_argument('--list', '-l', action='store_true',
help='Print test names and exit.')
parser.add_argument('--no_sanitizer', action='store_true',
help='Do not execute a clang sanitizer.')
parser.add_argument('--validate', action='store_true',
help='Validate XML output.')
parser.add_argument('--build_only', '-b', action='store_true',
help='Stop after building. Do not run the unittest.')
parser.add_argument('--output', '-o', help='Output XML file.')
parser.add_argument('--lldb', action='store_true',
help='Run the test through lldb.')
parser.add_argument('--run_name', '-n',
help='Name of the test run.')
args = parser.parse_args()
# Default sanitizer is tsan
sanitizer = args.sanitizer
if args.sanitizer is None:
sanitizer = 'tsan'
defaultRunName = 'ixengine_{}_{}'.format(platform.system(), sanitizer)
xmlOutput = args.output or defaultOutput
testRunName = args.run_name or os.getenv('IXENGINE_TEST_RUN_NAME') or defaultRunName
if args.list:
# catch2 exit with a different error code when requesting the list of files
try:
runTest('--list-test-names-only', buildDir, xmlOutput, testRunName)
except AssertionError:
pass
return
if args.validate:
validateTestSuite(xmlOutput)
return
if platform.system() != 'Darwin' and args.lldb:
print('LLDB is only supported on Apple at this point')
args.lldb = False
# Sanitizers display lots of strange errors on Linux on CI,
# which looks like false positives
if platform.system() != 'Darwin':
sanitizer = 'none'
return run(args.test, buildDir, sanitizer, xmlOutput,
testRunName, args.build_only, args.lldb)
if __name__ == '__main__':
main()

View File

@@ -7,14 +7,10 @@
#define CATCH_CONFIG_RUNNER #define CATCH_CONFIG_RUNNER
#include "catch.hpp" #include "catch.hpp"
#include <ixwebsocket/IXNetSystem.h> #include <ixwebsocket/IXSocket.h>
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
ix::initNetSystem();
int result = Catch::Session().run(argc, argv); int result = Catch::Session().run(argc, argv);
ix::uninitNetSystem();
return result; return result;
} }

View File

@@ -7,8 +7,8 @@
#ifndef ZCONF_H #ifndef ZCONF_H
#define ZCONF_H #define ZCONF_H
#cmakedefine Z_PREFIX /* #undef Z_PREFIX */
#cmakedefine Z_HAVE_UNISTD_H /* #undef Z_HAVE_UNISTD_H */
/* /*
* If you *really* need a unique prefix for all types and library functions, * If you *really* need a unique prefix for all types and library functions,

View File

@@ -7,6 +7,8 @@
#ifndef ZCONF_H #ifndef ZCONF_H
#define ZCONF_H #define ZCONF_H
/* #undef Z_PREFIX */
/* #undef Z_HAVE_UNISTD_H */
/* /*
* If you *really* need a unique prefix for all types and library functions, * If you *really* need a unique prefix for all types and library functions,

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
# Compiled Object files
*.slo
*.lo
*.o
# Compiled Dynamic libraries
*.so
*.dylib
# Compiled Static libraries
*.lai
*.la
*.a

View File

@@ -1,18 +0,0 @@
cmake_minimum_required(VERSION 3.1)
project(helloCLion)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
include_directories(
src
)
add_library(statsdcppclient STATIC src/statsd_client.cpp)
add_definitions("-fPIC")
target_link_libraries(statsdcppclient pthread)
add_executable(system_monitor demo/system_monitor.cpp)
target_link_libraries(system_monitor statsdcppclient)
add_executable(test_client demo/test_client.cpp)
target_link_libraries(test_client statsdcppclient)

View File

@@ -1,27 +0,0 @@
Copyright (c) 2014, Rex
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,34 +0,0 @@
# a client sdk for StatsD, written in C++
## API
See [header file](src/statsd_client.h) for more api detail.
** Notice: this client is not thread-safe **
## Demo
### test\_client
This simple demo shows how the use this client.
### system\_monitor
This is a daemon for monitoring a Linux system.
It'll wake up every minute and monitor the following:
* load
* cpu
* free memory
* free swap (disabled)
* received bytes
* transmitted bytes
* procs
* uptime
The stats sent to statsd will be in "host.MACAddress" namespace.
Usage:
system_monitor statsd-host interface-to-monitor
e.g.
`system_monitor 172.16.42.1 eth0`

View File

@@ -1,164 +0,0 @@
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <sys/sysinfo.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <string>
#include <vector>
#include "statsd_client.h"
using namespace std;
static int running = 1;
void sigterm(int sig)
{
running = 0;
}
string localhost() {
struct addrinfo hints, *info, *p;
string hostname(1024, '\0');
gethostname((char*)hostname.data(), hostname.capacity());
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; /*either IPV4 or IPV6*/
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_CANONNAME;
if ( getaddrinfo(hostname.c_str(), "http", &hints, &info) == 0) {
for(p = info; p != NULL; p = p->ai_next) {
hostname = p->ai_canonname;
}
freeaddrinfo(info);
}
string::size_type pos = hostname.find(".");
while ( pos != string::npos )
{
hostname[pos] = '_';
pos = hostname.find(".", pos);
}
return hostname;
}
vector<string>& StringSplitTrim(const string& sData,
const string& sDelim, vector<string>& vItems)
{
vItems.clear();
string::size_type bpos = 0;
string::size_type epos = 0;
string::size_type nlen = sDelim.size();
while(sData.substr(epos,nlen) == sDelim)
{
epos += nlen;
}
bpos = epos;
while ((epos=sData.find(sDelim, epos)) != string::npos)
{
vItems.push_back(sData.substr(bpos, epos-bpos));
epos += nlen;
while(sData.substr(epos,nlen) == sDelim)
{
epos += nlen;
}
bpos = epos;
}
if(bpos != sData.size())
{
vItems.push_back(sData.substr(bpos, sData.size()-bpos));
}
return vItems;
}
int main(int argc, char *argv[])
{
FILE *net, *stat;
struct sysinfo si;
char line[256];
unsigned int user, nice, sys, idle, total, busy, old_total=0, old_busy=0;
if (argc != 3) {
printf( "Usage: %s host port\n"
"Eg: %s 127.0.0.1 8125\n",
argv[0], argv[0]);
exit(1);
}
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN); /* will save one syscall per sleep */
signal(SIGTERM, sigterm);
if ( (net = fopen("/proc/net/dev", "r")) == NULL) {
perror("fopen");
exit(-1);
}
if ( (stat = fopen("/proc/stat", "r")) == NULL) {
perror("fopen");
exit(-1);
}
string ns = string("host.") + localhost().c_str() + ".";
statsd::StatsdClient client(argv[1], atoi(argv[2]), ns);
daemon(0,0);
printf("running in background.\n");
while(running) {
rewind(net);
vector<string> items;
while(!feof(net)) {
fgets(line, sizeof(line), net);
StringSplitTrim(line, " ", items);
if ( items.size() < 17 ) continue;
if ( items[0].find(":") == string::npos ) continue;
if ( items[1] == "0" and items[9] == "0" ) continue;
string netface = "network."+items[0].erase( items[0].find(":") );
client.count( netface+".receive.bytes", atoll(items[1].c_str()) );
client.count( netface+".receive.packets", atoll(items[2].c_str()) );
client.count( netface+".transmit.bytes", atoll(items[9].c_str()) );
client.count( netface+".transmit.packets", atoll(items[10].c_str()) );
}
sysinfo(&si);
client.gauge("system.load", 100*si.loads[0]/0x10000);
client.gauge("system.freemem", si.freeram/1024);
client.gauge("system.procs", si.procs);
client.count("system.uptime", si.uptime);
/* rewind doesn't do the trick for /proc/stat */
freopen("/proc/stat", "r", stat);
fgets(line, sizeof(line), stat);
sscanf(line, "cpu %u %u %u %u", &user, &nice, &sys, &idle);
total = user + sys + idle;
busy = user + sys;
client.send("system.cpu", 100 * (busy - old_busy)/(total - old_total), "g", 1.0);
old_total = total;
old_busy = busy;
sleep(6);
}
fclose(net);
fclose(stat);
exit(0);
}

View File

@@ -1,28 +0,0 @@
#include <iostream>
#include <unistd.h>
#include "statsd_client.h"
int main(void)
{
std::cout << "running..." << std::endl;
statsd::StatsdClient client;
statsd::StatsdClient client2("127.0.0.1", 8125, "myproject.abx.", true);
client.count("count1", 123, 1.0);
client.count("count2", 125, 1.0);
client.gauge("speed", 10);
int i;
for (i=0; i<1000; i++)
client2.timing("request", i);
sleep(1);
client.inc("count1", 1.0);
client2.dec("count2", 1.0);
// for(i=0; i<1000; i++) {
// client2.count("count3", i, 0.8);
// }
std::cout << "done" << std::endl;
return 0;
}

View File

@@ -1,246 +0,0 @@
#include <math.h>
#include <netdb.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>
#include "statsd_client.h"
using namespace std;
namespace statsd {
inline bool fequal(float a, float b)
{
const float epsilon = 0.0001;
return ( fabs(a - b) < epsilon );
}
inline bool should_send(float sample_rate)
{
if ( fequal(sample_rate, 1.0) )
{
return true;
}
float p = ((float)random() / RAND_MAX);
return sample_rate > p;
}
struct _StatsdClientData {
int sock;
struct sockaddr_in server;
string ns;
string host;
short port;
bool init;
char errmsg[1024];
};
StatsdClient::StatsdClient(const string& host,
int port,
const string& ns,
const bool batching)
: batching_(batching), exit_(false)
{
d = new _StatsdClientData;
d->sock = -1;
config(host, port, ns);
srandom((unsigned) time(NULL));
if (batching_) {
pthread_mutex_init(&batching_mutex_lock_, nullptr);
batching_thread_ = std::thread([this] {
while (!exit_) {
std::deque<std::string> staged_message_queue;
pthread_mutex_lock(&batching_mutex_lock_);
batching_message_queue_.swap(staged_message_queue);
pthread_mutex_unlock(&batching_mutex_lock_);
while(!staged_message_queue.empty()) {
send_to_daemon(staged_message_queue.front());
staged_message_queue.pop_front();
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
});
}
}
StatsdClient::~StatsdClient()
{
if (batching_) {
exit_ = true;
batching_thread_.join();
pthread_mutex_destroy(&batching_mutex_lock_);
}
// close socket
if (d->sock >= 0) {
close(d->sock);
d->sock = -1;
delete d;
d = NULL;
}
}
void StatsdClient::config(const string& host, int port, const string& ns)
{
d->ns = ns;
d->host = host;
d->port = port;
d->init = false;
if ( d->sock >= 0 ) {
close(d->sock);
}
d->sock = -1;
}
int StatsdClient::init()
{
if ( d->init ) return 0;
d->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if ( d->sock == -1 ) {
snprintf(d->errmsg, sizeof(d->errmsg), "could not create socket, err=%m");
return -1;
}
memset(&d->server, 0, sizeof(d->server));
d->server.sin_family = AF_INET;
d->server.sin_port = htons(d->port);
int ret = inet_aton(d->host.c_str(), &d->server.sin_addr);
if ( ret == 0 )
{
// host must be a domain, get it from internet
struct addrinfo hints, *result = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
ret = getaddrinfo(d->host.c_str(), NULL, &hints, &result);
if ( ret ) {
close(d->sock);
d->sock = -1;
snprintf(d->errmsg, sizeof(d->errmsg),
"getaddrinfo fail, error=%d, msg=%s", ret, gai_strerror(ret) );
return -2;
}
struct sockaddr_in* host_addr = (struct sockaddr_in*)result->ai_addr;
memcpy(&d->server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr));
freeaddrinfo(result);
}
d->init = true;
return 0;
}
/* will change the original string */
void StatsdClient::cleanup(string& key)
{
size_t pos = key.find_first_of(":|@");
while ( pos != string::npos )
{
key[pos] = '_';
pos = key.find_first_of(":|@");
}
}
int StatsdClient::dec(const string& key, float sample_rate)
{
return count(key, -1, sample_rate);
}
int StatsdClient::inc(const string& key, float sample_rate)
{
return count(key, 1, sample_rate);
}
int StatsdClient::count(const string& key, size_t value, float sample_rate)
{
return send(key, value, "c", sample_rate);
}
int StatsdClient::gauge(const string& key, size_t value, float sample_rate)
{
return send(key, value, "g", sample_rate);
}
int StatsdClient::timing(const string& key, size_t ms, float sample_rate)
{
return send(key, ms, "ms", sample_rate);
}
int StatsdClient::send(string key, size_t value, const string &type, float sample_rate)
{
if (!should_send(sample_rate)) {
return 0;
}
cleanup(key);
char buf[256];
if ( fequal( sample_rate, 1.0 ) )
{
snprintf(buf, sizeof(buf), "%s%s:%zd|%s",
d->ns.c_str(), key.c_str(), value, type.c_str());
}
else
{
snprintf(buf, sizeof(buf), "%s%s:%zd|%s|@%.2f",
d->ns.c_str(), key.c_str(), value, type.c_str(), sample_rate);
}
return send(buf);
}
int StatsdClient::send(const string &message)
{
if (batching_) {
pthread_mutex_lock(&batching_mutex_lock_);
if (batching_message_queue_.empty() ||
batching_message_queue_.back().length() > max_batching_size) {
batching_message_queue_.push_back(message);
} else {
(*batching_message_queue_.rbegin()).append("\n").append(message);
}
pthread_mutex_unlock(&batching_mutex_lock_);
return 0;
} else {
return send_to_daemon(message);
}
}
int StatsdClient::send_to_daemon(const string &message) {
std::cout << "send_to_daemon: " << message.length() << " B" << std::endl;
int ret = init();
if ( ret )
{
return ret;
}
ret = (int) sendto(d->sock, message.data(), message.size(), 0, (struct sockaddr *) &d->server, sizeof(d->server));
if ( ret == -1) {
snprintf(d->errmsg, sizeof(d->errmsg),
"sendto server fail, host=%s:%d, err=%m", d->host.c_str(), d->port);
return -1;
}
return 0;
}
const char* StatsdClient::errmsg()
{
return d->errmsg;
}
}

View File

@@ -1,66 +0,0 @@
#ifndef STATSD_CLIENT_H
#define STATSD_CLIENT_H
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#include <string>
#include <thread>
#include <deque>
#include <iostream>
namespace statsd {
struct _StatsdClientData;
class StatsdClient {
public:
StatsdClient(const std::string& host="127.0.0.1", int port=8125, const std::string& ns = "", const bool batching = false);
~StatsdClient();
public:
// you can config at anytime; client will use new address (useful for Singleton)
void config(const std::string& host, int port, const std::string& ns = "");
const char* errmsg();
int send_to_daemon(const std::string &);
public:
int inc(const std::string& key, float sample_rate = 1.0);
int dec(const std::string& key, float sample_rate = 1.0);
int count(const std::string& key, size_t value, float sample_rate = 1.0);
int gauge(const std::string& key, size_t value, float sample_rate = 1.0);
int timing(const std::string& key, size_t ms, float sample_rate = 1.0);
public:
/**
* (Low Level Api) manually send a message
* which might be composed of several lines.
*/
int send(const std::string& message);
/* (Low Level Api) manually send a message
* type = "c", "g" or "ms"
*/
int send(std::string key, size_t value,
const std::string& type, float sample_rate);
protected:
int init();
void cleanup(std::string& key);
protected:
struct _StatsdClientData* d;
bool batching_;
bool exit_;
pthread_mutex_t batching_mutex_lock_;
std::thread batching_thread_;
std::deque<std::string> batching_message_queue_;
const uint64_t max_batching_size = 32768;
};
} // end namespace
#endif

View File

@@ -1,26 +0,0 @@
*.diff
*.patch
*.orig
*.rej
*~
*.a
*.lo
*.o
*.dylib
*.gcda
*.gcno
*.gcov
/example
/example64
/examplesh
/libz.so*
/minigzip
/minigzip64
/minigzipsh
/zlib.pc
/configure.log
.DS_Store

View File

@@ -1,249 +0,0 @@
cmake_minimum_required(VERSION 2.4.4)
set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
project(zlib C)
set(VERSION "1.2.11")
option(ASM686 "Enable building i686 assembly implementation")
option(AMD64 "Enable building amd64 assembly implementation")
set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables")
set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries")
set(INSTALL_INC_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "Installation directory for headers")
set(INSTALL_MAN_DIR "${CMAKE_INSTALL_PREFIX}/share/man" CACHE PATH "Installation directory for manual pages")
set(INSTALL_PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/share/pkgconfig" CACHE PATH "Installation directory for pkgconfig (.pc) files")
include(CheckTypeSize)
include(CheckFunctionExists)
include(CheckIncludeFile)
include(CheckCSourceCompiles)
enable_testing()
check_include_file(sys/types.h HAVE_SYS_TYPES_H)
check_include_file(stdint.h HAVE_STDINT_H)
check_include_file(stddef.h HAVE_STDDEF_H)
#
# Check to see if we have large file support
#
set(CMAKE_REQUIRED_DEFINITIONS -D_LARGEFILE64_SOURCE=1)
# We add these other definitions here because CheckTypeSize.cmake
# in CMake 2.4.x does not automatically do so and we want
# compatibility with CMake 2.4.x.
if(HAVE_SYS_TYPES_H)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_SYS_TYPES_H)
endif()
if(HAVE_STDINT_H)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDINT_H)
endif()
if(HAVE_STDDEF_H)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDDEF_H)
endif()
check_type_size(off64_t OFF64_T)
if(HAVE_OFF64_T)
add_definitions(-D_LARGEFILE64_SOURCE=1)
endif()
set(CMAKE_REQUIRED_DEFINITIONS) # clear variable
#
# Check for fseeko
#
check_function_exists(fseeko HAVE_FSEEKO)
if(NOT HAVE_FSEEKO)
add_definitions(-DNO_FSEEKO)
endif()
#
# Check for unistd.h
#
check_include_file(unistd.h Z_HAVE_UNISTD_H)
if(MSVC)
set(CMAKE_DEBUG_POSTFIX "d")
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
endif()
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
# If we're doing an out of source build and the user has a zconf.h
# in their source tree...
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h)
message(STATUS "Renaming")
message(STATUS " ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h")
message(STATUS "to 'zconf.h.included' because this file is included with zlib")
message(STATUS "but CMake generates it automatically in the build directory.")
file(RENAME ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.included)
endif()
endif()
set(ZLIB_PC ${CMAKE_CURRENT_BINARY_DIR}/zlib.pc)
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zlib.pc.cmakein
${ZLIB_PC} @ONLY)
configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.cmakein
${CMAKE_CURRENT_BINARY_DIR}/zconf.h @ONLY)
include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR})
#============================================================================
# zlib
#============================================================================
set(ZLIB_PUBLIC_HDRS
${CMAKE_CURRENT_BINARY_DIR}/zconf.h
zlib.h
)
set(ZLIB_PRIVATE_HDRS
crc32.h
deflate.h
gzguts.h
inffast.h
inffixed.h
inflate.h
inftrees.h
trees.h
zutil.h
)
set(ZLIB_SRCS
adler32.c
compress.c
crc32.c
deflate.c
gzclose.c
gzlib.c
gzread.c
gzwrite.c
inflate.c
infback.c
inftrees.c
inffast.c
trees.c
uncompr.c
zutil.c
)
if(NOT MINGW)
set(ZLIB_DLL_SRCS
win32/zlib1.rc # If present will override custom build rule below.
)
endif()
if(CMAKE_COMPILER_IS_GNUCC)
if(ASM686)
set(ZLIB_ASMS contrib/asm686/match.S)
elseif (AMD64)
set(ZLIB_ASMS contrib/amd64/amd64-match.S)
endif ()
if(ZLIB_ASMS)
add_definitions(-DASMV)
set_source_files_properties(${ZLIB_ASMS} PROPERTIES LANGUAGE C COMPILE_FLAGS -DNO_UNDERLINE)
endif()
endif()
if(MSVC)
if(ASM686)
ENABLE_LANGUAGE(ASM_MASM)
set(ZLIB_ASMS
contrib/masmx86/inffas32.asm
contrib/masmx86/match686.asm
)
elseif (AMD64)
ENABLE_LANGUAGE(ASM_MASM)
set(ZLIB_ASMS
contrib/masmx64/gvmat64.asm
contrib/masmx64/inffasx64.asm
)
endif()
if(ZLIB_ASMS)
add_definitions(-DASMV -DASMINF)
endif()
endif()
# parse the full version number from zlib.h and include in ZLIB_FULL_VERSION
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/zlib.h _zlib_h_contents)
string(REGEX REPLACE ".*#define[ \t]+ZLIB_VERSION[ \t]+\"([-0-9A-Za-z.]+)\".*"
"\\1" ZLIB_FULL_VERSION ${_zlib_h_contents})
if(MINGW)
# This gets us DLL resource information when compiling on MinGW.
if(NOT CMAKE_RC_COMPILER)
set(CMAKE_RC_COMPILER windres.exe)
endif()
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj
COMMAND ${CMAKE_RC_COMPILER}
-D GCC_WINDRES
-I ${CMAKE_CURRENT_SOURCE_DIR}
-I ${CMAKE_CURRENT_BINARY_DIR}
-o ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj
-i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zlib1.rc)
set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj)
endif(MINGW)
add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS})
set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL)
set_target_properties(zlib PROPERTIES SOVERSION 1)
if(NOT CYGWIN)
# This property causes shared libraries on Linux to have the full version
# encoded into their final filename. We disable this on Cygwin because
# it causes cygz-${ZLIB_FULL_VERSION}.dll to be created when cygz.dll
# seems to be the default.
#
# This has no effect with MSVC, on that platform the version info for
# the DLL comes from the resource file win32/zlib1.rc
set_target_properties(zlib PROPERTIES VERSION ${ZLIB_FULL_VERSION})
endif()
if(UNIX)
# On unix-like platforms the library is almost always called libz
set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z)
if(NOT APPLE)
set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"")
endif()
elseif(BUILD_SHARED_LIBS AND WIN32)
# Creates zlib1.dll when building shared library version
set_target_properties(zlib PROPERTIES SUFFIX "1.dll")
endif()
if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL )
install(TARGETS zlib zlibstatic
RUNTIME DESTINATION "${INSTALL_BIN_DIR}"
ARCHIVE DESTINATION "${INSTALL_LIB_DIR}"
LIBRARY DESTINATION "${INSTALL_LIB_DIR}" )
endif()
if(NOT SKIP_INSTALL_HEADERS AND NOT SKIP_INSTALL_ALL )
install(FILES ${ZLIB_PUBLIC_HDRS} DESTINATION "${INSTALL_INC_DIR}")
endif()
if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL )
install(FILES zlib.3 DESTINATION "${INSTALL_MAN_DIR}/man3")
endif()
if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL )
install(FILES ${ZLIB_PC} DESTINATION "${INSTALL_PKGCONFIG_DIR}")
endif()
#============================================================================
# Example binaries
#============================================================================
add_executable(example test/example.c)
target_link_libraries(example zlib)
add_test(example example)
add_executable(minigzip test/minigzip.c)
target_link_libraries(minigzip zlib)
if(HAVE_OFF64_T)
add_executable(example64 test/example.c)
target_link_libraries(example64 zlib)
set_target_properties(example64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64")
add_test(example64 example64)
add_executable(minigzip64 test/minigzip.c)
target_link_libraries(minigzip64 zlib)
set_target_properties(minigzip64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64")
endif()

Some files were not shown because too many files have changed in this diff Show More