Compare commits

..

22 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
32 changed files with 386 additions and 75 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

@ -15,15 +15,17 @@ matrix:
- python test/run.py - python test/run.py
- make ws - make ws
# # Linux # Linux
# - os: linux - os: linux
# dist: xenial dist: bionic
# script: before_install:
# - python test/run.py - sudo apt-get install -y libmbedtls-dev
# - make ws script:
# env: - python test/run.py
# - CC=gcc - make ws
# - CXX=g++ env:
- CC=gcc
- CXX=g++
# Clang + Linux disabled for now # Clang + Linux disabled for now
# - os: linux # - os: linux

View File

@ -1,8 +1,36 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. 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 ## [5.0.1] - 2019-07-25
### Unreleased
- ws connect command has a new option to send in binary mode (still default to text) - 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 - 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

View File

@ -1 +1 @@
5.0.1 5.0.7

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. 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; ix::WebSocket webSocket;
std::string url("ws://localhost:8080/"); std::string url("ws://localhost:8080/");
@ -56,9 +79,13 @@ webSocket.sendBinary("some serialized binary data");
webSocket.stop() 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. // Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters. // Bound host name, max connections and listen backlog can also be passed in as parameters.
ix::WebSocketServer server(port); 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 // Preparation
// //
@ -170,7 +201,7 @@ out = httpClient.post(url, std::string("foo=bar"), args);
// //
// Result // 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 errorCode = response->errorCode; // 200, 404, etc...
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string) 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 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 // 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); ix::HttpServer server(port, hostname);
auto res = server.listen(); auto res = server.listen();
@ -243,7 +276,7 @@ CMakefiles for the library and the examples are available. This library has few
``` ```
mkdir build # make a build dir so that you can build out of tree. mkdir build # make a build dir so that you can build out of tree.
cd build cd build
cmake .. cmake -DUSE_TLS=1 ..
make -j 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 make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
``` ```
@ -251,6 +284,12 @@ 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. Headers and a static library will be installed to the target dir.
There is a unittest which can be executed by typing `make test`. There is a unittest which can be executed by typing `make test`.
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 ### vcpkg
It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/microsoft/vcpkg). It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/microsoft/vcpkg).
@ -471,3 +510,13 @@ idle connection.
``` ```
webSocket.setHeartBeatPeriod(45); 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

@ -12,10 +12,10 @@
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include <atomic> #include <atomic>
#include <memory>
#include <mutex> #include <mutex>
#include <set> #include <set>
#include <string> #include <string>
#include <memory>
struct addrinfo; struct addrinfo;

View File

@ -6,8 +6,8 @@
#pragma once #pragma once
#include "IXWebSocketHttpHeaders.h"
#include "IXProgressCallback.h" #include "IXProgressCallback.h"
#include "IXWebSocketHttpHeaders.h"
#include <tuple> #include <tuple>
namespace ix namespace ix
@ -111,10 +111,12 @@ namespace ix
class Http class Http
{ {
public: 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 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); static std::string trim(const std::string& str);
}; };
} } // namespace ix

View File

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

View File

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

View File

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

View File

