Compare commits

...

24 Commits

Author SHA1 Message Date
f8fef833b8 new options for cobra commands
- ws cobra_subscribe has a new -q (quiet) option
- ws cobra_subscribe knows to and display msg stats (count and # of messages received per second)
- ws cobra_subscribe, cobra_to_statsd and cobra_to_sentry commands have a new option, --filter to restrict the events they want to receive
2019-08-01 15:22:24 -07:00
fc4068f2e5 ws connect command has a new option to send in binary mode (still default to text) 2019-07-25 15:48:45 -07:00
c300866dcc add better line editing capability to ws connect, thanks to linenoise-cpp 2019-07-25 11:54:50 -07:00
18485a74e5 README.md / cosmetic 2019-07-23 14:04:45 -07:00
4dd5950406 fix typo in README 2019-07-23 13:52:16 -07:00
98de54106d README: add reference to conan/vcpk to the build section 2019-07-22 20:41:06 -07:00
4d64272a1a do not update homebrew when installing a package 2019-07-03 14:49:39 -07:00
0ccece908b ci / get mbedtls from homebrew on mac 2019-07-03 14:46:05 -07:00
64cd725060 do not use mbed tls for the unittest 2019-07-03 14:39:46 -07:00
cc2fa55608 add new docker file to run the unittest with tsan on latest Ubuntu 2019-06-30 23:37:25 -07:00
4fb268585c dns / use cancellable instead of blocking 2019-06-30 23:26:14 -07:00
3a2495c456 make IXDNSLookup more robust 2019-06-26 19:12:48 -07:00
1d4d058ed0 simplify IXDNSLookup 2019-06-26 16:25:07 -07:00
15a1347531 use poll instead of select in SocketServer 2019-06-25 17:18:24 -07:00
4cbfa71338 switch from select to poll to deal with Android 9 giving us high socket fds when calling ::connect 2019-06-25 17:11:27 -07:00
705625af0a refactor select code + add protection against large fds (cf Android 9) 2019-06-25 15:41:39 -07:00
01bc6654cb Add extra check in IXWebSocketCloseTest.cpp 2019-06-25 14:10:39 -07:00
eea42bff66 select refactoring IXSocket::select -> IXSocket::poll 2019-06-25 10:16:40 -07:00
06b4762c19 disable CI on Windows 2019-06-25 00:28:11 -07:00
1ee9479009 cmake use_tls fix 2019-06-24 23:34:31 -07:00
73e94ed03a do not build mbedtls on ci 2019-06-24 23:28:35 -07:00
1883519e82 try to disable TLS for unittesting 2019-06-24 23:27:44 -07:00
6f6c1f85ef CI / build zlib and mbedtls locally 2019-06-24 23:17:19 -07:00
c55ff3cb1b CI work 2019-06-24 10:17:57 -07:00
35 changed files with 3171 additions and 236 deletions

View File

@ -7,20 +7,23 @@ matrix:
include:
# macOS
- os: osx
env:
- HOMEBREW_NO_AUTO_UPDATE=1
compiler: clang
script:
- brew install mbedtls
- python test/run.py
- make ws
# Linux
- os: linux
dist: xenial
script:
- python test/run.py
- make ws
env:
- CC=gcc
- CXX=g++
# # Linux
# - os: linux
# dist: xenial
# script:
# - python test/run.py
# - make ws
# env:
# - CC=gcc
# - CXX=g++
# Clang + Linux disabled for now
# - os: linux
@ -30,12 +33,22 @@ matrix:
# - CC=clang
# - CXX=clang++
Windows
- os: windows
env:
- CMAKE_PATH="/c/Program Files/CMake/bin"
script:
- export PATH=$CMAKE_PATH:$PATH
# - cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 -DUSE_VENDORED_THIRD_PARTY=1 .
# - cmake --build --parallel .
- python test/run.py
# Windows
# - os: windows
# env:
# - CMAKE_PATH="/c/Program Files/CMake/bin"
# script:
# - cd third_party/zlib
# - cmake .
# - cmake --build . --target install
# - cd ../..
# # - cd third_party/mbedtls
# # - cmake .
# # - cmake --build . --target install
# # - cd ../..
# - export PATH=$CMAKE_PATH:$PATH
# - cd test
# - cmake .
# - cmake --build --parallel .
# - ixwebsocket_unittest.exe
# # - python test/run.py

View File

@ -1,6 +1,15 @@
# Changelog
All notable changes to this project will be documented in this file.
## [5.0.2] - 2019-08-01
- ws cobra_subscribe has a new -q (quiet) option
- ws cobra_subscribe knows to and display msg stats (count and # of messages received per second)
- ws cobra_subscribe, cobra_to_statsd and cobra_to_sentry commands have a new option, --filter to restrict the events they want to receive
## [5.0.1] - 2019-07-25
- ws connect command has a new option to send in binary mode (still default to text)
- ws connect command has readline history thanks to libnoise-cpp. Now ws connect one can use using arrows to lookup previous sent messages and edit them
## [5.0.0] - 2019-06-23
### Changed
- New HTTP server / still very early. ws gained a new command, httpd can run a simple webserver serving local files.

View File

@ -143,7 +143,7 @@ if (UNIX)
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
endif()
if (USE_OPEN_SSL)
if (USE_TLS AND USE_OPEN_SSL)
find_package(OpenSSL REQUIRED)
add_definitions(${OPENSSL_DEFINITIONS})
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
@ -151,7 +151,7 @@ if (USE_OPEN_SSL)
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
endif()
if (USE_MBED_TLS)
if (USE_TLS AND USE_MBED_TLS)
if (USE_VENDORED_THIRD_PARTY)
set (ENABLE_PROGRAMS OFF)
add_subdirectory(third_party/mbedtls)

View File

@ -1 +1 @@
5.0.0
5.0.1

View File

@ -32,14 +32,14 @@ webSocket.setHeartBeatPeriod(45);
webSocket.disablePerMessageDeflate();
// Setup a callback to be fired when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback(
[](const ix::WebSocketMessagePtr& msg)
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Message)
{
std::cout << msg->str << std::endl;
}
});
}
);
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
@ -221,7 +221,7 @@ setOnConnectionCallback(
{
// Build a string for the response
std::stringstream ss;
ss << request->method
ss << request->method
<< " "
<< request->uri;
@ -236,6 +236,8 @@ setOnConnectionCallback(
## Build
### CMake
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.
```
@ -247,12 +249,33 @@ make install # will install to /usr/local on Unix, on macOS it is a good idea to
```
Headers and a static library will be installed to the target dir.
A [conan](https://conan.io/) file is available at [conan-IXWebSocket](https://github.com/Zinnion/conan-IXWebSocket).
There is a unittest which can be executed by typing `make test`.
There is a Dockerfile for running some code on Linux. To use docker-compose you must make a docker container first.
### vcpkg
It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/microsoft/vcpkg).
```
vcpkg install ixwebsocket
```
### Conan
Support for building with conan was contributed by Olivia Zoe (thanks !). The package name to reference is `IXWebSocket/5.0.0@LunarWatcher/stable`. The package is in the process to be published to the official conan package repo, but in the meantime, it can be accessed by adding a new remote
```
conan remote add remote_name_here https://api.bintray.com/conan/oliviazoe0/conan-packages
```
### Docker
There is a Dockerfile for running the unittest on Linux, and to run the `ws` tool. It is also available on the docker registry.
```
docker run bsergean/ws
```
To use docker-compose you must make a docker container first.
```
$ make docker
@ -265,12 +288,6 @@ ws is a websocket tool
...
```
Finally you can build and install the `ws command line tool` with Homebrew. The homebrew version might be slightly out of date.
```
brew tap bsergean/IXWebSocket
brew install IXWebSocket
```
## Implementation details
@ -363,8 +380,7 @@ If the connection was closed and sending failed, the return value will be set to
The onMessage event will be fired when the connection is opened or closed. This is similar to the [Javascript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
```
webSocket.setOnMessageCallback(
[](const ix::WebSocketMessagePtr& msg)
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
@ -395,8 +411,7 @@ webSocket.setOnMessageCallback(
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
```
webSocket.setOnMessageCallback(
[](const ix::WebSocketMessagePtr& msg)
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Error)
{
@ -430,8 +445,7 @@ websocket.configure(url);
Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec.
```
webSocket.setOnMessageCallback(
[](const ix::WebSocketMessagePtr& msg)
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Ping ||
msg->type == ix::WebSocketMessageType::Pong)

View File

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

View File

@ -9,18 +9,20 @@
#include <string.h>
#include <chrono>
#include <thread>
namespace ix
{
const int64_t DNSLookup::kDefaultWait = 10; // ms
const int64_t DNSLookup::kDefaultWait = 1; // ms
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
_hostname(hostname),
_port(port),
_wait(wait),
_res(nullptr),
_done(false)
{
setHostname(hostname);
;
}
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
@ -48,14 +50,14 @@ namespace ix
struct addrinfo* DNSLookup::resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool blocking)
bool cancellable)
{
return blocking ? resolveBlocking(errMsg, isCancellationRequested)
: resolveAsync(errMsg, isCancellationRequested);
return cancellable ? resolveCancellable(errMsg, isCancellationRequested)
: resolveUnCancellable(errMsg, isCancellationRequested);
}
struct addrinfo* DNSLookup::resolveBlocking(std::string& errMsg,
const CancellationRequest& isCancellationRequested)
struct addrinfo* DNSLookup::resolveUnCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
@ -66,11 +68,11 @@ namespace ix
return nullptr;
}
return getAddrInfo(getHostname(), _port, errMsg);
return getAddrInfo(_hostname, _port, errMsg);
}
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
const CancellationRequest& isCancellationRequested)
struct addrinfo* DNSLookup::resolveCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
@ -89,20 +91,21 @@ namespace ix
auto ptr = shared_from_this();
std::weak_ptr<DNSLookup> self(ptr);
_thread = std::thread(&DNSLookup::run, this, self, getHostname(), _port);
_thread.detach();
int port = _port;
std::string hostname(_hostname);
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
// We make the background thread doing the work a shared pointer
// instead of a member variable, because it can keep running when
// this object goes out of scope, in case of cancellation
auto t = std::make_shared<std::thread>(&DNSLookup::run, this, self, hostname, port);
t->detach();
while (!_done)
{
// Wait for 10 milliseconds on the condition variable, to see
// if the bg thread has terminated.
if (_condition.wait_for(lock, std::chrono::milliseconds(_wait)) == std::cv_status::no_timeout)
{
// Background thread has terminated, so we can break of this loop
break;
}
// Wait for 1 milliseconds, to see if the bg thread has terminated.
// We do not use a condition variable to wait, as destroying this one
// if the bg thread is alive can cause undefined behavior.
std::this_thread::sleep_for(std::chrono::milliseconds(_wait));
// Were we cancelled ?
if (isCancellationRequested && isCancellationRequested())
@ -123,7 +126,7 @@ namespace ix
return getRes();
}
void DNSLookup::run(std::weak_ptr<DNSLookup> self, const std::string& hostname, int port) // thread runner
void DNSLookup::run(std::weak_ptr<DNSLookup> self, std::string hostname, int port) // thread runner
{
// We don't want to read or write into members variables of an object that could be
// gone, so we use temporary variables (res) or we pass in by copy everything that
@ -137,23 +140,10 @@ namespace ix
setRes(res);
setErrMsg(errMsg);
_condition.notify_one();
_done = true;
}
}
void DNSLookup::setHostname(const std::string& hostname)
{
std::lock_guard<std::mutex> lock(_hostnameMutex);
_hostname = hostname;
}
const std::string& DNSLookup::getHostname()
{
std::lock_guard<std::mutex> lock(_hostnameMutex);
return _hostname;
}
void DNSLookup::setErrMsg(const std::string& errMsg)
{
std::lock_guard<std::mutex> lock(_errMsgMutex);

View File

@ -12,10 +12,9 @@
#include "IXCancellationRequest.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <memory>
struct addrinfo;
@ -30,22 +29,19 @@ namespace ix
struct addrinfo* resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool blocking = false);
bool cancellable = true);
private:
struct addrinfo* resolveAsync(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveBlocking(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveUnCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
static struct addrinfo* getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg);
void run(std::weak_ptr<DNSLookup> self, const std::string& hostname, int port); // thread runner
void setHostname(const std::string& hostname);
const std::string& getHostname();
void run(std::weak_ptr<DNSLookup> self, std::string hostname, int port); // thread runner
void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg();
@ -54,10 +50,9 @@ namespace ix
struct addrinfo* getRes();
std::string _hostname;
std::mutex _hostnameMutex;
int _port;
int64_t _wait;
const static int64_t kDefaultWait;
struct addrinfo* _res;
std::mutex _resMutex;
@ -66,10 +61,5 @@ namespace ix
std::mutex _errMsgMutex;
std::atomic<bool> _done;
std::thread _thread;
std::condition_variable _condition;
std::mutex _conditionVariableMutex;
const static int64_t kDefaultWait;
};
} // namespace ix

View File

@ -131,7 +131,7 @@ namespace ix
return false;
}
return response->payload.empty()
return response->payload.empty()
? true
: socket->writeBytes(response->payload, nullptr);
}

View File

@ -131,7 +131,7 @@ namespace ix
// Log request
std::stringstream ss;
ss << request->method
ss << request->method
<< " "
<< request->uri
<< " "

View File

@ -17,6 +17,7 @@
#include <stdint.h>
#include <fcntl.h>
#include <sys/types.h>
#include <poll.h>
#include <algorithm>
@ -44,41 +45,42 @@ namespace ix
close();
}
PollResultType Socket::poll(int timeoutMs)
PollResultType Socket::poll(bool readyToRead,
int timeoutMs,
int sockfd,
std::shared_ptr<SelectInterrupt> selectInterrupt)
{
return isReadyToRead(timeoutMs);
}
//
// We used to use ::select to poll but on Android 9 we get large fds out of ::connect
// which crash in FD_SET as they are larger than FD_SETSIZE.
// Switching to ::poll does fix that.
//
// However poll isn't as portable as select and has bugs on Windows, so we should write a
// shim to fallback to select on those platforms.
// See https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
//
int nfds = 1;
struct pollfd fds[2];
PollResultType Socket::select(bool readyToRead, int timeoutMs)
{
fd_set rfds;
fd_set wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
fd_set* fds = (readyToRead) ? &rfds : & wfds;
if (_sockfd != -1)
{
FD_SET(_sockfd, fds);
}
fds[0].fd = sockfd;
fds[0].events = (readyToRead) ? POLLIN : POLLOUT;
fds[0].events |= POLLERR;
// File descriptor used to interrupt select when needed
int interruptFd = _selectInterrupt->getFd();
if (interruptFd != -1)
int interruptFd = -1;
if (selectInterrupt)
{
FD_SET(interruptFd, fds);
interruptFd = selectInterrupt->getFd();
if (interruptFd != -1)
{
nfds = 2;
fds[1].fd = interruptFd;
fds[1].events = POLLIN;
}
}
struct timeval timeout;
timeout.tv_sec = timeoutMs / 1000;
timeout.tv_usec = 1000 * (timeoutMs % 1000);
// Compute the highest fd.
int sockfd = _sockfd;
int nfds = (std::max)(sockfd, interruptFd);
int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
(timeoutMs < 0) ? nullptr : &timeout);
int ret = ::poll(fds, nfds, timeoutMs);
PollResultType pollResult = PollResultType::ReadyForRead;
if (ret < 0)
@ -89,9 +91,9 @@ namespace ix
{
pollResult = PollResultType::Timeout;
}
else if (interruptFd != -1 && FD_ISSET(interruptFd, &rfds))
else if (interruptFd != -1 && fds[1].revents & POLLIN)
{
uint64_t value = _selectInterrupt->read();
uint64_t value = selectInterrupt->read();
if (value == kSendRequest)
{
@ -102,13 +104,32 @@ namespace ix
pollResult = PollResultType::CloseRequest;
}
}
else if (sockfd != -1 && readyToRead && FD_ISSET(sockfd, &rfds))
else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN)
{
pollResult = PollResultType::ReadyForRead;
}
else if (sockfd != -1 && !readyToRead && FD_ISSET(sockfd, &wfds))
else if (sockfd != -1 && !readyToRead && fds[0].revents & POLLOUT)
{
pollResult = PollResultType::ReadyForWrite;
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (fds[0].revents & POLLERR)
{
pollResult = PollResultType::Error;
}
#else
int optval = -1;
socklen_t optlen = sizeof(optval);
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
optval != 0)
{
pollResult = PollResultType::Error;
}
#endif
}
return pollResult;
@ -122,7 +143,7 @@ namespace ix
}
bool readyToRead = true;
return select(readyToRead, timeoutMs);
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
}
PollResultType Socket::isReadyToWrite(int timeoutMs)
@ -133,7 +154,7 @@ namespace ix
}
bool readyToRead = false;
return select(readyToRead, timeoutMs);
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
}
// Wake up from poll/select by writing to the pipe which is watched by select
@ -232,14 +253,14 @@ namespace ix
bool Socket::writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested)
{
char* buffer = const_cast<char*>(str.c_str());
int offset = 0;
int len = (int) str.size();
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret = send(buffer, len);
ssize_t ret = send((char*)&str[offset], len);
// We wrote some bytes, as needed, all good.
if (ret > 0)
@ -250,8 +271,8 @@ namespace ix
}
else
{
buffer += ret;
len -= ret;
offset += ret;
len -= ret;
continue;
}
}

View File

@ -88,6 +88,12 @@ namespace ix
static bool isWaitNeeded();
static void closeSocket(int fd);
static PollResultType poll(bool readyToRead,
int timeoutMs,
int sockfd,
std::shared_ptr<SelectInterrupt> selectInterrupt = nullptr);
// Used as special codes for pipe communication
static const uint64_t kSendRequest;
static const uint64_t kCloseRequest;
@ -97,8 +103,6 @@ namespace ix
std::mutex _socketMutex;
private:
PollResultType select(bool readyToRead, int timeoutMs);
static const int kDefaultPollTimeout;
static const int kDefaultPollNoTimeout;

View File

@ -63,55 +63,31 @@ namespace ix
return -1;
}
// On Linux the timeout needs to be re-initialized everytime
// http://man7.org/linux/man-pages/man2/select.2.html
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
int timeoutMs = 10;
bool readyToRead = false;
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd);
fd_set wfds;
fd_set efds;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
FD_ZERO(&efds);
FD_SET(fd, &efds);
// Use select to check the status of the new connection
res = select(fd + 1, nullptr, &wfds, &efds, &timeout);
if (res < 0 && (Socket::getErrno() == EBADF || Socket::getErrno() == EINVAL))
if (pollResult == PollResultType::Timeout)
{
continue;
}
else if (pollResult == PollResultType::Error)
{
Socket::closeSocket(fd);
errMsg = std::string("Connect error, select error: ") + strerror(Socket::getErrno());
errMsg = std::string("Connect error: ") +
strerror(Socket::getErrno());
return -1;
}
// Nothing was written to the socket, wait again.
if (!FD_ISSET(fd, &wfds)) continue;
// Something was written to the socket. Check for errors.
int optval = -1;
socklen_t optlen = sizeof(optval);
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (FD_ISSET(fd, &efds))
#else
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
optval != 0)
#endif
else if (pollResult == PollResultType::ReadyForWrite)
{
Socket::closeSocket(fd);
errMsg = strerror(optval);
return -1;
return fd;
}
else
{
// Success !
return fd;
Socket::closeSocket(fd);
errMsg = std::string("Connect error: ") +
strerror(Socket::getErrno());
return -1;
}
}

View File

@ -213,17 +213,12 @@ namespace ix
{
if (_stop) return;
// Use select to check whether a new connection is in progress
fd_set rfds;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
// Use poll to check whether a new connection is in progress
int timeoutMs = 10;
bool readyToRead = true;
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, _serverFd);
FD_ZERO(&rfds);
FD_SET(_serverFd, &rfds);
if (select(_serverFd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
if (pollResult == PollResultType::Error)
{
std::stringstream ss;
ss << "SocketServer::run() error in select: "
@ -232,9 +227,8 @@ namespace ix
continue;
}
if (!FD_ISSET(_serverFd, &rfds))
if (pollResult != PollResultType::ReadyForRead)
{
// We reached the select timeout, and no new connections are pending
continue;
}

View File

@ -330,7 +330,7 @@ namespace ix
}
// poll the socket
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);
PollResultType pollResult = _socket->isReadyToRead(lastingTimeoutDelayInMs);
// Make sure we send all the buffered data
// there can be a lot of it for large messages.
@ -542,7 +542,7 @@ namespace ix
) {
unmaskReceiveBuffer(ws);
MessageKind messageKind =
MessageKind messageKind =
(ws.opcode == wsheader_type::TEXT_FRAME)
? MessageKind::MSG_TEXT
: MessageKind::MSG_BINARY;
@ -755,7 +755,7 @@ namespace ix
{
if (_readyState != ReadyState::OPEN && _readyState != ReadyState::CLOSING)
{
return WebSocketSendInfo();
return WebSocketSendInfo(false);
}
size_t payloadSize = message.size();

View File

@ -12,7 +12,7 @@ brew:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j install)
ws:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 -DUSE_VENDORED_THIRD_PARTY=1 .. ; make -j)
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j)
uninstall:
xargs rm -fv < build/install_manifest.txt

View File

@ -34,6 +34,8 @@ namespace
const std::string& getCloseReason();
bool getCloseRemote();
bool hasConnectionError() const;
private:
ix::WebSocket _webSocket;
int _port;
@ -42,6 +44,7 @@ namespace
uint16_t _closeCode;
std::string _closeReason;
bool _closeRemote;
std::atomic<bool> _connectionError;
};
WebSocketClient::WebSocketClient(int port)
@ -49,10 +52,16 @@ namespace
, _closeCode(0)
, _closeReason(std::string(""))
, _closeRemote(false)
, _connectionError(false)
{
;
}
bool WebSocketClient::hasConnectionError() const
{
return _connectionError;
}
bool WebSocketClient::isReady() const
{
return _webSocket.getReadyState() == ix::ReadyState::Open;
@ -133,6 +142,7 @@ namespace
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
_connectionError = true;
ss << "Error ! " << msg->errorInfo.reason;
log(ss.str());
}
@ -248,6 +258,7 @@ TEST_CASE("Websocket_client_close_default", "[close]")
// Wait for all chat instance to be ready
while (true)
{
REQUIRE(!webSocketClient.hasConnectionError());
if (webSocketClient.isReady()) break;
ix::msleep(10);
}

View File

@ -111,7 +111,6 @@ def runCMake(sanitizer, buildDir):
-B"{buildDir}" \
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
-DUSE_TLS=1 \
-DUSE_MBED_TLS=1 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DUSE_VENDORED_THIRD_PARTY={USE_VENDORED_THIRD_PARTY} \
-G{generator}'

44
third_party/cpp-linenoise/.gitignore vendored Normal file
View File

@ -0,0 +1,44 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Others
*.dSYM
*.swp
Debug
Release
*.suo
*.sdf
*.user
xcuserdata
*.xcworkspace
Makefile
CMakeFiles
CMakeCache.txt
*.cmake
history.txt

22
third_party/cpp-linenoise/LICENSE vendored Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2015 yhirose
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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 OWNER 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.

95
third_party/cpp-linenoise/README.md vendored Normal file
View File

@ -0,0 +1,95 @@
cpp-linenoise
=============
Multi-platfrom (Unix, Windows) C++ header-only linenoise-based readline library.
This library gathered code from following excellent libraries, clean it up, and put it into a C++ header file for convenience.
* `linenoise.h` and `linenose.c` ([antirez/linenoise](https://github.com/antirez/linenoise))
* `ANSI.c` ([adoxa/ansicon](https://github.com/adoxa/ansicon))
* `Win32_ANSI.h` and `Win32_ANSI.c` ([MSOpenTech/redis](https://github.com/MSOpenTech/redis))
The licenses for the libraries are included in `linenoise.hpp`.
Usage
-----
```c++
#include "linenoise.hpp"
...
const auto path = "history.txt";
// Setup completion words every time when a user types
linenoise::SetCompletionCallback([](const char* editBuffer, std::vector<std::string>& completions) {
if (editBuffer[0] == 'h') {
completions.push_back("hello");
completions.push_back("hello there");
}
});
// Enable the multi-line mode
linenoise::SetMultiLine(true);
// Set max length of the history
linenoise::SetHistoryMaxLen(4);
// Load history
linenoise::LoadHistory(path);
while (true) {
// Read line
std::string line;
auto quit = linenoise::Readline("hello> ", line);
if (quit) {
break;
}
cout << "echo: '" << line << "'" << endl;
// Add text to history
linenoise::AddHistory(line.c_str());
}
// Save history
linenoise::SaveHistory(path);
```
API
---
```c++
namespace linenoise;
std::string Readline(const char* prompt);
void SetMultiLine(bool multiLineMode);
typedef std::function<void (const char* editBuffer, std::vector<std::string>& completions)> CompletionCallback;
void SetCompletionCallback(CompletionCallback fn);
bool SetHistoryMaxLen(size_t len);
bool LoadHistory(const char* path);
bool SaveHistory(const char* path);
bool AddHistory(const char* line);
const std::vector<std::string>& GetHistory();
```
Tested compilers
----------------
* Visual Studio 2015
* Clang 3.5
* GCC 6.3.1
License
-------
BSD license (© 2015 Yuji Hirose)

View File

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.0)
include_directories(.)
add_definitions("-std=c++1y")
add_executable(example example.cpp)

View File

@ -0,0 +1,54 @@
#include <iostream>
#include "../linenoise.hpp"
using namespace std;
int main(int argc, const char** argv)
{
const auto path = "history.txt";
// Enable the multi-line mode
linenoise::SetMultiLine(true);
// Set max length of the history
linenoise::SetHistoryMaxLen(4);
// Setup completion words every time when a user types
linenoise::SetCompletionCallback([](const char* editBuffer, std::vector<std::string>& completions) {
if (editBuffer[0] == 'h') {
#ifdef _WIN32
completions.push_back("hello こんにちは");
completions.push_back("hello こんにちは there");
#else
completions.push_back("hello");
completions.push_back("hello there");
#endif
}
});
// Load history
linenoise::LoadHistory(path);
while (true) {
std::string line;
#ifdef _WIN32
auto quit = linenoise::Readline("hello> ", line);
#else
auto quit = linenoise::Readline("\033[32mこんにちは\x1b[0m> ", line);
#endif
if (quit) {
break;
}
cout << "echo: '" << line << "'" << endl;
// Add line to history
linenoise::AddHistory(line.c_str());
// Save history
linenoise::SaveHistory(path);
}
return 0;
}

View File

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{563BF990-4217-439F-92A4-F8A285052772}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{563BF990-4217-439F-92A4-F8A285052772}.Debug|x64.ActiveCfg = Debug|x64
{563BF990-4217-439F-92A4-F8A285052772}.Debug|x64.Build.0 = Debug|x64
{563BF990-4217-439F-92A4-F8A285052772}.Debug|x86.ActiveCfg = Debug|Win32
{563BF990-4217-439F-92A4-F8A285052772}.Debug|x86.Build.0 = Debug|Win32
{563BF990-4217-439F-92A4-F8A285052772}.Release|x64.ActiveCfg = Release|x64
{563BF990-4217-439F-92A4-F8A285052772}.Release|x64.Build.0 = Release|x64
{563BF990-4217-439F-92A4-F8A285052772}.Release|x86.ActiveCfg = Release|Win32
{563BF990-4217-439F-92A4-F8A285052772}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{563BF990-4217-439F-92A4-F8A285052772}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>example</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="example.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\linenoise.hpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

2421
third_party/cpp-linenoise/linenoise.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@ include_directories(ws ..)
include_directories(ws ../third_party)
include_directories(ws ../third_party/statsd-client-cpp/src)
include_directories(ws ../third_party/spdlog/include)
include_directories(ws ../third_party/cpp-linenoise)
include_directories(ws snake)
if (UNIX)

View File

@ -429,12 +429,18 @@ namespace ix
}
void CobraConnection::subscribe(const std::string& channel,
SubscriptionCallback cb)
const std::string& filter,
SubscriptionCallback cb)
{
// Create and send a subscribe pdu
Json::Value body;
body["channel"] = channel;
if (!filter.empty())
{
body["filter"] = filter;
}
Json::Value pdu;
pdu["action"] = "rtm/subscribe";
pdu["body"] = body;

View File

@ -75,7 +75,9 @@ namespace ix
// Subscribe to a channel, and execute a callback when an incoming
// message arrives.
void subscribe(const std::string& channel, SubscriptionCallback cb);
void subscribe(const std::string& channel,
const std::string& filter = std::string(),
SubscriptionCallback cb = nullptr);
/// Unsubscribe from a channel
void unsubscribe(const std::string& channel);

View File

@ -9,15 +9,10 @@
//
#include "ws.h"
//
// Main drive for websocket utilities
//
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
// #include <unistd.h>
#include <cli11/CLI11.hpp>
#include <spdlog/spdlog.h>
@ -60,6 +55,7 @@ int main(int argc, char** argv)
std::string hostname("127.0.0.1");
std::string pidfile;
std::string channel;
std::string filter;
std::string message;
std::string password;
std::string appkey;
@ -76,12 +72,14 @@ int main(int argc, char** argv)
bool followRedirects = false;
bool verbose = false;
bool save = false;
bool quiet = false;
bool compress = false;
bool strict = false;
bool stress = false;
bool disableAutomaticReconnection = false;
bool disablePerMessageDeflate = false;
bool greetings = false;
bool binaryMode = false;
int port = 8008;
int redisPort = 6379;
int statsdPort = 8125;
@ -113,6 +111,7 @@ int main(int argc, char** argv)
connectApp->add_option("url", url, "Connection url")->required();
connectApp->add_flag("-d", disableAutomaticReconnection, "Disable Automatic Reconnection");
connectApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate");
connectApp->add_flag("-b", binaryMode, "Send in binary mode");
CLI::App* chatApp = app.add_subcommand("chat", "Group chat");
chatApp->add_option("url", url, "Connection url")->required();
@ -168,6 +167,8 @@ int main(int argc, char** argv)
cobraSubscribeApp->add_option("--rolesecret", rolesecret, "Role secret");
cobraSubscribeApp->add_option("channel", channel, "Channel")->required();
cobraSubscribeApp->add_option("--pidfile", pidfile, "Pid file");
cobraSubscribeApp->add_option("--filter", filter, "Stream SQL Filter");
cobraSubscribeApp->add_flag("-q", quiet, "Quiet / only display stats");
CLI::App* cobraPublish = app.add_subcommand("cobra_publish", "Cobra publisher");
cobraPublish->add_option("--appkey", appkey, "Appkey");
@ -192,6 +193,7 @@ int main(int argc, char** argv)
cobra2statsd->add_option("channel", channel, "Channel")->required();
cobra2statsd->add_flag("-v", verbose, "Verbose");
cobra2statsd->add_option("--pidfile", pidfile, "Pid file");
cobra2statsd->add_option("--filter", filter, "Stream SQL Filter");
CLI::App* cobra2sentry = app.add_subcommand("cobra_to_sentry", "Cobra to sentry");
cobra2sentry->add_option("--appkey", appkey, "Appkey");
@ -204,6 +206,7 @@ int main(int argc, char** argv)
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");
cobra2sentry->add_option("--filter", filter, "Stream SQL Filter");
CLI::App* runApp = app.add_subcommand("snake", "Snake server");
runApp->add_option("--port", port, "Connection url");
@ -250,7 +253,7 @@ int main(int argc, char** argv)
else if (app.got_subcommand("connect"))
{
ret = ix::ws_connect_main(url, disableAutomaticReconnection,
disablePerMessageDeflate);
disablePerMessageDeflate, binaryMode);
}
else if (app.got_subcommand("chat"))
{
@ -288,7 +291,7 @@ int main(int argc, char** argv)
{
ret = ix::ws_cobra_subscribe_main(appkey, endpoint,
rolename, rolesecret,
channel);
channel, filter, quiet);
}
else if (app.got_subcommand("cobra_publish"))
{
@ -300,14 +303,14 @@ int main(int argc, char** argv)
{
ret = ix::ws_cobra_to_statsd_main(appkey, endpoint,
rolename, rolesecret,
channel, hostname, statsdPort,
channel, filter, hostname, statsdPort,
prefix, fields, verbose);
}
else if (app.got_subcommand("cobra_to_sentry"))
{
ret = ix::ws_cobra_to_sentry_main(appkey, endpoint,
rolename, rolesecret,
channel, dsn,
channel, filter, dsn,
verbose, strict, jobs);
}
else if (app.got_subcommand("snake"))

View File

@ -32,7 +32,8 @@ namespace ix
int ws_connect_main(const std::string& url,
bool disableAutomaticReconnection,
bool disablePerMessageDeflate);
bool disablePerMessageDeflate,
bool binaryMode);
int ws_receive_main(const std::string& url, bool enablePerMessageDeflate, int delayMs);
@ -55,7 +56,9 @@ namespace ix
const std::string& endpoint,
const std::string& rolename,
const std::string& rolesecret,
const std::string& channel);
const std::string& channel,
const std::string& filter,
bool quiet);
int ws_cobra_publish_main(const std::string& appkey,
const std::string& endpoint,
@ -70,6 +73,7 @@ namespace ix
const std::string& rolename,
const std::string& rolesecret,
const std::string& channel,
const std::string& filter,
const std::string& host,
int port,
const std::string& prefix,
@ -81,6 +85,7 @@ namespace ix
const std::string& rolename,
const std::string& rolesecret,
const std::string& channel,
const std::string& filter,
const std::string& dsn,
bool verbose,
bool strict,

View File

@ -11,13 +11,17 @@
#include <atomic>
#include <ixcobra/IXCobraConnection.h>
#include <spdlog/spdlog.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)
const std::string& channel,
const std::string& filter,
bool quiet)
{
ix::CobraConnection conn;
@ -28,8 +32,28 @@ namespace ix
Json::FastWriter jsonWriter;
// Display incoming messages
std::atomic<int> msgPerSeconds(0);
std::atomic<int> msgCount(0);
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);
conn.setEventCallback(
[&conn, &channel, &jsonWriter]
[&conn, &channel, &jsonWriter, &filter, &msgCount, &msgPerSeconds, &quiet]
(ix::CobraConnectionEventType eventType,
const std::string& errMsg,
const ix::WebSocketHttpHeaders& headers,
@ -37,33 +61,40 @@ namespace ix
{
if (eventType == ix::CobraConnection_EventType_Open)
{
std::cout << "Subscriber: connected" << std::endl;
spdlog::info("Subscriber connected");
for (auto it : headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
spdlog::info("{}: {}", it.first, it.second);
}
}
else if (eventType == ix::CobraConnection_EventType_Authenticated)
{
std::cout << "Subscriber authenticated" << std::endl;
conn.subscribe(channel,
[&jsonWriter](const Json::Value& msg)
spdlog::info("Subscriber authenticated");
conn.subscribe(channel, filter,
[&jsonWriter, &quiet,
&msgPerSeconds, &msgCount](const Json::Value& msg)
{
std::cout << jsonWriter.write(msg) << std::endl;
if (!quiet)
{
std::cout << jsonWriter.write(msg) << std::endl;
}
msgPerSeconds++;
msgCount++;
});
}
else if (eventType == ix::CobraConnection_EventType_Subscribed)
{
std::cout << "Subscriber: subscribed to channel " << subscriptionId << std::endl;
spdlog::info("Subscriber: subscribed to channel {}", subscriptionId);
}
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
{
std::cout << "Subscriber: unsubscribed from channel " << subscriptionId << std::endl;
spdlog::info("Subscriber: unsubscribed from channel {}", subscriptionId);
}
else if (eventType == ix::CobraConnection_EventType_Error)
{
std::cout << "Subscriber: error" << errMsg << std::endl;
spdlog::error("Subscriber: error {}", errMsg);
}
}
);

View File

@ -25,6 +25,7 @@ namespace ix
const std::string& rolename,
const std::string& rolesecret,
const std::string& channel,
const std::string& filter,
const std::string& dsn,
bool verbose,
bool strict,
@ -94,7 +95,7 @@ namespace ix
}
conn.setEventCallback(
[&conn, &channel, &jsonWriter,
[&conn, &channel, &filter, &jsonWriter,
verbose, &receivedCount, &sentCount,
&condition, &conditionVariableMutex,
&progressCondition, &queue]
@ -119,7 +120,7 @@ namespace ix
else if (eventType == ix::CobraConnection_EventType_Authenticated)
{
std::cerr << "Subscriber authenticated" << std::endl;
conn.subscribe(channel,
conn.subscribe(channel, filter,
[&jsonWriter, verbose,
&sentCount, &receivedCount,
&condition, &conditionVariableMutex,

View File

@ -63,6 +63,7 @@ namespace ix
const std::string& rolename,
const std::string& rolesecret,
const std::string& channel,
const std::string& filter,
const std::string& host,
int port,
const std::string& prefix,
@ -90,7 +91,7 @@ namespace ix
uint64_t msgCount = 0;
conn.setEventCallback(
[&conn, &channel, &jsonWriter, &statsdClient, verbose, &tokens, &prefix, &msgCount]
[&conn, &channel, &filter, &jsonWriter, &statsdClient, verbose, &tokens, &prefix, &msgCount]
(ix::CobraConnectionEventType eventType,
const std::string& errMsg,
const ix::WebSocketHttpHeaders& headers,
@ -112,7 +113,7 @@ namespace ix
else if (eventType == ix::CobraConnection_EventType_Authenticated)
{
spdlog::info("Subscriber authenticated");
conn.subscribe(channel,
conn.subscribe(channel, filter,
[&jsonWriter, &statsdClient,
verbose, &tokens, &prefix, &msgCount]
(const Json::Value& msg)

View File

@ -9,6 +9,9 @@
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXSocket.h>
#include "linenoise.hpp"
namespace ix
{
class WebSocketConnect
@ -16,7 +19,8 @@ namespace ix
public:
WebSocketConnect(const std::string& _url,
bool disableAutomaticReconnection,
bool disablePerMessageDeflate);
bool disablePerMessageDeflate,
bool binaryMode);
void subscribe(const std::string& channel);
void start();
@ -28,15 +32,18 @@ namespace ix
std::string _url;
ix::WebSocket _webSocket;
bool _disablePerMessageDeflate;
bool _binaryMode;
void log(const std::string& msg);
};
WebSocketConnect::WebSocketConnect(const std::string& url,
bool disableAutomaticReconnection,
bool disablePerMessageDeflate) :
bool disablePerMessageDeflate,
bool binaryMode) :
_url(url),
_disablePerMessageDeflate(disablePerMessageDeflate)
_disablePerMessageDeflate(disablePerMessageDeflate),
_binaryMode(binaryMode)
{
if (disableAutomaticReconnection)
{
@ -133,45 +140,57 @@ namespace ix
void WebSocketConnect::sendMessage(const std::string& text)
{
_webSocket.sendText(text);
if (_binaryMode)
{
_webSocket.sendBinary(text);
}
else
{
_webSocket.sendText(text);
}
}
int ws_connect_main(const std::string& url,
bool disableAutomaticReconnection,
bool disablePerMessageDeflate)
bool disablePerMessageDeflate,
bool binaryMode)
{
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketConnect webSocketChat(url,
disableAutomaticReconnection,
disablePerMessageDeflate);
disablePerMessageDeflate,
binaryMode);
webSocketChat.start();
while (true)
{
std::string text;
std::cout << "> " << std::flush;
std::getline(std::cin, text);
// Read line
std::string line;
auto quit = linenoise::Readline("> ", line);
if (text == "/stop")
if (quit)
{
break;
}
if (line == "/stop")
{
std::cout << "Stopping connection..." << std::endl;
webSocketChat.stop();
continue;
}
if (text == "/start")
if (line == "/start")
{
std::cout << "Starting connection..." << std::endl;
webSocketChat.start();
continue;
}
if (!std::cin)
{
break;
}
webSocketChat.sendMessage(line);
webSocketChat.sendMessage(text);
// Add text to history
linenoise::AddHistory(line.c_str());
}
std::cout << std::endl;