Compare commits

...

45 Commits

Author SHA1 Message Date
217d0650f4 bump version 2019-08-26 10:20:01 -07:00
45d7bb34d7 ws connect has a new option to send HTTP headers + use WebSocketHttpHeaders instead of unordered_map<string, string> 2019-08-26 10:19:09 -07:00
2e32319236 CobraConnection: sets a unique id field for all messages sent to [cobra](https://github.com/machinezone/cobra).
CobraConnection: sets a counter as a field for each event published.
2019-08-26 09:51:37 -07:00
8eb0d0b7c3 put windows poll in the global namespace, not ix namespace 2019-08-26 09:51:37 -07:00
f18f04c0ee Add client handshake extra headers (#105)
Even though 6455 defines all the necessary headers needed for
client/server handshake, in practice most of the cases websocket servers
expect few more headers. Therefore adding this functionality.
2019-08-26 09:37:40 -07:00
193da820b2 Windows: use select instead of WSAPoll, through a poll wrapper 2019-08-22 10:34:17 -07:00
c6198305d4 add new makefile target to make git tags 2019-08-20 09:21:30 -07:00
c77d6ae3f5 bump version + talk about Windows fix in the changelog 2019-08-20 09:20:02 -07:00
c72b2dbd6b add poll alias to WSAPoll on Windows 2019-08-19 22:26:25 -07:00
835523f77b fix #101 / wrong include in IXSocket.cpp on Windows 2019-08-19 22:19:39 -07:00
ec8a35b587 README tweaks 2019-08-19 20:35:26 -07:00
aca18995d1 README / formatting 2019-08-19 20:33:56 -07:00
f9178f58aa README.md: add reference to WSAStartup to initialize the networking system 2019-08-19 09:47:59 -07:00
2477946e68 (CI) linux: install libmbedtls 2019-08-14 21:49:43 -07:00
7c4d040384 (CI) try to build Linux on Ubuntu Bionic 2019-08-14 21:44:49 -07:00
197cf8ed36 bump version 2019-08-14 21:36:20 -07:00
dd0d7c268f CobraMetricThreadedPublisher _enable flag is an atomic, and CobraMetricsPublisher is enabled by default 2019-08-14 19:54:30 -07:00
b2bfccac0a clang format 2019-08-13 10:59:18 -07:00
8b8b352e61 fix #99 / Connect error descriptions are invalid 2019-08-13 10:49:11 -07:00
0403dd354b update readme 2019-08-06 20:55:44 -07:00
b78b453504 fix #98 2019-08-02 17:11:53 -07:00
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
51 changed files with 3460 additions and 264 deletions

12
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,12 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pocc/pre-commit-hooks
rev: ''
hooks:
- id: clang-format

View File

@ -7,14 +7,19 @@ 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
dist: bionic
before_install:
- sudo apt-get install -y libmbedtls-dev
script:
- python test/run.py
- make ws
@ -30,12 +35,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,39 @@
# Changelog
All notable changes to this project will be documented in this file.
## [5.0.7] - 2019-08-23
- WebSocket: add new option to pass in extra HTTP headers when connecting.
- `ws connect` add new option (-H, works like [curl](https://stackoverflow.com/questions/356705/how-to-send-a-header-using-a-http-request-through-a-curl-call)) to pass in extra HTTP headers when connecting
If you run against `ws echo_server` you will see the headers being received printed in the terminal.
```
ws connect -H "foo: bar" -H "baz: buz" ws://127.0.0.1:8008
```
- CobraConnection: sets a unique id field for all messages sent to [cobra](https://github.com/machinezone/cobra).
- CobraConnection: sets a counter as a field for each event published.
## [5.0.6] - 2019-08-22
- Windows: silly compile error (poll should be in the global namespace)
## [5.0.5] - 2019-08-22
- Windows: use select instead of WSAPoll, through a poll wrapper
## [5.0.4] - 2019-08-20
- Windows build fixes (there was a problem with the use of ::poll that has a different name on Windows (WSAPoll))
## [5.0.3] - 2019-08-14
- CobraMetricThreadedPublisher _enable flag is an atomic, and CobraMetricsPublisher is enabled by default
## [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.7

115
README.md
View File

@ -16,9 +16,32 @@
The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage.
Here is what the client API looks like.
### Windows note
To use the network system on Windows, you need to initialize it once with *WSAStartup()* and clean it up with *WSACleanup()*. We have helpers for that which you can use, see below. This init would typically take place in your main function.
```
#include <ixwebsocket/IXNetSystem.h>
int main()
{
ix::initNetSystem();
...
ix::uninitNetSystem();
return 0;
}
```
### WebSocket client API
```
#include <ixwebsocket/IXWebSocket.h>
...
# Our websocket object
ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
@ -32,14 +55,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();
@ -56,9 +79,13 @@ webSocket.sendBinary("some serialized binary data");
webSocket.stop()
```
Here is what the server API looks like. Note that server support is very recent and subject to changes.
### WebSocket server API
```
#include <ixwebsocket/IXWebSocketServer.h>
...
// Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters.
ix::WebSocketServer server(port);
@ -117,9 +144,13 @@ server.wait();
```
Here is what the HTTP client API looks like.
### HTTP client API
```
#include <ixwebsocket/IXHttpClient.h>
...
//
// Preparation
//
@ -170,7 +201,7 @@ out = httpClient.post(url, std::string("foo=bar"), args);
//
// Result
//
auto errorCode = response->errorCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
auto statusCode = response->statusCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
auto errorCode = response->errorCode; // 200, 404, etc...
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string)
auto payload = response->payload; // All the bytes from the response as an std::string
@ -196,9 +227,11 @@ bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
// ok will be false if your httpClient is not async
```
Here is what the HTTP server API looks like. Note that HTTP server support is very, very recent and subject to changes.
### HTTP server API
```
#include <ixwebsocket/IXHttpServer.h>
ix::HttpServer server(port, hostname);
auto res = server.listen();
@ -221,7 +254,7 @@ setOnConnectionCallback(
{
// Build a string for the response
std::stringstream ss;
ss << request->method
ss << request->method
<< " "
<< request->uri;
@ -236,23 +269,52 @@ 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.
```
mkdir build # make a build dir so that you can build out of tree.
cd build
cmake ..
cmake -DUSE_TLS=1 ..
make -j
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
```
Headers and a static library will be installed to the target dir.
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.
Options for building:
* `-DUSE_TLS=1` will enable TLS support
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support (default on Windows)
* `-DUSE_WS=1` will build the ws interactive command line tool
### 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 +327,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 +419,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 +450,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 +484,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)
@ -457,3 +510,13 @@ idle connection.
```
webSocket.setHeartBeatPeriod(45);
```
### Supply extra HTTP headers.
You can set extra HTTP headers to be sent during the WebSocket handshake.
```
WebSocketHttpHeaders headers;
headers["foo"] = "bar";
webSocket.setExtraHeaders(headers);
```

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,11 +12,10 @@
#include "IXCancellationRequest.h"
#include <atomic>
#include <condition_variable>
#include <memory>
#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

@ -6,8 +6,8 @@
#pragma once
#include "IXWebSocketHttpHeaders.h"
#include "IXProgressCallback.h"
#include "IXWebSocketHttpHeaders.h"
#include <tuple>
namespace ix
@ -111,10 +111,12 @@ namespace ix
class Http
{
public:
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(std::shared_ptr<Socket> socket);
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(
std::shared_ptr<Socket> socket);
static bool sendResponse(HttpResponsePtr response, std::shared_ptr<Socket> socket);
static std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
static std::tuple<std::string, std::string, std::string> parseRequestLine(
const std::string& line);
static std::string trim(const std::string& str);
};
}
} // namespace ix

View File

@ -14,6 +14,7 @@
#include <vector>
#include <cstring>
#include <assert.h>
#include <zlib.h>
namespace ix
@ -52,6 +53,8 @@ namespace ix
bool HttpClient::performRequest(HttpRequestArgsPtr args,
const OnResponseCallback& onResponseCallback)
{
assert(_async && "HttpClient needs its async parameter set to true "
"in order to call performRequest");
if (!_async) return false;
// Enqueue the task

View File

@ -6,9 +6,9 @@
#pragma once
#include "IXHttp.h"
#include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXHttp.h"
#include <algorithm>
#include <atomic>
#include <condition_variable>

View File

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

View File

@ -6,9 +6,9 @@
#pragma once
#include "IXHttp.h"
#include "IXSocketServer.h"
#include "IXWebSocket.h"
#include "IXHttp.h"
#include <functional>
#include <memory>
#include <mutex>
@ -47,4 +47,3 @@ namespace ix
void setDefaultConnectionCallback();
};
} // namespace ix

View File

@ -15,9 +15,8 @@ namespace ix
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
// Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
return err == 0;
@ -30,10 +29,83 @@ namespace ix
{
#ifdef _WIN32
int err = WSACleanup();
return err == 0;
#else
return true;
#endif
}
}
// This function should be in the global namespace
#ifdef _WIN32
//
// That function could 'return WSAPoll(pfd, nfds, timeout);'
// but WSAPoll is said to have weird behaviors on the internet
// (the curl folks have had problems with it).
//
// So we make it a select wrapper
//
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
int maxfd = 0;
fd_set readfds, writefds, errorfds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&errorfds);
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd *fd = &fds[i];
if (fd->fd > maxfd)
{
maxfd = fd->fd;
}
if ((fd->events & POLLIN))
{
FD_SET(fd->fd, &readfds);
}
if ((fd->events & POLLOUT))
{
FD_SET(fd->fd, &writefds);
}
if ((fd->events & POLLERR))
{
FD_SET(fd->fd, &errorfds);
}
}
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds,
timeout != -1 ? &tv : NULL);
if (ret < 0)
{
return ret;
}
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd *fd = &fds[i];
fd->revents = 0;
if (FD_ISSET(fd->fd, &readfds))
{
fd->revents |= POLLIN;
}
if (FD_ISSET(fd->fd, &writefds))
{
fd->revents |= POLLOUT;
}
if (FD_ISSET(fd->fd, &errorfds))
{
fd->revents |= POLLERR;
}
}
return ret;
}
#endif

View File

@ -12,11 +12,18 @@
#include <basetsd.h>
#include <io.h>
#include <ws2def.h>
// Define our own poll on Windows
typedef unsigned long int nfds_t;
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
#else
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>

View File

@ -44,41 +44,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.
//
nfds_t 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 +90,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 +103,36 @@ 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;
// set errno to optval so that external callers can have an
// appropriate error description when calling strerror
errno = optval;
}
#endif
}
return pollResult;
@ -122,7 +146,7 @@ namespace ix
}
bool readyToRead = true;
return select(readyToRead, timeoutMs);
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
}
PollResultType Socket::isReadyToWrite(int timeoutMs)
@ -133,7 +157,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 +256,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 +274,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

@ -70,6 +70,11 @@ namespace ix
std::lock_guard<std::mutex> lock(_configMutex);
_url = url;
}
void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers)
{
std::lock_guard<std::mutex> lock(_configMutex);
_extraHeaders = headers;
}
const std::string& WebSocket::getUrl() const
{
@ -176,7 +181,7 @@ namespace ix
_pingTimeoutSecs);
}
WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
WebSocketInitResult status = _ws.connectToUrl(_url, _extraHeaders, timeoutSecs);
if (!status.success)
{
return status;

View File

@ -44,6 +44,9 @@ namespace ix
~WebSocket();
void setUrl(const std::string& url);
// send extra headers in client handshake request
void setExtraHeaders(const WebSocketHttpHeaders& headers);
void setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setHeartBeatPeriod(int heartBeatPeriodSecs);
@ -111,6 +114,8 @@ namespace ix
WebSocketTransport _ws;
std::string _url;
WebSocketHttpHeaders _extraHeaders;
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
mutable std::mutex _configMutex; // protect all config variables access

View File

@ -88,6 +88,7 @@ namespace ix
}
WebSocketInitResult WebSocketHandshake::clientHandshake(const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const std::string& host,
const std::string& path,
int port,
@ -127,6 +128,9 @@ namespace ix
ss << "Sec-WebSocket-Version: 13\r\n";
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
for (auto& it : extraHeaders) {
ss << it.first << ":" << it.second << "\r\n";
}
if (_enablePerMessageDeflate)
{
ss << _perMessageDeflateOptions.generateHeader();

View File

@ -16,6 +16,7 @@
#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
namespace ix
{
@ -50,11 +51,13 @@ namespace ix
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
std::atomic<bool>& enablePerMessageDeflate);
WebSocketInitResult clientHandshake(const std::string& url,
const std::string& host,
const std::string& path,
int port,
int timeoutSecs);
WebSocketInitResult clientHandshake(
const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const std::string& host,
const std::string& path,
int port,
int timeoutSecs);
WebSocketInitResult serverHandshake(int fd, int timeoutSecs);

View File

@ -127,8 +127,10 @@ namespace ix
}
// Client
WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url,
int timeoutSecs)
WebSocketInitResult WebSocketTransport::connectToUrl(
const std::string& url,
const WebSocketHttpHeaders& headers,
int timeoutSecs)
{
std::lock_guard<std::mutex> lock(_socketMutex);
@ -156,8 +158,8 @@ namespace ix
_perMessageDeflateOptions,
_enablePerMessageDeflate);
auto result = webSocketHandshake.clientHandshake(url, host, path, port,
timeoutSecs);
auto result = webSocketHandshake.clientHandshake(url, headers, host, path,
port, timeoutSecs);
if (result.success)
{
setReadyState(ReadyState::OPEN);
@ -330,7 +332,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 +544,7 @@ namespace ix
) {
unmaskReceiveBuffer(ws);
MessageKind messageKind =
MessageKind messageKind =
(ws.opcode == wsheader_type::TEXT_FRAME)
? MessageKind::MSG_TEXT
: MessageKind::MSG_BINARY;
@ -755,7 +757,7 @@ namespace ix
{
if (_readyState != ReadyState::OPEN && _readyState != ReadyState::CLOSING)
{
return WebSocketSendInfo();
return WebSocketSendInfo(false);
}
size_t payloadSize = message.size();

View File

@ -24,6 +24,7 @@
#include <memory>
#include <mutex>
#include <string>
#include <vector>
namespace ix
@ -75,8 +76,10 @@ namespace ix
int pingIntervalSecs,
int pingTimeoutSecs);
WebSocketInitResult connectToUrl(const std::string& url, // Client
int timeoutSecs);
WebSocketInitResult connectToUrl( // Client
const std::string& url,
const WebSocketHttpHeaders& headers,
int timeoutSecs);
WebSocketInitResult connectToSocket(int fd, // Server
int timeoutSecs);

View File

@ -12,11 +12,14 @@ 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
tag:
git tag v"`cat DOCKER_VERSION`"
.PHONY: docker
NAME := bsergean/ws

View File

@ -6,9 +6,9 @@
#pragma once
#include "IXGetFreePort.h"
#include <iostream>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXGetFreePort.h"
#include <mutex>
#include <spdlog/spdlog.h>
#include <sstream>

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

@ -24,7 +24,8 @@ namespace ix
_webSocket(new WebSocket()),
_publishMode(CobraConnection_PublishMode_Immediate),
_authenticated(false),
_eventCallback(nullptr)
_eventCallback(nullptr),
_id(0)
{
_pdu["action"] = "rtm/publish";
@ -244,6 +245,7 @@ namespace ix
Json::Value pdu;
pdu["action"] = "auth/handshake";
pdu["body"] = body;
pdu["id"] = _id++;
std::string serializedJson = serializeJson(pdu);
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
@ -306,6 +308,7 @@ namespace ix
Json::Value pdu;
pdu["action"] = "auth/authenticate";
pdu["body"] = body;
pdu["id"] = _id++;
std::string serializedJson = serializeJson(pdu);
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
@ -402,6 +405,7 @@ namespace ix
_body["channels"] = channels;
_body["message"] = msg;
_pdu["body"] = _body;
_pdu["id"] = _id++;
std::string serializedJson = serializeJson(_pdu);
@ -429,15 +433,22 @@ 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;
pdu["id"] = _id++;
_webSocket->send(pdu.toStyledString());
@ -463,6 +474,7 @@ namespace ix
Json::Value pdu;
pdu["action"] = "rtm/unsubscribe";
pdu["body"] = body;
pdu["id"] = _id++;
_webSocket->send(pdu.toStyledString());
}

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);
@ -166,6 +168,9 @@ namespace ix
// Cap the queue size (100 elems so far -> ~100k)
static constexpr size_t kQueueMaxSize = 256;
// Each pdu sent should have an incremental unique id
std::atomic<uint64_t> _id;
};
} // namespace ix

View File

@ -17,7 +17,7 @@ namespace ix
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
CobraMetricsPublisher::CobraMetricsPublisher() :
_enabled(false)
_enabled(true)
{
}
@ -191,6 +191,19 @@ namespace ix
msg["device"] = _device;
}
{
//
// Bump a counter for each id
// This is used to make sure that we are not
// dropping messages, by checking that all the ids is the list of
// all natural numbers until the last value sent (0, 1, 2, ..., N)
//
std::lock_guard<std::mutex> lock(_device_mutex);
auto it = _counters.emplace(id, 0);
msg["per_id_counter"] = it.first->second;
it.first->second += 1;
}
// Now actually enqueue the task
_cobra_metrics_theaded_publisher.push(msg);
}

View File

@ -7,6 +7,7 @@
#pragma once
#include "IXCobraMetricsThreadedPublisher.h"
#include <atomic>
#include <chrono>
#include <jsoncpp/json/json.h>
#include <string>
@ -132,8 +133,8 @@ namespace ix
CobraMetricsThreadedPublisher _cobra_metrics_theaded_publisher;
/// A boolean to enable or disable this system
/// push becomes a no-op when _enabled is true
bool _enabled;
/// push becomes a no-op when _enabled is false
std::atomic<bool> _enabled;
/// A uuid used to uniquely identify a session
std::string _session;
@ -150,6 +151,10 @@ namespace ix
_last_update;
mutable std::mutex _last_update_mutex; // protect access to _last_update
/// Bump a counter for each metric type
std::unordered_map<std::string, int> _counters;
mutable std::mutex _counters_mutex; // protect access to _counters
// const strings for internal ids
static const std::string kSetRateControlId;
static const std::string kSetBlacklistId;

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;
@ -111,8 +109,10 @@ int main(int argc, char** argv)
CLI::App* connectApp = app.add_subcommand("connect", "Connect to a remote server");
connectApp->add_option("url", url, "Connection url")->required();
connectApp->add_option("-H", headers, "Header")->join();
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 +168,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 +194,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 +207,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");
@ -249,8 +253,8 @@ int main(int argc, char** argv)
}
else if (app.got_subcommand("connect"))
{
ret = ix::ws_connect_main(url, disableAutomaticReconnection,
disablePerMessageDeflate);
ret = ix::ws_connect_main(url, headers, disableAutomaticReconnection,
disablePerMessageDeflate, binaryMode);
}
else if (app.got_subcommand("chat"))
{
@ -288,7 +292,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 +304,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"))

10
ws/ws.h
View File

@ -31,8 +31,10 @@ namespace ix
int ws_chat_main(const std::string& url, const std::string& user);
int ws_connect_main(const std::string& url,
const std::string& headers,
bool disableAutomaticReconnection,
bool disablePerMessageDeflate);
bool disablePerMessageDeflate,
bool binaryMode);
int ws_receive_main(const std::string& url, bool enablePerMessageDeflate, int delayMs);
@ -55,7 +57,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 +74,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 +86,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,14 +9,19 @@
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXSocket.h>
#include "linenoise.hpp"
namespace ix
{
class WebSocketConnect
{
public:
WebSocketConnect(const std::string& _url,
const std::string& headers,
bool disableAutomaticReconnection,
bool disablePerMessageDeflate);
bool disablePerMessageDeflate,
bool binaryMode);
void subscribe(const std::string& channel);
void start();
@ -26,22 +31,30 @@ namespace ix
private:
std::string _url;
WebSocketHttpHeaders _headers;
ix::WebSocket _webSocket;
bool _disablePerMessageDeflate;
bool _binaryMode;
void log(const std::string& msg);
WebSocketHttpHeaders parseHeaders(const std::string& data);
};
WebSocketConnect::WebSocketConnect(const std::string& url,
const std::string& headers,
bool disableAutomaticReconnection,
bool disablePerMessageDeflate) :
bool disablePerMessageDeflate,
bool binaryMode) :
_url(url),
_disablePerMessageDeflate(disablePerMessageDeflate)
_disablePerMessageDeflate(disablePerMessageDeflate),
_binaryMode(binaryMode)
{
if (disableAutomaticReconnection)
{
_webSocket.disableAutomaticReconnection();
}
_headers = parseHeaders(headers);
}
void WebSocketConnect::log(const std::string& msg)
@ -49,6 +62,31 @@ namespace ix
std::cout << msg << std::endl;
}
WebSocketHttpHeaders WebSocketConnect::parseHeaders(const std::string& data)
{
WebSocketHttpHeaders headers;
// Split by \n
std::string token;
std::stringstream tokenStream(data);
while (std::getline(tokenStream, token))
{
std::size_t pos = token.rfind(':');
// Bail out if last '.' is found
if (pos == std::string::npos) continue;
auto key = token.substr(0, pos);
auto val = token.substr(pos+1);
std::cerr << key << ": " << val << std::endl;
headers[key] = val;
}
return headers;
}
void WebSocketConnect::stop()
{
_webSocket.stop();
@ -57,6 +95,7 @@ namespace ix
void WebSocketConnect::start()
{
_webSocket.setUrl(_url);
_webSocket.setExtraHeaders(_headers);
if (_disablePerMessageDeflate)
{
@ -133,45 +172,59 @@ 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,
const std::string& headers,
bool disableAutomaticReconnection,
bool disablePerMessageDeflate)
bool disablePerMessageDeflate,
bool binaryMode)
{
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketConnect webSocketChat(url,
headers,
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;

View File

@ -44,7 +44,7 @@ namespace ix
if (pos == std::string::npos) continue;
auto key = token.substr(0, pos);
auto val = token.substr(pos+2);
auto val = token.substr(pos+1);
std::cerr << key << ": " << val << std::endl;
headers[key] = val;