@ -15,9 +15,8 @@ namespace ix
WSADATA wsaData; WSADATA wsaData;
int err; 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); wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData); err = WSAStartup(wVersionRequested, &wsaData);
return err == 0; return err == 0;
@ -30,10 +29,83 @@ namespace ix
{ {
#ifdef _WIN32 #ifdef _WIN32
int err = WSACleanup(); int err = WSACleanup();
return err == 0; return err == 0;
#else #else
return true; return true;
#endif #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 <basetsd.h>
#include <io.h> #include <io.h>
#include <ws2def.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 #else
#include <arpa/inet.h> #include <arpa/inet.h>
#include <errno.h> #include <errno.h>
#include <netdb.h> #include <netdb.h>
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <poll.h>
#include <sys/select.h> #include <sys/select.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>

View File

@ -17,7 +17,6 @@
#include <stdint.h> #include <stdint.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
#include <poll.h>
#include <algorithm> #include <algorithm>
@ -59,7 +58,7 @@ namespace ix
// shim to fallback to select on those platforms. // shim to fallback to select on those platforms.
// See https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper. // See https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
// //
int nfds = 1; nfds_t nfds = 1;
struct pollfd fds[2]; struct pollfd fds[2];
fds[0].fd = sockfd; fds[0].fd = sockfd;
@ -128,6 +127,10 @@ namespace ix
optval != 0) optval != 0)
{ {
pollResult = PollResultType::Error; pollResult = PollResultType::Error;
// set errno to optval so that external callers can have an
// appropriate error description when calling strerror
errno = optval;
} }
#endif #endif
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -127,8 +127,10 @@ namespace ix
} }
// Client // Client
WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url, WebSocketInitResult WebSocketTransport::connectToUrl(
int timeoutSecs) const std::string& url,
const WebSocketHttpHeaders& headers,
int timeoutSecs)
{ {
std::lock_guard<std::mutex> lock(_socketMutex); std::lock_guard<std::mutex> lock(_socketMutex);
@ -156,8 +158,8 @@ namespace ix
_perMessageDeflateOptions, _perMessageDeflateOptions,
_enablePerMessageDeflate); _enablePerMessageDeflate);
auto result = webSocketHandshake.clientHandshake(url, host, path, port, auto result = webSocketHandshake.clientHandshake(url, headers, host, path,
timeoutSecs); port, timeoutSecs);
if (result.success) if (result.success)
{ {
setReadyState(ReadyState::OPEN); setReadyState(ReadyState::OPEN);

View File

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

View File

@ -17,6 +17,9 @@ ws:
uninstall: uninstall:
xargs rm -fv < build/install_manifest.txt xargs rm -fv < build/install_manifest.txt
tag:
git tag v"`cat DOCKER_VERSION`"
.PHONY: docker .PHONY: docker
NAME := bsergean/ws NAME := bsergean/ws

View File

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

View File

@ -24,7 +24,8 @@ namespace ix
_webSocket(new WebSocket()), _webSocket(new WebSocket()),
_publishMode(CobraConnection_PublishMode_Immediate), _publishMode(CobraConnection_PublishMode_Immediate),
_authenticated(false), _authenticated(false),
_eventCallback(nullptr) _eventCallback(nullptr),
_id(0)
{ {
_pdu["action"] = "rtm/publish"; _pdu["action"] = "rtm/publish";
@ -244,6 +245,7 @@ namespace ix
Json::Value pdu; Json::Value pdu;
pdu["action"] = "auth/handshake"; pdu["action"] = "auth/handshake";
pdu["body"] = body; pdu["body"] = body;
pdu["id"] = _id++;
std::string serializedJson = serializeJson(pdu); std::string serializedJson = serializeJson(pdu);
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false); CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
@ -306,6 +308,7 @@ namespace ix
Json::Value pdu; Json::Value pdu;
pdu["action"] = "auth/authenticate"; pdu["action"] = "auth/authenticate";
pdu["body"] = body; pdu["body"] = body;
pdu["id"] = _id++;
std::string serializedJson = serializeJson(pdu); std::string serializedJson = serializeJson(pdu);
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false); CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
@ -402,6 +405,7 @@ namespace ix
_body["channels"] = channels; _body["channels"] = channels;
_body["message"] = msg; _body["message"] = msg;
_pdu["body"] = _body; _pdu["body"] = _body;
_pdu["id"] = _id++;
std::string serializedJson = serializeJson(_pdu); std::string serializedJson = serializeJson(_pdu);
@ -429,15 +433,22 @@ namespace ix
} }
void CobraConnection::subscribe(const std::string& channel, void CobraConnection::subscribe(const std::string& channel,
SubscriptionCallback cb) const std::string& filter,
SubscriptionCallback cb)
{ {
// Create and send a subscribe pdu // Create and send a subscribe pdu
Json::Value body; Json::Value body;
body["channel"] = channel; body["channel"] = channel;
if (!filter.empty())
{
body["filter"] = filter;
}
Json::Value pdu; Json::Value pdu;
pdu["action"] = "rtm/subscribe"; pdu["action"] = "rtm/subscribe";
pdu["body"] = body; pdu["body"] = body;
pdu["id"] = _id++;
_webSocket->send(pdu.toStyledString()); _webSocket->send(pdu.toStyledString());
@ -463,6 +474,7 @@ namespace ix
Json::Value pdu; Json::Value pdu;
pdu["action"] = "rtm/unsubscribe"; pdu["action"] = "rtm/unsubscribe";
pdu["body"] = body; pdu["body"] = body;
pdu["id"] = _id++;
_webSocket->send(pdu.toStyledString()); _webSocket->send(pdu.toStyledString());
} }

View File

@ -75,7 +75,9 @@ namespace ix
// Subscribe to a channel, and execute a callback when an incoming // Subscribe to a channel, and execute a callback when an incoming
// message arrives. // 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 /// Unsubscribe from a channel
void unsubscribe(const std::string& channel); void unsubscribe(const std::string& channel);
@ -166,6 +168,9 @@ namespace ix
// Cap the queue size (100 elems so far -> ~100k) // Cap the queue size (100 elems so far -> ~100k)
static constexpr size_t kQueueMaxSize = 256; static constexpr size_t kQueueMaxSize = 256;
// Each pdu sent should have an incremental unique id
std::atomic<uint64_t> _id;
}; };
} // namespace ix } // namespace ix

