Compare commits

..

43 Commits

Author SHA1 Message Date
b1c1e6e28d websocket server: closed connection threads are joined properly 2019-04-17 16:23:24 -07:00
66440e2330 less buggy server connection per thread system 2019-04-16 22:14:30 -07:00
792610d44f use threads instead of std::async, need to cleanup threads 2019-04-16 18:21:52 -07:00
bcf2fc1812 make closeWireSize a default parameter of WebSocketTransport::close 2019-04-16 09:55:12 -07:00
935e6791a3 close method change and fix code (#28)
* close method change and fix code

* missing mutex
2019-04-16 08:58:34 -07:00
fbb7c012a3 fix windows build (#29)
* fix windows build

* fix for Unix

* Fix build if TLS is OFF

* add OpenSSL req to ws

* Fix build on Mac

* fix tests for windows
2019-04-16 08:51:57 -07:00
dac18fcabf move security framework linking to ixwebsocket (#26) 2019-04-14 17:13:24 -07:00
d8e83caffc fix warning 2019-04-13 21:16:04 -07:00
fbf80b9f50 ws: new command to subscribe to a cobra server and send an event to a sentry server 2019-04-11 16:03:05 -07:00
c2a9139d41 (ws) add subcommands: cobra subscribe, and cobra subscribe to statsd bridge 2019-04-08 21:56:01 -07:00
6e3dff149a linux ci tentative fix 2019-04-03 22:02:10 -07:00
1bacbe38f4 better unittest runner / can run through lldb and produce a junit XML artifact 2019-03-29 15:54:05 -07:00
2e9c610ac9 Bump sleep time in test shell script 2019-03-29 09:36:56 -07:00
eb063ec60a (redis_subscribe) in verbose mode, received message gets printed with a 'received: ' header 2019-03-29 09:35:19 -07:00
37fb14646d Add clarification notice about third party modules 2019-03-29 09:34:17 -07:00
ae543518d3 offline version of remark-latest 2019-03-28 16:06:43 -07:00
c865d64608 redis conf slides 2019-03-28 14:17:19 -07:00
3004422cb6 slides 2019-03-27 16:27:52 -07:00
0c46a17443 add redis-conf slides 2019-03-27 15:53:55 -07:00
497373d976 ws redis command improvements + test script 2019-03-27 13:41:46 -07:00
91198aca0d (ws) redis_subscribe and redis_publish can take a password + display subscribe response 2019-03-26 09:33:22 -07:00
b17a5e5f0b update doc 2019-03-24 21:48:14 -07:00
3f0ef59f65 remove Formula folder
Homebrew stuff is at https://github.com/bsergean/homebrew-IXWebSocket
2019-03-24 21:43:38 -07:00
1e96edc293 (server) fix masking bug 2019-03-22 15:33:04 -07:00
0afb77393b can send TEXT message (we only support BINARY messages now) 2019-03-22 14:24:22 -07:00
7614b642bb unmasked code is broken 2019-03-22 14:24:15 -07:00
bc89580dfe remove printf + unittest fix 2019-03-22 09:56:28 -07:00
358ae13a88 (server) server should not mask data when sending to client (some python client libraries enforce that and assert) 2019-03-22 09:53:56 -07:00
ccf9dcba70 (server) HTTP response is malformed 2019-03-22 09:52:19 -07:00
94604fad61 minor cleanup 2019-03-21 13:51:25 -07:00
5c4cc7c50d HTTP/1.1 response should contains a reason (websocket server)
Fix compatibility problem with websockets python library, where the response does not contains a reason

File "/.../lib/python3.7/site-packages/websockets/http.py", line 126, in read_response
version, status_code, reason = status_line[:-2].split(b' ', 2)
ValueError: not enough values to unpack (expected 3, got 2)

The above exception was the direct cause of the following exception:

websockets.exceptions.InvalidMessage: Malformed HTTP message
2019-03-21 13:43:47 -07:00
9ed961ec06 cleanup, remove dead method 2019-03-21 10:06:59 -07:00
e6bd8cc8c4 (cmake) add a warning about 32/64 conversion problems. 2019-03-20 21:51:38 -07:00
ee25bd0f92 Feature/connection state (#25)
* (cmake) add a warning about 32/64 conversion problems.

* fix typo

* New connection state for server code + fix OpenSSL double init bug

* update README
2019-03-20 18:34:24 -07:00
e77b9176f3 Feature/redis (#23)
* Fix warning

* (cmake) add a warning about 32/64 conversion problems.

* simple redis clients

* can publish to redis

* redis subscribe

* display messages received per second

* verbose flag

* (cmake) use clang only compile option -Wshorten-64-to-32 when compiling with clang
2019-03-20 14:29:02 -07:00
afe8b966ad Fixed heartbeat typos (#22) 2019-03-19 21:31:43 -07:00
310724c961 make PollResultType an enum class 2019-03-19 09:29:57 -07:00
ceba8ae620 fix bug with isReadyToWrite 2019-03-18 22:05:04 -07:00
fead661ab7 workaround bug in Socket::isReadyToWrite 2019-03-18 20:37:33 -07:00
9c8c17f577 use milliseconds 2019-03-18 20:17:44 -07:00
a04f83930f ws / log subcommand name 2019-03-18 17:54:06 -07:00
c421d19800 disable sigpipe on osx when writing/reading into a dead pipe 2019-03-18 17:52:01 -07:00
521f02c90e edit homebrew install steps 2019-03-18 15:45:33 -07:00
86 changed files with 3066 additions and 8576 deletions

View File

@ -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
View File

@ -0,0 +1 @@
1.4.0

View File

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

View File

@ -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
View 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:

View 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;
}
}

View 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;
};
}

View File

@ -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;

View File

@ -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;
} }

View File

@ -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());
} }

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -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),

View File

@ -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;
}; };

View File

@ -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);
} }

View File

@ -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:
}; };

View File

@ -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);
} }
} }
} }

View File

@ -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();
}; };
} }

View File

@ -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);

View File

@ -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;

View File

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

View File

@ -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)

View File

@ -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>;

View File

@ -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()

View File

@ -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;
}; };
} }

View File

@ -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);
} }

View File

@ -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);

View File

@ -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:

View File

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

View 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);
}
}

View File

@ -73,7 +73,7 @@ TEST_CASE("socket", "[socket]")
testSocket(host, port, request, socket, expectedStatus, timeoutSecs); testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
} }
#if defined(__APPLE__) 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;

View File

@ -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;
}
} }

View File

@ -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);

View File

@ -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);
} }

View File

@ -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
View 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()

View File

@ -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
View 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.

View File

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

View File

@ -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:]]*$//' {} \+

View File

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

View 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
View 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
View 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`

View 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);
}

View 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;
}

View 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;
}
}

View 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
View File

@ -1 +1,2 @@
build build
node_modules

View File

@ -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)

View File

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

View File

@ -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
View 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
View 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
View 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
View 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

View File

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

View File

@ -1,3 +0,0 @@
venv
build
node_modules

View File

@ -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)

View File

@ -1,6 +0,0 @@
```
mkdir build
cd build
cmake ..
make && (cd .. ; sh cobra_publisher.sh)
```

View File

@ -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;
}

View File

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

View File

@ -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)
}
});
})

View File

@ -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()

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -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
View 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
View File

@ -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
View File

@ -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);
} }

View File

@ -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
View 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
View 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
View 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;
}
}

View File

@ -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)

View File

@ -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
View 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
View 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;
}
}

View File

@ -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;

View File

@ -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)