Compare commits

..

5 Commits

Author SHA1 Message Date
Benjamin Sergeant
c36dc0e16a fix unittest 2019-03-20 18:50:56 -07:00
Benjamin Sergeant
7e45659377 update README 2019-03-20 18:33:55 -07:00
Benjamin Sergeant
788c92487c New connection state for server code + fix OpenSSL double init bug 2019-03-20 18:26:29 -07:00
Benjamin Sergeant
0999073435 fix typo 2019-03-20 18:25:28 -07:00
Benjamin Sergeant
2cae6f4cf8 (cmake) add a warning about 32/64 conversion problems. 2019-03-20 16:16:54 -07:00
512 changed files with 12391 additions and 112422 deletions

View File

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

View File

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

View File

@@ -1 +0,0 @@
1.5.3

View File

@@ -1 +0,0 @@
docker/Dockerfile.fedora

31
Dockerfile 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

@@ -0,0 +1,20 @@
class Ixwebsocket < Formula
desc "WebSocket client and server, and HTTP client command-line tool"
homepage "https://github.com/machinezone/IXWebSocket"
url "https://github.com/machinezone/IXWebSocket/archive/v1.1.0.tar.gz"
sha256 "52592ce3d0a67ad0f90ac9e8a458f61724175d95a01a38d1bad3fcdc5c7b6666"
depends_on "cmake" => :build
def install
system "cmake", ".", *std_cmake_args
system "make", "install"
end
test do
system "#{bin}/ws", "--help"
system "#{bin}/ws", "send", "--help"
system "#{bin}/ws", "receive", "--help"
system "#{bin}/ws", "transfer", "--help"
system "#{bin}/ws", "curl", "--help"
end
end

View File