View File

@ -17,7 +17,7 @@ namespace ix
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id"; const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
CobraMetricsPublisher::CobraMetricsPublisher() : CobraMetricsPublisher::CobraMetricsPublisher() :
_enabled(false) _enabled(true)
{ {
} }
@ -191,6 +191,19 @@ namespace ix
msg["device"] = _device; 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 // Now actually enqueue the task
_cobra_metrics_theaded_publisher.push(msg); _cobra_metrics_theaded_publisher.push(msg);
} }

View File

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

View File

@ -9,15 +9,10 @@
// //
#include "ws.h" #include "ws.h"
//
// Main drive for websocket utilities
//
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
// #include <unistd.h>
#include <cli11/CLI11.hpp> #include <cli11/CLI11.hpp>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
@ -60,6 +55,7 @@ int main(int argc, char** argv)
std::string hostname("127.0.0.1"); std::string hostname("127.0.0.1");
std::string pidfile; std::string pidfile;
std::string channel; std::string channel;
std::string filter;
std::string message; std::string message;
std::string password; std::string password;
std::string appkey; std::string appkey;
@ -76,6 +72,7 @@ int main(int argc, char** argv)
bool followRedirects = false; bool followRedirects = false;
bool verbose = false; bool verbose = false;
bool save = false; bool save = false;
bool quiet = false;
bool compress = false; bool compress = false;
bool strict = false; bool strict = false;
bool stress = false; bool stress = false;
@ -112,6 +109,7 @@ int main(int argc, char** argv)
CLI::App* connectApp = app.add_subcommand("connect", "Connect to a remote server"); CLI::App* connectApp = app.add_subcommand("connect", "Connect to a remote server");
connectApp->add_option("url", url, "Connection url")->required(); 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("-d", disableAutomaticReconnection, "Disable Automatic Reconnection");
connectApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate"); connectApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate");
connectApp->add_flag("-b", binaryMode, "Send in binary mode"); connectApp->add_flag("-b", binaryMode, "Send in binary mode");
@ -170,6 +168,8 @@ int main(int argc, char** argv)
cobraSubscribeApp->add_option("--rolesecret", rolesecret, "Role secret"); cobraSubscribeApp->add_option("--rolesecret", rolesecret, "Role secret");
cobraSubscribeApp->add_option("channel", channel, "Channel")->required(); cobraSubscribeApp->add_option("channel", channel, "Channel")->required();
cobraSubscribeApp->add_option("--pidfile", pidfile, "Pid file"); 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"); CLI::App* cobraPublish = app.add_subcommand("cobra_publish", "Cobra publisher");
cobraPublish->add_option("--appkey", appkey, "Appkey"); cobraPublish->add_option("--appkey", appkey, "Appkey");
@ -194,6 +194,7 @@ int main(int argc, char** argv)
cobra2statsd->add_option("channel", channel, "Channel")->required(); cobra2statsd->add_option("channel", channel, "Channel")->required();
cobra2statsd->add_flag("-v", verbose, "Verbose"); cobra2statsd->add_flag("-v", verbose, "Verbose");
cobra2statsd->add_option("--pidfile", pidfile, "Pid file"); 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"); CLI::App* cobra2sentry = app.add_subcommand("cobra_to_sentry", "Cobra to sentry");
cobra2sentry->add_option("--appkey", appkey, "Appkey"); cobra2sentry->add_option("--appkey", appkey, "Appkey");
@ -206,6 +207,7 @@ int main(int argc, char** argv)
cobra2sentry->add_flag("-v", verbose, "Verbose"); cobra2sentry->add_flag("-v", verbose, "Verbose");
cobra2sentry->add_flag("-s", strict, "Strict mode. Error out when sending to sentry fails"); cobra2sentry->add_flag("-s", strict, "Strict mode. Error out when sending to sentry fails");
cobra2sentry->add_option("--pidfile", pidfile, "Pid file"); cobra2sentry->add_option("--pidfile", pidfile, "Pid file");
cobra2sentry->add_option("--filter", filter, "Stream SQL Filter");
CLI::App* runApp = app.add_subcommand("snake", "Snake server"); CLI::App* runApp = app.add_subcommand("snake", "Snake server");
runApp->add_option("--port", port, "Connection url"); runApp->add_option("--port", port, "Connection url");
@ -251,7 +253,7 @@ int main(int argc, char** argv)
} }
else if (app.got_subcommand("connect")) else if (app.got_subcommand("connect"))
{ {
ret = ix::ws_connect_main(url, disableAutomaticReconnection, ret = ix::ws_connect_main(url, headers, disableAutomaticReconnection,
disablePerMessageDeflate, binaryMode); disablePerMessageDeflate, binaryMode);
} }
else if (app.got_subcommand("chat")) else if (app.got_subcommand("chat"))
@ -290,7 +292,7 @@ int main(int argc, char** argv)
{ {
ret = ix::ws_cobra_subscribe_main(appkey, endpoint, ret = ix::ws_cobra_subscribe_main(appkey, endpoint,
rolename, rolesecret, rolename, rolesecret,
channel); channel, filter, quiet);
} }
else if (app.got_subcommand("cobra_publish")) else if (app.got_subcommand("cobra_publish"))
{ {
@ -302,14 +304,14 @@ int main(int argc, char** argv)
{ {
ret = ix::ws_cobra_to_statsd_main(appkey, endpoint, ret = ix::ws_cobra_to_statsd_main(appkey, endpoint,
rolename, rolesecret, rolename, rolesecret,
channel, hostname, statsdPort, channel, filter, hostname, statsdPort,
prefix, fields, verbose); prefix, fields, verbose);
} }
else if (app.got_subcommand("cobra_to_sentry")) else if (app.got_subcommand("cobra_to_sentry"))
{ {
ret = ix::ws_cobra_to_sentry_main(appkey, endpoint, ret = ix::ws_cobra_to_sentry_main(appkey, endpoint,
rolename, rolesecret, rolename, rolesecret,
channel, dsn, channel, filter, dsn,
verbose, strict, jobs); verbose, strict, jobs);
} }
else if (app.got_subcommand("snake")) else if (app.got_subcommand("snake"))

View File

@ -31,6 +31,7 @@ namespace ix
int ws_chat_main(const std::string& url, const std::string& user); int ws_chat_main(const std::string& url, const std::string& user);
int ws_connect_main(const std::string& url, int ws_connect_main(const std::string& url,
const std::string& headers,
bool disableAutomaticReconnection, bool disableAutomaticReconnection,
bool disablePerMessageDeflate, bool disablePerMessageDeflate,
bool binaryMode); bool binaryMode);
@ -56,7 +57,9 @@ namespace ix
const std::string& endpoint, const std::string& endpoint,
const std::string& rolename, const std::string& rolename,
const std::string& rolesecret, 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, int ws_cobra_publish_main(const std::string& appkey,
const std::string& endpoint, const std::string& endpoint,
@ -71,6 +74,7 @@ namespace ix
const std::string& rolename, const std::string& rolename,
const std::string& rolesecret, const std::string& rolesecret,
const std::string& channel, const std::string& channel,
const std::string& filter,
const std::string& host, const std::string& host,
int port, int port,
const std::string& prefix, const std::string& prefix,
@ -82,6 +86,7 @@ namespace ix
const std::string& rolename, const std::string& rolename,
const std::string& rolesecret, const std::string& rolesecret,
const std::string& channel, const std::string& channel,
const std::string& filter,
const std::string& dsn, const std::string& dsn,
bool verbose, bool verbose,
bool strict, bool strict,

View File

@ -11,13 +11,17 @@
#include <atomic> #include <atomic>
#include <ixcobra/IXCobraConnection.h> #include <ixcobra/IXCobraConnection.h>
#include <spdlog/spdlog.h>
namespace ix namespace ix
{ {
int ws_cobra_subscribe_main(const std::string& appkey, int ws_cobra_subscribe_main(const std::string& appkey,
const std::string& endpoint, const std::string& endpoint,
const std::string& rolename, const std::string& rolename,
const std::string& rolesecret, const std::string& rolesecret,
const std::string& channel) const std::string& channel,
const std::string& filter,
bool quiet)
{ {
ix::CobraConnection conn; ix::CobraConnection conn;
@ -28,8 +32,28 @@ namespace ix
Json::FastWriter jsonWriter; 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.setEventCallback(
[&conn, &channel, &jsonWriter] [&conn, &channel, &jsonWriter, &filter, &msgCount, &msgPerSeconds, &quiet]
(ix::CobraConnectionEventType eventType, (ix::CobraConnectionEventType eventType,
const std::string& errMsg, const std::string& errMsg,
const ix::WebSocketHttpHeaders& headers, const ix::WebSocketHttpHeaders& headers,
@ -37,33 +61,40 @@ namespace ix
{ {
if (eventType == ix::CobraConnection_EventType_Open) if (eventType == ix::CobraConnection_EventType_Open)
{ {
std::cout << "Subscriber: connected" << std::endl; spdlog::info("Subscriber connected");
for (auto it : headers) 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) else if (eventType == ix::CobraConnection_EventType_Authenticated)
{ {
std::cout << "Subscriber authenticated" << std::endl; spdlog::info("Subscriber authenticated");
conn.subscribe(channel, conn.subscribe(channel, filter,
[&jsonWriter](const Json::Value& msg) [&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) 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) 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) 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& rolename,
const std::string& rolesecret, const std::string& rolesecret,
const std::string& channel, const std::string& channel,
const std::string& filter,
const std::string& dsn, const std::string& dsn,
bool verbose, bool verbose,
bool strict, bool strict,
@ -94,7 +95,7 @@ namespace ix
} }
conn.setEventCallback( conn.setEventCallback(
[&conn, &channel, &jsonWriter, [&conn, &channel, &filter, &jsonWriter,
verbose, &receivedCount, &sentCount, verbose, &receivedCount, &sentCount,
&condition, &conditionVariableMutex, &condition, &conditionVariableMutex,
&progressCondition, &queue] &progressCondition, &queue]
@ -119,7 +120,7 @@ namespace ix
else if (eventType == ix::CobraConnection_EventType_Authenticated) else if (eventType == ix::CobraConnection_EventType_Authenticated)
{ {
std::cerr << "Subscriber authenticated" << std::endl; std::cerr << "Subscriber authenticated" << std::endl;
conn.subscribe(channel, conn.subscribe(channel, filter,
[&jsonWriter, verbose, [&jsonWriter, verbose,
&sentCount, &receivedCount, &sentCount, &receivedCount,
&condition, &conditionVariableMutex, &condition, &conditionVariableMutex,

View File

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

View File

@ -18,6 +18,7 @@ namespace ix
{ {
public: public:
WebSocketConnect(const std::string& _url, WebSocketConnect(const std::string& _url,
const std::string& headers,
bool disableAutomaticReconnection, bool disableAutomaticReconnection,
bool disablePerMessageDeflate, bool disablePerMessageDeflate,
bool binaryMode); bool binaryMode);
@ -30,14 +31,17 @@ namespace ix
private: private:
std::string _url; std::string _url;
WebSocketHttpHeaders _headers;
ix::WebSocket _webSocket; ix::WebSocket _webSocket;
bool _disablePerMessageDeflate; bool _disablePerMessageDeflate;
bool _binaryMode; bool _binaryMode;
void log(const std::string& msg); void log(const std::string& msg);
WebSocketHttpHeaders parseHeaders(const std::string& data);
}; };
WebSocketConnect::WebSocketConnect(const std::string& url, WebSocketConnect::WebSocketConnect(const std::string& url,
const std::string& headers,
bool disableAutomaticReconnection, bool disableAutomaticReconnection,
bool disablePerMessageDeflate, bool disablePerMessageDeflate,
bool binaryMode) : bool binaryMode) :
@ -49,6 +53,8 @@ namespace ix
{ {
_webSocket.disableAutomaticReconnection(); _webSocket.disableAutomaticReconnection();
} }
_headers = parseHeaders(headers);
} }
void WebSocketConnect::log(const std::string& msg) void WebSocketConnect::log(const std::string& msg)
@ -56,6 +62,31 @@ namespace ix
std::cout << msg << std::endl; 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() void WebSocketConnect::stop()
{ {
_webSocket.stop(); _webSocket.stop();
@ -64,6 +95,7 @@ namespace ix
void WebSocketConnect::start() void WebSocketConnect::start()
{ {
_webSocket.setUrl(_url); _webSocket.setUrl(_url);
_webSocket.setExtraHeaders(_headers);
if (_disablePerMessageDeflate) if (_disablePerMessageDeflate)
{ {
@ -151,12 +183,14 @@ namespace ix
} }
int ws_connect_main(const std::string& url, int ws_connect_main(const std::string& url,
const std::string& headers,
bool disableAutomaticReconnection, bool disableAutomaticReconnection,
bool disablePerMessageDeflate, bool disablePerMessageDeflate,
bool binaryMode) bool binaryMode)
{ {
std::cout << "Type Ctrl-D to exit prompt..." << std::endl; std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketConnect webSocketChat(url, WebSocketConnect webSocketChat(url,
headers,
disableAutomaticReconnection, disableAutomaticReconnection,
disablePerMessageDeflate, disablePerMessageDeflate,
binaryMode); binaryMode);

View File

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