Compare commits
43 Commits
v1.3.0
...
bug/30_ser
Author | SHA1 | Date | |
---|---|---|---|
b1c1e6e28d | |||
66440e2330 | |||
792610d44f | |||
bcf2fc1812 | |||
935e6791a3 | |||
fbb7c012a3 | |||
dac18fcabf | |||
d8e83caffc | |||
fbf80b9f50 | |||
c2a9139d41 | |||
6e3dff149a | |||
1bacbe38f4 | |||
2e9c610ac9 | |||
eb063ec60a | |||
37fb14646d | |||
ae543518d3 | |||
c865d64608 | |||
3004422cb6 | |||
0c46a17443 | |||
497373d976 | |||
91198aca0d | |||
b17a5e5f0b | |||
3f0ef59f65 | |||
1e96edc293 | |||
0afb77393b | |||
7614b642bb | |||
bc89580dfe | |||
358ae13a88 | |||
ccf9dcba70 | |||
94604fad61 | |||
5c4cc7c50d | |||
9ed961ec06 | |||
e6bd8cc8c4 | |||
ee25bd0f92 | |||
e77b9176f3 | |||
afe8b966ad | |||
310724c961 | |||
ceba8ae620 | |||
fead661ab7 | |||
9c8c17f577 | |||
a04f83930f | |||
c421d19800 | |||
521f02c90e |
@ -15,6 +15,10 @@ if (NOT WIN32)
|
|||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshorten-64-to-32")
|
||||||
|
endif()
|
||||||
|
|
||||||
set( IXWEBSOCKET_SOURCES
|
set( IXWEBSOCKET_SOURCES
|
||||||
ixwebsocket/IXSocket.cpp
|
ixwebsocket/IXSocket.cpp
|
||||||
ixwebsocket/IXSocketServer.cpp
|
ixwebsocket/IXSocketServer.cpp
|
||||||
@ -33,8 +37,8 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXHttpClient.cpp
|
ixwebsocket/IXHttpClient.cpp
|
||||||
ixwebsocket/IXUrlParser.cpp
|
ixwebsocket/IXUrlParser.cpp
|
||||||
ixwebsocket/IXSelectInterrupt.cpp
|
ixwebsocket/IXSelectInterrupt.cpp
|
||||||
ixwebsocket/IXSelectInterruptPipe.cpp
|
|
||||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||||
|
ixwebsocket/IXConnectionState.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set( IXWEBSOCKET_HEADERS
|
set( IXWEBSOCKET_HEADERS
|
||||||
@ -60,10 +64,16 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXHttpClient.h
|
ixwebsocket/IXHttpClient.h
|
||||||
ixwebsocket/IXUrlParser.h
|
ixwebsocket/IXUrlParser.h
|
||||||
ixwebsocket/IXSelectInterrupt.h
|
ixwebsocket/IXSelectInterrupt.h
|
||||||
ixwebsocket/IXSelectInterruptPipe.h
|
|
||||||
ixwebsocket/IXSelectInterruptFactory.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
|
# Platform specific code
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
|
||||||
@ -75,6 +85,7 @@ else()
|
|||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(USE_OPEN_SSL FALSE)
|
||||||
if (USE_TLS)
|
if (USE_TLS)
|
||||||
add_definitions(-DIXWEBSOCKET_USE_TLS)
|
add_definitions(-DIXWEBSOCKET_USE_TLS)
|
||||||
|
|
||||||
@ -85,6 +96,7 @@ if (USE_TLS)
|
|||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
|
||||||
else()
|
else()
|
||||||
|
set(USE_OPEN_SSL TRUE)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
||||||
endif()
|
endif()
|
||||||
@ -95,14 +107,16 @@ add_library( ixwebsocket STATIC
|
|||||||
${IXWEBSOCKET_HEADERS}
|
${IXWEBSOCKET_HEADERS}
|
||||||
)
|
)
|
||||||
|
|
||||||
# gcc/Linux needs -pthread
|
if (APPLE AND USE_TLS)
|
||||||
find_package(Threads)
|
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(USE_OPEN_SSL)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
add_definitions(${OPENSSL_DEFINITIONS})
|
add_definitions(${OPENSSL_DEFINITIONS})
|
||||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||||
|
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
@ -117,15 +131,21 @@ if (WIN32)
|
|||||||
|
|
||||||
target_link_libraries(ixwebsocket libz wsock32 ws2_32)
|
target_link_libraries(ixwebsocket libz wsock32 ws2_32)
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||||
|
|
||||||
else()
|
else()
|
||||||
|
# gcc/Linux needs -pthread
|
||||||
|
find_package(Threads)
|
||||||
|
|
||||||
target_link_libraries(ixwebsocket
|
target_link_libraries(ixwebsocket
|
||||||
z ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
z ${CMAKE_THREAD_LIBS_INIT})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set( IXWEBSOCKET_INCLUDE_DIRS
|
set( IXWEBSOCKET_INCLUDE_DIRS
|
||||||
.
|
.
|
||||||
../../shared/OpenSSL/include)
|
)
|
||||||
|
|
||||||
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
|
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
|
||||||
|
|
||||||
add_subdirectory(ws)
|
if (NOT WIN32)
|
||||||
|
add_subdirectory(ws)
|
||||||
|
endif()
|
||||||
|
1
DOCKER_VERSION
Normal file
1
DOCKER_VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
1.4.0
|
52
Dockerfile
52
Dockerfile
@ -1,31 +1,47 @@
|
|||||||
FROM debian:stretch
|
# Build time
|
||||||
|
FROM debian:buster as build
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
RUN apt-get update
|
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 g++
|
||||||
RUN apt-get -y install libssl-dev
|
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 libz-dev
|
||||||
RUN apt-get -y install vim
|
|
||||||
RUN apt-get -y install make
|
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 . .
|
COPY . .
|
||||||
|
|
||||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-rc4-Linux-x86_64/bin
|
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||||
|
|
||||||
# RUN ["make"]
|
RUN ["make"]
|
||||||
|
|
||||||
EXPOSE 8765
|
# Runtime
|
||||||
CMD ["/ws/ws", "transfer", "--port", "8765", "--host", "0.0.0.0"]
|
FROM debian:buster as runtime
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt-get update
|
||||||
|
# Runtime
|
||||||
|
RUN apt-get install -y libssl1.1
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
RUN apt-get install -y strace
|
||||||
|
RUN apt-get install -y gdb
|
||||||
|
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
|
||||||
|
|
||||||
|
CMD ["ws"]
|
||||||
|
33
README.md
33
README.md
@ -4,13 +4,14 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
|
[*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.
|
||||||
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
|
* macOS
|
||||||
* iOS
|
* iOS
|
||||||
* Linux
|
* Linux
|
||||||
* Android
|
* Android
|
||||||
|
|
||||||
|
The code was made to compile once on Windows but support is currently broken on this platform.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@ -46,9 +47,12 @@ webSocket.setOnMessageCallback(
|
|||||||
// Now that our callback is setup, we can start our background thread and receive messages
|
// Now that our callback is setup, we can start our background thread and receive messages
|
||||||
webSocket.start();
|
webSocket.start();
|
||||||
|
|
||||||
// Send a message to the server
|
// Send a message to the server (default to BINARY mode)
|
||||||
webSocket.send("hello world");
|
webSocket.send("hello world");
|
||||||
|
|
||||||
|
// The message can be sent in TEXT mode
|
||||||
|
webSocket.sendText("hello again");
|
||||||
|
|
||||||
// ... finally ...
|
// ... finally ...
|
||||||
|
|
||||||
// Stop the connection
|
// Stop the connection
|
||||||
@ -63,10 +67,11 @@ Here is what the server API looks like. Note that server support is very recent
|
|||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server](std::shared_ptr<WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -77,6 +82,12 @@ server.setOnConnectionCallback(
|
|||||||
{
|
{
|
||||||
std::cerr << "New connection" << std::endl;
|
std::cerr << "New connection" << std::endl;
|
||||||
|
|
||||||
|
// A connection state object is available, and has a default id
|
||||||
|
// You can subclass ConnectionState and pass an alternate factory
|
||||||
|
// to override it. It is useful if you want to store custom
|
||||||
|
// attributes per connection (authenticated bool flag, attributes, etc...)
|
||||||
|
std::cerr << "id: " << connectionState->getId() << std::endl;
|
||||||
|
|
||||||
// The uri the client did connect to.
|
// The uri the client did connect to.
|
||||||
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
||||||
|
|
||||||
@ -183,7 +194,7 @@ There is a Dockerfile for running some code on Linux, and a unittest which can b
|
|||||||
You can build and install the ws command line tool with Homebrew.
|
You can build and install the ws command line tool with Homebrew.
|
||||||
|
|
||||||
```
|
```
|
||||||
brew create --cmake https://github.com/machinezone/IXWebSocket/archive/v1.1.0.tar.gz
|
brew tap bsergean/IXWebSocket
|
||||||
brew install IXWebSocket
|
brew install IXWebSocket
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -207,11 +218,11 @@ If the remote end (server) breaks the connection, the code will try to perpetual
|
|||||||
|
|
||||||
### Large messages
|
### 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 500M 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 1G were sent and received succesfully.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
|
* No utf-8 validation is made when sending TEXT message with sendText()
|
||||||
* 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.
|
* 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.
|
* 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.
|
||||||
|
|
||||||
@ -223,13 +234,13 @@ Here is a simplistic diagram which explains how the code is structured in term o
|
|||||||
+-----------------------+ --- Public
|
+-----------------------+ --- Public
|
||||||
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
|
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
|
||||||
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
|
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
|
||||||
| |
|
| |
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
| |
|
| |
|
||||||
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
|
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
|
||||||
| | Each connection is handled in a new OS thread.
|
| | Each connection is handled in a new OS thread.
|
||||||
| |
|
| |
|
||||||
+-----------------------+ --- Private
|
+-----------------------+ --- Private
|
||||||
| |
|
| |
|
||||||
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
|
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
|
||||||
| |
|
| |
|
||||||
|
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
ws:
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
image: bsergean/ws:build
|
||||||
|
ports:
|
||||||
|
- "8765:8765"
|
||||||
|
entrypoint: bash
|
||||||
|
networks:
|
||||||
|
- ws-net
|
||||||
|
depends_on:
|
||||||
|
- redis1
|
||||||
|
|
||||||
|
redis1:
|
||||||
|
image: redis:alpine
|
||||||
|
networks:
|
||||||
|
- ws-net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ws-net:
|
43
ixwebsocket/IXConnectionState.cpp
Normal file
43
ixwebsocket/IXConnectionState.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* IXConnectionState.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXConnectionState.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
std::atomic<uint64_t> ConnectionState::_globalId(0);
|
||||||
|
|
||||||
|
ConnectionState::ConnectionState() : _terminated(false)
|
||||||
|
{
|
||||||
|
computeId();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectionState::computeId()
|
||||||
|
{
|
||||||
|
_id = std::to_string(_globalId++);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& ConnectionState::getId() const
|
||||||
|
{
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
|
||||||
|
{
|
||||||
|
return std::make_shared<ConnectionState>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConnectionState::isTerminated() const
|
||||||
|
{
|
||||||
|
return _terminated;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConnectionState::setTerminated()
|
||||||
|
{
|
||||||
|
_terminated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
37
ixwebsocket/IXConnectionState.h
Normal file
37
ixwebsocket/IXConnectionState.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* IXConnectionState.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class ConnectionState {
|
||||||
|
public:
|
||||||
|
ConnectionState();
|
||||||
|
virtual ~ConnectionState() = default;
|
||||||
|
|
||||||
|
virtual void computeId();
|
||||||
|
virtual const std::string& getId() const;
|
||||||
|
|
||||||
|
bool setTerminated();
|
||||||
|
bool isTerminated() const;
|
||||||
|
|
||||||
|
static std::shared_ptr<ConnectionState> createConnectionState();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::atomic<bool> _terminated;
|
||||||
|
std::string _id;
|
||||||
|
|
||||||
|
static std::atomic<uint64_t> _globalId;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ namespace ix
|
|||||||
errMsg = "no error";
|
errMsg = "no error";
|
||||||
|
|
||||||
// Maybe a cancellation request got in before the background thread terminated ?
|
// Maybe a cancellation request got in before the background thread terminated ?
|
||||||
if (isCancellationRequested())
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
{
|
{
|
||||||
errMsg = "cancellation requested";
|
errMsg = "cancellation requested";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -121,7 +121,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Were we cancelled ?
|
// Were we cancelled ?
|
||||||
if (isCancellationRequested())
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
{
|
{
|
||||||
errMsg = "cancellation requested";
|
errMsg = "cancellation requested";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -129,7 +129,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Maybe a cancellation request got in before the bg terminated ?
|
// Maybe a cancellation request got in before the bg terminated ?
|
||||||
if (isCancellationRequested())
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
{
|
{
|
||||||
errMsg = "cancellation requested";
|
errMsg = "cancellation requested";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -56,7 +56,7 @@ namespace ix
|
|||||||
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
|
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "SelectInterruptPipe::init() failed in fcntl() call"
|
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
|
||||||
<< " : " << strerror(errno);
|
<< " : " << strerror(errno);
|
||||||
errorMsg = ss.str();
|
errorMsg = ss.str();
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ namespace ix
|
|||||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
|
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "SelectInterruptPipe::init() failed in fcntl() call"
|
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
|
||||||
<< " : " << strerror(errno);
|
<< " : " << strerror(errno);
|
||||||
errorMsg = ss.str();
|
errorMsg = ss.str();
|
||||||
|
|
||||||
@ -77,13 +77,31 @@ namespace ix
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
#ifdef F_SETNOSIGPIPE
|
||||||
// FIXME: on macOS we should configure the pipe to not trigger SIGPIPE
|
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
|
||||||
// on reads/writes to a closed fd
|
{
|
||||||
//
|
std::stringstream ss;
|
||||||
// The generation of the SIGPIPE signal can be suppressed using the
|
ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call"
|
||||||
// F_SETNOSIGPIPE fcntl command.
|
<< " : " << strerror(errno);
|
||||||
//
|
errorMsg = ss.str();
|
||||||
|
|
||||||
|
_fildes[kPipeReadIndex] = -1;
|
||||||
|
_fildes[kPipeWriteIndex] = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call"
|
||||||
|
<< " : " << strerror(errno);
|
||||||
|
errorMsg = ss.str();
|
||||||
|
|
||||||
|
_fildes[kPipeReadIndex] = -1;
|
||||||
|
_fildes[kPipeWriteIndex] = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -104,6 +122,7 @@ namespace ix
|
|||||||
|
|
||||||
uint64_t value = 0;
|
uint64_t value = 0;
|
||||||
::read(fd, &value, sizeof(value));
|
::read(fd, &value, sizeof(value));
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
|
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
|
||||||
@ -45,16 +49,16 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (_sockfd == -1)
|
if (_sockfd == -1)
|
||||||
{
|
{
|
||||||
if (onPollCallback) onPollCallback(PollResultType_Error);
|
if (onPollCallback) onPollCallback(PollResultType::Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PollResultType pollResult = isReadyToRead(timeoutSecs, 0);
|
PollResultType pollResult = isReadyToRead(1000 * timeoutSecs);
|
||||||
|
|
||||||
if (onPollCallback) onPollCallback(pollResult);
|
if (onPollCallback) onPollCallback(pollResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
PollResultType Socket::select(bool readyToRead, int timeoutSecs, int timeoutMs)
|
PollResultType Socket::select(bool readyToRead, int timeoutMs)
|
||||||
{
|
{
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
fd_set wfds;
|
fd_set wfds;
|
||||||
@ -62,7 +66,6 @@ namespace ix
|
|||||||
FD_ZERO(&wfds);
|
FD_ZERO(&wfds);
|
||||||
|
|
||||||
fd_set* fds = (readyToRead) ? &rfds : & wfds;
|
fd_set* fds = (readyToRead) ? &rfds : & wfds;
|
||||||
|
|
||||||
FD_SET(_sockfd, fds);
|
FD_SET(_sockfd, fds);
|
||||||
|
|
||||||
// File descriptor used to interrupt select when needed
|
// File descriptor used to interrupt select when needed
|
||||||
@ -73,64 +76,60 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
timeout.tv_sec = timeoutSecs;
|
timeout.tv_sec = timeoutMs / 1000;
|
||||||
timeout.tv_usec = 1000 * timeoutMs;
|
timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
|
||||||
|
|
||||||
// Compute the highest fd.
|
// Compute the highest fd.
|
||||||
int sockfd = _sockfd;
|
int sockfd = _sockfd;
|
||||||
int nfds = (std::max)(sockfd, interruptFd);
|
int nfds = (std::max)(sockfd, interruptFd);
|
||||||
|
|
||||||
int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
|
int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
|
||||||
(timeoutSecs < 0) ? nullptr : &timeout);
|
(timeoutMs < 0) ? nullptr : &timeout);
|
||||||
|
|
||||||
PollResultType pollResult = PollResultType_ReadyForRead;
|
PollResultType pollResult = PollResultType::ReadyForRead;
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
pollResult = PollResultType_Error;
|
pollResult = PollResultType::Error;
|
||||||
}
|
}
|
||||||
else if (ret == 0)
|
else if (ret == 0)
|
||||||
{
|
{
|
||||||
pollResult = PollResultType_Timeout;
|
pollResult = PollResultType::Timeout;
|
||||||
}
|
}
|
||||||
else if (interruptFd != -1 && FD_ISSET(interruptFd, fds))
|
else if (interruptFd != -1 && FD_ISSET(interruptFd, &rfds))
|
||||||
{
|
{
|
||||||
uint64_t value = _selectInterrupt->read();
|
uint64_t value = _selectInterrupt->read();
|
||||||
|
|
||||||
if (value == kSendRequest)
|
if (value == kSendRequest)
|
||||||
{
|
{
|
||||||
pollResult = PollResultType_SendRequest;
|
pollResult = PollResultType::SendRequest;
|
||||||
}
|
}
|
||||||
else if (value == kCloseRequest)
|
else if (value == kCloseRequest)
|
||||||
{
|
{
|
||||||
pollResult = PollResultType_CloseRequest;
|
pollResult = PollResultType::CloseRequest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (sockfd != -1 && FD_ISSET(sockfd, fds))
|
else if (sockfd != -1 && readyToRead && FD_ISSET(sockfd, &rfds))
|
||||||
{
|
{
|
||||||
if (readyToRead)
|
pollResult = PollResultType::ReadyForRead;
|
||||||
{
|
}
|
||||||
pollResult = PollResultType_ReadyForRead;
|
else if (sockfd != -1 && !readyToRead && FD_ISSET(sockfd, &wfds))
|
||||||
}
|
{
|
||||||
else
|
pollResult = PollResultType::ReadyForWrite;
|
||||||
{
|
|
||||||
pollResult = PollResultType_ReadyForWrite;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return pollResult;
|
return pollResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
PollResultType Socket::isReadyToRead(int timeoutSecs, int timeoutMs)
|
PollResultType Socket::isReadyToRead(int timeoutMs)
|
||||||
{
|
{
|
||||||
bool readyToRead = true;
|
bool readyToRead = true;
|
||||||
return select(readyToRead, timeoutSecs, timeoutMs);
|
return select(readyToRead, timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
PollResultType Socket::isReadyToWrite(int timeoutSecs, int timeoutMs)
|
PollResultType Socket::isReadyToWrite(int timeoutMs)
|
||||||
{
|
{
|
||||||
bool readyToRead = false;
|
bool readyToRead = false;
|
||||||
return select(readyToRead, timeoutSecs, timeoutMs);
|
return select(readyToRead, timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wake up from poll/select by writing to the pipe which is watched by select
|
// Wake up from poll/select by writing to the pipe which is watched by select
|
||||||
@ -215,7 +214,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (isCancellationRequested()) return false;
|
if (isCancellationRequested && isCancellationRequested()) return false;
|
||||||
|
|
||||||
char* buffer = const_cast<char*>(str.c_str());
|
char* buffer = const_cast<char*>(str.c_str());
|
||||||
int len = (int) str.size();
|
int len = (int) str.size();
|
||||||
@ -227,7 +226,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
return ret == len;
|
return ret == len;
|
||||||
}
|
}
|
||||||
// There is possibly something to be write, try again
|
// There is possibly something to be writen, try again
|
||||||
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
|
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
|
||||||
getErrno() == EAGAIN))
|
getErrno() == EAGAIN))
|
||||||
{
|
{
|
||||||
@ -246,7 +245,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (isCancellationRequested()) return false;
|
if (isCancellationRequested && isCancellationRequested()) return false;
|
||||||
|
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
ret = recv(buffer, 1);
|
ret = recv(buffer, 1);
|
||||||
@ -262,7 +261,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
// Wait with a 1ms timeout until the socket is ready to read.
|
// Wait with a 1ms timeout until the socket is ready to read.
|
||||||
// This way we are not busy looping
|
// This way we are not busy looping
|
||||||
if (isReadyToRead(0, 1) == PollResultType_Error)
|
if (isReadyToRead(1) == PollResultType::Error)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -309,9 +308,12 @@ namespace ix
|
|||||||
std::vector<uint8_t> output;
|
std::vector<uint8_t> output;
|
||||||
while (output.size() != length)
|
while (output.size() != length)
|
||||||
{
|
{
|
||||||
if (isCancellationRequested()) return std::make_pair(false, std::string());
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
|
{
|
||||||
|
return std::make_pair(false, std::string());
|
||||||
|
}
|
||||||
|
|
||||||
int size = std::min(kChunkSize, length - output.size());
|
size_t size = std::min(kChunkSize, length - output.size());
|
||||||
ssize_t ret = recv((char*)&_readBuffer[0], size);
|
ssize_t ret = recv((char*)&_readBuffer[0], size);
|
||||||
|
|
||||||
if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
|
if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
|
||||||
@ -331,7 +333,7 @@ namespace ix
|
|||||||
|
|
||||||
// Wait with a 1ms timeout until the socket is ready to read.
|
// Wait with a 1ms timeout until the socket is ready to read.
|
||||||
// This way we are not busy looping
|
// This way we are not busy looping
|
||||||
if (isReadyToRead(0, 1) == PollResultType_Error)
|
if (isReadyToRead(1) == PollResultType::Error)
|
||||||
{
|
{
|
||||||
return std::make_pair(false, std::string());
|
return std::make_pair(false, std::string());
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,14 @@ namespace ix
|
|||||||
{
|
{
|
||||||
class SelectInterrupt;
|
class SelectInterrupt;
|
||||||
|
|
||||||
enum PollResultType
|
enum class PollResultType
|
||||||
{
|
{
|
||||||
PollResultType_ReadyForRead = 0,
|
ReadyForRead = 0,
|
||||||
PollResultType_ReadyForWrite = 1,
|
ReadyForWrite = 1,
|
||||||
PollResultType_Timeout = 2,
|
Timeout = 2,
|
||||||
PollResultType_Error = 3,
|
Error = 3,
|
||||||
PollResultType_SendRequest = 4,
|
SendRequest = 4,
|
||||||
PollResultType_CloseRequest = 5
|
CloseRequest = 5
|
||||||
};
|
};
|
||||||
|
|
||||||
class Socket {
|
class Socket {
|
||||||
@ -50,9 +50,8 @@ namespace ix
|
|||||||
int timeoutSecs = kDefaultPollTimeout);
|
int timeoutSecs = kDefaultPollTimeout);
|
||||||
bool wakeUpFromPoll(uint8_t wakeUpCode);
|
bool wakeUpFromPoll(uint8_t wakeUpCode);
|
||||||
|
|
||||||
PollResultType select(bool readyToRead, int timeoutSecs, int timeoutMs);
|
PollResultType isReadyToWrite(int timeoutMs);
|
||||||
PollResultType isReadyToWrite(int timeoutSecs, int timeoutMs);
|
PollResultType isReadyToRead(int timeoutMs);
|
||||||
PollResultType isReadyToRead(int timeoutSecs, int timeoutMs);
|
|
||||||
|
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
virtual bool connect(const std::string& url,
|
virtual bool connect(const std::string& url,
|
||||||
@ -92,6 +91,8 @@ namespace ix
|
|||||||
std::mutex _socketMutex;
|
std::mutex _socketMutex;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
PollResultType select(bool readyToRead, int timeoutMs);
|
||||||
|
|
||||||
static const int kDefaultPollTimeout;
|
static const int kDefaultPollTimeout;
|
||||||
static const int kDefaultPollNoTimeout;
|
static const int kDefaultPollNoTimeout;
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ namespace ix
|
|||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (isCancellationRequested()) // Must handle timeout as well
|
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
|
||||||
{
|
{
|
||||||
closeSocket(fd);
|
closeSocket(fd);
|
||||||
errMsg = "Cancelled";
|
errMsg = "Cancelled";
|
||||||
|
@ -6,12 +6,20 @@
|
|||||||
|
|
||||||
#include "IXSocketFactory.h"
|
#include "IXSocketFactory.h"
|
||||||
|
|
||||||
#if defined(__APPLE__) or defined(__linux__)
|
#ifdef IXWEBSOCKET_USE_TLS
|
||||||
|
|
||||||
# ifdef __APPLE__
|
# ifdef __APPLE__
|
||||||
# include <ixwebsocket/IXSocketAppleSSL.h>
|
# include <ixwebsocket/IXSocketAppleSSL.h>
|
||||||
|
# elif defined(_WIN32)
|
||||||
|
# include <ixwebsocket/IXSocketSChannel.h>
|
||||||
# else
|
# else
|
||||||
# include <ixwebsocket/IXSocketOpenSSL.h>
|
# include <ixwebsocket/IXSocketOpenSSL.h>
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -31,6 +39,8 @@ namespace ix
|
|||||||
#ifdef IXWEBSOCKET_USE_TLS
|
#ifdef IXWEBSOCKET_USE_TLS
|
||||||
# ifdef __APPLE__
|
# ifdef __APPLE__
|
||||||
socket = std::make_shared<SocketAppleSSL>();
|
socket = std::make_shared<SocketAppleSSL>();
|
||||||
|
# elif defined(_WIN32)
|
||||||
|
socket = std::make_shared<SocketSChannel>();
|
||||||
# else
|
# else
|
||||||
socket = std::make_shared<SocketOpenSSL>();
|
socket = std::make_shared<SocketOpenSSL>();
|
||||||
# endif
|
# endif
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
||||||
|
std::once_flag SocketOpenSSL::_openSSLInitFlag;
|
||||||
|
|
||||||
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
|
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
|
||||||
_ssl_connection(nullptr),
|
_ssl_connection(nullptr),
|
||||||
|
@ -50,7 +50,7 @@ namespace ix
|
|||||||
const SSL_METHOD* _ssl_method;
|
const SSL_METHOD* _ssl_method;
|
||||||
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
|
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
|
||||||
|
|
||||||
std::once_flag _openSSLInitFlag;
|
static std::once_flag _openSSLInitFlag;
|
||||||
static std::atomic<bool> _openSSLInitializationSuccessful;
|
static std::atomic<bool> _openSSLInitializationSuccessful;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
# include <ws2def.h>
|
# include <ws2def.h>
|
||||||
# include <WS2tcpip.h>
|
# include <WS2tcpip.h>
|
||||||
# include <schannel.h>
|
# include <schannel.h>
|
||||||
# include <sslsock.h>
|
//# include <sslsock.h>
|
||||||
# include <io.h>
|
# include <io.h>
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
@ -75,7 +75,7 @@ namespace ix
|
|||||||
int port,
|
int port,
|
||||||
std::string& errMsg)
|
std::string& errMsg)
|
||||||
{
|
{
|
||||||
return Socket::connect(host, port, errMsg);
|
return Socket::connect(host, port, errMsg, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -89,17 +89,17 @@ namespace ix
|
|||||||
Socket::close();
|
Socket::close();
|
||||||
}
|
}
|
||||||
|
|
||||||
int SocketSChannel::send(char* buf, size_t nbyte)
|
ssize_t SocketSChannel::send(char* buf, size_t nbyte)
|
||||||
{
|
{
|
||||||
return Socket::send(buf, nbyte);
|
return Socket::send(buf, nbyte);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SocketSChannel::send(const std::string& buffer)
|
ssize_t SocketSChannel::send(const std::string& buffer)
|
||||||
{
|
{
|
||||||
return Socket::send(buffer);
|
return Socket::send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SocketSChannel::recv(void* buf, size_t nbyte)
|
ssize_t SocketSChannel::recv(void* buf, size_t nbyte)
|
||||||
{
|
{
|
||||||
return Socket::recv(buf, nbyte);
|
return Socket::recv(buf, nbyte);
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ namespace ix
|
|||||||
// The important override
|
// The important override
|
||||||
virtual void secureSocket() final;
|
virtual void secureSocket() final;
|
||||||
|
|
||||||
virtual int send(char* buffer, size_t length) final;
|
virtual ssize_t send(char* buffer, size_t length) final;
|
||||||
virtual int send(const std::string& buffer) final;
|
virtual ssize_t send(const std::string& buffer) final;
|
||||||
virtual int recv(void* buffer, size_t length) final;
|
virtual ssize_t recv(void* buffer, size_t length) final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
@ -29,7 +29,8 @@ namespace ix
|
|||||||
_host(host),
|
_host(host),
|
||||||
_backlog(backlog),
|
_backlog(backlog),
|
||||||
_maxConnections(maxConnections),
|
_maxConnections(maxConnections),
|
||||||
_stop(false)
|
_stop(false),
|
||||||
|
_connectionStateFactory(&ConnectionState::createConnectionState)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -135,6 +136,9 @@ namespace ix
|
|||||||
|
|
||||||
void SocketServer::stop()
|
void SocketServer::stop()
|
||||||
{
|
{
|
||||||
|
closeTerminatedThreads();
|
||||||
|
assert(_connectionsThreads.empty());
|
||||||
|
|
||||||
if (!_thread.joinable()) return; // nothing to do
|
if (!_thread.joinable()) return; // nothing to do
|
||||||
|
|
||||||
_stop = true;
|
_stop = true;
|
||||||
@ -145,18 +149,50 @@ namespace ix
|
|||||||
::close(_serverFd);
|
::close(_serverFd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SocketServer::setConnectionStateFactory(
|
||||||
|
const ConnectionStateFactory& connectionStateFactory)
|
||||||
|
{
|
||||||
|
_connectionStateFactory = connectionStateFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// join the threads for connections that have been closed
|
||||||
|
void SocketServer::closeTerminatedThreads()
|
||||||
|
{
|
||||||
|
auto it = _connectionsThreads.begin();
|
||||||
|
auto itEnd = _connectionsThreads.end();
|
||||||
|
|
||||||
|
while (it != itEnd)
|
||||||
|
{
|
||||||
|
auto& connectionState = it->first;
|
||||||
|
auto& thread = it->second;
|
||||||
|
|
||||||
|
if (!connectionState->isTerminated() ||
|
||||||
|
!thread.joinable())
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread.join();
|
||||||
|
it = _connectionsThreads.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SocketServer::run()
|
void SocketServer::run()
|
||||||
{
|
{
|
||||||
// Set the socket to non blocking mode, so that accept calls are not blocking
|
// Set the socket to non blocking mode, so that accept calls are not blocking
|
||||||
SocketConnect::configure(_serverFd);
|
SocketConnect::configure(_serverFd);
|
||||||
|
|
||||||
// Return value of std::async, ignored
|
|
||||||
std::future<void> f;
|
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (_stop) return;
|
if (_stop) return;
|
||||||
|
|
||||||
|
// Garbage collection to shutdown/join threads for closed connections.
|
||||||
|
// We could run this in its own thread, so that we dont need to accept
|
||||||
|
// a new connection to close a thread.
|
||||||
|
// We could also use a condition variable to be notify when we need to do this
|
||||||
|
closeTerminatedThreads();
|
||||||
|
|
||||||
// Use select to check whether a new connection is in progress
|
// Use select to check whether a new connection is in progress
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
@ -214,14 +250,19 @@ namespace ix
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ConnectionState> connectionState;
|
||||||
|
if (_connectionStateFactory)
|
||||||
|
{
|
||||||
|
connectionState = _connectionStateFactory();
|
||||||
|
}
|
||||||
|
|
||||||
// Launch the handleConnection work asynchronously in its own thread.
|
// Launch the handleConnection work asynchronously in its own thread.
|
||||||
//
|
_connectionsThreads.push_back(std::make_pair(
|
||||||
// the destructor of a future returned by std::async blocks,
|
connectionState,
|
||||||
// so we need to declare it outside of this loop
|
std::thread(&SocketServer::handleConnection,
|
||||||
f = std::async(std::launch::async,
|
this,
|
||||||
&SocketServer::handleConnection,
|
clientFd,
|
||||||
this,
|
connectionState)));
|
||||||
clientFd);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXConnectionState.h"
|
||||||
|
|
||||||
#include <utility> // pair
|
#include <utility> // pair
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -20,6 +23,12 @@ namespace ix
|
|||||||
{
|
{
|
||||||
class SocketServer {
|
class SocketServer {
|
||||||
public:
|
public:
|
||||||
|
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
|
||||||
|
|
||||||
|
// We use a list as we only care about remove and append operations.
|
||||||
|
using ConnectionThreads = std::list<std::pair<std::shared_ptr<ConnectionState>,
|
||||||
|
std::thread>>;
|
||||||
|
|
||||||
SocketServer(int port = SocketServer::kDefaultPort,
|
SocketServer(int port = SocketServer::kDefaultPort,
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
@ -27,6 +36,8 @@ namespace ix
|
|||||||
virtual ~SocketServer();
|
virtual ~SocketServer();
|
||||||
virtual void stop();
|
virtual void stop();
|
||||||
|
|
||||||
|
void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory);
|
||||||
|
|
||||||
const static int kDefaultPort;
|
const static int kDefaultPort;
|
||||||
const static std::string kDefaultHost;
|
const static std::string kDefaultHost;
|
||||||
const static int kDefaultTcpBacklog;
|
const static int kDefaultTcpBacklog;
|
||||||
@ -57,12 +68,20 @@ namespace ix
|
|||||||
std::atomic<bool> _stop;
|
std::atomic<bool> _stop;
|
||||||
std::thread _thread;
|
std::thread _thread;
|
||||||
|
|
||||||
|
ConnectionThreads _connectionsThreads;
|
||||||
|
|
||||||
std::condition_variable _conditionVariable;
|
std::condition_variable _conditionVariable;
|
||||||
std::mutex _conditionVariableMutex;
|
std::mutex _conditionVariableMutex;
|
||||||
|
|
||||||
|
//
|
||||||
|
ConnectionStateFactory _connectionStateFactory;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
void run();
|
void run();
|
||||||
virtual void handleConnection(int fd) = 0;
|
virtual void handleConnection(int fd,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||||
virtual size_t getConnectedClientsCount() = 0;
|
virtual size_t getConnectedClientsCount() = 0;
|
||||||
|
|
||||||
|
void closeTerminatedThreads();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -79,10 +79,10 @@ namespace ix
|
|||||||
return _perMessageDeflateOptions;
|
return _perMessageDeflateOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::setHeartBeatPeriod(int hearBeatPeriod)
|
void WebSocket::setHeartBeatPeriod(int heartBeatPeriod)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
_heartBeatPeriod = hearBeatPeriod;
|
_heartBeatPeriod = heartBeatPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WebSocket::getHeartBeatPeriod() const
|
int WebSocket::getHeartBeatPeriod() const
|
||||||
@ -302,7 +302,13 @@ namespace ix
|
|||||||
WebSocketSendInfo WebSocket::send(const std::string& text,
|
WebSocketSendInfo WebSocket::send(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback)
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
return sendMessage(text, false, onProgressCallback);
|
return sendMessage(text, SendMessageKind::Binary, onProgressCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketSendInfo WebSocket::sendText(const std::string& text,
|
||||||
|
const OnProgressCallback& onProgressCallback)
|
||||||
|
{
|
||||||
|
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::ping(const std::string& text)
|
WebSocketSendInfo WebSocket::ping(const std::string& text)
|
||||||
@ -311,11 +317,11 @@ namespace ix
|
|||||||
constexpr size_t pingMaxPayloadSize = 125;
|
constexpr size_t pingMaxPayloadSize = 125;
|
||||||
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
|
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
|
||||||
|
|
||||||
return sendMessage(text, true);
|
return sendMessage(text, SendMessageKind::Ping);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
|
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
|
||||||
bool ping,
|
SendMessageKind sendMessageKind,
|
||||||
const OnProgressCallback& onProgressCallback)
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
if (!isConnected()) return WebSocketSendInfo(false);
|
if (!isConnected()) return WebSocketSendInfo(false);
|
||||||
@ -332,13 +338,22 @@ namespace ix
|
|||||||
std::lock_guard<std::mutex> lock(_writeMutex);
|
std::lock_guard<std::mutex> lock(_writeMutex);
|
||||||
WebSocketSendInfo webSocketSendInfo;
|
WebSocketSendInfo webSocketSendInfo;
|
||||||
|
|
||||||
if (ping)
|
switch (sendMessageKind)
|
||||||
{
|
{
|
||||||
webSocketSendInfo = _ws.sendPing(text);
|
case SendMessageKind::Text:
|
||||||
}
|
{
|
||||||
else
|
webSocketSendInfo = _ws.sendText(text, onProgressCallback);
|
||||||
{
|
} break;
|
||||||
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
|
|
||||||
|
case SendMessageKind::Binary:
|
||||||
|
{
|
||||||
|
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SendMessageKind::Ping:
|
||||||
|
{
|
||||||
|
webSocketSendInfo = _ws.sendPing(text);
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
||||||
|
@ -89,7 +89,7 @@ namespace ix
|
|||||||
void setUrl(const std::string& url);
|
void setUrl(const std::string& url);
|
||||||
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
||||||
void setHandshakeTimeout(int handshakeTimeoutSecs);
|
void setHandshakeTimeout(int handshakeTimeoutSecs);
|
||||||
void setHeartBeatPeriod(int hearBeatPeriod);
|
void setHeartBeatPeriod(int heartBeatPeriod);
|
||||||
|
|
||||||
// Run asynchronously, by calling start and stop.
|
// Run asynchronously, by calling start and stop.
|
||||||
void start();
|
void start();
|
||||||
@ -101,6 +101,8 @@ namespace ix
|
|||||||
|
|
||||||
WebSocketSendInfo send(const std::string& text,
|
WebSocketSendInfo send(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
|
WebSocketSendInfo sendText(const std::string& text,
|
||||||
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo ping(const std::string& text);
|
WebSocketSendInfo ping(const std::string& text);
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
@ -120,7 +122,7 @@ namespace ix
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
WebSocketSendInfo sendMessage(const std::string& text,
|
WebSocketSendInfo sendMessage(const std::string& text,
|
||||||
bool ping,
|
SendMessageKind sendMessageKind,
|
||||||
const OnProgressCallback& callback = nullptr);
|
const OnProgressCallback& callback = nullptr);
|
||||||
|
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
|
@ -114,7 +114,7 @@ namespace ix
|
|||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "HTTP/1.1 ";
|
ss << "HTTP/1.1 ";
|
||||||
ss << code;
|
ss << code;
|
||||||
ss << "\r\n";
|
ss << " ";
|
||||||
ss << reason;
|
ss << reason;
|
||||||
ss << "\r\n";
|
ss << "\r\n";
|
||||||
|
|
||||||
@ -353,7 +353,7 @@ namespace ix
|
|||||||
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output);
|
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output);
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "HTTP/1.1 101\r\n";
|
ss << "HTTP/1.1 101 Switching Protocols\r\n";
|
||||||
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
||||||
ss << "Upgrade: websocket\r\n";
|
ss << "Upgrade: websocket\r\n";
|
||||||
ss << "Connection: Upgrade\r\n";
|
ss << "Connection: Upgrade\r\n";
|
||||||
|
@ -6,12 +6,28 @@
|
|||||||
|
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include "IXSocket.h"
|
#include "IXSocket.h"
|
||||||
|
#include <algorithm>
|
||||||
#include <string>
|
#include <locale>
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
|
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char & c1, const unsigned char & c2) const
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
|
||||||
|
#else
|
||||||
|
return std::tolower(c1) < std::tolower(c2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CaseInsensitiveLess::operator()(const std::string & s1, const std::string & s2) const
|
||||||
|
{
|
||||||
|
return std::lexicographical_compare
|
||||||
|
(s1.begin(), s1.end(), // source range
|
||||||
|
s2.begin(), s2.end(), // dest range
|
||||||
|
NocaseCompare()); // comparison
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
||||||
std::shared_ptr<Socket> socket,
|
std::shared_ptr<Socket> socket,
|
||||||
const CancellationRequest& isCancellationRequested)
|
const CancellationRequest& isCancellationRequested)
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -22,19 +21,10 @@ namespace ix
|
|||||||
// Case Insensitive compare_less binary function
|
// Case Insensitive compare_less binary function
|
||||||
struct NocaseCompare
|
struct NocaseCompare
|
||||||
{
|
{
|
||||||
bool operator() (const unsigned char& c1, const unsigned char& c2) const
|
bool operator() (const unsigned char& c1, const unsigned char& c2) const;
|
||||||
{
|
|
||||||
return std::tolower(c1) < std::tolower(c2);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool operator() (const std::string & s1, const std::string & s2) const
|
bool operator() (const std::string & s1, const std::string & s2) const;
|
||||||
{
|
|
||||||
return std::lexicographical_compare
|
|
||||||
(s1.begin(), s1.end(), // source range
|
|
||||||
s2.begin(), s2.end(), // dest range
|
|
||||||
NocaseCompare()); // comparison
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
|
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
|
||||||
|
@ -49,10 +49,12 @@ namespace ix
|
|||||||
_onConnectionCallback = callback;
|
_onConnectionCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketServer::handleConnection(int fd)
|
void WebSocketServer::handleConnection(
|
||||||
|
int fd,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
auto webSocket = std::make_shared<WebSocket>();
|
auto webSocket = std::make_shared<WebSocket>();
|
||||||
_onConnectionCallback(webSocket);
|
_onConnectionCallback(webSocket, connectionState);
|
||||||
|
|
||||||
webSocket->disableAutomaticReconnection();
|
webSocket->disableAutomaticReconnection();
|
||||||
|
|
||||||
@ -89,6 +91,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
logInfo("WebSocketServer::handleConnection() done");
|
logInfo("WebSocketServer::handleConnection() done");
|
||||||
|
connectionState->setTerminated();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
|
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
|
||||||
|
@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>)>;
|
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
|
||||||
|
std::shared_ptr<ConnectionState>)>;
|
||||||
|
|
||||||
class WebSocketServer : public SocketServer {
|
class WebSocketServer : public SocketServer {
|
||||||
public:
|
public:
|
||||||
@ -49,7 +50,8 @@ namespace ix
|
|||||||
const static int kDefaultHandShakeTimeoutSecs;
|
const static int kDefaultHandShakeTimeoutSecs;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
virtual void handleConnection(int fd) final;
|
virtual void handleConnection(int fd,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState) final;
|
||||||
virtual size_t getConnectedClientsCount() final;
|
virtual size_t getConnectedClientsCount() final;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -53,11 +53,12 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::hearbeat");
|
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::heartbeat");
|
||||||
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
|
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
|
||||||
constexpr size_t WebSocketTransport::kChunkSize;
|
constexpr size_t WebSocketTransport::kChunkSize;
|
||||||
|
|
||||||
WebSocketTransport::WebSocketTransport() :
|
WebSocketTransport::WebSocketTransport() :
|
||||||
|
_useMask(true),
|
||||||
_readyState(CLOSED),
|
_readyState(CLOSED),
|
||||||
_closeCode(0),
|
_closeCode(0),
|
||||||
_closeWireSize(0),
|
_closeWireSize(0),
|
||||||
@ -75,11 +76,11 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
||||||
int hearBeatPeriod)
|
int heartBeatPeriod)
|
||||||
{
|
{
|
||||||
_perMessageDeflateOptions = perMessageDeflateOptions;
|
_perMessageDeflateOptions = perMessageDeflateOptions;
|
||||||
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
|
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
|
||||||
_heartBeatPeriod = hearBeatPeriod;
|
_heartBeatPeriod = heartBeatPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client
|
// Client
|
||||||
@ -123,6 +124,9 @@ namespace ix
|
|||||||
// Server
|
// Server
|
||||||
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
|
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
|
||||||
{
|
{
|
||||||
|
// Server should not mask the data it sends to the client
|
||||||
|
_useMask = false;
|
||||||
|
|
||||||
std::string errorMsg;
|
std::string errorMsg;
|
||||||
_socket = createSocket(fd, errorMsg);
|
_socket = createSocket(fd, errorMsg);
|
||||||
|
|
||||||
@ -189,7 +193,7 @@ namespace ix
|
|||||||
// If (1) heartbeat is enabled, and (2) no data was received or
|
// If (1) heartbeat is enabled, and (2) no data was received or
|
||||||
// send for a duration exceeding our heart-beat period, send a
|
// send for a duration exceeding our heart-beat period, send a
|
||||||
// ping to the server.
|
// ping to the server.
|
||||||
if (pollResult == PollResultType_Timeout &&
|
if (pollResult == PollResultType::Timeout &&
|
||||||
heartBeatPeriodExceeded())
|
heartBeatPeriodExceeded())
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@ -198,26 +202,27 @@ namespace ix
|
|||||||
}
|
}
|
||||||
// Make sure we send all the buffered data
|
// Make sure we send all the buffered data
|
||||||
// there can be a lot of it for large messages.
|
// there can be a lot of it for large messages.
|
||||||
else if (pollResult == PollResultType_SendRequest)
|
else if (pollResult == PollResultType::SendRequest)
|
||||||
{
|
{
|
||||||
while (!isSendBufferEmpty() && !_requestInitCancellation)
|
while (!isSendBufferEmpty() && !_requestInitCancellation)
|
||||||
{
|
{
|
||||||
// Wait with a 10ms timeout until the socket is ready to write.
|
// Wait with a 10ms timeout until the socket is ready to write.
|
||||||
// This way we are not busy looping
|
// This way we are not busy looping
|
||||||
PollResultType result = _socket->isReadyToWrite(0, 10);
|
PollResultType result = _socket->isReadyToWrite(10);
|
||||||
if (result == PollResultType_Error)
|
|
||||||
|
if (result == PollResultType::Error)
|
||||||
{
|
{
|
||||||
_socket->close();
|
_socket->close();
|
||||||
setReadyState(CLOSED);
|
setReadyState(CLOSED);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (result == PollResultType_ReadyForWrite)
|
else if (result == PollResultType::ReadyForWrite)
|
||||||
{
|
{
|
||||||
sendOnSocket();
|
sendOnSocket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (pollResult == PollResultType_ReadyForRead)
|
else if (pollResult == PollResultType::ReadyForRead)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@ -243,11 +248,11 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (pollResult == PollResultType_Error)
|
else if (pollResult == PollResultType::Error)
|
||||||
{
|
{
|
||||||
_socket->close();
|
_socket->close();
|
||||||
}
|
}
|
||||||
else if (pollResult == PollResultType_CloseRequest)
|
else if (pollResult == PollResultType::CloseRequest)
|
||||||
{
|
{
|
||||||
_socket->close();
|
_socket->close();
|
||||||
}
|
}
|
||||||
@ -279,19 +284,15 @@ namespace ix
|
|||||||
_txbuf.insert(_txbuf.end(), header.begin(), header.end());
|
_txbuf.insert(_txbuf.end(), header.begin(), header.end());
|
||||||
_txbuf.insert(_txbuf.end(), begin, end);
|
_txbuf.insert(_txbuf.end(), begin, end);
|
||||||
|
|
||||||
// Masking
|
if (_useMask)
|
||||||
for (size_t i = 0; i != (size_t) message_size; ++i)
|
|
||||||
{
|
{
|
||||||
*(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i&0x3];
|
for (size_t i = 0; i != (size_t) message_size; ++i)
|
||||||
|
{
|
||||||
|
*(_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)
|
void WebSocketTransport::unmaskReceiveBuffer(const wsheader_type& ws)
|
||||||
{
|
{
|
||||||
if (ws.mask)
|
if (ws.mask)
|
||||||
@ -473,14 +474,8 @@ namespace ix
|
|||||||
std::string reason(_rxbuf.begin()+ws.header_size + 2,
|
std::string reason(_rxbuf.begin()+ws.header_size + 2,
|
||||||
_rxbuf.begin()+ws.header_size + 2 + (size_t) ws.N);
|
_rxbuf.begin()+ws.header_size + 2 + (size_t) ws.N);
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
|
||||||
_closeCode = code;
|
|
||||||
_closeReason = reason;
|
|
||||||
_closeWireSize = _rxbuf.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
close();
|
close(code, reason, _rxbuf.size());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -643,7 +638,7 @@ namespace ix
|
|||||||
std::string::const_iterator message_end,
|
std::string::const_iterator message_end,
|
||||||
bool compress)
|
bool compress)
|
||||||
{
|
{
|
||||||
auto message_size = message_end - message_begin;
|
uint64_t message_size = static_cast<uint64_t>(message_end - message_begin);
|
||||||
|
|
||||||
unsigned x = getRandomUnsigned();
|
unsigned x = getRandomUnsigned();
|
||||||
uint8_t masking_key[4] = {};
|
uint8_t masking_key[4] = {};
|
||||||
@ -655,7 +650,8 @@ namespace ix
|
|||||||
std::vector<uint8_t> header;
|
std::vector<uint8_t> header;
|
||||||
header.assign(2 +
|
header.assign(2 +
|
||||||
(message_size >= 126 ? 2 : 0) +
|
(message_size >= 126 ? 2 : 0) +
|
||||||
(message_size >= 65536 ? 6 : 0) + 4, 0);
|
(message_size >= 65536 ? 6 : 0) +
|
||||||
|
(_useMask ? 4 : 0), 0);
|
||||||
header[0] = type;
|
header[0] = type;
|
||||||
|
|
||||||
// The fin bit indicate that this is the last fragment. Fin is French for end.
|
// The fin bit indicate that this is the last fragment. Fin is French for end.
|
||||||
@ -672,27 +668,33 @@ namespace ix
|
|||||||
|
|
||||||
if (message_size < 126)
|
if (message_size < 126)
|
||||||
{
|
{
|
||||||
header[1] = (message_size & 0xff) | 0x80;
|
header[1] = (message_size & 0xff) | (_useMask ? 0x80 : 0);
|
||||||
|
|
||||||
header[2] = masking_key[0];
|
if (_useMask)
|
||||||
header[3] = masking_key[1];
|
{
|
||||||
header[4] = masking_key[2];
|
header[2] = masking_key[0];
|
||||||
header[5] = masking_key[3];
|
header[3] = masking_key[1];
|
||||||
|
header[4] = masking_key[2];
|
||||||
|
header[5] = masking_key[3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (message_size < 65536)
|
else if (message_size < 65536)
|
||||||
{
|
{
|
||||||
header[1] = 126 | 0x80;
|
header[1] = 126 | (_useMask ? 0x80 : 0);
|
||||||
header[2] = (message_size >> 8) & 0xff;
|
header[2] = (message_size >> 8) & 0xff;
|
||||||
header[3] = (message_size >> 0) & 0xff;
|
header[3] = (message_size >> 0) & 0xff;
|
||||||
|
|
||||||
header[4] = masking_key[0];
|
if (_useMask)
|
||||||
header[5] = masking_key[1];
|
{
|
||||||
header[6] = masking_key[2];
|
header[4] = masking_key[0];
|
||||||
header[7] = masking_key[3];
|
header[5] = masking_key[1];
|
||||||
|
header[6] = masking_key[2];
|
||||||
|
header[7] = masking_key[3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // TODO: run coverage testing here
|
{ // TODO: run coverage testing here
|
||||||
header[1] = 127 | 0x80;
|
header[1] = 127 | (_useMask ? 0x80 : 0);
|
||||||
header[2] = (message_size >> 56) & 0xff;
|
header[2] = (message_size >> 56) & 0xff;
|
||||||
header[3] = (message_size >> 48) & 0xff;
|
header[3] = (message_size >> 48) & 0xff;
|
||||||
header[4] = (message_size >> 40) & 0xff;
|
header[4] = (message_size >> 40) & 0xff;
|
||||||
@ -702,10 +704,13 @@ namespace ix
|
|||||||
header[8] = (message_size >> 8) & 0xff;
|
header[8] = (message_size >> 8) & 0xff;
|
||||||
header[9] = (message_size >> 0) & 0xff;
|
header[9] = (message_size >> 0) & 0xff;
|
||||||
|
|
||||||
header[10] = masking_key[0];
|
if (_useMask)
|
||||||
header[11] = masking_key[1];
|
{
|
||||||
header[12] = masking_key[2];
|
header[10] = masking_key[0];
|
||||||
header[13] = masking_key[3];
|
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:
|
// _txbuf will keep growing until it can be transmitted over the socket:
|
||||||
@ -731,6 +736,15 @@ namespace ix
|
|||||||
_enablePerMessageDeflate, onProgressCallback);
|
_enablePerMessageDeflate, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebSocketSendInfo WebSocketTransport::sendText(
|
||||||
|
const std::string& message,
|
||||||
|
const OnProgressCallback& onProgressCallback)
|
||||||
|
|
||||||
|
{
|
||||||
|
return sendData(wsheader_type::TEXT_FRAME, message,
|
||||||
|
_enablePerMessageDeflate, onProgressCallback);
|
||||||
|
}
|
||||||
|
|
||||||
void WebSocketTransport::sendOnSocket()
|
void WebSocketTransport::sendOnSocket()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_txbufMutex);
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
||||||
@ -761,7 +775,7 @@ namespace ix
|
|||||||
_lastSendTimePoint = std::chrono::steady_clock::now();
|
_lastSendTimePoint = std::chrono::steady_clock::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::close()
|
void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize)
|
||||||
{
|
{
|
||||||
_requestInitCancellation = true;
|
_requestInitCancellation = true;
|
||||||
|
|
||||||
@ -769,21 +783,22 @@ namespace ix
|
|||||||
|
|
||||||
// See list of close events here:
|
// See list of close events here:
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||||
// We use 1000: normal closure.
|
const std::string closure{(char)(code >> 8), (char)(code & 0xff)};
|
||||||
//
|
|
||||||
// >>> struct.pack('!H', 1000)
|
|
||||||
// b'\x03\xe8'
|
|
||||||
//
|
|
||||||
const std::string normalClosure = std::string("\x03\xe8");
|
|
||||||
bool compress = false;
|
bool compress = false;
|
||||||
sendData(wsheader_type::CLOSE, normalClosure, compress);
|
sendData(wsheader_type::CLOSE, closure, compress);
|
||||||
setReadyState(CLOSING);
|
setReadyState(CLOSING);
|
||||||
|
|
||||||
_socket->wakeUpFromPoll(Socket::kCloseRequest);
|
_socket->wakeUpFromPoll(Socket::kCloseRequest);
|
||||||
_socket->close();
|
_socket->close();
|
||||||
|
|
||||||
_closeCode = 1000;
|
{
|
||||||
_closeReason = "Normal Closure";
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||||
|
_closeCode = code;
|
||||||
|
_closeReason = reason;
|
||||||
|
_closeWireSize = closeWireSize;
|
||||||
|
}
|
||||||
|
|
||||||
setReadyState(CLOSED);
|
setReadyState(CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,13 @@ namespace ix
|
|||||||
{
|
{
|
||||||
class Socket;
|
class Socket;
|
||||||
|
|
||||||
|
enum class SendMessageKind
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
Binary,
|
||||||
|
Ping
|
||||||
|
};
|
||||||
|
|
||||||
class WebSocketTransport
|
class WebSocketTransport
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -61,7 +68,7 @@ namespace ix
|
|||||||
~WebSocketTransport();
|
~WebSocketTransport();
|
||||||
|
|
||||||
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
||||||
int hearBeatPeriod);
|
int heartBeatPeriod);
|
||||||
|
|
||||||
WebSocketInitResult connectToUrl(const std::string& url, // Client
|
WebSocketInitResult connectToUrl(const std::string& url, // Client
|
||||||
int timeoutSecs);
|
int timeoutSecs);
|
||||||
@ -71,8 +78,14 @@ namespace ix
|
|||||||
void poll();
|
void poll();
|
||||||
WebSocketSendInfo sendBinary(const std::string& message,
|
WebSocketSendInfo sendBinary(const std::string& message,
|
||||||
const OnProgressCallback& onProgressCallback);
|
const OnProgressCallback& onProgressCallback);
|
||||||
|
WebSocketSendInfo sendText(const std::string& message,
|
||||||
|
const OnProgressCallback& onProgressCallback);
|
||||||
WebSocketSendInfo sendPing(const std::string& message);
|
WebSocketSendInfo sendPing(const std::string& message);
|
||||||
void close();
|
|
||||||
|
void close(uint16_t code = 1000,
|
||||||
|
const std::string& reason = "Normal closure",
|
||||||
|
size_t closeWireSize = 0);
|
||||||
|
|
||||||
ReadyStateValues getReadyState() const;
|
ReadyStateValues getReadyState() const;
|
||||||
void setReadyState(ReadyStateValues readyStateValue);
|
void setReadyState(ReadyStateValues readyStateValue);
|
||||||
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
||||||
@ -100,6 +113,10 @@ namespace ix
|
|||||||
uint8_t masking_key[4];
|
uint8_t masking_key[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tells whether we should mask the data we send.
|
||||||
|
// client should mask but server should not
|
||||||
|
bool _useMask;
|
||||||
|
|
||||||
// Buffer for reading from our socket. That buffer is never resized.
|
// Buffer for reading from our socket. That buffer is never resized.
|
||||||
std::vector<uint8_t> _readbuf;
|
std::vector<uint8_t> _readbuf;
|
||||||
|
|
||||||
@ -148,7 +165,7 @@ namespace ix
|
|||||||
mutable std::mutex _lastSendTimePointMutex;
|
mutable std::mutex _lastSendTimePointMutex;
|
||||||
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
|
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
|
||||||
|
|
||||||
// No data was send through the socket for longer that the hearbeat period
|
// No data was send through the socket for longer than the heartbeat period
|
||||||
bool heartBeatPeriodExceeded();
|
bool heartBeatPeriodExceeded();
|
||||||
|
|
||||||
void sendOnSocket();
|
void sendOnSocket();
|
||||||
@ -174,7 +191,6 @@ namespace ix
|
|||||||
std::string::const_iterator end,
|
std::string::const_iterator end,
|
||||||
uint64_t message_size,
|
uint64_t message_size,
|
||||||
uint8_t masking_key[4]);
|
uint8_t masking_key[4]);
|
||||||
void appendToSendBuffer(const std::vector<uint8_t>& buffer);
|
|
||||||
|
|
||||||
unsigned getRandomUnsigned();
|
unsigned getRandomUnsigned();
|
||||||
void unmaskReceiveBuffer(const wsheader_type& ws);
|
void unmaskReceiveBuffer(const wsheader_type& ws);
|
||||||
|
18
makefile
18
makefile
@ -9,8 +9,20 @@ brew:
|
|||||||
mkdir -p build && (cd build ; cmake .. ; make -j install)
|
mkdir -p build && (cd build ; cmake .. ; make -j install)
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
|
|
||||||
|
NAME := bsergean/ws
|
||||||
|
TAG := $(shell cat DOCKER_VERSION)
|
||||||
|
IMG := ${NAME}:${TAG}
|
||||||
|
LATEST := ${NAME}:latest
|
||||||
|
BUILD := ${NAME}:build
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t ws:latest .
|
docker build -t ${IMG} .
|
||||||
|
docker tag ${IMG} ${BUILD}
|
||||||
|
|
||||||
|
docker_push:
|
||||||
|
docker tag ${IMG} ${LATEST}
|
||||||
|
docker push ${LATEST}
|
||||||
|
|
||||||
run:
|
run:
|
||||||
docker run --cap-add sys_ptrace -it ws:latest
|
docker run --cap-add sys_ptrace -it ws:latest
|
||||||
@ -36,10 +48,10 @@ test_server:
|
|||||||
# env TEST=Websocket_chat make test
|
# env TEST=Websocket_chat make test
|
||||||
# env TEST=heartbeat make test
|
# env TEST=heartbeat make test
|
||||||
test:
|
test:
|
||||||
python test/run.py
|
(cd test ; python2.7 run.py)
|
||||||
|
|
||||||
ws_test: all
|
ws_test: all
|
||||||
(cd ws ; sh test_ws.sh)
|
(cd ws ; bash test_ws.sh)
|
||||||
|
|
||||||
# For the fork that is configured with appveyor
|
# For the fork that is configured with appveyor
|
||||||
rebase_upstream:
|
rebase_upstream:
|
||||||
|
@ -29,6 +29,7 @@ set (SOURCES
|
|||||||
|
|
||||||
IXDNSLookupTest.cpp
|
IXDNSLookupTest.cpp
|
||||||
IXSocketTest.cpp
|
IXSocketTest.cpp
|
||||||
|
IXSocketConnectTest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Some unittest don't work on windows yet
|
# Some unittest don't work on windows yet
|
||||||
|
43
test/IXSocketConnectTest.cpp
Normal file
43
test/IXSocketConnectTest.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
@ -73,7 +73,7 @@ TEST_CASE("socket", "[socket]")
|
|||||||
testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
|
testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__APPLE__) or defined(__linux__)
|
#if defined(__APPLE__) || defined(__linux__)
|
||||||
SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
|
SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
|
||||||
{
|
{
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -108,7 +109,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_in sa; // server address information
|
struct sockaddr_in sa; // server address information
|
||||||
unsigned int len;
|
socklen_t len;
|
||||||
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
|
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
|
||||||
{
|
{
|
||||||
log("Cannot compute a free port. getsockname error.");
|
log("Cannot compute a free port. getsockname error.");
|
||||||
@ -148,4 +149,21 @@ namespace ix
|
|||||||
|
|
||||||
return -1;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ namespace
|
|||||||
_webSocket.setUrl(url);
|
_webSocket.setUrl(url);
|
||||||
|
|
||||||
// The important bit for this test.
|
// The important bit for this test.
|
||||||
// Set a 1 second hearbeat ; if no traffic is present on the connection for 1 second
|
// 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.
|
// a ping message will be sent by the client.
|
||||||
_webSocket.setHeartBeatPeriod(1);
|
_webSocket.setHeartBeatPeriod(1);
|
||||||
|
|
||||||
@ -128,10 +128,11 @@ namespace
|
|||||||
{
|
{
|
||||||
// A dev/null server
|
// A dev/null server
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server, &receivedPingMessages](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server, &receivedPingMessages](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -141,6 +142,7 @@ namespace
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
Logger() << "New server connection";
|
Logger() << "New server connection";
|
||||||
|
Logger() << "id: " << connectionState->getId();
|
||||||
Logger() << "Uri: " << openInfo.uri;
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
Logger() << "Headers:";
|
Logger() << "Headers:";
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
@ -210,6 +212,10 @@ TEST_CASE("Websocket_heartbeat", "[heartbeat]")
|
|||||||
webSocketClientA.stop();
|
webSocketClientA.stop();
|
||||||
webSocketClientB.stop();
|
webSocketClientB.stop();
|
||||||
|
|
||||||
|
|
||||||
|
// Here we test heart beat period exceeded for clientA
|
||||||
|
// but it should not be exceeded for clientB which has sent data.
|
||||||
|
// -> expected ping messages == 2, but add a small buffer to make this more reliable.
|
||||||
REQUIRE(serverReceivedPingMessages >= 2);
|
REQUIRE(serverReceivedPingMessages >= 2);
|
||||||
REQUIRE(serverReceivedPingMessages <= 4);
|
REQUIRE(serverReceivedPingMessages <= 4);
|
||||||
|
|
||||||
|
@ -18,13 +18,32 @@ using namespace ix;
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
bool startServer(ix::WebSocketServer& server)
|
// Test that we can override the connectionState impl to provide our own
|
||||||
|
class ConnectionStateCustom : public ConnectionState
|
||||||
{
|
{
|
||||||
|
void computeId()
|
||||||
|
{
|
||||||
|
// a very boring invariant id that we can test against in the unittest
|
||||||
|
_id = "foobarConnectionId";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool startServer(ix::WebSocketServer& server,
|
||||||
|
std::string& connectionId)
|
||||||
|
{
|
||||||
|
auto factory = []() -> std::shared_ptr<ConnectionState>
|
||||||
|
{
|
||||||
|
return std::make_shared<ConnectionStateCustom>();
|
||||||
|
};
|
||||||
|
server.setConnectionStateFactory(factory);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server, &connectionId](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState,
|
||||||
|
&connectionId, &server](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -34,12 +53,16 @@ namespace ix
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
Logger() << "New connection";
|
Logger() << "New connection";
|
||||||
|
connectionState->computeId();
|
||||||
|
Logger() << "id: " << connectionState->getId();
|
||||||
Logger() << "Uri: " << openInfo.uri;
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
Logger() << "Headers:";
|
Logger() << "Headers:";
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
{
|
{
|
||||||
Logger() << it.first << ": " << it.second;
|
Logger() << it.first << ": " << it.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectionId = connectionState->getId();
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||||
{
|
{
|
||||||
@ -78,7 +101,8 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
{
|
{
|
||||||
int port = getFreePort();
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
std::string connectionId;
|
||||||
|
REQUIRE(startServer(server, connectionId));
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
bool tls = false;
|
bool tls = false;
|
||||||
@ -111,7 +135,8 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
{
|
{
|
||||||
int port = getFreePort();
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
std::string connectionId;
|
||||||
|
REQUIRE(startServer(server, connectionId));
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
bool tls = false;
|
bool tls = false;
|
||||||
@ -147,7 +172,8 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
{
|
{
|
||||||
int port = getFreePort();
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
std::string connectionId;
|
||||||
|
REQUIRE(startServer(server, connectionId));
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
bool tls = false;
|
bool tls = false;
|
||||||
@ -178,6 +204,8 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 500ms for the server to notice that clients went away
|
||||||
ix::msleep(500);
|
ix::msleep(500);
|
||||||
|
|
||||||
|
REQUIRE(connectionId == "foobarConnectionId");
|
||||||
|
|
||||||
server.stop();
|
server.stop();
|
||||||
REQUIRE(server.getClients().size() == 0);
|
REQUIRE(server.getClients().size() == 0);
|
||||||
}
|
}
|
||||||
|
@ -217,10 +217,11 @@ namespace
|
|||||||
bool startServer(ix::WebSocketServer& server)
|
bool startServer(ix::WebSocketServer& server)
|
||||||
{
|
{
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -230,6 +231,7 @@ namespace
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
Logger() << "New connection";
|
Logger() << "New connection";
|
||||||
|
Logger() << "id: " << connectionState->getId();
|
||||||
Logger() << "Uri: " << openInfo.uri;
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
Logger() << "Headers:";
|
Logger() << "Headers:";
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
|
507
test/run.py
Normal file → Executable file
507
test/run.py
Normal file → Executable file
@ -1,9 +1,31 @@
|
|||||||
import os
|
#!/usr/bin/env python2.7
|
||||||
import platform
|
'''
|
||||||
import shutil
|
'''
|
||||||
|
|
||||||
import subprocess
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import platform
|
||||||
|
import argparse
|
||||||
|
import multiprocessing
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
import threading
|
import threading
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
class Command(object):
|
class Command(object):
|
||||||
@ -16,18 +38,22 @@ class Command(object):
|
|||||||
self.cmd = cmd
|
self.cmd = cmd
|
||||||
self.process = None
|
self.process = None
|
||||||
|
|
||||||
def run_command(self, capture = False):
|
def run_command(self):
|
||||||
self.process = subprocess.Popen(self.cmd, shell=True)
|
self.process = subprocess.Popen(self.cmd, shell=True)
|
||||||
self.process.communicate()
|
self.process.communicate()
|
||||||
|
|
||||||
def run(self, timeout = 5 * 60):
|
def run(self, timeout=None):
|
||||||
'''5 minutes default timeout'''
|
'''5 minutes default timeout'''
|
||||||
|
|
||||||
|
if timeout is None:
|
||||||
|
timeout = 5 * 60
|
||||||
|
|
||||||
thread = threading.Thread(target=self.run_command, args=())
|
thread = threading.Thread(target=self.run_command, args=())
|
||||||
thread.start()
|
thread.start()
|
||||||
thread.join(timeout)
|
thread.join(timeout)
|
||||||
|
|
||||||
if thread.is_alive():
|
if thread.is_alive():
|
||||||
print 'Command timeout, kill it: ' + self.cmd
|
print('Command timeout, kill it: ' + self.cmd)
|
||||||
self.process.terminate()
|
self.process.terminate()
|
||||||
thread.join()
|
thread.join()
|
||||||
return False, 255
|
return False, 255
|
||||||
@ -35,85 +61,424 @@ class Command(object):
|
|||||||
return True, self.process.returncode
|
return True, self.process.returncode
|
||||||
|
|
||||||
|
|
||||||
osName = platform.system()
|
def runCommand(cmd, assertOnFailure=True, timeout=None):
|
||||||
print('os name = {}'.format(osName))
|
'''Small wrapper to run a command and make sure it succeed'''
|
||||||
|
|
||||||
root = os.path.dirname(os.path.realpath(__file__))
|
if timeout is None:
|
||||||
buildDir = os.path.join(root, 'build', osName)
|
timeout = 30 * 60 # 30 minute default timeout
|
||||||
|
|
||||||
if not os.path.exists(buildDir):
|
print('\nRunning', cmd)
|
||||||
os.makedirs(buildDir)
|
command = Command(cmd)
|
||||||
|
timedout, ret = command.run(timeout)
|
||||||
|
|
||||||
os.chdir(buildDir)
|
if timedout:
|
||||||
|
print('Unittest timed out')
|
||||||
|
|
||||||
if osName == 'Windows':
|
msg = 'cmd {} failed with error code {}'.format(cmd, ret)
|
||||||
generator = '-G"NMake Makefiles"'
|
if ret != 0:
|
||||||
make = 'nmake'
|
print(msg)
|
||||||
testBinary ='ixwebsocket_unittest.exe'
|
if assertOnFailure:
|
||||||
else:
|
assert False
|
||||||
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]
|
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).
|
||||||
|
'''
|
||||||
|
|
||||||
# if osName == 'Windows':
|
# CMake installed via Self Service ends up here.
|
||||||
# os.environ['CC'] = 'clang-cl'
|
cmake_executable = '/Applications/CMake.app/Contents/bin/cmake'
|
||||||
# os.environ['CXX'] = 'clang-cl'
|
|
||||||
|
|
||||||
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ../..'.format(generator, sanitizerFlags)
|
if not os.path.exists(cmake_executable):
|
||||||
print(cmakeCmd)
|
cmake_executable = 'cmake'
|
||||||
ret = os.system(cmakeCmd)
|
|
||||||
assert ret == 0, 'CMake failed, exiting'
|
|
||||||
|
|
||||||
ret = os.system(make)
|
sanitizersFlags = {
|
||||||
assert ret == 0, 'Make failed, exiting'
|
'asan': '-DSANITIZE_ADDRESS=On',
|
||||||
|
'ubsan': '-DSANITIZE_UNDEFINED=On',
|
||||||
|
'tsan': '-DSANITIZE_THREAD=On',
|
||||||
|
'none': ''
|
||||||
|
}
|
||||||
|
sanitizerFlag = sanitizersFlags[sanitizer]
|
||||||
|
|
||||||
def findFiles(prefix):
|
# CMake installed via Self Service ends up here.
|
||||||
'''Find all files under a given directory'''
|
cmakeExecutable = '/Applications/CMake.app/Contents/bin/cmake'
|
||||||
|
if not os.path.exists(cmakeExecutable):
|
||||||
|
cmakeExecutable = 'cmake'
|
||||||
|
|
||||||
paths = []
|
fmt = '''
|
||||||
|
{cmakeExecutable} -H. \
|
||||||
|
{sanitizerFlag} \
|
||||||
|
-B{buildDir} \
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug \
|
||||||
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
|
'''
|
||||||
|
cmakeCmd = fmt.format(**locals())
|
||||||
|
runCommand(cmakeCmd)
|
||||||
|
|
||||||
for root, _, files in os.walk(prefix):
|
|
||||||
for path in files:
|
|
||||||
fullPath = os.path.join(root, path)
|
|
||||||
|
|
||||||
if os.path.islink(fullPath):
|
def runTest(args, buildDir, xmlOutput, testRunName):
|
||||||
continue
|
'''Execute the unittest.
|
||||||
|
'''
|
||||||
|
if args is None:
|
||||||
|
args = ''
|
||||||
|
|
||||||
paths.append(fullPath)
|
fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"'
|
||||||
|
testCommand = fmt.format(**locals())
|
||||||
|
runCommand(testCommand,
|
||||||
|
assertOnFailure=False)
|
||||||
|
|
||||||
return paths
|
|
||||||
|
|
||||||
#for path in findFiles('.'):
|
def validateTestSuite(xmlOutput):
|
||||||
# print(path)
|
'''
|
||||||
|
Parse the output XML file to validate that all tests passed.
|
||||||
|
|
||||||
# We need to copy the zlib DLL in the current work directory
|
Assume that the XML file contains only one testsuite.
|
||||||
shutil.copy(os.path.join(
|
(which is true when generate by catch2)
|
||||||
'..',
|
'''
|
||||||
'..',
|
tree = ET.parse(xmlOutput)
|
||||||
'..',
|
root = tree.getroot()
|
||||||
'third_party',
|
testSuite = root[0]
|
||||||
'ZLIB-Windows',
|
testSuiteAttributes = testSuite.attrib
|
||||||
'zlib-1.2.11_deploy_v140',
|
|
||||||
'release_dynamic',
|
|
||||||
'x64',
|
|
||||||
'bin',
|
|
||||||
'zlib.dll'), '.')
|
|
||||||
|
|
||||||
# lldb = "lldb --batch -o 'run' -k 'thread backtrace all' -k 'quit 1'"
|
tests = testSuiteAttributes['tests']
|
||||||
lldb = "" # Disabled for now
|
|
||||||
testCommand = '{} {} {}'.format(lldb, testBinary, os.getenv('TEST', ''))
|
success = True
|
||||||
command = Command(testCommand)
|
|
||||||
timedout, ret = command.run()
|
for testcase in testSuite:
|
||||||
assert ret == 0, 'Test command failed'
|
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('.')
|
||||||
|
|
||||||
|
# 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.'''
|
||||||
|
|
||||||
|
runCMake(sanitizer, buildDir)
|
||||||
|
runCommand('make -C {} -j8'.format(buildDir))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
cmd = '{} "{}" "{}" >& "{}"'.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():
|
||||||
|
buildDir = '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
|
||||||
|
|
||||||
|
return run(args.test, buildDir, sanitizer, xmlOutput,
|
||||||
|
testRunName, args.build_only, args.lldb)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
@ -7,8 +7,6 @@
|
|||||||
#define CATCH_CONFIG_RUNNER
|
#define CATCH_CONFIG_RUNNER
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
|
|
||||||
#include <ixwebsocket/IXSocket.h>
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
int result = Catch::Session().run(argc, argv);
|
int result = Catch::Session().run(argc, argv);
|
||||||
|
1
third_party/README.md
vendored
Normal file
1
third_party/README.md
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Except ZLIB on Windows (whose port is currently broken...) all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.
|
20
third_party/homebrew_formula.rb
vendored
20
third_party/homebrew_formula.rb
vendored
@ -1,20 +0,0 @@
|
|||||||
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
|
|
1
third_party/remote_trailing_whitespaces.sh
vendored
1
third_party/remote_trailing_whitespaces.sh
vendored
@ -1,2 +1,3 @@
|
|||||||
find . -type f -name '*.cpp' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
find . -type f -name '*.cpp' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
||||||
find . -type f -name '*.h' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
find . -type f -name '*.h' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
||||||
|
find . -type f -name '*.md' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
||||||
|
13
third_party/statsd-client-cpp/.gitignore
vendored
Normal file
13
third_party/statsd-client-cpp/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
18
third_party/statsd-client-cpp/CMakeLists.txt
vendored
Normal file
18
third_party/statsd-client-cpp/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.1)
|
||||||
|
project(helloCLion)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
src
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(statsdcppclient STATIC src/statsd_client.cpp)
|
||||||
|
add_definitions("-fPIC")
|
||||||
|
target_link_libraries(statsdcppclient pthread)
|
||||||
|
|
||||||
|
add_executable(system_monitor demo/system_monitor.cpp)
|
||||||
|
target_link_libraries(system_monitor statsdcppclient)
|
||||||
|
|
||||||
|
add_executable(test_client demo/test_client.cpp)
|
||||||
|
target_link_libraries(test_client statsdcppclient)
|
27
third_party/statsd-client-cpp/LICENSE
vendored
Normal file
27
third_party/statsd-client-cpp/LICENSE
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2014, Rex
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the {organization} nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
third_party/statsd-client-cpp/README.md
vendored
Normal file
34
third_party/statsd-client-cpp/README.md
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# a client sdk for StatsD, written in C++
|
||||||
|
|
||||||
|
## API
|
||||||
|
See [header file](src/statsd_client.h) for more api detail.
|
||||||
|
|
||||||
|
** Notice: this client is not thread-safe **
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
### test\_client
|
||||||
|
This simple demo shows how the use this client.
|
||||||
|
|
||||||
|
### system\_monitor
|
||||||
|
This is a daemon for monitoring a Linux system.
|
||||||
|
It'll wake up every minute and monitor the following:
|
||||||
|
|
||||||
|
* load
|
||||||
|
* cpu
|
||||||
|
* free memory
|
||||||
|
* free swap (disabled)
|
||||||
|
* received bytes
|
||||||
|
* transmitted bytes
|
||||||
|
* procs
|
||||||
|
* uptime
|
||||||
|
|
||||||
|
The stats sent to statsd will be in "host.MACAddress" namespace.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
system_monitor statsd-host interface-to-monitor
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
`system_monitor 172.16.42.1 eth0`
|
||||||
|
|
164
third_party/statsd-client-cpp/demo/system_monitor.cpp
vendored
Normal file
164
third_party/statsd-client-cpp/demo/system_monitor.cpp
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <sys/sysinfo.h>
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "statsd_client.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
static int running = 1;
|
||||||
|
|
||||||
|
void sigterm(int sig)
|
||||||
|
{
|
||||||
|
running = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
string localhost() {
|
||||||
|
struct addrinfo hints, *info, *p;
|
||||||
|
string hostname(1024, '\0');
|
||||||
|
gethostname((char*)hostname.data(), hostname.capacity());
|
||||||
|
|
||||||
|
memset(&hints, 0, sizeof hints);
|
||||||
|
hints.ai_family = AF_UNSPEC; /*either IPV4 or IPV6*/
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_flags = AI_CANONNAME;
|
||||||
|
|
||||||
|
if ( getaddrinfo(hostname.c_str(), "http", &hints, &info) == 0) {
|
||||||
|
for(p = info; p != NULL; p = p->ai_next) {
|
||||||
|
hostname = p->ai_canonname;
|
||||||
|
}
|
||||||
|
freeaddrinfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
string::size_type pos = hostname.find(".");
|
||||||
|
while ( pos != string::npos )
|
||||||
|
{
|
||||||
|
hostname[pos] = '_';
|
||||||
|
pos = hostname.find(".", pos);
|
||||||
|
}
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<string>& StringSplitTrim(const string& sData,
|
||||||
|
const string& sDelim, vector<string>& vItems)
|
||||||
|
{
|
||||||
|
vItems.clear();
|
||||||
|
|
||||||
|
string::size_type bpos = 0;
|
||||||
|
string::size_type epos = 0;
|
||||||
|
string::size_type nlen = sDelim.size();
|
||||||
|
|
||||||
|
while(sData.substr(epos,nlen) == sDelim)
|
||||||
|
{
|
||||||
|
epos += nlen;
|
||||||
|
}
|
||||||
|
bpos = epos;
|
||||||
|
|
||||||
|
while ((epos=sData.find(sDelim, epos)) != string::npos)
|
||||||
|
{
|
||||||
|
vItems.push_back(sData.substr(bpos, epos-bpos));
|
||||||
|
epos += nlen;
|
||||||
|
while(sData.substr(epos,nlen) == sDelim)
|
||||||
|
{
|
||||||
|
epos += nlen;
|
||||||
|
}
|
||||||
|
bpos = epos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bpos != sData.size())
|
||||||
|
{
|
||||||
|
vItems.push_back(sData.substr(bpos, sData.size()-bpos));
|
||||||
|
}
|
||||||
|
return vItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
FILE *net, *stat;
|
||||||
|
struct sysinfo si;
|
||||||
|
char line[256];
|
||||||
|
unsigned int user, nice, sys, idle, total, busy, old_total=0, old_busy=0;
|
||||||
|
|
||||||
|
if (argc != 3) {
|
||||||
|
printf( "Usage: %s host port\n"
|
||||||
|
"Eg: %s 127.0.0.1 8125\n",
|
||||||
|
argv[0], argv[0]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
signal(SIGHUP, SIG_IGN);
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
signal(SIGCHLD, SIG_IGN); /* will save one syscall per sleep */
|
||||||
|
signal(SIGTERM, sigterm);
|
||||||
|
|
||||||
|
if ( (net = fopen("/proc/net/dev", "r")) == NULL) {
|
||||||
|
perror("fopen");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (stat = fopen("/proc/stat", "r")) == NULL) {
|
||||||
|
perror("fopen");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
string ns = string("host.") + localhost().c_str() + ".";
|
||||||
|
statsd::StatsdClient client(argv[1], atoi(argv[2]), ns);
|
||||||
|
|
||||||
|
daemon(0,0);
|
||||||
|
printf("running in background.\n");
|
||||||
|
|
||||||
|
while(running) {
|
||||||
|
rewind(net);
|
||||||
|
vector<string> items;
|
||||||
|
while(!feof(net)) {
|
||||||
|
fgets(line, sizeof(line), net);
|
||||||
|
StringSplitTrim(line, " ", items);
|
||||||
|
|
||||||
|
if ( items.size() < 17 ) continue;
|
||||||
|
if ( items[0].find(":") == string::npos ) continue;
|
||||||
|
if ( items[1] == "0" and items[9] == "0" ) continue;
|
||||||
|
|
||||||
|
string netface = "network."+items[0].erase( items[0].find(":") );
|
||||||
|
client.count( netface+".receive.bytes", atoll(items[1].c_str()) );
|
||||||
|
client.count( netface+".receive.packets", atoll(items[2].c_str()) );
|
||||||
|
client.count( netface+".transmit.bytes", atoll(items[9].c_str()) );
|
||||||
|
client.count( netface+".transmit.packets", atoll(items[10].c_str()) );
|
||||||
|
}
|
||||||
|
|
||||||
|
sysinfo(&si);
|
||||||
|
client.gauge("system.load", 100*si.loads[0]/0x10000);
|
||||||
|
client.gauge("system.freemem", si.freeram/1024);
|
||||||
|
client.gauge("system.procs", si.procs);
|
||||||
|
client.count("system.uptime", si.uptime);
|
||||||
|
|
||||||
|
/* rewind doesn't do the trick for /proc/stat */
|
||||||
|
freopen("/proc/stat", "r", stat);
|
||||||
|
fgets(line, sizeof(line), stat);
|
||||||
|
sscanf(line, "cpu %u %u %u %u", &user, &nice, &sys, &idle);
|
||||||
|
total = user + sys + idle;
|
||||||
|
busy = user + sys;
|
||||||
|
|
||||||
|
client.send("system.cpu", 100 * (busy - old_busy)/(total - old_total), "g", 1.0);
|
||||||
|
|
||||||
|
old_total = total;
|
||||||
|
old_busy = busy;
|
||||||
|
sleep(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(net);
|
||||||
|
fclose(stat);
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
28
third_party/statsd-client-cpp/demo/test_client.cpp
vendored
Normal file
28
third_party/statsd-client-cpp/demo/test_client.cpp
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "statsd_client.h"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
std::cout << "running..." << std::endl;
|
||||||
|
|
||||||
|
statsd::StatsdClient client;
|
||||||
|
statsd::StatsdClient client2("127.0.0.1", 8125, "myproject.abx.", true);
|
||||||
|
|
||||||
|
client.count("count1", 123, 1.0);
|
||||||
|
client.count("count2", 125, 1.0);
|
||||||
|
client.gauge("speed", 10);
|
||||||
|
int i;
|
||||||
|
for (i=0; i<1000; i++)
|
||||||
|
client2.timing("request", i);
|
||||||
|
sleep(1);
|
||||||
|
client.inc("count1", 1.0);
|
||||||
|
client2.dec("count2", 1.0);
|
||||||
|
// for(i=0; i<1000; i++) {
|
||||||
|
// client2.count("count3", i, 0.8);
|
||||||
|
// }
|
||||||
|
|
||||||
|
std::cout << "done" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
246
third_party/statsd-client-cpp/src/statsd_client.cpp
vendored
Normal file
246
third_party/statsd-client-cpp/src/statsd_client.cpp
vendored
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
#include <math.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include "statsd_client.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
namespace statsd {
|
||||||
|
|
||||||
|
inline bool fequal(float a, float b)
|
||||||
|
{
|
||||||
|
const float epsilon = 0.0001;
|
||||||
|
return ( fabs(a - b) < epsilon );
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool should_send(float sample_rate)
|
||||||
|
{
|
||||||
|
if ( fequal(sample_rate, 1.0) )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float p = ((float)random() / RAND_MAX);
|
||||||
|
return sample_rate > p;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _StatsdClientData {
|
||||||
|
int sock;
|
||||||
|
struct sockaddr_in server;
|
||||||
|
|
||||||
|
string ns;
|
||||||
|
string host;
|
||||||
|
short port;
|
||||||
|
bool init;
|
||||||
|
|
||||||
|
char errmsg[1024];
|
||||||
|
};
|
||||||
|
|
||||||
|
StatsdClient::StatsdClient(const string& host,
|
||||||
|
int port,
|
||||||
|
const string& ns,
|
||||||
|
const bool batching)
|
||||||
|
: batching_(batching), exit_(false)
|
||||||
|
{
|
||||||
|
d = new _StatsdClientData;
|
||||||
|
d->sock = -1;
|
||||||
|
config(host, port, ns);
|
||||||
|
srandom(time(NULL));
|
||||||
|
|
||||||
|
if (batching_) {
|
||||||
|
pthread_mutex_init(&batching_mutex_lock_, nullptr);
|
||||||
|
batching_thread_ = std::thread([this] {
|
||||||
|
while (!exit_) {
|
||||||
|
std::deque<std::string> staged_message_queue;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&batching_mutex_lock_);
|
||||||
|
batching_message_queue_.swap(staged_message_queue);
|
||||||
|
pthread_mutex_unlock(&batching_mutex_lock_);
|
||||||
|
|
||||||
|
while(!staged_message_queue.empty()) {
|
||||||
|
send_to_daemon(staged_message_queue.front());
|
||||||
|
staged_message_queue.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatsdClient::~StatsdClient()
|
||||||
|
{
|
||||||
|
if (batching_) {
|
||||||
|
exit_ = true;
|
||||||
|
batching_thread_.join();
|
||||||
|
pthread_mutex_destroy(&batching_mutex_lock_);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// close socket
|
||||||
|
if (d->sock >= 0) {
|
||||||
|
close(d->sock);
|
||||||
|
d->sock = -1;
|
||||||
|
delete d;
|
||||||
|
d = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatsdClient::config(const string& host, int port, const string& ns)
|
||||||
|
{
|
||||||
|
d->ns = ns;
|
||||||
|
d->host = host;
|
||||||
|
d->port = port;
|
||||||
|
d->init = false;
|
||||||
|
if ( d->sock >= 0 ) {
|
||||||
|
close(d->sock);
|
||||||
|
}
|
||||||
|
d->sock = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatsdClient::init()
|
||||||
|
{
|
||||||
|
if ( d->init ) return 0;
|
||||||
|
|
||||||
|
d->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if ( d->sock == -1 ) {
|
||||||
|
snprintf(d->errmsg, sizeof(d->errmsg), "could not create socket, err=%m");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&d->server, 0, sizeof(d->server));
|
||||||
|
d->server.sin_family = AF_INET;
|
||||||
|
d->server.sin_port = htons(d->port);
|
||||||
|
|
||||||
|
int ret = inet_aton(d->host.c_str(), &d->server.sin_addr);
|
||||||
|
if ( ret == 0 )
|
||||||
|
{
|
||||||
|
// host must be a domain, get it from internet
|
||||||
|
struct addrinfo hints, *result = NULL;
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
hints.ai_socktype = SOCK_DGRAM;
|
||||||
|
|
||||||
|
ret = getaddrinfo(d->host.c_str(), NULL, &hints, &result);
|
||||||
|
if ( ret ) {
|
||||||
|
close(d->sock);
|
||||||
|
d->sock = -1;
|
||||||
|
snprintf(d->errmsg, sizeof(d->errmsg),
|
||||||
|
"getaddrinfo fail, error=%d, msg=%s", ret, gai_strerror(ret) );
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
struct sockaddr_in* host_addr = (struct sockaddr_in*)result->ai_addr;
|
||||||
|
memcpy(&d->server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr));
|
||||||
|
freeaddrinfo(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
d->init = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* will change the original string */
|
||||||
|
void StatsdClient::cleanup(string& key)
|
||||||
|
{
|
||||||
|
size_t pos = key.find_first_of(":|@");
|
||||||
|
while ( pos != string::npos )
|
||||||
|
{
|
||||||
|
key[pos] = '_';
|
||||||
|
pos = key.find_first_of(":|@");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatsdClient::dec(const string& key, float sample_rate)
|
||||||
|
{
|
||||||
|
return count(key, -1, sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatsdClient::inc(const string& key, float sample_rate)
|
||||||
|
{
|
||||||
|
return count(key, 1, sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatsdClient::count(const string& key, size_t value, float sample_rate)
|
||||||
|
{
|
||||||
|
return send(key, value, "c", sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatsdClient::gauge(const string& key, size_t value, float sample_rate)
|
||||||
|
{
|
||||||
|
return send(key, value, "g", sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatsdClient::timing(const string& key, size_t ms, float sample_rate)
|
||||||
|
{
|
||||||
|
return send(key, ms, "ms", sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatsdClient::send(string key, size_t value, const string &type, float sample_rate)
|
||||||
|
{
|
||||||
|
if (!should_send(sample_rate)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup(key);
|
||||||
|
|
||||||
|
char buf[256];
|
||||||
|
if ( fequal( sample_rate, 1.0 ) )
|
||||||
|
{
|
||||||
|
snprintf(buf, sizeof(buf), "%s%s:%zd|%s",
|
||||||
|
d->ns.c_str(), key.c_str(), value, type.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(buf, sizeof(buf), "%s%s:%zd|%s|@%.2f",
|
||||||
|
d->ns.c_str(), key.c_str(), value, type.c_str(), sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return send(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int StatsdClient::send(const string &message)
|
||||||
|
{
|
||||||
|
if (batching_) {
|
||||||
|
pthread_mutex_lock(&batching_mutex_lock_);
|
||||||
|
if (batching_message_queue_.empty() ||
|
||||||
|
batching_message_queue_.back().length() > max_batching_size) {
|
||||||
|
batching_message_queue_.push_back(message);
|
||||||
|
} else {
|
||||||
|
(*batching_message_queue_.rbegin()).append("\n").append(message);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&batching_mutex_lock_);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return send_to_daemon(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int StatsdClient::send_to_daemon(const string &message) {
|
||||||
|
std::cout << "send_to_daemon: " << message.length() << " B" << std::endl;
|
||||||
|
int ret = init();
|
||||||
|
if ( ret )
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = sendto(d->sock, message.data(), message.size(), 0, (struct sockaddr *) &d->server, sizeof(d->server));
|
||||||
|
if ( ret == -1) {
|
||||||
|
snprintf(d->errmsg, sizeof(d->errmsg),
|
||||||
|
"sendto server fail, host=%s:%d, err=%m", d->host.c_str(), d->port);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* StatsdClient::errmsg()
|
||||||
|
{
|
||||||
|
return d->errmsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
66
third_party/statsd-client-cpp/src/statsd_client.h
vendored
Normal file
66
third_party/statsd-client-cpp/src/statsd_client.h
vendored
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
#ifndef STATSD_CLIENT_H
|
||||||
|
#define STATSD_CLIENT_H
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <deque>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace statsd {
|
||||||
|
|
||||||
|
struct _StatsdClientData;
|
||||||
|
|
||||||
|
class StatsdClient {
|
||||||
|
public:
|
||||||
|
StatsdClient(const std::string& host="127.0.0.1", int port=8125, const std::string& ns = "", const bool batching = false);
|
||||||
|
~StatsdClient();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// you can config at anytime; client will use new address (useful for Singleton)
|
||||||
|
void config(const std::string& host, int port, const std::string& ns = "");
|
||||||
|
const char* errmsg();
|
||||||
|
int send_to_daemon(const std::string &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
int inc(const std::string& key, float sample_rate = 1.0);
|
||||||
|
int dec(const std::string& key, float sample_rate = 1.0);
|
||||||
|
int count(const std::string& key, size_t value, float sample_rate = 1.0);
|
||||||
|
int gauge(const std::string& key, size_t value, float sample_rate = 1.0);
|
||||||
|
int timing(const std::string& key, size_t ms, float sample_rate = 1.0);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* (Low Level Api) manually send a message
|
||||||
|
* which might be composed of several lines.
|
||||||
|
*/
|
||||||
|
int send(const std::string& message);
|
||||||
|
|
||||||
|
/* (Low Level Api) manually send a message
|
||||||
|
* type = "c", "g" or "ms"
|
||||||
|
*/
|
||||||
|
int send(std::string key, size_t value,
|
||||||
|
const std::string& type, float sample_rate);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int init();
|
||||||
|
void cleanup(std::string& key);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct _StatsdClientData* d;
|
||||||
|
|
||||||
|
bool batching_;
|
||||||
|
bool exit_;
|
||||||
|
pthread_mutex_t batching_mutex_lock_;
|
||||||
|
std::thread batching_thread_;
|
||||||
|
std::deque<std::string> batching_message_queue_;
|
||||||
|
const uint64_t max_batching_size = 32768;
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // end namespace
|
||||||
|
|
||||||
|
#endif
|
1
ws/.gitignore
vendored
1
ws/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
build
|
build
|
||||||
|
node_modules
|
||||||
|
@ -7,7 +7,12 @@ cmake_minimum_required (VERSION 3.4.1)
|
|||||||
project (ws)
|
project (ws)
|
||||||
|
|
||||||
# There's -Weverything too for clang
|
# There's -Weverything too for clang
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
if (NOT WIN32)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
|
#set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
@ -16,12 +21,20 @@ option(USE_TLS "Add TLS support" ON)
|
|||||||
include_directories(ws .)
|
include_directories(ws .)
|
||||||
include_directories(ws ..)
|
include_directories(ws ..)
|
||||||
include_directories(ws ../third_party)
|
include_directories(ws ../third_party)
|
||||||
|
include_directories(ws ../third_party/statsd-client-cpp/src)
|
||||||
|
|
||||||
add_executable(ws
|
add_executable(ws
|
||||||
../third_party/msgpack11/msgpack11.cpp
|
../third_party/msgpack11/msgpack11.cpp
|
||||||
|
../third_party/jsoncpp/jsoncpp.cpp
|
||||||
|
../third_party/statsd-client-cpp/src/statsd_client.cpp
|
||||||
ixcrypto/IXBase64.cpp
|
ixcrypto/IXBase64.cpp
|
||||||
ixcrypto/IXHash.cpp
|
ixcrypto/IXHash.cpp
|
||||||
ixcrypto/IXUuid.cpp
|
ixcrypto/IXUuid.cpp
|
||||||
|
ixcrypto/IXHMac.cpp
|
||||||
|
|
||||||
|
IXRedisClient.cpp
|
||||||
|
IXSentryClient.cpp
|
||||||
|
IXCobraConnection.cpp
|
||||||
|
|
||||||
ws_http_client.cpp
|
ws_http_client.cpp
|
||||||
ws_ping_pong.cpp
|
ws_ping_pong.cpp
|
||||||
@ -32,11 +45,21 @@ add_executable(ws
|
|||||||
ws_transfer.cpp
|
ws_transfer.cpp
|
||||||
ws_send.cpp
|
ws_send.cpp
|
||||||
ws_receive.cpp
|
ws_receive.cpp
|
||||||
|
ws_redis_publish.cpp
|
||||||
|
ws_redis_subscribe.cpp
|
||||||
|
ws_cobra_subscribe.cpp
|
||||||
|
ws_cobra_to_statsd.cpp
|
||||||
|
ws_cobra_to_sentry.cpp
|
||||||
ws.cpp)
|
ws.cpp)
|
||||||
|
|
||||||
if (APPLE AND USE_TLS)
|
target_link_libraries(ws ixwebsocket)
|
||||||
target_link_libraries(ws "-framework foundation" "-framework security")
|
|
||||||
|
if(NOT APPLE)
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
add_definitions(${OPENSSL_DEFINITIONS})
|
||||||
|
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||||
|
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||||
|
target_link_libraries(ws ${OPENSSL_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(ws ixwebsocket)
|
|
||||||
install(TARGETS ws RUNTIME DESTINATION bin)
|
install(TARGETS ws RUNTIME DESTINATION bin)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "IXCobraConnection.h"
|
#include "IXCobraConnection.h"
|
||||||
#include <ixcrypto/IXHMac.h>
|
#include <ixcrypto/IXHMac.h>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@ -20,9 +21,10 @@ namespace ix
|
|||||||
constexpr size_t CobraConnection::kQueueMaxSize;
|
constexpr size_t CobraConnection::kQueueMaxSize;
|
||||||
|
|
||||||
CobraConnection::CobraConnection() :
|
CobraConnection::CobraConnection() :
|
||||||
|
_webSocket(new WebSocket()),
|
||||||
|
_publishMode(CobraConnection_PublishMode_Immediate),
|
||||||
_authenticated(false),
|
_authenticated(false),
|
||||||
_eventCallback(nullptr),
|
_eventCallback(nullptr)
|
||||||
_publishMode(CobraConnection_PublishMode_Immediate)
|
|
||||||
{
|
{
|
||||||
_pdu["action"] = "rtm/publish";
|
_pdu["action"] = "rtm/publish";
|
||||||
|
|
||||||
@ -32,6 +34,7 @@ namespace ix
|
|||||||
CobraConnection::~CobraConnection()
|
CobraConnection::~CobraConnection()
|
||||||
{
|
{
|
||||||
disconnect();
|
disconnect();
|
||||||
|
setEventCallback(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::setTrafficTrackerCallback(const TrafficTrackerCallback& callback)
|
void CobraConnection::setTrafficTrackerCallback(const TrafficTrackerCallback& callback)
|
||||||
@ -59,36 +62,40 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType,
|
void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType,
|
||||||
const std::string& errorMsg,
|
const std::string& errorMsg,
|
||||||
const WebSocketHttpHeaders& headers)
|
const WebSocketHttpHeaders& headers,
|
||||||
|
const std::string& subscriptionId)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
||||||
if (_eventCallback)
|
if (_eventCallback)
|
||||||
{
|
{
|
||||||
_eventCallback(eventType, errorMsg, headers);
|
_eventCallback(eventType, errorMsg, headers, subscriptionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::invokeErrorCallback(const std::string& errorMsg)
|
void CobraConnection::invokeErrorCallback(const std::string& errorMsg,
|
||||||
|
const std::string& serializedPdu)
|
||||||
{
|
{
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Error, errorMsg);
|
std::stringstream ss;
|
||||||
|
ss << errorMsg << " : received pdu => " << serializedPdu;
|
||||||
|
invokeEventCallback(ix::CobraConnection_EventType_Error, ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::disconnect()
|
void CobraConnection::disconnect()
|
||||||
{
|
{
|
||||||
_authenticated = false;
|
_authenticated = false;
|
||||||
_webSocket.stop();
|
_webSocket->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::initWebSocketOnMessageCallback()
|
void CobraConnection::initWebSocketOnMessageCallback()
|
||||||
{
|
{
|
||||||
_webSocket.setOnMessageCallback(
|
_webSocket->setOnMessageCallback(
|
||||||
[this](ix::WebSocketMessageType messageType,
|
[this](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
const ix::WebSocketCloseInfo& closeInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketHttpHeaders& headers)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
CobraConnection::invokeTrafficTrackerCallback(wireSize, true);
|
CobraConnection::invokeTrafficTrackerCallback(wireSize, true);
|
||||||
|
|
||||||
@ -97,7 +104,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
invokeEventCallback(ix::CobraConnection_EventType_Open,
|
invokeEventCallback(ix::CobraConnection_EventType_Open,
|
||||||
std::string(),
|
std::string(),
|
||||||
headers);
|
openInfo.headers);
|
||||||
sendHandshakeMessage();
|
sendHandshakeMessage();
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||||
@ -116,13 +123,13 @@ namespace ix
|
|||||||
Json::Reader reader;
|
Json::Reader reader;
|
||||||
if (!reader.parse(str, data))
|
if (!reader.parse(str, data))
|
||||||
{
|
{
|
||||||
invokeErrorCallback(std::string("Invalid json: ") + str);
|
invokeErrorCallback("Invalid json", str);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.isMember("action"))
|
if (!data.isMember("action"))
|
||||||
{
|
{
|
||||||
invokeErrorCallback("Missing action");
|
invokeErrorCallback("Missing action", str);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,12 +139,12 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (!handleHandshakeResponse(data))
|
if (!handleHandshakeResponse(data))
|
||||||
{
|
{
|
||||||
invokeErrorCallback("Error extracting nonce from handshake response");
|
invokeErrorCallback("Error extracting nonce from handshake response", str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (action == "auth/handshake/error")
|
else if (action == "auth/handshake/error")
|
||||||
{
|
{
|
||||||
invokeErrorCallback("Handshake error."); // print full message ?
|
invokeErrorCallback("Handshake error", str);
|
||||||
}
|
}
|
||||||
else if (action == "auth/authenticate/ok")
|
else if (action == "auth/authenticate/ok")
|
||||||
{
|
{
|
||||||
@ -147,15 +154,37 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else if (action == "auth/authenticate/error")
|
else if (action == "auth/authenticate/error")
|
||||||
{
|
{
|
||||||
invokeErrorCallback("Authentication error."); // print full message ?
|
invokeErrorCallback("Authentication error", str);
|
||||||
}
|
}
|
||||||
else if (action == "rtm/subscription/data")
|
else if (action == "rtm/subscription/data")
|
||||||
{
|
{
|
||||||
handleSubscriptionData(data);
|
handleSubscriptionData(data);
|
||||||
}
|
}
|
||||||
|
else if (action == "rtm/subscribe/ok")
|
||||||
|
{
|
||||||
|
if (!handleSubscriptionResponse(data))
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Error processing subscribe response", str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (action == "rtm/subscribe/error")
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Subscription error", str);
|
||||||
|
}
|
||||||
|
else if (action == "rtm/unsubscribe/ok")
|
||||||
|
{
|
||||||
|
if (!handleUnsubscriptionResponse(data))
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Error processing subscribe response", str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (action == "rtm/unsubscribe/error")
|
||||||
|
{
|
||||||
|
invokeErrorCallback("Unsubscription error", str);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
invokeErrorCallback(std::string("Un-handled message type: ") + action);
|
invokeErrorCallback("Un-handled message type", str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||||
@ -165,7 +194,7 @@ namespace ix
|
|||||||
ss << "#retries: " << error.retries << std::endl;
|
ss << "#retries: " << error.retries << std::endl;
|
||||||
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
||||||
ss << "HTTP Status: " << error.http_status << std::endl;
|
ss << "HTTP Status: " << error.http_status << std::endl;
|
||||||
invokeErrorCallback(ss.str());
|
invokeErrorCallback(ss.str(), std::string());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -176,10 +205,10 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::configure(const std::string& appkey,
|
void CobraConnection::configure(const std::string& appkey,
|
||||||
const std::string& endpoint,
|
const std::string& endpoint,
|
||||||
const std::string& rolename,
|
const std::string& rolename,
|
||||||
const std::string& rolesecret,
|
const std::string& rolesecret,
|
||||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions)
|
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions)
|
||||||
{
|
{
|
||||||
_appkey = appkey;
|
_appkey = appkey;
|
||||||
_endpoint = endpoint;
|
_endpoint = endpoint;
|
||||||
@ -192,8 +221,8 @@ namespace ix
|
|||||||
ss << _appkey;
|
ss << _appkey;
|
||||||
|
|
||||||
std::string url = ss.str();
|
std::string url = ss.str();
|
||||||
_webSocket.setUrl(url);
|
_webSocket->setUrl(url);
|
||||||
_webSocket.setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
_webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -226,10 +255,10 @@ namespace ix
|
|||||||
std::string serializedJson = serializeJson(pdu);
|
std::string serializedJson = serializeJson(pdu);
|
||||||
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
|
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
|
||||||
|
|
||||||
return _webSocket.send(serializedJson).success;
|
return _webSocket->send(serializedJson).success;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Extract the nonce from the handshake response
|
// Extract the nonce from the handshake response
|
||||||
// use it to compute a hash during authentication
|
// use it to compute a hash during authentication
|
||||||
//
|
//
|
||||||
@ -288,16 +317,47 @@ namespace ix
|
|||||||
std::string serializedJson = serializeJson(pdu);
|
std::string serializedJson = serializeJson(pdu);
|
||||||
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
|
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
|
||||||
|
|
||||||
return _webSocket.send(serializedJson).success;
|
return _webSocket->send(serializedJson).success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CobraConnection::handleSubscriptionResponse(const Json::Value& pdu)
|
||||||
|
{
|
||||||
|
if (!pdu.isMember("body")) return false;
|
||||||
|
Json::Value body = pdu["body"];
|
||||||
|
|
||||||
|
if (!body.isMember("subscription_id")) return false;
|
||||||
|
Json::Value subscriptionId = body["subscription_id"];
|
||||||
|
|
||||||
|
if (!subscriptionId.isString()) return false;
|
||||||
|
|
||||||
|
invokeEventCallback(ix::CobraConnection_EventType_Subscribed,
|
||||||
|
std::string(), WebSocketHttpHeaders(),
|
||||||
|
subscriptionId.asString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CobraConnection::handleUnsubscriptionResponse(const Json::Value& pdu)
|
||||||
|
{
|
||||||
|
if (!pdu.isMember("body")) return false;
|
||||||
|
Json::Value body = pdu["body"];
|
||||||
|
|
||||||
|
if (!body.isMember("subscription_id")) return false;
|
||||||
|
Json::Value subscriptionId = body["subscription_id"];
|
||||||
|
|
||||||
|
if (!subscriptionId.isString()) return false;
|
||||||
|
|
||||||
|
invokeEventCallback(ix::CobraConnection_EventType_UnSubscribed,
|
||||||
|
std::string(), WebSocketHttpHeaders(),
|
||||||
|
subscriptionId.asString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool CobraConnection::handleSubscriptionData(const Json::Value& pdu)
|
bool CobraConnection::handleSubscriptionData(const Json::Value& pdu)
|
||||||
{
|
{
|
||||||
if (!pdu.isMember("body")) return false;
|
if (!pdu.isMember("body")) return false;
|
||||||
Json::Value body = pdu["body"];
|
Json::Value body = pdu["body"];
|
||||||
|
|
||||||
// Identify subscription_id, so that we can find
|
// Identify subscription_id, so that we can find
|
||||||
// which callback to execute
|
// which callback to execute
|
||||||
if (!body.isMember("subscription_id")) return false;
|
if (!body.isMember("subscription_id")) return false;
|
||||||
Json::Value subscriptionId = body["subscription_id"];
|
Json::Value subscriptionId = body["subscription_id"];
|
||||||
@ -320,13 +380,13 @@ namespace ix
|
|||||||
|
|
||||||
bool CobraConnection::connect()
|
bool CobraConnection::connect()
|
||||||
{
|
{
|
||||||
_webSocket.start();
|
_webSocket->start();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CobraConnection::isConnected() const
|
bool CobraConnection::isConnected() const
|
||||||
{
|
{
|
||||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
|
return _webSocket->getReadyState() == ix::WebSocket_ReadyState_Open;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CobraConnection::serializeJson(const Json::Value& value)
|
std::string CobraConnection::serializeJson(const Json::Value& value)
|
||||||
@ -339,7 +399,7 @@ namespace ix
|
|||||||
// publish is not thread safe as we are trying to reuse some Json objects.
|
// publish is not thread safe as we are trying to reuse some Json objects.
|
||||||
//
|
//
|
||||||
bool CobraConnection::publish(const Json::Value& channels,
|
bool CobraConnection::publish(const Json::Value& channels,
|
||||||
const Json::Value& msg)
|
const Json::Value& msg)
|
||||||
{
|
{
|
||||||
_body["channels"] = channels;
|
_body["channels"] = channels;
|
||||||
_body["message"] = msg;
|
_body["message"] = msg;
|
||||||
@ -371,7 +431,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::subscribe(const std::string& channel,
|
void CobraConnection::subscribe(const std::string& channel,
|
||||||
SubscriptionCallback cb)
|
SubscriptionCallback cb)
|
||||||
{
|
{
|
||||||
// Create and send a subscribe pdu
|
// Create and send a subscribe pdu
|
||||||
Json::Value body;
|
Json::Value body;
|
||||||
@ -381,7 +441,7 @@ namespace ix
|
|||||||
pdu["action"] = "rtm/subscribe";
|
pdu["action"] = "rtm/subscribe";
|
||||||
pdu["body"] = body;
|
pdu["body"] = body;
|
||||||
|
|
||||||
_webSocket.send(pdu.toStyledString());
|
_webSocket->send(pdu.toStyledString());
|
||||||
|
|
||||||
// Set the callback
|
// Set the callback
|
||||||
std::lock_guard<std::mutex> lock(_cbsMutex);
|
std::lock_guard<std::mutex> lock(_cbsMutex);
|
||||||
@ -400,13 +460,13 @@ namespace ix
|
|||||||
|
|
||||||
// Create and send an unsubscribe pdu
|
// Create and send an unsubscribe pdu
|
||||||
Json::Value body;
|
Json::Value body;
|
||||||
body["channel"] = channel;
|
body["subscription_id"] = channel;
|
||||||
|
|
||||||
Json::Value pdu;
|
Json::Value pdu;
|
||||||
pdu["action"] = "rtm/unsubscribe";
|
pdu["action"] = "rtm/unsubscribe";
|
||||||
pdu["body"] = body;
|
pdu["body"] = body;
|
||||||
|
|
||||||
_webSocket.send(pdu.toStyledString());
|
_webSocket->send(pdu.toStyledString());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -456,7 +516,7 @@ namespace ix
|
|||||||
|
|
||||||
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
||||||
{
|
{
|
||||||
auto webSocketSendInfo = _webSocket.send(serializedJson);
|
auto webSocketSendInfo = _webSocket->send(serializedJson);
|
||||||
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize,
|
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize,
|
||||||
false);
|
false);
|
||||||
return webSocketSendInfo.success;
|
return webSocketSendInfo.success;
|
||||||
@ -471,5 +531,5 @@ namespace ix
|
|||||||
{
|
{
|
||||||
connect();
|
connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
@ -11,19 +11,24 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <jsoncpp/json/json.h>
|
#include <jsoncpp/json/json.h>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
|
class WebSocket;
|
||||||
|
|
||||||
enum CobraConnectionEventType
|
enum CobraConnectionEventType
|
||||||
{
|
{
|
||||||
CobraConnection_EventType_Authenticated = 0,
|
CobraConnection_EventType_Authenticated = 0,
|
||||||
CobraConnection_EventType_Error = 1,
|
CobraConnection_EventType_Error = 1,
|
||||||
CobraConnection_EventType_Open = 2,
|
CobraConnection_EventType_Open = 2,
|
||||||
CobraConnection_EventType_Closed = 3
|
CobraConnection_EventType_Closed = 3,
|
||||||
|
CobraConnection_EventType_Subscribed = 4,
|
||||||
|
CobraConnection_EventType_UnSubscribed = 5
|
||||||
};
|
};
|
||||||
|
|
||||||
enum CobraConnectionPublishMode
|
enum CobraConnectionPublishMode
|
||||||
@ -35,7 +40,8 @@ namespace ix
|
|||||||
using SubscriptionCallback = std::function<void(const Json::Value&)>;
|
using SubscriptionCallback = std::function<void(const Json::Value&)>;
|
||||||
using EventCallback = std::function<void(CobraConnectionEventType,
|
using EventCallback = std::function<void(CobraConnectionEventType,
|
||||||
const std::string&,
|
const std::string&,
|
||||||
const WebSocketHttpHeaders&)>;
|
const WebSocketHttpHeaders&,
|
||||||
|
const std::string&)>;
|
||||||
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
||||||
|
|
||||||
class CobraConnection
|
class CobraConnection
|
||||||
@ -84,7 +90,7 @@ namespace ix
|
|||||||
|
|
||||||
/// Returns true only if we're connected
|
/// Returns true only if we're connected
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
|
|
||||||
/// Flush the publish queue
|
/// Flush the publish queue
|
||||||
bool flushQueue();
|
bool flushQueue();
|
||||||
|
|
||||||
@ -100,6 +106,8 @@ namespace ix
|
|||||||
bool handleHandshakeResponse(const Json::Value& data);
|
bool handleHandshakeResponse(const Json::Value& data);
|
||||||
bool sendAuthMessage(const std::string& nonce);
|
bool sendAuthMessage(const std::string& nonce);
|
||||||
bool handleSubscriptionData(const Json::Value& pdu);
|
bool handleSubscriptionData(const Json::Value& pdu);
|
||||||
|
bool handleSubscriptionResponse(const Json::Value& pdu);
|
||||||
|
bool handleUnsubscriptionResponse(const Json::Value& pdu);
|
||||||
|
|
||||||
void initWebSocketOnMessageCallback();
|
void initWebSocketOnMessageCallback();
|
||||||
|
|
||||||
@ -113,13 +121,15 @@ namespace ix
|
|||||||
/// Invoke event callbacks
|
/// Invoke event callbacks
|
||||||
void invokeEventCallback(CobraConnectionEventType eventType,
|
void invokeEventCallback(CobraConnectionEventType eventType,
|
||||||
const std::string& errorMsg = std::string(),
|
const std::string& errorMsg = std::string(),
|
||||||
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders());
|
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
|
||||||
void invokeErrorCallback(const std::string& errorMsg);
|
const std::string& subscriptionId = std::string());
|
||||||
|
void invokeErrorCallback(const std::string& errorMsg,
|
||||||
|
const std::string& serializedPdu);
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Member variables
|
/// Member variables
|
||||||
///
|
///
|
||||||
WebSocket _webSocket;
|
std::unique_ptr<WebSocket> _webSocket;
|
||||||
|
|
||||||
/// Configuration data
|
/// Configuration data
|
||||||
std::string _appkey;
|
std::string _appkey;
|
||||||
@ -148,10 +158,10 @@ namespace ix
|
|||||||
std::unordered_map<std::string, SubscriptionCallback> _cbs;
|
std::unordered_map<std::string, SubscriptionCallback> _cbs;
|
||||||
mutable std::mutex _cbsMutex;
|
mutable std::mutex _cbsMutex;
|
||||||
|
|
||||||
// Message Queue can be touched on control+background thread,
|
// Message Queue can be touched on control+background thread,
|
||||||
// protecting with a mutex.
|
// protecting with a mutex.
|
||||||
//
|
//
|
||||||
// Message queue is used when there are problems sending messages so
|
// Message queue is used when there are problems sending messages so
|
||||||
// that sending can be retried later.
|
// that sending can be retried later.
|
||||||
std::deque<std::string> _messageQueue;
|
std::deque<std::string> _messageQueue;
|
||||||
mutable std::mutex _queueMutex;
|
mutable std::mutex _queueMutex;
|
||||||
@ -159,5 +169,5 @@ namespace ix
|
|||||||
// Cap the queue size (100 elems so far -> ~100k)
|
// Cap the queue size (100 elems so far -> ~100k)
|
||||||
static constexpr size_t kQueueMaxSize = 256;
|
static constexpr size_t kQueueMaxSize = 256;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
207
ws/IXRedisClient.cpp
Normal file
207
ws/IXRedisClient.cpp
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* IXRedisClient.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXRedisClient.h"
|
||||||
|
#include <ixwebsocket/IXSocketFactory.h>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
bool RedisClient::connect(const std::string& hostname, int port)
|
||||||
|
{
|
||||||
|
bool tls = false;
|
||||||
|
std::string errorMsg;
|
||||||
|
_socket = createSocket(tls, errorMsg);
|
||||||
|
|
||||||
|
if (!_socket)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string errMsg;
|
||||||
|
return _socket->connect(hostname, port, errMsg, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RedisClient::auth(const std::string& password,
|
||||||
|
std::string& response)
|
||||||
|
{
|
||||||
|
response.clear();
|
||||||
|
|
||||||
|
if (!_socket) return false;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "AUTH ";
|
||||||
|
ss << password;
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
|
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
||||||
|
if (!sent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pollResult = _socket->isReadyToRead(-1);
|
||||||
|
if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
response = line;
|
||||||
|
return lineValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool RedisClient::publish(const std::string& channel,
|
||||||
|
const std::string& message)
|
||||||
|
{
|
||||||
|
if (!_socket) return false;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "PUBLISH ";
|
||||||
|
ss << channel;
|
||||||
|
ss << " ";
|
||||||
|
ss << message;
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
|
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
||||||
|
if (!sent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pollResult = _socket->isReadyToRead(-1);
|
||||||
|
if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
return lineValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// FIXME: we assume that redis never return errors...
|
||||||
|
//
|
||||||
|
bool RedisClient::subscribe(const std::string& channel,
|
||||||
|
const OnRedisSubscribeResponseCallback& responseCallback,
|
||||||
|
const OnRedisSubscribeCallback& callback)
|
||||||
|
{
|
||||||
|
if (!_socket) return false;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "SUBSCRIBE ";
|
||||||
|
ss << channel;
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
|
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
||||||
|
if (!sent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait 1s for the response
|
||||||
|
auto pollResult = _socket->isReadyToRead(-1);
|
||||||
|
if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the response as a single string
|
||||||
|
std::stringstream oss;
|
||||||
|
|
||||||
|
// Read the first line of the response
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
oss << line;
|
||||||
|
|
||||||
|
if (!lineValid) return false;
|
||||||
|
|
||||||
|
// There are 5 items for the subscribe repply
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
oss << line;
|
||||||
|
|
||||||
|
if (!lineValid) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseCallback(oss.str());
|
||||||
|
|
||||||
|
// Wait indefinitely for new messages
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Wait until something is ready to read
|
||||||
|
auto pollResult = _socket->isReadyToRead(-1);
|
||||||
|
if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first line of the response describe the return type,
|
||||||
|
// => *3 (an array of 3 elements)
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
if (!lineValid) return false;
|
||||||
|
|
||||||
|
int arraySize;
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << line.substr(1, line.size()-1);
|
||||||
|
ss >> arraySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are 6 items for each received message
|
||||||
|
for (int i = 0; i < arraySize; ++i)
|
||||||
|
{
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
if (!lineValid) return false;
|
||||||
|
|
||||||
|
// Messages are string, which start with a string size
|
||||||
|
// => $7 (7 bytes)
|
||||||
|
int stringSize;
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << line.substr(1, line.size()-1);
|
||||||
|
ss >> stringSize;
|
||||||
|
|
||||||
|
auto readResult = _socket->readBytes(stringSize, nullptr, nullptr);
|
||||||
|
if (!readResult.first) return false;
|
||||||
|
|
||||||
|
if (i == 2)
|
||||||
|
{
|
||||||
|
// The message is the 3rd element.
|
||||||
|
callback(readResult.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read last 2 bytes (\r\n)
|
||||||
|
char c;
|
||||||
|
_socket->readByte(&c, nullptr);
|
||||||
|
_socket->readByte(&c, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
41
ws/IXRedisClient.h
Normal file
41
ws/IXRedisClient.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* IXRedisClient.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class Socket;
|
||||||
|
|
||||||
|
class RedisClient {
|
||||||
|
public:
|
||||||
|
using OnRedisSubscribeResponseCallback = std::function<void(const std::string&)>;
|
||||||
|
using OnRedisSubscribeCallback = std::function<void(const std::string&)>;
|
||||||
|
|
||||||
|
RedisClient() = default;
|
||||||
|
~RedisClient() = default;
|
||||||
|
|
||||||
|
bool connect(const std::string& hostname,
|
||||||
|
int port);
|
||||||
|
|
||||||
|
bool auth(const std::string& password,
|
||||||
|
std::string& response);
|
||||||
|
|
||||||
|
bool publish(const std::string& channel,
|
||||||
|
const std::string& message);
|
||||||
|
|
||||||
|
bool subscribe(const std::string& channel,
|
||||||
|
const OnRedisSubscribeResponseCallback& responseCallback,
|
||||||
|
const OnRedisSubscribeCallback& callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Socket> _socket;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
178
ws/IXSentryClient.cpp
Normal file
178
ws/IXSentryClient.cpp
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* IXSentryClient.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXSentryClient.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
SentryClient::SentryClient(const std::string& dsn) :
|
||||||
|
_dsn(dsn),
|
||||||
|
_validDsn(false),
|
||||||
|
_luaFrameRegex("\t([^/]+):([0-9]+): in function '([^/]+)'")
|
||||||
|
{
|
||||||
|
const std::regex dsnRegex("(http[s]?)://([^:]+):([^@]+)@([^/]+)/([0-9]+)");
|
||||||
|
std::smatch group;
|
||||||
|
|
||||||
|
if (std::regex_match(dsn, group, dsnRegex) and group.size() == 6)
|
||||||
|
{
|
||||||
|
_validDsn = true;
|
||||||
|
|
||||||
|
const auto scheme = group.str(1);
|
||||||
|
const auto host = group.str(4);
|
||||||
|
const auto project_id = group.str(5);
|
||||||
|
_url = scheme + "://" + host + "/api/" + project_id + "/store/";
|
||||||
|
|
||||||
|
_publicKey = group.str(2);
|
||||||
|
_secretKey = group.str(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t SentryClient::getTimestamp()
|
||||||
|
{
|
||||||
|
const auto tp = std::chrono::system_clock::now();
|
||||||
|
const auto dur = tp.time_since_epoch();
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(dur).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SentryClient::getIso8601()
|
||||||
|
{
|
||||||
|
std::time_t now;
|
||||||
|
std::time(&now);
|
||||||
|
char buf[sizeof "2011-10-08T07:07:09Z"];
|
||||||
|
std::strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&now));
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SentryClient::computeAuthHeader()
|
||||||
|
{
|
||||||
|
std::string securityHeader("Sentry sentry_version=5");
|
||||||
|
securityHeader += ",sentry_client=ws/1.0.0";
|
||||||
|
securityHeader += ",sentry_timestamp=" + std::to_string(SentryClient::getTimestamp());
|
||||||
|
securityHeader += ",sentry_key=" + _publicKey;
|
||||||
|
securityHeader += ",sentry_secret=" + _secretKey;
|
||||||
|
|
||||||
|
return securityHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value SentryClient::parseLuaStackTrace(const std::string& stack)
|
||||||
|
{
|
||||||
|
Json::Value frames;
|
||||||
|
|
||||||
|
// Split by lines
|
||||||
|
std::string line;
|
||||||
|
std::stringstream tokenStream(stack);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
std::smatch group;
|
||||||
|
|
||||||
|
while (std::getline(tokenStream, line))
|
||||||
|
{
|
||||||
|
// MapScene.lua:2169: in function 'singleCB'
|
||||||
|
if (std::regex_match(line, group, _luaFrameRegex))
|
||||||
|
{
|
||||||
|
const auto fileName = group.str(1);
|
||||||
|
const auto linenoStr = group.str(2);
|
||||||
|
const auto function = group.str(3);
|
||||||
|
|
||||||
|
ss << linenoStr;
|
||||||
|
uint64_t lineno;
|
||||||
|
ss >> lineno;
|
||||||
|
|
||||||
|
Json::Value frame;
|
||||||
|
frame["lineno"] = lineno;
|
||||||
|
frame["filename"] = fileName;
|
||||||
|
frame["function"] = function;
|
||||||
|
|
||||||
|
frames.append(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SentryClient::computePayload(const Json::Value& msg)
|
||||||
|
{
|
||||||
|
Json::Value payload;
|
||||||
|
payload["platform"] = "python";
|
||||||
|
payload["sdk"]["name"] = "ws";
|
||||||
|
payload["sdk"]["version"] = "1.0.0";
|
||||||
|
payload["timestamp"] = SentryClient::getIso8601();
|
||||||
|
|
||||||
|
Json::Value exception;
|
||||||
|
exception["value"] = msg["data"]["message"];
|
||||||
|
|
||||||
|
std::string stackTraceFieldName =
|
||||||
|
(msg["id"].asString() == "game_noisytypes_id") ? "traceback" : "stack";
|
||||||
|
|
||||||
|
exception["stacktrace"]["frames"] =
|
||||||
|
parseLuaStackTrace(msg["data"][stackTraceFieldName].asString());
|
||||||
|
|
||||||
|
payload["exception"].append(exception);
|
||||||
|
|
||||||
|
Json::Value extra;
|
||||||
|
extra["cobra_event"] = msg;
|
||||||
|
|
||||||
|
exception["extra"] = extra;
|
||||||
|
|
||||||
|
return _jsonWriter.write(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SentryClient::send(const Json::Value& msg,
|
||||||
|
bool verbose)
|
||||||
|
{
|
||||||
|
HttpRequestArgs args;
|
||||||
|
args.extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||||
|
args.connectTimeout = 60;
|
||||||
|
args.transferTimeout = 5 * 60;
|
||||||
|
args.followRedirects = true;
|
||||||
|
args.verbose = verbose;
|
||||||
|
args.logger = [](const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string body = computePayload(msg);
|
||||||
|
HttpResponse out = _httpClient.post(_url, body, args);
|
||||||
|
|
||||||
|
auto statusCode = std::get<0>(out);
|
||||||
|
auto errorCode = std::get<1>(out);
|
||||||
|
auto responseHeaders = std::get<2>(out);
|
||||||
|
auto payload = std::get<3>(out);
|
||||||
|
auto errorMsg = std::get<4>(out);
|
||||||
|
auto uploadSize = std::get<5>(out);
|
||||||
|
auto downloadSize = std::get<6>(out);
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
for (auto it : responseHeaders)
|
||||||
|
{
|
||||||
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Upload size: " << uploadSize << std::endl;
|
||||||
|
std::cerr << "Download size: " << downloadSize << std::endl;
|
||||||
|
|
||||||
|
std::cerr << "Status: " << statusCode << std::endl;
|
||||||
|
if (errorCode != HttpErrorCode_Ok)
|
||||||
|
{
|
||||||
|
std::cerr << "error message: " << errorMsg << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseHeaders["Content-Type"] != "application/octet-stream")
|
||||||
|
{
|
||||||
|
std::cerr << "payload: " << payload << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusCode == 200;
|
||||||
|
}
|
||||||
|
} // namespace ix
|
47
ws/IXSentryClient.h
Normal file
47
ws/IXSentryClient.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* IXSentryClient.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <jsoncpp/json/json.h>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXHttpClient.h>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class SentryClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SentryClient(const std::string& dsn);
|
||||||
|
~SentryClient() = default;
|
||||||
|
|
||||||
|
bool send(const Json::Value& msg, bool verbose);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int64_t getTimestamp();
|
||||||
|
std::string computeAuthHeader();
|
||||||
|
std::string getIso8601();
|
||||||
|
std::string computePayload(const Json::Value& msg);
|
||||||
|
|
||||||
|
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||||
|
|
||||||
|
std::string _dsn;
|
||||||
|
bool _validDsn;
|
||||||
|
std::string _url;
|
||||||
|
|
||||||
|
// Used for authentication with a header
|
||||||
|
std::string _publicKey;
|
||||||
|
std::string _secretKey;
|
||||||
|
|
||||||
|
Json::FastWriter _jsonWriter;
|
||||||
|
|
||||||
|
std::regex _luaFrameRegex;
|
||||||
|
|
||||||
|
HttpClient _httpClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ix
|
@ -20,6 +20,8 @@ Subcommands:
|
|||||||
broadcast_server Broadcasting server
|
broadcast_server Broadcasting server
|
||||||
ping Ping pong
|
ping Ping pong
|
||||||
curl HTTP Client
|
curl HTTP Client
|
||||||
|
redis_publish Redis publisher
|
||||||
|
redis_subscribe Redis subscriber
|
||||||
```
|
```
|
||||||
|
|
||||||
## file transfer
|
## file transfer
|
||||||
@ -29,7 +31,7 @@ Subcommands:
|
|||||||
ws transfer # running on port 8080.
|
ws transfer # running on port 8080.
|
||||||
|
|
||||||
# Start receiver first
|
# Start receiver first
|
||||||
ws receive ws://localhost:8080
|
ws receive ws://localhost:8080
|
||||||
|
|
||||||
# Then send a file. File will be received and written to disk by the receiver process
|
# Then send a file. File will be received and written to disk by the receiver process
|
||||||
ws send ws://localhost:8080 /file/to/path
|
ws send ws://localhost:8080 /file/to/path
|
||||||
|
3
ws/cobra_publisher/.gitignore
vendored
3
ws/cobra_publisher/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
venv
|
|
||||||
build
|
|
||||||
node_modules
|
|
@ -1,38 +0,0 @@
|
|||||||
#
|
|
||||||
# Author: Benjamin Sergeant
|
|
||||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
#
|
|
||||||
|
|
||||||
cmake_minimum_required (VERSION 3.4.1)
|
|
||||||
project (cobra_publisher)
|
|
||||||
|
|
||||||
# There's -Weverything too for clang
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
|
|
||||||
|
|
||||||
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
|
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
|
||||||
|
|
||||||
option(USE_TLS "Add TLS support" ON)
|
|
||||||
|
|
||||||
include_directories(cobra_publisher ${OPENSSL_PREFIX}/include)
|
|
||||||
include_directories(cobra_publisher .)
|
|
||||||
|
|
||||||
add_executable(cobra_publisher
|
|
||||||
jsoncpp/jsoncpp.cpp
|
|
||||||
ixcrypto/IXHMac.cpp
|
|
||||||
ixcrypto/IXBase64.cpp
|
|
||||||
IXCobraConnection.cpp
|
|
||||||
cobra_publisher.cpp)
|
|
||||||
|
|
||||||
if (APPLE AND USE_TLS)
|
|
||||||
target_link_libraries(cobra_publisher "-framework foundation" "-framework security")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
get_filename_component(crypto_lib_path ${OPENSSL_PREFIX}/lib/libcrypto.a ABSOLUTE)
|
|
||||||
add_library(lib_crypto STATIC IMPORTED)
|
|
||||||
set_target_properties(lib_crypto PROPERTIES IMPORTED_LOCATION ${crypto_lib_path})
|
|
||||||
|
|
||||||
link_directories(/usr/local/opt/openssl/lib)
|
|
||||||
target_link_libraries(cobra_publisher ixwebsocket lib_crypto)
|
|
||||||
install(TARGETS cobra_publisher DESTINATION bin)
|
|
@ -1,6 +0,0 @@
|
|||||||
```
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake ..
|
|
||||||
make && (cd .. ; sh cobra_publisher.sh)
|
|
||||||
```
|
|
@ -1,123 +0,0 @@
|
|||||||
/*
|
|
||||||
* cobra_publisher.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <atomic>
|
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
|
||||||
#include "IXCobraConnection.h"
|
|
||||||
#include "jsoncpp/json/json.h"
|
|
||||||
|
|
||||||
void msleep(int ms)
|
|
||||||
{
|
|
||||||
std::chrono::duration<double, std::milli> duration(ms);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
if (argc != 7)
|
|
||||||
{
|
|
||||||
std::cerr << "Usage error: need 6 arguments." << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string endpoint = argv[1];
|
|
||||||
std::string appkey = argv[2];
|
|
||||||
std::string channel = argv[3];
|
|
||||||
std::string rolename = argv[4];
|
|
||||||
std::string rolesecret = argv[5];
|
|
||||||
std::string path = argv[6];
|
|
||||||
|
|
||||||
std::atomic<size_t> incomingBytes(0);
|
|
||||||
std::atomic<size_t> outgoingBytes(0);
|
|
||||||
ix::CobraConnection::setTrafficTrackerCallback(
|
|
||||||
[&incomingBytes, &outgoingBytes](size_t size, bool incoming)
|
|
||||||
{
|
|
||||||
if (incoming)
|
|
||||||
{
|
|
||||||
incomingBytes += size;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outgoingBytes += size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
bool done = false;
|
|
||||||
ix::CobraConnection cobraConnection;
|
|
||||||
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
|
|
||||||
true, false, false, 15, 15);
|
|
||||||
cobraConnection.configure(appkey, endpoint, rolename, rolesecret,
|
|
||||||
webSocketPerMessageDeflateOptions);
|
|
||||||
cobraConnection.connect();
|
|
||||||
cobraConnection.setEventCallback(
|
|
||||||
[&cobraConnection, channel, path, &done]
|
|
||||||
(ix::CobraConnectionEventType eventType,
|
|
||||||
const std::string& errMsg,
|
|
||||||
const ix::WebSocketHttpHeaders& headers)
|
|
||||||
{
|
|
||||||
if (eventType == ix::CobraConnection_EventType_Open)
|
|
||||||
{
|
|
||||||
std::cout << "Handshake Headers:" << std::endl;
|
|
||||||
for (auto it : headers)
|
|
||||||
{
|
|
||||||
std::cout << it.first << ": " << it.second << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
|
||||||
{
|
|
||||||
std::cout << "Authenticated" << std::endl;
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
std::ifstream f(path);
|
|
||||||
if (!f.is_open())
|
|
||||||
{
|
|
||||||
std::cerr << "Error while opening file: " << path << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
int n = 0;
|
|
||||||
while (getline(f, line))
|
|
||||||
{
|
|
||||||
Json::Value value;
|
|
||||||
Json::Reader reader;
|
|
||||||
reader.parse(line, value);
|
|
||||||
|
|
||||||
cobraConnection.publish(channel, value);
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
std::cerr << "#published messages: " << n << std::endl;
|
|
||||||
|
|
||||||
if (f.bad())
|
|
||||||
{
|
|
||||||
std::cerr << "Error while opening file: " << path << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
|
||||||
{
|
|
||||||
std::cerr << "Cobra Error received: " << errMsg << std::endl;
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
else if (eventType == ix::CobraConnection_EventType_Closed)
|
|
||||||
{
|
|
||||||
std::cerr << "Cobra connection closed" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
while (!done)
|
|
||||||
{
|
|
||||||
msleep(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Incoming bytes: " << incomingBytes << std::endl;
|
|
||||||
std::cout << "Outgoing bytes: " << outgoingBytes << std::endl;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
endpoint="ws://127.0.0.1:8765"
|
|
||||||
endpoint="ws://127.0.0.1:5678"
|
|
||||||
appkey="appkey"
|
|
||||||
channel="foo"
|
|
||||||
rolename="a_role"
|
|
||||||
rolesecret="a_secret"
|
|
||||||
filename=${FILENAME:=events.jsonl}
|
|
||||||
|
|
||||||
build/cobra_publisher $endpoint $appkey $channel $rolename $rolesecret $filename
|
|
@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* devnull_server.js
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
const WebSocket = require('ws');
|
|
||||||
|
|
||||||
let wss = new WebSocket.Server({ port: 5678, perMessageDeflate: true })
|
|
||||||
|
|
||||||
wss.on('connection', (ws) => {
|
|
||||||
|
|
||||||
let handshake = false
|
|
||||||
let authenticated = false
|
|
||||||
|
|
||||||
ws.on('message', (data) => {
|
|
||||||
|
|
||||||
console.log(data.toString('utf-8'))
|
|
||||||
|
|
||||||
if (!handshake) {
|
|
||||||
let response = {
|
|
||||||
"action": "auth/handshake/ok",
|
|
||||||
"body": {
|
|
||||||
"data": {
|
|
||||||
"nonce": "MTI0Njg4NTAyMjYxMzgxMzgzMg==",
|
|
||||||
"version": "0.0.24"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": 1
|
|
||||||
}
|
|
||||||
ws.send(JSON.stringify(response))
|
|
||||||
handshake = true
|
|
||||||
} else if (!authenticated) {
|
|
||||||
let response = {
|
|
||||||
"action": "auth/authenticate/ok",
|
|
||||||
"body": {},
|
|
||||||
"id": 2
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.send(JSON.stringify(response))
|
|
||||||
authenticated = true
|
|
||||||
} else {
|
|
||||||
console.log(data)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import asyncio
|
|
||||||
import websockets
|
|
||||||
|
|
||||||
|
|
||||||
async def echo(websocket, path):
|
|
||||||
handshake = False
|
|
||||||
authenticated = False
|
|
||||||
|
|
||||||
async for message in websocket:
|
|
||||||
print(message)
|
|
||||||
|
|
||||||
if not handshake:
|
|
||||||
response = {
|
|
||||||
"action": "auth/handshake/ok",
|
|
||||||
"body": {
|
|
||||||
"data": {
|
|
||||||
"nonce": "MTI0Njg4NTAyMjYxMzgxMzgzMg==",
|
|
||||||
"version": "0.0.24"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": 1
|
|
||||||
}
|
|
||||||
await websocket.send(json.dumps(response))
|
|
||||||
handshake = True
|
|
||||||
|
|
||||||
elif not authenticated:
|
|
||||||
response = {
|
|
||||||
"action": "auth/authenticate/ok",
|
|
||||||
"body": {},
|
|
||||||
"id": 2
|
|
||||||
}
|
|
||||||
|
|
||||||
await websocket.send(json.dumps(response))
|
|
||||||
authenticated = True
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.get_event_loop().run_until_complete(
|
|
||||||
websockets.serve(echo, 'localhost', 5678))
|
|
||||||
asyncio.get_event_loop().run_forever()
|
|
@ -1,3 +0,0 @@
|
|||||||
{"array":[1,2,3],"boolean":true,"color":"#82b92c","null":null,"number":123,"object":{"a":"b","c":"d","e":"f"},"string":"Foo"}
|
|
||||||
{"array":[1,2,3],"boolean":true,"color":"#82b92c","null":null,"number":123,"object":{"a":"b","c":"d","e":"f"},"string":"Bar"}
|
|
||||||
{"array":[1,2,3],"boolean":true,"color":"#82b92c","null":null,"number":123,"object":{"a":"b","c":"d","e":"f"},"string":"Baz"}
|
|
@ -1,333 +0,0 @@
|
|||||||
/// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/).
|
|
||||||
/// It is intended to be used with #include "json/json-forwards.h"
|
|
||||||
/// This header provides forward declaration for all JsonCpp types.
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// Beginning of content of file: LICENSE
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/*
|
|
||||||
The JsonCpp library's source code, including accompanying documentation,
|
|
||||||
tests and demonstration applications, are licensed under the following
|
|
||||||
conditions...
|
|
||||||
|
|
||||||
Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all
|
|
||||||
jurisdictions which recognize such a disclaimer. In such jurisdictions,
|
|
||||||
this software is released into the Public Domain.
|
|
||||||
|
|
||||||
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
|
|
||||||
2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and
|
|
||||||
The JsonCpp Authors, and is released under the terms of the MIT License (see below).
|
|
||||||
|
|
||||||
In jurisdictions which recognize Public Domain property, the user of this
|
|
||||||
software may choose to accept it either as 1) Public Domain, 2) under the
|
|
||||||
conditions of the MIT License (see below), or 3) under the terms of dual
|
|
||||||
Public Domain/MIT License conditions described here, as they choose.
|
|
||||||
|
|
||||||
The MIT License is about as close to Public Domain as a license can get, and is
|
|
||||||
described in clear, concise terms at:
|
|
||||||
|
|
||||||
http://en.wikipedia.org/wiki/MIT_License
|
|
||||||
|
|
||||||
The full text of the MIT License follows:
|
|
||||||
|
|
||||||
========================================================================
|
|
||||||
Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
|
||||||
|
|
||||||
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.
|
|
||||||
========================================================================
|
|
||||||
(END LICENSE TEXT)
|
|
||||||
|
|
||||||
The MIT license is compatible with both the GPL and commercial
|
|
||||||
software, affording one all of the rights of Public Domain with the
|
|
||||||
minor nuisance of being required to keep the above copyright notice
|
|
||||||
and license text in the source code. Note also that by accepting the
|
|
||||||
Public Domain "license" you can re-license your copy using whatever
|
|
||||||
license you like.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// End of content of file: LICENSE
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
|
|
||||||
# define JSON_FORWARD_AMALGATED_H_INCLUDED
|
|
||||||
/// If defined, indicates that the source file is amalgated
|
|
||||||
/// to prevent private header inclusion.
|
|
||||||
#define JSON_IS_AMALGAMATION
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// Beginning of content of file: include/json/config.h
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
|
||||||
// Distributed under MIT license, or public domain if desired and
|
|
||||||
// recognized in your jurisdiction.
|
|
||||||
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
|
||||||
|
|
||||||
#ifndef JSON_CONFIG_H_INCLUDED
|
|
||||||
#define JSON_CONFIG_H_INCLUDED
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string> //typedef String
|
|
||||||
#include <stdint.h> //typedef int64_t, uint64_t
|
|
||||||
|
|
||||||
/// If defined, indicates that json library is embedded in CppTL library.
|
|
||||||
//# define JSON_IN_CPPTL 1
|
|
||||||
|
|
||||||
/// If defined, indicates that json may leverage CppTL library
|
|
||||||
//# define JSON_USE_CPPTL 1
|
|
||||||
/// If defined, indicates that cpptl vector based map should be used instead of
|
|
||||||
/// std::map
|
|
||||||
/// as Value container.
|
|
||||||
//# define JSON_USE_CPPTL_SMALLMAP 1
|
|
||||||
|
|
||||||
// If non-zero, the library uses exceptions to report bad input instead of C
|
|
||||||
// assertion macros. The default is to use exceptions.
|
|
||||||
#ifndef JSON_USE_EXCEPTION
|
|
||||||
#define JSON_USE_EXCEPTION 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// If defined, indicates that the source file is amalgated
|
|
||||||
/// to prevent private header inclusion.
|
|
||||||
/// Remarks: it is automatically defined in the generated amalgated header.
|
|
||||||
// #define JSON_IS_AMALGAMATION
|
|
||||||
|
|
||||||
#ifdef JSON_IN_CPPTL
|
|
||||||
#include <cpptl/config.h>
|
|
||||||
#ifndef JSON_USE_CPPTL
|
|
||||||
#define JSON_USE_CPPTL 1
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_IN_CPPTL
|
|
||||||
#define JSON_API CPPTL_API
|
|
||||||
#elif defined(JSON_DLL_BUILD)
|
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
|
||||||
#define JSON_API __declspec(dllexport)
|
|
||||||
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
|
||||||
#endif // if defined(_MSC_VER)
|
|
||||||
#elif defined(JSON_DLL)
|
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
|
||||||
#define JSON_API __declspec(dllimport)
|
|
||||||
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
|
||||||
#endif // if defined(_MSC_VER)
|
|
||||||
#endif // ifdef JSON_IN_CPPTL
|
|
||||||
#if !defined(JSON_API)
|
|
||||||
#define JSON_API
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
|
|
||||||
// integer
|
|
||||||
// Storages, and 64 bits integer support is disabled.
|
|
||||||
// #define JSON_NO_INT64 1
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) // MSVC
|
|
||||||
# if _MSC_VER <= 1200 // MSVC 6
|
|
||||||
// Microsoft Visual Studio 6 only support conversion from __int64 to double
|
|
||||||
// (no conversion from unsigned __int64).
|
|
||||||
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
|
||||||
// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
|
|
||||||
// characters in the debug information)
|
|
||||||
// All projects I've ever seen with VS6 were using this globally (not bothering
|
|
||||||
// with pragma push/pop).
|
|
||||||
# pragma warning(disable : 4786)
|
|
||||||
# endif // MSVC 6
|
|
||||||
|
|
||||||
# if _MSC_VER >= 1500 // MSVC 2008
|
|
||||||
/// Indicates that the following function is deprecated.
|
|
||||||
# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
|
|
||||||
# endif
|
|
||||||
|
|
||||||
#endif // defined(_MSC_VER)
|
|
||||||
|
|
||||||
// In c++11 the override keyword allows you to explicity define that a function
|
|
||||||
// is intended to override the base-class version. This makes the code more
|
|
||||||
// managable and fixes a set of common hard-to-find bugs.
|
|
||||||
#if __cplusplus >= 201103L
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT noexcept
|
|
||||||
#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT throw()
|
|
||||||
#elif defined(_MSC_VER) && _MSC_VER >= 1900
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT noexcept
|
|
||||||
#else
|
|
||||||
# define JSONCPP_OVERRIDE
|
|
||||||
# define JSONCPP_NOEXCEPT throw()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef JSON_HAS_RVALUE_REFERENCES
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // MSVC >= 2010
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
#if __has_feature(cxx_rvalue_references)
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // has_feature
|
|
||||||
|
|
||||||
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
|
||||||
#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L)
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // GXX_EXPERIMENTAL
|
|
||||||
|
|
||||||
#endif // __clang__ || __GNUC__
|
|
||||||
|
|
||||||
#endif // not defined JSON_HAS_RVALUE_REFERENCES
|
|
||||||
|
|
||||||
#ifndef JSON_HAS_RVALUE_REFERENCES
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
# if __has_extension(attribute_deprecated_with_message)
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
|
||||||
# endif
|
|
||||||
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
|
||||||
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
|
||||||
# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
|
|
||||||
# endif // GNUC version
|
|
||||||
#endif // __clang__ || __GNUC__
|
|
||||||
|
|
||||||
#if !defined(JSONCPP_DEPRECATED)
|
|
||||||
#define JSONCPP_DEPRECATED(message)
|
|
||||||
#endif // if !defined(JSONCPP_DEPRECATED)
|
|
||||||
|
|
||||||
#if __GNUC__ >= 6
|
|
||||||
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
|
|
||||||
# include "version.h"
|
|
||||||
|
|
||||||
# if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
# include "allocator.h" //typedef Allocator
|
|
||||||
# endif
|
|
||||||
|
|
||||||
#endif // if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
|
|
||||||
namespace Json {
|
|
||||||
typedef int Int;
|
|
||||||
typedef unsigned int UInt;
|
|
||||||
#if defined(JSON_NO_INT64)
|
|
||||||
typedef int LargestInt;
|
|
||||||
typedef unsigned int LargestUInt;
|
|
||||||
#undef JSON_HAS_INT64
|
|
||||||
#else // if defined(JSON_NO_INT64)
|
|
||||||
// For Microsoft Visual use specific types as long long is not supported
|
|
||||||
#if defined(_MSC_VER) // Microsoft Visual Studio
|
|
||||||
typedef __int64 Int64;
|
|
||||||
typedef unsigned __int64 UInt64;
|
|
||||||
#else // if defined(_MSC_VER) // Other platforms, use long long
|
|
||||||
typedef int64_t Int64;
|
|
||||||
typedef uint64_t UInt64;
|
|
||||||
#endif // if defined(_MSC_VER)
|
|
||||||
typedef Int64 LargestInt;
|
|
||||||
typedef UInt64 LargestUInt;
|
|
||||||
#define JSON_HAS_INT64
|
|
||||||
#endif // if defined(JSON_NO_INT64)
|
|
||||||
#if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
#define JSONCPP_STRING std::basic_string<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_OSTREAM std::basic_ostream<char, std::char_traits<char>>
|
|
||||||
#define JSONCPP_ISTRINGSTREAM std::basic_istringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_ISTREAM std::istream
|
|
||||||
#else
|
|
||||||
#define JSONCPP_STRING std::string
|
|
||||||
#define JSONCPP_OSTRINGSTREAM std::ostringstream
|
|
||||||
#define JSONCPP_OSTREAM std::ostream
|
|
||||||
#define JSONCPP_ISTRINGSTREAM std::istringstream
|
|
||||||
#define JSONCPP_ISTREAM std::istream
|
|
||||||
#endif // if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
} // end namespace Json
|
|
||||||
|
|
||||||
#endif // JSON_CONFIG_H_INCLUDED
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// End of content of file: include/json/config.h
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// Beginning of content of file: include/json/forwards.h
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
|
||||||
// Distributed under MIT license, or public domain if desired and
|
|
||||||
// recognized in your jurisdiction.
|
|
||||||
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
|
||||||
|
|
||||||
#ifndef JSON_FORWARDS_H_INCLUDED
|
|
||||||
#define JSON_FORWARDS_H_INCLUDED
|
|
||||||
|
|
||||||
#if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
#include "config.h"
|
|
||||||
#endif // if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
|
|
||||||
namespace Json {
|
|
||||||
|
|
||||||
// writer.h
|
|
||||||
class FastWriter;
|
|
||||||
class StyledWriter;
|
|
||||||
|
|
||||||
// reader.h
|
|
||||||
class Reader;
|
|
||||||
|
|
||||||
// features.h
|
|
||||||
class Features;
|
|
||||||
|
|
||||||
// value.h
|
|
||||||
typedef unsigned int ArrayIndex;
|
|
||||||
class StaticString;
|
|
||||||
class Path;
|
|
||||||
class PathArgument;
|
|
||||||
class Value;
|
|
||||||
class ValueIteratorBase;
|
|
||||||
class ValueIterator;
|
|
||||||
class ValueConstIterator;
|
|
||||||
|
|
||||||
} // namespace Json
|
|
||||||
|
|
||||||
#endif // JSON_FORWARDS_H_INCLUDED
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// End of content of file: include/json/forwards.h
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,11 @@
|
|||||||
#include "IXHMac.h"
|
#include "IXHMac.h"
|
||||||
#include "IXBase64.h"
|
#include "IXBase64.h"
|
||||||
|
|
||||||
#include <openssl/hmac.h>
|
#ifdef __APPLE__
|
||||||
|
# include <CommonCrypto/CommonHMAC.h>
|
||||||
|
#else
|
||||||
|
# include <openssl/hmac.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -15,10 +19,17 @@ namespace ix
|
|||||||
constexpr size_t hashSize = 16;
|
constexpr size_t hashSize = 16;
|
||||||
unsigned char hash[hashSize];
|
unsigned char hash[hashSize];
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
CCHmac(kCCHmacAlgMD5,
|
||||||
|
key.c_str(), key.size(),
|
||||||
|
data.c_str(), data.size(),
|
||||||
|
&hash);
|
||||||
|
#else
|
||||||
HMAC(EVP_md5(),
|
HMAC(EVP_md5(),
|
||||||
key.c_str(), (int) key.size(),
|
key.c_str(), (int) key.size(),
|
||||||
(unsigned char *) data.c_str(), (int) data.size(),
|
(unsigned char *) data.c_str(), (int) data.size(),
|
||||||
(unsigned char *) hash, nullptr);
|
(unsigned char *) hash, nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
std::string hashString(reinterpret_cast<char*>(hash), hashSize);
|
std::string hashString(reinterpret_cast<char*>(hash), hashSize);
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "6.1.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz",
|
||||||
"integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==",
|
"integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"async-limiter": "1.0.0"
|
"async-limiter": "1.0.0"
|
||||||
}
|
}
|
@ -1,11 +1,21 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Handle Ctrl-C by killing all sub-processing AND exiting
|
||||||
|
trap cleanup INT
|
||||||
|
|
||||||
|
function cleanup {
|
||||||
|
kill `cat /tmp/ws_test/pidfile.transfer`
|
||||||
|
kill `cat /tmp/ws_test/pidfile.receive`
|
||||||
|
kill `cat /tmp/ws_test/pidfile.send`
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
rm -rf /tmp/ws_test
|
rm -rf /tmp/ws_test
|
||||||
mkdir -p /tmp/ws_test
|
mkdir -p /tmp/ws_test
|
||||||
|
|
||||||
# Start a transport server
|
# Start a transport server
|
||||||
cd /tmp/ws_test
|
cd /tmp/ws_test
|
||||||
ws transfer --port 8090 --pidfile /tmp/ws_test/pidfile &
|
ws transfer --port 8090 --pidfile /tmp/ws_test/pidfile.transfer &
|
||||||
|
|
||||||
# Wait until the transfer server is up
|
# Wait until the transfer server is up
|
||||||
while true
|
while true
|
||||||
@ -14,21 +24,21 @@ do
|
|||||||
echo "Transfer server up and running"
|
echo "Transfer server up and running"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
echo "sleep ..."
|
echo "sleep ... wait for transfer server"
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
done
|
done
|
||||||
|
|
||||||
# Start a receiver
|
# Start a receiver
|
||||||
mkdir -p /tmp/ws_test/receive
|
mkdir -p /tmp/ws_test/receive
|
||||||
cd /tmp/ws_test/receive
|
cd /tmp/ws_test/receive
|
||||||
ws receive --delay 5 ws://127.0.0.1:8090 &
|
ws receive --delay 10 ws://127.0.0.1:8090 --pidfile /tmp/ws_test/pidfile.receive &
|
||||||
|
|
||||||
mkdir /tmp/ws_test/send
|
mkdir /tmp/ws_test/send
|
||||||
cd /tmp/ws_test/send
|
cd /tmp/ws_test/send
|
||||||
dd if=/dev/urandom of=20M_file count=20000 bs=1024
|
dd if=/dev/urandom of=20M_file count=20000 bs=1024
|
||||||
|
|
||||||
# Start the sender job
|
# Start the sender job
|
||||||
ws send ws://127.0.0.1:8090 20M_file
|
ws send --pidfile /tmp/ws_test/pidfile.send ws://127.0.0.1:8090 20M_file
|
||||||
|
|
||||||
# Wait until the file has been written to disk
|
# Wait until the file has been written to disk
|
||||||
while true
|
while true
|
||||||
@ -37,7 +47,7 @@ do
|
|||||||
echo "Received file does exists, exiting loop"
|
echo "Received file does exists, exiting loop"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
echo "sleep ..."
|
echo "sleep ... wait for output file"
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -48,4 +58,7 @@ cksum /tmp/ws_test/receive/20M_file
|
|||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
kill `cat /tmp/ws_test/pidfile`
|
kill `cat /tmp/ws_test/pidfile.transfer`
|
||||||
|
kill `cat /tmp/ws_test/pidfile.receive`
|
||||||
|
kill `cat /tmp/ws_test/pidfile.send`
|
||||||
|
|
||||||
|
25
ws/test_ws_redis.sh
Normal file
25
ws/test_ws_redis.sh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Handle Ctrl-C by killing all sub-processing AND exiting
|
||||||
|
trap cleanup INT
|
||||||
|
|
||||||
|
function cleanup {
|
||||||
|
kill `cat /tmp/pidfile.subscribe`
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
REDIS_HOST=${REDIS_HOST:=localhost}
|
||||||
|
|
||||||
|
ws redis_subscribe --pidfile /tmp/pidfile.subscribe --host $REDIS_HOST foo &
|
||||||
|
|
||||||
|
# Wait for the subscriber to be ready
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
# Now publish messages
|
||||||
|
ws redis_publish -c 100000 --host ${REDIS_HOST} foo bar
|
||||||
|
|
||||||
|
# Wait a little for all messages to be received
|
||||||
|
sleep 1.5
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
cleanup
|
118
ws/ws.cpp
118
ws/ws.cpp
@ -35,26 +35,43 @@ int main(int argc, char** argv)
|
|||||||
std::string output;
|
std::string output;
|
||||||
std::string hostname("127.0.0.1");
|
std::string hostname("127.0.0.1");
|
||||||
std::string pidfile;
|
std::string pidfile;
|
||||||
|
std::string channel;
|
||||||
|
std::string message;
|
||||||
|
std::string password;
|
||||||
|
std::string appkey;
|
||||||
|
std::string endpoint;
|
||||||
|
std::string rolename;
|
||||||
|
std::string rolesecret;
|
||||||
|
std::string prefix("ws.test.v0");
|
||||||
|
std::string fields;
|
||||||
|
std::string dsn;
|
||||||
bool headersOnly = false;
|
bool headersOnly = false;
|
||||||
bool followRedirects = false;
|
bool followRedirects = false;
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
bool save = false;
|
bool save = false;
|
||||||
bool compress = false;
|
bool compress = false;
|
||||||
|
bool strict = false;
|
||||||
int port = 8080;
|
int port = 8080;
|
||||||
|
int redisPort = 6379;
|
||||||
|
int statsdPort = 8125;
|
||||||
int connectTimeOut = 60;
|
int connectTimeOut = 60;
|
||||||
int transferTimeout = 1800;
|
int transferTimeout = 1800;
|
||||||
int maxRedirects = 5;
|
int maxRedirects = 5;
|
||||||
int delayMs = -1;
|
int delayMs = -1;
|
||||||
|
int count = 1;
|
||||||
|
int jobs = 4;
|
||||||
|
|
||||||
CLI::App* sendApp = app.add_subcommand("send", "Send a file");
|
CLI::App* sendApp = app.add_subcommand("send", "Send a file");
|
||||||
sendApp->add_option("url", url, "Connection url")->required();
|
sendApp->add_option("url", url, "Connection url")->required();
|
||||||
sendApp->add_option("path", path, "Path to the file to send")
|
sendApp->add_option("path", path, "Path to the file to send")
|
||||||
->required()->check(CLI::ExistingPath);
|
->required()->check(CLI::ExistingPath);
|
||||||
|
sendApp->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
CLI::App* receiveApp = app.add_subcommand("receive", "Receive a file");
|
CLI::App* receiveApp = app.add_subcommand("receive", "Receive a file");
|
||||||
receiveApp->add_option("url", url, "Connection url")->required();
|
receiveApp->add_option("url", url, "Connection url")->required();
|
||||||
receiveApp->add_option("--delay", delayMs, "Delay (ms) to wait after receiving a fragment"
|
receiveApp->add_option("--delay", delayMs, "Delay (ms) to wait after receiving a fragment"
|
||||||
" to artificially slow down the receiver");
|
" to artificially slow down the receiver");
|
||||||
|
receiveApp->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
CLI::App* transferApp = app.add_subcommand("transfer", "Broadcasting server");
|
CLI::App* transferApp = app.add_subcommand("transfer", "Broadcasting server");
|
||||||
transferApp->add_option("--port", port, "Connection url");
|
transferApp->add_option("--port", port, "Connection url");
|
||||||
@ -94,21 +111,71 @@ int main(int argc, char** argv)
|
|||||||
httpClientApp->add_option("--connect-timeout", connectTimeOut, "Connection timeout");
|
httpClientApp->add_option("--connect-timeout", connectTimeOut, "Connection timeout");
|
||||||
httpClientApp->add_option("--transfer-timeout", transferTimeout, "Transfer timeout");
|
httpClientApp->add_option("--transfer-timeout", transferTimeout, "Transfer timeout");
|
||||||
|
|
||||||
|
CLI::App* redisPublishApp = app.add_subcommand("redis_publish", "Redis publisher");
|
||||||
|
redisPublishApp->add_option("--port", redisPort, "Port");
|
||||||
|
redisPublishApp->add_option("--host", hostname, "Hostname");
|
||||||
|
redisPublishApp->add_option("--password", password, "Password");
|
||||||
|
redisPublishApp->add_option("channel", channel, "Channel")->required();
|
||||||
|
redisPublishApp->add_option("message", message, "Message")->required();
|
||||||
|
redisPublishApp->add_option("-c", count, "Count");
|
||||||
|
|
||||||
|
CLI::App* redisSubscribeApp = app.add_subcommand("redis_subscribe", "Redis subscriber");
|
||||||
|
redisSubscribeApp->add_option("--port", redisPort, "Port");
|
||||||
|
redisSubscribeApp->add_option("--host", hostname, "Hostname");
|
||||||
|
redisSubscribeApp->add_option("--password", password, "Password");
|
||||||
|
redisSubscribeApp->add_option("channel", channel, "Channel")->required();
|
||||||
|
redisSubscribeApp->add_flag("-v", verbose, "Verbose");
|
||||||
|
redisSubscribeApp->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
|
CLI::App* cobraSubscribeApp = app.add_subcommand("cobra_subscribe", "Cobra subscriber");
|
||||||
|
cobraSubscribeApp->add_option("--appkey", appkey, "Appkey");
|
||||||
|
cobraSubscribeApp->add_option("--endpoint", endpoint, "Endpoint");
|
||||||
|
cobraSubscribeApp->add_option("--rolename", rolename, "Role name");
|
||||||
|
cobraSubscribeApp->add_option("--rolesecret", rolesecret, "Role secret");
|
||||||
|
cobraSubscribeApp->add_option("channel", channel, "Channel")->required();
|
||||||
|
cobraSubscribeApp->add_flag("-v", verbose, "Verbose");
|
||||||
|
cobraSubscribeApp->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
|
CLI::App* cobra2statsd = app.add_subcommand("cobra_to_statsd", "Cobra to statsd");
|
||||||
|
cobra2statsd->add_option("--appkey", appkey, "Appkey");
|
||||||
|
cobra2statsd->add_option("--endpoint", endpoint, "Endpoint");
|
||||||
|
cobra2statsd->add_option("--rolename", rolename, "Role name");
|
||||||
|
cobra2statsd->add_option("--rolesecret", rolesecret, "Role secret");
|
||||||
|
cobra2statsd->add_option("--host", hostname, "Statsd host");
|
||||||
|
cobra2statsd->add_option("--port", statsdPort, "Statsd port");
|
||||||
|
cobra2statsd->add_option("--prefix", prefix, "Statsd prefix");
|
||||||
|
cobra2statsd->add_option("--fields", fields, "Extract fields for naming the event")->join();
|
||||||
|
cobra2statsd->add_option("channel", channel, "Channel")->required();
|
||||||
|
cobra2statsd->add_flag("-v", verbose, "Verbose");
|
||||||
|
cobra2statsd->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
|
CLI::App* cobra2sentry = app.add_subcommand("cobra_to_sentry", "Cobra to sentry");
|
||||||
|
cobra2sentry->add_option("--appkey", appkey, "Appkey");
|
||||||
|
cobra2sentry->add_option("--endpoint", endpoint, "Endpoint");
|
||||||
|
cobra2sentry->add_option("--rolename", rolename, "Role name");
|
||||||
|
cobra2sentry->add_option("--rolesecret", rolesecret, "Role secret");
|
||||||
|
cobra2sentry->add_option("--dsn", dsn, "Sentry DSN");
|
||||||
|
cobra2sentry->add_option("--jobs", jobs, "Number of thread sending events to Sentry");
|
||||||
|
cobra2sentry->add_option("channel", channel, "Channel")->required();
|
||||||
|
cobra2sentry->add_flag("-v", verbose, "Verbose");
|
||||||
|
cobra2sentry->add_flag("-s", strict, "Strict mode. Error out when sending to sentry fails");
|
||||||
|
cobra2sentry->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
CLI11_PARSE(app, argc, argv);
|
CLI11_PARSE(app, argc, argv);
|
||||||
|
|
||||||
|
// pid file handling
|
||||||
|
if (!pidfile.empty())
|
||||||
|
{
|
||||||
|
unlink(pidfile.c_str());
|
||||||
|
|
||||||
|
std::ofstream f;
|
||||||
|
f.open(pidfile);
|
||||||
|
f << getpid();
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
|
||||||
if (app.got_subcommand("transfer"))
|
if (app.got_subcommand("transfer"))
|
||||||
{
|
{
|
||||||
// pid file handling
|
|
||||||
if (!pidfile.empty())
|
|
||||||
{
|
|
||||||
unlink(pidfile.c_str());
|
|
||||||
|
|
||||||
std::ofstream f;
|
|
||||||
f.open(pidfile);
|
|
||||||
f << getpid();
|
|
||||||
f.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ix::ws_transfer_main(port, hostname);
|
return ix::ws_transfer_main(port, hostname);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("send"))
|
else if (app.got_subcommand("send"))
|
||||||
@ -147,6 +214,35 @@ int main(int argc, char** argv)
|
|||||||
followRedirects, maxRedirects, verbose,
|
followRedirects, maxRedirects, verbose,
|
||||||
save, output, compress);
|
save, output, compress);
|
||||||
}
|
}
|
||||||
|
else if (app.got_subcommand("redis_publish"))
|
||||||
|
{
|
||||||
|
return ix::ws_redis_publish_main(hostname, redisPort, password,
|
||||||
|
channel, message, count);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("redis_subscribe"))
|
||||||
|
{
|
||||||
|
return ix::ws_redis_subscribe_main(hostname, redisPort, password, channel, verbose);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("cobra_subscribe"))
|
||||||
|
{
|
||||||
|
return ix::ws_cobra_subscribe_main(appkey, endpoint,
|
||||||
|
rolename, rolesecret,
|
||||||
|
channel, verbose);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("cobra_to_statsd"))
|
||||||
|
{
|
||||||
|
return ix::ws_cobra_to_statsd_main(appkey, endpoint,
|
||||||
|
rolename, rolesecret,
|
||||||
|
channel, hostname, statsdPort,
|
||||||
|
prefix, fields, verbose);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("cobra_to_sentry"))
|
||||||
|
{
|
||||||
|
return ix::ws_cobra_to_sentry_main(appkey, endpoint,
|
||||||
|
rolename, rolesecret,
|
||||||
|
channel, dsn,
|
||||||
|
verbose, strict, jobs);
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
41
ws/ws.h
41
ws/ws.h
@ -39,4 +39,45 @@ namespace ix
|
|||||||
|
|
||||||
int ws_send_main(const std::string& url,
|
int ws_send_main(const std::string& url,
|
||||||
const std::string& path);
|
const std::string& path);
|
||||||
|
|
||||||
|
int ws_redis_publish_main(const std::string& hostname,
|
||||||
|
int port,
|
||||||
|
const std::string& password,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& message,
|
||||||
|
int count);
|
||||||
|
|
||||||
|
int ws_redis_subscribe_main(const std::string& hostname,
|
||||||
|
int port,
|
||||||
|
const std::string& password,
|
||||||
|
const std::string& channel,
|
||||||
|
bool verbose);
|
||||||
|
|
||||||
|
int ws_cobra_subscribe_main(const std::string& appkey,
|
||||||
|
const std::string& endpoint,
|
||||||
|
const std::string& rolename,
|
||||||
|
const std::string& rolesecret,
|
||||||
|
const std::string& channel,
|
||||||
|
bool verbose);
|
||||||
|
|
||||||
|
int ws_cobra_to_statsd_main(const std::string& appkey,
|
||||||
|
const std::string& endpoint,
|
||||||
|
const std::string& rolename,
|
||||||
|
const std::string& rolesecret,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& host,
|
||||||
|
int port,
|
||||||
|
const std::string& prefix,
|
||||||
|
const std::string& fields,
|
||||||
|
bool verbose);
|
||||||
|
|
||||||
|
int ws_cobra_to_sentry_main(const std::string& appkey,
|
||||||
|
const std::string& endpoint,
|
||||||
|
const std::string& rolename,
|
||||||
|
const std::string& rolesecret,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& dsn,
|
||||||
|
bool verbose,
|
||||||
|
bool strict,
|
||||||
|
int jobs);
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,11 @@ namespace ix
|
|||||||
ix::WebSocketServer server(port, hostname);
|
ix::WebSocketServer server(port, hostname);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server](std::shared_ptr<WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -30,6 +31,7 @@ namespace ix
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
std::cerr << "New connection" << std::endl;
|
std::cerr << "New connection" << std::endl;
|
||||||
|
std::cerr << "id: " << connectionState->getId() << std::endl;
|
||||||
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
||||||
std::cerr << "Headers:" << std::endl;
|
std::cerr << "Headers:" << std::endl;
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
|
76
ws/ws_cobra_subscribe.cpp
Normal file
76
ws/ws_cobra_subscribe.cpp
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* ws_cobra_subscribe.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include "IXCobraConnection.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int ws_cobra_subscribe_main(const std::string& appkey,
|
||||||
|
const std::string& endpoint,
|
||||||
|
const std::string& rolename,
|
||||||
|
const std::string& rolesecret,
|
||||||
|
const std::string& channel,
|
||||||
|
bool verbose)
|
||||||
|
{
|
||||||
|
|
||||||
|
ix::CobraConnection conn;
|
||||||
|
conn.configure(appkey, endpoint,
|
||||||
|
rolename, rolesecret,
|
||||||
|
ix::WebSocketPerMessageDeflateOptions(true));
|
||||||
|
conn.connect();
|
||||||
|
|
||||||
|
Json::FastWriter jsonWriter;
|
||||||
|
|
||||||
|
conn.setEventCallback(
|
||||||
|
[&conn, &channel, &jsonWriter]
|
||||||
|
(ix::CobraConnectionEventType eventType,
|
||||||
|
const std::string& errMsg,
|
||||||
|
const ix::WebSocketHttpHeaders& headers,
|
||||||
|
const std::string& subscriptionId)
|
||||||
|
{
|
||||||
|
if (eventType == ix::CobraConnection_EventType_Open)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: connected" << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber authenticated" << std::endl;
|
||||||
|
conn.subscribe(channel,
|
||||||
|
[&jsonWriter](const Json::Value& msg)
|
||||||
|
{
|
||||||
|
// std::cout << "Received message" << std::endl;
|
||||||
|
std::cout << jsonWriter.write(msg) << std::endl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: subscribed to channel " << subscriptionId << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: unsubscribed from channel " << subscriptionId << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Error)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: error" << errMsg << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::chrono::duration<double, std::milli> duration(10);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
189
ws/ws_cobra_to_sentry.cpp
Normal file
189
ws/ws_cobra_to_sentry.cpp
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* ws_cobra_to_sentry.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
#include <queue>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include "IXCobraConnection.h"
|
||||||
|
|
||||||
|
#include "IXSentryClient.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int ws_cobra_to_sentry_main(const std::string& appkey,
|
||||||
|
const std::string& endpoint,
|
||||||
|
const std::string& rolename,
|
||||||
|
const std::string& rolesecret,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& dsn,
|
||||||
|
bool verbose,
|
||||||
|
bool strict,
|
||||||
|
int jobs)
|
||||||
|
{
|
||||||
|
ix::CobraConnection conn;
|
||||||
|
conn.configure(appkey, endpoint,
|
||||||
|
rolename, rolesecret,
|
||||||
|
ix::WebSocketPerMessageDeflateOptions(true));
|
||||||
|
conn.connect();
|
||||||
|
|
||||||
|
Json::FastWriter jsonWriter;
|
||||||
|
std::atomic<uint64_t> sentCount(0);
|
||||||
|
std::atomic<uint64_t> receivedCount(0);
|
||||||
|
std::atomic<bool> errorSending(false);
|
||||||
|
std::atomic<bool> stop(false);
|
||||||
|
|
||||||
|
std::mutex conditionVariableMutex;
|
||||||
|
std::condition_variable condition;
|
||||||
|
std::condition_variable progressCondition;
|
||||||
|
std::queue<Json::Value> queue;
|
||||||
|
|
||||||
|
auto sentrySender = [&condition, &progressCondition, &conditionVariableMutex,
|
||||||
|
&queue, verbose, &errorSending, &sentCount,
|
||||||
|
&stop, &dsn]
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Json::Value msg;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(conditionVariableMutex);
|
||||||
|
condition.wait(lock, [&queue, &stop]{ return !queue.empty() && !stop; });
|
||||||
|
|
||||||
|
msg = queue.front();
|
||||||
|
queue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
SentryClient sc(dsn);
|
||||||
|
|
||||||
|
if (!sc.send(msg, verbose))
|
||||||
|
{
|
||||||
|
errorSending = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++sentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressCondition.notify_one();
|
||||||
|
|
||||||
|
if (stop) return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a thread pool
|
||||||
|
std::cerr << "Starting " << jobs << " sentry sender jobs" << std::endl;
|
||||||
|
std::vector<std::thread> pool;
|
||||||
|
for (int i = 0; i < jobs; i++)
|
||||||
|
{
|
||||||
|
pool.push_back(std::thread(sentrySender));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.setEventCallback(
|
||||||
|
[&conn, &channel, &jsonWriter,
|
||||||
|
verbose, &receivedCount, &sentCount,
|
||||||
|
&condition, &conditionVariableMutex,
|
||||||
|
&progressCondition, &queue]
|
||||||
|
(ix::CobraConnectionEventType eventType,
|
||||||
|
const std::string& errMsg,
|
||||||
|
const ix::WebSocketHttpHeaders& headers,
|
||||||
|
const std::string& subscriptionId)
|
||||||
|
{
|
||||||
|
if (eventType == ix::CobraConnection_EventType_Open)
|
||||||
|
{
|
||||||
|
std::cerr << "Subscriber: connected" << std::endl;
|
||||||
|
|
||||||
|
for (auto it : headers)
|
||||||
|
{
|
||||||
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eventType == ix::CobraConnection_EventType_Closed)
|
||||||
|
{
|
||||||
|
std::cerr << "Subscriber: closed" << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||||
|
{
|
||||||
|
std::cerr << "Subscriber authenticated" << std::endl;
|
||||||
|
conn.subscribe(channel,
|
||||||
|
[&jsonWriter, verbose,
|
||||||
|
&sentCount, &receivedCount,
|
||||||
|
&condition, &conditionVariableMutex,
|
||||||
|
&progressCondition, &queue]
|
||||||
|
(const Json::Value& msg)
|
||||||
|
{
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
std::cerr << jsonWriter.write(msg) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we cannot send to sentry fast enough, drop the message
|
||||||
|
const uint64_t scaleFactor = 2;
|
||||||
|
|
||||||
|
if (sentCount != 0 &&
|
||||||
|
receivedCount != 0 &&
|
||||||
|
(sentCount * scaleFactor < receivedCount))
|
||||||
|
{
|
||||||
|
std::cerr << "message dropped: sending is backlogged !"
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
condition.notify_one();
|
||||||
|
progressCondition.notify_one();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++receivedCount;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(conditionVariableMutex);
|
||||||
|
queue.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
condition.notify_one();
|
||||||
|
progressCondition.notify_one();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||||
|
{
|
||||||
|
std::cerr << "Subscriber: subscribed to channel " << subscriptionId << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
||||||
|
{
|
||||||
|
std::cerr << "Subscriber: unsubscribed from channel " << subscriptionId << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Error)
|
||||||
|
{
|
||||||
|
std::cerr << "Subscriber: error" << errMsg << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
std::mutex progressConditionVariableMutex;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(progressConditionVariableMutex);
|
||||||
|
progressCondition.wait(lock);
|
||||||
|
|
||||||
|
std::cout << "messages"
|
||||||
|
<< " received " << receivedCount
|
||||||
|
<< " sent " << sentCount
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
if (strict && errorSending) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.disconnect();
|
||||||
|
|
||||||
|
// FIXME: join all the bg threads and stop them.
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
148
ws/ws_cobra_to_statsd.cpp
Normal file
148
ws/ws_cobra_to_statsd.cpp
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* ws_cobra_to_statsd.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
#include "IXCobraConnection.h"
|
||||||
|
|
||||||
|
#include <statsd_client.h>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
// fields are command line argument that can be specified multiple times
|
||||||
|
std::vector<std::string> parseFields(const std::string& fields)
|
||||||
|
{
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
|
||||||
|
// Split by \n
|
||||||
|
std::string token;
|
||||||
|
std::stringstream tokenStream(fields);
|
||||||
|
|
||||||
|
while (std::getline(tokenStream, token))
|
||||||
|
{
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Extract an attribute from a Json Value.
|
||||||
|
// extractAttr("foo.bar", {"foo": {"bar": "baz"}}) => baz
|
||||||
|
//
|
||||||
|
std::string extractAttr(const std::string& attr,
|
||||||
|
const Json::Value& jsonValue)
|
||||||
|
{
|
||||||
|
// Split by .
|
||||||
|
std::string token;
|
||||||
|
std::stringstream tokenStream(attr);
|
||||||
|
|
||||||
|
Json::Value val(jsonValue);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (std::getline(tokenStream, token, '.'))
|
||||||
|
{
|
||||||
|
val = val[token];
|
||||||
|
}
|
||||||
|
|
||||||
|
return val.asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ws_cobra_to_statsd_main(const std::string& appkey,
|
||||||
|
const std::string& endpoint,
|
||||||
|
const std::string& rolename,
|
||||||
|
const std::string& rolesecret,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& host,
|
||||||
|
int port,
|
||||||
|
const std::string& prefix,
|
||||||
|
const std::string& fields,
|
||||||
|
bool verbose)
|
||||||
|
{
|
||||||
|
ix::CobraConnection conn;
|
||||||
|
conn.configure(appkey, endpoint,
|
||||||
|
rolename, rolesecret,
|
||||||
|
ix::WebSocketPerMessageDeflateOptions(true));
|
||||||
|
conn.connect();
|
||||||
|
|
||||||
|
auto tokens = parseFields(fields);
|
||||||
|
|
||||||
|
// statsd client
|
||||||
|
// test with netcat as a server: `nc -ul 8125`
|
||||||
|
bool statsdBatch = true;
|
||||||
|
statsd::StatsdClient statsdClient(host, port, prefix, statsdBatch);
|
||||||
|
|
||||||
|
Json::FastWriter jsonWriter;
|
||||||
|
uint64_t msgCount = 0;
|
||||||
|
|
||||||
|
conn.setEventCallback(
|
||||||
|
[&conn, &channel, &jsonWriter, &statsdClient, verbose, &tokens, &prefix, &msgCount]
|
||||||
|
(ix::CobraConnectionEventType eventType,
|
||||||
|
const std::string& errMsg,
|
||||||
|
const ix::WebSocketHttpHeaders& headers,
|
||||||
|
const std::string& subscriptionId)
|
||||||
|
{
|
||||||
|
if (eventType == ix::CobraConnection_EventType_Open)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: connected" << std::endl;
|
||||||
|
}
|
||||||
|
if (eventType == ix::CobraConnection_EventType_Closed)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: closed" << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber authenticated" << std::endl;
|
||||||
|
conn.subscribe(channel,
|
||||||
|
[&jsonWriter, &statsdClient, &channel,
|
||||||
|
verbose, &tokens, &prefix, &msgCount]
|
||||||
|
(const Json::Value& msg)
|
||||||
|
{
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
std::cout << jsonWriter.write(msg) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string id;
|
||||||
|
for (auto&& attr : tokens)
|
||||||
|
{
|
||||||
|
id += ".";
|
||||||
|
id += extractAttr(attr, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << msgCount++ << " " << prefix << id << std::endl;
|
||||||
|
|
||||||
|
statsdClient.count(id, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: subscribed to channel " << subscriptionId << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: unsubscribed from channel " << subscriptionId << std::endl;
|
||||||
|
}
|
||||||
|
else if (eventType == ix::CobraConnection_EventType_Error)
|
||||||
|
{
|
||||||
|
std::cout << "Subscriber: error" << errMsg << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::chrono::duration<double, std::milli> duration(1000);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -17,10 +17,11 @@ namespace ix
|
|||||||
ix::WebSocketServer server(port, hostname);
|
ix::WebSocketServer server(port, hostname);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[](std::shared_ptr<ix::WebSocket> webSocket)
|
[](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -30,6 +31,7 @@ namespace ix
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
std::cerr << "New connection" << std::endl;
|
std::cerr << "New connection" << std::endl;
|
||||||
|
std::cerr << "id: " << connectionState->getId() << std::endl;
|
||||||
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
||||||
std::cerr << "Headers:" << std::endl;
|
std::cerr << "Headers:" << std::endl;
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
|
@ -231,6 +231,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||||
{
|
{
|
||||||
|
ss << "ws_receive ";
|
||||||
ss << "Connection error: " << error.reason << std::endl;
|
ss << "Connection error: " << error.reason << std::endl;
|
||||||
ss << "#retries: " << error.retries << std::endl;
|
ss << "#retries: " << error.retries << std::endl;
|
||||||
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
||||||
|
52
ws/ws_redis_publish.cpp
Normal file
52
ws/ws_redis_publish.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* ws_redis_publish.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include "IXRedisClient.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int ws_redis_publish_main(const std::string& hostname,
|
||||||
|
int port,
|
||||||
|
const std::string& password,
|
||||||
|
const std::string& channel,
|
||||||
|
const std::string& message,
|
||||||
|
int count)
|
||||||
|
{
|
||||||
|
RedisClient redisClient;
|
||||||
|
if (!redisClient.connect(hostname, port))
|
||||||
|
{
|
||||||
|
std::cerr << "Cannot connect to redis host" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.empty())
|
||||||
|
{
|
||||||
|
std::string authResponse;
|
||||||
|
if (!redisClient.auth(password, authResponse))
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
std::cerr << "Cannot authenticated to redis" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::cout << "Auth response: " << authResponse << ":" << port << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
//std::cerr << "Publishing message " << message
|
||||||
|
// << " to " << channel << "..." << std::endl;
|
||||||
|
if (!redisClient.publish(channel, message))
|
||||||
|
{
|
||||||
|
std::cerr << "Error publishing to channel " << channel << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
86
ws/ws_redis_subscribe.cpp
Normal file
86
ws/ws_redis_subscribe.cpp
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* ws_redis_subscribe.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include "IXRedisClient.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int ws_redis_subscribe_main(const std::string& hostname,
|
||||||
|
int port,
|
||||||
|
const std::string& password,
|
||||||
|
const std::string& channel,
|
||||||
|
bool verbose)
|
||||||
|
{
|
||||||
|
RedisClient redisClient;
|
||||||
|
if (!redisClient.connect(hostname, port))
|
||||||
|
{
|
||||||
|
std::cerr << "Cannot connect to redis host" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.empty())
|
||||||
|
{
|
||||||
|
std::string authResponse;
|
||||||
|
if (!redisClient.auth(password, authResponse))
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
std::cerr << "Cannot authenticated to redis" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::cout << "Auth response: " << authResponse << ":" << port << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic<int> msgPerSeconds(0);
|
||||||
|
std::atomic<int> msgCount(0);
|
||||||
|
|
||||||
|
auto callback = [&msgPerSeconds, &msgCount, verbose]
|
||||||
|
(const std::string& message)
|
||||||
|
{
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
std::cout << "received: " << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
msgPerSeconds++;
|
||||||
|
msgCount++;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto responseCallback = [](const std::string& redisResponse)
|
||||||
|
{
|
||||||
|
std::cout << "Redis subscribe response: " << redisResponse << std::endl;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto timer = [&msgPerSeconds, &msgCount]
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::cout << "#messages " << msgCount << " "
|
||||||
|
<< "msg/s " << msgPerSeconds
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
msgPerSeconds = 0;
|
||||||
|
auto duration = std::chrono::seconds(1);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t(timer);
|
||||||
|
|
||||||
|
std::cerr << "Subscribing to " << channel << "..." << std::endl;
|
||||||
|
if (!redisClient.subscribe(channel, responseCallback, callback))
|
||||||
|
{
|
||||||
|
std::cerr << "Error subscribing to channel " << channel << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -162,6 +162,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
else if (messageType == ix::WebSocket_MessageType_Error)
|
||||||
{
|
{
|
||||||
|
ss << "ws_send ";
|
||||||
ss << "Connection error: " << error.reason << std::endl;
|
ss << "Connection error: " << error.reason << std::endl;
|
||||||
ss << "#retries: " << error.retries << std::endl;
|
ss << "#retries: " << error.retries << std::endl;
|
||||||
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
||||||
|
@ -17,10 +17,11 @@ namespace ix
|
|||||||
ix::WebSocketServer server(port, hostname);
|
ix::WebSocketServer server(port, hostname);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -30,6 +31,7 @@ namespace ix
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
std::cerr << "New connection" << std::endl;
|
std::cerr << "New connection" << std::endl;
|
||||||
|
std::cerr << "id: " << connectionState->getId() << std::endl;
|
||||||
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
||||||
std::cerr << "Headers:" << std::endl;
|
std::cerr << "Headers:" << std::endl;
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
|
Reference in New Issue
Block a user