@@ -4,13 +4,13 @@
## Introduction
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client HTTP communication. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client HTTP communication. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
* macOS
* iOS
* Linux
* Android
* Windows (no TLS)
## Examples
@@ -34,10 +34,10 @@ webSocket.setOnMessageCallback(
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
{
if (messageType == ix::WebSocketMessageType::Message)
if (messageType == ix::WebSocket_MessageType_Message)
{
std::cout << str << std::endl;
}
@@ -46,12 +46,9 @@ webSocket.setOnMessageCallback(
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
// Send a message to the server (default to BINARY mode)
// Send a message to the server
webSocket.send("hello world");
// The message can be sent in TEXT mode
webSocket.sendText("hello again");
// ... finally ...
// Stop the connection
@@ -77,7 +74,7 @@ server.setOnConnectionCallback(
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocketMessageType::Open)
if (messageType == ix::WebSocket_MessageType_Open)
{
std::cerr << "New connection" << std::endl;
@@ -96,7 +93,7 @@ server.setOnConnectionCallback(
std::cerr << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocketMessageType::Message)
else if (messageType == ix::WebSocket_MessageType_Message)
{
// For an echo server, we just send back to the client whatever was received by the server
// All connected clients are available in an std::set. See the broadcast cpp example.
@@ -186,36 +183,11 @@ auto downloadSize = std::get<6>(out);
## 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.
```
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
```
There is a Dockerfile for running some code on Linux, and a unittest which can be executed by typing `make test`.
Headers and a static library will be installed to the target dir.
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.
You can build and install the ws command line tool with Homebrew.
```
brew tap bsergean/IXWebSocket
@@ -242,11 +214,11 @@ If the remote end (server) breaks the connection, the code will try to perpetual
### Large messages
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully.
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 500M were sent and received succesfully.
## Limitations
* No utf-8 validation is made when sending TEXT message with sendText()
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
@@ -301,10 +273,10 @@ If the connection was closed and sending failed, the return value will be set to
`getReadyState()` returns the state of the connection. There are 4 possible states.
1. ReadyState::Connecting - The connection is not yet open.
2. ReadyState::Open - The connection is open and ready to communicate.
3. ReadyState::Closing - The connection is in the process of closing.
4. ReadyState::Closed - The connection is closed or could not be opened.
1. WebSocket_ReadyState_Connecting - The connection is not yet open.
2. WebSocket_ReadyState_Open - The connection is open and ready to communicate.
3. WebSocket_ReadyState_Closing - The connection is in the process of closing.
4. WebSocket_MessageType_Close - The connection is closed or could not be opened.
### Open and Close notifications
@@ -316,10 +288,10 @@ webSocket.setOnMessageCallback(
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
{
if (messageType == ix::WebSocketMessageType::Open)
if (messageType == ix::WebSocket_MessageType_Open)
{
std::cout << "send greetings" << std::endl;
@@ -330,7 +302,7 @@ webSocket.setOnMessageCallback(
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocketMessageType::Close)
else if (messageType == ix::WebSocket_MessageType_Close)
{
std::cout << "disconnected" << std::endl;
@@ -345,7 +317,7 @@ webSocket.setOnMessageCallback(
### Error notification
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocket_MessageType_Error`. Multiple fields will be available on the event to describe the error.
```
webSocket.setOnMessageCallback(
@@ -353,10 +325,10 @@ webSocket.setOnMessageCallback(
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
{
if (messageType == ix::WebSocketMessageType::Error)
if (messageType == ix::WebSocket_MessageType_Error)
{
std::stringstream ss;
ss << "Error: " << error.reason << std::endl;
@@ -393,11 +365,11 @@ webSocket.setOnMessageCallback(
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
{
if (messageType == ix::WebSocketMessageType::Ping ||
messageType == ix::WebSocketMessageType::Pong)
if (messageType == ix::WebSocket_MessageType_Ping ||
messageType == ix::WebSocket_MessageType_Pong)
{
std::cout << "pong data: " << str << std::endl;
}

View File

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

View File

@@ -1,33 +0,0 @@
version: "3"
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:
security_opt:
- seccomp:unconfined
cap_add:
- SYS_PTRACE
stdin_open: true
tty: true
image: bsergean/ws:build
entrypoint: bash
networks:
- ws-net
depends_on:
- redis1
redis1:
image: redis:alpine
networks:
- ws-net
networks:
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

@@ -1,24 +0,0 @@
# Build time
FROM ubuntu:xenial as build
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install wget
RUN mkdir -p /tmp/cmake
WORKDIR /tmp/cmake
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
RUN apt-get -y install g++
RUN apt-get -y install libssl-dev
RUN apt-get -y install libz-dev
RUN apt-get -y install make
RUN apt-get -y install python
COPY . .
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
# RUN ["make"]
RUN ["make", "test"]

View File

@@ -6,18 +6,22 @@
#include "IXConnectionState.h"
#include <sstream>
namespace ix
{
std::atomic<uint64_t> ConnectionState::_globalId(0);
ConnectionState::ConnectionState() : _terminated(false)
ConnectionState::ConnectionState()
{
computeId();
}
void ConnectionState::computeId()
{
_id = std::to_string(_globalId++);
std::stringstream ss;
ss << _globalId++;
_id = ss.str();
}
const std::string& ConnectionState::getId() const
@@ -29,15 +33,5 @@ namespace ix
{
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 const std::string& getId() const;
void setTerminated();
bool isTerminated() const;
static std::shared_ptr<ConnectionState> createConnectionState();
protected:
std::atomic<bool> _terminated;
std::string _id;
static std::atomic<uint64_t> _globalId;

View File

@@ -19,19 +19,20 @@ namespace ix
std::mutex DNSLookup::_activeJobsMutex;
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
_hostname(hostname),
_port(port),
_wait(wait),
_res(nullptr),
_done(false),
_id(_nextId++)
{
setHostname(hostname);
}
DNSLookup::~DNSLookup()
{
// 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);
}
@@ -78,7 +79,7 @@ namespace ix
return nullptr;
}
return getAddrInfo(getHostname(), _port, errMsg);
return getAddrInfo(_hostname, _port, errMsg);
}
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
@@ -96,7 +97,7 @@ namespace ix
// Record job in the active Job set
{
std::lock_guard<std::mutex> lock(_activeJobsMutex);
std::unique_lock<std::mutex> lock(_activeJobsMutex);
_activeJobs.insert(_id);
}
@@ -104,7 +105,7 @@ namespace ix
// Good resource on thread forced termination
// 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();
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
@@ -134,8 +135,7 @@ namespace ix
return nullptr;
}
errMsg = getErrMsg();
return getRes();
return _res;
}
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);
// 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
std::lock_guard<std::mutex> lock(_activeJobsMutex);
std::unique_lock<std::mutex> lock(_activeJobsMutex);
if (_activeJobs.count(id) == 0)
{
return;
}
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
_res = res;
_errMsg = errMsg;
_condition.notify_one();
_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 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::mutex _hostnameMutex;
int _port;
int64_t _wait;
struct addrinfo* _res;
std::mutex _resMutex;
std::string _errMsg;
std::mutex _errMsgMutex;
struct addrinfo* _res;
std::atomic<bool> _done;
std::thread _thread;

View File

@@ -47,12 +47,13 @@ namespace ix
std::string protocol, host, path, query;
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;
ss << "Cannot parse url: " << url;
return std::make_tuple(code, HttpErrorCode::UrlMalformed,
return std::make_tuple(code, HttpErrorCode_UrlMalformed,
headers, payload, ss.str(),
uploadSize, downloadSize);
}
@@ -63,7 +64,7 @@ namespace ix
if (!_socket)
{
return std::make_tuple(code, HttpErrorCode::CannotCreateSocket,
return std::make_tuple(code, HttpErrorCode_CannotCreateSocket,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -116,7 +117,7 @@ namespace ix
{
std::stringstream ss;
ss << "Cannot connect to url: " << url;
return std::make_tuple(code, HttpErrorCode::CannotConnect,
return std::make_tuple(code, HttpErrorCode_CannotConnect,
headers, payload, ss.str(),
uploadSize, downloadSize);
}
@@ -142,7 +143,7 @@ namespace ix
if (!_socket->writeBytes(req, isCancellationRequested))
{
std::string errorMsg("Cannot send request");
return std::make_tuple(code, HttpErrorCode::SendError,
return std::make_tuple(code, HttpErrorCode_SendError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -156,7 +157,7 @@ namespace ix
if (!lineValid)
{
std::string errorMsg("Cannot retrieve status line");
return std::make_tuple(code, HttpErrorCode::CannotReadStatusLine,
return std::make_tuple(code, HttpErrorCode_CannotReadStatusLine,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -171,7 +172,7 @@ namespace ix
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{
std::string errorMsg("Cannot parse response code from status line");
return std::make_tuple(code, HttpErrorCode::MissingStatus,
return std::make_tuple(code, HttpErrorCode_MissingStatus,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -183,7 +184,7 @@ namespace ix
if (!headersValid)
{
std::string errorMsg("Cannot parse http headers");
return std::make_tuple(code, HttpErrorCode::HeaderParsingError,
return std::make_tuple(code, HttpErrorCode_HeaderParsingError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -194,7 +195,7 @@ namespace ix
if (headers.find("Location") == headers.end())
{
std::string errorMsg("Missing location header for redirect");
return std::make_tuple(code, HttpErrorCode::MissingLocation,
return std::make_tuple(code, HttpErrorCode_MissingLocation,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -203,7 +204,7 @@ namespace ix
{
std::stringstream ss;
ss << "Too many redirects: " << redirects;
return std::make_tuple(code, HttpErrorCode::TooManyRedirects,
return std::make_tuple(code, HttpErrorCode_TooManyRedirects,
headers, payload, ss.str(),
uploadSize, downloadSize);
}
@@ -215,7 +216,7 @@ namespace ix
if (verb == "HEAD")
{
return std::make_tuple(code, HttpErrorCode::Ok,
return std::make_tuple(code, HttpErrorCode_Ok,
headers, payload, std::string(),
uploadSize, downloadSize);
}
@@ -236,7 +237,7 @@ namespace ix
if (!chunkResult.first)
{
errorMsg = "Cannot read chunk";
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -254,7 +255,7 @@ namespace ix
if (!lineResult.first)
{
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -281,7 +282,7 @@ namespace ix
if (!chunkResult.first)
{
errorMsg = "Cannot read chunk";
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -292,7 +293,7 @@ namespace ix
if (!lineResult.first)
{
return std::make_tuple(code, HttpErrorCode::ChunkReadError,
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -307,7 +308,7 @@ namespace ix
else
{
std::string errorMsg("Cannot read http body");
return std::make_tuple(code, HttpErrorCode::CannotReadBody,
return std::make_tuple(code, HttpErrorCode_CannotReadBody,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@@ -321,14 +322,14 @@ namespace ix
if (!gzipInflate(payload, decompressedPayload))
{
std::string errorMsg("Error decompressing payload");
return std::make_tuple(code, HttpErrorCode::Gzip,
return std::make_tuple(code, HttpErrorCode_Gzip,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
payload = decompressedPayload;
}
return std::make_tuple(code, HttpErrorCode::Ok,
return std::make_tuple(code, HttpErrorCode_Ok,
headers, payload, std::string(),
uploadSize, downloadSize);
}

View File

@@ -19,23 +19,23 @@
namespace ix
{
enum class HttpErrorCode
enum HttpErrorCode
{
Ok = 0,
CannotConnect = 1,
Timeout = 2,
Gzip = 3,
UrlMalformed = 4,
CannotCreateSocket = 5,
SendError = 6,
ReadError = 7,
CannotReadStatusLine = 8,
MissingStatus = 9,
HeaderParsingError = 10,
MissingLocation = 11,
TooManyRedirects = 12,
ChunkReadError = 13,
CannotReadBody = 14
HttpErrorCode_Ok = 0,
HttpErrorCode_CannotConnect = 1,
HttpErrorCode_Timeout = 2,
HttpErrorCode_Gzip = 3,
HttpErrorCode_UrlMalformed = 4,
HttpErrorCode_CannotCreateSocket = 5,
HttpErrorCode_SendError = 6,
HttpErrorCode_ReadError = 7,
HttpErrorCode_CannotReadStatusLine = 8,
HttpErrorCode_MissingStatus = 9,
HttpErrorCode_HeaderParsingError = 10,
HttpErrorCode_MissingLocation = 11,
HttpErrorCode_TooManyRedirects = 12,
HttpErrorCode_ChunkReadError = 13,
HttpErrorCode_CannotReadBody = 14
};
using HttpResponse = std::tuple<int, // status

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 <unistd.h>
#endif
namespace ix
{
bool initNetSystem();
bool uninitNetSystem();
}

View File

@@ -40,8 +40,6 @@ namespace ix
bool SelectInterruptPipe::init(std::string& errorMsg)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
// calling init twice is a programming error
assert(_fildes[kPipeReadIndex] == -1);
assert(_fildes[kPipeWriteIndex] == -1);
@@ -110,8 +108,6 @@ namespace ix
bool SelectInterruptPipe::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeWriteIndex];
if (fd == -1) return false;
@@ -122,8 +118,6 @@ namespace ix
// TODO: return max uint64_t for errors ?
uint64_t SelectInterruptPipe::read()
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeReadIndex];
uint64_t value = 0;
@@ -139,8 +133,6 @@ namespace ix
int SelectInterruptPipe::getFd() const
{
std::lock_guard<std::mutex> lock(_fildesMutex);
return _fildes[kPipeReadIndex];
}
}

View File

@@ -10,7 +10,6 @@
#include <stdint.h>
#include <string>
#include <mutex>
namespace ix
{
@@ -31,7 +30,6 @@ namespace ix
// happens between a control thread and a background thread, which is
// blocked on select.
int _fildes[2];
mutable std::mutex _fildesMutex;
// Used to identify the read/write idx
static const int kPipeReadIndex;

View File

@@ -19,10 +19,7 @@
#include <sys/types.h>
#include <algorithm>
#ifdef min
#undef min
#endif
#include <iostream>
namespace ix
{
@@ -44,14 +41,17 @@ namespace ix
close();
}
PollResultType Socket::poll(int timeoutMs)
void Socket::poll(const OnPollCallback& onPollCallback, int timeoutSecs)
{
if (_sockfd == -1)
{
return PollResultType::Error;
if (onPollCallback) onPollCallback(PollResultType::Error);
return;
}
return isReadyToRead(timeoutMs);
PollResultType pollResult = isReadyToRead(1000 * timeoutSecs);
if (onPollCallback) onPollCallback(pollResult);
}
PollResultType Socket::select(bool readyToRead, int timeoutMs)
@@ -184,27 +184,11 @@ namespace ix
int Socket::getErrno()
{
int err;
#ifdef _WIN32
err = WSAGetLastError();
return WSAGetLastError();
#else
err = errno;
return errno;
#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)
@@ -239,7 +223,8 @@ namespace ix
return ret == len;
}
// There is possibly something to be writen, try again
else if (ret < 0 && Socket::isWaitNeeded())
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
{
continue;
}
@@ -267,7 +252,8 @@ namespace ix
return true;
}
// 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.
// This way we are not busy looping
@@ -326,12 +312,13 @@ namespace ix
size_t size = std::min(kChunkSize, length - output.size());
ssize_t ret = recv((char*)&_readBuffer[0], size);
if (ret <= 0 && !Socket::isWaitNeeded())
if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
getErrno() != EAGAIN))
{
// Error
return std::make_pair(false, std::string());
}
else
else if (ret > 0)
{
output.insert(output.end(),
_readBuffer.begin(),

View File

@@ -16,20 +16,6 @@
#ifdef _WIN32
#include <BaseTsd.h>
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
#include "IXCancellationRequest.h"
@@ -41,22 +27,27 @@ namespace ix
enum class PollResultType
{
ReadyForRead = 0,
ReadyForWrite = 1,
Timeout = 2,
Error = 3,
SendRequest = 4,
CloseRequest = 5
ReadyForRead = 0,
ReadyForWrite = 1,
Timeout = 2,
Error = 3,
SendRequest = 4,
CloseRequest = 5
};
class Socket {
public:
using OnPollCallback = std::function<void(PollResultType)>;
Socket(int fd = -1);
virtual ~Socket();
bool init(std::string& errorMsg);
void configure();
// Functions to check whether there is activity on the socket
PollResultType poll(int timeoutMs = kDefaultPollTimeout);
void poll(const OnPollCallback& onPollCallback,
int timeoutSecs = kDefaultPollTimeout);
bool wakeUpFromPoll(uint8_t wakeUpCode);
PollResultType isReadyToWrite(int timeoutMs);
@@ -88,14 +79,14 @@ namespace ix
const CancellationRequest& isCancellationRequested);
static int getErrno();
static bool isWaitNeeded();
static void closeSocket(int fd);
// Used as special codes for pipe communication
static const uint64_t kSendRequest;
static const uint64_t kCloseRequest;
protected:
void closeSocket(int fd);
std::atomic<int> _sockfd;
std::mutex _socketMutex;

View File

@@ -20,6 +20,8 @@
#include <unistd.h>
#include <stdint.h>
#include <iostream>
#include <errno.h>
#define socketerrno errno

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@
#include "IXSocketConnect.h"
#include <cassert>
#include <iostream>
#include <openssl/x509v3.h>
@@ -341,7 +342,7 @@ namespace ix
ERR_clear_error();
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) {
nbyte -= write_result;
@@ -381,7 +382,7 @@ namespace ix
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)
{

View File

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

View File

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

View File

@@ -13,7 +13,6 @@
#include <sstream>
#include <future>
#include <string.h>
#include <assert.h>
namespace ix
{
@@ -77,7 +76,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
::close(_serverFd);
return std::make_pair(false, ss.str());
}
@@ -101,7 +100,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
::close(_serverFd);
return std::make_pair(false, ss.str());
}
@@ -115,7 +114,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
::close(_serverFd);
return std::make_pair(false, ss.str());
}
@@ -135,23 +134,8 @@ namespace ix
_conditionVariable.wait(lock);
}
void SocketServer::stopAcceptingConnections()
{
_stop = true;
}
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
_stop = true;
@@ -159,7 +143,7 @@ namespace ix
_stop = false;
_conditionVariable.notify_one();
Socket::closeSocket(_serverFd);
::close(_serverFd);
}
void SocketServer::setConnectionStateFactory(
@@ -168,52 +152,18 @@ namespace ix
_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()
{
// Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd);
// Return value of std::async, ignored
std::future<void> f;
for (;;)
{
if (_stop) return;
// Garbage collection to shutdown/join threads for closed connections.
// We could run this in its own thread, so that we dont need to accept
// a new connection to close a thread.
// We could also use a condition variable to be notify when we need to do this
closeTerminatedThreads();
// Use select to check whether a new connection is in progress
fd_set rfds;
struct timeval timeout;
@@ -242,18 +192,17 @@ namespace ix
// Accept a connection.
struct sockaddr_in client; // client address information
int clientFd; // socket connected to client
socklen_t addressLen = sizeof(client);
socklen_t addressLen = sizeof(socklen_t);
memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
{
if (!Socket::isWaitNeeded())
if (Socket::getErrno() != EWOULDBLOCK)
{
// FIXME: that error should be propagated
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error accepting connection: "
<< err << ", " << strerror(err);
<< strerror(Socket::getErrno());
logError(ss.str());
}
continue;
@@ -267,7 +216,7 @@ namespace ix
<< "Not accepting connection";
logError(ss.str());
Socket::closeSocket(clientFd);
::close(clientFd);
continue;
}
@@ -278,16 +227,15 @@ namespace ix
connectionState = _connectionStateFactory();
}
if (_stop) return;
// Launch the handleConnection work asynchronously in its own thread.
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
_connectionsThreads.push_back(std::make_pair(
connectionState,
std::thread(&SocketServer::handleConnection,
this,
clientFd,
connectionState)));
//
// the destructor of a future returned by std::async blocks,
// so we need to declare it outside of this loop
f = std::async(std::launch::async,
&SocketServer::handleConnection,
this,
clientFd,
connectionState);
}
}
}

View File

@@ -12,7 +12,6 @@
#include <string>
#include <set>
#include <thread>
#include <list>
#include <mutex>
#include <functional>
#include <memory>
@@ -25,11 +24,6 @@ namespace ix
public:
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,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
@@ -37,9 +31,6 @@ namespace ix
virtual ~SocketServer();
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);
const static int kDefaultPort;
@@ -57,8 +48,6 @@ namespace ix
void logError(const std::string& str);
void logInfo(const std::string& str);
void stopAcceptingConnections();
private:
// Member variables
int _port;
@@ -71,20 +60,13 @@ namespace ix
std::mutex _logMutex;
// background thread to wait for incoming connections
std::atomic<bool> _stop;
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::mutex _conditionVariableMutex;
// the factory to create ConnectionState objects
//
ConnectionStateFactory _connectionStateFactory;
// Methods
@@ -92,8 +74,5 @@ namespace ix
virtual void handleConnection(int fd,
std::shared_ptr<ConnectionState> connectionState) = 0;
virtual size_t getConnectedClientsCount() = 0;
// Returns true if all connection threads are joined
bool closeTerminatedThreads();
};
}

View File

@@ -5,30 +5,43 @@
*/
#include "IXUrlParser.h"
#include "LUrlParser.h"
#include <iostream>
#include <sstream>
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,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port)
int& port,
bool websocket)
{
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
if (!res.IsValid())
std::cmatch what;
if (!regex_match(url.c_str(), what,
websocket ? _webSocketRegex : _httpRegex))
{
return false;
}
protocol = res.m_Scheme;
host = res.m_Host;
path = res.m_Path;
query = res.m_Query;
std::string portStr;
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")
{
@@ -45,6 +58,12 @@ namespace ix
return false;
}
}
else
{
std::stringstream ss;
ss << portStr;
ss >> port;
}
if (path.empty())
{
@@ -64,4 +83,22 @@ namespace ix
return true;
}
void UrlParser::printUrl(const std::string& url, bool websocket)
{
std::string protocol, host, path, query;
int port {0};
if (!parse(url, protocol, host, path, query, port, websocket))
{
return;
}
std::cout << "[" << url << "]" << std::endl;
std::cout << protocol << std::endl;
std::cout << host << std::endl;
std::cout << port << std::endl;
std::cout << path << std::endl;
std::cout << query << std::endl;
std::cout << "-------------------------------" << std::endl;
}
}

View File

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

View File

@@ -8,26 +8,22 @@
#include "IXSetThreadName.h"
#include "IXWebSocketHandshake.h"
#include <iostream>
#include <cmath>
#include <cassert>
namespace
{
uint64_t calculateRetryWaitMilliseconds(uint32_t retry_count)
uint64_t calculateRetryWaitMilliseconds(uint64_t retry_count)
{
uint64_t wait_time;
// This will overflow quite fast for large value of retry_count
// and will become 0, in which case the wait time will be none
// and we'll be constantly retrying to connect.
uint64_t wait_time = ((uint64_t) std::pow(2, retry_count) * 100L);
if (retry_count <= 6)
{
// max wait_time is 6400 ms (2 ^ 6 = 64)
wait_time = ((uint64_t)std::pow(2, retry_count) * 100L);
}
else
{
wait_time = 10 * 1000; // 10 sec
}
return wait_time;
// cap the wait time to 10s, or to retry_count == 10 for which wait_time > 10s
uint64_t tenSeconds = 10 * 1000;
return (wait_time > tenSeconds || retry_count > 10) ? tenSeconds : wait_time;
}
}
@@ -35,25 +31,21 @@ namespace ix
{
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
const int WebSocket::kDefaultPingIntervalSecs(-1);
const int WebSocket::kDefaultPingTimeoutSecs(-1);
const bool WebSocket::kDefaultEnablePong(true);
const int WebSocket::kDefaultHeartBeatPeriod(-1);
WebSocket::WebSocket() :
_onMessageCallback(OnMessageCallback()),
_stop(false),
_automaticReconnection(true),
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
_enablePong(kDefaultEnablePong),
_pingIntervalSecs(kDefaultPingIntervalSecs),
_pingTimeoutSecs(kDefaultPingTimeoutSecs)
_heartBeatPeriod(kDefaultHeartBeatPeriod)
{
_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(WebSocketMessageType::Close, "", wireSize,
_onMessageCallback(WebSocket_MessageType_Close, "", wireSize,
WebSocketErrorInfo(), WebSocketOpenInfo(),
WebSocketCloseInfo(code, reason, remote));
WebSocketCloseInfo(code, reason));
}
);
}
@@ -87,52 +79,16 @@ namespace ix
return _perMessageDeflateOptions;
}
void WebSocket::setHeartBeatPeriod(int heartBeatPeriodSecs)
void WebSocket::setHeartBeatPeriod(int heartBeatPeriod)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingIntervalSecs = heartBeatPeriodSecs;
_heartBeatPeriod = heartBeatPeriod;
}
int WebSocket::getHeartBeatPeriod() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingIntervalSecs;
}
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;
return _heartBeatPeriod;
}
void WebSocket::start()
@@ -144,16 +100,24 @@ namespace ix
void WebSocket::stop()
{
bool automaticReconnection = _automaticReconnection;
// This value needs to be forced when shutting down, it is restored later
_automaticReconnection = false;
close();
if (_thread.joinable())
if (!_thread.joinable())
{
// wait until working thread will exit
// it will exit after close operation is finished
_stop = true;
_thread.join();
_stop = false;
_automaticReconnection = automaticReconnection;
return;
}
_stop = true;
_thread.join();
_stop = false;
_automaticReconnection = automaticReconnection;
}
WebSocketInitResult WebSocket::connect(int timeoutSecs)
@@ -161,9 +125,7 @@ namespace ix
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions,
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
_heartBeatPeriod);
}
WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
@@ -172,7 +134,7 @@ namespace ix
return status;
}
_onMessageCallback(WebSocketMessageType::Open, "", 0,
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo());
@@ -183,10 +145,7 @@ namespace ix
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions,
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
_ws.configure(_perMessageDeflateOptions, _heartBeatPeriod);
}
WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
@@ -195,7 +154,7 @@ namespace ix
return status;
}
_onMessageCallback(WebSocketMessageType::Open, "", 0,
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo());
@@ -204,12 +163,12 @@ namespace ix
bool WebSocket::isConnected() const
{
return getReadyState() == ReadyState::Open;
return getReadyState() == WebSocket_ReadyState_Open;
}
bool WebSocket::isClosing() const
{
return getReadyState() == ReadyState::Closing;
return getReadyState() == WebSocket_ReadyState_Closing;
}
void WebSocket::close()
@@ -217,87 +176,60 @@ namespace ix
_ws.close();
}
void WebSocket::checkConnection(bool firstConnectionAttempt)
void WebSocket::reconnectPerpetuallyIfDisconnected()
{
using millis = std::chrono::duration<double, std::milli>;
uint32_t retries = 0;
millis duration;
uint64_t retries = 0;
WebSocketErrorInfo connectErr;
ix::WebSocketInitResult status;
using millis = std::chrono::duration<double, std::milli>;
millis duration;
// Try to connect perpertually
while (true)
{
if (isConnected() || isClosing() || _stop)
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
{
break;
}
if (!firstConnectionAttempt && !_automaticReconnection)
{
// Do not attempt to reconnect
break;
}
firstConnectionAttempt = false;
// Only sleep if we are retrying
if (duration.count() > 0)
{
// to do: make sleeping conditional
std::this_thread::sleep_for(duration);
}
// Try to connect synchronously
status = connect(_handshakeTimeoutSecs);
if (!status.success)
if (!status.success && !_stop)
{
WebSocketErrorInfo connectErr;
duration = millis(calculateRetryWaitMilliseconds(retries++));
if (_automaticReconnection)
{
duration = millis(calculateRetryWaitMilliseconds(retries++));
connectErr.wait_time = duration.count();
connectErr.retries = retries;
}
connectErr.reason = status.errorStr;
connectErr.retries = retries;
connectErr.wait_time = duration.count();
connectErr.reason = status.errorStr;
connectErr.http_status = status.http_status;
_onMessageCallback(WebSocketMessageType::Error, "", 0,
_onMessageCallback(WebSocket_MessageType_Error, "", 0,
connectErr, WebSocketOpenInfo(),
WebSocketCloseInfo());
std::this_thread::sleep_for(duration);
}
}
}
void WebSocket::run()
{
setThreadName(getUrl());
bool firstConnectionAttempt = true;
setThreadName(_url);
while (true)
{
if (_stop) return;
// 1. Make sure we are always connected
checkConnection(firstConnectionAttempt);
reconnectPerpetuallyIfDisconnected();
firstConnectionAttempt = false;
// if here we are closed then checkConnection was not able to connect
if (getReadyState() == ReadyState::Closed)
{
break;
}
if (_stop) return;
// 2. Poll to see if there's any new data available
WebSocketTransport::PollResult pollResult = _ws.poll();
_ws.poll();
if (_stop) return;
// 3. Dispatch the incoming messages
_ws.dispatch(
pollResult,
[this](const std::string& msg,
size_t wireSize,
bool decompressionError,
@@ -306,25 +238,24 @@ namespace ix
WebSocketMessageType webSocketMessageType;
switch (messageKind)
{
default:
case WebSocketTransport::MessageKind::MSG:
case WebSocketTransport::MSG:
{
webSocketMessageType = WebSocketMessageType::Message;
webSocketMessageType = WebSocket_MessageType_Message;
} break;
case WebSocketTransport::MessageKind::PING:
case WebSocketTransport::PING:
{
webSocketMessageType = WebSocketMessageType::Ping;
webSocketMessageType = WebSocket_MessageType_Ping;
} break;
case WebSocketTransport::MessageKind::PONG:
case WebSocketTransport::PONG:
{
webSocketMessageType = WebSocketMessageType::Pong;
webSocketMessageType = WebSocket_MessageType_Pong;
} break;
case WebSocketTransport::MessageKind::FRAGMENT:
case WebSocketTransport::FRAGMENT:
{
webSocketMessageType = WebSocketMessageType::Fragment;
webSocketMessageType = WebSocket_MessageType_Fragment;
} break;
}
@@ -337,6 +268,11 @@ namespace ix
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
});
// 4. In blocking mode, getting out of this function is triggered by
// an explicit disconnection from the callback, or by the remote end
// closing the connection, ie isConnected() == false.
if (!_thread.joinable() && !isConnected() && !_automaticReconnection) return;
}
}
@@ -363,16 +299,10 @@ namespace ix
}
}
WebSocketSendInfo WebSocket::send(const std::string& data,
WebSocketSendInfo WebSocket::send(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendText(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
return sendMessage(text, false, onProgressCallback);
}
WebSocketSendInfo WebSocket::ping(const std::string& text)
@@ -381,11 +311,11 @@ namespace ix
constexpr size_t pingMaxPayloadSize = 125;
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
return sendMessage(text, SendMessageKind::Ping);
return sendMessage(text, true);
}
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
SendMessageKind sendMessageKind,
bool ping,
const OnProgressCallback& onProgressCallback)
{
if (!isConnected()) return WebSocketSendInfo(false);
@@ -402,22 +332,13 @@ namespace ix
std::lock_guard<std::mutex> lock(_writeMutex);
WebSocketSendInfo webSocketSendInfo;
switch (sendMessageKind)
if (ping)
{
case SendMessageKind::Text:
{
webSocketSendInfo = _ws.sendText(text, onProgressCallback);
} break;
case SendMessageKind::Binary:
{
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
} break;
case SendMessageKind::Ping:
{
webSocketSendInfo = _ws.sendPing(text);
} break;
webSocketSendInfo = _ws.sendPing(text);
}
else
{
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
}
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
@@ -429,11 +350,11 @@ namespace ix
{
switch (_ws.getReadyState())
{
case ix::WebSocketTransport::ReadyState::OPEN : return ReadyState::Open;
case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting;
case ix::WebSocketTransport::ReadyState::CLOSING : return ReadyState::Closing;
case ix::WebSocketTransport::ReadyState::CLOSED : return ReadyState::Closed;
default: return ReadyState::Closed;
case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open;
case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
case ix::WebSocketTransport::CLOSING: return WebSocket_ReadyState_Closing;
case ix::WebSocketTransport::CLOSED: return WebSocket_ReadyState_Closed;
default: return WebSocket_ReadyState_Closed;
}
}
@@ -441,11 +362,11 @@ namespace ix
{
switch (readyState)
{
case ReadyState::Open : return "OPEN";
case ReadyState::Connecting: return "CONNECTING";
case ReadyState::Closing : return "CLOSING";
case ReadyState::Closed : return "CLOSED";
default: return "UNKNOWN";
case WebSocket_ReadyState_Open: return "OPEN";
case WebSocket_ReadyState_Connecting: return "CONNECTING";
case WebSocket_ReadyState_Closing: return "CLOSING";
case WebSocket_ReadyState_Closed: return "CLOSED";
default: return "CLOSED";
}
}

View File

@@ -24,23 +24,23 @@
namespace ix
{
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
enum class ReadyState
enum ReadyState
{
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3
WebSocket_ReadyState_Connecting = 0,
WebSocket_ReadyState_Open = 1,
WebSocket_ReadyState_Closing = 2,
WebSocket_ReadyState_Closed = 3
};
enum class WebSocketMessageType
enum WebSocketMessageType
{
Message = 0,
Open = 1,
Close = 2,
Error = 3,
Ping = 4,
Pong = 5,
Fragment = 6
WebSocket_MessageType_Message = 0,
WebSocket_MessageType_Open = 1,
WebSocket_MessageType_Close = 2,
WebSocket_MessageType_Error = 3,
WebSocket_MessageType_Ping = 4,
WebSocket_MessageType_Pong = 5,
WebSocket_MessageType_Fragment = 6
};
struct WebSocketOpenInfo
@@ -61,14 +61,11 @@ namespace ix
{
uint16_t code;
std::string reason;
bool remote;
WebSocketCloseInfo(uint16_t c = 0,
const std::string& r = std::string(),
bool rem = false)
const std::string& r = std::string())
: code(c)
, reason(r)
, remote(rem)
{
;
}
@@ -91,26 +88,19 @@ namespace ix
void setUrl(const std::string& url);
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setHeartBeatPeriod(int heartBeatPeriodSecs);
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
void setPingTimeout(int pingTimeoutSecs);
void enablePong();
void disablePong();
void setHandshakeTimeout(int handshakeTimeoutSecs);
void setHeartBeatPeriod(int heartBeatPeriod);
// Run asynchronously, by calling start and stop.
void start();
// stop is synchronous
void stop();
// Run in blocking mode, by connecting first manually, and then calling run.
WebSocketInitResult connect(int timeoutSecs);
void run();
// send binary data
WebSocketSendInfo send(const std::string& data,
WebSocketSendInfo send(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendText(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string& text);
void close();
@@ -119,13 +109,9 @@ namespace ix
static void resetTrafficTrackerCallback();
ReadyState getReadyState() const;
static std::string readyStateToString(ReadyState readyState);
const std::string& getUrl() const;
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
int getHeartBeatPeriod() const;
int getPingInterval() const;
int getPingTimeout() const;
size_t bufferedAmount() const;
void enableAutomaticReconnection();
@@ -134,15 +120,17 @@ namespace ix
private:
WebSocketSendInfo sendMessage(const std::string& text,
SendMessageKind sendMessageKind,
bool ping,
const OnProgressCallback& callback = nullptr);
bool isConnected() const;
bool isClosing() const;
void checkConnection(bool firstConnectionAttempt);
void reconnectPerpetuallyIfDisconnected();
std::string readyStateToString(ReadyState readyState);
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
// Server
void setSocketFileDescriptor(int fd);
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
WebSocketTransport _ws;
@@ -162,15 +150,9 @@ namespace ix
std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs;
// enable or disable PONG frame response to received PING frame
bool _enablePong;
static const bool kDefaultEnablePong;
// Optional ping and ping timeout
int _pingIntervalSecs;
int _pingTimeoutSecs;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
// Optional Heartbeat
int _heartBeatPeriod;
static const int kDefaultHeartBeatPeriod;
friend class WebSocketServer;
};

View File

@@ -12,10 +12,10 @@ namespace ix
{
struct WebSocketErrorInfo
{
uint32_t retries = 0;
double wait_time = 0;
int http_status = 0;
uint64_t retries;
double wait_time;
int http_status;
std::string reason;
bool decompressionError = false;
bool decompressionError;
};
}

View File

@@ -10,7 +10,9 @@
#include "libwshandshake.hpp"
#include <iostream>
#include <sstream>
#include <regex>
#include <random>
#include <algorithm>
@@ -112,7 +114,7 @@ namespace ix
std::stringstream ss;
ss << "HTTP/1.1 ";
ss << code;
ss << " ";
ss << "\r\n";
ss << reason;
ss << "\r\n";
@@ -351,7 +353,7 @@ namespace ix
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output);
std::stringstream ss;
ss << "HTTP/1.1 101 Switching Protocols\r\n";
ss << "HTTP/1.1 101\r\n";
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
ss << "Upgrade: websocket\r\n";
ss << "Connection: Upgrade\r\n";

View File

@@ -6,28 +6,12 @@
#include "IXWebSocketHttpHeaders.h"
#include "IXSocket.h"
#include <algorithm>
#include <locale>
#include <string>
#include <unordered_map>
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::shared_ptr<Socket> socket,
const CancellationRequest& isCancellationRequested)

View File

@@ -11,6 +11,7 @@
#include <string>
#include <map>
#include <memory>
#include <algorithm>
namespace ix
{
@@ -21,10 +22,19 @@ namespace ix
// Case Insensitive compare_less binary function
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>;

View File

@@ -7,6 +7,7 @@
#include "IXWebSocketPerMessageDeflateCodec.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <iostream>
#include <cassert>
#include <string.h>

View File

@@ -7,6 +7,7 @@
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <sstream>
#include <iostream>
#include <algorithm>
#include <cctype>

View File

@@ -23,6 +23,7 @@ namespace ix
WebSocketPerMessageDeflateOptions(std::string extension);
std::string generateHeader();
std::string parseHeader();
bool enabled() const;
bool getClientNoContextTakeover() const;
bool getServerNoContextTakeover() const;

View File

@@ -6,6 +6,9 @@
#pragma once
#include <string>
#include <iostream>
namespace ix
{
struct WebSocketSendInfo

View File

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

View File

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

View File

@@ -45,62 +45,26 @@
#include <vector>
#include <string>
#include <cstdarg>
#include <iostream>
#include <sstream>
#include <chrono>
#include <thread>
namespace
{
int greatestCommonDivisor(int a, int b)
{
while (b != 0)
{
int t = b;
b = a % b;
a = t;
}
return a;
}
}
namespace ix
{
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat");
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
const bool WebSocketTransport::kDefaultEnablePong(true);
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(200);
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::heartbeat");
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
constexpr size_t WebSocketTransport::kChunkSize;
const uint16_t WebSocketTransport::kInternalErrorCode(1011);
const uint16_t WebSocketTransport::kAbnormalCloseCode(1006);
const uint16_t WebSocketTransport::kProtocolErrorCode(1002);
const uint16_t WebSocketTransport::kNoStatusCodeErrorCode(1005);
const std::string WebSocketTransport::kInternalErrorMessage("Internal error");
const std::string WebSocketTransport::kAbnormalCloseMessage("Abnormal closure");
const std::string WebSocketTransport::kPingTimeoutMessage("Ping timeout");
const std::string WebSocketTransport::kProtocolErrorMessage("Protocol error");
const std::string WebSocketTransport::kNoStatusCodeErrorMessage("No status code");
WebSocketTransport::WebSocketTransport() :
_useMask(true),
_readyState(ReadyState::CLOSED),
_closeCode(kInternalErrorCode),
_closeReason(kInternalErrorMessage),
_readyState(CLOSED),
_closeCode(0),
_closeWireSize(0),
_closeRemote(false),
_enablePerMessageDeflate(false),
_requestInitCancellation(false),
_closingTimePoint(std::chrono::steady_clock::now()),
_enablePong(kDefaultEnablePong),
_pingIntervalSecs(kDefaultPingIntervalSecs),
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
_pingIntervalOrTimeoutGCDSecs(-1),
_nextGCDTimePoint(std::chrono::steady_clock::now()),
_lastSendPingTimePoint(std::chrono::steady_clock::now()),
_lastReceivePongTimePoint(std::chrono::steady_clock::now())
_heartBeatPeriod(kDefaultHeartBeatPeriod),
_lastSendTimePoint(std::chrono::steady_clock::now())
{
_readbuf.resize(kChunkSize);
}
@@ -111,34 +75,11 @@ namespace ix
}
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
bool enablePong,
int pingIntervalSecs,
int pingTimeoutSecs)
int heartBeatPeriod)
{
_perMessageDeflateOptions = perMessageDeflateOptions;
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
_enablePong = enablePong;
_pingIntervalSecs = pingIntervalSecs;
_pingTimeoutSecs = pingTimeoutSecs;
if (pingIntervalSecs > 0 && pingTimeoutSecs > 0)
{
_pingIntervalOrTimeoutGCDSecs = greatestCommonDivisor(pingIntervalSecs,
pingTimeoutSecs);
}
else if (_pingTimeoutSecs > 0)
{
_pingIntervalOrTimeoutGCDSecs = pingTimeoutSecs;
}
else
{
_pingIntervalOrTimeoutGCDSecs = pingIntervalSecs;
}
if (_pingIntervalOrTimeoutGCDSecs > 0)
{
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
}
_heartBeatPeriod = heartBeatPeriod;
}
// Client
@@ -147,8 +88,9 @@ namespace ix
{
std::string protocol, host, path, query;
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,
std::string("Could not parse URL ") + url);
@@ -173,7 +115,7 @@ namespace ix
timeoutSecs);
if (result.success)
{
setReadyState(ReadyState::OPEN);
setReadyState(OPEN);
}
return result;
}
@@ -181,9 +123,6 @@ namespace ix
// Server
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
{
// Server should not mask the data it sends to the client
_useMask = false;
std::string errorMsg;
_socket = createSocket(fd, errorMsg);
@@ -201,32 +140,31 @@ namespace ix
auto result = webSocketHandshake.serverHandshake(fd, timeoutSecs);
if (result.success)
{
setReadyState(ReadyState::OPEN);
setReadyState(OPEN);
}
return result;
}
WebSocketTransport::ReadyState WebSocketTransport::getReadyState() const
WebSocketTransport::ReadyStateValues WebSocketTransport::getReadyState() const
{
return _readyState;
}
void WebSocketTransport::setReadyState(ReadyState readyState)
void WebSocketTransport::setReadyState(ReadyStateValues readyStateValue)
{
// No state change, return
if (_readyState == readyState) return;
if (_readyState == readyStateValue) return;
if (readyState == ReadyState::CLOSED)
if (readyStateValue == CLOSED)
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote);
_closeCode = kInternalErrorCode;
_closeReason = kInternalErrorMessage;
_onCloseCallback(_closeCode, _closeReason, _closeWireSize);
_closeCode = 0;
_closeReason = std::string();
_closeWireSize = 0;
_closeRemote = false;
}
_readyState = readyState;
_readyState = readyStateValue;
}
void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback)
@@ -234,145 +172,95 @@ namespace ix
_onCloseCallback = onCloseCallback;
}
// Only consider send PING time points for that computation.
bool WebSocketTransport::pingIntervalExceeded()
// Only consider send time points for that computation.
// The receive time points is taken into account in Socket::poll (second parameter).
bool WebSocketTransport::heartBeatPeriodExceeded()
{
if (_pingIntervalSecs <= 0)
return false;
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
std::lock_guard<std::mutex> lock(_lastSendTimePointMutex);
auto now = std::chrono::steady_clock::now();
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs);
return now - _lastSendTimePoint > std::chrono::seconds(_heartBeatPeriod);
}
bool WebSocketTransport::pingTimeoutExceeded()
void WebSocketTransport::poll()
{
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);
}
bool WebSocketTransport::closingDelayExceeded()
{
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
auto now = std::chrono::steady_clock::now();
return now - _closingTimePoint > std::chrono::milliseconds(kClosingMaximumWaitingDelayInMs);
}
WebSocketTransport::PollResult WebSocketTransport::poll()
{
if (_readyState == 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())
_socket->poll(
[this](PollResultType pollResult)
{
close(kInternalErrorCode, kPingTimeoutMessage);
}
// If ping is enabled and no ping has been sent for a duration
// exceeding our ping interval, send a ping to the server.
else if (pingIntervalExceeded())
{
std::stringstream ss;
ss << kPingMessage << "::" << _pingIntervalSecs << "s";
sendPing(ss.str());
}
}
// No timeout if state is not OPEN, otherwise computed
// pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set)
int lastingTimeoutDelayInMs = (_readyState != ReadyState::OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs;
// If (1) heartbeat is enabled, and (2) no data was received or
// send for a duration exceeding our heart-beat period, send a
// ping to the server.
if (pollResult == PollResultType::Timeout &&
heartBeatPeriodExceeded())
{
std::stringstream ss;
ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
sendPing(ss.str());
}
// Make sure we send all the buffered data
// there can be a lot of it for large messages.
else 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 (_pingIntervalOrTimeoutGCDSecs > 0)
{
// compute lasting delay to wait for next ping / timeout, if at least one set
auto now = std::chrono::steady_clock::now();
if (result == PollResultType::Error)
{
_socket->close();
setReadyState(CLOSED);
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 (now >= _nextGCDTimePoint)
{
_nextGCDTimePoint = now + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
lastingTimeoutDelayInMs = _pingIntervalOrTimeoutGCDSecs * 1000;
}
else
{
lastingTimeoutDelayInMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(_nextGCDTimePoint - now).count();
}
}
// poll the socket
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);
// 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)
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();
setReadyState(ReadyState::CLOSED);
break;
}
else if (result == PollResultType::ReadyForWrite)
else if (pollResult == PollResultType::CloseRequest)
{
sendOnSocket();
}
}
}
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)
{
// if there are received data pending to be processed, then delay the abnormal closure
// to after dispatch (other close code/reason could be read from the buffer)
_socket->close();
return PollResult::AbnormalClose;
}
else
// Avoid a race condition where we get stuck in select
// while closing.
if (_readyState == CLOSING)
{
_rxbuf.insert(_rxbuf.end(),
_readbuf.begin(),
_readbuf.begin() + ret);
_socket->close();
}
}
}
else if (pollResult == PollResultType::Error)
{
_socket->close();
}
else if (pollResult == PollResultType::CloseRequest)
{
_socket->close();
}
if (_readyState == ReadyState::CLOSING && closingDelayExceeded())
{
_rxbuf.clear();
// close code and reason were set when calling close()
_socket->close();
setReadyState(ReadyState::CLOSED);
}
return PollResult::Succeeded;
},
_heartBeatPeriod);
}
bool WebSocketTransport::isSendBufferEmpty() const
@@ -392,15 +280,19 @@ namespace ix
_txbuf.insert(_txbuf.end(), header.begin(), header.end());
_txbuf.insert(_txbuf.end(), begin, end);
if (_useMask)
// Masking
for (size_t i = 0; i != (size_t) message_size; ++i)
{
for (size_t i = 0; i != (size_t) message_size; ++i)
{
*(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i&0x3];
}
*(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i&0x3];
}
}
void WebSocketTransport::appendToSendBuffer(const std::vector<uint8_t>& buffer)
{
std::lock_guard<std::mutex> lock(_txbufMutex);
_txbuf.insert(_txbuf.end(), buffer.begin(), buffer.end());
}
void WebSocketTransport::unmaskReceiveBuffer(const wsheader_type& ws)
{
if (ws.mask)
@@ -434,13 +326,12 @@ namespace ix
// | Payload Data continued ... |
// +---------------------------------------------------------------+
//
void WebSocketTransport::dispatch(WebSocketTransport::PollResult pollResult,
const OnMessageCallback& onMessageCallback)
void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback)
{
while (true)
{
wsheader_type ws;
if (_rxbuf.size() < 2) break; /* Need at least 2 */
if (_rxbuf.size() < 2) return; /* Need at least 2 */
const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume
ws.fin = (data[0] & 0x80) == 0x80;
ws.rsv1 = (data[0] & 0x40) == 0x40;
@@ -448,7 +339,7 @@ namespace ix
ws.mask = (data[1] & 0x80) == 0x80;
ws.N0 = (data[1] & 0x7f);
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */
if (_rxbuf.size() < ws.header_size) return; /* Need: ws.header_size - _rxbuf.size() */
//
// Calculate payload length:
@@ -521,7 +412,7 @@ namespace ix
//
if (ws.fin && _chunks.empty())
{
emitMessage(MessageKind::MSG,
emitMessage(MSG,
std::string(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
ws,
@@ -541,30 +432,26 @@ namespace ix
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
if (ws.fin)
{
emitMessage(MessageKind::MSG, getMergedChunks(), ws, onMessageCallback);
emitMessage(MSG, getMergedChunks(), ws, onMessageCallback);
_chunks.clear();
}
else
{
emitMessage(MessageKind::FRAGMENT, std::string(), ws, onMessageCallback);
emitMessage(FRAGMENT, std::string(), ws, onMessageCallback);
}
}
}
else if (ws.opcode == wsheader_type::PING)
{
unmaskReceiveBuffer(ws);
std::string pingData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
if (_enablePong)
{
// Reply back right away
bool compress = false;
sendData(wsheader_type::PONG, pingData, compress);
}
// Reply back right away
bool compress = false;
sendData(wsheader_type::PONG, pingData, compress);
emitMessage(MessageKind::PING, pingData, ws, onMessageCallback);
emitMessage(PING, pingData, ws, onMessageCallback);
}
else if (ws.opcode == wsheader_type::PONG)
{
@@ -572,96 +459,39 @@ namespace ix
std::string pongData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
std::lock_guard<std::mutex> lck(_lastReceivePongTimePointMutex);
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
emitMessage(MessageKind::PONG, pongData, ws, onMessageCallback);
emitMessage(PONG, pongData, ws, onMessageCallback);
}
else if (ws.opcode == wsheader_type::CLOSE)
{
std::string reason;
uint16_t code = 0;
unmaskReceiveBuffer(ws);
if (ws.N >= 2)
{
// Extract the close code first, available as the first 2 bytes
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
// Extract the close code first, available as the first 2 bytes
uint16_t code = 0;
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
// Get the reason.
std::string reason(_rxbuf.begin()+ws.header_size + 2,
_rxbuf.begin()+ws.header_size + 2 + (size_t) ws.N);
// Get the reason.
if (ws.N > 2)
{
reason.assign(_rxbuf.begin()+ws.header_size + 2,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
}
}
else
{
// no close code received
code = kNoStatusCodeErrorCode;
reason = kNoStatusCodeErrorMessage;
std::lock_guard<std::mutex> lock(_closeDataMutex);
_closeCode = code;
_closeReason = reason;
_closeWireSize = _rxbuf.size();
}
// We receive a CLOSE frame from remote and are NOT the ones who triggered the close
if (_readyState != ReadyState::CLOSING)
{
// send back the CLOSE frame
sendCloseFrame(code, reason);
_socket->wakeUpFromPoll(Socket::kCloseRequest);
bool remote = true;
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
}
else
{
// we got the CLOSE frame answer from our close, so we can close the connection if
// the code/reason are the same
bool identicalReason;
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
identicalReason = _closeCode == code && _closeReason == reason;
}
if (identicalReason)
{
bool remote = false;
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
}
}
close();
}
else
{
// Unexpected frame type
close(kProtocolErrorCode, kProtocolErrorMessage, _rxbuf.size());
close();
}
// Erase the message that has been processed from the input/read buffer
_rxbuf.erase(_rxbuf.begin(),
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
}
// if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in
// the received and processed data then close the connection
if (pollResult == PollResult::AbnormalClose)
{
_rxbuf.clear();
// if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before)
if (_readyState == ReadyState::CLOSING)
{
_socket->close();
setReadyState(ReadyState::CLOSED);
}
// if we weren't closing, then close using abnormal close code and message
else if (_readyState != ReadyState::CLOSED)
{
closeSocketAndSwitchToClosedState(kAbnormalCloseCode, kAbnormalCloseMessage, 0, false);
}
}
}
std::string WebSocketTransport::getMergedChunks() const
@@ -692,7 +522,7 @@ namespace ix
size_t wireSize = message.size();
// When the RSV1 bit is 1 it means the message is compressed
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != MessageKind::FRAGMENT)
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != FRAGMENT)
{
std::string decompressedMessage;
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
@@ -719,7 +549,7 @@ namespace ix
bool compress,
const OnProgressCallback& onProgressCallback)
{
if (_readyState != ReadyState::OPEN)
if (_readyState == CLOSING || _readyState == CLOSED)
{
return WebSocketSendInfo();
}
@@ -814,7 +644,7 @@ namespace ix
std::string::const_iterator message_end,
bool compress)
{
uint64_t message_size = static_cast<uint64_t>(message_end - message_begin);
auto message_size = message_end - message_begin;
unsigned x = getRandomUnsigned();
uint8_t masking_key[4] = {};
@@ -826,8 +656,7 @@ namespace ix
std::vector<uint8_t> header;
header.assign(2 +
(message_size >= 126 ? 2 : 0) +
(message_size >= 65536 ? 6 : 0) +
(_useMask ? 4 : 0), 0);
(message_size >= 65536 ? 6 : 0) + 4, 0);
header[0] = type;
// The fin bit indicate that this is the last fragment. Fin is French for end.
@@ -844,33 +673,27 @@ namespace ix
if (message_size < 126)
{
header[1] = (message_size & 0xff) | (_useMask ? 0x80 : 0);
header[1] = (message_size & 0xff) | 0x80;
if (_useMask)
{
header[2] = masking_key[0];
header[3] = masking_key[1];
header[4] = masking_key[2];
header[5] = masking_key[3];
}
header[2] = masking_key[0];
header[3] = masking_key[1];
header[4] = masking_key[2];
header[5] = masking_key[3];
}
else if (message_size < 65536)
{
header[1] = 126 | (_useMask ? 0x80 : 0);
header[1] = 126 | 0x80;
header[2] = (message_size >> 8) & 0xff;
header[3] = (message_size >> 0) & 0xff;
if (_useMask)
{
header[4] = masking_key[0];
header[5] = masking_key[1];
header[6] = masking_key[2];
header[7] = masking_key[3];
}
header[4] = masking_key[0];
header[5] = masking_key[1];
header[6] = masking_key[2];
header[7] = masking_key[3];
}
else
{ // TODO: run coverage testing here
header[1] = 127 | (_useMask ? 0x80 : 0);
header[1] = 127 | 0x80;
header[2] = (message_size >> 56) & 0xff;
header[3] = (message_size >> 48) & 0xff;
header[4] = (message_size >> 40) & 0xff;
@@ -880,13 +703,10 @@ namespace ix
header[8] = (message_size >> 8) & 0xff;
header[9] = (message_size >> 0) & 0xff;
if (_useMask)
{
header[10] = masking_key[0];
header[11] = masking_key[1];
header[12] = masking_key[2];
header[13] = masking_key[3];
}
header[10] = masking_key[0];
header[11] = masking_key[1];
header[12] = masking_key[2];
header[13] = masking_key[3];
}
// _txbuf will keep growing until it can be transmitted over the socket:
@@ -900,15 +720,7 @@ namespace ix
WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
{
bool compress = false;
WebSocketSendInfo info = sendData(wsheader_type::PING, message, compress);
if (info.success)
{
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
return info;
return sendData(wsheader_type::PING, message, compress);
}
WebSocketSendInfo WebSocketTransport::sendBinary(
@@ -920,15 +732,6 @@ namespace ix
_enablePerMessageDeflate, onProgressCallback);
}
WebSocketSendInfo WebSocketTransport::sendText(
const std::string& message,
const OnProgressCallback& onProgressCallback)
{
return sendData(wsheader_type::TEXT_FRAME, message,
_enablePerMessageDeflate, onProgressCallback);
}
void WebSocketTransport::sendOnSocket()
{
std::lock_guard<std::mutex> lock(_txbufMutex);
@@ -937,7 +740,8 @@ namespace ix
{
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;
}
@@ -945,7 +749,7 @@ namespace ix
{
_socket->close();
setReadyState(ReadyState::CLOSED);
setReadyState(CLOSED);
break;
}
else
@@ -953,66 +757,35 @@ namespace ix
_txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret);
}
}
std::lock_guard<std::mutex> lck(_lastSendTimePointMutex);
_lastSendTimePoint = std::chrono::steady_clock::now();
}
void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason)
{
bool compress = false;
// if a status is set/was read
if (code != kNoStatusCodeErrorCode)
{
// See list of close events here:
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
std::string closure{(char)(code >> 8), (char)(code & 0xff)};
// copy reason after code
closure.append(reason);
sendData(wsheader_type::CLOSE, closure, compress);
}
else
{
// no close code/reason set
sendData(wsheader_type::CLOSE, "", compress);
}
}
void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
{
_socket->close();
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
_closeCode = code;
_closeReason = reason;
_closeWireSize = closeWireSize;
_closeRemote = remote;
}
setReadyState(ReadyState::CLOSED);
}
void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
void WebSocketTransport::close()
{
_requestInitCancellation = true;
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return;
if (_readyState == CLOSING || _readyState == CLOSED) return;
sendCloseFrame(code, reason);
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
_closeCode = code;
_closeReason = reason;
_closeWireSize = closeWireSize;
_closeRemote = remote;
}
{
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
_closingTimePoint = std::chrono::steady_clock::now();
}
setReadyState(ReadyState::CLOSING);
// See list of close events here:
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
// We use 1000: normal closure.
//
// >>> struct.pack('!H', 1000)
// b'\x03\xe8'
//
const std::string normalClosure = std::string("\x03\xe8");
bool compress = false;
sendData(wsheader_type::CLOSE, normalClosure, compress);
setReadyState(CLOSING);
// wake up the poll, but do not close yet
_socket->wakeUpFromPoll(Socket::kSendRequest);
_socket->wakeUpFromPoll(Socket::kCloseRequest);
_socket->close();
_closeCode = 1000;
_closeReason = "Normal Closure";
setReadyState(CLOSED);
}
size_t WebSocketTransport::bufferedAmount() const

View File

@@ -30,17 +30,10 @@ namespace ix
{
class Socket;
enum class SendMessageKind
{
Text,
Binary,
Ping
};
class WebSocketTransport
{
public:
enum class ReadyState
enum ReadyStateValues
{
CLOSING,
CLOSED,
@@ -48,7 +41,7 @@ namespace ix
OPEN
};
enum class MessageKind
enum MessageKind
{
MSG,
PING,
@@ -56,51 +49,34 @@ namespace ix
FRAGMENT
};
enum class PollResult
{
Succeeded,
AbnormalClose
};
using OnMessageCallback = std::function<void(const std::string&,
size_t,
bool,
MessageKind)>;
using OnCloseCallback = std::function<void(uint16_t,
const std::string&,
size_t,
bool)>;
size_t)>;
WebSocketTransport();
~WebSocketTransport();
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
bool enablePong,
int pingIntervalSecs,
int pingTimeoutSecs);
int heartBeatPeriod);
WebSocketInitResult connectToUrl(const std::string& url, // Client
int timeoutSecs);
WebSocketInitResult connectToSocket(int fd, // Server
int timeoutSecs);
PollResult poll();
void poll();
WebSocketSendInfo sendBinary(const std::string& message,
const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendText(const std::string& message,
const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendPing(const std::string& message);
void close(uint16_t code = 1000,
const std::string& reason = "Normal closure",
size_t closeWireSize = 0,
bool remote = false);
ReadyState getReadyState() const;
void setReadyState(ReadyState readyState);
void close();
ReadyStateValues getReadyState() const;
void setReadyState(ReadyStateValues readyStateValue);
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
void dispatch(PollResult pollResult,
const OnMessageCallback& onMessageCallback);
void dispatch(const OnMessageCallback& onMessageCallback);
size_t bufferedAmount() const;
private:
@@ -113,21 +89,17 @@ namespace ix
bool mask;
enum opcode_type {
CONTINUATION = 0x0,
TEXT_FRAME = 0x1,
TEXT_FRAME = 0x1,
BINARY_FRAME = 0x2,
CLOSE = 8,
PING = 9,
PONG = 0xa,
CLOSE = 8,
PING = 9,
PONG = 0xa,
} opcode;
int N0;
uint64_t N;
uint8_t masking_key[4];
};
// Tells whether we should mask the data we send.
// client should mask but server should not
std::atomic<bool> _useMask;
// Buffer for reading from our socket. That buffer is never resized.
std::vector<uint8_t> _readbuf;
@@ -153,13 +125,12 @@ namespace ix
std::shared_ptr<Socket> _socket;
// Hold the state of the connection (OPEN, CLOSED, etc...)
std::atomic<ReadyState> _readyState;
std::atomic<ReadyStateValues> _readyState;
OnCloseCallback _onCloseCallback;
uint16_t _closeCode;
std::string _closeReason;
size_t _closeWireSize;
bool _closeRemote;
mutable std::mutex _closeDataMutex;
// Data used for Per Message Deflate compression (with zlib)
@@ -169,63 +140,16 @@ namespace ix
// Used to cancel dns lookup + socket connect + http upgrade
std::atomic<bool> _requestInitCancellation;
mutable std::mutex _closingTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock>_closingTimePoint;
static const int kClosingMaximumWaitingDelayInMs;
// Constants for dealing with closing conneections
static const uint16_t kInternalErrorCode;
static const uint16_t kAbnormalCloseCode;
static const uint16_t kProtocolErrorCode;
static const uint16_t kNoStatusCodeErrorCode;
static const std::string kInternalErrorMessage;
static const std::string kAbnormalCloseMessage;
static const std::string kPingTimeoutMessage;
static const std::string kProtocolErrorMessage;
static const std::string kNoStatusCodeErrorMessage;
// Optional Heartbeat
int _heartBeatPeriod;
static const int kDefaultHeartBeatPeriod;
const static std::string kHeartBeatPingMessage;
mutable std::mutex _lastSendTimePointMutex;
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
// enable auto response to ping
std::atomic<bool> _enablePong;
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;
// Record time step for ping/ ping timeout to ensure we wait for the right left duration
std::chrono::time_point<std::chrono::steady_clock> _nextGCDTimePoint;
// 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();
// after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion
bool closingDelayExceeded();
void sendCloseFrame(uint16_t code, const std::string& reason);
void closeSocketAndSwitchToClosedState(uint16_t code,
const std::string& reason,
size_t closeWireSize,
bool remote);
// No data was send through the socket for longer than the heartbeat period
bool heartBeatPeriodExceeded();
void sendOnSocket();
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
@@ -250,6 +174,7 @@ namespace ix
std::string::const_iterator end,
uint64_t message_size,
uint8_t masking_key[4]);
void appendToSendBuffer(const std::vector<uint8_t>& buffer);
unsigned getRandomUnsigned();
void unmaskReceiveBuffer(const wsheader_type& ws);

View File

@@ -1,263 +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 );
}
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

@@ -4,41 +4,13 @@
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "../IXSetThreadName.h"
#include <Windows.h>
#include <iostream>
namespace ix
{
const DWORD MS_VC_EXCEPTION = 0x406D1388;
#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)
void SetThreadName(DWORD dwThreadID, const char* threadName)
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = threadName;
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
__try
{
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)& info);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
}
void setThreadName(const std::string& name)
{
SetThreadName(-1, name.c_str());
// FIXME
std::cerr << "setThreadName not implemented on Windows yet" << std::endl;
}
}

View File

@@ -5,38 +5,28 @@ all: 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:
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 .. ; make -j install)
uninstall:
xargs rm -fv < build/install_manifest.txt
mkdir -p build && (cd build ; cmake .. ; make -j install)
.PHONY: docker
NAME := bsergean/ws
TAG := $(shell cat DOCKER_VERSION)
IMG := ${NAME}:${TAG}
LATEST := ${NAME}:latest
BUILD := ${NAME}:build
docker:
docker build -t ${IMG} .
docker tag ${IMG} ${BUILD}
docker_push:
docker tag ${IMG} ${LATEST}
docker push ${LATEST}
docker build -t ws:latest .
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
trail:
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
# a builtin C++ server started in the unittest now
test_server:
@@ -46,7 +36,7 @@ test_server:
# env TEST=Websocket_chat make test
# env TEST=heartbeat make test
test:
python2.7 test/run.py
python test/run.py
ws_test: all
(cd ws ; bash test_ws.sh)
@@ -60,7 +50,7 @@ rebase_upstream:
install_cmake_for_linux:
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: build

View File

@@ -11,8 +11,6 @@ find_package(Sanitizers)
set (CMAKE_CXX_STANDARD 14)
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)
endif()
@@ -21,8 +19,6 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
include_directories(
${PROJECT_SOURCE_DIR}/Catch2/single_include
../third_party/msgpack11
../third_party/spdlog/include
../ws
)
# Shared sources
@@ -30,22 +26,18 @@ set (SOURCES
test_runner.cpp
IXTest.cpp
../third_party/msgpack11/msgpack11.cpp
../ws/ixcore/utils/IXCoreLogger.cpp
IXDNSLookupTest.cpp
IXSocketTest.cpp
IXSocketConnectTest.cpp
IXWebSocketServerTest.cpp
IXWebSocketPingTest.cpp
IXWebSocketTestConnectionDisconnection.cpp
IXUrlParserTest.cpp
)
# Some unittest don't work on windows yet
if (NOT WIN32)
list(APPEND SOURCES
IXWebSocketPingTimeoutTest.cpp
list(APPEND SOURCES
IXWebSocketServerTest.cpp
IXWebSocketHeartBeatTest.cpp
cmd_websocket_chat.cpp
IXWebSocketTestConnectionDisconnection.cpp
)
endif()

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +0,0 @@
/*
* IXSocketConnectTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
#include "catch.hpp"
#include "IXTest.h"
#include <ixwebsocket/IXSocketConnect.h>
#include <iostream>
using namespace ix;
TEST_CASE("socket_connect", "[net]")
{
SECTION("Test connecting to a known hostname")
{
std::string errMsg;
int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(fd != -1);
}
SECTION("Test connecting to a non-existing hostname")
{
std::string errMsg;
std::string hostname("12313lhjlkjhopiupoijlkasdckljqwehrlkqjwehraospidcuaposidcasdc");
int fd = SocketConnect::connect(hostname, 80, errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(fd == -1);
}
SECTION("Test connecting to a good hostname, with cancellation")
{
std::string errMsg;
// The callback returning true means we are requesting cancellation
int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return true; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(fd == -1);
}
}

View File

@@ -73,7 +73,7 @@ TEST_CASE("socket", "[socket]")
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")
{
std::string errMsg;

View File

@@ -16,9 +16,6 @@
#include <iostream>
#include <stdlib.h>
#include <stack>
#include <iomanip>
#include <random>
namespace ix
{
@@ -72,12 +69,10 @@ namespace ix
Logger() << msg;
}
int getAnyFreePortRandom()
int getAnyFreePortSimple()
{
std::random_device rd;
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
return dist(rd);
static int defaultPort = 8090;
return defaultPort++;
}
int getAnyFreePort()
@@ -87,7 +82,7 @@ namespace ix
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
log("Cannot compute a free port. socket error.");
return getAnyFreePortRandom();
return defaultPort;
}
int enable = 1;
@@ -95,7 +90,7 @@ namespace ix
(char*) &enable, sizeof(enable)) < 0)
{
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.
@@ -108,22 +103,22 @@ namespace ix
{
log("Cannot compute a free port. bind error.");
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
::close(sockfd);
return defaultPort;
}
struct sockaddr_in sa; // server address information
socklen_t len = sizeof(sa);
unsigned int len;
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
{
log("Cannot compute a free port. getsockname error.");
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
::close(sockfd);
return defaultPort;
}
int port = ntohs(sa.sin_port);
Socket::closeSocket(sockfd);
::close(sockfd);
return port;
}
@@ -134,7 +129,7 @@ namespace ix
{
#if defined(__has_feature)
# if __has_feature(address_sanitizer)
int port = getAnyFreePortRandom();
int port = getAnyFreePortSimple();
# else
int port = getAnyFreePort();
# endif
@@ -153,21 +148,4 @@ namespace ix
return -1;
}
void hexDump(const std::string& prefix,
const std::string& s)
{
std::ostringstream ss;
bool upper_case = false;
for (std::string::size_type i = 0; i < s.length(); ++i)
{
ss << std::hex
<< std::setfill('0')
<< std::setw(2)
<< (upper_case ? std::uppercase : std::nouppercase) << (int)s[i];
}
std::cout << prefix << ": " << s << " => " << ss.str() << std::endl;
}
}

View File

@@ -27,6 +27,15 @@ namespace ix
struct Logger
{
public:
Logger& operator<<(const std::string& msg)
{
std::lock_guard<std::mutex> lock(_mutex);
std::cerr << msg;
std::cerr << std::endl;
return *this;
}
template <typename T>
Logger& operator<<(T const& obj)
{
@@ -43,5 +52,6 @@ namespace ix
void log(const std::string& msg);
bool computeFreePorts(int count);
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

@@ -0,0 +1,224 @@
/*
* IXWebSocketHeartBeatTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <queue>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXTest.h"
#include "catch.hpp"
using namespace ix;
namespace
{
class WebSocketClient
{
public:
WebSocketClient(int port);
void subscribe(const std::string& channel);
void start();
void stop();
bool isReady() const;
void sendMessage(const std::string& text);
private:
ix::WebSocket _webSocket;
int _port;
};
WebSocketClient::WebSocketClient(int port)
: _port(port)
{
;
}
bool WebSocketClient::isReady() const
{
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
}
void WebSocketClient::stop()
{
_webSocket.stop();
}
void WebSocketClient::start()
{
std::string url;
{
std::stringstream ss;
ss << "ws://localhost:"
<< _port
<< "/";
url = ss.str();
}
_webSocket.setUrl(url);
// The important bit for this test.
// Set a 1 second heartbeat ; if no traffic is present on the connection for 1 second
// a ping message will be sent by the client.
_webSocket.setHeartBeatPeriod(1);
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
_webSocket.setOnMessageCallback(
[](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");
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("client disconnected");
}
else if (messageType == ix::WebSocket_MessageType_Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Pong)
{
ss << "Received pong message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Ping)
{
ss << "Received ping message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
ss << "Received message " << str;
log(ss.str());
}
else
{
ss << "Invalid ix::WebSocketMessageType";
log(ss.str());
}
});
_webSocket.start();
}
void WebSocketClient::sendMessage(const std::string& text)
{
_webSocket.send(text);
}
bool startServer(ix::WebSocketServer& server, std::atomic<int>& receivedPingMessages)
{
// 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++;
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
log(res.second);
return false;
}
server.start();
return true;
}
}
TEST_CASE("Websocket_heartbeat", "[heartbeat]")
{
SECTION("Make sure that ping messages are sent during heartbeat.")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
std::atomic<int> serverReceivedPingMessages(0);
REQUIRE(startServer(server, serverReceivedPingMessages));
std::string session = ix::generateSessionId();
WebSocketClient webSocketClientA(port);
WebSocketClient webSocketClientB(port);
webSocketClientA.start();
webSocketClientB.start();
// Wait for all chat instance to be ready
while (true)
{
if (webSocketClientA.isReady() && webSocketClientB.isReady()) break;
ix::msleep(10);
}
REQUIRE(server.getClients().size() == 2);
ix::msleep(900);
webSocketClientB.sendMessage("hello world");
ix::msleep(900);
webSocketClientB.sendMessage("hello world");
ix::msleep(900);
webSocketClientA.stop();
webSocketClientB.stop();
REQUIRE(serverReceivedPingMessages >= 2);
REQUIRE(serverReceivedPingMessages <= 4);
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}

View File

@@ -1,481 +0,0 @@
/*
* IXWebSocketPingTest.cpp
* Author: Benjamin Sergeant
* 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, bool useHeartBeatMethod);
void start();
void stop();
bool isReady() const;
void sendMessage(const std::string& text);
private:
ix::WebSocket _webSocket;
int _port;
bool _useHeartBeatMethod;
};
WebSocketClient::WebSocketClient(int port, bool useHeartBeatMethod)
: _port(port),
_useHeartBeatMethod(useHeartBeatMethod)
{
;
}
bool WebSocketClient::isReady() const
{
return _webSocket.getReadyState() == ix::ReadyState::Open;
}
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 1 second heartbeat with the setter method to test
if (_useHeartBeatMethod)
{
_webSocket.setHeartBeatPeriod(1);
}
else
{
_webSocket.setPingInterval(1);
}
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
_webSocket.setOnMessageCallback(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocketMessageType::Open)
{
log("client connected");
}
else if (messageType == ix::WebSocketMessageType::Close)
{
log("client disconnected");
}
else if (messageType == ix::WebSocketMessageType::Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Pong)
{
ss << "Received pong message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Ping)
{
ss << "Received ping message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Message)
{
// too many messages to log
}
else
{
ss << "Invalid ix::WebSocketMessageType";
log(ss.str());
}
});
_webSocket.start();
}
void WebSocketClient::sendMessage(const std::string& text)
{
_webSocket.send(text);
}
bool startServer(ix::WebSocketServer& server, std::atomic<int>& receivedPingMessages)
{
// 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::WebSocketMessageType::Open)
{
Logger() << "New server connection";
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocketMessageType::Close)
{
log("Server closed connection");
}
else if (messageType == ix::WebSocketMessageType::Ping)
{
log("Server received a ping");
receivedPingMessages++;
}
else if (messageType == ix::WebSocketMessageType::Message)
{
// to many messages to log
for(auto client: server.getClients())
{
client->sendText("reply");
}
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
log(res.second);
return false;
}
server.start();
return true;
}
}
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();
// 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
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_ping_data_sent_setPingInterval_half_full", "[setPingInterval]")
{
SECTION("Make sure that ping messages are sent, even if other messages are sent continuously during a given time")
{
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);
// send continuously for 1100ms
auto now = std::chrono::steady_clock::now();
while(std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(900))
{
webSocketClient.sendMessage("message");
ix::msleep(1);
}
ix::msleep(150);
// Here we test ping interval
// client has sent data, but ping should have been sent no matter what
// -> expected ping messages == 1, as 900+150 = 1050ms, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 1);
ix::msleep(100);
webSocketClient.stop();
// 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_full", "[setPingInterval]")
{
SECTION("Make sure that ping messages are sent, even if other messages are sent continuously for longer than ping interval")
{
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(1);
}
REQUIRE(server.getClients().size() == 1);
// send continuously for 1100ms
auto now = std::chrono::steady_clock::now();
while(std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(1100))
{
webSocketClient.sendMessage("message");
ix::msleep(1);
}
// Here we test ping interval
// client has sent data, but ping should have been sent no matter what
// -> expected ping messages == 1, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 1);
ix::msleep(100);
webSocketClient.stop();
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
// Using setHeartBeatPeriod
TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
{
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 = true;
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
webSocketClient.start();
// Wait for all chat instance to be ready
while (true)
{
if (webSocketClient.isReady()) break;
ix::msleep(1);
}
REQUIRE(server.getClients().size() == 1);
ix::msleep(1850);
webSocketClient.stop();
// Here we test ping interval
// -> expected ping messages == 1 as 1850 seconds, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 1);
// 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_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(1);
}
REQUIRE(server.getClients().size() == 1);
ix::msleep(900);
webSocketClient.sendMessage("hello world");
ix::msleep(900);
webSocketClient.sendMessage("hello world");
ix::msleep(900);
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 == 2 as 900+900+900 = 2700 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();
}
}

View File

@@ -1,485 +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::ReadyState::Open;
}
bool WebSocketClient::isClosed() const
{
return _webSocket.getReadyState() == ix::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);
_webSocket.disableAutomaticReconnection();
// 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::WebSocketMessageType::Open)
{
log("client connected");
}
else if (messageType == ix::WebSocketMessageType::Close)
{
log("client disconnected");
if (closeInfo.code == 1011)
{
_closedDueToPingTimeout = true;
}
}
else if (messageType == ix::WebSocketMessageType::Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Pong)
{
_receivedPongMessages++;
ss << "Received pong message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Ping)
{
ss << "Received ping message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Message)
{
ss << "Received message " << str;
log(ss.str());
}
else
{
ss << "Invalid ix::WebSocketMessageType";
log(ss.str());
}
});
_webSocket.start();
}
void WebSocketClient::sendMessage(const std::string& text)
{
_webSocket.send(text);
}
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::WebSocketMessageType::Open)
{
Logger() << "New server connection";
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocketMessageType::Close)
{
log("Server closed connection");
}
else if (messageType == ix::WebSocketMessageType::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(2700);
// 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(400);
// 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

@@ -39,7 +39,7 @@ namespace ix
server.setOnConnectionCallback(
[&server, &connectionId](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState,
@@ -50,10 +50,11 @@ namespace ix
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocketMessageType::Open)
if (messageType == ix::WebSocket_MessageType_Open)
{
Logger() << "New connection";
connectionState->computeId();
Logger() << "New connection";
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
@@ -64,11 +65,11 @@ namespace ix
connectionId = connectionState->getId();
}
else if (messageType == ix::WebSocketMessageType::Close)
else if (messageType == ix::WebSocket_MessageType_Close)
{
Logger() << "Closed connection";
}
else if (messageType == ix::WebSocketMessageType::Message)
else if (messageType == ix::WebSocket_MessageType_Message)
{
for (auto&& client : server.getClients())
{
@@ -107,7 +108,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg;
bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("127.0.0.1");
std::string host("localhost");
auto isCancellationRequested = []() -> bool
{
return false;
@@ -141,7 +142,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg;
bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("127.0.0.1");
std::string host("localhost");
auto isCancellationRequested = []() -> bool
{
return false;
@@ -178,7 +179,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
std::string errMsg;
bool tls = false;
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
std::string host("127.0.0.1");
std::string host("localhost");
auto isCancellationRequested = []() -> bool
{
return false;
@@ -204,8 +205,9 @@ TEST_CASE("Websocket_server", "[websocket_server]")
// Give us 500ms for the server to notice that clients went away
ix::msleep(500);
server.stop();
REQUIRE(connectionId == "foobarConnectionId");
server.stop();
REQUIRE(server.getClients().size() == 0);
}
}

View File

@@ -60,36 +60,30 @@ namespace
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocketMessageType::Open)
if (messageType == ix::WebSocket_MessageType_Open)
{
log("cmd_websocket_satori_chat: connected !");
}
else if (messageType == ix::WebSocketMessageType::Close)
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("cmd_websocket_satori_chat: disconnected !");
}
else if (messageType == ix::WebSocketMessageType::Error)
else if (messageType == ix::WebSocket_MessageType_Error)
{
ss << "cmd_websocket_satori_chat: Error! ";
ss << error.reason;
log(ss.str());
log("cmd_websocket_satori_chat: Error!");
}
else if (messageType == ix::WebSocketMessageType::Message)
else if (messageType == ix::WebSocket_MessageType_Message)
{
log("cmd_websocket_satori_chat: received message.!");
}
else if (messageType == ix::WebSocketMessageType::Ping)
else if (messageType == ix::WebSocket_MessageType_Ping)
{
log("cmd_websocket_satori_chat: received ping message.!");
}
else if (messageType == ix::WebSocketMessageType::Pong)
else if (messageType == ix::WebSocket_MessageType_Pong)
{
log("cmd_websocket_satori_chat: received pong message.!");
}
else if (messageType == ix::WebSocketMessageType::Fragment)
{
log("cmd_websocket_satori_chat: received fragment.!");
}
else
{
log("Invalid ix::WebSocketMessageType");
@@ -109,40 +103,26 @@ TEST_CASE("websocket_connections", "[websocket]")
{
SECTION("Try to connect to invalid servers.")
{
IXWebSocketTestConnectionDisconnection test;
IXWebSocketTestConnectionDisconnection chatA;
test.start(GOOGLE_URL);
chatA.start(GOOGLE_URL);
ix::msleep(1000);
test.stop();
chatA.stop();
test.start(UNKNOWN_URL);
chatA.start(UNKNOWN_URL);
ix::msleep(1000);
test.stop();
chatA.stop();
}
SECTION("Try to connect and disconnect with different timing, not enough time to succesfully connect")
SECTION("Try to connect and disconnect with different timing.")
{
IXWebSocketTestConnectionDisconnection test;
IXWebSocketTestConnectionDisconnection chatA;
for (int i = 0; i < 50; ++i)
{
log(std::string("Run: ") + std::to_string(i));
test.start(WEBSOCKET_DOT_ORG_URL);
chatA.start(WEBSOCKET_DOT_ORG_URL);
ix::msleep(i);
test.stop();
chatA.stop();
}
}
// This test breaks on travis CI - Ubuntu Xenial + gcc + tsan
// We should fix this.
/*SECTION("Try to connect and disconnect with different timing, from not enough time to successfull connect")
{
IXWebSocketTestConnectionDisconnection test;
for (int i = 0; i < 20; ++i)
{
log(std::string("Run: ") + std::to_string(i));
test.start(WEBSOCKET_DOT_ORG_URL);
ix::msleep(i*50);
test.stop();
}
}*/
}

View File

@@ -87,7 +87,7 @@ namespace
bool WebSocketChat::isReady() const
{
return _webSocket.getReadyState() == ix::ReadyState::Open;
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
}
void WebSocketChat::stop()
@@ -100,7 +100,7 @@ namespace
std::string url;
{
std::stringstream ss;
ss << "ws://127.0.0.1:"
ss << "ws://localhost:"
<< _port
<< "/"
<< _user;
@@ -122,21 +122,21 @@ namespace
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocketMessageType::Open)
if (messageType == ix::WebSocket_MessageType_Open)
{
ss << "cmd_websocket_chat: user "
<< _user
<< " Connected !";
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Close)
else if (messageType == ix::WebSocket_MessageType_Close)
{
ss << "cmd_websocket_chat: user "
<< _user
<< " disconnected !";
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Message)
else if (messageType == ix::WebSocket_MessageType_Message)
{
auto result = decodeMessage(str);
@@ -159,20 +159,20 @@ namespace
<< _user << " > ";
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Error)
else if (messageType == ix::WebSocket_MessageType_Error)
{
ss << "cmd_websocket_chat: Error ! " << error.reason;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Ping)
else if (messageType == ix::WebSocket_MessageType_Ping)
{
log("cmd_websocket_chat: received ping message");
}
else if (messageType == ix::WebSocketMessageType::Pong)
else if (messageType == ix::WebSocket_MessageType_Pong)
{
log("cmd_websocket_chat: received pong message");
}
else if (messageType == ix::WebSocketMessageType::Fragment)
else if (messageType == ix::WebSocket_MessageType_Fragment)
{
log("cmd_websocket_chat: received message fragment");
}
@@ -228,7 +228,7 @@ namespace
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocketMessageType::Open)
if (messageType == ix::WebSocket_MessageType_Open)
{
Logger() << "New connection";
Logger() << "id: " << connectionState->getId();
@@ -239,11 +239,11 @@ namespace
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocketMessageType::Close)
else if (messageType == ix::WebSocket_MessageType_Close)
{
log("Closed connection");
}
else if (messageType == ix::WebSocketMessageType::Message)
else if (messageType == ix::WebSocket_MessageType_Message)
{
for (auto&& client : server.getClients())
{

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 sys
import platform
import argparse
import multiprocessing
import tempfile
import time
import datetime
import threading
import shutil
import subprocess
import re
import xml.etree.ElementTree as ET
from xml.dom import minidom
hasClick = True
try:
import click
except ImportError:
hasClick = False
DEFAULT_EXE = 'ixwebsocket_unittest'
import threading
class Command(object):
@@ -42,16 +16,12 @@ class Command(object):
self.cmd = cmd
self.process = None
def run_command(self):
def run_command(self, capture = False):
self.process = subprocess.Popen(self.cmd, shell=True)
self.process.communicate()
def run(self, timeout=None):
def run(self, timeout = 5 * 60):
'''5 minutes default timeout'''
if timeout is None:
timeout = 5 * 60
thread = threading.Thread(target=self.run_command, args=())
thread.start()
thread.join(timeout)
@@ -65,454 +35,85 @@ class Command(object):
return True, self.process.returncode
def runCommand(cmd, assertOnFailure=True, timeout=None):
'''Small wrapper to run a command and make sure it succeed'''
if timeout is None:
timeout = 30 * 60 # 30 minute default timeout
print('\nRunning', cmd)
command = Command(cmd)
timedout, ret = command.run(timeout)
if timedout:
print('Unittest timed out')
msg = 'cmd {} failed with error code {}'.format(cmd, ret)
if ret != 0:
print(msg)
if assertOnFailure:
assert False
def runCMake(sanitizer, buildDir):
'''Generate a makefile from CMake.
We do an out of dir build, so that cleaning up is easy
(remove build sub-folder).
'''
# CMake installed via Self Service ends up here.
cmake_executable = '/Applications/CMake.app/Contents/bin/cmake'
if not os.path.exists(cmake_executable):
cmake_executable = 'cmake'
sanitizersFlags = {
'asan': '-DSANITIZE_ADDRESS=On',
'ubsan': '-DSANITIZE_UNDEFINED=On',
'tsan': '-DSANITIZE_THREAD=On',
'none': ''
}
sanitizerFlag = sanitizersFlags.get(sanitizer, '')
# CMake installed via Self Service ends up here.
cmakeExecutable = '/Applications/CMake.app/Contents/bin/cmake'
if not os.path.exists(cmakeExecutable):
cmakeExecutable = 'cmake'
generator = '"Unix Makefiles"'
if platform.system() == 'Windows':
generator = '"NMake Makefiles"'
fmt = '''
{cmakeExecutable} -H. \
{sanitizerFlag} \
-B{buildDir} \
-DCMAKE_BUILD_TYPE=Debug \
-DUSE_TLS=1 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-G{generator}
'''
cmakeCmd = fmt.format(**locals())
runCommand(cmakeCmd)
def runTest(args, buildDir, xmlOutput, testRunName):
'''Execute the unittest.
'''
if args is None:
args = ''
fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"'
testCommand = fmt.format(**locals())
runCommand(testCommand,
assertOnFailure=False)
def validateTestSuite(xmlOutput):
'''
Parse the output XML file to validate that all tests passed.
Assume that the XML file contains only one testsuite.
(which is true when generate by catch2)
'''
tree = ET.parse(xmlOutput)
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()
osName = platform.system()
print('os name = {}'.format(osName))
root = os.path.dirname(os.path.realpath(__file__))
buildDir = os.path.join(root, 'build', osName)
if not os.path.exists(buildDir):
os.makedirs(buildDir)
os.chdir(buildDir)
if osName == 'Windows':
generator = '-G"NMake Makefiles"'
make = 'nmake'
testBinary ='ixwebsocket_unittest.exe'
else:
generator = ''
make = 'make -j6'
testBinary ='./ixwebsocket_unittest'
sanitizersFlags = {
'asan': '-DSANITIZE_ADDRESS=On',
'ubsan': '-DSANITIZE_UNDEFINED=On',
'tsan': '-DSANITIZE_THREAD=On',
'none': ''
}
sanitizer = 'tsan'
if osName == 'Linux':
sanitizer = 'none'
sanitizerFlags = sanitizersFlags[sanitizer]
# if osName == 'Windows':
# os.environ['CC'] = 'clang-cl'
# os.environ['CXX'] = 'clang-cl'
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ../..'.format(generator, sanitizerFlags)
print(cmakeCmd)
ret = os.system(cmakeCmd)
assert ret == 0, 'CMake failed, exiting'
ret = os.system(make)
assert ret == 0, 'Make failed, exiting'
def findFiles(prefix):
'''Find all files under a given directory'''
paths = []
for root, _, files in os.walk(prefix):
for path in files:
fullPath = os.path.join(root, path)
if os.path.islink(fullPath):
continue
paths.append(fullPath)
return paths
#for path in findFiles('.'):
# print(path)
# We need to copy the zlib DLL in the current work directory
shutil.copy(os.path.join(
'..',
'..',
'..',
'third_party',
'ZLIB-Windows',
'zlib-1.2.11_deploy_v140',
'release_dynamic',
'x64',
'bin',
'zlib.dll'), '.')
# lldb = "lldb --batch -o 'run' -k 'thread backtrace all' -k 'quit 1'"
lldb = "" # Disabled for now
testCommand = '{} {} {}'.format(lldb, testBinary, os.getenv('TEST', ''))
command = Command(testCommand)
timedout, ret = command.run()
assert ret == 0, 'Test command failed'

View File

@@ -7,23 +7,10 @@
#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
#include <spdlog/spdlog.h>
#include <ixwebsocket/IXNetSystem.h>
#include <ixcore/utils/IXCoreLogger.h>
#include <ixwebsocket/IXSocket.h>
int main(int argc, char* argv[])
{
ix::initNetSystem();
ix::IXCoreLogger::LogFunc logFunc = [](const char* msg)
{
spdlog::info(msg);
};
ix::IXCoreLogger::setLogFunction(logFunc);
int result = Catch::Session().run(argc, argv);
ix::uninitNetSystem();
return result;
}

View File

@@ -1,3 +0,0 @@
# Note
Except *zlib* on Windows, all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.

View File

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

View File

@@ -7,6 +7,8 @@
#ifndef 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,

File diff suppressed because it is too large Load Diff

View File

@@ -119,62 +119,62 @@ static void verify_length(size_t len)
}
static void dump(NullStruct, std::string &out) {
out.push_back((char) 0xc0);
out.push_back(0xc0);
}
static void dump(float value, std::string &out) {
out.push_back((char) 0xca);
out.push_back(0xca);
dump_data(value, out);
}
static void dump(double value, std::string &out) {
out.push_back((char) 0xcb);
out.push_back(0xcb);
dump_data(value, out);
}
static void dump(int8_t value, std::string &out) {
if( value < -32 )
{
out.push_back((char) 0xd0);
out.push_back(0xd0);
}
out.push_back(value);
}
static void dump(int16_t value, std::string &out) {
out.push_back((char) 0xd1);
out.push_back(0xd1);
dump_data(value, out);
}
static void dump(int32_t value, std::string &out) {
out.push_back((char) 0xd2);
out.push_back(0xd2);
dump_data(value, out);
}
static void dump(int64_t value, std::string &out) {
out.push_back((char) 0xd3);
out.push_back(0xd3);
dump_data(value, out);
}
static void dump(uint8_t value, std::string &out) {
if(128 <= value)
{
out.push_back((char) 0xcc);
out.push_back(0xcc);
}
out.push_back(value);
}
static void dump(uint16_t value, std::string &out) {
out.push_back((char) 0xcd);
out.push_back(0xcd);
dump_data(value, out);
}
static void dump(uint32_t value, std::string &out) {
out.push_back((char) 0xce);
out.push_back(0xce);
dump_data(value, out);
}
static void dump(uint64_t value, std::string &out) {
out.push_back((char) 0xcf);
out.push_back(0xcf);
dump_data(value, out);
}
@@ -194,19 +194,19 @@ static void dump(const std::string& value, std::string &out) {
else if(len <= 0xff)
{
uint8_t const length = static_cast<uint8_t>(len);
out.push_back((char) 0xd9);
out.push_back(0xd9);
out.push_back(length);
}
else if(len <= 0xffff)
{
uint16_t const length = static_cast<uint16_t>(len);
out.push_back((char) 0xda);
out.push_back(0xda);
dump_data(length, out);
}
else
{
uint32_t const length = static_cast<uint32_t>(len);
out.push_back((char) 0xdb);
out.push_back(0xdb);
dump_data(length, out);
}
@@ -226,13 +226,13 @@ static void dump(const MsgPack::array& value, std::string &out) {
else if(len <= 0xffff)
{
uint16_t const length = static_cast<uint16_t>(len);
out.push_back((char) 0xdc);
out.push_back(0xdc);
dump_data(length, out);
}
else
{
uint32_t const length = static_cast<uint32_t>(len);
out.push_back((char) 0xdd);
out.push_back(0xdd);
dump_data(length, out);
}
@@ -252,13 +252,13 @@ static void dump(const MsgPack::object& value, std::string &out) {
else if(len <= 0xffff)
{
uint16_t const length = static_cast<uint16_t>(len);
out.push_back((char) 0xde);
out.push_back(0xde);
dump_data(length, out);
}
else
{
uint32_t const length = static_cast<uint32_t>(len);
out.push_back((char) 0xdf);
out.push_back(0xdf);
dump_data(length, out);
}
@@ -274,19 +274,19 @@ static void dump(const MsgPack::binary& value, std::string &out) {
if(len <= 0xff)
{
uint8_t const length = static_cast<uint8_t>(len);
out.push_back((char) 0xc4);
out.push_back(0xc4);
dump_data(length, out);
}
else if(len <= 0xffff)
{
uint16_t const length = static_cast<uint16_t>(len);
out.push_back((char) 0xc5);
out.push_back(0xc5);
dump_data(length, out);
}
else
{
uint32_t const length = static_cast<uint32_t>(len);
out.push_back((char) 0xc6);
out.push_back(0xc6);
dump_data(length, out);
}
@@ -302,33 +302,33 @@ static void dump(const MsgPack::extension& value, std::string &out) {
verify_length(len);
if(len == 0x01) {
out.push_back((char) 0xd4);
out.push_back(0xd4);
}
else if(len == 0x02) {
out.push_back((char) 0xd5);
out.push_back(0xd5);
}
else if(len == 0x04) {
out.push_back((char) 0xd6);
out.push_back(0xd6);
}
else if(len == 0x08) {
out.push_back((char) 0xd7);
out.push_back(0xd7);
}
else if(len == 0x10) {
out.push_back((char) 0xd8);
out.push_back(0xd8);
}
else if(len <= 0xff) {
uint8_t const length = static_cast<uint8_t>(len);
out.push_back((char) 0xc7);
out.push_back(0xc7);
out.push_back(length);
}
else if(len <= 0xffff) {
uint16_t const length = static_cast<uint16_t>(len);
out.push_back((char) 0xc8);
out.push_back(0xc8);
dump_data(length, out);
}
else {
uint32_t const length = static_cast<uint32_t>(len);
out.push_back((char) 0xc9);
out.push_back(0xc9);
dump_data(length, out);
}

View File

@@ -1,108 +0,0 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 140
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
...

View File

@@ -1,28 +0,0 @@
Checks: 'modernize-*,modernize-use-override,google-*,-google-runtime-references,misc-*,clang-analyzer-*'
WarningsAsErrors: ''
HeaderFilterRegex: 'async.h|async_logger.h|common.h|details|formatter.h|logger.h|sinks|spdlog.h|tweakme.h|version.h'
AnalyzeTemporaryDtors: false
FormatStyle: none
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'

View File

@@ -1,68 +0,0 @@
# Auto generated files
build/*
*.slo
*.lo
*.o
*.obj
*.suo
*.tlog
*.ilk
*.log
*.pdb
*.idb
*.iobj
*.ipdb
*.opensdf
*.sdf
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Codelite
.codelite
# .orig files
*.orig
# example files
example/*
!example/example.cpp
!example/bench.cpp
!example/utils.h
!example/Makefile*
!example/example.sln
!example/example.vcxproj
!example/CMakeLists.txt
!example/multisink.cpp
!example/jni
# generated files
generated
# Cmake
CMakeCache.txt
CMakeFiles
CMakeScripts
Makefile
cmake_install.cmake
install_manifest.txt
/tests/tests.VC.VC.opendb
/tests/tests.VC.db
/tests/tests
/tests/logs/*
# idea
.idea/

View File

@@ -1,116 +0,0 @@
# Adapted from various sources, including:
# - Louis Dionne's Hana: https://github.com/ldionne/hana
# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit
# - Eric Niebler's range-v3: https://github.com/ericniebler/range-v3
sudo: required
language: cpp
addons: &gcc48
apt:
packages:
- g++-4.8
sources:
- ubuntu-toolchain-r-test
addons: &gcc7
apt:
packages:
- g++-7
sources:
- ubuntu-toolchain-r-test
addons: &clang35
apt:
packages:
- clang-3.5
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.5
addons: &clang6
apt:
packages:
- clang-6.0
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-6.0
matrix:
include:
# Test gcc-4.8: C++11, Build=Debug/Release
- env: GCC_VERSION=4.8 BUILD_TYPE=Debug CPP=11
os: linux
addons: *gcc48
- env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11
os: linux
addons: *gcc48
- env: GCC_VERSION=7 BUILD_TYPE=Release CPP=11
os: linux
addons: *gcc7
# Test clang-3.5: C++11, Build=Debug/Release
- env: CLANG_VERSION=3.5 BUILD_TYPE=Debug CPP=11
os: linux
addons: *clang35
- env: CLANG_VERSION=3.5 BUILD_TYPE=Release CPP=11
os: linux
addons: *clang35
# Test clang-6.0: C++11, Build=Debug, ASAN=On
- env: CLANG_VERSION=6.0 BUILD_TYPE=Debug CPP=11 ASAN=On TSAN=Off
os: linux
addons: *clang6
- env: CLANG_VERSION=6.0 BUILD_TYPE=Release CPP=11 ASAN=On TSAN=Off
os: linux
addons: *clang6
# Test clang-6.0: C++11, Build=Debug, TSAN=On
- env: CLANG_VERSION=6.0 BUILD_TYPE=Debug CPP=11 ASAN=Off TSAN=On
os: linux
addons: *clang6
- env: CLANG_VERSION=6.0 BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=On
os: linux
addons: *clang6
# osx
- env: BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=Off
os: osx
before_script:
- if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
- if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CXX="clang++" CC="clang"; fi
- which $CXX
- which $CC
- $CXX --version
- cmake --version
script:
- cd ${TRAVIS_BUILD_DIR}
- mkdir -p build && cd build
- |
cmake .. \
--warn-uninitialized \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DCMAKE_CXX_STANDARD=$CPP \
-DSPDLOG_BUILD_EXAMPLES=ON \
-DSPDLOG_BUILD_BENCH=OFF \
-DSPDLOG_BUILD_TESTS=ON \
-DSPDLOG_SANITIZE_ADDRESS=$ASAN \
-DSPDLOG_SANITIZE_THREAD=$TSAN
- make VERBOSE=1 -j2
- ctest -j2 --output-on-failure
notifications:
email: false

View File

@@ -1,157 +0,0 @@
#
# Copyright(c) 2015 Ruslan Baratov.
# Distributed under the MIT License (http://opensource.org/licenses/MIT)
#
cmake_minimum_required(VERSION 3.1)
project(spdlog VERSION 1.3.1 LANGUAGES CXX)
include(CMakeDependentOption)
include(GNUInstallDirs)
#---------------------------------------------------------------------------------------
# set default build to release
#---------------------------------------------------------------------------------------
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
endif()
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
#---------------------------------------------------------------------------------------
# compiler config
#---------------------------------------------------------------------------------------
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
add_compile_options("-Wall")
add_compile_options("-Wextra")
add_compile_options("-Wconversion")
add_compile_options("-pedantic")
add_compile_options("-Wfatal-errors")
endif()
#---------------------------------------------------------------------------------------
# address sanitizers check
#---------------------------------------------------------------------------------------
include(cmake/sanitizers.cmake)
#---------------------------------------------------------------------------------------
# spdlog target
#---------------------------------------------------------------------------------------
add_library(spdlog INTERFACE)
add_library(spdlog::spdlog ALIAS spdlog)
# Check if spdlog is being used directly or via add_subdirectory
set(SPDLOG_MASTER_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(SPDLOG_MASTER_PROJECT ON)
endif()
option(SPDLOG_BUILD_EXAMPLES "Build examples" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_BUILD_BENCH "Build benchmarks" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_BUILD_TESTS "Build tests" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_FMT_EXTERNAL "Use external fmt library instead of bundled" OFF)
if(SPDLOG_FMT_EXTERNAL)
find_package(fmt REQUIRED CONFIG)
endif()
target_include_directories(
spdlog
INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
if(SPDLOG_FMT_EXTERNAL)
target_compile_definitions(spdlog INTERFACE SPDLOG_FMT_EXTERNAL)
target_link_libraries(spdlog INTERFACE fmt::fmt)
endif()
set(HEADER_BASE "${CMAKE_CURRENT_SOURCE_DIR}/include")
if(SPDLOG_BUILD_EXAMPLES)
add_subdirectory(example)
endif()
if(SPDLOG_BUILD_TESTS)
include(CTest)
add_subdirectory(tests)
endif()
if(SPDLOG_BUILD_BENCH)
add_subdirectory(bench)
endif()
#---------------------------------------------------------------------------------------
# Install/export targets and files
#---------------------------------------------------------------------------------------
# set files and directories
set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
set(include_install_dir "${CMAKE_INSTALL_INCLUDEDIR}")
set(pkgconfig_install_dir "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
set(version_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
set(project_config "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake")
set(targets_config "${PROJECT_NAME}Targets.cmake")
set(pkg_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc")
set(targets_export_name "${PROJECT_NAME}Targets")
set(namespace "${PROJECT_NAME}::")
# generate package version file
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${version_config}" COMPATIBILITY SameMajorVersion
)
# configure pkg config file
configure_file("cmake/spdlog.pc.in" "${pkg_config}" @ONLY)
# configure spdlogConfig.cmake file
configure_file("cmake/Config.cmake.in" "${project_config}" @ONLY)
# install targets
install(
TARGETS spdlog
EXPORT "${targets_export_name}"
)
# install headers
install(
DIRECTORY "${HEADER_BASE}/${PROJECT_NAME}"
DESTINATION "${include_install_dir}"
)
# install project config and version file
install(
FILES "${project_config}" "${version_config}"
DESTINATION "${config_install_dir}"
)
# install pkg config file
install(
FILES "${pkg_config}"
DESTINATION "${pkgconfig_install_dir}"
)
# install targets config file
install(
EXPORT "${targets_export_name}"
NAMESPACE "${namespace}"
DESTINATION "${config_install_dir}"
FILE ${targets_config}
)
# export build directory targets file
export(
EXPORT ${targets_export_name}
NAMESPACE "${namespace}"
FILE ${targets_config}
)
# register project in CMake user registry
export(PACKAGE ${PROJECT_NAME})
file(GLOB_RECURSE spdlog_include_SRCS "${HEADER_BASE}/*.h")
add_custom_target(spdlog_headers_for_ide SOURCES ${spdlog_include_SRCS})

View File

@@ -1,13 +0,0 @@
spdlog is header only library.
Just copy the files to your build tree and use a C++11 compiler
Tested on:
gcc 4.8.1 and above
clang 3.5
Visual Studio 2013
gcc 4.8 flags: --std==c++11 -pthread -O3 -flto -Wl,--no-as-needed
gcc 4.9 flags: --std=c++11 -pthread -O3 -flto
see the makefile in the example folder

View File

@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Gabi Melman.
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.

View File

@@ -1,319 +0,0 @@
# spdlog
Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.org/gabime/spdlog.svg?branch=master)](https://travis-ci.org/gabime/spdlog)&nbsp; [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog)
## Install
#### Just copy the headers:
* Copy the source [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler.
#### Or use your favorite package manager:
* Ubuntu: `apt-get install libspdlog-dev`
* Homebrew: `brew install spdlog`
* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean`
* Fedora: `yum install spdlog`
* Gentoo: `emerge dev-libs/spdlog`
* Arch Linux: `yaourt -S spdlog-git`
* vcpkg: `vcpkg install spdlog`
## Platforms
* Linux, FreeBSD, OpenBSD, Solaris, AIX
* Windows (msvc 2013+, cygwin)
* macOS (clang 3.5+)
* Android
## Features
* Very fast (see [benchmarks](#benchmarks) below).
* Headers only, just copy and use.
* Feature rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library.
* Fast asynchronous mode (optional)
* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting.
* Multi/Single threaded loggers.
* Various log targets:
* Rotating log files.
* Daily log files.
* Console logging (colors supported).
* syslog.
* Windows debugger (```OutputDebugString(..)```)
* Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface).
* Severity based filtering - threshold levels can be modified in runtime as well as in compile time.
* Binary data logging.
## Benchmarks
Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz
#### Synchronous mode
```
*******************************************************************************
Single thread, 1,000,000 iterations
*******************************************************************************
basic_st... Elapsed: 0.181652 5,505,042/sec
rotating_st... Elapsed: 0.181781 5,501,117/sec
daily_st... Elapsed: 0.187595 5,330,630/sec
null_st... Elapsed: 0.0504704 19,813,602/sec
*******************************************************************************
10 threads sharing same logger, 1,000,000 iterations
*******************************************************************************
basic_mt... Elapsed: 0.616035 1,623,284/sec
rotating_mt... Elapsed: 0.620344 1,612,008/sec
daily_mt... Elapsed: 0.648353 1,542,369/sec
null_mt... Elapsed: 0.151972 6,580,166/sec
```
#### Asynchronous mode
```
*******************************************************************************
10 threads sharing same logger, 1,000,000 iterations
*******************************************************************************
async... Elapsed: 0.350066 2,856,606/sec
async... Elapsed: 0.314865 3,175,960/sec
async... Elapsed: 0.349851 2,858,358/sec
```
## Usage samples
#### Basic usage
```c++
#include "spdlog/spdlog.h"
int main()
{
spdlog::info("Welcome to spdlog!");
spdlog::error("Some error message with arg: {}", 1);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned");
spdlog::set_level(spdlog::level::debug); // Set global log level to debug
spdlog::debug("This message should be displayed..");
// change log pattern
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
// Compile time log levels
// define SPDLOG_ACTIVE_LEVEL to desired level
SPDLOG_TRACE("Some trace message with param {}", {});
SPDLOG_DEBUG("Some debug message");
}
```
#### create stdout/stderr logger object
```c++
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
void stdout_example()
{
// create color multi threaded logger
auto console = spdlog::stdout_color_mt("console");
auto err_logger = spdlog::stderr_color_mt("stderr");
spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
}
```
---
#### Basic file logger
```c++
#include "spdlog/sinks/basic_file_sink.h"
void basic_logfile_example()
{
try
{
auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt");
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
```
---
#### Rotating files
```c++
#include "spdlog/sinks/rotating_file_sink.h"
void rotating_example()
{
// Create a file rotating logger with 5mb size max and 3 rotated files
auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3);
}
```
---
#### Daily files
```c++
#include "spdlog/sinks/daily_file_sink.h"
void daily_example()
{
// Create a daily logger - a new file is created every day on 2:30am
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
}
```
---
#### Cloning loggers
```c++
// clone a logger and give it new name.
// Useful for creating subsystem loggers from some "root" logger
void clone_example()
{
auto network_logger = spdlog::get("root")->clone("network");
network_logger->info("Logging network stuff..");
}
```
---
#### Periodic flush
```c++
// periodically flush all *registered* loggers every 3 seconds:
// warning: only use if all your loggers are thread safe!
spdlog::flush_every(std::chrono::seconds(3));
```
---
#### Binary logging
```c++
// log binary data as hex.
// many types of std::container<char> types can be used.
// ranges are supported too.
// format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.
#include "spdlog/fmt/bin_to_hex.h"
void binary_example()
{
auto console = spdlog::get("console");
std::array<char, 80> buf;
console->info("Binary example: {}", spdlog::to_hex(buf));
console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
// more examples:
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}
```
---
#### Logger with multi sinks - each with different format and log level
```c++
// create logger with 2 targets with different log levels and formats.
// the console will show only warnings or errors, while the file will log all.
void multi_sink_example()
{
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::warn);
console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
file_sink->set_level(spdlog::level::trace);
spdlog::logger logger("multi_sink", {console_sink, file_sink});
logger.set_level(spdlog::level::debug);
logger.warn("this should appear in both console and file");
logger.info("this message should not appear in the console, only in the file");
}
```
---
#### Asynchronous logging
```c++
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
void async_example()
{
// default thread pool settings can be modified *before* creating the async logger:
// spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread.
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");
// alternatively:
// auto async_file = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt");
}
```
---
#### Asynchronous logger with multi sinks
```c++
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"
void multi_sink_example2()
{
spdlog::init_thread_pool(8192, 1);
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt >();
auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("mylog.txt", 1024*1024*10, 3);
std::vector<spdlog::sink_ptr> sinks {stdout_sink, rotating_sink};
auto logger = std::make_shared<spdlog::async_logger>("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block);
spdlog::register_logger(logger);
}
```
---
#### User defined types
```c++
// user defined types logging by implementing operator<<
#include "spdlog/fmt/ostr.h" // must be included
struct my_type
{
int i;
template<typename OStream>
friend OStream &operator<<(OStream &os, const my_type &c)
{
return os << "[my_type i=" << c.i << "]";
}
};
void user_defined_example()
{
spdlog::get("console")->info("user defined type: {}", my_type{14});
}
```
---
#### Custom error handler
```c++
void err_handler_example()
{
// can be set globally or per logger(logger->set_error_handler(..))
spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); });
spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3);
}
```
---
#### syslog
```c++
#include "spdlog/sinks/syslog_sink.h"
void syslog_example()
{
std::string ident = "spdlog-example";
auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID);
syslog_logger->warn("This is warning that will end up in syslog.");
}
```
---
#### Android example
```c++
#include "spdlog/sinks/android_sink.h"
void android_example()
{
std::string tag = "spdlog-android";
auto android_logger = spdlog::android_logger("android", tag);
android_logger->critical("Use \"adb shell logcat\" to view this message.");
}
```
## Documentation
Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages.

View File

@@ -1,34 +0,0 @@
version: 1.0.{build}
image: Visual Studio 2015
environment:
matrix:
- GENERATOR: '"MinGW Makefiles"'
BUILD_TYPE: Debug
- GENERATOR: '"MinGW Makefiles"'
BUILD_TYPE: Release
- GENERATOR: '"Visual Studio 14 2015"'
BUILD_TYPE: Debug
- GENERATOR: '"Visual Studio 14 2015"'
BUILD_TYPE: Release
- GENERATOR: '"Visual Studio 14 2015 Win64"'
BUILD_TYPE: Debug
- GENERATOR: '"Visual Studio 14 2015 Win64"'
BUILD_TYPE: Release
build_script:
- cmd: >-
set
mkdir build
cd build
set PATH=%PATH:C:\Program Files\Git\usr\bin;=%
set PATH=C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin;%PATH%
cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DSPDLOG_BUILD_BENCH=OFF
cmake --build . --config %BUILD_TYPE%
test_script:
- ctest -VV -C "%BUILD_TYPE%"

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