Compare commits

...

471 Commits

Author SHA1 Message Date
Benjamin Sergeant
9884c325dd makefile: remove some install targets 2024-05-17 02:02:58 -07:00
teejusb
c27f5a94bd
Version check mbedtls instead of introducing a new define (#516) 2024-05-08 11:02:00 -07:00
Sergey Fedorov
2d47af89cf
IXSocket.h: add missing <sys/types.h> for macOS (#512) 2024-05-08 07:33:37 -07:00
Sergey Fedorov
c106e6cb24
Minor fixes for < 10.6 (#515)
* Fix for missing AI_NUMERICSERV on < 10.6

* Do not use pthread_setname_np on < 10.6
2024-05-08 07:33:23 -07:00
teejusb
1d210c0139
Initialize the PSA Crypto API if requested (#514) 2024-04-29 21:12:56 -07:00
Benjamin Sergeant
dc8807ec9d changelog 2024-03-27 22:12:37 -07:00
Benjamin Sergeant
03e5a6970f bump version in CMakeLists.txt 2024-03-27 22:11:15 -07:00
Olivia (Zoe)
38d6da7755
Fix bad version variable (#510) 2024-03-27 22:06:44 -07:00
Benjamin Sergeant
9ef61bf224 bump version 2024-03-27 22:06:32 -07:00
Benjamin Sergeant
93e673da9f Merge branch 'master' of github.com:machinezone/IXWebSocket 2024-03-27 22:03:54 -07:00
Benjamin Sergeant
92beef8348 avoid some object copies 2024-03-27 22:03:26 -07:00
Giuseppe Penone
755d98d918
Support URLs with no slash before the question mark (#507)
* Support Url No Slash Before Question Mark

* Support Url No Slash Before Question Mark

* unit test fix

---------

Co-authored-by: Giuseppe Penone <giuseppe.penone@delonghigroup.com>
2024-03-18 22:26:45 -07:00
CryptoManiac
98b4828e93
Update IXSelectInterruptPipe.cpp (#502)
Valgrind keeps complaining that close() on the invalid descriptor -1 is happening here. I suppose that close shouldn't be called when the descriptor value is known to be invalid. It's not a fatal error or something, but this practice is able to create a lot of flood in the logs.
2024-03-18 22:24:11 -07:00
Paul Whiting
39e085bebc
Fix MbedTLS disconnect handling. (#500) 2024-03-18 22:23:14 -07:00
Daniel Wymark
70602c4e6b
Add ping interval to constructor params for WebSocketServer (#497)
* Update .gitignore for CLion compatibility

* Add pingIntervalSeconds to constructor for WebSocketServer

* Add Heartbeat section to WebSocketServer usage documentation

* Fix typo
2024-03-12 09:46:27 -07:00
bsergean
c5a02f1066
Update README.md 2024-02-10 22:03:22 -08:00
bsergean
e03c0be8a4
Update unittest_windows_gcc.yml (#494)
* Update unittest_windows_gcc.yml

* Update unittest_windows_gcc.yml
2023-11-20 22:28:12 -08:00
RH
3b66efbb6a
Fix C++/WinRT compile issue (#493) 2023-11-15 10:40:49 -08:00
arenevier
f29906c72f
Allow building without rtti (#487)
Since factory returns a ProxyConnectionState, setOnConnectionCallback
will be a ProxyConnectionState. The code already makes that assumption,
since it does not check of state return value. Using a
static_pointer_cast will allow the library to be build with rtti.
2023-10-13 19:56:24 -07:00
arenevier
872f516ede
allow building when cpp exceptions are disabled (#489)
IXWebSocket needs exceptions support because of the use of stoi. In
order to build when cpp exceptions are disabled, we can use strtol.
2023-10-13 19:55:26 -07:00
Benjamin Sergeant
014d43eb13 stop building mingw ; if someone wants to maintain this port please reach out ! 2023-10-13 19:54:25 -07:00
bsergean
d77067e50f
Update window gcc action to latest setup_mingw 2023-10-13 19:39:41 -07:00
Michael M
ed5b1a0895
Fix links & update info in README (#485) 2023-09-15 22:36:52 -07:00
David Capello
ef57e3a2b1
Fix hanging of WebSocket::stop() waiting for its thread to join (#481)
This might happen in some special cases where
WebSocketTransport::sendOnSocket() fails, closes the socket, and set
the ready state to CLOSED, but WebSocket::run() stills wait the
interrupt event to happen.

Possibly related to #474
2023-09-01 08:11:36 -07:00
Glenn Engel
28832f8732
Fix #286 - http response headers overwritten with request headers (#483) 2023-09-01 08:11:07 -07:00
lanthora
0dd284267a
Fix MinGW build warning (#482) 2023-09-01 08:00:35 -07:00
lanthora
a7019631b7
Fix server empty thread name (#478) 2023-08-01 22:16:43 -07:00
Cheney Wang
632ee31509
Update IXSocketMbedTLS.cpp (#471) 2023-06-22 14:12:51 -07:00
Benjamin Sergeant
688af99747 bump version 2023-06-05 10:06:53 -07:00
lanthora
397bb5d18a
Fix version in CMakeLists.txt (#467)
* Fix version in CMakeLists.txt

* Disable IXHttpClientTest
2023-06-05 10:03:17 -07:00
lanthora
f79c64ae97
Add a reference to Candy in the README (#462) 2023-05-04 00:03:28 -07:00
lanthora
bc765e73a3
Add pkgconfig file (#459) 2023-04-25 11:54:25 -07:00
bsergean
dfa10df5ae
Set an origin header in websocket and http clients (#455) 2023-04-01 12:19:38 -07:00
Benjamin Sergeant
eb9a7bed76 fix warning about member variable initialization order + minor makefile build fix 2023-04-01 09:03:39 -07:00
Numendacil
d20864d7d1
Replace CMAKE_BINARY_DIR with CMAKE_CURRENT_BINARY_DIR (#454) 2023-03-29 20:59:39 -07:00
ouwou
f184a7adef
fix incorrect closures with code 1011 Internal error (#450)
* fix incorrect closures with code 1011 Internal error

* enable IXWebSocketCloseTest
2023-03-29 20:45:29 -07:00
Thefrank
dc7b986e10
Build fix for FreeBSD (#449) 2023-03-13 09:36:25 -07:00
itytophile
1e3560014f
Prevent deadlock when server is stopping (#426) 2023-02-25 14:41:05 -08:00
Azamat H. Hackimov
9157873f5b
Fix compilation on GCC-13 (#443)
* Fix compilation on GCC-13

GCC-13 changes internal cstdint includes, and now files that uses
standart integer types should directly include cstdint header.

See: https://gcc.gnu.org/gcc-13/porting_to.html#header-dep-changes
Bug: https://bugs.gentoo.org/865117
Bug: https://bugs.gentoo.org/895440

* Convert line endings to Unix format
2023-02-25 13:50:35 -08:00
YuHuanTin
aa2ca19895
Added support for setting custom ping messages with support for any 'ping' message type (binary ping message, text ping message) (#438)
* Added support for setting custom ping messages with support for any 'ping' message type (binary ping message, text ping message)

* Add default value

---------

Co-authored-by: YuHuanTin <2@>
2023-02-23 08:29:07 -08:00
Vol-Alex
6cc21f3658
HTTP should contain port in 'Host' header (#427) 2023-02-11 15:27:40 -08:00
itytophile
679ce519dd
Fix DNSLookup memory leak (#422)
* Fix memory leak with shared_ptr and -fsanitize=address

* Replace addrinfo* by shared_ptr

* fsanitize=address only on Linux

* Add USE_WS Linux CI test

* Remove fsanitize from the cmake files

* Remove USE_WS in linux test suite
2022-12-22 17:13:51 -08:00
crjc
a5d4911a16
Fix macos link error (#423)
* Fix macos link error

* Update CMakeLists.txt

* Update CMakeLists.txt
2022-12-22 09:16:10 -08:00
TheArtfulBodger
b0fd119d14
Host HTTP and WS on the same port (#414)
* Quick hack to host HTTP and WS on the same port #373

* Quick hack to host HTTP and WS on the same port #373 - simplify code

* ran clang-format

Co-authored-by: En Shih <seanstone5923@gmail.com>
Co-authored-by: The Artful Bodger <TheArtfulBodger@users.noreply.github.com>
2022-11-05 18:53:11 -07:00
李浩能
472cf68c31
add Content-Type support (#405) 2022-10-12 06:43:05 -07:00
Robin Sommer
1e46466114
Add option to disable hostname check (#399)
* Suppress compiler warnings about unused elements.

* Enable CMake's compilation database.

* Add TLS option to disable checking a certificate's host name.

* Add `--disable-hostname-validation` to `ws`.

* Add test for disabling hostname validation.
2022-10-12 06:41:32 -07:00
bsergean
0b8b5608dc
Update doc to talk about binding to 0.0.0.0 2022-09-08 14:58:19 -07:00
Seizure Salad
20a028e2ae
Fix spelling mistake (#401) 2022-08-02 13:28:59 -07:00
Benjamin Sergeant
8d7b557be6
Delete stale.yml
Remove the stale github action.
2022-06-30 10:41:08 +02:00
Benjamin Sergeant
e417e63605
Update CHANGELOG.md 2022-05-13 10:45:46 -07:00
Benjamin Sergeant
7b1524d7ec
Update IXWebSocketVersion.h 2022-05-13 10:43:32 -07:00
Max Weisel
e8048ad826
BoringSSL does not allow setting the hostname with a null-terminated string. The length is always required: https://boringssl.googlesource.com/boringssl/+/master/crypto/x509/x509_vpm.c#93 (#391) 2022-05-05 08:11:18 -07:00
Benjamin Sergeant
2b40a30c8f
Update README.md 2022-05-02 09:34:43 -07:00
Benjamin Sergeant
d7bfe89e43
Set shorter thread names (#379) 2022-04-30 10:18:20 -07:00
Benjamin Sergeant
84aa652846
Set shorter thread names (#379) 2022-04-30 10:16:53 -07:00
Robin Sommer
edb6ded99f
Fix Sec-WebSocket-Key to contain valid Base64. (#389)
The generated header only "looked like" Base64, but if the other side
actually tried to decode it as such, it could fail. This change fixes
that to always generate a valid Base64 value.

The Base64 code is copied from
https://gist.github.com/tomykaira/f0fd86b6c73063283afe550bc5d77594.
2022-04-29 00:05:06 -07:00
Benjamin Sergeant
2f560ff4c0
Update IXWebSocketVersion.h 2022-04-28 23:56:40 -07:00
Benjamin Sergeant
002d9c8985
Update ixwebsocket-config.cmake.in (#390) 2022-04-28 23:56:00 -07:00
Benjamin Sergeant
6d8495bd73
Update CHANGELOG.md 2022-04-23 22:53:36 -07:00
Benjamin Sergeant
b8563eddd1
11.4.1 2022-04-23 22:52:32 -07:00
Cheney Wang
46bd2aa4a1
vcpkg zlib dep fix (#385)
* vcpkg zlib dep fix

* Use cmake.in file instead of write file directly

Co-authored-by: Cheney-Wang <v-xincwa@microsoft.com>
2022-04-23 18:16:13 -07:00
Benjamin Sergeant
4420bc70b5
Revert "Export static symbols when building ws with shared library (#370)" (#383)
This reverts commit a3d2fa4b7ee65a40cdabbe6258db75c3866d5908.
2022-04-12 08:55:43 -07:00
Benjamin Sergeant
20921f341a
Update README.md 2022-03-28 22:04:27 -07:00
Benjamin Sergeant
2829c62ef9
Fix error handling after calling X509_NAME_get_index_by_NID
This should fix #376
2022-03-27 19:14:40 -07:00
Anton Ivlev
a3d2fa4b7e
Export static symbols when building ws with shared library (#370) 2022-03-19 11:41:40 -07:00
Benjamin Sergeant
f7eb3688dd
Update IXExponentialBackoffTest.cpp 2022-02-17 09:17:47 -08:00
Benjamin Sergeant
7360333aca
Handle overflow in IXExponentialBackoff.cpp 2022-02-17 09:17:02 -08:00
Benjamin Sergeant
90f19e0280
Reference new IXExponentialBackoffTest test in CMakeLists.txt 2022-02-17 09:08:49 -08:00
Benjamin Sergeant
b72f81540b
Create IXExponentialBackoffTest.cpp 2022-02-17 09:07:46 -08:00
Benjamin Sergeant
a77fd2d698
Update catch to v2.13.8 (#365) 2022-02-17 08:38:46 -08:00
flagarde
e12a6ddbdd
Update IXSocketConnect.cpp (#356)
Part of the code is never executed
warning raised by clang
2022-02-10 20:50:55 -08:00
flagarde
127cc4a023
Fix for MINGW32 and clang on windows (#352) (#357)
* Update IXSocket.h

Avoid "conflicting declaration 'typedef SSIZE_T ssize_t'"

* Update IXUdpSocket.h

* Update IXNetSystem.cpp

ENOSPC and EAFNOSUPPORT are not defined for clang on windows
2022-02-10 20:49:33 -08:00
Benjamin Sergeant
7711cb1ae7
Feature/no libdeflate (#360)
remove libdeflate code in gzip codec
2022-02-10 20:47:32 -08:00
Martin Natano
db7057de69
Add support for streaming transfers (#353)
This change adds onChunkCallback to the request. If defined it will be
called repeatedly with the incoming data. This allows to process data on
the go or write it to disk instead of accumulating the data in memory.
2022-01-31 21:54:32 -08:00
Benjamin Sergeant
c28b569535
Edit doc about thread safety, fix #350 2022-01-28 16:27:45 -08:00
flagarde
8d661b8e81
Use Threads::Threads target (#349) 2022-01-16 17:39:21 -08:00
flagarde
a951bc9cae
Add an alias for ixwebsocket (#348) 2022-01-16 17:36:38 -08:00
Andreas Hausladen
b5cf33a582
Introduction of IXWebSocketSendData (#347)
* Introduction of IXWebSocketSendData that makes it possible to not only send std::string but also std::vector<char/uint8_t> and char* without copying them to a std::string first.

Add a sendUtf8Text() method that doesn't check for invalid UTF-8 characters. The caller must guarantee that the string only contains valid UTF-8 characters.

* Updated usage.md: sendUtf8Text() and IXWebSocketSendData
2022-01-10 10:34:24 -08:00
Andreas Hausladen
2bc3afcf6c
Fixed missing header for gcc (9.3.0) compilation (#346) 2022-01-06 19:27:00 -08:00
Benjamin Sergeant
60563d88f2
Feature/11.4.0 (#344)
* mbedls system certs

* missing curly brace ...

* windows uwp for appveyor

* try again uwp

* update version and changelog

* revert odd change in test/IXSocketTest.cpp

Co-authored-by: Benjamin Sergeant <bsergeant@mz.com>
2022-01-05 10:43:18 -08:00
Andreas Hausladen
1f2895a469
Win wsa select event (#342)
* Fix #323: Missing SelectInterrupt implementation for Windows

Using WSAEventSelect, WSAWaitForMultipleEvents and WSAEnumNetworkEvents to emulate poll() with an interrupt-event.

* Cleanup

* Fixed incomplete comment.

* Switched ifdefs to support other Unixes with pipe file descriptors

* Fixed: SelectInterrupt fallback code for getFd()==-1 && getEvent()==nullptr converted a PollResultType::Timeout into a ReadyForRead causing the HttpClient to fail because it uses a hard-coded "SelectInterrupt" instance that doesn't implement getFd() and getEvent().

* Fixed gcc compile errors

* - HttpClient now uses the SelectInterruptFactory
- Fixed wrong ix::poll result when using Windows WSA functions

* We must deselect the networkevents from the socket event. Otherwise the socket will report states that aren't there.
2022-01-05 10:21:33 -08:00
Andreas Hausladen
9f00428d57
Fix "HTTP/1.1 400 Illegal character CNTL=0xf" caused by serverMaxWindowBits/clientMaxWindowBits being uint8_t (signed char). (#341) 2022-01-04 12:25:18 -08:00
CryptoManiac
f53b2f8878
Export symbols into .def files on MSVC (#339)
Fix #335
2022-01-04 12:13:38 -08:00
CryptoManiac
47d0b70ebf
Include <cerrno> to provide standard error constants (#338)
See https://en.cppreference.com/w/cpp/header/cerrno for additional details. Some of used constants are defined in this header.

Inclusion is necessary to avoid these errors:

```
/home/user/IXWebSocket/ixwebsocket/IXNetSystem.cpp:189:30: error: use of undeclared identifier 'EAFNOSUPPORT'
            default: errno = EAFNOSUPPORT; return 0;
                             ^
/home/user/IXWebSocket/ixwebsocket/IXNetSystem.cpp:191:17: error: use of undeclared identifier 'ENOSPC'
        errno = ENOSPC;
                ^
/home/user/IXWebSocket/ixwebsocket/IXNetSystem.cpp:175:25: warning: implicit conversion loses integer precision: 'size_t' (aka 'unsigned long long') to 'int' [-Wshorten-64-to-32]
                    j = strspn(buf + i, ":0");
                      ~ ^~~~~~~~~~~~~~~~~~~~~
/home/user/IXWebSocket/ixwebsocket/IXNetSystem.cpp:234:21: error: use of undeclared identifier 'EAFNOSUPPORT'
            errno = EAFNOSUPPORT;
                    ^
2 warnings and 3 errors generated.
```
2022-01-04 12:13:19 -08:00
Benjamin Sergeant
8c15405ed0
Add a reference to NovaCoin in the README 2021-12-22 22:52:53 -08:00
svost
5457217503
Improved compatibility - fix mingw crossbuild (#337) 2021-12-22 22:48:20 -08:00
Martin Natano
66cd29e747
Allow to cancel asynchronous HTTP requests (#332)
Usage:

	auto args = this->httpClient.createRequest(url, method);
	httpClient.performRequest(args, ...);
	[...]
	// Oops, we don't actually want to complete the request!
	args->cancel = true;
2021-12-20 23:01:55 -08:00
Matthew Gordon
688f85fda6
Fix errors in example code. (#336) 2021-12-20 22:59:15 -08:00
Benjamin Sergeant
71f73e5f6e
Update README (add another project using the lib) 2021-12-02 22:49:12 -08:00
Benjamin Sergeant
42db05a38b
Update mkdocs.yml 2021-11-24 09:05:54 -08:00
Benjamin Sergeant
6cce066021
Update index.md 2021-11-24 09:02:42 -08:00
Benjamin Sergeant
9c6dcb24a9
Update mkdocs.yml 2021-11-24 09:01:10 -08:00
Benjamin Sergeant
05a27c89e3
Update design.md 2021-11-24 09:00:23 -08:00
Benjamin Sergeant
23dbc8ae63
Update mkdocs.yml
Try a new version of the checkout action.
2021-11-24 08:56:14 -08:00
Benjamin Sergeant
5f2955ef78
Feature/version 11.3.2 (#329)
* mbedls system certs

* missing curly brace ...

* windows uwp for appveyor

* try again uwp

* bump version

* keep using local cacert.pem in unittest

* appveyor back to normal

* remove appveyor file

Co-authored-by: Benjamin Sergeant <bsergeant@mz.com>
2021-11-24 08:45:04 -08:00
flagarde
882081536c
Fix IXWebSocketMessage.h:35:15: warning: ‘webSocketMessageType’ may be used uninitialized in this function [-Wmaybe-uninitialized] (#328) 2021-11-24 08:33:09 -08:00
flagarde
74bb85efe9
Add getters (#327)
* Add getters for IXSocketServer class

* Add getters for IXHttpServer class

* Add getters for IXWebSocketServer class
2021-11-24 08:28:25 -08:00
ouwou
e66437b560
fix compilation under mingw64 (#325) 2021-11-16 15:22:51 -08:00
Benjamin Sergeant
97aa1f956a
Fix mbedtls-3.0 problem (#322)
* Fix mbedtls-3.0 problem

This cause CI to fail on macOS.

See this migration guide => https://github.com/ARMmbed/mbedtls/blob/development/docs/3.0-migration-guide.md

* cmake change find header file

* define macro for mbedtls >= 3

* update api call

* Update IXWebSocketVersion.h

* Update CHANGELOG.md
2021-10-22 11:10:58 -07:00
Enzo
3f1fc6906c
Correctly convert remote port bytecode to uint16 port number. (#321)
* Correctly convert remote port bytecode to uint16 port number.

Copied the network_to_host_short function from the ASIO library to convert the remote port byte code to a uint16 number.

* Switched from uint16_t to unsigned short to work in Windows

* Updated missed uint16_t to unsigned short
2021-10-22 10:23:43 -07:00
Benjamin Sergeant
9bbd1f1b30
Update package-lock.json
Fix a security issue with the node.js ws package, which is only used in testing.
2021-09-29 12:18:15 -07:00
Benjamin Sergeant
18c2b69633
Update build.md 2021-09-29 12:15:31 -07:00
Benjamin Sergeant
178f218374
Update IXWebSocketVersion.h 2021-09-20 18:19:29 -07:00
Benjamin Sergeant
6a1aa27b5f
Update CHANGELOG.md 2021-09-20 18:19:10 -07:00
Matthew Albrecht
e7f89ae529
Only find OpenSSL, MbedTLS, zlib if they have not already been found, make CMake install optional. (#307)
Co-authored-by: Benjamin Sergeant <bsergean@gmail.com>
2021-09-20 18:15:37 -07:00
Adrian Schollmeyer
cdeaf8e2be
use GNUInstallDirs in cmake (#318)
Signed-off-by: NexAdn <git@nexadn.de>

Co-authored-by: NexAdn <git@nexadn.de>
2021-09-20 18:11:46 -07:00
Benjamin Sergeant
dbafa0aa07
trigger github actions on pull requests (#313) 2021-08-13 05:07:24 -07:00
Benjamin Sergeant
3baf59a031 (ws) bump CLI command line parsing library from 1.8 to 2.0 2021-07-27 20:48:25 +02:00
Benjamin Sergeant
b5804c2082 cmake tweak 2021-06-09 10:46:31 -07:00
Benjamin Sergeant
30bcddb99f (ws) ws connect has a -g option to gzip decompress messages for API such as the websocket Huobi Global. 2021-06-08 09:49:27 -07:00
Benjamin Sergeant
47fd04e210 (websocket client + server) WebSocketMessage class tweak to fix unsafe patterns 2021-06-08 09:47:53 -07:00
Nikos Athanasiou
4f5b0c4f07
Noexcept ix web socket per message deflate options (#299)
* Fix unsafe calls and safeguard WebSocketMessage from being called w/
temporaries

* Use unnamed namespace to express internal linkage

* Avoid returning references that are mutex protected
Motivation for this MR

The antipattern of returning references to mutex protected members was
removed. Since a caller can hold the reference it would make all class
level locking meaningless.

Instead values are returned. The IXWebSocketPerMessageDeflateOptions
class was shrunk by 7 bytes (1 padding + 2*3) after changing the int
members to the used uint8_t; side effects of that were handled.

An inefficient "string -> int" was replaced by standard library. As
seen here http://coliru.stacked-crooked.com/a/46b5990bafb9c626 this
gives an order of magnitude better performance.

* noexcept string to integer conversion
2021-06-07 11:19:52 -07:00
Nikos Athanasiou
c2d497abc5
Avoid returning references that are mutex protected (#297)
* Fix unsafe calls and safeguard WebSocketMessage from being called w/
temporaries

* Use unnamed namespace to express internal linkage

* Avoid returning references that are mutex protected
Motivation for this MR

The antipattern of returning references to mutex protected members was
removed. Since a caller can hold the reference it would make all class
level locking meaningless.

Instead values are returned. The IXWebSocketPerMessageDeflateOptions
class was shrunk by 7 bytes (1 padding + 2*3) after changing the int
members to the used uint8_t; side effects of that were handled.

An inefficient "string -> int" was replaced by standard library. As
seen here http://coliru.stacked-crooked.com/a/46b5990bafb9c626 this
gives an order of magnitude better performance.
2021-06-05 11:23:18 -07:00
crjc
bbe2ae6dd3
fix: check the request's headers rather than the empty response's headers for User-Agent and Accept (#296) 2021-06-05 11:19:53 -07:00
Nikos Athanasiou
26897b2425
Fix unsafe calls and safeguard WebSocketMessage (#294)
* Fix unsafe calls and safeguard WebSocketMessage from being called w/
temporaries

* Use unnamed namespace to express internal linkage
2021-06-03 18:39:38 -07:00
Benjamin Sergeant
e3c98a03cc (websocket server) Handle and accept firefox browser special upgrade value (keep-alive, Upgrade) 2021-05-27 10:54:21 -07:00
Benjamin Sergeant
97fedf9482 (Windows) move EINVAL (re)definition from IXSocket.h to IXNetSystem.h (fix #289) 2021-05-27 10:54:21 -07:00
Benjamin Sergeant
ae187c0e98
Readme: Add mingw to build matrix 2021-05-18 11:15:21 -07:00
Benjamin Sergeant
0f21a20fe3 Move errno windows definitions to IXNetSystem.h 2021-05-17 19:04:02 -07:00
Benjamin Sergeant
54db6ec8bb add notes about ssl configuration in demo program 2021-05-09 13:45:01 -07:00
flagarde
0e0a748037
Remove warnings (#284) 2021-04-19 09:25:06 -07:00
Benjamin Sergeant
3b19b0eeca http client: DEL is not a verb, but DELETE is, fix #281 2021-04-04 23:27:28 -07:00
Bart
dbfe3104e8
Fixed example code for the new API of IXWebSocketServer (#279) 2021-03-26 23:55:34 -07:00
Benjamin Sergeant
68fd8c20d6 change CI mkdocs invocation 2021-03-25 11:12:59 -07:00
Benjamin Sergeant
d932af8568 (cmake) install IXUniquePtr.h 2021-03-25 10:55:59 -07:00
Benjamin Sergeant
3add6d4c2e (ssl + windows) missing include for CertOpenStore function 2021-03-24 08:03:56 -07:00
Benjamin Sergeant
0d7fb05567 (ixwebsocket) version bump 2021-03-23 21:54:54 -07:00
Benjamin Sergeant
bf1747ef18 (ixwebsocket) version bump 2021-03-23 21:54:15 -07:00
Benjamin Sergeant
5c9c05caff bump version 2021-03-23 21:52:49 -07:00
Benjamin Sergeant
2573ca151b CaseInsensitiveLess::NocaseCompare::operator mingw fix attempt 2021-03-23 21:21:36 -07:00
Benjamin Sergeant
c5b5fa82be use inet_* wrapper only on mingw 2021-03-23 21:13:18 -07:00
Benjamin Sergeant
80dff08304 invoke ctest manually on ci for windows + gcc builder 2021-03-23 21:07:26 -07:00
Benjamin Sergeant
24c2eae3d7 use inet_ntop and inet_pton musl implementations on all platforms 2021-03-23 20:53:19 -07:00
Benjamin Sergeant
449c5fa138 (ixwebsocket) add getMinWaitBetweenReconnectionRetries 2021-03-23 08:29:50 -07:00
Benjamin Sergeant
b6234ff908 compile errors due to missing changes for the introduction of setMinWaitBetweenReconnectionRetries and getMinWaitBetweenReconnectionRetries 2021-03-23 08:28:40 -07:00
Benjamin Sergeant
d26664fccc (ixwebsocket) New option to set the min wait between reconnection attempts. Still default to 1ms. (setMinWaitBetweenReconnectionRetries). 2021-03-23 07:33:48 -07:00
Benjamin Sergeant
def0243d6d (ws) initialize maxWaitBetweenReconnectionRetries to a non zero value ; a zero value was causing spurious reconnections attempts 2021-03-22 21:10:18 -07:00
Benjamin Sergeant
1410797d6f document -DBUILD_DEMO=ON 2021-03-22 08:51:58 -07:00
inull
2670187fe0
Adds demo building option. (#278) 2021-03-22 08:47:10 -07:00
Benjamin Sergeant
95359461d7 enable test on windows + gcc 2021-03-21 10:22:44 -07:00
Benjamin Sergeant
4d7b149649 mingw: cast fixes 2021-03-21 10:16:06 -07:00
Benjamin Sergeant
b29a37ce76 mingw: inet_ntop and inet_pton compilation fix, use correct parameter names 2021-03-21 09:50:15 -07:00
Benjamin Sergeant
9a4dfb40da mingw: add real implementation of inet_ntop and inet_pton taken from musl C library 2021-03-21 09:43:16 -07:00
Benjamin Sergeant
c4c344518d disable shared-libs on windows where test does not work yet 2021-03-20 10:18:10 -07:00
Benjamin Sergeant
d706a4a73e doc: document BUILD_SHARED_LIBS 2021-03-20 09:50:21 -07:00
Benjamin Sergeant
88970604e3 ixwebsocketserver::broadcast server to return a boolean to know whether the server could start/listen, and use that in ws 2021-03-19 11:52:41 -07:00
Benjamin Sergeant
7fee54464e WebSocketServer::listenAndStart: fix branch where we do not return an integer 2021-03-19 11:48:21 -07:00
Benjamin Sergeant
1c7634d075 ws: cannot use << with an std::vector 2021-03-19 11:43:29 -07:00
Benjamin Sergeant
99f9556aa9 ws + mingw: uses << operator to write file to disk in WebSocketReceiver::handleMessage 2021-03-19 11:39:14 -07:00
Benjamin Sergeant
39b2a3d6df ws curl -O mingw compile fix + detect when we cannot extract a filename from the url to save file to disk with -O option 2021-03-19 11:35:25 -07:00
Benjamin Sergeant
056b02a494 ws: WebSocketSender uses anonymous namespace load instead of its own method 2021-03-19 11:25:48 -07:00
Benjamin Sergeant
48166a9a72 mingw: fix compile errors with linenoise and fstream 2021-03-19 11:18:55 -07:00
Benjamin Sergeant
b36a2d1faa mingw compile fix / remove restrict in inet_* functions 2021-03-19 10:58:38 -07:00
Benjamin Sergeant
968cc5c1c4 reference wslay as alternative C websocket library 2021-03-19 08:05:01 -07:00
Benjamin Sergeant
0813eb1788 mention disablePerMessageDeflate in the doc 2021-03-16 09:56:08 -07:00
Benjamin Sergeant
cadb8336f2 add reference to Teleport which is using ixwebsockets 2021-03-16 09:10:36 -07:00
Benjamin Sergeant
7fd782f72f add WIN32_LEAN_AND_MEAN windows blip 2021-03-15 19:58:18 -07:00
Benjamin Sergeant
85bcdaaec3 stub inet_ntop and inet_pton function that mingw does not have 2021-03-14 14:25:40 -07:00
Benjamin Sergeant
461641f3d0 ci with unity build for windows + gcc 2021-03-14 13:23:16 -07:00
Benjamin Sergeant
2d65c27d11 rename windows+gcc unittest ci file 2021-03-14 13:18:09 -07:00
Benjamin Sergeant
6a7785d9d9 no set thread name on mingw 2021-03-13 19:02:20 -08:00
Benjamin Sergeant
78a670e0c8 more mingw quirks 2021-03-13 18:55:30 -08:00
Benjamin Sergeant
e63ac69ec6 mock poll struct and macro for mingw 2021-03-13 18:49:29 -08:00
Benjamin Sergeant
afa15d6dcf mingw build problem fix attempt 2021-03-13 18:31:42 -08:00
Benjamin Sergeant
432a202c07 use https://github.com/marketplace/actions/install-mingw 2021-03-13 18:21:46 -08:00
Benjamin Sergeant
d609370a85 change compiler 2021-03-13 18:16:21 -08:00
Benjamin Sergeant
bbe3a766f4 ci windows_gcc, disable zlib 2021-03-13 18:04:00 -08:00
Benjamin Sergeant
09d3520b66
Create windows_gcc.yml 2021-03-13 18:00:32 -08:00
Benjamin Sergeant
f090c7659b (ixwebsocket) Expose setHandshakeTimeout method 2021-03-07 19:29:28 -08:00
Benjamin Sergeant
7c195219cd reorder methods in IXWebSocket.h 2021-03-07 19:25:53 -08:00
Duncan Ogilvie
d739662a7c
Allow customizing the websocket handshake timeout (#264) 2021-03-07 19:23:43 -08:00
Laurent Amat
e7f7e470e2
Case sensitive link (#269) 2021-03-04 23:04:04 -08:00
Benjamin Sergeant
d239738ec6 add a.out to .gitignore 2021-02-19 13:51:10 -08:00
Benjamin Sergeant
c61975bf75 minor improvement to the main.cpp builtin example 2021-02-19 13:50:50 -08:00
Benjamin Sergeant
39cc0ed32f add comment in WebSocketServer::makeBroadcastServer 2021-01-28 21:04:18 -08:00
Benjamin Sergeant
22c3a7264e ws: document bug in ws dnslookup command 2021-01-21 15:07:47 -08:00
Benjamin Sergeant
ee5a2eb46e mention C++11 compatibility in the readme 2021-01-03 11:48:10 -08:00
Benjamin Sergeant
f6e34e4b34 stop using C++14 lambda capture init, code should be C++11 compatible 2021-01-03 11:44:05 -08:00
Benjamin Sergeant
d0359a1764 new makeBroadcastServer websocket server method for classic servers, used by ws 2021-01-03 11:24:12 -08:00
Benjamin Sergeant
8910ebcc3c enable some unittests on windows 2020-12-26 12:44:06 -08:00
Benjamin Sergeant
1ea3bc3666 no unity build on Windows because of a problem with spdlog 2020-12-25 17:31:30 -08:00
Benjamin Sergeant
fe92ad205d build with unity builds 2020-12-25 17:16:36 -08:00
Benjamin Sergeant
e4a1ac80c2 more stale references to ixcore 2020-12-25 16:32:52 -08:00
Benjamin Sergeant
e9dc7f7aed case sensitive file name 2020-12-25 16:29:47 -08:00
Benjamin Sergeant
cd82eed4ec simple cmake build error 2020-12-25 16:28:15 -08:00
Benjamin Sergeant
fabc07d598 (ws) trim ws dependencies no more ixcrypto and ixcore deps 2020-12-25 16:25:58 -08:00
Benjamin Sergeant
b89621fa78 remove ixbots / ixsnake / ixcobra / ixredis (which should go in their own standalone project 2020-12-25 15:32:15 -08:00
Benjamin Sergeant
049d1eec63 remove some un-needed third party code 2020-12-25 15:28:39 -08:00
Benjamin Sergeant
6122154f74 test only depend on ixcore and ixcrypto 2020-12-25 15:27:11 -08:00
Benjamin Sergeant
0b7919834a (ws) trim ws dependencies, only depends on ixcrypto and ixcore 2020-12-25 15:17:46 -08:00
Benjamin Sergeant
6035dd4c11 fix ci 2020-12-22 21:45:26 -08:00
Benjamin Sergeant
1d0432c8c5 (build) rename makefile to makefile.dev to ease cmake BuildExternal (fix #261) 2020-12-22 21:42:39 -08:00
Benjamin Sergeant
461a645704 (ws) Implement simple header based websocket authorization technique to reject 2020-12-17 22:42:14 -08:00
Benjamin Sergeant
93ad709dfd fix ws curl error message + some Windows warnings 2020-12-12 11:01:22 -08:00
Benjamin Sergeant
2fac4bd9ef s/autobahn/autoroute 2020-11-25 10:00:35 -08:00
Benjamin Sergeant
f566fb457b update readme 2020-11-25 09:59:27 -08:00
Benjamin Sergeant
75e9c84388 fix buggy message and remove un-needed include 2020-11-19 14:27:10 -08:00
Benjamin Sergeant
223cd41b3c (ixwebsocket) Handle EINTR return code in ix::poll and IXSelectInterrupt 2020-11-16 13:53:09 -08:00
Benjamin Sergeant
60aeaec734 hand EINTR in IXSelectInterruptPipe::notify and IXSelectInterruptPipe::read 2020-11-16 13:52:13 -08:00
Benjamin Sergeant
fcf114e2b2 Handle EINTR in ix::poll on Unix 2020-11-16 10:14:59 -08:00
Benjamin Sergeant
ea32c0e1ec fix ixsentry detection of std::regex 2020-11-16 09:19:08 -08:00
Benjamin Sergeant
866670a906 (ixwebsocket) Fix #252 / regression in 11.0.2 with string comparisons 2020-11-16 08:41:08 -08:00
Benjamin Sergeant
80432edbd0 add 2 new ubuntu docker files 2020-11-15 21:26:59 -08:00
Benjamin Sergeant
23606b45c7 C++11 compatible 2020-11-15 21:09:58 -08:00
Benjamin Sergeant
2aac0afca3 compile attempt 2 with old OpenSSL versions 2020-11-15 11:32:50 -08:00
Benjamin Sergeant
508d8c7253 compile attempt with old OpenSSL versions 2020-11-15 11:23:44 -08:00
Benjamin Sergeant
8f5134528b (ixwebsocket) use a C++11 compatible make_unique shim 2020-11-15 09:56:54 -08:00
Benjamin Sergeant
738c6040f7 fix memory leak in dns unittest 2020-11-12 13:07:31 -08:00
Benjamin Sergeant
1350e9b307 missing vector header 2020-11-11 21:47:07 -08:00
Benjamin Sergeant
4e2a40e031 (socket) replace a std::vector with an std::array used as a tmp buffer in Socket::readBytes 2020-11-11 21:39:31 -08:00
Benjamin Sergeant
594d2e194a linux asan / run test in verbose mode 2020-11-11 11:32:47 -08:00
Benjamin Sergeant
977a1ed7e1 link ordering fix for Linux 2020-11-11 19:23:51 +00:00
Benjamin Sergeant
8b3789af56 linux build fix attempt 2020-11-11 11:16:19 -08:00
Benjamin Sergeant
f60485d9c2 use ctest for testing 2020-11-11 11:11:34 -08:00
Benjamin Sergeant
b05b124cb3 update readme 2020-11-11 09:20:42 -08:00
Benjamin Sergeant
723c208f22 fix version 2020-11-11 09:18:03 -08:00
Benjamin Sergeant
21758f1183 (openssl security fix) in the client to server connection, peer verification is not done in all cases. See https://github.com/machinezone/IXWebSocket/pull/250 2020-11-11 09:16:14 -08:00
jb-gcx
422febf15d
(openssl) Always set verify peer when it is not disabled (#250) 2020-11-11 09:12:39 -08:00
Benjamin Sergeant
51ec32405d (docker) build docker container with zlib disabled 2020-11-07 11:22:52 -08:00
Benjamin Sergeant
6a90dc7259 (cmake) DEFLATE -> Deflate in CMake to stop warnings about casing 2020-11-07 09:40:54 -08:00
Benjamin Sergeant
262f32857f (ws autoroute) Display result in compliant way (AUTOROUTE IXWebSocket :: N ms) so that result can be parsed easily 2020-11-07 09:34:54 -08:00
Benjamin Sergeant
91fb3992ac (ws gunzip + IXGZipCodec) Can decompress gziped data with libdeflate. ws gunzip computed output filename was incorrect (was the extension aka gz) instead of the file without the extension. Also check whether the output file is writeable. 2020-11-07 09:34:54 -08:00
Lucca Nunes
e8b12feaeb
Update README.md (#249) 2020-11-01 18:17:12 -08:00
Benjamin Sergeant
730fbc5b31 unity build fixes 2020-10-26 19:18:55 -07:00
Benjamin Sergeant
d0562664ad (http code) With zlib disabled, some code should not be reached 2020-10-19 13:37:42 -07:00
SeanOMik
d9b4beff8b
Fix an issue with disabling zlib and getting linker errors from the http client. (#247)
* (http client) #ifdefs so we dont try to compress http requests with zlib

* (http client) Remove some #ifdefs for including zlib and removing fields
2020-10-19 13:36:04 -07:00
Benjamin Sergeant
b2f21840c6 (ws curl) Add support for --data-binary option, to set the request body. When present the request will be sent with the POST verb 2020-10-12 14:03:01 -07:00
Benjamin Sergeant
67cb48537a (http client + server + ws) Add support for compressing http client requests with gzip. --compress_request argument is used in ws to enable this. The Content-Encoding is set to gzip, and decoded on the server side if present. 2020-10-09 17:51:56 -07:00
Benjamin Sergeant
fa0408e70b (http client + server + ws) Add support for uploading files with ws -F foo=@filename, new -D http server option to debug incoming client requests, internal api changed for http POST, PUT and PATCH to supply an HttpFormDataParameters 2020-10-08 12:43:18 -07:00
Benjamin Sergeant
032ed9af9c IXExponentialBackoff.cpp: fix typo in source code file name in the header block 2020-10-05 10:39:11 -07:00
Benjamin Sergeant
dc84080401 Add support for gzip compression through libdeflate 2020-09-30 14:34:03 -07:00
Benjamin Sergeant
82e759732b (cmake) Stop using FetchContent cmake module to retrieve jsoncpp third party dependency 2020-09-30 14:24:04 -07:00
Benjamin Sergeant
61dbcc2b84 fix docker and linux build 2020-09-28 11:56:49 -07:00
Benjamin Sergeant
e61680ff0f linux build fix about memset not being found 2020-09-28 11:01:59 -07:00
Benjamin Sergeant
6f188a5131 (ws) add gzip and gunzip ws sub commands 2020-09-28 10:19:27 -07:00
Benjamin Sergeant
6077f86af8 (cmake) use FetchContent cmake module to retrieve jsoncpp third party dependency 2020-09-26 14:11:40 -07:00
Benjamin Sergeant
93167e3917 cmake / move FetchContent spdlog to a single place 2020-09-26 13:55:03 -07:00
Benjamin Sergeant
2526a94454 (cmake) use FetchContent cmake module to retrieve spdlog third party dependency 2020-09-26 13:51:19 -07:00
Benjamin Sergeant
97cc543e53 (cobra connection) retrieve cobra server connection id from the cobra handshake message and display it in ws clients, metrics publisher and bots 2020-09-22 09:30:19 -07:00
Benjamin Sergeant
62d220f49a (cobra 2 cobra) specify as an HTTP header which channel we will republish to 2020-09-22 08:55:21 -07:00
Benjamin Sergeant
49995e32f0 (cobra bots) change an error log to a warning log when reconnecting because no messages were received for a minute 2020-09-18 15:25:10 -07:00
Benjamin Sergeant
d525c28907 (cobra connection and bots) set an HTTP header when connecting to help with debugging bots 2020-09-18 15:11:20 -07:00
carr-7
39c84c7d51
Rename HttpResponse's payload to body (#245)
* rename payload to body

* Fixed ws cmd line tool to use the renamed body

Co-authored-by: Jay <jasoncarr@Jasons-MacBook-Pro.local>
2020-09-12 19:01:37 -07:00
Benjamin Sergeant
128bc0afa9 (http server) read body request when the Content-Length is specified + set timeout to read the request to 30 seconds max by default, and make it configurable as a constructor parameter 2020-09-12 14:17:06 -07:00
Benjamin Sergeant
b04e5c5529 http server: use socket->readBytes which reads in bulk instead of N calls to socket->readByte 2020-09-12 14:09:25 -07:00
Benjamin Sergeant
1e8c421d66 formatting 2020-09-12 13:55:27 -07:00
carr-7
72d6651ded
Read body in parseRequest for HttpServer (#244)
Co-authored-by: Jay <jasoncarr@Jasons-MacBook-Pro.local>
2020-09-12 13:53:56 -07:00
Benjamin Sergeant
a4e5d1b47a (ws) autoroute command exit on its own once all messages have been received 2020-09-09 18:01:38 -07:00
Benjamin Sergeant
9f51a54a83 (docker) ws docker file installs strace 2020-09-04 13:47:12 -07:00
Benjamin Sergeant
b74f7319c6 add a note to the readme about the fact that the MinGW compiler is not supported. close #242 2020-09-03 13:50:46 -07:00
Benjamin Sergeant
0ad66a27f2 Fix ws/ws.cpp:2875:10: warning: unused variable noSend [-Wunused-variable] 2020-09-03 09:17:52 -07:00
Benjamin Sergeant
a40003e85a (ws) echo_client command renamed to autoroute. Command exit once the server close the connection. push_server commands exit once N messages have been sent. 2020-09-03 09:13:23 -07:00
Benjamin Sergeant
5534a7fdf9 add a github action to publish a docker container for ws 2020-09-02 11:52:59 -07:00
Benjamin Sergeant
efb245278d unittest / switch from using the REQUIRE macro, which halts (and usually crash) the test to the CHECK macro in IXWebSocketChatTest.cpp 2020-08-31 13:56:45 -07:00
Benjamin Sergeant
5896d3740f (ws + cobra bots) add a cobra_to_cobra ws subcommand to subscribe to a channel and republish received events to a different channel 2020-08-31 13:45:00 -07:00
Benjamin Sergeant
73b9c0b89b (socket servers) merge the ConnectionInfo class with the ConnectionState one, which simplify all the server apis 2020-08-28 14:55:40 -07:00
Benjamin Sergeant
629c155044 (ws) fix silly compile error (missing ix:: namespace) 2020-08-26 14:30:58 -07:00
Benjamin Sergeant
08640d877f (ws) set the main thread name, to help with debugging in XCode, gdb, lldb etc... 2020-08-26 13:38:45 -07:00
Benjamin Sergeant
ed5c63144e (ws) cobra to python bot / take a module python name as argument foo.bar.baz instead of a path foo/bar/baz.py 2020-08-19 10:00:00 -07:00
Benjamin Sergeant
ee69aed2b0 (ws) on Linux with mbedtls, when the system ca certs are specified (the default) pick up sensible OS supplied paths (tested with CentOS and Alpine) 2020-08-19 09:31:57 -07:00
Benjamin Sergeant
fcb92f862d (ws push_server) on the server side, stop sending and close the connection when the remote end has disconnected 2020-08-18 14:09:27 -07:00
Benjamin Sergeant
e8e98e667d add ruby websocket bencharking code using
faye-websocket-ruby to receive messages as fast as possible
2020-08-18 13:45:53 -07:00
Benjamin Sergeant
e1502017ce (ixwebsocket) replace std::unique_ptr<unsigned char[]> with std::array for some fixed arrays (which are in C++11) 2020-08-17 16:48:26 -07:00
Benjamin Sergeant
72472f2899 IXWebSocketPerMessageDeflateCodec: use std::array instead of std::unique_ptr for a fixed size array 2020-08-17 16:36:24 -07:00
Benjamin Sergeant
42f71364ca IXHttpClient.cpp: use std::array instead of std::unique_ptr for a fixed size array 2020-08-17 16:25:55 -07:00
Benjamin Sergeant
3dabd3a556 (ws) merge all ws_*.cpp files into a single one to speedup compilation 2020-08-15 19:30:17 -07:00
Benjamin Sergeant
0498e2fa98 IXBench.h is missing a pragma once 2020-08-15 18:58:46 -07:00
Benjamin Sergeant
2aaf59651e (socket server) in the loop accepting connections, call select without a timeout on unix to avoid busy looping, and only wake up when a new connection happens 2020-08-15 18:32:59 -07:00
Benjamin Sergeant
cd4e51eacf (socket server) instead of busy looping with a sleep, only wake up the GC thread when a new thread will have to be joined, (we know that thanks to the ConnectionState OnSetTerminated callback 2020-08-15 16:24:35 -07:00
Benjamin Sergeant
785842de03 (socket server) add a callback to the ConnectionState to be invoked when the connection is terminated. This will be used by the SocketServer in the future to know on time that the associated connection thread can be terminated. 2020-08-15 16:03:40 -07:00
Benjamin Sergeant
261095fa12 (socket server) do not create a select interrupt object everytime when polling for notifications while waiting for new connections, instead use a persistent one which is a member variable 2020-08-15 15:28:15 -07:00
Benjamin Sergeant
ed2ed0f7ae (ixwebsocket client) handle HTTP redirects 2020-08-14 18:13:34 -07:00
Benjamin Sergeant
7ad5ead0f6 document the --config_path option in usage.md 2020-08-14 15:15:27 -07:00
Benjamin Sergeant
a8284e64e3 add a simple shell script to test websocket proxy 2020-08-14 15:09:34 -07:00
Benjamin Sergeant
5423a31d5a (ws) have more subcommand handle --pidfile, to write pid to a file 2020-08-14 15:09:12 -07:00
Benjamin Sergeant
53575f8d90 change makefile openssl target to use ninja and install ws 2020-08-14 15:08:37 -07:00
Benjamin Sergeant
d3bcbdac26 (ws) upgrade to latest version of nlohmann json (3.9.1 from 3.2.0) 2020-08-13 22:10:38 -07:00
Benjamin Sergeant
8c5b28adce (websocket proxy server) add ability to map different hosts to different websocket servers, using a json config file 2020-08-13 21:20:42 -07:00
Benjamin Sergeant
dcbafae35a (ws) on macOS, with OpenSSL or MbedTLS, use /etc/ssl/cert.pem as the system certs 2020-08-12 18:55:13 -07:00
Benjamin Sergeant
eb197edcec ws --version does not get printed with a log prefix 2020-08-12 18:44:47 -07:00
Benjamin Sergeant
b8265bf7f2 (ws) -q option imply info log level, not warning log level 2020-08-11 15:44:06 -07:00
Benjamin Sergeant
e7c4f0b171 add documentation for the websocket send callback and the send return type (fix #239) 2020-08-11 11:24:00 -07:00
Benjamin Sergeant
12f36b61ff (websocket server) Handle programmer error when the server callback is not registered properly (fix #227) 2020-08-06 04:40:32 -07:00
Benjamin Sergeant
b15c4189f5 add csharp/dotnet devnull client to measure througput with different runtimes 2020-08-05 13:59:26 -07:00
Benjamin Sergeant
74d3278258 add python test file to benchmark how many messages can be received per second 2020-08-04 10:53:35 -07:00
Benjamin Sergeant
831152b906 add a devnull like sample code using libwebsockets C library, to see how many messages per second a client library can receive (answer is about the same as IXWebSocket) 2020-08-02 19:26:19 -07:00
Benjamin Sergeant
7c81a98632 Add a node.js benchmarking test program, to see how fast node can receive messages. 2020-08-02 14:21:11 -07:00
Benjamin Sergeant
6e47c62c06 (ws) Add a new ws sub-command, push_server. This command runs a server which sends many messages in a loop to a websocket client. We can receive above 200,000 messages per second (cf #235). 2020-08-02 12:41:34 -07:00
Benjamin Sergeant
bcae7f326d (ws) Add a new ws sub-command, echo_client. This command send a message to an echo server, and send back to a server whatever message it does receive. When connecting to a local ws echo_server, on my MacBook Pro 2015 I can send/receive around 30,000 messages per second. (cf #235) 2020-08-02 12:09:13 -07:00
Benjamin Sergeant
d719c41e31 (ws) ws echo_server. Add a -q option to only enable warning and error log levels. This is useful for bench-marking so that we do not print a lot of things on the console. (cf #235) 2020-08-02 11:53:21 -07:00
Benjamin Sergeant
6f0307fb35 (build) make using zlib optional, with the caveat that some http and websocket features are not available when zlib is absent 2020-07-31 22:54:57 -07:00
Benjamin Sergeant
2e3d625c1e (websocket client) onProgressCallback not called for short messages on a websocket (fix #233) 2020-07-29 17:47:33 -07:00
Benjamin Sergeant
029289413c ws test shell script / add option so tune how large sent file will be 2020-07-29 17:46:37 -07:00
Benjamin Sergeant
4d51098c86 (websocket client) heartbeat is not sent at the requested frequency (fix #232) 2020-07-29 11:24:42 -07:00
Benjamin Sergeant
c2b05af022 can compile on macOS against jsoncpp installed from homebrew 2020-07-28 22:00:29 -07:00
Benjamin Sergeant
e85f975ab0 compiler warning fixes 2020-07-28 21:46:26 -07:00
Benjamin Sergeant
dc77d62a5d (ixcobra) CobraConnection: unsubscribe from all subscriptions when disconnecting 2020-07-28 10:32:18 -07:00
Benjamin Sergeant
4f41f209a2 (socket utility) move ix::getFreePort to ixwebsocket library 2020-07-27 18:17:13 -07:00
Benjamin Sergeant
5940e53d77 enable cobra tests which were disabled 2020-07-27 17:39:53 -07:00
Benjamin Sergeant
22dffd5b7e WebSocket::close is re-entrant 2020-07-27 17:38:33 -07:00
Benjamin Sergeant
af2f31045d snake server / join subscription background thread in the ConnectionState destructor + attach cobra message subscription id to the connection state instead of having it be a local reference that gets unbound 2020-07-27 17:35:03 -07:00
Benjamin Sergeant
5daa59f9f3 minor makefile tweaks 2020-07-27 17:19:05 -07:00
Benjamin Sergeant
2ea9d06a93 fix typo in unittest string description: ununexpected -> unsubscribed 2020-07-27 17:16:53 -07:00
Benjamin Sergeant
847fc142d1 (ixwebsocket server) change legacy api with 2 nested callbacks, so that the first api takes a weak_ptr<WebSocket> as its first argument 2020-07-25 11:42:07 -07:00
Benjamin Sergeant
0388459bd0 (ixwebsocket) add WebSocketProxyServer, from ws. Still need to make the interface better. 2020-07-25 11:26:06 -07:00
Benjamin Sergeant
9a47ec1217 (ixsnake) uses an std::thread to handle redis subscriptions (2 unittest still failing) 2020-07-24 18:12:07 -07:00
Benjamin Sergeant
45a40c8640 new Dockerfile to run locally the test on an Ubuntu 20.04 system 2020-07-24 14:46:09 -07:00
Benjamin Sergeant
e34f1c30d6 (ws) port broadcast_server sub-command to the new server API 2020-07-24 14:35:07 -07:00
Benjamin Sergeant
c14a4c0e3e formatting 2020-07-24 13:04:14 -07:00
Benjamin Sergeant
b146e93a3a (unittest) port most unittests to the new server API 2020-07-24 12:49:36 -07:00
Benjamin Sergeant
9957ec9724 (ws) port ws snake to the new server API 2020-07-24 12:33:17 -07:00
Benjamin Sergeant
78a42f61bd add tool to ease making commits 2020-07-24 11:53:09 -07:00
Benjamin Sergeant
e78019dad6 (ws) port ws transfer to the new server API 2020-07-24 11:52:16 -07:00
Benjamin Sergeant
0f026c5da2 (websocket client) reset WebSocketTransport onClose callback in the WebSocket destructor 2020-07-24 10:03:29 -07:00
Benjamin Sergeant
c26a2d5d39 (websocket server) reset client websocket callback when the connection is closed 2020-07-24 09:41:02 -07:00
Benjamin Sergeant
2798886c0b (websocket server) add a new simpler API to handle client connections / that API does not trigger a memory leak while the previous one did 2020-07-23 19:29:41 -07:00
Benjamin Sergeant
ffde283a4b (build) merge platform specific files which were used to have different implementations for setting a thread name into a single file, to make it easier to include every source files and build the ixwebsocket library (fix #226) 2020-07-17 11:58:06 -07:00
Benjamin Sergeant
f7031d0d3e set thread name in only one file 2020-07-17 11:43:50 -07:00
Benjamin Sergeant
595e6c57df IXSelectInterruptPipe.h included in cmake on windows but compiled out 2020-07-17 11:33:02 -07:00
Benjamin Sergeant
87709c201e (socket server) bump default max connection count from 32 to 128 2020-07-10 17:11:11 -07:00
Benjamin Sergeant
e70d83ace1 (snake) implement super simple stream sql expression support in snake server 2020-07-10 16:10:59 -07:00
Benjamin Sergeant
ca829a3a98 implement very very simple stream sql support 2020-07-10 16:07:51 -07:00
Benjamin Sergeant
26a1e63626 snake: stream sql mock + add republished channel option 2020-07-10 15:06:55 -07:00
Benjamin Sergeant
c98959b895 comment out unittest which cannot be activated yet 2020-07-09 10:34:52 -07:00
marcelkauf
baf18648e9
Added test for websocket leak (#225)
* Added test for websocket leak

* Fixed test
2020-07-09 10:19:44 -07:00
Benjamin Sergeant
b21306376b uwp build fix + more ivp6 support 2020-07-08 12:38:55 -07:00
Benjamin Sergeant
fbd17685a1 (socket+websocket+http+redis+snake servers) expose the remote ip and remote port when a new connection is made (see #222) / only ipv4 is handled 2020-07-08 12:10:35 -07:00
Benjamin Sergeant
3a673575dd clang format 2020-07-08 10:39:46 -07:00
Benjamin Sergeant
d5e51840ab use const iterators 2020-07-08 10:34:14 -07:00
Benjamin Sergeant
543c2086b2 more templates in WebSocketTransport 2020-07-07 21:26:42 -07:00
Benjamin Sergeant
95eab59c08 WebSocketPerMessageDeflateCompressor can work with vector or std::string 2020-07-07 21:26:04 -07:00
Benjamin Sergeant
e9e768a288 better unittest for IXWebSocketPerMessageDeflateCompressor 2020-07-07 21:15:34 -07:00
Benjamin Sergeant
e2180a1f31 add unittest for IXWebSocketPerMessageDeflateCompressor 2020-07-07 20:56:38 -07:00
Benjamin Sergeant
7c1b57c8cd (cmake) change the way zlib and openssl are searched 2020-07-07 10:58:20 -07:00
Benjamin Sergeant
89e7a35a81 add cmake comment about using a custom zlib 2020-07-06 18:34:14 -07:00
Benjamin Sergeant
de6acfe54e Merge branch 'flagarde-patch-1' 2020-07-06 18:31:05 -07:00
flagarde
789e620451 Update CMakeLists.txt
just have to provide OPENSSL_ROOT_DIR to cmake
2020-07-06 18:30:44 -07:00
Benjamin Sergeant
4789e190a0 zlib needs to be found (with vcpkg in CI), do not use our bundled copy 2020-07-06 18:09:45 -07:00
Benjamin Sergeant
d6366587a0 (UWP CI) install zlib from vcpkg 2020-07-06 17:52:28 -07:00
Benjamin Sergeant
dddf00e3b1 (cobra python bots) remove the test which stop the bot when events do not follow cobra metrics system schema with an id and a device entry 2020-07-06 16:15:24 -07:00
Benjamin Sergeant
cc47fb1c83 (cobra bots) remove bots which is not required now that we can use Python extensions 2020-06-26 16:49:08 -07:00
Benjamin Sergeant
8e8cea1bcd delete C++ code for first memory warning 2020-06-25 11:22:21 -07:00
Benjamin Sergeant
68c97da518 (cmake) new python code is optional and enabled at cmake time with -DUSE_PYTHON=1 2020-06-25 10:05:02 -07:00
Benjamin Sergeant
f8b8799799 fix windows compile error with misplaced #ifdefs/#endifs 2020-06-24 23:26:14 -07:00
Benjamin Sergeant
615f1778c3 new cobra to python bot (still sending to statsd)
values + string building can be done in python (we are embedding it)
2020-06-24 23:21:19 -07:00
Benjamin Sergeant
c45b197c85 (cobra metrics to statsd bot) fps slow frame info : do not include os name 2020-06-19 18:10:49 -07:00
Benjamin Sergeant
78713895dd (cobra metrics to statsd bot) send info about memory warnings 2020-06-19 17:46:59 -07:00
Benjamin Sergeant
aae2402ed2 disable flaky unittest once again ... ping pong is not reliable timing wise 2020-06-19 01:16:07 -07:00
Benjamin Sergeant
b62de6e516 tweak ping/pong test to be more lenient 2020-06-19 01:11:05 -07:00
Benjamin Sergeant
6e747849d7 enable ping unittest, which is flaky -> see #218 2020-06-19 01:04:44 -07:00
Benjamin Sergeant
a3a73ce1ac add unittest to test http redirection fully 2020-06-19 00:22:39 -07:00
Benjamin Sergeant
10c014bf98 (http client) fix deadlock when following redirects 2020-06-19 00:11:06 -07:00
Benjamin Sergeant
9bb3643fc7 (cobra metrics to statsd bot) send info about net requests 2020-06-18 11:25:48 -07:00
Benjamin Sergeant
56db55caca (bots) display received/sent message logs only if we are authenticated 2020-06-18 10:49:36 -07:00
Benjamin Sergeant
565a08b229 (cobra client and bots) add batch_size subscription option for retrieving multiple messages at once 2020-06-17 17:13:45 -07:00
Benjamin Sergeant
bf0f11fd65 fix size_t with int comparison warning 2020-06-17 16:56:06 -07:00
Benjamin Sergeant
558daf8911 (websocket) WebSocketServer is not a final class, so that users can extend it (fix #215) 2020-06-15 18:29:42 -07:00
Benjamin Sergeant
7ba7ff4b2a (cobra bots) minor aesthetic change, in how we display http headers with a : then space as key value separator instead of :: with
no space
2020-06-15 16:09:31 -07:00
tostc
c9854be1c4
- Updated libDiscordBot description (#214) 2020-06-14 09:58:43 -07:00
Xu Zhipei
2fbb1a846f
feat(cmake): add cmake find config support (#213) 2020-06-13 21:04:43 -07:00
Benjamin Sergeant
aa142df486 (cobra metrics to statsd bot) change from a statsd type of gauge to a timing one 2020-06-12 13:47:01 -07:00
Benjamin Sergeant
5e200a440f (redis cobra bots) capture most used devices in a zset 2020-06-11 18:49:45 -07:00
Benjamin Sergeant
6ed8723d7d try to fix a double linking error on Windows with linenoise.hpp 2020-06-11 18:01:45 -07:00
Benjamin Sergeant
ac9710d5d6 (ws) add bare bone redis-cli like sub-command, with command line editing powered by libnoise 2020-06-11 17:30:42 -07:00
Benjamin Sergeant
35d76c20dc add redis cli skeleton ws sub-command 2020-06-11 13:51:10 -07:00
Benjamin Sergeant
ca7344d9dc use reference in range for loop 2020-06-11 13:50:37 -07:00
Benjamin Sergeant
7603d1a71b (redis cobra bots) ws cobra metrics to redis / hostname invalid parsing 2020-06-11 08:33:36 -07:00
Benjamin Sergeant
d0cd4aed5a (redis cobra bots) xadd with maxlen + fix bug in xadd client implementation and ws cobra metrics to redis command argument parsing 2020-06-11 08:20:03 -07:00
Benjamin Sergeant
c5aadffa08 (redis cobra bots) update the cobra to redis bot to use the bot framework, and change it to report fps metrics into redis streams. 2020-06-10 22:30:55 -07:00
Benjamin Sergeant
ecfca1f905 cobra bots: handle stalled connection by disconnecting and reconnecting instead of quitting, and expecting kubernete to restart us 2020-06-09 21:39:37 -07:00
Benjamin Sergeant
e49bf24d2d add device counter 2020-06-09 20:10:37 -07:00
Benjamin Sergeant
2a1cd6bb3e Merge branch 'feature/cobra_metrics_to_statsd' 2020-06-08 18:34:18 -07:00
Benjamin Sergeant
766e33774c count slow frames 2020-06-08 18:19:01 -07:00
Benjamin Sergeant
9b90b1d302 fix cmake warning about mbedtls name incorrect case (all uppercase) 2020-06-04 15:01:49 -07:00
Benjamin Sergeant
ee8a3a52ec compile tweak on linux + version bump 2020-06-04 15:01:16 -07:00
Benjamin Sergeant
531bd624b5 remove spaces in keys + verbose statsd 2020-06-04 14:57:36 -07:00
Benjamin Sergeant
abd6581242 (cobra bots) set thread name for utility threads 2020-06-04 09:52:44 -07:00
Benjamin Sergeant
7095367b93 (cobra bots) set thread name for utility threads 2020-06-04 09:52:35 -07:00
Benjamin Sergeant
3bb359a774 fix warnings 2020-06-04 09:48:16 -07:00
Benjamin Sergeant
1c6ff733f9 fix cmake warning about mbedtls name incorrect case (all uppercase) 2020-06-04 09:41:12 -07:00
Benjamin Sergeant
2ecf5d8a5a (statsd cobra bots) statsd improvement: prefix does not need a dot as a suffix, message size can be larger than 256 bytes, error handling was invalid, use core logger for logging instead of std::cerr 2020-06-04 09:37:16 -07:00
Benjamin Sergeant
0f88969b77 add metrics statsd files 2020-06-04 09:36:28 -07:00
Benjamin Sergeant
c317100b47 (statsd cobra bots) statsd improvement: prefix does not need a dot as a suffix, message size can be larger than 256 bytes, error handling was invalid, use core logger for logging instead of std::cerr 2020-06-04 09:35:55 -07:00
Benjamin Sergeant
b029f176b6 add new subcommand + skeleton files 2020-06-03 16:29:25 -07:00
Benjamin Sergeant
bcfcfb628e build docker file with alpine 3.12 2020-06-02 20:31:32 -07:00
Benjamin Sergeant
268f528423 httpd gzip compression / set a response header and search for gzip in the request header in case deflate is added to gzip in there 2020-06-01 17:11:42 -07:00
Benjamin Sergeant
31be2e2527 update .gitignore file 2020-06-01 17:01:59 -07:00
Benjamin Sergeant
502f021a0e embedded help for httpd tool 2020-06-01 17:01:12 -07:00
Benjamin Sergeant
b0b451d2c7 add a simple httpd server standalone example 2020-05-29 22:10:23 -07:00
Benjamin Sergeant
4872b59fac fix windows build (or operator is not supported || is required 2020-05-29 17:18:09 -07:00
Benjamin Sergeant
bb1be240ec fix linux compile failure 2020-05-29 16:53:57 -07:00
Benjamin Sergeant
b008c97c83 (http server) support gzip compression 2020-05-29 16:49:29 -07:00
Benjamin Sergeant
9886a30490 fix #210 / better standalone example, an echo client 2020-05-27 16:24:33 -07:00
Benjamin Sergeant
4ed5206d79 fix #210 / add include to README hello world example 2020-05-27 16:04:39 -07:00
Benjamin Sergeant
33916869f1 add simple doc link for multipart uploads - fix #209 2020-05-27 10:38:32 -07:00
Benjamin Sergeant
9ddf707804 add script to build with Android NDK 2020-05-26 15:15:45 -07:00
Benjamin Sergeant
3a020a66b7 Merge branch 'feature/badges' 2020-05-21 09:54:41 -07:00
Benjamin Sergeant
bd39e69185
Update index.md 2020-05-21 09:42:15 -07:00
Benjamin Sergeant
9d4ca3f34e
Update README.md 2020-05-21 09:35:33 -07:00
Benjamin Sergeant
de6f3ded09 ci / break unittest job into small job files 2020-05-21 09:01:50 -07:00
flagarde
e0aace33ea
Update CMakeLists.txt (#207) 2020-05-21 08:46:06 -07:00
Benjamin Sergeant
16eb269e1e bump version for (compiler fix) support clang 5 and earlier (contributed by @LunarWatcher) #206 2020-05-20 10:58:30 -07:00
Benjamin Sergeant
2319dec278 (cmake) revert CMake changes to fix #203 and be able to use an external OpenSSL 2020-05-20 10:56:58 -07:00
Olivia Zoe
f1be48aff1
Re-enable support for clang 5 and earlier (#206) 2020-05-20 10:56:24 -07:00
Liz3
93fd44813a
extend docs (#204) 2020-05-18 09:22:25 -07:00
Benjamin Sergeant
54d4d81bf4 (cmake) make install cmake files optional to not conflict with vcpkg
See https://github.com/microsoft/vcpkg/pull/11030
2020-05-17 20:36:46 -07:00
Benjamin Sergeant
ea207d8199 (windows + tls) mbedtls is the default windows tls backend + add ability to load system certificates with mbdetls on windows 2020-05-17 20:36:46 -07:00
Liz3
e8287e91e4
Updated project reference/description (#202)
* edit project reference

* simple rephrase
2020-05-15 09:48:28 -07:00
Benjamin Sergeant
c0505ac7fb windows build fix with max which is a macro 2020-05-12 21:48:41 -07:00
Benjamin Sergeant
1af39bf0eb (ixbots) add options to limit how many messages per minute should be processed 2020-05-12 21:40:17 -07:00
Benjamin Sergeant
2e904801a0 (ixbots) add new class to configure a bot to simplify passing options around 2020-05-12 19:08:16 -07:00
Benjamin Sergeant
cc72494b63 Add reference to DisCPP to the README (fix #198) 2020-05-09 21:08:34 -07:00
Benjamin Sergeant
fa9a4660c6 bump some test timeout 2020-05-08 10:03:18 -07:00
Benjamin Sergeant
4773af8f2f (openssl tls) (openssl < 1.1) logic inversion - crypto locking callback are not registered properly 2020-05-08 09:54:42 -07:00
Benjamin Sergeant
c1403df74a (cmake) default TLS back to mbedtls on Windows Universal Platform 2020-05-08 09:31:53 -07:00
Benjamin Sergeant
3912e22b28 give websocket_subprotocol test more time to establish a connection 2020-05-08 09:26:05 -07:00
XLPhere
c9d5b4a581
Moved fPIC option to the top of the CMakeLists (#197)
The fPIC option was not properly registered before
2020-05-08 08:00:51 -07:00
Benjamin Sergeant
9f8643032d fix dumb compile error 2020-05-06 22:07:47 -07:00
Benjamin Sergeant
0772ef7ef5 (cobra bots) add a --heartbeat_timeout option to specify when the bot should terminate because no events are received 2020-05-06 22:01:48 -07:00
Benjamin Sergeant
c030a62c8b openSSLLockingCallback should be static 2020-05-06 16:57:53 -07:00
Benjamin Sergeant
931530b101 only register the crypto lock callback if no-one has registered them before us 2020-05-06 16:49:04 -07:00
Benjamin Sergeant
6c205b983e (openssl tls) when OpenSSL is older than 1.1, register the crypto locking callback to be thread safe. Should fix lots of CI failures 2020-05-06 16:26:30 -07:00
Benjamin Sergeant
a65b334961 assert that the timeout is non zero in makeCancellationRequestWithTimeout 2020-05-06 15:53:27 -07:00
Benjamin Sergeant
2de8aafcbc another windows build error in IXUdpSocket ... 2020-05-05 08:29:39 -07:00
Benjamin Sergeant
f075f586e1 fix windows compile error with UdpSocket::recvfrom 2020-05-05 08:15:01 -07:00
Benjamin Sergeant
93cb898989 fix compile error with UdpSocket::recvfrom 2020-05-05 08:03:04 -07:00
Benjamin Sergeant
e4da62547b add reference to multiple projects using IXWebSocket 2020-05-05 07:52:02 -07:00
Benjamin Sergeant
2b4c06e6d2 UdpSocket::recvfrom last argument does not have to be a uint32_t 2020-05-05 07:49:07 -07:00
tostc
7337ed34a6
Added asynchronous udp receive function (#193)
* Added asynchronous udp receive function

* Remove receive_async and added low level recv, which is non-blocking.

* Remove thread include

* Moved unix include to IXNetSystem.h
2020-05-05 07:47:41 -07:00
Benjamin Sergeant
15355188d5 (http client) rework a bit PATCH pull request, fix compile error with setForceBody and initialize _forceBody to false 2020-05-05 07:43:55 -07:00
Liz3
8760c87635
add PATCH and add option to enforce a http request body write (#195)
* add PATCH and add option to enforce a http request body write

* remove private bool prop
2020-05-05 07:38:55 -07:00
Benjamin Sergeant
2786631e3b clang-format 2020-05-04 17:19:25 -07:00
Benjamin Sergeant
1b30061a4d remove unused variable 2020-05-04 17:18:21 -07:00
Benjamin Sergeant
af003fc79b (ixbots) fix tsan data race error when accessing verbose parameter 2020-05-04 17:15:35 -07:00
Benjamin Sergeant
4f17cd5e74 (cobra bots) do not use a queue to store messages pending processing, let the bot handle queuing 2020-05-04 15:45:11 -07:00
Benjamin Sergeant
b04764489c (doc) add link to a project using ixwebsocket #187 2020-05-04 09:21:39 -07:00
Benjamin Sergeant
fc4a4bfb7c
fix #194 / linux needs to built with position independant code 2020-05-03 12:19:58 -07:00
ebenali
9e54fd5f1a
Fix CMake/zlibstatic-related regression (#192)
* cmake: add export() and install(EXPORT) for easier packageability

Enable the package to be more readily packageable as a system-wide
install or as a third-party dependency to another CMake-base project

This does not change CMake version requirements AFAICT

* CMake: link-in OpenSSL::Crypto

* CMake: explicitly manage dependencies. Fixes building with zlibstatic
2020-05-02 22:08:58 -07:00
ebenali
1096f62196
cmake: add export() and install(EXPORT) for easier packageability (#190)
* cmake: add export() and install(EXPORT) for easier packageability

Enable the package to be more readily packageable as a system-wide
install or as a third-party dependency to another CMake-base project

This does not change CMake version requirements AFAICT

* CMake: link-in OpenSSL::Crypto
2020-05-02 20:20:59 -07:00
Benjamin Sergeant
b34d9f6a06 uwp fixes 2020-05-01 11:27:59 -07:00
Benjamin Sergeant
b21e2506bf (ci) add universal windows platform 2020-05-01 10:44:10 -07:00
Benjamin Sergeant
303f99a432 refine previous commit 2020-05-01 10:37:25 -07:00
Benjamin Sergeant
a42ccea8dd disable unicode 2020-05-01 09:22:03 -07:00
Benjamin Sergeant
beb26bc096 use ninja for local builds 2020-04-29 11:53:56 -07:00
Benjamin Sergeant
b45980f0f6 (http client) better current request cancellation support when the HttpClient destructor is invoked (see #189) 2020-04-29 11:53:23 -07:00
Benjamin Sergeant
fbca513008 bump version 2020-04-27 12:36:56 -07:00
Benjamin Sergeant
33ebd00932 fix cmake tls backend option parsing 2020-04-27 11:29:50 -07:00
Benjamin Sergeant
fbe5e74109 fix openssl cmake errors 2020-04-27 10:59:47 -07:00
Benjamin Sergeant
a9f5d5353f fix cmake syntax error and convert some errors to fatal errors 2020-04-27 10:29:27 -07:00
Benjamin Sergeant
22e0083832 CMake TLS cleanup 2020-04-27 10:09:51 -07:00
Benjamin Sergeant
5632360fbd (http client) Set default values for most HttpRequestArgs struct members (fix #185) 2020-04-27 09:43:31 -07:00
Benjamin Sergeant
20294841b3 ci - on windows, disable building tls as it is too slow (> 15minutes total) 2020-04-25 15:58:56 -07:00
Benjamin Sergeant
74efdfebba remove bundled mbedtls 2020-04-25 15:41:39 -07:00
Benjamin Sergeant
0ab04f51fe (ssl) Default to OpenSSL on Windows, since it can load the system certificates by default 2020-04-25 15:36:31 -07:00
Benjamin Sergeant
4ed7968b05 ci / try to force an openssl 1.1 install on mac 2020-04-25 11:53:09 -07:00
Benjamin Sergeant
287e48962f bump version number 2020-04-25 11:41:58 -07:00
Francisco Javier
953c680eee
Bug on setting extra headers. Now it loses the first string character. (#184)
Client code:

    ...

    ix::WebSocketHttpHeaders headers {
        {"Cookie", "ABC"}
    };

    ...

Expected header string on server:
    "Cookie: ABC"

Resulted header string on server:
    "Cookie: BC"

Solution:
    The easy way I found to solve the problem is to add a space where extra headers are set before sended to server.

Co-authored-by: Fco. Javier M. C <fcojavmc@todo-redes.com>
2020-04-25 11:39:37 -07:00
Benjamin Sergeant
2802cad8c4 more tls in memory certs doc + bump file format 2020-04-24 15:50:39 -07:00
Benjamin Sergeant
9f770b10c0 clang-format 2020-04-24 15:34:00 -07:00
Max Weisel
677f79b0ea
Implement API for adding custom roots via a string (#178)
* Implement API for adding custom roots via a string. SocketTLSOptions API design needs work, but the IXSocketOpenSSL implementation feels good to me.

* Improve API design for specifying roots from memory.

* Add in-memory root CAs mbedtls implementation.

* Fix bug in newer versions of OpenSSL with in-memory certificate handling.
2020-04-24 15:32:11 -07:00
Benjamin Sergeant
646b18bf28 core logger support multiple level + switch ixbots to user corelogger instead of spdlog 2020-04-24 15:17:50 -07:00
Benjamin Sergeant
0670954faf unittest / remove deleted file reference 2020-04-24 14:23:38 -07:00
Benjamin Sergeant
2469d7102e missing headers for url parsing 2020-04-24 14:13:15 -07:00
Benjamin Sergeant
79acb915ce merge the 2 url parsing file into one, fix a silly build error 2020-04-24 14:08:59 -07:00
Benjamin Sergeant
bad3adb6b4 cmake pb with renamed file 2020-04-24 12:55:00 -07:00
Benjamin Sergeant
e3dd4e60c0 remove deleted IXSelectInterruptEventFd file reference in cmake 2020-04-24 12:52:13 -07:00
Benjamin Sergeant
c70f1d09a8 include all ssl backends inside special per backend macro 2020-04-24 12:47:47 -07:00
Benjamin Sergeant
4b2b133c10 fix #182 2020-04-22 14:26:16 -07:00
Benjamin Sergeant
cd5fae6a5b generate a compilation database when building with make for the default target, so that clang-tidy can be used 2020-04-22 14:14:09 -07:00
Ross Jacobs
5860c5c80b
Fixes #179 (#180) 2020-04-20 22:59:20 -07:00
Benjamin Sergeant
36257cbfe4 update build doc 2020-04-18 03:49:26 -07:00
2277 changed files with 20548 additions and 605837 deletions

View File

@ -2,3 +2,4 @@ build
CMakeCache.txt
ws/CMakeCache.txt
test/build
makefile

View File

@ -1,78 +0,0 @@
name: unittest
on:
push:
paths-ignore:
- 'docs/**'
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: make test
run: make test
mac_tsan_sectransport:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: make test_tsan
run: make test_tsan
mac_tsan_openssl:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: install openssl
run: brew install openssl
- name: make test
run: make test_tsan_openssl
mac_tsan_mbedtls:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: install mbedtls
run: brew install mbedtls
- name: make test
run: make test_tsan_mbedtls
windows_mbedtls:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-vsdevenv@master
- run: |
vcpkg install zlib:x64-windows
vcpkg install mbedtls:x64-windows
- run: |
mkdir build
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_MBED_TLS=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 ..
- run: cmake --build build
# Running the unittest does not work, the binary cannot be found
#- run: ../build/test/ixwebsocket_unittest.exe
# working-directory: test
#
# Windows with OpenSSL is working but disabled as it takes 13 minutes (10 for openssl) to build with vcpkg
#
# windows_openssl:
# runs-on: windows-latest
# steps:
# - uses: actions/checkout@v1
# - uses: seanmiddleditch/gha-setup-vsdevenv@master
# - run: |
# vcpkg install zlib:x64-windows
# vcpkg install openssl:x64-windows
# - run: |
# mkdir build
# cd build
# cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_OPEN_SSL=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 ..
# - run: cmake --build build
#
# # Running the unittest does not work, the binary cannot be found
# #- run: ../build/test/ixwebsocket_unittest.exe
# # working-directory: test

66
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: docker
# When its time to do a release do a build for amd64
# and push all of them to Docker Hub.
# Only trigger on semver shaped tags.
on:
push:
tags:
- "v*.*.*"
jobs:
login:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Prepare
id: prep
run: |
DOCKER_IMAGE=machinezone/ws
VERSION=edge
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
fi
if [ "${{ github.event_name }}" = "schedule" ]; then
VERSION=nightly
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
fi
echo ::set-output name=tags::${TAGS}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to GitHub Package Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2-build-push
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile
target: prod
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.prep.outputs.tags }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache

View File

@ -1,6 +1,8 @@
name: mkdocs
on:
push:
branches:
- master
paths:
- 'docs/**'
@ -21,5 +23,8 @@ jobs:
pip install pygments
- name: Build doc
run: |
git checkout master
git clean -dfx .
git fetch
git pull
mkdocs gh-deploy

View File

@ -1,19 +0,0 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'

15
.github/workflows/unittest_linux.yml vendored Normal file
View File

@ -0,0 +1,15 @@
name: linux
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-ninja@master
- name: make test
run: make -f makefile.dev test

View File

@ -0,0 +1,15 @@
name: linux_asan
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-ninja@master
- name: make test_asan
run: make -f makefile.dev test_asan

View File

@ -0,0 +1,17 @@
name: mac_tsan_mbedtls
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
mac_tsan_mbedtls:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-ninja@master
- name: install mbedtls
run: brew install mbedtls
- name: make test
run: make -f makefile.dev test_tsan_mbedtls

View File

@ -0,0 +1,17 @@
name: mac_tsan_openssl
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
mac_tsan_openssl:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-ninja@master
- name: install openssl
run: brew install openssl@1.1
- name: make test
run: make -f makefile.dev test_tsan_openssl

View File

@ -0,0 +1,15 @@
name: mac_tsan_sectransport
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
mac_tsan_sectransport:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-ninja@master
- name: make test_tsan_sectransport
run: make -f makefile.dev test_tsan_sectransport

45
.github/workflows/unittest_uwp.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: uwp
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
uwp:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-vsdevenv@master
- uses: seanmiddleditch/gha-setup-ninja@master
- run: |
mkdir build
cd build
cmake -GNinja -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe -DUSE_TEST=1 -DUSE_ZLIB=0 ..
- run: |
cd build
ninja
- run: |
cd build
ninja test
#
# Windows with OpenSSL is working but disabled as it takes 13 minutes (10 for openssl) to build with vcpkg
#
# windows_openssl:
# runs-on: windows-latest
# steps:
# - uses: actions/checkout@v1
# - uses: seanmiddleditch/gha-setup-vsdevenv@master
# - run: |
# vcpkg install zlib:x64-windows
# vcpkg install openssl:x64-windows
# - run: |
# mkdir build
# cd build
# cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_OPEN_SSL=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 ..
# - run: cmake --build build
#
# # Running the unittest does not work, the binary cannot be found
# #- run: ../build/test/ixwebsocket_unittest.exe
# # working-directory: test

View File

@ -0,0 +1,28 @@
name: windows_gcc
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-ninja@master
- uses: bsergean/setup-mingw@d79ce405bac9edef3a1726ef00554a56f0bafe66
- run: |
mkdir build
cd build
cmake -GNinja -DCMAKE_CXX_COMPILER=c++ -DCMAKE_C_COMPILER=cc -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 -DCMAKE_UNITY_BUILD=ON ..
- run: |
cd build
ninja
- run: |
cd build
ctest -V
# ninja test
#- run: ../build/test/ixwebsocket_unittest.exe
# working-directory: test

5
.gitignore vendored
View File

@ -5,3 +5,8 @@ ixsnake/ixsnake/.certs/
site/
ws/.certs/
ws/.srl
ixhttpd
makefile
a.out
.idea/
cmake-build-debug/

View File

@ -1,7 +1,12 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
rev: v2.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pocc/pre-commit-hooks
rev: v1.1.1
hooks:
- id: clang-format
args: [-i, -style=file]

19
CMake/FindDeflate.cmake Normal file
View File

@ -0,0 +1,19 @@
# Find package structure taken from libcurl
include(FindPackageHandleStandardArgs)
find_path(DEFLATE_INCLUDE_DIRS libdeflate.h)
find_library(DEFLATE_LIBRARY deflate)
find_package_handle_standard_args(Deflate
FOUND_VAR
DEFLATE_FOUND
REQUIRED_VARS
DEFLATE_LIBRARY
DEFLATE_INCLUDE_DIRS
FAIL_MESSAGE
"Could NOT find deflate"
)
set(DEFLATE_INCLUDE_DIRS ${DEFLATE_INCLUDE_DIRS})
set(DEFLATE_LIBRARIES ${DEFLATE_LIBRARY})

View File

@ -1,19 +0,0 @@
# Find package structure taken from libcurl
include(FindPackageHandleStandardArgs)
find_path(JSONCPP_INCLUDE_DIRS json/json.h)
find_library(JSONCPP_LIBRARY jsoncpp)
find_package_handle_standard_args(JsonCpp
FOUND_VAR
JSONCPP_FOUND
REQUIRED_VARS
JSONCPP_LIBRARY
JSONCPP_INCLUDE_DIRS
FAIL_MESSAGE
"Could NOT find jsoncpp"
)
set(JSONCPP_INCLUDE_DIRS ${JSONCPP_INCLUDE_DIRS})
set(JSONCPP_LIBRARIES ${JSONCPP_LIBRARY})

View File

@ -1,5 +1,8 @@
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
# mbedtls-3.0 changed headers files, and we need to ifdef'out a few things
find_path(MBEDTLS_VERSION_GREATER_THAN_3 mbedtls/build_info.h)
find_library(MBEDTLS_LIBRARY mbedtls)
find_library(MBEDX509_LIBRARY mbedx509)
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
@ -7,7 +10,7 @@ find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MBEDTLS DEFAULT_MSG
find_package_handle_standard_args(MbedTLS DEFAULT_MSG
MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)

View File

@ -3,14 +3,21 @@
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required(VERSION 3.4.1)
cmake_minimum_required(VERSION 3.4.1...3.17.2)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
project(ixwebsocket C CXX)
project(ixwebsocket LANGUAGES C CXX VERSION 11.4.6)
set (CMAKE_CXX_STANDARD 14)
set (CMAKE_CXX_STANDARD 11)
set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)
set (CMAKE_EXPORT_COMPILE_COMMANDS yes)
option (BUILD_DEMO OFF)
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
@ -26,19 +33,26 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXConnectionState.cpp
ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXExponentialBackoff.cpp
ixwebsocket/IXGetFreePort.cpp
ixwebsocket/IXGzipCodec.cpp
ixwebsocket/IXHttp.cpp
ixwebsocket/IXHttpClient.cpp
ixwebsocket/IXHttpServer.cpp
ixwebsocket/IXNetSystem.cpp
ixwebsocket/IXSelectInterrupt.cpp
ixwebsocket/IXSelectInterruptFactory.cpp
ixwebsocket/IXSelectInterruptPipe.cpp
ixwebsocket/IXSelectInterruptEvent.cpp
ixwebsocket/IXSetThreadName.cpp
ixwebsocket/IXSocket.cpp
ixwebsocket/IXSocketConnect.cpp
ixwebsocket/IXSocketFactory.cpp
ixwebsocket/IXSocketServer.cpp
ixwebsocket/IXSocketTLSOptions.cpp
ixwebsocket/IXStrCaseCompare.cpp
ixwebsocket/IXUdpSocket.cpp
ixwebsocket/IXUrlParser.cpp
ixwebsocket/IXUuid.cpp
ixwebsocket/IXUserAgent.cpp
ixwebsocket/IXWebSocket.cpp
ixwebsocket/IXWebSocketCloseConstants.cpp
@ -47,17 +61,20 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
ixwebsocket/IXWebSocketProxyServer.cpp
ixwebsocket/IXWebSocketServer.cpp
ixwebsocket/IXWebSocketTransport.cpp
ixwebsocket/LUrlParser.cpp
)
set( IXWEBSOCKET_HEADERS
ixwebsocket/IXBase64.h
ixwebsocket/IXBench.h
ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h
ixwebsocket/IXDNSLookup.h
ixwebsocket/IXExponentialBackoff.h
ixwebsocket/IXGetFreePort.h
ixwebsocket/IXGzipCodec.h
ixwebsocket/IXHttp.h
ixwebsocket/IXHttpClient.h
ixwebsocket/IXHttpServer.h
@ -65,14 +82,19 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXProgressCallback.h
ixwebsocket/IXSelectInterrupt.h
ixwebsocket/IXSelectInterruptFactory.h
ixwebsocket/IXSelectInterruptPipe.h
ixwebsocket/IXSelectInterruptEvent.h
ixwebsocket/IXSetThreadName.h
ixwebsocket/IXSocket.h
ixwebsocket/IXSocketConnect.h
ixwebsocket/IXSocketFactory.h
ixwebsocket/IXSocketServer.h
ixwebsocket/IXSocketTLSOptions.h
ixwebsocket/IXStrCaseCompare.h
ixwebsocket/IXUdpSocket.h
ixwebsocket/IXUniquePtr.h
ixwebsocket/IXUrlParser.h
ixwebsocket/IXUuid.h
ixwebsocket/IXUtf8Validator.h
ixwebsocket/IXUserAgent.h
ixwebsocket/IXWebSocket.h
@ -80,6 +102,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXWebSocketCloseInfo.h
ixwebsocket/IXWebSocketErrorInfo.h
ixwebsocket/IXWebSocketHandshake.h
ixwebsocket/IXWebSocketHandshakeKeyGen.h
ixwebsocket/IXWebSocketHttpHeaders.h
ixwebsocket/IXWebSocketInitResult.h
ixwebsocket/IXWebSocketMessage.h
@ -88,61 +111,71 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXWebSocketPerMessageDeflate.h
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
ixwebsocket/IXWebSocketProxyServer.h
ixwebsocket/IXWebSocketSendData.h
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h
ixwebsocket/IXWebSocketVersion.h
ixwebsocket/LUrlParser.h
ixwebsocket/libwshandshake.hpp
)
if (UNIX)
# Linux, Mac, iOS, Android
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.cpp )
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.h )
endif()
# Platform specific code
if (APPLE)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
elseif (WIN32)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp)
else()
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
endif()
option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF)
option(USE_TLS "Enable TLS support" FALSE)
if (USE_TLS)
option(USE_MBED_TLS "Use Mbed TLS" OFF)
option(USE_OPEN_SSL "Use OpenSSL" OFF)
# default to securetranport on Apple if nothing is configured
if (APPLE)
if (NOT USE_MBED_TLS AND NOT USE_OPEN_SSL) # unless we want something else
set(USE_SECURE_TRANSPORT ON)
endif()
# default to mbedtls on windows if nothing is configured
if (WIN32 AND NOT USE_OPEN_SSL AND NOT USE_MBED_TLS)
option(USE_MBED_TLS "Use Mbed TLS" ON)
elseif (WIN32)
if (NOT USE_OPEN_SSL) # unless we want something else
set(USE_MBED_TLS ON)
endif()
else() # default to OpenSSL on all other platforms
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
set(USE_OPEN_SSL ON)
set(requires "openssl")
endif()
endif()
if (USE_MBED_TLS)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
elseif (APPLE AND NOT USE_OPEN_SSL)
elseif (USE_SECURE_TRANSPORT)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
else()
set(USE_OPEN_SSL ON)
elseif (USE_OPEN_SSL)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
else()
message(FATAL_ERROR "TLS Configuration error: unknown backend")
endif()
endif()
add_library( ixwebsocket STATIC
${IXWEBSOCKET_SOURCES}
${IXWEBSOCKET_HEADERS}
)
if(BUILD_SHARED_LIBS)
# Building shared library
if(MSVC)
# Workaround for some projects
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
add_library( ixwebsocket SHARED
${IXWEBSOCKET_SOURCES}
${IXWEBSOCKET_HEADERS}
)
# Set library version
set_target_properties(ixwebsocket PROPERTIES VERSION ${PROJECT_VERSION})
else()
# Static library
add_library( ixwebsocket
${IXWEBSOCKET_SOURCES}
${IXWEBSOCKET_HEADERS}
)
endif()
if (USE_TLS)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_TLS)
@ -150,66 +183,80 @@ if (USE_TLS)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS)
elseif (USE_OPEN_SSL)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_OPEN_SSL)
elseif (USE_SECURE_TRANSPORT)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_SECURE_TRANSPORT)
else()
message(FATAL_ERROR "TLS Configuration error: unknown backend")
endif()
endif()
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS AND NOT USE_OPEN_SSL)
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
if (USE_TLS)
if (USE_OPEN_SSL)
message(STATUS "TLS configured to use openssl")
# Help finding Homebrew's OpenSSL on macOS
if (APPLE)
set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib)
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
# This is for MacPort OpenSSL 1.0
# set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /opt/local/lib/openssl-1.0)
# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0)
endif()
# This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
add_definitions(${OPENSSL_DEFINITIONS})
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
target_link_libraries(ixwebsocket PRIVATE ${OPENSSL_LIBRARIES})
elseif (USE_MBED_TLS)
message(STATUS "TLS configured to use mbedtls")
# This MBEDTLS_FOUND check is to help find a cmake manually configured MbedTLS
if (NOT MBEDTLS_FOUND)
find_package(MbedTLS REQUIRED)
if (MBEDTLS_VERSION_GREATER_THAN_3)
target_compile_definitions(ixwebsocket PRIVATE IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3)
endif()
endif()
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${MBEDTLS_INCLUDE_DIRS}>)
target_link_libraries(ixwebsocket PRIVATE ${MBEDTLS_LIBRARIES})
elseif (USE_SECURE_TRANSPORT)
message(STATUS "TLS configured to use secure transport")
target_link_libraries(ixwebsocket PRIVATE "-framework Foundation" "-framework Security")
endif()
endif()
option(USE_ZLIB "Enable zlib support" TRUE)
if (USE_ZLIB)
find_package(ZLIB REQUIRED)
target_link_libraries(ixwebsocket PRIVATE ZLIB::ZLIB)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
endif()
if (WIN32)
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
target_link_libraries(ixwebsocket PRIVATE wsock32 ws2_32 shlwapi)
target_compile_definitions(ixwebsocket PRIVATE _CRT_SECURE_NO_WARNINGS)
if (USE_TLS)
target_link_libraries(ixwebsocket PRIVATE Crypt32)
endif()
endif()
if (UNIX)
if (UNIX AND NOT APPLE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads)
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(ixwebsocket PRIVATE Threads::Threads)
endif()
if (USE_TLS AND USE_OPEN_SSL)
# Help finding Homebrew's OpenSSL on macOS
if (APPLE)
set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib)
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
endif()
if(NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
add_definitions(${OPENSSL_DEFINITIONS})
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
endif()
if (USE_TLS AND USE_MBED_TLS)
# FIXME I'm not too sure that this USE_VENDORED_THIRD_PARTY thing works
if (USE_VENDORED_THIRD_PARTY)
set (ENABLE_PROGRAMS OFF)
add_subdirectory(third_party/mbedtls)
include_directories(third_party/mbedtls/include)
target_link_libraries(ixwebsocket mbedtls)
else()
find_package(MbedTLS REQUIRED)
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
endif()
endif()
if (NOT ZLIB_FOUND)
find_package(ZLIB)
endif()
if (ZLIB_FOUND)
include_directories(${ZLIB_INCLUDE_DIRS})
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
else()
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
add_subdirectory(third_party/zlib)
target_link_libraries(ixwebsocket zlibstatic)
endif()
set( IXWEBSOCKET_INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}
@ -220,29 +267,59 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_compile_options(ixwebsocket PRIVATE /MP)
endif()
target_include_directories(ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS})
include(GNUInstallDirs)
target_include_directories(ixwebsocket PUBLIC
$<BUILD_INTERFACE:${IXWEBSOCKET_INCLUDE_DIRS}/>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket>
)
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
install(TARGETS ixwebsocket
ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
)
add_library(ixwebsocket::ixwebsocket ALIAS ixwebsocket)
option(IXWEBSOCKET_INSTALL "Install IXWebSocket" TRUE)
if (IXWEBSOCKET_INSTALL)
install(TARGETS ixwebsocket
EXPORT ixwebsocket
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
)
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket")
set(prefix ${CMAKE_INSTALL_PREFIX})
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
install(EXPORT ixwebsocket
FILE ixwebsocket-targets.cmake
NAMESPACE ixwebsocket::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket
)
endif()
if (USE_WS OR USE_TEST)
add_subdirectory(ixcore)
add_subdirectory(ixcrypto)
add_subdirectory(ixcobra)
add_subdirectory(ixsnake)
add_subdirectory(ixsentry)
add_subdirectory(ixbots)
include(FetchContent)
FetchContent_Declare(spdlog
GIT_REPOSITORY "https://github.com/gabime/spdlog"
GIT_TAG "v1.8.0"
GIT_SHALLOW 1)
add_subdirectory(third_party/spdlog spdlog)
FetchContent_MakeAvailable(spdlog)
if (USE_WS)
add_subdirectory(ws)
add_subdirectory(ws)
endif()
if (USE_TEST)
add_subdirectory(test)
enable_testing()
add_subdirectory(test)
endif()
endif()
if (BUILD_DEMO)
add_executable(demo main.cpp)
target_link_libraries(demo ixwebsocket)
endif()

143
README.md
View File

@ -1,37 +1,91 @@
## Hello world
![Build status](https://github.com/machinezone/IXWebSocket/workflows/unittest/badge.svg)
(note from the main developer, sadly I don't have too much time to devote to this library anymore, maybe it's time to pass the maintenance to someone else more motivated ?)
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
```cpp
// Required on Windows
ix::initNetSystem();
/*
* main.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
*
* On macOS
* $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install)
* $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
* $ ./a.out
*
* Or use cmake -DBUILD_DEMO=ON option for other platforms
*/
// Our websocket object
ix::WebSocket webSocket;
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXUserAgent.h>
#include <iostream>
std::string url("ws://localhost:8080/");
webSocket.setUrl(url);
int main()
{
// Required on Windows
ix::initNetSystem();
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
// when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Message)
// Our websocket object
ix::WebSocket webSocket;
// Connect to a server with encryption
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
// https://github.com/machinezone/IXWebSocket/issues/386#issuecomment-1105235227 (self signed certificates)
std::string url("wss://echo.websocket.org");
webSocket.setUrl(url);
std::cout << "Connecting to " << url << "..." << std::endl;
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
// when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
std::cout << msg->str << std::endl;
if (msg->type == ix::WebSocketMessageType::Message)
{
std::cout << "received message: " << msg->str << std::endl;
std::cout << "> " << std::flush;
}
else if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "Connection established" << std::endl;
std::cout << "> " << std::flush;
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
// Maybe SSL is not configured properly
std::cout << "Connection error: " << msg->errorInfo.reason << std::endl;
std::cout << "> " << std::flush;
}
}
);
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
// Send a message to the server (default to TEXT mode)
webSocket.send("hello world");
// Display a prompt
std::cout << "> " << std::flush;
std::string text;
// Read text from the console and send messages in text mode.
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
while (std::getline(std::cin, text))
{
webSocket.send(text);
std::cout << "> " << std::flush;
}
);
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
// Send a message to the server (default to TEXT mode)
webSocket.send("hello world");
return 0;
}
```
Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
@ -40,8 +94,59 @@ IXWebSocket is actively being developed, check out the [changelog](https://machi
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code.
Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible.
## Users
If your company or project is using this library, feel free to open an issue or PR to amend this list.
- [Machine Zone](https://www.mz.com)
- [Tokio](https://github.com/liz3/tokio), a discord library focused on audio playback with node bindings.
- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework.
- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod
- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper (archived as of Oct 8, 2021)
- [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots
- [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar
- [Abaddon](https://github.com/uowuo/abaddon), An alternative Discord client made with C++/gtkmm
- [NovaCoin](https://github.com/novacoin-project/novacoin), a hybrid scrypt PoW + PoS based cryptocurrency.
- [Candy](https://github.com/lanthora/candy), A WebSocket and TUN based VPN for Linux
- [ITGmania](https://github.com/itgmania/itgmania), a cross platform Dance Dance Revolution-like emulator.
## Alternative libraries
There are plenty of great websocket libraries out there, which might work for you. Here are a couple of serious ones.
* [websocketpp](https://github.com/zaphoyd/websocketpp) - C++
* [beast](https://github.com/boostorg/beast) - C++
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C++
* [libwebsockets](https://libwebsockets.org/) - C
* [wslay](https://github.com/tatsuhiro-t/wslay) - C
[uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv.
To check the performance of a websocket library, you can look at the [autoroute](https://github.com/bsergean/autoroute) project.
## Continuous Integration
| OS | TLS | Sanitizer | Status |
|-------------------|-------------------|-------------------|-------------------|
| Linux | OpenSSL | None | [![Build2][1]][0] |
| macOS | Secure Transport | Thread Sanitizer | [![Build2][2]][0] |
| macOS | OpenSSL | Thread Sanitizer | [![Build2][3]][0] |
| macOS | MbedTLS | Thread Sanitizer | [![Build2][4]][0] |
| Windows | Disabled | None | [![Build2][5]][0] |
| UWP | Disabled | None | [![Build2][6]][0] |
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] |
* Some tests are disabled on Windows/UWP because of a pathing problem
* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.
[0]: https://github.com/machinezone/IXWebSocket
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg
[3]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg
[4]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg

View File

@ -1,22 +0,0 @@
image:
- Visual Studio 2017
install:
- cd C:\Tools\vcpkg
- git pull
- .\bootstrap-vcpkg.bat
- cd %APPVEYOR_BUILD_FOLDER%
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
- vcpkg install zlib:x64-windows
- vcpkg install mbedtls:x64-windows
- mkdir build
- cd build
- cmake -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DUSE_WS=1 -DUSE_TEST=1 -DUSE_TLS=1 -G"NMake Makefiles" ..
- nmake
- cd ..
- cd test
- ..\build\test\ixwebsocket_unittest.exe
cache: c:\tools\vcpkg\installed\
build: off

View File

@ -1,67 +1,11 @@
version: "3"
version: "3.3"
services:
# snake:
# image: bsergean/ws:build
# entrypoint: ws snake --port 8767 --host 0.0.0.0 --redis_hosts redis1
# ports:
# - "8767:8767"
# networks:
# - ws-net
# depends_on:
# - redis1
push:
entrypoint: ws push_server --host 0.0.0.0
image: ${DOCKER_REPO}/ws:build
# proxy:
# image: bsergean/ws:build
# entrypoint: strace ws proxy_server --remote_host 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765 -v
# ports:
# - "8765:8765"
# networks:
# - ws-net
#pyproxy:
# image: bsergean/ws_proxy:build
# entrypoint: /usr/bin/ws_proxy.py --remote_url 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765
# ports:
# - "8765:8765"
# networks:
# - ws-net
# # ws:
# # security_opt:
# # - seccomp:unconfined
# # cap_add:
# # - SYS_PTRACE
# # stdin_open: true
# # tty: true
# # image: bsergean/ws:build
# # entrypoint: sh
# # networks:
# # - ws-net
# # depends_on:
# # - redis1
# #
# # redis1:
# # image: redis:alpine
# # networks:
# # - ws-net
# #
# # statsd:
# # image: jaconel/statsd
# # ports:
# # - "8125:8125"
# # environment:
# # - STATSD_DUMP_MSG=true
# # - GRAPHITE_HOST=127.0.0.1
# # networks:
# # - ws-net
compile:
image: alpine
entrypoint: sh
stdin_open: true
tty: true
volumes:
- /Users/bsergeant/src/foss:/home/bsergean/src/foss
networks:
ws-net:
autoroute:
entrypoint: ws autoroute ws://push:8008
image: ${DOCKER_REPO}/ws:build
depends_on:
- push

View File

@ -1,8 +1,8 @@
FROM alpine:3.11 as build
FROM alpine:3.12 as build
RUN apk add --no-cache \
gcc g++ musl-dev linux-headers \
cmake mbedtls-dev make zlib-dev
cmake mbedtls-dev make zlib-dev python3-dev ninja git
RUN addgroup -S app && \
adduser -S -G app app && \
@ -15,12 +15,12 @@ COPY --chown=app:app . /opt
WORKDIR /opt
USER app
RUN make ws_mbedtls_install && \
RUN make -f makefile.dev ws_mbedtls_install && \
sh tools/trim_repo_for_docker.sh
FROM alpine:3.11 as runtime
FROM alpine:3.12 as runtime
RUN apk add --no-cache libstdc++ mbedtls ca-certificates && \
RUN apk add --no-cache libstdc++ mbedtls ca-certificates python3 strace && \
addgroup -S app && \
adduser -S -G app app

26
docker/Dockerfile.centos7 Normal file
View File

@ -0,0 +1,26 @@
FROM centos:7 as build
RUN yum install -y gcc-c++ make zlib-devel openssl-devel redhat-rpm-config
RUN groupadd app && useradd -g app app
RUN chown -R app:app /opt
RUN chown -R app:app /usr/local
WORKDIR /tmp
RUN curl -O https://cmake.org/files/v3.14/cmake-3.14.0-Linux-x86_64.tar.gz
RUN tar zxvf cmake-3.14.0-Linux-x86_64.tar.gz
RUN cp -rf cmake-3.14.0-Linux-x86_64/* /usr/
RUN yum install -y git
# There is a bug in CMake where we cannot build from the root top folder
# So we build from /opt
COPY --chown=app:app . /opt
WORKDIR /opt
USER app
RUN [ "make", "ws_no_python" ]
RUN [ "rm", "-rf", "build" ]
ENTRYPOINT ["ws"]
CMD ["--help"]

View File

@ -0,0 +1,23 @@
# Build time
FROM ubuntu:groovy as build
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install g++ libssl-dev libz-dev make python ninja-build
RUN apt-get -y install cmake
RUN apt-get -y install gdb
COPY . /opt
WORKDIR /opt
#
# To use the container interactively for debugging/building
# 1. Build with
# CMD ["ls"]
# 2. Run with
# docker run --entrypoint sh -it docker-game-eng-dev.addsrv.com/ws:9.10.6
#
RUN ["make", "test"]
# CMD ["ls"]

View File

@ -0,0 +1,27 @@
# Build time
FROM ubuntu:precise 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 --no-check-certificate 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
RUN apt-get -y install git
COPY . .
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
RUN ["make", "ws_no_python"]
ENTRYPOINT ["ws"]
CMD ["--help"]

View File

@ -0,0 +1,22 @@
# Build time
FROM ubuntu:trusty 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 --no-check-certificate 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++ libssl-dev libz-dev make python git
COPY . .
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
RUN ["make", "ws_no_python"]
ENTRYPOINT ["ws"]
CMD ["--help"]

View File

@ -1,6 +1,540 @@
# Changelog
All changes to this project will be documented in this file.
## [11.4.5] - 2024-06-05
New changes are documented in the Release page in the GitHub repository.
## [11.4.4] - 2023-06-05
## [11.4.3] - 2022-05-13
Set shorter thread names
BoringSSL fix with SNI
Websocket computed header is valid Base64
## [11.4.1] - 2022-04-23
vckpg + cmake fix, to handle zlib as a dependency better
## [11.4.0] - 2022-01-05
(Windows) Use wsa select event, which should lead to a much better behavior on Windows in general, and also when sending large payloads (#342)
Fix "HTTP/1.1 400 Illegal character CNTL=0xf" caused by serverMaxWindowBits/clientMaxWindowBits being uint8_t (signed char). (#341)
Export symbols into .def files
Export symbols into .def files on MSVC (#339)
Include <cerrno> to provide standard error constants (#338)
Improved compatibility - fix mingw crossbuild (#337)
Allow to cancel asynchronous HTTP requests (#332)
Fix errors in example code. (#336)
## [11.3.2] - 2021-11-24
(server) Add getters for basic Servers properties (like port, host, etc...) (#327) + fix one compiler warning
## [11.3.1] - 2021-10-22
(library/cmake) Compatible with MbedTLS 3 + fix a bug on Windows where the incorrect remote port is computed (#320)
## [11.3.0] - 2021-09-20
(library/cmake) Only find OpenSSL, MbedTLS, zlib if they have not already been found, make CMake install optional (#317) + Use GNUInstallDirs in cmake (#318)
## [11.2.10] - 2021-07-27
(ws) bump CLI command line parsing library from 1.8 to 2.0
## [11.2.9] - 2021-06-08
(ws) ws connect has a -g option to gzip decompress messages for API such as the websocket Huobi Global.
## [11.2.8] - 2021-06-03
(websocket client + server) WebSocketMessage class tweak to fix unsafe patterns
## [11.2.7] - 2021-05-27
(websocket server) Handle and accept firefox browser special upgrade value (keep-alive, Upgrade)
## [11.2.6] - 2021-05-18
(Windows) move EINVAL (re)definition from IXSocket.h to IXNetSystem.h (fix #289)
## [11.2.5] - 2021-04-04
(http client) DEL is not an HTTP method name, but DELETE is
## [11.2.4] - 2021-03-25
(cmake) install IXUniquePtr.h
## [11.2.3] - 2021-03-24
(ssl + windows) missing include for CertOpenStore function
## [11.2.2] - 2021-03-23
(ixwebsocket) version bump
## [11.2.1] - 2021-03-23
(ixwebsocket) version bump
## [11.2.0] - 2021-03-23
(ixwebsocket) correct mingw support (gcc on windows)
## [11.1.4] - 2021-03-23
(ixwebsocket) add getMinWaitBetweenReconnectionRetries
## [11.1.3] - 2021-03-23
(ixwebsocket) New option to set the min wait between reconnection attempts. Still default to 1ms. (setMinWaitBetweenReconnectionRetries).
## [11.1.2] - 2021-03-22
(ws) initialize maxWaitBetweenReconnectionRetries to a non zero value ; a zero value was causing spurious reconnections attempts
## [11.1.1] - 2021-03-20
(cmake) Library can be built as a static or a dynamic library, controlled with BUILD_SHARED_LIBS. Default to static library
## [11.1.0] - 2021-03-16
(ixwebsocket) Use LEAN_AND_MEAN Windows define to help with undefined link error when building a DLL. Support websocket server disablePerMessageDeflate option correctly.
## [11.0.9] - 2021-03-07
(ixwebsocket) Expose setHandshakeTimeout method
## [11.0.8] - 2020-12-25
(ws) trim ws dependencies no more ixcrypto and ixcore deps
## [11.0.7] - 2020-12-25
(ws) trim ws dependencies, only depends on ixcrypto and ixcore
## [11.0.6] - 2020-12-22
(build) rename makefile to makefile.dev to ease cmake BuildExternal (fix #261)
## [11.0.5] - 2020-12-17
(ws) Implement simple header based websocket authorization technique to reject
client which do not supply a certain header ("Authorization") with a special
value (see doc).
## [11.0.4] - 2020-11-16
(ixwebsocket) Handle EINTR return code in ix::poll and IXSelectInterrupt
## [11.0.3] - 2020-11-16
(ixwebsocket) Fix #252 / regression in 11.0.2 with string comparisons
## [11.0.2] - 2020-11-15
(ixwebsocket) use a C++11 compatible make_unique shim
## [11.0.1] - 2020-11-11
(socket) replace a std::vector with an std::array used as a tmp buffer in Socket::readBytes
## [11.0.0] - 2020-11-11
(openssl security fix) in the client to server connection, peer verification is not done in all cases. See https://github.com/machinezone/IXWebSocket/pull/250
## [10.5.7] - 2020-11-07
(docker) build docker container with zlib disabled
## [10.5.6] - 2020-11-07
(cmake) DEFLATE -> Deflate in CMake to stop warnings about casing
## [10.5.5] - 2020-11-07
(ws autoroute) Display result in compliant way (AUTOROUTE IXWebSocket :: N ms) so that result can be parsed easily
## [10.5.4] - 2020-10-30
(ws gunzip + IXGZipCodec) Can decompress gziped data with libdeflate. ws gunzip computed output filename was incorrect (was the extension aka gz) instead of the file without the extension. Also check whether the output file is writeable.
## [10.5.3] - 2020-10-19
(http code) With zlib disabled, some code should not be reached
## [10.5.2] - 2020-10-12
(ws curl) Add support for --data-binary option, to set the request body. When present the request will be sent with the POST verb
## [10.5.1] - 2020-10-09
(http client + server + ws) Add support for compressing http client requests with gzip. --compress_request argument is used in ws to enable this. The Content-Encoding is set to gzip, and decoded on the server side if present.
## [10.5.0] - 2020-09-30
(http client + server + ws) Add support for uploading files with ws -F foo=@filename, new -D http server option to debug incoming client requests, internal api changed for http POST, PUT and PATCH to supply an HttpFormDataParameters
## [10.4.9] - 2020-09-30
(http server + utility code) Add support for doing gzip compression with libdeflate library, if available
## [10.4.8] - 2020-09-30
(cmake) Stop using FetchContent cmake module to retrieve jsoncpp third party dependency
## [10.4.7] - 2020-09-28
(ws) add gzip and gunzip ws sub commands
## [10.4.6] - 2020-09-26
(cmake) use FetchContent cmake module to retrieve jsoncpp third party dependency
## [10.4.5] - 2020-09-26
(cmake) use FetchContent cmake module to retrieve spdlog third party dependency
## [10.4.4] - 2020-09-22
(cobra connection) retrieve cobra server connection id from the cobra handshake message and display it in ws clients, metrics publisher and bots
## [10.4.3] - 2020-09-22
(cobra 2 cobra) specify as an HTTP header which channel we will republish to
## [10.4.2] - 2020-09-18
(cobra bots) change an error log to a warning log when reconnecting because no messages were received for a minute
## [10.4.1] - 2020-09-18
(cobra connection and bots) set an HTTP header when connecting to help with debugging bots
## [10.4.0] - 2020-09-12
(http server) read body request when the Content-Length is specified + set timeout to read the request to 30 seconds max by default, and make it configurable as a constructor parameter
## [10.3.5] - 2020-09-09
(ws) autoroute command exit on its own once all messages have been received
## [10.3.4] - 2020-09-04
(docker) ws docker file installs strace
## [10.3.3] - 2020-09-02
(ws) echo_client command renamed to autoroute. Command exit once the server close the connection. push_server commands exit once N messages have been sent.
## [10.3.2] - 2020-08-31
(ws + cobra bots) add a cobra_to_cobra ws subcommand to subscribe to a channel and republish received events to a different channel
## [10.3.1] - 2020-08-28
(socket servers) merge the ConnectionInfo class with the ConnectionState one, which simplify all the server apis
## [10.3.0] - 2020-08-26
(ws) set the main thread name, to help with debugging in XCode, gdb, lldb etc...
## [10.2.9] - 2020-08-19
(ws) cobra to python bot / take a module python name as argument foo.bar.baz instead of a path foo/bar/baz.py
## [10.2.8] - 2020-08-19
(ws) on Linux with mbedtls, when the system ca certs are specified (the default) pick up sensible OS supplied paths (tested with CentOS and Alpine)
## [10.2.7] - 2020-08-18
(ws push_server) on the server side, stop sending and close the connection when the remote end has disconnected
## [10.2.6] - 2020-08-17
(ixwebsocket) replace std::unique_ptr<unsigned char[]> with std::array for some fixed arrays (which are in C++11)
## [10.2.5] - 2020-08-15
(ws) merge all ws_*.cpp files into a single one to speedup compilation
## [10.2.4] - 2020-08-15
(socket server) in the loop accepting connections, call select without a timeout on unix to avoid busy looping, and only wake up when a new connection happens
## [10.2.3] - 2020-08-15
(socket server) instead of busy looping with a sleep, only wake up the GC thread when a new thread will have to be joined, (we know that thanks to the ConnectionState OnSetTerminated callback
## [10.2.2] - 2020-08-15
(socket server) add a callback to the ConnectionState to be invoked when the connection is terminated. This will be used by the SocketServer in the future to know on time that the associated connection thread can be terminated.
## [10.2.1] - 2020-08-15
(socket server) do not create a select interrupt object everytime when polling for notifications while waiting for new connections, instead use a persistent one which is a member variable
## [10.2.0] - 2020-08-14
(ixwebsocket client) handle HTTP redirects
## [10.2.0] - 2020-08-13
(ws) upgrade to latest version of nlohmann json (3.9.1 from 3.2.0)
## [10.1.9] - 2020-08-13
(websocket proxy server) add ability to map different hosts to different websocket servers, using a json config file
## [10.1.8] - 2020-08-12
(ws) on macOS, with OpenSSL or MbedTLS, use /etc/ssl/cert.pem as the system certs
## [10.1.7] - 2020-08-11
(ws) -q option imply info log level, not warning log level
## [10.1.6] - 2020-08-06
(websocket server) Handle programmer error when the server callback is not registered properly (fix #227)
## [10.1.5] - 2020-08-02
(ws) Add a new ws sub-command, push_server. This command runs a server which sends many messages in a loop to a websocket client. We can receive above 200,000 messages per second (cf #235).
## [10.1.4] - 2020-08-02
(ws) Add a new ws sub-command, echo_client. This command sends a message to an echo server, and send back to a server whatever message it does receive. When connecting to a local ws echo_server, on my MacBook Pro 2015 I can send/receive around 30,000 messages per second. (cf #235)
## [10.1.3] - 2020-08-02
(ws) ws echo_server. Add a -q option to only enable warning and error log levels. This is useful for bench-marking so that we do not print a lot of things on the console. (cf #235)
## [10.1.2] - 2020-07-31
(build) make using zlib optional, with the caveat that some http and websocket features are not available when zlib is absent
## [10.1.1] - 2020-07-29
(websocket client) onProgressCallback not called for short messages on a websocket (fix #233)
## [10.1.0] - 2020-07-29
(websocket client) heartbeat is not sent at the requested frequency (fix #232)
## [10.0.3] - 2020-07-28
compiler warning fixes
## [10.0.2] - 2020-07-28
(ixcobra) CobraConnection: unsubscribe from all subscriptions when disconnecting
## [10.0.1] - 2020-07-27
(socket utility) move ix::getFreePort to ixwebsocket library
## [10.0.0] - 2020-07-25
(ixwebsocket server) change legacy api with 2 nested callbacks, so that the first api takes a weak_ptr<WebSocket> as its first argument
## [9.10.7] - 2020-07-25
(ixwebsocket) add WebSocketProxyServer, from ws. Still need to make the interface better.
## [9.10.6] - 2020-07-24
(ws) port broadcast_server sub-command to the new server API
## [9.10.5] - 2020-07-24
(unittest) port most unittests to the new server API
## [9.10.3] - 2020-07-24
(ws) port ws transfer to the new server API
## [9.10.2] - 2020-07-24
(websocket client) reset WebSocketTransport onClose callback in the WebSocket destructor
## [9.10.1] - 2020-07-24
(websocket server) reset client websocket callback when the connection is closed
## [9.10.0] - 2020-07-23
(websocket server) add a new simpler API to handle client connections / that API does not trigger a memory leak while the previous one did
## [9.9.3] - 2020-07-17
(build) merge platform specific files which were used to have different implementations for setting a thread name into a single file, to make it easier to include every source files and build the ixwebsocket library (fix #226)
## [9.9.2] - 2020-07-10
(socket server) bump default max connection count from 32 to 128
## [9.9.1] - 2020-07-10
(snake) implement super simple stream sql expression support in snake server
## [9.9.0] - 2020-07-08
(socket+websocket+http+redis+snake servers) expose the remote ip and remote port when a new connection is made
## [9.8.6] - 2020-07-06
(cmake) change the way zlib and openssl are searched
## [9.8.5] - 2020-07-06
(cobra python bots) remove the test which stop the bot when events do not follow cobra metrics system schema with an id and a device entry
## [9.8.4] - 2020-06-26
(cobra bots) remove bots which is not required now that we can use Python extensions
## [9.8.3] - 2020-06-25
(cmake) new python code is optional and enabled at cmake time with -DUSE_PYTHON=1
## [9.8.2] - 2020-06-24
(cobra bots) new cobra metrics bot to send data to statsd using Python for processing the message
## [9.8.1] - 2020-06-19
(cobra metrics to statsd bot) fps slow frame info : do not include os name
## [9.8.0] - 2020-06-19
(cobra metrics to statsd bot) send info about memory warnings
## [9.7.9] - 2020-06-18
(http client) fix deadlock when following redirects
## [9.7.8] - 2020-06-18
(cobra metrics to statsd bot) send info about net requests
## [9.7.7] - 2020-06-17
(cobra client and bots) add batch_size subscription option for retrieving multiple messages at once
## [9.7.6] - 2020-06-15
(websocket) WebSocketServer is not a final class, so that users can extend it (fix #215)
## [9.7.5] - 2020-06-15
(cobra bots) minor aesthetic change, in how we display http headers with a : then space as key value separator instead of :: with no space
## [9.7.4] - 2020-06-11
(cobra metrics to statsd bot) change from a statsd type of gauge to a timing one
## [9.7.3] - 2020-06-11
(redis cobra bots) capture most used devices in a zset
## [9.7.2] - 2020-06-11
(ws) add bare bone redis-cli like sub-command, with command line editing powered by libnoise
## [9.7.1] - 2020-06-11
(redis cobra bots) ws cobra metrics to redis / hostname invalid parsing
## [9.7.0] - 2020-06-11
(redis cobra bots) xadd with maxlen + fix bug in xadd client implementation and ws cobra metrics to redis command argument parsing
## [9.6.9] - 2020-06-10
(redis cobra bots) update the cobra to redis bot to use the bot framework, and change it to report fps metrics into redis streams.
## [9.6.6] - 2020-06-04
(statsd cobra bots) statsd improvement: prefix does not need a dot as a suffix, message size can be larger than 256 bytes, error handling was invalid, use core logger for logging instead of std::cerr
## [9.6.5] - 2020-05-29
(http server) support gzip compression
## [9.6.4] - 2020-05-20
(compiler fix) support clang 5 and earlier (contributed by @LunarWatcher)
## [9.6.3] - 2020-05-18
(cmake) revert CMake changes to fix #203 and be able to use an external OpenSSL
## [9.6.2] - 2020-05-17
(cmake) make install cmake files optional to not conflict with vcpkg
## [9.6.1] - 2020-05-17
(windows + tls) mbedtls is the default windows tls backend + add ability to load system certificates with mbdetls on windows
## [9.6.0] - 2020-05-12
(ixbots) add options to limit how many messages per minute should be processed
## [9.5.9] - 2020-05-12
(ixbots) add new class to configure a bot to simplify passing options around
## [9.5.8] - 2020-05-08
(openssl tls) (openssl < 1.1) logic inversion - crypto locking callback are not registered properly
## [9.5.7] - 2020-05-08
(cmake) default TLS back to mbedtls on Windows Universal Platform
## [9.5.6] - 2020-05-06
(cobra bots) add a --heartbeat_timeout option to specify when the bot should terminate because no events are received
## [9.5.5] - 2020-05-06
(openssl tls) when OpenSSL is older than 1.1, register the crypto locking callback to be thread safe. Should fix lots of CI failures
## [9.5.4] - 2020-05-04
(cobra bots) do not use a queue to store messages pending processing, let the bot handle queuing
## [9.5.3] - 2020-04-29
(http client) better current request cancellation support when the HttpClient destructor is invoked (see #189)
## [9.5.2] - 2020-04-27
(cmake) fix cmake broken tls option parsing
## [9.5.1] - 2020-04-27
(http client) Set default values for most HttpRequestArgs struct members (fix #185)
## [9.5.0] - 2020-04-25
(ssl) Default to OpenSSL on Windows, since it can load the system certificates by default
## [9.4.1] - 2020-04-25
(header) Add a space between header name and header value since most http parsers expects it, although it it not required. Cf #184 and #155
## [9.4.0] - 2020-04-24
(ssl) Add support for supplying SSL CA from memory, for OpenSSL and MbedTLS backends
## [9.3.3] - 2020-04-17
(ixbots) display sent/receive message, per seconds as accumulated

View File

@ -17,11 +17,15 @@ There is a unittest which can be executed by typing `make test`.
Options for building:
* `-DBUILD_SHARED_LIBS=ON` will build the unittest as a shared libary instead of a static library, which is the default
* `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension
* `-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_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows). When using a custom version of openssl (say a prebuilt version, odd runtime problems can happens, as in #319, and special cmake trickery will be required (see this [comment](https://github.com/machinezone/IXWebSocket/issues/175#issuecomment-620231032))
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support
* `-DUSE_WS=1` will build the ws interactive command line tool
* `-DUSE_TEST=1` will build the unittest
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file that has instructions for building dependencies.
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/unittest_windows.yml) which have instructions for building dependencies.
It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:
@ -40,6 +44,19 @@ It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/m
```
vcpkg install ixwebsocket
```
To use the installed package within a cmake project, use the following:
```cmake
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") # this is super important in order for cmake to include the vcpkg search/lib paths!
# find library and its headers
find_path(IXWEBSOCKET_INCLUDE_DIR ixwebsocket/IXWebSocket.h)
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
# include headers
include_directories(${IXWEBSOCKET_INCLUDE_DIR})
# ...
target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUND
```
### Conan
@ -59,7 +76,7 @@ Note that the version listed here might not be the latest one. See Bintray or th
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
docker run docker.pkg.github.com/machinezone/ixwebsocket/ws:latest --help
```
To use docker-compose you must make a docker container first.

View File

@ -1,81 +0,0 @@
## General
[cobra](https://github.com/machinezone/cobra) is a real time messaging server. The `ws` utility can run a cobra server (named snake), and has client to publish and subscribe to a cobra server.
Bring up 3 terminals and run a server, a publisher and a subscriber in each one. As you publish data you should see it being received by the subscriber. You can run `redis-cli MONITOR` too to see how redis is being used.
### Server
You will need to have a redis server running locally. To run the server:
```bash
$ cd <ixwebsocket-top-level-folder>/ixsnake/ixsnake
$ ws snake
{
"apps": {
"FC2F10139A2BAc53BB72D9db967b024f": {
"roles": {
"_sub": {
"secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba"
},
"_pub": {
"secret": "1c04DB8fFe76A4EeFE3E318C72d771db"
}
}
}
}
}
redis host: 127.0.0.1
redis password:
redis port: 6379
```
### Publisher
```bash
$ cd <ixwebsocket-top-level-folder>/ws
$ ws cobra_publish --appkey FC2F10139A2BAc53BB72D9db967b024f --endpoint ws://127.0.0.1:8008 --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db test_channel cobraMetricsSample.json
[2019-11-27 09:06:12.980] [info] Publisher connected
[2019-11-27 09:06:12.980] [info] Connection: Upgrade
[2019-11-27 09:06:12.980] [info] Sec-WebSocket-Accept: zTtQKMKbvwjdivURplYXwCVUCWM=
[2019-11-27 09:06:12.980] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
[2019-11-27 09:06:12.980] [info] Server: ixwebsocket/7.4.0 macos ssl/DarwinSSL zlib 1.2.11
[2019-11-27 09:06:12.980] [info] Upgrade: websocket
[2019-11-27 09:06:12.982] [info] Publisher authenticated
[2019-11-27 09:06:12.982] [info] Published msg 3
[2019-11-27 09:06:12.982] [info] Published message id 3 acked
```
### Subscriber
```bash
$ ws cobra_subscribe --appkey FC2F10139A2BAc53BB72D9db967b024f --endpoint ws://127.0.0.1:8008 --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db test_channel
#messages 0 msg/s 0
[2019-11-27 09:07:39.341] [info] Subscriber connected
[2019-11-27 09:07:39.341] [info] Connection: Upgrade
[2019-11-27 09:07:39.341] [info] Sec-WebSocket-Accept: 9vkQWofz49qMCUlTSptCCwHWm+Q=
[2019-11-27 09:07:39.341] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
[2019-11-27 09:07:39.341] [info] Server: ixwebsocket/7.4.0 macos ssl/DarwinSSL zlib 1.2.11
[2019-11-27 09:07:39.341] [info] Upgrade: websocket
[2019-11-27 09:07:39.342] [info] Subscriber authenticated
[2019-11-27 09:07:39.345] [info] Subscriber: subscribed to channel test_channel
#messages 0 msg/s 0
#messages 0 msg/s 0
#messages 0 msg/s 0
{"baz":123,"foo":"bar"}
#messages 1 msg/s 1
#messages 1 msg/s 0
#messages 1 msg/s 0
{"baz":123,"foo":"bar"}
{"baz":123,"foo":"bar"}
#messages 3 msg/s 2
#messages 3 msg/s 0
{"baz":123,"foo":"bar"}
#messages 4 msg/s 1
^C
```

View File

@ -12,7 +12,7 @@ If you are using OpenSSL, try to be on a version higher than 1.1.x as there ther
### Polling and background thread work
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes. However concurrent writes need to be protected with mutex.
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes.
### Automatic reconnection
@ -30,16 +30,9 @@ The unittest tries to be comprehensive, and has been running on multiple platfor
The regression test is running after each commit on github actions for multiple configurations.
* Linux
* macOS with thread sanitizer
* macOS, with OpenSSL, with thread sanitizer
* macOS, with MbedTLS, with thread sanitizer
* Windows, with MbedTLS (the unittest is not run yet)
## Limitations
* On Windows and Android certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. On Windows with mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
* There is no convenient way to embed a ca cert.
* On some configuration (mostly Android) certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. With mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.

View File

@ -1,56 +1,111 @@
![Build status](https://github.com/machinezone/IXWebSocket/workflows/unittest/badge.svg)
## Hello world
## Introduction
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client and server HTTP communication. *TLS* aka *SSL* is supported. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Note that the MinGW compiler is not supported at this point. Two important design goals are simplicity and correctness.
* macOS
* iOS
* Linux
* Android
* Windows
* FreeBSD
A bad security bug affecting users compiling with SSL enabled and OpenSSL as the backend was just fixed in newly released version 11.0.0. Please upgrade ! (more details in the [https://github.com/machinezone/IXWebSocket/pull/250](PR).
## Example code
```cpp
/*
* main.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
*
* On macOS
* $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install)
* $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
* $ ./a.out
*
* Or use cmake -DBUILD_DEMO=ON option for other platforms
*/
```c++
// Required on Windows
ix::initNetSystem();
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXUserAgent.h>
#include <iostream>
// Our websocket object
ix::WebSocket webSocket;
int main()
{
// Required on Windows
ix::initNetSystem();
std::string url("ws://localhost:8080/");
webSocket.setUrl(url);
// Our websocket object
ix::WebSocket webSocket;
// Setup a callback to be fired when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Message)
// Connect to a server with encryption
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
std::string url("wss://echo.websocket.org");
webSocket.setUrl(url);
std::cout << "Connecting to " << url << "..." << std::endl;
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
// when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
std::cout << msg->str << std::endl;
if (msg->type == ix::WebSocketMessageType::Message)
{
std::cout << "received message: " << msg->str << std::endl;
std::cout << "> " << std::flush;
}
else if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "Connection established" << std::endl;
std::cout << "> " << std::flush;
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
// Maybe SSL is not configured properly
std::cout << "Connection error: " << msg->errorInfo.reason << std::endl;
std::cout << "> " << std::flush;
}
}
);
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
// Send a message to the server (default to TEXT mode)
webSocket.send("hello world");
// Display a prompt
std::cout << "> " << std::flush;
std::string text;
// Read text from the console and send messages in text mode.
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
while (std::getline(std::cin, text))
{
webSocket.send(text);
std::cout << "> " << std::flush;
}
);
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
// Send a message to the server (default to TEXT mode)
webSocket.send("hello world");
return 0;
}
```
## Why another library?
Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
There are 2 main reasons that explain why IXWebSocket got written. First, we needed a C++ cross-platform client library, which should have few dependencies. What looked like the most solid one, [websocketpp](https://github.com/zaphoyd/websocketpp) did depend on boost and this was not an option for us. Secondly, there were other available libraries with fewer dependencies (C ones), but they required calling an explicit poll routine periodically to know if a client had received data from a server, which was not elegant.
IXWebSocket is actively being developed, check out the [changelog](https://machinezone.github.io/IXWebSocket/CHANGELOG/) to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at [cobra](https://github.com/machinezone/cobra).
We started by solving those 2 problems, then we added server websocket code, then an HTTP client, and finally a very simple HTTP server. IXWebSocket comes with a command line utility named ws which is quite handy, and is now packaged with alpine linux. You can install it with `apk add ws`.
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code.
* Few dependencies (only zlib)
* Simple to use ; uses std::string and std::function callbacks.
* Complete support of the websocket protocol, and basic http support.
* Client and Server
* TLS support
Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible.
## Users
If your company or project is using this library, feel free to open an issue or PR to amend this list.
- [Machine Zone](https://www.mz.com)
- [Tokio](https://gitlab.com/HCInk/tokio), a discord library focused on audio playback with node bindings.
- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework.
- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod
- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper
- [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots
- [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar
## Alternative libraries
@ -60,7 +115,35 @@ There are plenty of great websocket libraries out there, which might work for yo
* [beast](https://github.com/boostorg/beast) - C++
* [libwebsockets](https://libwebsockets.org/) - C
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C
* [wslay](https://github.com/tatsuhiro-t/wslay) - C
## Contributing
[uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv.
IXWebSocket is developed on [GitHub](https://github.com/machinezone/IXWebSocket). We'd love to hear about how you use it; opening up an issue on GitHub is ok for that. If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
To check the performance of a websocket library, you can look at the [autoroute](https://github.com/bsergean/autoroute) project.
## Continuous Integration
| OS | TLS | Sanitizer | Status |
|-------------------|-------------------|-------------------|-------------------|
| Linux | OpenSSL | None | [![Build2][1]][0] |
| macOS | Secure Transport | Thread Sanitizer | [![Build2][2]][0] |
| macOS | OpenSSL | Thread Sanitizer | [![Build2][3]][0] |
| macOS | MbedTLS | Thread Sanitizer | [![Build2][4]][0] |
| Windows | Disabled | None | [![Build2][5]][0] |
| UWP | Disabled | None | [![Build2][6]][0] |
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] |
| Mingw | Disabled | None | [![Build2][8]][0] |
* ASAN fails on Linux because of a known problem, we need a
* Some tests are disabled on Windows/UWP because of a pathing problem
* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.
[0]: https://github.com/machinezone/IXWebSocket
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg
[3]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg
[4]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg
[8]: https://github.com/machinezone/IXWebSocket/workflows/unittest_windows_gcc/badge.svg

View File

@ -4,7 +4,7 @@ Notes on how we can update the different packages for ixwebsocket.
Visit the [releases](https://github.com/machinezone/IXWebSocket/releases) page on Github. A tag must have been made first.
Download the latest entry.
Download the latest entry.
```
$ cd /tmp

37
docs/performance.md Normal file
View File

@ -0,0 +1,37 @@
## WebSocket Client performance
We will run a client and a server on the same machine, connecting to localhost. This bench is run on a MacBook Pro from 2015. We can receive over 200,000 (small) messages per second, another way to put it is that it takes 5 micro-second to receive and process one message. This is an indication about the minimal latency to receive messages.
### Receiving messages
By using the push_server ws sub-command, the server will send the same message in a loop to any connected client.
```
ws push_server -q --send_msg 'yo'
```
By using the echo_client ws sub-command, with the -m (mute or no_send), we will display statistics on how many messages we can receive per second.
```
$ ws echo_client -m ws://localhost:8008
[2020-08-02 12:31:17.284] [info] ws_echo_client: connected
[2020-08-02 12:31:17.284] [info] Uri: /
[2020-08-02 12:31:17.284] [info] Headers:
[2020-08-02 12:31:17.284] [info] Connection: Upgrade
[2020-08-02 12:31:17.284] [info] Sec-WebSocket-Accept: byy/pMK2d0PtRwExaaiOnXJTQHo=
[2020-08-02 12:31:17.284] [info] Server: ixwebsocket/10.1.4 macos ssl/SecureTransport zlib 1.2.11
[2020-08-02 12:31:17.284] [info] Upgrade: websocket
[2020-08-02 12:31:17.663] [info] messages received: 0 per second 2595307 total
[2020-08-02 12:31:18.668] [info] messages received: 79679 per second 2674986 total
[2020-08-02 12:31:19.668] [info] messages received: 207438 per second 2882424 total
[2020-08-02 12:31:20.673] [info] messages received: 209207 per second 3091631 total
[2020-08-02 12:31:21.676] [info] messages received: 216056 per second 3307687 total
[2020-08-02 12:31:22.680] [info] messages received: 214927 per second 3522614 total
[2020-08-02 12:31:23.684] [info] messages received: 216960 per second 3739574 total
[2020-08-02 12:31:24.688] [info] messages received: 215232 per second 3954806 total
[2020-08-02 12:31:25.691] [info] messages received: 212300 per second 4167106 total
[2020-08-02 12:31:26.694] [info] messages received: 212501 per second 4379607 total
[2020-08-02 12:31:27.699] [info] messages received: 212330 per second 4591937 total
[2020-08-02 12:31:28.702] [info] messages received: 216511 per second 4808448 total
```

View File

@ -67,9 +67,40 @@ webSocket.stop()
### Sending messages
`websocket.send("foo")` will send a message.
`WebSocketSendInfo result = websocket.send("foo")` will send a message.
If the connection was closed and sending failed, the return value will be set to false.
If the connection was closed, sending will fail, and the success field of the result object will be set to false. There could also be a compression error in which case the compressError field will be set to true. The payloadSize field and wireSize fields will tell you respectively how much bytes the message weight, and how many bytes were sent on the wire (potentially compressed + counting the message header (a few bytes).
There is an optional progress callback that can be passed in as the second argument. If a message is large it will be fragmented into chunks which will be sent independantly. Everytime the we can write a fragment into the OS network cache, the callback will be invoked. If a user wants to cancel a slow send, false should be returned from within the callback.
Here is an example code snippet copied from the ws send sub-command. Each fragment weights 32K, so the total integer is the wireSize divided by 32K. As an example if you are sending 32M of data, uncompressed, total will be 1000. current will be set to 0 for the first fragment, then 1, 2 etc...
```
auto result =
_webSocket.sendBinary(serializedMsg, [this, throttle](int current, int total) -> bool {
spdlog::info("ws_send: Step {} out of {}", current + 1, total);
if (throttle)
{
std::chrono::duration<double, std::milli> duration(10);
std::this_thread::sleep_for(duration);
}
return _connected;
});
```
The `send()` and `sendText()` methods check that the string contains only valid UTF-8 characters. If you know that the string is a valid UTF-8 string you can skip that step and use the `sendUtf8Text` method instead.
With the IXWebSocketSendData overloads of `sendUtf8Text` and `sendBinary` it is possible to not only send std::string but also `std::vector<char>`, `std::vector<uint8_t>` and `char*`.
```
std::vector<uint8_t> data({1, 2, 3, 4});
auto result = webSocket.sendBinary(data);
const char* text = "Hello World!";
result = webSocket.sendUtf8Text(IXWebSocketSendData(text, strlen(text)));
```
### ReadyState
@ -122,9 +153,9 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
std::stringstream ss;
ss << "Error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->eventInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->eventInfo.http_status << std::endl;
ss << "#retries: " << msg->errorInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
std::cout << ss.str() << std::endl;
}
}
@ -237,15 +268,32 @@ Wait time(ms): 6400
Wait time(ms): 10000
```
The waiting time is capped by default at 10s between 2 attempts, but that value can be changed and queried.
The waiting time is capped by default at 10s between 2 attempts, but that value
can be changed and queried. The minimum waiting time can also be set.
```cpp
webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s
uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
webSocket.setMinWaitBetweenReconnectionRetries(1000); // 1000ms = 1s
uint32_t m = webSocket.getMinWaitBetweenReconnectionRetries();
```
## Handshake timeout
You can control how long to wait until timing out while waiting for the websocket handshake to be performed.
```
int handshakeTimeoutSecs = 1;
setHandshakeTimeout(handshakeTimeoutSecs);
```
## WebSocket server API
### Legacy api
This api was actually changed to take a weak_ptr<WebSocket> as the first argument to setOnConnectionCallback ; previously it would take a shared_ptr<WebSocket> which was creating cycles and then memory leaks problems.
```cpp
#include <ixwebsocket/IXWebSocketServer.h>
@ -253,41 +301,53 @@ uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
// 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);
int port = 8008;
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
ix::WebSocketServer server(port, host);
server.setOnConnectionCallback(
[&server](std::shared_ptr<WebSocket> webSocket,
[&server](std::weak_ptr<WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
std::cout << "Remote ip: " << connectionState->remoteIp << std::endl;
auto ws = webSocket.lock();
if (ws)
{
ws->setOnMessageCallback(
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
{
std::cerr << "New connection" << std::endl;
// A connection state object is available, and has a default id
// You can subclass ConnectionState and pass an alternate factory
// to override it. It is useful if you want to store custom
// attributes per connection (authenticated bool flag, attributes, etc...)
std::cerr << "id: " << connectionState->getId() << std::endl;
// The uri the client did connect to.
std::cerr << "Uri: " << msg->openInfo.uri << std::endl;
std::cerr << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers)
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cerr << it.first << ": " << it.second << std::endl;
std::cout << "New connection" << std::endl;
// A connection state object is available, and has a default id
// You can subclass ConnectionState and pass an alternate factory
// to override it. It is useful if you want to store custom
// attributes per connection (authenticated bool flag, attributes, etc...)
std::cout << "id: " << connectionState->getId() << std::endl;
// The uri the client did connect to.
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
std::cout << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
// For an echo server, we just send back to the client whatever was received by the server
// All connected clients are available in an std::set. See the broadcast cpp example.
// Second parameter tells whether we are sending the message in binary or text mode.
// Here we send it in the same mode as it was received.
auto ws = webSocket.lock();
if (ws)
{
ws->send(msg->str, msg->binary);
}
}
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
// For an echo server, we just send back to the client whatever was received by the server
// All connected clients are available in an std::set. See the broadcast cpp example.
// Second parameter tells whether we are sending the message in binary or text mode.
// Here we send it in the same mode as it was received.
webSocket->send(msg->str, msg->binary);
}
}
);
@ -301,6 +361,10 @@ if (!res.first)
return 1;
}
// Per message deflate connection is enabled by default. It can be disabled
// which might be helpful when running on low power devices such as a Rasbery Pi
server.disablePerMessageDeflate();
// Run the server in the background. Server can be stoped by calling server.stop()
server.start();
@ -309,6 +373,89 @@ server.wait();
```
### New api
The new API does not require to use 2 nested callbacks, which is a bit annoying. The real fix is that there was a memory leak due to a shared_ptr cycle, due to passing down a shared_ptr<WebSocket> down to the callbacks.
The webSocket reference is guaranteed to be always valid ; by design the callback will never be invoked with a null webSocket object.
```cpp
#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.
int port = 8008;
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
ix::WebSocketServer server(port, host);
server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
// The ConnectionState object contains information about the connection,
// at this point only the client ip address and the port.
std::cout << "Remote ip: " << connectionState->getRemoteIp() << std::endl;
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "New connection" << std::endl;
// A connection state object is available, and has a default id
// You can subclass ConnectionState and pass an alternate factory
// to override it. It is useful if you want to store custom
// attributes per connection (authenticated bool flag, attributes, etc...)
std::cout << "id: " << connectionState->getId() << std::endl;
// The uri the client did connect to.
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
std::cout << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers)
{
std::cout << "\t" << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
// For an echo server, we just send back to the client whatever was received by the server
// All connected clients are available in an std::set. See the broadcast cpp example.
// Second parameter tells whether we are sending the message in binary or text mode.
// Here we send it in the same mode as it was received.
std::cout << "Received: " << msg->str << std::endl;
webSocket.send(msg->str, msg->binary);
}
});
auto res = server.listen();
if (!res.first)
{
// Error handling
return 1;
}
// Per message deflate connection is enabled by default. It can be disabled
// which might be helpful when running on low power devices such as a Rasbery Pi
server.disablePerMessageDeflate();
// Run the server in the background. Server can be stoped by calling server.stop()
server.start();
// Block until server.stop() is called.
server.wait();
```
### Heartbeat
You can configure an optional heartbeat / keep-alive for the WebSocket server. The heartbeat interval can be adjusted or disabled when constructing the `WebSocketServer`. Setting the interval to `-1` disables the heartbeat feature; this is the default setting. The parameter you set will be applied to every `WebSocket` object that the server creates.
To enable a 45 second heartbeat on a `WebSocketServer`:
```cpp
int pingIntervalSeconds = 45;
ix::WebSocketServer server(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily, pingIntervalSeconds);
```
## HTTP client API
```cpp
@ -358,18 +505,25 @@ out = httpClient.get(url, args);
// POST request with parameters
HttpParameters httpParameters;
httpParameters["foo"] = "bar";
out = httpClient.post(url, httpParameters, args);
// HTTP form data can be passed in as well, for multi-part upload of files
HttpFormDataParameters httpFormDataParameters;
httpParameters["baz"] = "booz";
out = httpClient.post(url, httpParameters, httpFormDataParameters, args);
// POST request with a body
out = httpClient.post(url, std::string("foo=bar"), args);
// PUT and PATCH are available too.
//
// Result
//
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
auto body = response->body; // All the bytes from the response as an std::string
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
auto uploadSize = response->uploadSize; // Byte count of uploaded data
auto downloadSize = response->downloadSize; // Byte count of downloaded data
@ -381,17 +535,32 @@ bool async = true;
HttpClient httpClient(async);
auto args = httpClient.createRequest(url, HttpClient::kGet);
// If you define a chunk callback it will be called repeteadly with the
// incoming data. This allows to process data on the go or write it to disk
// instead of accumulating the data in memory.
args.onChunkCallback = [](const std::string& data)
{
// process data
};
// Push the request to a queue,
bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
{
// This callback execute in a background thread. Make sure you uses appropriate protection such as mutex
auto statusCode = response->statusCode; // acess results
// response->body is empty if onChunkCallback was used
}
);
// ok will be false if your httpClient is not async
// A request in progress can be cancelled by setting the cancel flag. It does nothing if the request already completed.
args->cancel = true;
```
See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart.
## HTTP server API
```cpp
@ -415,11 +584,13 @@ If you want to handle how requests are processed, implement the setOnConnectionC
```cpp
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
// Build a string for the response
std::stringstream ss;
ss << request->method
ss << connectionState->getRemoteIp();
<< " "
<< request->method
<< " "
<< request->uri;
@ -447,7 +618,7 @@ Additional TLS options can be configured by passing a `ix::SocketTLSOptions` ins
webSocket.setTLSOptions({
.certFile = "path/to/cert/file.pem",
.keyFile = "path/to/key/file.pem",
.caFile = "path/to/trust/bundle/file.pem",
.caFile = "path/to/trust/bundle/file.pem", // as a file, or in memory buffer in PEM format
.tls = true // required in server mode
});
```
@ -461,9 +632,12 @@ On a server, this is necessary for TLS support.
Specifying `caFile` configures the trusted roots bundle file (in PEM format) that will be used to verify peer certificates.
- The special value of `SYSTEM` (the default) indicates that the system-configured trust bundle should be used; this is generally what you want when connecting to any publicly exposed API/server.
- The special value of `NONE` can be used to disable peer verification; this is only recommended to rule out certificate verification when testing connectivity.
- If the value contain the special value `-----BEGIN CERTIFICATE-----`, the value will be read from memory, and not from a file. This is convenient on platforms like Android where reading / writing to the file system can be challenging without proper permissions, or without knowing the location of a temp directory.
For a client, specifying `caFile` can be used if connecting to a server that uses a self-signed cert, or when using a custom CA in an internal environment.
For a server, specifying `caFile` implies that:
1. You require clients to present a certificate
1. It must be signed by one of the trusted roots in the file
By default, a destination's hostname is always validated against the certificate that it presents. To accept certificates with any hostname, set `ix::SocketTLSOptions::disable_hostname_validation` to `true`.

View File

@ -19,13 +19,6 @@ Subcommands:
broadcast_server Broadcasting server
ping Ping pong
curl HTTP Client
redis_publish Redis publisher
redis_subscribe Redis subscriber
cobra_subscribe Cobra subscriber
cobra_publish Cobra publisher
cobra_to_statsd Cobra to statsd
cobra_to_sentry Cobra to sentry
snake Snake server
httpd HTTP server
```
@ -195,6 +188,63 @@ Server: Python/3.7 websockets/8.0.2
Upgrade: websocket
```
It is possible to pass custom HTTP header when doing the connection handshake,
the remote server might process them to implement a simple authorization
scheme.
```
src$ ws connect -H Authorization:supersecret ws://localhost:8008
Type Ctrl-D to exit prompt...
[2020-12-17 22:35:08.732] [info] Authorization: supersecret
Connecting to url: ws://localhost:8008
> [2020-12-17 22:35:08.736] [info] ws_connect: connected
[2020-12-17 22:35:08.736] [info] Uri: /
[2020-12-17 22:35:08.736] [info] Headers:
[2020-12-17 22:35:08.736] [info] Connection: Upgrade
[2020-12-17 22:35:08.736] [info] Sec-WebSocket-Accept: 2yaTFcdwn8KL6IzSMj2u6Le7KTg=
[2020-12-17 22:35:08.736] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
[2020-12-17 22:35:08.736] [info] Server: ixwebsocket/11.0.4 macos ssl/SecureTransport zlib 1.2.11
[2020-12-17 22:35:08.736] [info] Upgrade: websocket
[2020-12-17 22:35:08.736] [info] Received 25 bytes
ws_connect: received message: Authorization suceeded!
[2020-12-17 22:35:08.736] [info] Received pong ixwebsocket::heartbeat::30s::0
hello
> [2020-12-17 22:35:25.157] [info] Received 7 bytes
ws_connect: received message: hello
```
If the wrong header is passed in, the server would close the connection with a custom close code (>4000, and <4999).
```
[2020-12-17 22:39:37.044] [info] Upgrade: websocket
ws_connect: connection closed: code 4001 reason Permission denied
```
## echo server
The ws echo server will respond what the client just sent him. If we use the
simple --http_authorization_header we can enforce that client need to pass a
special value in the Authorization header to connect.
```
$ ws echo_server --http_authorization_header supersecret
[2020-12-17 22:35:06.192] [info] Listening on 127.0.0.1:8008
[2020-12-17 22:35:08.735] [info] New connection
[2020-12-17 22:35:08.735] [info] remote ip: 127.0.0.1
[2020-12-17 22:35:08.735] [info] id: 0
[2020-12-17 22:35:08.735] [info] Uri: /
[2020-12-17 22:35:08.735] [info] Headers:
[2020-12-17 22:35:08.735] [info] Authorization: supersecret
[2020-12-17 22:35:08.735] [info] Connection: Upgrade
[2020-12-17 22:35:08.735] [info] Host: localhost:8008
[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Key: eFF2Gf25dC7eC15Ab1135G==
[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Version: 13
[2020-12-17 22:35:08.735] [info] Upgrade: websocket
[2020-12-17 22:35:08.735] [info] User-Agent: ixwebsocket/11.0.4 macos ssl/SecureTransport zlib 1.2.11
[2020-12-17 22:35:25.157] [info] Received 7 bytes
```
## Websocket proxy
```
@ -204,6 +254,20 @@ Listening on 127.0.0.1:8008
If you connect to ws://127.0.0.1:8008, the proxy will connect to ws://127.0.0.1:9000 and pass all traffic to this server.
You can also use a more complex setup if you want to redirect to different websocket servers based on the hostname your client is trying to connect to. If you have multiple CNAME aliases that point to the same server.
A JSON config file is used to express that mapping ; here connecting to echo.jeanserge.com will proxy the client to ws://localhost:8008 on the local machine (which actually runs ws echo_server), while connecting to bavarde.jeanserge.com will proxy the client to ws://localhost:5678 where a cobra python server is running. As a side note you will need a wildcard SSL certificate if you want to have SSL enabled on that machine.
```
echo.jeanserge.com=ws://localhost:8008
bavarde.jeanserge.com=ws://localhost:5678
```
The --config_path option is required to instruct ws proxy_server to read that file.
```
ws proxy_server --config_path proxyConfig.json --port 8765
```
## File transfer
```
@ -242,128 +306,3 @@ Options:
--connect-timeout INT Connection timeout
--transfer-timeout INT Transfer timeout
```
## Cobra client and server
[cobra](https://github.com/machinezone/cobra) is a real time messenging server. ws has several sub-command to interact with cobra. There is also a minimal cobra compatible server named snake available.
Below are examples on running a snake server and clients with TLS enabled (the server only works with the OpenSSL and the Mbed TLS backend for now).
First, generate certificates.
```
$ cd /path/to/IXWebSocket
$ cd ixsnake/ixsnake
$ bash ../../ws/generate_certs.sh
Generating RSA private key, 2048 bit long modulus
.....+++
.................+++
e is 65537 (0x10001)
generated ./.certs/trusted-ca-key.pem
generated ./.certs/trusted-ca-crt.pem
Generating RSA private key, 2048 bit long modulus
..+++
.......................................+++
e is 65537 (0x10001)
generated ./.certs/trusted-server-key.pem
Signature ok
subject=/O=machinezone/O=IXWebSocket/CN=trusted-server
Getting CA Private Key
generated ./.certs/trusted-server-crt.pem
Generating RSA private key, 2048 bit long modulus
...................................+++
..................................................+++
e is 65537 (0x10001)
generated ./.certs/trusted-client-key.pem
Signature ok
subject=/O=machinezone/O=IXWebSocket/CN=trusted-client
Getting CA Private Key
generated ./.certs/trusted-client-crt.pem
Generating RSA private key, 2048 bit long modulus
..............+++
.......................................+++
e is 65537 (0x10001)
generated ./.certs/untrusted-ca-key.pem
generated ./.certs/untrusted-ca-crt.pem
Generating RSA private key, 2048 bit long modulus
..........+++
................................................+++
e is 65537 (0x10001)
generated ./.certs/untrusted-client-key.pem
Signature ok
subject=/O=machinezone/O=IXWebSocket/CN=untrusted-client
Getting CA Private Key
generated ./.certs/untrusted-client-crt.pem
Generating RSA private key, 2048 bit long modulus
.....................................................................................+++
...........+++
e is 65537 (0x10001)
generated ./.certs/selfsigned-client-key.pem
Signature ok
subject=/O=machinezone/O=IXWebSocket/CN=selfsigned-client
Getting Private key
generated ./.certs/selfsigned-client-crt.pem
```
Now run the snake server.
```
$ export certs=.certs
$ ws snake --tls --port 8765 --cert-file ${certs}/trusted-server-crt.pem --key-file ${certs}/trusted-server-key.pem --ca-file ${certs}/trusted-ca-crt.pem
{
"apps": {
"FC2F10139A2BAc53BB72D9db967b024f": {
"roles": {
"_sub": {
"secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba"
},
"_pub": {
"secret": "1c04DB8fFe76A4EeFE3E318C72d771db"
}
}
}
}
}
redis host: 127.0.0.1
redis password:
redis port: 6379
```
As a new connection comes in, such output should be printed
```
[2019-12-19 20:27:19.724] [info] New connection
id: 0
Uri: /v2?appkey=_health
Headers:
Connection: Upgrade
Host: 127.0.0.1:8765
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
Sec-WebSocket-Key: d747B0fE61Db73f7Eh47c0==
Sec-WebSocket-Protocol: json
Sec-WebSocket-Version: 13
Upgrade: websocket
User-Agent: ixwebsocket/7.5.8 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11
```
To connect and publish a message, do:
```
$ export certs=.certs
$ cd /path/to/ws/folder
$ ls cobraMetricsSample.json
cobraMetricsSample.json
$ ws cobra_publish --endpoint wss://127.0.0.1:8765 --appkey FC2F10139A2BAc53BB72D9db967b024f --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db --channel foo --cert-file ${certs}/trusted-client-crt.pem --key-file ${certs}/trusted-client-key.pem --ca-file ${certs}/trusted-ca-crt.pem cobraMetricsSample.json
[2019-12-19 20:46:42.656] [info] Publisher connected
[2019-12-19 20:46:42.657] [info] Connection: Upgrade
[2019-12-19 20:46:42.657] [info] Sec-WebSocket-Accept: rs99IFThoBrhSg+k8G4ixH9yaq4=
[2019-12-19 20:46:42.657] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
[2019-12-19 20:46:42.657] [info] Server: ixwebsocket/7.5.8 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11
[2019-12-19 20:46:42.657] [info] Upgrade: websocket
[2019-12-19 20:46:42.658] [info] Publisher authenticated
[2019-12-19 20:46:42.658] [info] Published msg 3
[2019-12-19 20:46:42.659] [info] Published message id 3 acked
```
To use OpenSSL on macOS, compile with `make ws_openssl`. First you will have to install OpenSSL libraries, which can be done with Homebrew. Use `make ws_mbedtls` accordingly to use MbedTLS.

46
httpd.cpp Normal file
View File

@ -0,0 +1,46 @@
/*
* httpd.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*
* Buid with make httpd
*/
#include <ixwebsocket/IXHttpServer.h>
#include <sstream>
#include <iostream>
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0]
<< " <port> <host>" << std::endl;
std::cerr << " " << argv[0] << " 9090 127.0.0.1" << std::endl;
std::cerr << " " << argv[0] << " 9090 0.0.0.0" << std::endl;
return 1;
}
int port;
std::stringstream ss;
ss << argv[1];
ss >> port;
std::string hostname(argv[2]);
std::cout << "Listening on " << hostname
<< ":" << port << std::endl;
ix::HttpServer server(port, hostname);
auto res = server.listen();
if (!res.first)
{
std::cout << res.second << std::endl;
return 1;
}
server.start();
server.wait();
return 0;
}

View File

@ -1,49 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
#
set (IXBOTS_SOURCES
ixbots/IXCobraBot.cpp
ixbots/IXCobraToSentryBot.cpp
ixbots/IXCobraToStatsdBot.cpp
ixbots/IXCobraToStdoutBot.cpp
ixbots/IXQueueManager.cpp
ixbots/IXStatsdClient.cpp
)
set (IXBOTS_HEADERS
ixbots/IXCobraBot.h
ixbots/IXCobraToSentryBot.h
ixbots/IXCobraToStatsdBot.h
ixbots/IXCobraToStdoutBot.h
ixbots/IXQueueManager.h
ixbots/IXStatsdClient.h
)
add_library(ixbots STATIC
${IXBOTS_SOURCES}
${IXBOTS_HEADERS}
)
find_package(JsonCpp)
if (NOT JSONCPP_FOUND)
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
endif()
find_package(SpdLog)
if (NOT SPDLOG_FOUND)
set(SPDLOG_INCLUDE_DIRS ../third_party/spdlog/include)
endif()
set(IXBOTS_INCLUDE_DIRS
.
..
../ixcore
../ixwebsocket
../ixcobra
../ixsentry
${JSONCPP_INCLUDE_DIRS}
${SPDLOG_INCLUDE_DIRS})
target_include_directories( ixbots PUBLIC ${IXBOTS_INCLUDE_DIRS} )

View File

@ -1,303 +0,0 @@
/*
* IXCobraBot.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXCobraBot.h"
#include "IXQueueManager.h"
#include <algorithm>
#include <chrono>
#include <ixcobra/IXCobraConnection.h>
#include <spdlog/spdlog.h>
#include <sstream>
#include <thread>
#include <vector>
namespace ix
{
int64_t CobraBot::run(const CobraConfig& config,
const std::string& channel,
const std::string& filter,
const std::string& position,
bool verbose,
size_t maxQueueSize,
bool useQueue,
bool enableHeartbeat,
int runtime)
{
ix::CobraConnection conn;
conn.configure(config);
conn.connect();
Json::FastWriter jsonWriter;
std::atomic<uint64_t> sentCount(0);
std::atomic<uint64_t> receivedCount(0);
std::atomic<uint64_t> sentCountTotal(0);
std::atomic<uint64_t> receivedCountTotal(0);
std::atomic<uint64_t> sentCountPerSecs(0);
std::atomic<uint64_t> receivedCountPerSecs(0);
std::atomic<bool> stop(false);
std::atomic<bool> throttled(false);
std::atomic<bool> fatalCobraError(false);
QueueManager queueManager(maxQueueSize);
auto timer = [&sentCount,
&receivedCount,
&sentCountTotal,
&receivedCountTotal,
&sentCountPerSecs,
&receivedCountPerSecs,
&stop] {
while (!stop)
{
//
// We cannot write to sentCount and receivedCount
// as those are used externally, so we need to introduce
// our own counters
//
spdlog::info("messages received {} {} sent {} {}",
receivedCountPerSecs,
receivedCountTotal,
sentCountPerSecs,
sentCountTotal);
receivedCountPerSecs = receivedCount - receivedCountTotal;
sentCountPerSecs = sentCount - receivedCountTotal;
receivedCountTotal += receivedCountPerSecs;
sentCountTotal += sentCountPerSecs;
auto duration = std::chrono::seconds(1);
std::this_thread::sleep_for(duration);
}
spdlog::info("timer thread done");
};
std::thread t1(timer);
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat] {
std::string state("na");
if (!enableHeartbeat) return;
while (!stop)
{
std::stringstream ss;
ss << "messages received " << receivedCount;
ss << "messages sent " << sentCount;
std::string currentState = ss.str();
if (currentState == state)
{
spdlog::error("no messages received or sent for 1 minute, exiting");
exit(1);
}
state = currentState;
auto duration = std::chrono::minutes(1);
std::this_thread::sleep_for(duration);
}
spdlog::info("heartbeat thread done");
};
std::thread t2(heartbeat);
auto sender =
[this, &queueManager, verbose, &sentCount, &stop, &throttled, &fatalCobraError] {
while (true)
{
auto data = queueManager.pop();
Json::Value msg = data.first;
std::string position = data.second;
if (stop) break;
if (msg.isNull()) continue;
if (_onBotMessageCallback && _onBotMessageCallback(msg, position, verbose, throttled, fatalCobraError))
{
// That might be too noisy
if (verbose)
{
spdlog::info("cobra bot: sending succesfull");
}
++sentCount;
}
else
{
spdlog::error("cobra bot: error sending");
}
if (stop) break;
}
spdlog::info("sender thread done");
};
std::thread t3(sender);
std::string subscriptionPosition(position);
conn.setEventCallback([this,
&conn,
&channel,
&filter,
&subscriptionPosition,
&jsonWriter,
verbose,
&throttled,
&receivedCount,
&fatalCobraError,
&useQueue,
&queueManager,
&sentCount](const CobraEventPtr& event)
{
if (event->type == ix::CobraEventType::Open)
{
spdlog::info("Subscriber connected");
for (auto&& it : event->headers)
{
spdlog::info("{}: {}", it.first, it.second);
}
}
else if (event->type == ix::CobraEventType::Closed)
{
spdlog::info("Subscriber closed: {}", event->errMsg);
}
else if (event->type == ix::CobraEventType::Authenticated)
{
spdlog::info("Subscriber authenticated");
spdlog::info("Subscribing to {} at position {}", channel, subscriptionPosition);
spdlog::info("Using filter: {}", filter);
conn.subscribe(channel,
filter,
subscriptionPosition,
[this, &jsonWriter, verbose, &throttled, &receivedCount, &queueManager, &useQueue, &subscriptionPosition, &fatalCobraError, &sentCount](
const Json::Value& msg, const std::string& position) {
if (verbose)
{
spdlog::info("Subscriber received message {} -> {}", position, jsonWriter.write(msg));
}
subscriptionPosition = position;
// If we cannot send to sentry fast enough, drop the message
if (throttled)
{
return;
}
++receivedCount;
if (useQueue)
{
queueManager.add(msg, position);
}
else
{
if (_onBotMessageCallback && _onBotMessageCallback(msg, position, verbose, throttled, fatalCobraError))
{
// That might be too noisy
if (verbose)
{
spdlog::info("cobra bot: sending succesfull");
}
++sentCount;
}
else
{
spdlog::error("cobra bot: error sending");
}
}
});
}
else if (event->type == ix::CobraEventType::Subscribed)
{
spdlog::info("Subscriber: subscribed to channel {}", event->subscriptionId);
}
else if (event->type == ix::CobraEventType::UnSubscribed)
{
spdlog::info("Subscriber: unsubscribed from channel {}", event->subscriptionId);
}
else if (event->type == ix::CobraEventType::Error)
{
spdlog::error("Subscriber: error {}", event->errMsg);
}
else if (event->type == ix::CobraEventType::Published)
{
spdlog::error("Published message hacked: {}", event->msgId);
}
else if (event->type == ix::CobraEventType::Pong)
{
spdlog::info("Received websocket pong: {}", event->errMsg);
}
else if (event->type == ix::CobraEventType::HandshakeError)
{
spdlog::error("Subscriber: Handshake error: {}", event->errMsg);
fatalCobraError = true;
}
else if (event->type == ix::CobraEventType::AuthenticationError)
{
spdlog::error("Subscriber: Authentication error: {}", event->errMsg);
fatalCobraError = true;
}
else if (event->type == ix::CobraEventType::SubscriptionError)
{
spdlog::error("Subscriber: Subscription error: {}", event->errMsg);
fatalCobraError = true;
}
});
// Run forever
if (runtime == -1)
{
while (true)
{
auto duration = std::chrono::seconds(1);
std::this_thread::sleep_for(duration);
if (fatalCobraError) break;
}
}
// Run for a duration, used by unittesting now
else
{
for (int i = 0 ; i < runtime; ++i)
{
auto duration = std::chrono::seconds(1);
std::this_thread::sleep_for(duration);
if (fatalCobraError) break;
}
}
//
// Cleanup.
// join all the bg threads and stop them.
//
conn.disconnect();
stop = true;
// progress thread
t1.join();
// heartbeat thread
if (t2.joinable()) t2.join();
// sentry sender thread
t3.join();
return fatalCobraError ? -1 : (int64_t) sentCount;
}
void CobraBot::setOnBotMessageCallback(const OnBotMessageCallback& callback)
{
_onBotMessageCallback = callback;
}
}

View File

@ -1,43 +0,0 @@
/*
* IXCobraBot.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <ixcobra/IXCobraConfig.h>
#include <stddef.h>
#include <json/json.h>
#include <functional>
#include <atomic>
namespace ix
{
using OnBotMessageCallback = std::function<bool(const Json::Value&,
const std::string&,
const bool verbose,
std::atomic<bool>&,
std::atomic<bool>&)>;
class CobraBot
{
public:
CobraBot() = default;
int64_t run(const CobraConfig& config,
const std::string& channel,
const std::string& filter,
const std::string& position,
bool verbose,
size_t maxQueueSize,
bool useQueue,
bool enableHeartbeat,
int runtime);
void setOnBotMessageCallback(const OnBotMessageCallback& callback);
private:
OnBotMessageCallback _onBotMessageCallback;
};
}

View File

@ -1,117 +0,0 @@
/*
* IXCobraToSentryBot.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXCobraToSentryBot.h"
#include "IXCobraBot.h"
#include "IXQueueManager.h"
#include <chrono>
#include <ixcobra/IXCobraConnection.h>
#include <spdlog/spdlog.h>
#include <sstream>
#include <vector>
namespace ix
{
int64_t cobra_to_sentry_bot(const CobraConfig& config,
const std::string& channel,
const std::string& filter,
const std::string& position,
SentryClient& sentryClient,
bool verbose,
size_t maxQueueSize,
bool enableHeartbeat,
int runtime)
{
CobraBot bot;
bot.setOnBotMessageCallback([&sentryClient](const Json::Value& msg,
const std::string& /*position*/,
const bool verbose,
std::atomic<bool>& throttled,
std::atomic<bool>& /*fatalCobraError*/) -> bool {
auto ret = sentryClient.send(msg, verbose);
HttpResponsePtr response = ret.first;
if (!response)
{
spdlog::warn("Null HTTP Response");
return false;
}
if (verbose)
{
for (auto it : response->headers)
{
spdlog::info("{}: {}", it.first, it.second);
}
spdlog::info("Upload size: {}", response->uploadSize);
spdlog::info("Download size: {}", response->downloadSize);
spdlog::info("Status: {}", response->statusCode);
if (response->errorCode != HttpErrorCode::Ok)
{
spdlog::info("error message: {}", response->errorMsg);
}
if (response->headers["Content-Type"] != "application/octet-stream")
{
spdlog::info("payload: {}", response->payload);
}
}
bool success = response->statusCode == 200;
if (!success)
{
spdlog::error("Error sending data to sentry: {}", response->statusCode);
spdlog::error("Body: {}", ret.second);
spdlog::error("Response: {}", response->payload);
// Error 429 Too Many Requests
if (response->statusCode == 429)
{
auto retryAfter = response->headers["Retry-After"];
std::stringstream ss;
ss << retryAfter;
int seconds;
ss >> seconds;
if (!ss.eof() || ss.fail())
{
seconds = 30;
spdlog::warn("Error parsing Retry-After header. "
"Using {} for the sleep duration",
seconds);
}
spdlog::warn("Error 429 - Too Many Requests. ws will sleep "
"and retry after {} seconds",
retryAfter);
throttled = true;
auto duration = std::chrono::seconds(seconds);
std::this_thread::sleep_for(duration);
throttled = false;
}
}
return success;
});
bool useQueue = true;
return bot.run(config,
channel,
filter,
position,
verbose,
maxQueueSize,
useQueue,
enableHeartbeat,
runtime);
}
} // namespace ix

View File

@ -1,24 +0,0 @@
/*
* IXCobraToSentryBot.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <ixcobra/IXCobraConfig.h>
#include <ixsentry/IXSentryClient.h>
#include <string>
#include <cstdint>
namespace ix
{
int64_t cobra_to_sentry_bot(const CobraConfig& config,
const std::string& channel,
const std::string& filter,
const std::string& position,
SentryClient& sentryClient,
bool verbose,
size_t maxQueueSize,
bool enableHeartbeat,
int runtime);
} // namespace ix

View File

@ -1,157 +0,0 @@
/*
* IXCobraToStatsdBot.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXCobraToStatsdBot.h"
#include "IXQueueManager.h"
#include "IXStatsdClient.h"
#include "IXCobraBot.h"
#include <chrono>
#include <ixcobra/IXCobraConnection.h>
#include <spdlog/spdlog.h>
#include <sstream>
#include <vector>
namespace ix
{
// fields are command line argument that can be specified multiple times
std::vector<std::string> parseFields(const std::string& fields)
{
std::vector<std::string> tokens;
// Split by \n
std::string token;
std::stringstream tokenStream(fields);
while (std::getline(tokenStream, token))
{
tokens.push_back(token);
}
return tokens;
}
//
// Extract an attribute from a Json Value.
// extractAttr("foo.bar", {"foo": {"bar": "baz"}}) => baz
//
Json::Value extractAttr(const std::string& attr, const Json::Value& jsonValue)
{
// Split by .
std::string token;
std::stringstream tokenStream(attr);
Json::Value val(jsonValue);
while (std::getline(tokenStream, token, '.'))
{
val = val[token];
}
return val;
}
int64_t cobra_to_statsd_bot(const ix::CobraConfig& config,
const std::string& channel,
const std::string& filter,
const std::string& position,
StatsdClient& statsdClient,
const std::string& fields,
const std::string& gauge,
const std::string& timer,
bool verbose,
size_t maxQueueSize,
bool enableHeartbeat,
int runtime)
{
ix::CobraConnection conn;
conn.configure(config);
conn.connect();
auto tokens = parseFields(fields);
CobraBot bot;
bot.setOnBotMessageCallback([&statsdClient, &tokens, &gauge, &timer](const Json::Value& msg,
const std::string& /*position*/,
const bool verbose,
std::atomic<bool>& /*throttled*/,
std::atomic<bool>& fatalCobraError) -> bool {
std::string id;
for (auto&& attr : tokens)
{
id += ".";
auto val = extractAttr(attr, msg);
id += val.asString();
}
if (gauge.empty() && timer.empty())
{
statsdClient.count(id, 1);
}
else
{
std::string attrName = (!gauge.empty()) ? gauge : timer;
auto val = extractAttr(attrName, msg);
size_t x;
if (val.isInt())
{
x = (size_t) val.asInt();
}
else if (val.isInt64())
{
x = (size_t) val.asInt64();
}
else if (val.isUInt())
{
x = (size_t) val.asUInt();
}
else if (val.isUInt64())
{
x = (size_t) val.asUInt64();
}
else if (val.isDouble())
{
x = (size_t) val.asUInt64();
}
else
{
spdlog::error("Gauge {} is not a numeric type", gauge);
fatalCobraError = true;
return false;
}
if (verbose)
{
spdlog::info("{} - {} -> {}", id, attrName, x);
}
if (!gauge.empty())
{
statsdClient.gauge(id, x);
}
else
{
statsdClient.timing(id, x);
}
}
return true;
});
bool useQueue = true;
return bot.run(config,
channel,
filter,
position,
verbose,
maxQueueSize,
useQueue,
enableHeartbeat,
runtime);
}
} // namespace ix

View File

@ -1,28 +0,0 @@
/*
* IXCobraToStatsdBot.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <ixcobra/IXCobraConfig.h>
#include <ixbots/IXStatsdClient.h>
#include <string>
#include <stddef.h>
#include <cstdint>
namespace ix
{
int64_t cobra_to_statsd_bot(const ix::CobraConfig& config,
const std::string& channel,
const std::string& filter,
const std::string& position,
StatsdClient& statsdClient,
const std::string& fields,
const std::string& gauge,
const std::string& timer,
bool verbose,
size_t maxQueueSize,
bool enableHeartbeat,
int runtime);
} // namespace ix

View File

@ -1,106 +0,0 @@
/*
* IXCobraToStdoutBot.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXCobraToStdoutBot.h"
#include "IXCobraBot.h"
#include "IXQueueManager.h"
#include <chrono>
#include <spdlog/spdlog.h>
#include <sstream>
#include <iostream>
namespace ix
{
using StreamWriterPtr = std::unique_ptr<Json::StreamWriter>;
StreamWriterPtr makeStreamWriter()
{
Json::StreamWriterBuilder builder;
builder["commentStyle"] = "None";
builder["indentation"] = ""; // will make the JSON object compact
std::unique_ptr<Json::StreamWriter> jsonWriter(builder.newStreamWriter());
return jsonWriter;
}
std::string timeSinceEpoch()
{
std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();
std::chrono::system_clock::duration dtn = tp.time_since_epoch();
std::stringstream ss;
ss << dtn.count() * std::chrono::system_clock::period::num /
std::chrono::system_clock::period::den;
return ss.str();
}
void writeToStdout(bool fluentd,
const StreamWriterPtr& jsonWriter,
const Json::Value& msg,
const std::string& position)
{
Json::Value enveloppe;
if (fluentd)
{
enveloppe["producer"] = "cobra";
enveloppe["consumer"] = "fluentd";
Json::Value nestedMessage(msg);
nestedMessage["position"] = position;
nestedMessage["created_at"] = timeSinceEpoch();
enveloppe["message"] = nestedMessage;
jsonWriter->write(enveloppe, &std::cout);
std::cout << std::endl; // add lf and flush
}
else
{
enveloppe = msg;
std::cout << position << " ";
jsonWriter->write(enveloppe, &std::cout);
std::cout << std::endl;
}
}
int64_t cobra_to_stdout_bot(const CobraConfig& config,
const std::string& channel,
const std::string& filter,
const std::string& position,
bool fluentd,
bool quiet,
bool verbose,
size_t maxQueueSize,
bool enableHeartbeat,
int runtime)
{
CobraBot bot;
auto jsonWriter = makeStreamWriter();
bot.setOnBotMessageCallback([&fluentd, &quiet, &jsonWriter](const Json::Value& msg,
const std::string& position,
const bool /*verbose*/,
std::atomic<bool>& /*throttled*/,
std::atomic<bool>& /*fatalCobraError*/) -> bool {
if (!quiet)
{
writeToStdout(fluentd, jsonWriter, msg, position);
}
return true;
});
bool useQueue = false;
return bot.run(config,
channel,
filter,
position,
verbose,
maxQueueSize,
useQueue,
enableHeartbeat,
runtime);
}
} // namespace ix

View File

@ -1,25 +0,0 @@
/*
* IXCobraToStdoutBot.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <ixcobra/IXCobraConfig.h>
#include <string>
#include <stddef.h>
#include <cstdint>
namespace ix
{
int64_t cobra_to_stdout_bot(const ix::CobraConfig& config,
const std::string& channel,
const std::string& filter,
const std::string& position,
bool fluentd,
bool quiet,
bool verbose,
size_t maxQueueSize,
bool enableHeartbeat,
int runtime);
} // namespace ix

View File

@ -1,66 +0,0 @@
/*
* IXQueueManager.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXQueueManager.h"
#include <vector>
#include <algorithm>
namespace ix
{
std::pair<Json::Value, std::string> QueueManager::pop()
{
std::unique_lock<std::mutex> lock(_mutex);
if (_queues.empty())
{
Json::Value val;
return std::make_pair(val, std::string());
}
std::vector<std::string> games;
for (auto it : _queues)
{
games.push_back(it.first);
}
std::random_shuffle(games.begin(), games.end());
std::string game = games[0];
auto duration = std::chrono::seconds(1);
_condition.wait_for(lock, duration);
if (_queues[game].empty())
{
Json::Value val;
return std::make_pair(val, std::string());
}
auto msg = _queues[game].front();
_queues[game].pop();
return msg;
}
void QueueManager::add(const Json::Value& msg, const std::string& position)
{
std::unique_lock<std::mutex> lock(_mutex);
std::string game;
if (msg.isMember("device") && msg["device"].isMember("game"))
{
game = msg["device"]["game"].asString();
}
if (game.empty()) return;
// if the sending is not fast enough there is no point
// in queuing too many events.
if (_queues[game].size() < _maxQueueSize)
{
_queues[game].push(std::make_pair(msg, position));
_condition.notify_one();
}
}
}

View File

@ -1,35 +0,0 @@
/*
* IXQueueManager.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <stddef.h>
#include <json/json.h>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <map>
namespace ix
{
class QueueManager
{
public:
QueueManager(size_t maxQueueSize)
: _maxQueueSize(maxQueueSize)
{
}
std::pair<Json::Value, std::string> pop();
void add(const Json::Value& msg, const std::string& position);
private:
std::map<std::string, std::queue<std::pair<Json::Value, std::string>>> _queues;
std::mutex _mutex;
std::condition_variable _condition;
size_t _maxQueueSize;
};
}

View File

@ -1,152 +0,0 @@
/*
* Copyright (c) 2014, Rex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the {organization} nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* IXStatsdClient.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
// Adapted from statsd-client-cpp
// test with netcat as a server: `nc -ul 8125`
#include "IXStatsdClient.h"
#include <ixwebsocket/IXNetSystem.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <iostream>
namespace ix
{
StatsdClient::StatsdClient(const std::string& host,
int port,
const std::string& prefix)
: _host(host)
, _port(port)
, _prefix(prefix)
, _stop(false)
{
_thread = std::thread([this]
{
while (!_stop)
{
flushQueue();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
StatsdClient::~StatsdClient()
{
_stop = true;
if (_thread.joinable()) _thread.join();
_socket.close();
}
bool StatsdClient::init(std::string& errMsg)
{
return _socket.init(_host, _port, errMsg);
}
/* will change the original string */
void StatsdClient::cleanup(std::string& key)
{
size_t pos = key.find_first_of(":|@");
while (pos != std::string::npos)
{
key[pos] = '_';
pos = key.find_first_of(":|@");
}
}
int StatsdClient::dec(const std::string& key)
{
return count(key, -1);
}
int StatsdClient::inc(const std::string& key)
{
return count(key, 1);
}
int StatsdClient::count(const std::string& key, size_t value)
{
return send(key, value, "c");
}
int StatsdClient::gauge(const std::string& key, size_t value)
{
return send(key, value, "g");
}
int StatsdClient::timing(const std::string& key, size_t ms)
{
return send(key, ms, "ms");
}
int StatsdClient::send(std::string key, size_t value, const std::string& type)
{
cleanup(key);
char buf[256];
snprintf(buf, sizeof(buf), "%s%s:%zd|%s\n",
_prefix.c_str(), key.c_str(), value, type.c_str());
enqueue(buf);
return 0;
}
void StatsdClient::enqueue(const std::string& message)
{
std::lock_guard<std::mutex> lock(_mutex);
_queue.push_back(message);
}
void StatsdClient::flushQueue()
{
std::lock_guard<std::mutex> lock(_mutex);
while (!_queue.empty())
{
auto message = _queue.front();
auto ret = _socket.sendto(message);
if (ret != 0)
{
std::cerr << "error: "
<< strerror(UdpSocket::getErrno())
<< std::endl;
}
_queue.pop_front();
}
}
} // end namespace ix

View File

@ -1,58 +0,0 @@
/*
* IXStatsdClient.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <ixwebsocket/IXUdpSocket.h>
#include <string>
#include <thread>
#include <deque>
#include <mutex>
#include <atomic>
namespace ix
{
class StatsdClient
{
public:
StatsdClient(const std::string& host="127.0.0.1",
int port=8125,
const std::string& prefix = "");
~StatsdClient();
bool init(std::string& errMsg);
int inc(const std::string& key);
int dec(const std::string& key);
int count(const std::string& key, size_t value);
int gauge(const std::string& key, size_t value);
int timing(const std::string& key, size_t ms);
private:
void enqueue(const std::string& message);
/* (Low Level Api) manually send a message
* type = "c", "g" or "ms"
*/
int send(std::string key, size_t value, const std::string& type);
void cleanup(std::string& key);
void flushQueue();
UdpSocket _socket;
std::string _host;
int _port;
std::string _prefix;
std::atomic<bool> _stop;
std::thread _thread;
std::mutex _mutex; // for the queue
std::deque<std::string> _queue;
};
} // end namespace ix

View File

@ -1,37 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
#
set (IXCOBRA_SOURCES
ixcobra/IXCobraConnection.cpp
ixcobra/IXCobraMetricsThreadedPublisher.cpp
ixcobra/IXCobraMetricsPublisher.cpp
)
set (IXCOBRA_HEADERS
ixcobra/IXCobraConnection.h
ixcobra/IXCobraMetricsThreadedPublisher.h
ixcobra/IXCobraMetricsPublisher.h
ixcobra/IXCobraConfig.h
ixcobra/IXCobraEventType.h
)
add_library(ixcobra STATIC
${IXCOBRA_SOURCES}
${IXCOBRA_HEADERS}
)
find_package(JsonCpp)
if (NOT JSONCPP_FOUND)
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
endif()
set(IXCOBRA_INCLUDE_DIRS
.
..
../ixcore
../ixcrypto
${JSONCPP_INCLUDE_DIRS})
target_include_directories( ixcobra PUBLIC ${IXCOBRA_INCLUDE_DIRS} )

View File

@ -1,35 +0,0 @@
/*
* IXCobraConfig.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
#include <ixwebsocket/IXSocketTLSOptions.h>
namespace ix
{
struct CobraConfig
{
std::string appkey;
std::string endpoint;
std::string rolename;
std::string rolesecret;
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions;
SocketTLSOptions socketTLSOptions;
CobraConfig(const std::string& a = std::string(),
const std::string& e = std::string(),
const std::string& r = std::string(),
const std::string& s = std::string())
: appkey(a)
, endpoint(e)
, rolename(r)
, rolesecret(s)
{
;
}
};
} // namespace ix

View File

@ -1,685 +0,0 @@
/*
* IXCobraConnection.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone. All rights reserved.
*/
#include "IXCobraConnection.h"
#include <ixcrypto/IXHMac.h>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <algorithm>
#include <stdexcept>
#include <cmath>
#include <cassert>
#include <cstring>
#include <iostream>
#include <sstream>
namespace ix
{
TrafficTrackerCallback CobraConnection::_trafficTrackerCallback = nullptr;
PublishTrackerCallback CobraConnection::_publishTrackerCallback = nullptr;
constexpr size_t CobraConnection::kQueueMaxSize;
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
constexpr int CobraConnection::kPingIntervalSecs;
CobraConnection::CobraConnection() :
_webSocket(new WebSocket()),
_publishMode(CobraConnection_PublishMode_Immediate),
_authenticated(false),
_eventCallback(nullptr),
_id(1)
{
_pdu["action"] = "rtm/publish";
_webSocket->addSubProtocol("json");
initWebSocketOnMessageCallback();
}
CobraConnection::~CobraConnection()
{
disconnect();
setEventCallback(nullptr);
}
void CobraConnection::setTrafficTrackerCallback(const TrafficTrackerCallback& callback)
{
_trafficTrackerCallback = callback;
}
void CobraConnection::resetTrafficTrackerCallback()
{
setTrafficTrackerCallback(nullptr);
}
void CobraConnection::invokeTrafficTrackerCallback(size_t size, bool incoming)
{
if (_trafficTrackerCallback)
{
_trafficTrackerCallback(size, incoming);
}
}
void CobraConnection::setPublishTrackerCallback(const PublishTrackerCallback& callback)
{
_publishTrackerCallback = callback;
}
void CobraConnection::resetPublishTrackerCallback()
{
setPublishTrackerCallback(nullptr);
}
void CobraConnection::invokePublishTrackerCallback(bool sent, bool acked)
{
if (_publishTrackerCallback)
{
_publishTrackerCallback(sent, acked);
}
}
void CobraConnection::setEventCallback(const EventCallback& eventCallback)
{
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
_eventCallback = eventCallback;
}
void CobraConnection::invokeEventCallback(ix::CobraEventType eventType,
const std::string& errorMsg,
const WebSocketHttpHeaders& headers,
const std::string& subscriptionId,
CobraConnection::MsgId msgId)
{
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
if (_eventCallback)
{
_eventCallback(
std::make_unique<CobraEvent>(eventType,
errorMsg,
headers,
subscriptionId,
msgId));
}
}
void CobraConnection::invokeErrorCallback(const std::string& errorMsg,
const std::string& serializedPdu)
{
std::stringstream ss;
ss << errorMsg << " : received pdu => " << serializedPdu;
invokeEventCallback(ix::CobraEventType::Error, ss.str());
}
void CobraConnection::disconnect()
{
_authenticated = false;
_webSocket->stop();
}
void CobraConnection::initWebSocketOnMessageCallback()
{
_webSocket->setOnMessageCallback(
[this](const ix::WebSocketMessagePtr& msg)
{
CobraConnection::invokeTrafficTrackerCallback(msg->wireSize, true);
std::stringstream ss;
if (msg->type == ix::WebSocketMessageType::Open)
{
invokeEventCallback(ix::CobraEventType::Open,
std::string(),
msg->openInfo.headers);
sendHandshakeMessage();
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
_authenticated = false;
std::stringstream ss;
ss << "Close code " << msg->closeInfo.code;
ss << " reason " << msg->closeInfo.reason;
invokeEventCallback(ix::CobraEventType::Closed,
ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
Json::Value data;
Json::Reader reader;
if (!reader.parse(msg->str, data))
{
invokeErrorCallback("Invalid json", msg->str);
return;
}
if (!data.isMember("action"))
{
invokeErrorCallback("Missing action", msg->str);
return;
}
auto action = data["action"].asString();
if (action == "auth/handshake/ok")
{
if (!handleHandshakeResponse(data))
{
invokeErrorCallback("Error extracting nonce from handshake response", msg->str);
}
}
else if (action == "auth/handshake/error")
{
invokeEventCallback(ix::CobraEventType::HandshakeError,
msg->str);
}
else if (action == "auth/authenticate/ok")
{
_authenticated = true;
invokeEventCallback(ix::CobraEventType::Authenticated);
flushQueue();
}
else if (action == "auth/authenticate/error")
{
invokeEventCallback(ix::CobraEventType::AuthenticationError,
msg->str);
}
else if (action == "rtm/subscription/data")
{
handleSubscriptionData(data);
}
else if (action == "rtm/subscribe/ok")
{
if (!handleSubscriptionResponse(data))
{
invokeErrorCallback("Error processing subscribe response", msg->str);
}
}
else if (action == "rtm/subscribe/error")
{
invokeEventCallback(ix::CobraEventType::SubscriptionError,
msg->str);
}
else if (action == "rtm/unsubscribe/ok")
{
if (!handleUnsubscriptionResponse(data))
{
invokeErrorCallback("Error processing unsubscribe response", msg->str);
}
}
else if (action == "rtm/unsubscribe/error")
{
invokeErrorCallback("Unsubscription error", msg->str);
}
else if (action == "rtm/publish/ok")
{
if (!handlePublishResponse(data))
{
invokeErrorCallback("Error processing publish response", msg->str);
}
}
else if (action == "rtm/publish/error")
{
invokeErrorCallback("Publish error", msg->str);
}
else
{
invokeErrorCallback("Un-handled message type", msg->str);
}
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
std::stringstream ss;
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->errorInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
invokeErrorCallback(ss.str(), std::string());
}
else if (msg->type == ix::WebSocketMessageType::Pong)
{
invokeEventCallback(ix::CobraEventType::Pong, msg->str);
}
});
}
void CobraConnection::setPublishMode(CobraConnectionPublishMode publishMode)
{
_publishMode = publishMode;
}
CobraConnectionPublishMode CobraConnection::getPublishMode()
{
return _publishMode;
}
void CobraConnection::configure(const std::string& appkey,
const std::string& endpoint,
const std::string& rolename,
const std::string& rolesecret,
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
const SocketTLSOptions& socketTLSOptions)
{
_roleName = rolename;
_roleSecret = rolesecret;
std::stringstream ss;
ss << endpoint;
ss << "/v2?appkey=";
ss << appkey;
std::string url = ss.str();
_webSocket->setUrl(url);
_webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
_webSocket->setTLSOptions(socketTLSOptions);
// Send a websocket ping every N seconds (N = 30) now
// This should keep the connection open and prevent some load balancers such as
// the Amazon one from shutting it down
_webSocket->setPingInterval(kPingIntervalSecs);
}
void CobraConnection::configure(const ix::CobraConfig& config)
{
configure(config.appkey,
config.endpoint,
config.rolename,
config.rolesecret,
config.webSocketPerMessageDeflateOptions,
config.socketTLSOptions);
}
//
// Handshake message schema.
//
// handshake = {
// "action": "auth/handshake",
// "body": {
// "data": {
// "role": role
// },
// "method": "role_secret"
// },
// }
//
//
bool CobraConnection::sendHandshakeMessage()
{
Json::Value data;
data["role"] = _roleName;
Json::Value body;
body["data"] = data;
body["method"] = "role_secret";
Json::Value pdu;
pdu["action"] = "auth/handshake";
pdu["body"] = body;
pdu["id"] = Json::UInt64(_id++);
std::string serializedJson = serializeJson(pdu);
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
return _webSocket->send(serializedJson).success;
}
//
// Extract the nonce from the handshake response
// use it to compute a hash during authentication
//
// {
// "action": "auth/handshake/ok",
// "body": {
// "data": {
// "nonce": "MTI0Njg4NTAyMjYxMzgxMzgzMg==",
// "version": "0.0.24"
// }
// }
// }
//
bool CobraConnection::handleHandshakeResponse(const Json::Value& pdu)
{
if (!pdu.isObject()) return false;
if (!pdu.isMember("body")) return false;
Json::Value body = pdu["body"];
if (!body.isMember("data")) return false;
Json::Value data = body["data"];
if (!data.isMember("nonce")) return false;
Json::Value nonce = data["nonce"];
if (!nonce.isString()) return false;
return sendAuthMessage(nonce.asString());
}
//
// Authenticate message schema.
//
// challenge = {
// "action": "auth/authenticate",
// "body": {
// "method": "role_secret",
// "credentials": {
// "hash": computeHash(secret, nonce)
// }
// },
// }
//
bool CobraConnection::sendAuthMessage(const std::string& nonce)
{
Json::Value credentials;
credentials["hash"] = hmac(nonce, _roleSecret);
Json::Value body;
body["credentials"] = credentials;
body["method"] = "role_secret";
Json::Value pdu;
pdu["action"] = "auth/authenticate";
pdu["body"] = body;
pdu["id"] = Json::UInt64(_id++);
std::string serializedJson = serializeJson(pdu);
CobraConnection::invokeTrafficTrackerCallback(serializedJson.size(), false);
return _webSocket->send(serializedJson).success;
}
bool CobraConnection::handleSubscriptionResponse(const Json::Value& pdu)
{
if (!pdu.isObject()) return false;
if (!pdu.isMember("body")) return false;
Json::Value body = pdu["body"];
if (!body.isMember("subscription_id")) return false;
Json::Value subscriptionId = body["subscription_id"];
if (!subscriptionId.isString()) return false;
invokeEventCallback(ix::CobraEventType::Subscribed,
std::string(), WebSocketHttpHeaders(),
subscriptionId.asString());
return true;
}
bool CobraConnection::handleUnsubscriptionResponse(const Json::Value& pdu)
{
if (!pdu.isObject()) return false;
if (!pdu.isMember("body")) return false;
Json::Value body = pdu["body"];
if (!body.isMember("subscription_id")) return false;
Json::Value subscriptionId = body["subscription_id"];
if (!subscriptionId.isString()) return false;
invokeEventCallback(ix::CobraEventType::UnSubscribed,
std::string(), WebSocketHttpHeaders(),
subscriptionId.asString());
return true;
}
bool CobraConnection::handleSubscriptionData(const Json::Value& pdu)
{
if (!pdu.isObject()) return false;
if (!pdu.isMember("body")) return false;
Json::Value body = pdu["body"];
// Identify subscription_id, so that we can find
// which callback to execute
if (!body.isMember("subscription_id")) return false;
Json::Value subscriptionId = body["subscription_id"];
std::lock_guard<std::mutex> lock(_cbsMutex);
auto cb = _cbs.find(subscriptionId.asString());
if (cb == _cbs.end()) return false; // cannot find callback
// Extract messages now
if (!body.isMember("messages")) return false;
Json::Value messages = body["messages"];
if (!body.isMember("position")) return false;
std::string position = body["position"].asString();
for (auto&& msg : messages)
{
cb->second(msg, position);
}
return true;
}
bool CobraConnection::handlePublishResponse(const Json::Value& pdu)
{
if (!pdu.isObject()) return false;
if (!pdu.isMember("id")) return false;
Json::Value id = pdu["id"];
if (!id.isUInt64()) return false;
uint64_t msgId = id.asUInt64();
invokeEventCallback(ix::CobraEventType::Published,
std::string(), WebSocketHttpHeaders(),
std::string(), msgId);
invokePublishTrackerCallback(false, true);
return true;
}
bool CobraConnection::connect()
{
_webSocket->start();
return true;
}
bool CobraConnection::isConnected() const
{
return _webSocket->getReadyState() == ix::ReadyState::Open;
}
bool CobraConnection::isAuthenticated() const
{
return isConnected() && _authenticated;
}
std::string CobraConnection::serializeJson(const Json::Value& value)
{
std::lock_guard<std::mutex> lock(_jsonWriterMutex);
return _jsonWriter.write(value);
}
std::pair<CobraConnection::MsgId, std::string> CobraConnection::prePublish(
const Json::Value& channels,
const Json::Value& msg,
bool addToQueue)
{
std::lock_guard<std::mutex> lock(_prePublishMutex);
invokePublishTrackerCallback(true, false);
CobraConnection::MsgId msgId = _id;
_body["channels"] = channels;
_body["message"] = msg;
_pdu["body"] = _body;
_pdu["id"] = Json::UInt64(_id++);
std::string serializedJson = serializeJson(_pdu);
if (addToQueue)
{
enqueue(serializedJson);
}
return std::make_pair(msgId, serializedJson);
}
bool CobraConnection::publishNext()
{
std::lock_guard<std::mutex> lock(_queueMutex);
if (_messageQueue.empty()) return true;
auto&& msg = _messageQueue.back();
if (!_authenticated || !publishMessage(msg))
{
return false;
}
_messageQueue.pop_back();
return true;
}
//
// publish is not thread safe as we are trying to reuse some Json objects.
//
CobraConnection::MsgId CobraConnection::publish(const Json::Value& channels,
const Json::Value& msg)
{
auto p = prePublish(channels, msg, false);
auto msgId = p.first;
auto serializedJson = p.second;
//
// 1. When we use batch mode, we just enqueue and will do the flush explicitely
// 2. When we aren't authenticated yet to the cobra server, we need to enqueue
// and retry later
// 3. If the network connection was droped (WebSocket::send will return false),
// it means the message won't be sent so we need to enqueue as well.
//
// The order of the conditionals is important.
//
if (_publishMode == CobraConnection_PublishMode_Batch || !_authenticated ||
!publishMessage(serializedJson))
{
enqueue(serializedJson);
}
return msgId;
}
void CobraConnection::subscribe(const std::string& channel,
const std::string& filter,
const std::string& position,
SubscriptionCallback cb)
{
// Create and send a subscribe pdu
Json::Value body;
body["channel"] = channel;
if (!filter.empty())
{
body["filter"] = filter;
}
if (!position.empty())
{
body["position"] = position;
}
Json::Value pdu;
pdu["action"] = "rtm/subscribe";
pdu["body"] = body;
pdu["id"] = Json::UInt64(_id++);
_webSocket->send(pdu.toStyledString());
// Set the callback
std::lock_guard<std::mutex> lock(_cbsMutex);
_cbs[channel] = cb;
}
void CobraConnection::unsubscribe(const std::string& channel)
{
{
std::lock_guard<std::mutex> lock(_cbsMutex);
auto cb = _cbs.find(channel);
if (cb == _cbs.end()) return;
_cbs.erase(cb);
}
// Create and send an unsubscribe pdu
Json::Value body;
body["subscription_id"] = channel;
Json::Value pdu;
pdu["action"] = "rtm/unsubscribe";
pdu["body"] = body;
pdu["id"] = Json::UInt64(_id++);
_webSocket->send(pdu.toStyledString());
}
//
// Enqueue strategy drops old messages when we are at full capacity
//
// If we want to keep only 3 items max in the queue:
//
// enqueue(A) -> [A]
// enqueue(B) -> [B, A]
// enqueue(C) -> [C, B, A]
// enqueue(D) -> [D, C, B] -- now we drop A, the oldest message,
// -- and keep the 'fresh ones'
//
void CobraConnection::enqueue(const std::string& msg)
{
std::lock_guard<std::mutex> lock(_queueMutex);
if (_messageQueue.size() == CobraConnection::kQueueMaxSize)
{
_messageQueue.pop_back();
}
_messageQueue.push_front(msg);
}
//
// We process messages back (oldest) to front (newest) to respect ordering
// when sending them. If we fail to send something, we put it back in the queue
// at the end we picked it up originally (at the end).
//
bool CobraConnection::flushQueue()
{
while (!isQueueEmpty())
{
bool ok = publishNext();
if (!ok) return false;
}
return true;
}
bool CobraConnection::isQueueEmpty()
{
std::lock_guard<std::mutex> lock(_queueMutex);
return _messageQueue.empty();
}
bool CobraConnection::publishMessage(const std::string& serializedJson)
{
auto webSocketSendInfo = _webSocket->send(serializedJson);
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize,
false);
return webSocketSendInfo.success;
}
void CobraConnection::suspend()
{
disconnect();
}
void CobraConnection::resume()
{
connect();
}
} // namespace ix

View File

@ -1,219 +0,0 @@
/*
* IXCobraConnection.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone. All rights reserved.
*/
#pragma once
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
#include "IXCobraEventType.h"
#include "IXCobraEvent.h"
#include <json/json.h>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <unordered_map>
#include <limits>
#include "IXCobraConfig.h"
#ifdef max
#undef max
#endif
namespace ix
{
class WebSocket;
struct SocketTLSOptions;
enum CobraConnectionPublishMode
{
CobraConnection_PublishMode_Immediate = 0,
CobraConnection_PublishMode_Batch = 1
};
using SubscriptionCallback = std::function<void(const Json::Value&, const std::string&)>;
using EventCallback = std::function<void(const CobraEventPtr&)>;
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
using PublishTrackerCallback = std::function<void(bool sent, bool acked)>;
class CobraConnection
{
public:
using MsgId = uint64_t;
CobraConnection();
~CobraConnection();
/// Configuration / set keys, etc...
/// All input data but the channel name is encrypted with rc4
void configure(const std::string& appkey,
const std::string& endpoint,
const std::string& rolename,
const std::string& rolesecret,
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
const SocketTLSOptions& socketTLSOptions);
void configure(const ix::CobraConfig& config);
/// Set the traffic tracker callback
static void setTrafficTrackerCallback(const TrafficTrackerCallback& callback);
/// Reset the traffic tracker callback to an no-op one.
static void resetTrafficTrackerCallback();
/// Set the publish tracker callback
static void setPublishTrackerCallback(const PublishTrackerCallback& callback);
/// Reset the publish tracker callback to an no-op one.
static void resetPublishTrackerCallback();
/// Set the closed callback
void setEventCallback(const EventCallback& eventCallback);
/// Start the worker thread, used for background publishing
void start();
/// Publish a message to a channel
///
/// No-op if the connection is not established
MsgId publish(const Json::Value& channels, const Json::Value& msg);
// Subscribe to a channel, and execute a callback when an incoming
// message arrives.
void subscribe(const std::string& channel,
const std::string& filter = std::string(),
const std::string& position = std::string(),
SubscriptionCallback cb = nullptr);
/// Unsubscribe from a channel
void unsubscribe(const std::string& channel);
/// Close the connection
void disconnect();
/// Connect to Cobra and authenticate the connection
bool connect();
/// Returns true only if we're connected
bool isConnected() const;
/// Returns true only if we're authenticated
bool isAuthenticated() const;
/// Flush the publish queue
bool flushQueue();
/// Set the publish mode
void setPublishMode(CobraConnectionPublishMode publishMode);
/// Query the publish mode
CobraConnectionPublishMode getPublishMode();
/// Lifecycle management. Free resources when backgrounding
void suspend();
void resume();
/// Prepare a message for transmission
/// (update the pdu, compute a msgId, serialize json to a string)
std::pair<CobraConnection::MsgId, std::string> prePublish(
const Json::Value& channels,
const Json::Value& msg,
bool addToQueue);
/// Attempt to send next message from the internal queue
bool publishNext();
// An invalid message id, signifying an error.
static constexpr MsgId kInvalidMsgId = 0;
private:
bool sendHandshakeMessage();
bool handleHandshakeResponse(const Json::Value& data);
bool sendAuthMessage(const std::string& nonce);
bool handleSubscriptionData(const Json::Value& pdu);
bool handleSubscriptionResponse(const Json::Value& pdu);
bool handleUnsubscriptionResponse(const Json::Value& pdu);
bool handlePublishResponse(const Json::Value& pdu);
void initWebSocketOnMessageCallback();
bool publishMessage(const std::string& serializedJson);
void enqueue(const std::string& msg);
std::string serializeJson(const Json::Value& pdu);
/// Invoke the traffic tracker callback
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
/// Invoke the publish tracker callback
static void invokePublishTrackerCallback(bool sent, bool acked);
/// Invoke event callbacks
void invokeEventCallback(CobraEventType eventType,
const std::string& errorMsg = std::string(),
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
const std::string& subscriptionId = std::string(),
uint64_t msgId = std::numeric_limits<uint64_t>::max());
void invokeErrorCallback(const std::string& errorMsg, const std::string& serializedPdu);
/// Tells whether the internal queue is empty or not
bool isQueueEmpty();
///
/// Member variables
///
std::unique_ptr<WebSocket> _webSocket;
/// Configuration data
std::string _roleName;
std::string _roleSecret;
std::atomic<CobraConnectionPublishMode> _publishMode;
// Can be set on control+background thread, protecting with an atomic
std::atomic<bool> _authenticated;
// Keep some objects around
Json::Value _body;
Json::Value _pdu;
Json::FastWriter _jsonWriter;
mutable std::mutex _jsonWriterMutex;
std::mutex _prePublishMutex;
/// Traffic tracker callback
static TrafficTrackerCallback _trafficTrackerCallback;
/// Publish tracker callback
static PublishTrackerCallback _publishTrackerCallback;
/// Cobra events callbacks
EventCallback _eventCallback;
mutable std::mutex _eventCallbackMutex;
/// Subscription callbacks, only one per channel
std::unordered_map<std::string, SubscriptionCallback> _cbs;
mutable std::mutex _cbsMutex;
// Message Queue can be touched on control+background thread,
// protecting with a mutex.
//
// Message queue is used when there are problems sending messages so
// that sending can be retried later.
std::deque<std::string> _messageQueue;
mutable std::mutex _queueMutex;
// 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;
// Frequency at which we send a websocket ping to the backing cobra connection
static constexpr int kPingIntervalSecs = 30;
};
} // namespace ix

View File

@ -1,41 +0,0 @@
/*
* IXCobraEvent.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXCobraEventType.h"
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
#include <memory>
#include <string>
#include <cstdint>
namespace ix
{
struct CobraEvent
{
ix::CobraEventType type;
const std::string& errMsg;
const ix::WebSocketHttpHeaders& headers;
const std::string& subscriptionId;
uint64_t msgId; // CobraConnection::MsgId
CobraEvent(ix::CobraEventType t,
const std::string& e,
const ix::WebSocketHttpHeaders& h,
const std::string& s,
uint64_t m)
: type(t)
, errMsg(e)
, headers(h)
, subscriptionId(s)
, msgId(m)
{
;
}
};
using CobraEventPtr = std::unique_ptr<CobraEvent>;
}

View File

@ -1,25 +0,0 @@
/*
* IXCobraEventType.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
namespace ix
{
enum class CobraEventType
{
Authenticated = 0,
Error = 1,
Open = 2,
Closed = 3,
Subscribed = 4,
UnSubscribed = 5,
Published = 6,
Pong = 7,
HandshakeError = 8,
AuthenticationError = 9,
SubscriptionError = 10
};
}

View File

@ -1,236 +0,0 @@
/*
* IXCobraMetricsPublisher.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#include "IXCobraMetricsPublisher.h"
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <algorithm>
#include <stdexcept>
namespace ix
{
const int CobraMetricsPublisher::kVersion = 1;
const std::string CobraMetricsPublisher::kSetRateControlId = "sms_set_rate_control_id";
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
CobraMetricsPublisher::CobraMetricsPublisher() :
_enabled(true)
{
}
CobraMetricsPublisher::~CobraMetricsPublisher()
{
;
}
void CobraMetricsPublisher::configure(const CobraConfig& config,
const std::string& channel)
{
// Configure the satori connection and start its publish background thread
_cobra_metrics_theaded_publisher.configure(config, channel);
_cobra_metrics_theaded_publisher.start();
}
Json::Value& CobraMetricsPublisher::getGenericAttributes()
{
std::lock_guard<std::mutex> lock(_device_mutex);
return _device;
}
void CobraMetricsPublisher::setGenericAttributes(const std::string& attrName,
const Json::Value& value)
{
std::lock_guard<std::mutex> lock(_device_mutex);
_device[attrName] = value;
}
void CobraMetricsPublisher::enable(bool enabled)
{
_enabled = enabled;
}
void CobraMetricsPublisher::setBlacklist(const std::vector<std::string>& blacklist)
{
_blacklist = blacklist;
std::sort(_blacklist.begin(), _blacklist.end());
// publish our blacklist
Json::Value data;
Json::Value metrics;
for (auto&& metric : blacklist)
{
metrics.append(metric);
}
data["blacklist"] = metrics;
push(kSetBlacklistId, data);
}
bool CobraMetricsPublisher::isMetricBlacklisted(const std::string& id) const
{
return std::binary_search(_blacklist.begin(), _blacklist.end(), id);
}
void CobraMetricsPublisher::setRateControl(
const std::unordered_map<std::string, int>& rate_control)
{
for (auto&& it : rate_control)
{
if (it.second >= 0)
{
_rate_control[it.first] = it.second;
}
}
// publish our rate_control
Json::Value data;
Json::Value metrics;
for (auto&& it : _rate_control)
{
metrics[it.first] = it.second;
}
data["rate_control"] = metrics;
push(kSetRateControlId, data);
}
bool CobraMetricsPublisher::isAboveMaxUpdateRate(const std::string& id) const
{
// Is this metrics rate controlled ?
auto rate_control_it = _rate_control.find(id);
if (rate_control_it == _rate_control.end()) return false;
// Was this metrics already sent ?
std::lock_guard<std::mutex> lock(_last_update_mutex);
auto last_update = _last_update.find(id);
if (last_update == _last_update.end()) return false;
auto timeDeltaFromLastSend =
std::chrono::steady_clock::now() - last_update->second;
return timeDeltaFromLastSend < std::chrono::seconds(rate_control_it->second);
}
void CobraMetricsPublisher::setLastUpdate(const std::string& id)
{
std::lock_guard<std::mutex> lock(_last_update_mutex);
_last_update[id] = std::chrono::steady_clock::now();
}
uint64_t CobraMetricsPublisher::getMillisecondsSinceEpoch() const
{
auto now = std::chrono::system_clock::now();
auto ms =
std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()).count();
return ms;
}
CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id,
const std::string& data,
bool shouldPushTest)
{
if (!_enabled) return CobraConnection::kInvalidMsgId;
Json::Value root;
Json::Reader reader;
if (!reader.parse(data, root)) return CobraConnection::kInvalidMsgId;
return push(id, root, shouldPushTest);
}
CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id,
const CobraMetricsPublisher::Message& data)
{
if (!_enabled) return CobraConnection::kInvalidMsgId;
Json::Value root;
for (auto it : data)
{
root[it.first] = it.second;
}
return push(id, root);
}
bool CobraMetricsPublisher::shouldPush(const std::string& id) const
{
if (!_enabled) return false;
if (isMetricBlacklisted(id)) return false;
if (isAboveMaxUpdateRate(id)) return false;
return true;
}
CobraConnection::MsgId CobraMetricsPublisher::push(
const std::string& id,
const Json::Value& data,
bool shouldPushTest)
{
if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId;
setLastUpdate(id);
Json::Value msg;
msg["id"] = id;
msg["data"] = data;
msg["session"] = _session;
msg["version"] = kVersion;
msg["timestamp"] = Json::UInt64(getMillisecondsSinceEpoch());
{
std::lock_guard<std::mutex> lock(_device_mutex);
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
return _cobra_metrics_theaded_publisher.push(msg);
}
void CobraMetricsPublisher::setPublishMode(CobraConnectionPublishMode publishMode)
{
_cobra_metrics_theaded_publisher.setPublishMode(publishMode);
}
bool CobraMetricsPublisher::flushQueue()
{
return _cobra_metrics_theaded_publisher.flushQueue();
}
void CobraMetricsPublisher::suspend()
{
_cobra_metrics_theaded_publisher.suspend();
}
void CobraMetricsPublisher::resume()
{
_cobra_metrics_theaded_publisher.resume();
}
bool CobraMetricsPublisher::isConnected() const
{
return _cobra_metrics_theaded_publisher.isConnected();
}
bool CobraMetricsPublisher::isAuthenticated() const
{
return _cobra_metrics_theaded_publisher.isAuthenticated();
}
} // namespace ix

View File

@ -1,166 +0,0 @@
/*
* IXCobraMetricsPublisher.h
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#pragma once
#include "IXCobraMetricsThreadedPublisher.h"
#include <atomic>
#include <chrono>
#include <json/json.h>
#include <string>
#include <unordered_map>
namespace ix
{
struct SocketTLSOptions;
class CobraMetricsPublisher
{
public:
CobraMetricsPublisher();
~CobraMetricsPublisher();
/// Thread safety notes:
///
/// 1. _enabled, _blacklist and _rate_control read/writes are not protected by a mutex
/// to make shouldPush as fast as possible. _enabled default to false.
///
/// The code that set those is ran only once at init, and
/// the last value to be set is _enabled, which is also the first value checked in
/// shouldPush, so there shouldn't be any race condition.
///
/// 2. The queue of messages is thread safe, so multiple metrics can be safely pushed on
/// multiple threads
///
/// 3. Access to _last_update is protected as it needs to be read/write.
///
/// Configuration / set keys, etc...
/// All input data but the channel name is encrypted with rc4
void configure(const CobraConfig& config,
const std::string& channel);
/// Setter for the list of blacklisted metrics ids.
/// That list is sorted internally for fast lookups
void setBlacklist(const std::vector<std::string>& blacklist);
/// Set the maximum rate at which a metrics can be sent. Unit is seconds
/// if rate_control = { 'foo_id': 60 },
/// the foo_id metric cannot be pushed more than once every 60 seconds
void setRateControl(const std::unordered_map<std::string, int>& rate_control);
/// Configuration / enable/disable
void enable(bool enabled);
/// Simple interface, list of key value pairs where typeof(key) == typeof(value) == string
typedef std::unordered_map<std::string, std::string> Message;
CobraConnection::MsgId push(
const std::string& id,
const CobraMetricsPublisher::Message& data = CobraMetricsPublisher::Message());
/// Richer interface using json, which supports types (bool, int, float) and hierarchies of
/// elements
///
/// The shouldPushTest argument should be set to false, and used in combination with the
/// shouldPush method for places where we want to be as lightweight as possible when
/// collecting metrics. When set to false, it is used so that we don't do double work when
/// computing whether a metrics should be sent or not.
CobraConnection::MsgId push(const std::string& id, const Json::Value& data, bool shouldPushTest = true);
/// Interface used by lua. msg is a json encoded string.
CobraConnection::MsgId push(const std::string& id, const std::string& data, bool shouldPushTest = true);
/// Tells whether a metric can be pushed.
/// A metric can be pushed if it satisfies those conditions:
///
/// 1. the metrics system should be enabled
/// 2. the metrics shouldn't be black-listed
/// 3. the metrics shouldn't have reached its rate control limit at this
/// "sampling"/"calling" time
bool shouldPush(const std::string& id) const;
/// Get generic information json object
Json::Value& getGenericAttributes();
/// Set generic information values
void setGenericAttributes(const std::string& attrName, const Json::Value& value);
/// Set a unique id for the session. A uuid can be used.
void setSession(const std::string& session) { _session = session; }
/// Get the unique id used to identify the current session
const std::string& getSession() const { return _session; }
/// Return the number of milliseconds since the epoch (~1970)
uint64_t getMillisecondsSinceEpoch() const;
/// Set satori connection publish mode
void setPublishMode(CobraConnectionPublishMode publishMode);
/// Flush the publish queue
bool flushQueue();
/// Lifecycle management. Free resources when backgrounding
void suspend();
void resume();
/// Tells whether the socket connection is opened
bool isConnected() const;
/// Returns true only if we're authenticated
bool isAuthenticated() const;
private:
/// Lookup an id in our metrics to see whether it is blacklisted
/// Complexity is logarithmic
bool isMetricBlacklisted(const std::string& id) const;
/// Tells whether we should drop a metrics or not as part of an enqueuing
/// because it exceed the max update rate (it is sent too often)
bool isAboveMaxUpdateRate(const std::string& id) const;
/// Record when a metric was last sent. Used for rate control
void setLastUpdate(const std::string& id);
///
/// Member variables
///
CobraMetricsThreadedPublisher _cobra_metrics_theaded_publisher;
/// A boolean to enable or disable this system
/// push becomes a no-op when _enabled is false
std::atomic<bool> _enabled;
/// A uuid used to uniquely identify a session
std::string _session;
/// The _device json blob is populated once when configuring this system
/// It record generic metadata about the client, run (version, device model, etc...)
Json::Value _device;
mutable std::mutex _device_mutex; // protect access to _device
/// Metrics control (black list + rate control)
std::vector<std::string> _blacklist;
std::unordered_map<std::string, int> _rate_control;
std::unordered_map<std::string, std::chrono::time_point<std::chrono::steady_clock>>
_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;
/// Our protocol version. Can be used by subscribers who would want to be backward
/// compatible if we change the way we arrange data
static const int kVersion;
};
} // namespace ix

View File

@ -1,229 +0,0 @@
/*
* IXCobraMetricsThreadedPublisher.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#include "IXCobraMetricsThreadedPublisher.h"
#include <ixwebsocket/IXSetThreadName.h>
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <ixcore/utils/IXCoreLogger.h>
#include <algorithm>
#include <stdexcept>
#include <cmath>
#include <cassert>
#include <iostream>
#include <sstream>
namespace ix
{
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher() :
_stop(false)
{
_cobra_connection.setEventCallback([](const CobraEventPtr& event)
{
std::stringstream ss;
if (event->type == ix::CobraEventType::Open)
{
ss << "Handshake headers" << std::endl;
for (auto&& it : event->headers)
{
ss << it.first << ": " << it.second << std::endl;
}
}
else if (event->type == ix::CobraEventType::Authenticated)
{
ss << "Authenticated";
}
else if (event->type == ix::CobraEventType::Error)
{
ss << "Error: " << event->errMsg;
}
else if (event->type == ix::CobraEventType::Closed)
{
ss << "Connection closed: " << event->errMsg;
}
else if (event->type == ix::CobraEventType::Subscribed)
{
ss << "Subscribed through subscription id: " << event->subscriptionId;
}
else if (event->type == ix::CobraEventType::UnSubscribed)
{
ss << "Unsubscribed through subscription id: " << event->subscriptionId;
}
else if (event->type == ix::CobraEventType::Published)
{
ss << "Published message " << event->msgId << " acked";
}
else if (event->type == ix::CobraEventType::Pong)
{
ss << "Received websocket pong";
}
else if (event->type == ix::CobraEventType::HandshakeError)
{
ss << "Handshake error: " << event->errMsg;
}
else if (event->type == ix::CobraEventType::AuthenticationError)
{
ss << "Authentication error: " << event->errMsg;
}
else if (event->type == ix::CobraEventType::SubscriptionError)
{
ss << "Subscription error: " << event->errMsg;
}
ix::IXCoreLogger::Log(ss.str().c_str());
});
}
CobraMetricsThreadedPublisher::~CobraMetricsThreadedPublisher()
{
// The background thread won't be joinable if it was never
// started by calling CobraMetricsThreadedPublisher::start
if (!_thread.joinable()) return;
_stop = true;
_condition.notify_one();
_thread.join();
}
void CobraMetricsThreadedPublisher::start()
{
if (_thread.joinable()) return; // we've already been started
_thread = std::thread(&CobraMetricsThreadedPublisher::run, this);
}
void CobraMetricsThreadedPublisher::configure(const CobraConfig& config,
const std::string& channel)
{
ix::IXCoreLogger::Log(config.socketTLSOptions.getDescription().c_str());
_channel = channel;
_cobra_connection.configure(config);
}
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
{
{
std::unique_lock<std::mutex> lock(_queue_mutex);
_queue.push(messageKind);
}
// wake up one thread
_condition.notify_one();
}
void CobraMetricsThreadedPublisher::setPublishMode(CobraConnectionPublishMode publishMode)
{
_cobra_connection.setPublishMode(publishMode);
}
bool CobraMetricsThreadedPublisher::flushQueue()
{
return _cobra_connection.flushQueue();
}
void CobraMetricsThreadedPublisher::run()
{
setThreadName("CobraMetricsPublisher");
_cobra_connection.connect();
while (true)
{
Json::Value msg;
MessageKind messageKind;
{
std::unique_lock<std::mutex> lock(_queue_mutex);
while (!_stop && _queue.empty())
{
_condition.wait(lock);
}
if (_stop)
{
_cobra_connection.disconnect();
return;
}
messageKind = _queue.front();
_queue.pop();
}
switch (messageKind)
{
case MessageKind::Suspend:
{
_cobra_connection.suspend();
continue;
}; break;
case MessageKind::Resume:
{
_cobra_connection.resume();
continue;
}; break;
case MessageKind::Message:
{
if (_cobra_connection.getPublishMode() == CobraConnection_PublishMode_Immediate)
{
_cobra_connection.publishNext();
}
}; break;
}
}
}
CobraConnection::MsgId CobraMetricsThreadedPublisher::push(const Json::Value& msg)
{
static const std::string messageIdKey("id");
//
// Publish to multiple channels. This let the consumer side
// easily subscribe to all message of a certain type, without having
// to do manipulations on the messages on the server side.
//
Json::Value channels;
channels.append(_channel);
if (msg.isMember(messageIdKey))
{
channels.append(msg[messageIdKey]);
}
auto res = _cobra_connection.prePublish(channels, msg, true);
auto msgId = res.first;
pushMessage(MessageKind::Message);
return msgId;
}
void CobraMetricsThreadedPublisher::suspend()
{
pushMessage(MessageKind::Suspend);
}
void CobraMetricsThreadedPublisher::resume()
{
pushMessage(MessageKind::Resume);
}
bool CobraMetricsThreadedPublisher::isConnected() const
{
return _cobra_connection.isConnected();
}
bool CobraMetricsThreadedPublisher::isAuthenticated() const
{
return _cobra_connection.isAuthenticated();
}
} // namespace ix

View File

@ -1,102 +0,0 @@
/*
* IXCobraMetricsThreadedPublisher.h
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#pragma once
#include "IXCobraConnection.h"
#include <atomic>
#include <condition_variable>
#include <json/json.h>
#include <map>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
namespace ix
{
struct SocketTLSOptions;
class CobraMetricsThreadedPublisher
{
public:
CobraMetricsThreadedPublisher();
~CobraMetricsThreadedPublisher();
/// Configuration / set keys, etc...
void configure(const CobraConfig& config,
const std::string& channel);
/// Start the worker thread, used for background publishing
void start();
/// Push a msg to our queue of messages to be published to cobra on the background
// thread. Main user right now is the Cobra Metrics System
CobraConnection::MsgId push(const Json::Value& msg);
/// Set cobra connection publish mode
void setPublishMode(CobraConnectionPublishMode publishMode);
/// Flush the publish queue
bool flushQueue();
/// Lifecycle management. Free resources when backgrounding
void suspend();
void resume();
/// Tells whether the socket connection is opened
bool isConnected() const;
/// Returns true only if we're authenticated
bool isAuthenticated() const;
private:
enum class MessageKind
{
Message = 0,
Suspend = 1,
Resume = 2
};
/// Push a message to be processed by the background thread
void pushMessage(MessageKind messageKind);
/// Get a wait time which is increasing exponentially based on the number of retries
uint64_t getWaitTimeExp(int retry_count);
/// Debugging routine to print the connection parameters to the console
void printInfo();
/// Publish a message to satory
/// Will retry multiple times (3) if a problem occurs.
///
/// Right now, only called on the publish worker thread.
void safePublish(const Json::Value& msg);
/// The worker thread "daemon" method. That method never returns unless _stop is set to true
void run();
/// Our connection to cobra.
CobraConnection _cobra_connection;
/// The channel we are publishing to
std::string _channel;
/// Internal data structures used to publish to cobra
/// Pending messages are stored into a queue, which is protected by a mutex
/// We used a condition variable to prevent the worker thread from busy polling
/// So we notify the condition variable when an incoming message arrives to signal
/// that it should wake up and take care of publishing it to cobra
/// To shutdown the worker thread one has to set the _stop boolean to true.
/// This is done in the destructor
std::queue<MessageKind> _queue;
mutable std::mutex _queue_mutex;
std::condition_variable _condition;
std::atomic<bool> _stop;
std::thread _thread;
};
} // namespace ix

View File

@ -1 +0,0 @@
Client code to publish to a real time analytic system, described in [https://bsergean.github.io/redis_conf_2019/slides.html#1](link).

View File

@ -1,19 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
#
set (IXCORE_SOURCES
ixcore/utils/IXCoreLogger.cpp
)
set (IXCORE_HEADERS
ixcore/utils/IXCoreLogger.h
)
add_library(ixcore STATIC
${IXCORE_SOURCES}
${IXCORE_HEADERS}
)
target_include_directories( ixcore PUBLIC . )

View File

@ -1,14 +0,0 @@
#include "ixcore/utils/IXCoreLogger.h"
namespace ix
{
// Default do nothing logger
IXCoreLogger::LogFunc IXCoreLogger::_currentLogger = [](const char* /*msg*/){};
void IXCoreLogger::Log(const char* msg)
{
_currentLogger(msg);
}
} // ix

View File

@ -1,18 +0,0 @@
#pragma once
#include <functional>
namespace ix
{
class IXCoreLogger
{
public:
using LogFunc = std::function<void(const char*)>;
static void Log(const char* msg);
static void setLogFunction(LogFunc& func) { _currentLogger = func; }
private:
static LogFunc _currentLogger;
};
} // namespace ix

View File

@ -1,49 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
#
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../CMake;${CMAKE_MODULE_PATH}")
set (IXCRYPTO_SOURCES
ixcrypto/IXHMac.cpp
ixcrypto/IXBase64.cpp
ixcrypto/IXUuid.cpp
ixcrypto/IXHash.cpp
)
set (IXCRYPTO_HEADERS
ixcrypto/IXHMac.h
ixcrypto/IXBase64.h
ixcrypto/IXUuid.h
ixcrypto/IXHash.h
)
add_library(ixcrypto STATIC
${IXCRYPTO_SOURCES}
${IXCRYPTO_HEADERS}
)
set(IXCRYPTO_INCLUDE_DIRS
.
../ixcore)
target_include_directories( ixcrypto PUBLIC ${IXCRYPTO_INCLUDE_DIRS} )
# hmac computation needs a crypto library
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_TLS)
if (USE_MBED_TLS)
find_package(MbedTLS REQUIRED)
target_include_directories(ixcrypto PUBLIC ${MBEDTLS_INCLUDE_DIRS})
target_link_libraries(ixcrypto ${MBEDTLS_LIBRARIES})
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_MBED_TLS)
elseif (APPLE)
elseif (WIN32)
else()
find_package(OpenSSL REQUIRED)
add_definitions(${OPENSSL_DEFINITIONS})
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(ixcrypto ${OPENSSL_LIBRARIES})
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_OPEN_SSL)
endif()

View File

@ -1,142 +0,0 @@
/*
base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
*/
#include "IXBase64.h"
namespace ix
{
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
std::string base64_encode(const std::string& data, size_t len)
{
const char* bytes_to_encode = data.c_str();
return base64_encode(bytes_to_encode, len);
}
std::string base64_encode(const char* bytes_to_encode, size_t len)
{
std::string ret;
ret.reserve(((len + 2) / 3) * 4);
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while(len--)
{
char_array_3[i++] = *(bytes_to_encode++);
if(i == 3)
{
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if(i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
while((i++ < 3))
ret += '=';
}
return ret;
}
static inline bool is_base64(unsigned char c)
{
return (isalnum(c) || (c == '+') || (c == '/'));
}
std::string base64_decode(const std::string& encoded_string)
{
int in_len = (int)encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
ret.reserve(((in_len + 3) / 4) * 3);
while(in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
{
char_array_4[i++] = encoded_string[in_]; in_++;
if(i ==4)
{
for(i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for(i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if(i)
{
for(j = i; j <4; j++)
char_array_4[j] = 0;
for(j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for(j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
}

View File

@ -1,16 +0,0 @@
/*
* base64.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
std::string base64_encode(const std::string& data, size_t len);
std::string base64_encode(const char* data, size_t len);
std::string base64_decode(const std::string& encoded_string);
} // namespace ix

View File

@ -1,50 +0,0 @@
/*
* IXHMac.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
#include "IXHMac.h"
#include "IXBase64.h"
#if defined(IXCRYPTO_USE_MBED_TLS)
# include <mbedtls/md.h>
#elif defined(__APPLE__)
# include <CommonCrypto/CommonHMAC.h>
#elif defined(IXCRYPTO_USE_OPEN_SSL)
# include <openssl/hmac.h>
#else
# include <assert.h>
#endif
namespace ix
{
std::string hmac(const std::string& data, const std::string& key)
{
constexpr size_t hashSize = 16;
unsigned char hash[hashSize];
#if defined(IXCRYPTO_USE_MBED_TLS)
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5),
(unsigned char *) key.c_str(), key.size(),
(unsigned char *) data.c_str(), data.size(),
(unsigned char *) &hash);
#elif defined(__APPLE__)
CCHmac(kCCHmacAlgMD5,
key.c_str(), key.size(),
data.c_str(), data.size(),
&hash);
#elif defined(IXCRYPTO_USE_OPEN_SSL)
HMAC(EVP_md5(),
key.c_str(), (int) key.size(),
(unsigned char *) data.c_str(), (int) data.size(),
(unsigned char *) hash, nullptr);
#else
assert(false && "hmac not implemented on this platform");
#endif
std::string hashString(reinterpret_cast<char*>(hash), hashSize);
return base64_encode(hashString, (uint32_t) hashString.size());
}
}

View File

@ -1,14 +0,0 @@
/*
* IXHMac.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
std::string hmac(const std::string& data, const std::string& key);
}

View File

@ -1,22 +0,0 @@
/*
* IXHash.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
#include "IXHash.h"
namespace ix
{
uint64_t djb2Hash(const std::vector<uint8_t>& data)
{
uint64_t hashAddress = 5381;
for (auto&& c : data)
{
hashAddress = ((hashAddress << 5) + hashAddress) + c;
}
return hashAddress;
}
}

View File

@ -1,15 +0,0 @@
/*
* IXHash.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <vector>
namespace ix
{
uint64_t djb2Hash(const std::vector<uint8_t>& data);
}

View File

@ -1,30 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
#
set (IXSENTRY_SOURCES
ixsentry/IXSentryClient.cpp
)
set (IXSENTRY_HEADERS
ixsentry/IXSentryClient.h
)
add_library(ixsentry STATIC
${IXSENTRY_SOURCES}
${IXSENTRY_HEADERS}
)
find_package(JsonCpp)
if (NOT JSONCPP_FOUND)
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
endif()
set(IXSENTRY_INCLUDE_DIRS
.
..
../ixcore
${JSONCPP_INCLUDE_DIRS})
target_include_directories( ixsentry PUBLIC ${IXSENTRY_INCLUDE_DIRS} )

View File

@ -1,310 +0,0 @@
/*
* IXSentryClient.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include "IXSentryClient.h"
#include <chrono>
#include <iostream>
#include <fstream>
#include <sstream>
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
#include <ixwebsocket/IXWebSocketVersion.h>
#include <ixcore/utils/IXCoreLogger.h>
namespace ix
{
SentryClient::SentryClient(const std::string& dsn)
: _dsn(dsn)
, _validDsn(false)
, _luaFrameRegex("\t([^/]+):([0-9]+): in function ['<]([^/]+)['>]")
, _httpClient(std::make_shared<HttpClient>(true))
{
const std::regex dsnRegex("(http[s]?)://([^:]+):([^@]+)@([^/]+)/([0-9]+)");
std::smatch group;
if (std::regex_match(dsn, group, dsnRegex) && group.size() == 6)
{
_validDsn = true;
const auto scheme = group.str(1);
const auto host = group.str(4);
const auto project_id = group.str(5);
_url = scheme + "://" + host + "/api/" + project_id + "/store/";
_publicKey = group.str(2);
_secretKey = group.str(3);
}
}
void SentryClient::setTLSOptions(const SocketTLSOptions& tlsOptions)
{
_httpClient->setTLSOptions(tlsOptions);
}
int64_t SentryClient::getTimestamp()
{
const auto tp = std::chrono::system_clock::now();
const auto dur = tp.time_since_epoch();
return std::chrono::duration_cast<std::chrono::seconds>(dur).count();
}
std::string SentryClient::getIso8601()
{
std::time_t now;
std::time(&now);
char buf[sizeof("2011-10-08T07:07:09Z")];
std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&now));
return buf;
}
std::string SentryClient::computeAuthHeader()
{
std::string securityHeader("Sentry sentry_version=5");
securityHeader += ",sentry_client=ws/";
securityHeader += std::string(IX_WEBSOCKET_VERSION);
securityHeader += ",sentry_timestamp=" + std::to_string(SentryClient::getTimestamp());
securityHeader += ",sentry_key=" + _publicKey;
securityHeader += ",sentry_secret=" + _secretKey;
return securityHeader;
}
Json::Value SentryClient::parseLuaStackTrace(const std::string& stack)
{
Json::Value frames;
// Split by lines
std::string line;
std::stringstream tokenStream(stack);
std::smatch group;
while (std::getline(tokenStream, line))
{
// MapScene.lua:2169: in function 'singleCB'
if (std::regex_match(line, group, _luaFrameRegex))
{
const auto fileName = group.str(1);
const auto linenoStr = group.str(2);
const auto function = group.str(3);
std::stringstream ss;
ss << linenoStr;
uint64_t lineno;
ss >> lineno;
Json::Value frame;
frame["lineno"] = Json::UInt64(lineno);
frame["filename"] = fileName;
frame["function"] = function;
frames.append(frame);
}
}
std::reverse(frames.begin(), frames.end());
return frames;
}
std::string parseExceptionName(const std::string& stack)
{
// Split by lines
std::string line;
std::stringstream tokenStream(stack);
// Extract the first line
std::getline(tokenStream, line);
return line;
}
std::string SentryClient::computePayload(const Json::Value& msg)
{
Json::Value payload;
//
// "tags": [
// [
// "a",
// "b"
// ],
// ]
//
Json::Value tags(Json::arrayValue);
payload["platform"] = "python";
payload["sdk"]["name"] = "ws";
payload["sdk"]["version"] = IX_WEBSOCKET_VERSION;
payload["timestamp"] = SentryClient::getIso8601();
bool isNoisyTypes = msg["id"].asString() == "game_noisytypes_id";
std::string stackTraceFieldName = isNoisyTypes ? "traceback" : "stack";
std::string stack;
std::string message;
if (isNoisyTypes)
{
stack = msg["data"][stackTraceFieldName].asString();
message = parseExceptionName(stack);
}
else // logging
{
if (msg["data"].isMember("info"))
{
stack = msg["data"]["info"][stackTraceFieldName].asString();
message = msg["data"]["info"]["message"].asString();
if (msg["data"].isMember("tags"))
{
auto members = msg["data"]["tags"].getMemberNames();
for (auto member : members)
{
Json::Value tag;
tag.append(member);
tag.append(msg["data"]["tags"][member]);
tags.append(tag);
}
}
if (msg["data"]["info"].isMember("level_str"))
{
// https://docs.sentry.io/enriching-error-data/context/?platform=python#setting-the-level
std::string level = msg["data"]["info"]["level_str"].asString();
if (level == "critical")
{
level = "fatal";
}
payload["level"] = level;
}
}
else
{
stack = msg["data"][stackTraceFieldName].asString();
message = msg["data"]["message"].asString();
}
}
Json::Value exception;
exception["stacktrace"]["frames"] = parseLuaStackTrace(stack);
exception["value"] = message;
payload["exception"].append(exception);
Json::Value extra;
extra["cobra_event"] = msg;
// Builtin tags
Json::Value gameTag;
gameTag.append("game");
gameTag.append(msg["device"]["game"]);
tags.append(gameTag);
Json::Value userIdTag;
userIdTag.append("userid");
userIdTag.append(msg["device"]["user_id"]);
tags.append(userIdTag);
Json::Value environmentTag;
environmentTag.append("environment");
environmentTag.append(msg["device"]["environment"]);
tags.append(environmentTag);
Json::Value clientVersionTag;
clientVersionTag.append("client_version");
clientVersionTag.append(msg["device"]["app_version"]);
tags.append(clientVersionTag);
payload["tags"] = tags;
return _jsonWriter.write(payload);
}
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg, bool verbose)
{
auto args = _httpClient->createRequest();
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
args->connectTimeout = 60;
args->transferTimeout = 5 * 60;
args->followRedirects = true;
args->verbose = verbose;
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
std::string body = computePayload(msg);
HttpResponsePtr response = _httpClient->post(_url, body, args);
return std::make_pair(response, body);
}
// https://sentry.io/api/12345/minidump?sentry_key=abcdefgh");
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
{
std::stringstream ss;
ss << "https://sentry.io/api/"
<< project
<< "/minidump?sentry_key="
<< key;
return ss.str();
}
//
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
//
void SentryClient::uploadMinidump(
const std::string& sentryMetadata,
const std::string& minidumpBytes,
const std::string& project,
const std::string& key,
bool verbose,
const OnResponseCallback& onResponseCallback)
{
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
auto args = _httpClient->createRequest();
args->verb = HttpClient::kPost;
args->connectTimeout = 60;
args->transferTimeout = 5 * 60;
args->followRedirects = true;
args->verbose = verbose;
args->multipartBoundary = multipartBoundary;
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
HttpFormDataParameters httpFormDataParameters;
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
HttpParameters httpParameters;
httpParameters["sentry"] = sentryMetadata;
args->url = computeUrl(project, key);
args->body = _httpClient->serializeHttpFormDataParameters(multipartBoundary, httpFormDataParameters, httpParameters);
_httpClient->performRequest(args, onResponseCallback);
}
void SentryClient::uploadPayload(
const Json::Value& payload,
bool verbose,
const OnResponseCallback& onResponseCallback)
{
auto args = _httpClient->createRequest();
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
args->verb = HttpClient::kPost;
args->connectTimeout = 60;
args->transferTimeout = 5 * 60;
args->followRedirects = true;
args->verbose = verbose;
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
args->url = _url;
args->body = _jsonWriter.write(payload);
_httpClient->performRequest(args, onResponseCallback);
}
} // namespace ix

View File

@ -1,69 +0,0 @@
/*
* IXSentryClient.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#pragma once
#include <algorithm>
#include <ixwebsocket/IXHttpClient.h>
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <json/json.h>
#include <regex>
#include <memory>
namespace ix
{
class SentryClient
{
public:
SentryClient(const std::string& dsn);
~SentryClient() = default;
std::pair<HttpResponsePtr, std::string> send(const Json::Value& msg, bool verbose);
Json::Value parseLuaStackTrace(const std::string& stack);
// Mostly for testing
void setTLSOptions(const SocketTLSOptions& tlsOptions);
void uploadMinidump(
const std::string& sentryMetadata,
const std::string& minidumpBytes,
const std::string& project,
const std::string& key,
bool verbose,
const OnResponseCallback& onResponseCallback);
void uploadPayload(
const Json::Value& payload,
bool verbose,
const OnResponseCallback& onResponseCallback);
private:
int64_t getTimestamp();
std::string computeAuthHeader();
std::string getIso8601();
std::string computePayload(const Json::Value& msg);
std::string computeUrl(const std::string& project, const std::string& key);
void displayReponse(HttpResponsePtr response);
std::string _dsn;
bool _validDsn;
std::string _url;
// Used for authentication with a header
std::string _publicKey;
std::string _secretKey;
Json::FastWriter _jsonWriter;
std::regex _luaFrameRegex;
std::shared_ptr<HttpClient> _httpClient;
};
} // namespace ix

View File

@ -1,35 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
#
set (IXSNAKE_SOURCES
ixsnake/IXSnakeServer.cpp
ixsnake/IXSnakeProtocol.cpp
ixsnake/IXAppConfig.cpp
ixsnake/IXRedisClient.cpp
ixsnake/IXRedisServer.cpp
)
set (IXSNAKE_HEADERS
ixsnake/IXSnakeServer.h
ixsnake/IXSnakeProtocol.h
ixsnake/IXAppConfig.h
ixsnake/IXRedisClient.h
ixsnake/IXRedisServer.h
)
add_library(ixsnake STATIC
${IXSNAKE_SOURCES}
${IXSNAKE_HEADERS}
)
set(IXSNAKE_INCLUDE_DIRS
.
..
../ixcore
../ixcrypto
../ixwebsocket
../third_party)
target_include_directories( ixsnake PUBLIC ${IXSNAKE_INCLUDE_DIRS} )

View File

@ -1,48 +0,0 @@
/*
* IXSnakeProtocol.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXAppConfig.h"
#include "IXSnakeProtocol.h"
#include <iostream>
#include <ixcrypto/IXUuid.h>
namespace snake
{
bool isAppKeyValid(const AppConfig& appConfig, std::string appkey)
{
return appConfig.apps.count(appkey) != 0;
}
std::string getRoleSecret(const AppConfig& appConfig, std::string appkey, std::string role)
{
if (!isAppKeyValid(appConfig, appkey))
{
std::cerr << "Missing appkey " << appkey << std::endl;
return std::string();
}
auto roles = appConfig.apps[appkey]["roles"];
auto channel = roles[role]["secret"];
return channel;
}
std::string generateNonce()
{
return ix::uuid4();
}
void dumpConfig(const AppConfig& appConfig)
{
for (auto&& host : appConfig.redisHosts)
{
std::cout << "redis host: " << host << std::endl;
}
std::cout << "redis password: " << appConfig.redisPassword << std::endl;
std::cout << "redis port: " << appConfig.redisPort << std::endl;
}
} // namespace snake

View File

@ -1,45 +0,0 @@
/*
* IXAppConfig.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include <ixwebsocket/IXSocketTLSOptions.h>
namespace snake
{
struct AppConfig
{
// Server
std::string hostname;
int port;
// Redis
std::vector<std::string> redisHosts;
int redisPort;
std::string redisPassword;
// AppKeys
nlohmann::json apps;
// TLS options
ix::SocketTLSOptions socketTLSOptions;
// Misc
bool verbose;
bool disablePong;
};
bool isAppKeyValid(const AppConfig& appConfig, std::string appkey);
std::string getRoleSecret(const AppConfig& appConfig, std::string appkey, std::string role);
std::string generateNonce();
void dumpConfig(const AppConfig& appConfig);
} // namespace snake

View File

@ -1,353 +0,0 @@
/*
* IXRedisClient.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXRedisClient.h"
#include <cstring>
#include <iomanip>
#include <iostream>
#include <ixwebsocket/IXSocketFactory.h>
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <sstream>
#include <vector>
namespace ix
{
bool RedisClient::connect(const std::string& hostname, int port)
{
bool tls = false;
std::string errorMsg;
SocketTLSOptions tlsOptions;
_socket = createSocket(tls, -1, errorMsg, tlsOptions);
if (!_socket)
{
return false;
}
CancellationRequest cancellationRequest = []() -> bool
{
return false;
};
std::string errMsg;
return _socket->connect(hostname, port, errMsg, cancellationRequest);
}
void RedisClient::stop()
{
_stop = true;
}
bool RedisClient::auth(const std::string& password, std::string& response)
{
response.clear();
if (!_socket) return false;
std::stringstream ss;
ss << "AUTH ";
ss << password;
ss << "\r\n";
bool sent = _socket->writeBytes(ss.str(), nullptr);
if (!sent)
{
return false;
}
auto pollResult = _socket->isReadyToRead(-1);
if (pollResult == PollResultType::Error)
{
return false;
}
auto lineResult = _socket->readLine(nullptr);
auto lineValid = lineResult.first;
auto line = lineResult.second;
response = line;
return lineValid;
}
std::string RedisClient::writeString(const std::string& str)
{
std::stringstream ss;
ss << "$";
ss << str.size();
ss << "\r\n";
ss << str;
ss << "\r\n";
return ss.str();
}
bool RedisClient::publish(const std::string& channel,
const std::string& message,
std::string& errMsg)
{
errMsg.clear();
if (!_socket)
{
errMsg = "socket is not initialized";
return false;
}
std::stringstream ss;
ss << "*3\r\n";
ss << writeString("PUBLISH");
ss << writeString(channel);
ss << writeString(message);
bool sent = _socket->writeBytes(ss.str(), nullptr);
if (!sent)
{
errMsg = "Cannot write bytes to socket";
return false;
}
auto pollResult = _socket->isReadyToRead(-1);
if (pollResult == PollResultType::Error)
{
errMsg = "Error while polling for result";
return false;
}
auto lineResult = _socket->readLine(nullptr);
auto lineValid = lineResult.first;
auto line = lineResult.second;
// A successful response starts with a :
if (line.empty() || line[0] != ':')
{
errMsg = line;
return false;
}
return lineValid;
}
//
// FIXME: we assume that redis never return errors...
//
bool RedisClient::subscribe(const std::string& channel,
const OnRedisSubscribeResponseCallback& responseCallback,
const OnRedisSubscribeCallback& callback)
{
_stop = false;
if (!_socket) return false;
std::stringstream ss;
ss << "*2\r\n";
ss << writeString("SUBSCRIBE");
ss << writeString(channel);
bool sent = _socket->writeBytes(ss.str(), nullptr);
if (!sent)
{
return false;
}
// Wait 1s for the response
auto pollResult = _socket->isReadyToRead(-1);
if (pollResult == PollResultType::Error)
{
return false;
}
// build the response as a single string
std::stringstream oss;
// Read the first line of the response
auto lineResult = _socket->readLine(nullptr);
auto lineValid = lineResult.first;
auto line = lineResult.second;
oss << line;
if (!lineValid) return false;
// There are 5 items for the subscribe reply
for (int i = 0; i < 5; ++i)
{
auto lineResult = _socket->readLine(nullptr);
auto lineValid = lineResult.first;
auto line = lineResult.second;
oss << line;
if (!lineValid) return false;
}
responseCallback(oss.str());
// Wait indefinitely for new messages
while (true)
{
if (_stop) break;
// Wait until something is ready to read
int timeoutMs = 10;
auto pollResult = _socket->isReadyToRead(timeoutMs);
if (pollResult == PollResultType::Error)
{
return false;
}
if (pollResult == PollResultType::Timeout)
{
continue;
}
// The first line of the response describe the return type,
// => *3 (an array of 3 elements)
auto lineResult = _socket->readLine(nullptr);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) return false;
int arraySize;
{
std::stringstream ss;
ss << line.substr(1, line.size() - 1);
ss >> arraySize;
}
// There are 6 items for each received message
for (int i = 0; i < arraySize; ++i)
{
auto lineResult = _socket->readLine(nullptr);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) return false;
// Messages are string, which start with a string size
// => $7 (7 bytes)
int stringSize;
std::stringstream ss;
ss << line.substr(1, line.size() - 1);
ss >> stringSize;
auto readResult = _socket->readBytes(stringSize, nullptr, nullptr);
if (!readResult.first) return false;
if (i == 2)
{
// The message is the 3rd element.
callback(readResult.second);
}
// read last 2 bytes (\r\n)
char c;
_socket->readByte(&c, nullptr);
_socket->readByte(&c, nullptr);
}
}
return true;
}
std::string RedisClient::prepareXaddCommand(
const std::string& stream,
const std::string& message)
{
std::stringstream ss;
ss << "*5\r\n";
ss << writeString("XADD");
ss << writeString(stream);
ss << writeString("*");
ss << writeString("field");
ss << writeString(message);
return ss.str();
}
std::string RedisClient::xadd(const std::string& stream,
const std::string& message,
std::string& errMsg)
{
errMsg.clear();
if (!_socket)
{
errMsg = "socket is not initialized";
return std::string();
}
std::string command = prepareXaddCommand(stream, message);
bool sent = _socket->writeBytes(command, nullptr);
if (!sent)
{
errMsg = "Cannot write bytes to socket";
return std::string();
}
return readXaddReply(errMsg);
}
std::string RedisClient::readXaddReply(std::string& errMsg)
{
// Read result
auto pollResult = _socket->isReadyToRead(-1);
if (pollResult == PollResultType::Error)
{
errMsg = "Error while polling for result";
return std::string();
}
// First line is the string length
auto lineResult = _socket->readLine(nullptr);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid)
{
errMsg = "Error while polling for result";
return std::string();
}
int stringSize;
{
std::stringstream ss;
ss << line.substr(1, line.size() - 1);
ss >> stringSize;
}
// Read the result, which is the stream id computed by the redis server
lineResult = _socket->readLine(nullptr);
lineValid = lineResult.first;
line = lineResult.second;
std::string streamId = line.substr(0, stringSize - 1);
return streamId;
}
bool RedisClient::sendCommand(const std::string& commands, int commandsCount, std::string& errMsg)
{
bool sent = _socket->writeBytes(commands, nullptr);
if (!sent)
{
errMsg = "Cannot write bytes to socket";
return false;
}
bool success = true;
for (int i = 0; i < commandsCount; ++i)
{
auto reply = readXaddReply(errMsg);
if (reply == std::string())
{
success = false;
}
}
return success;
}
} // namespace ix

View File

@ -1,63 +0,0 @@
/*
* IXRedisClient.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <ixwebsocket/IXSocket.h>
namespace ix
{
class RedisClient
{
public:
using OnRedisSubscribeResponseCallback = std::function<void(const std::string&)>;
using OnRedisSubscribeCallback = std::function<void(const std::string&)>;
RedisClient()
: _stop(false)
{
}
~RedisClient() = default;
bool connect(const std::string& hostname, int port);
bool auth(const std::string& password, std::string& response);
// Publish / Subscribe
bool publish(const std::string& channel, const std::string& message, std::string& errMsg);
bool subscribe(const std::string& channel,
const OnRedisSubscribeResponseCallback& responseCallback,
const OnRedisSubscribeCallback& callback);
// XADD
std::string xadd(
const std::string& channel,
const std::string& message,
std::string& errMsg);
std::string prepareXaddCommand(
const std::string& stream,
const std::string& message);
std::string readXaddReply(std::string& errMsg);
bool sendCommand(const std::string& commands, int commandsCount, std::string& errMsg);
void stop();
private:
std::string writeString(const std::string& str);
std::unique_ptr<Socket> _socket;
std::atomic<bool> _stop;
};
} // namespace ix

View File

@ -1,292 +0,0 @@
/*
* IXRedisServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXRedisServer.h"
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXSocketConnect.h>
#include <ixwebsocket/IXSocket.h>
#include <ixwebsocket/IXCancellationRequest.h>
#include <fstream>
#include <sstream>
#include <vector>
namespace ix
{
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
, _connectedClientsCount(0)
, _stopHandlingConnections(false)
{
;
}
RedisServer::~RedisServer()
{
stop();
}
void RedisServer::stop()
{
stopAcceptingConnections();
_stopHandlingConnections = true;
while (_connectedClientsCount != 0)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
_stopHandlingConnections = false;
SocketServer::stop();
}
void RedisServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
{
_connectedClientsCount++;
while (!_stopHandlingConnections)
{
std::vector<std::string> tokens;
if (!parseRequest(socket, tokens))
{
if (_stopHandlingConnections)
{
logError("Cancellation requested");
}
else
{
logError("Error parsing request");
}
break;
}
bool success = false;
// publish
if (tokens[0] == "COMMAND")
{
success = handleCommand(socket, tokens);
}
else if (tokens[0] == "PUBLISH")
{
success = handlePublish(socket, tokens);
}
else if (tokens[0] == "SUBSCRIBE")
{
success = handleSubscribe(socket, tokens);
}
if (!success)
{
if (_stopHandlingConnections)
{
logError("Cancellation requested");
}
else
{
logError("Error processing request for command: " + tokens[0]);
}
break;
}
}
cleanupSubscribers(socket);
logInfo("Connection closed for connection id " + connectionState->getId());
connectionState->setTerminated();
_connectedClientsCount--;
}
void RedisServer::cleanupSubscribers(std::unique_ptr<Socket>& socket)
{
std::lock_guard<std::mutex> lock(_mutex);
for (auto&& it : _subscribers)
{
it.second.erase(socket.get());
}
for (auto it : _subscribers)
{
std::stringstream ss;
ss << "Subscription id: " << it.first
<< " #subscribers: " << it.second.size();
logInfo(ss.str());
}
}
size_t RedisServer::getConnectedClientsCount()
{
return _connectedClientsCount;
}
bool RedisServer::startsWith(const std::string& str,
const std::string& start)
{
return str.compare(0, start.length(), start) == 0;
}
std::string RedisServer::writeString(const std::string& str)
{
std::stringstream ss;
ss << "$";
ss << str.size();
ss << "\r\n";
ss << str;
ss << "\r\n";
return ss.str();
}
bool RedisServer::parseRequest(
std::unique_ptr<Socket>& socket,
std::vector<std::string>& tokens)
{
// Parse first line
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
auto lineResult = socket->readLine(cb);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) return false;
std::string str = line.substr(1);
std::stringstream ss;
ss << str;
int count;
ss >> count;
for (int i = 0; i < count; ++i)
{
auto lineResult = socket->readLine(cb);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) return false;
int stringSize;
std::stringstream ss;
ss << line.substr(1, line.size() - 1);
ss >> stringSize;
auto readResult = socket->readBytes(stringSize, nullptr, nullptr);
if (!readResult.first) return false;
// read last 2 bytes (\r\n)
char c;
socket->readByte(&c, nullptr);
socket->readByte(&c, nullptr);
tokens.push_back(readResult.second);
}
return true;
}
bool RedisServer::handleCommand(
std::unique_ptr<Socket>& socket,
const std::vector<std::string>& tokens)
{
if (tokens.size() != 1) return false;
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
std::stringstream ss;
// return 2 nested arrays
ss << "*2\r\n";
//
// publish
//
ss << "*6\r\n";
ss << writeString("publish"); // 1
ss << ":3\r\n"; // 2
ss << "*0\r\n"; // 3
ss << ":1\r\n"; // 4
ss << ":2\r\n"; // 5
ss << ":1\r\n"; // 6
//
// subscribe
//
ss << "*6\r\n";
ss << writeString("subscribe"); // 1
ss << ":2\r\n"; // 2
ss << "*0\r\n"; // 3
ss << ":1\r\n"; // 4
ss << ":1\r\n"; // 5
ss << ":1\r\n"; // 6
socket->writeBytes(ss.str(), cb);
return true;
}
bool RedisServer::handleSubscribe(
std::unique_ptr<Socket>& socket,
const std::vector<std::string>& tokens)
{
if (tokens.size() != 2) return false;
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
std::string channel = tokens[1];
// Respond
socket->writeBytes("*3\r\n", cb);
socket->writeBytes(writeString("subscribe"), cb);
socket->writeBytes(writeString(channel), cb);
socket->writeBytes(":1\r\n", cb);
std::lock_guard<std::mutex> lock(_mutex);
_subscribers[channel].insert(socket.get());
return true;
}
bool RedisServer::handlePublish(
std::unique_ptr<Socket>& socket,
const std::vector<std::string>& tokens)
{
if (tokens.size() != 3) return false;
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
std::string channel = tokens[1];
std::string data = tokens[2];
// now dispatch the message to subscribers (write custom method)
std::lock_guard<std::mutex> lock(_mutex);
auto it = _subscribers.find(channel);
if (it == _subscribers.end())
{
// return the number of clients that received the message, 0 in that case
socket->writeBytes(":0\r\n", cb);
return true;
}
auto subscribers = it->second;
for (auto jt : subscribers)
{
jt->writeBytes("*3\r\n", cb);
jt->writeBytes(writeString("message"), cb);
jt->writeBytes(writeString(channel), cb);
jt->writeBytes(writeString(data), cb);
}
// return the number of clients that received the message.
std::stringstream ss;
ss << ":"
<< std::to_string(subscribers.size())
<< "\r\n";
socket->writeBytes(ss.str(), cb);
return true;
}
} // namespace ix

View File

@ -1,68 +0,0 @@
/*
* IXRedisServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocketServer.h"
#include "IXSocket.h"
#include <functional>
#include <memory>
#include <mutex>
#include <set>
#include <map>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix
{
class RedisServer final : public SocketServer
{
public:
RedisServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily);
virtual ~RedisServer();
virtual void stop() final;
private:
// Member variables
std::atomic<int> _connectedClientsCount;
// Subscribers
// We could store connection states in there, to add better debugging
// since a connection state has a readable ID
std::map<std::string, std::set<Socket*>> _subscribers;
std::mutex _mutex;
std::atomic<bool> _stopHandlingConnections;
// Methods
virtual void handleConnection(std::unique_ptr<Socket>,
std::shared_ptr<ConnectionState> connectionState) final;
virtual size_t getConnectedClientsCount() final;
bool startsWith(const std::string& str, const std::string& start);
std::string writeString(const std::string& str);
bool parseRequest(
std::unique_ptr<Socket>& socket,
std::vector<std::string>& tokens);
bool handlePublish(std::unique_ptr<Socket>& socket,
const std::vector<std::string>& tokens);
bool handleSubscribe(std::unique_ptr<Socket>& socket,
const std::vector<std::string>& tokens);
bool handleCommand(std::unique_ptr<Socket>& socket,
const std::vector<std::string>& tokens);
void cleanupSubscribers(std::unique_ptr<Socket>& socket);
};
} // namespace ix

View File

@ -1,61 +0,0 @@
/*
* IXSnakeConnectionState.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXRedisClient.h"
#include <future>
#include <ixwebsocket/IXConnectionState.h>
#include <string>
namespace snake
{
class SnakeConnectionState : public ix::ConnectionState
{
public:
std::string getNonce()
{
return _nonce;
}
void setNonce(const std::string& nonce)
{
_nonce = nonce;
}
std::string appkey()
{
return _appkey;
}
void setAppkey(const std::string& appkey)
{
_appkey = appkey;
}
std::string role()
{
return _role;
}
void setRole(const std::string& role)
{
_role = role;
}
ix::RedisClient& redisClient()
{
return _redisClient;
}
std::future<void> fut;
private:
std::string _nonce;
std::string _role;
std::string _appkey;
ix::RedisClient _redisClient;
};
} // namespace snake

View File

@ -1,297 +0,0 @@
/*
* IXSnakeProtocol.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSnakeProtocol.h"
#include "IXAppConfig.h"
#include "IXSnakeConnectionState.h"
#include "nlohmann/json.hpp"
#include <iostream>
#include <ixcrypto/IXHMac.h>
#include <ixwebsocket/IXWebSocket.h>
#include <ixcore/utils/IXCoreLogger.h>
#include <sstream>
namespace snake
{
void handleError(const std::string& action,
std::shared_ptr<ix::WebSocket> ws,
nlohmann::json pdu,
const std::string& errMsg)
{
std::string actionError(action);
actionError += "/error";
nlohmann::json response = {
{"action", actionError}, {"id", pdu.value("id", 1)}, {"body", {{"reason", errMsg}}}};
ws->sendText(response.dump());
}
void handleHandshake(std::shared_ptr<SnakeConnectionState> state,
std::shared_ptr<ix::WebSocket> ws,
const nlohmann::json& pdu)
{
std::string role = pdu["body"]["data"]["role"];
state->setNonce(generateNonce());
state->setRole(role);
nlohmann::json response = {
{"action", "auth/handshake/ok"},
{"id", pdu.value("id", 1)},
{"body",
{
{"data", {{"nonce", state->getNonce()}, {"connection_id", state->getId()}}},
}}};
auto serializedResponse = response.dump();
ws->sendText(serializedResponse);
}
void handleAuth(std::shared_ptr<SnakeConnectionState> state,
std::shared_ptr<ix::WebSocket> ws,
const AppConfig& appConfig,
const nlohmann::json& pdu)
{
auto secret = getRoleSecret(appConfig, state->appkey(), state->role());
if (secret.empty())
{
nlohmann::json response = {
{"action", "auth/authenticate/error"},
{"id", pdu.value("id", 1)},
{"body", {{"error", "authentication_failed"}, {"reason", "invalid secret"}}}};
ws->sendText(response.dump());
return;
}
auto nonce = state->getNonce();
auto serverHash = ix::hmac(nonce, secret);
std::string clientHash = pdu["body"]["credentials"]["hash"];
if (serverHash != clientHash)
{
nlohmann::json response = {
{"action", "auth/authenticate/error"},
{"id", pdu.value("id", 1)},
{"body", {{"error", "authentication_failed"}, {"reason", "invalid hash"}}}};
ws->sendText(response.dump());
return;
}
nlohmann::json response = {
{"action", "auth/authenticate/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
ws->sendText(response.dump());
}
void handlePublish(std::shared_ptr<SnakeConnectionState> state,
std::shared_ptr<ix::WebSocket> ws,
const nlohmann::json& pdu)
{
std::vector<std::string> channels;
auto body = pdu["body"];
if (body.find("channels") != body.end())
{
for (auto&& channel : body["channels"])
{
channels.push_back(channel);
}
}
else if (body.find("channel") != body.end())
{
channels.push_back(body["channel"]);
}
else
{
std::stringstream ss;
ss << "Missing channels or channel field in publish data";
handleError("rtm/publish", ws, pdu, ss.str());
return;
}
for (auto&& channel : channels)
{
std::stringstream ss;
ss << state->appkey() << "::" << channel;
std::string errMsg;
if (!state->redisClient().publish(ss.str(), pdu.dump(), errMsg))
{
std::stringstream ss;
ss << "Cannot publish to redis host " << errMsg;
handleError("rtm/publish", ws, pdu, ss.str());
return;
}
}
nlohmann::json response = {
{"action", "rtm/publish/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
ws->sendText(response.dump());
}
//
// FIXME: this is not cancellable. We should be able to cancel the redis subscription
//
void handleRedisSubscription(std::shared_ptr<SnakeConnectionState> state,
std::shared_ptr<ix::WebSocket> ws,
const AppConfig& appConfig,
const nlohmann::json& pdu)
{
std::string channel = pdu["body"]["channel"];
std::string subscriptionId = channel;
std::stringstream ss;
ss << state->appkey() << "::" << channel;
std::string appChannel(ss.str());
ix::RedisClient redisClient;
int port = appConfig.redisPort;
auto urls = appConfig.redisHosts;
std::string hostname(urls[0]);
// Connect to redis first
if (!redisClient.connect(hostname, port))
{
std::stringstream ss;
ss << "Cannot connect to redis host " << hostname << ":" << port;
handleError("rtm/subscribe", ws, pdu, ss.str());
return;
}
// Now authenticate, if needed
if (!appConfig.redisPassword.empty())
{
std::string authResponse;
if (!redisClient.auth(appConfig.redisPassword, authResponse))
{
std::stringstream ss;
ss << "Cannot authenticated to redis";
handleError("rtm/subscribe", ws, pdu, ss.str());
return;
}
}
int id = 0;
auto callback = [ws, &id, &subscriptionId](const std::string& messageStr) {
auto msg = nlohmann::json::parse(messageStr);
msg = msg["body"]["message"];
nlohmann::json response = {
{"action", "rtm/subscription/data"},
{"id", id++},
{"body", {{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
ws->sendText(response.dump());
};
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
std::stringstream ss;
ss << "Redis Response: " << redisResponse << "...";
ix::IXCoreLogger::Log(ss.str().c_str());
// Success
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
{"id", pdu.value("id", 1)},
{"body", {{"subscription_id", subscriptionId}}}};
ws->sendText(response.dump());
};
{
std::stringstream ss;
ss << "Subscribing to " << appChannel << "...";
ix::IXCoreLogger::Log(ss.str().c_str());
}
if (!redisClient.subscribe(appChannel, responseCallback, callback))
{
std::stringstream ss;
ss << "Error subscribing to channel " << appChannel;
handleError("rtm/subscribe", ws, pdu, ss.str());
return;
}
}
void handleSubscribe(std::shared_ptr<SnakeConnectionState> state,
std::shared_ptr<ix::WebSocket> ws,
const AppConfig& appConfig,
const nlohmann::json& pdu)
{
state->fut =
std::async(std::launch::async, handleRedisSubscription, state, ws, appConfig, pdu);
}
void handleUnSubscribe(std::shared_ptr<SnakeConnectionState> state,
std::shared_ptr<ix::WebSocket> ws,
const nlohmann::json& pdu)
{
// extract subscription_id
auto body = pdu["body"];
auto subscriptionId = body["subscription_id"];
state->redisClient().stop();
nlohmann::json response = {{"action", "rtm/unsubscribe/ok"},
{"id", pdu.value("id", 1)},
{"body", {{"subscription_id", subscriptionId}}}};
ws->sendText(response.dump());
}
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
std::shared_ptr<ix::WebSocket> ws,
const AppConfig& appConfig,
const std::string& str)
{
nlohmann::json pdu;
try
{
pdu = nlohmann::json::parse(str);
}
catch (const nlohmann::json::parse_error& e)
{
std::stringstream ss;
ss << "malformed json pdu: " << e.what() << " -> " << str << "";
nlohmann::json response = {{"body", {{"error", "invalid_json"},
{"reason", ss.str()}}}};
ws->sendText(response.dump());
return;
}
auto action = pdu["action"];
if (action == "auth/handshake")
{
handleHandshake(state, ws, pdu);
}
else if (action == "auth/authenticate")
{
handleAuth(state, ws, appConfig, pdu);
}
else if (action == "rtm/publish")
{
handlePublish(state, ws, pdu);
}
else if (action == "rtm/subscribe")
{
handleSubscribe(state, ws, appConfig, pdu);
}
else if (action == "rtm/unsubscribe")
{
handleUnSubscribe(state, ws, pdu);
}
else
{
std::cerr << "Unhandled action: " << action << std::endl;
}
}
} // namespace snake

View File

@ -1,26 +0,0 @@
/*
* IXSnakeProtocol.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <memory>
#include <string>
namespace ix
{
class WebSocket;
}
namespace snake
{
class SnakeConnectionState;
struct AppConfig;
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
std::shared_ptr<ix::WebSocket> ws,
const AppConfig& appConfig,
const std::string& str);
} // namespace snake

View File

@ -1,142 +0,0 @@
/*
* IXSnakeServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSnakeServer.h"
#include "IXAppConfig.h"
#include "IXSnakeConnectionState.h"
#include "IXSnakeProtocol.h"
#include <iostream>
#include <sstream>
#include <ixcore/utils/IXCoreLogger.h>
namespace snake
{
SnakeServer::SnakeServer(const AppConfig& appConfig)
: _appConfig(appConfig)
, _server(appConfig.port, appConfig.hostname)
{
_server.setTLSOptions(appConfig.socketTLSOptions);
if (appConfig.disablePong)
{
_server.disablePong();
}
std::stringstream ss;
ss << "Listening on " << appConfig.hostname << ":" << appConfig.port;
ix::IXCoreLogger::Log(ss.str().c_str());
}
//
// Parse appkey from this uri. Won't work if multiple args are present in the uri
// Uri: /v2?appkey=FC2F10139A2BAc53BB72D9db967b024f
//
std::string SnakeServer::parseAppKey(const std::string& path)
{
std::string::size_type idx;
idx = path.rfind('=');
if (idx != std::string::npos)
{
std::string appkey = path.substr(idx + 1);
return appkey;
}
else
{
return std::string();
}
}
bool SnakeServer::run()
{
auto factory = []() -> std::shared_ptr<ix::ConnectionState> {
return std::make_shared<SnakeConnectionState>();
};
_server.setConnectionStateFactory(factory);
_server.setOnConnectionCallback(
[this](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ix::ConnectionState> connectionState) {
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
webSocket->setOnMessageCallback(
[this, webSocket, state](const ix::WebSocketMessagePtr& msg) {
std::stringstream ss;
if (msg->type == ix::WebSocketMessageType::Open)
{
ss << "New connection" << std::endl;
ss << "id: " << state->getId() << std::endl;
ss << "Uri: " << msg->openInfo.uri << std::endl;
ss << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers)
{
ss << it.first << ": " << it.second << std::endl;
}
std::string appkey = parseAppKey(msg->openInfo.uri);
state->setAppkey(appkey);
// Connect to redis first
if (!state->redisClient().connect(_appConfig.redisHosts[0],
_appConfig.redisPort))
{
ss << "Cannot connect to redis host" << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
ss << "Closed connection"
<< " code " << msg->closeInfo.code << " reason "
<< msg->closeInfo.reason << std::endl;
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
std::stringstream ss;
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->errorInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
}
else if (msg->type == ix::WebSocketMessageType::Fragment)
{
ss << "Received message fragment" << std::endl;
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
ss << "Received " << msg->wireSize << " bytes" << std::endl;
processCobraMessage(state, webSocket, _appConfig, msg->str);
}
ix::IXCoreLogger::Log(ss.str().c_str());
});
});
auto res = _server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
return false;
}
_server.start();
return true;
}
void SnakeServer::runForever()
{
if (run())
{
_server.wait();
}
}
void SnakeServer::stop()
{
_server.stop();
}
} // namespace snake

View File

@ -1,31 +0,0 @@
/*
* IXSnakeServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXAppConfig.h"
#include <ixwebsocket/IXWebSocketServer.h>
#include <string>
namespace snake
{
class SnakeServer
{
public:
SnakeServer(const AppConfig& appConfig);
~SnakeServer() = default;
bool run();
void runForever();
void stop();
private:
std::string parseAppKey(const std::string& path);
AppConfig _appConfig;
ix::WebSocketServer _server;
};
} // namespace snake

View File

@ -1,14 +0,0 @@
{
"apps": {
"FC2F10139A2BAc53BB72D9db967b024f": {
"roles": {
"_sub": {
"secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba"
},
"_pub": {
"secret": "1c04DB8fFe76A4EeFE3E318C72d771db"
}
}
}
}
}

View File

@ -0,0 +1,9 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
if (@USE_ZLIB@)
find_dependency(ZLIB)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-targets.cmake")

11
ixwebsocket.pc.in Normal file
View File

@ -0,0 +1,11 @@
prefix=@prefix@
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: ixwebsocket
Description: websocket and http client and server library, with TLS support and very few dependencies
Version: @CMAKE_PROJECT_VERSION@
Libs: -L${libdir} -lixwebsocket
Cflags: -I${includedir}
Requires: @requires@

125
ixwebsocket/IXBase64.h Normal file
View File

@ -0,0 +1,125 @@
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <cstdint>
#include <string>
namespace macaron {
class Base64 {
public:
static std::string Encode(const std::string data) {
static constexpr char sEncodingTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::string Decode(const std::string& input, std::string& out) {
static constexpr unsigned char kDecodingTable[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
size_t in_len = input.size();
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=') out_len--;
if (input[in_len - 2] == '=') out_len--;
out.resize(out_len);
for (size_t i = 0, j = 0; i < in_len;) {
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
}
return "";
}
};
}
#endif /* _MACARON_BASE64_H_ */

View File

@ -12,10 +12,8 @@ namespace ix
{
Bench::Bench(const std::string& description)
: _description(description)
, _start(std::chrono::high_resolution_clock::now())
, _reported(false)
{
;
reset();
}
Bench::~Bench()
@ -26,19 +24,38 @@ namespace ix
}
}
void Bench::reset()
{
_start = std::chrono::high_resolution_clock::now();
_reported = false;
}
void Bench::report()
{
auto now = std::chrono::high_resolution_clock::now();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
_ms = milliseconds.count();
std::cerr << _description << " completed in " << _ms << "ms" << std::endl;
_duration = microseconds.count();
std::cerr << _description << " completed in " << _duration << " us" << std::endl;
setReported();
}
void Bench::record()
{
auto now = std::chrono::high_resolution_clock::now();
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
_duration = microseconds.count();
}
void Bench::setReported()
{
_reported = true;
}
uint64_t Bench::getDuration() const
{
return _ms;
return _duration;
}
} // namespace ix

View File

@ -3,9 +3,10 @@
* Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <chrono>
#include <stdint.h>
#include <cstdint>
#include <string>
namespace ix
@ -16,13 +17,16 @@ namespace ix
Bench(const std::string& description);
~Bench();
void reset();
void record();
void report();
void setReported();
uint64_t getDuration() const;
private:
std::string _description;
std::chrono::time_point<std::chrono::high_resolution_clock> _start;
uint64_t _ms;
uint64_t _duration;
bool _reported;
};
} // namespace ix

View File

@ -6,6 +6,7 @@
#include "IXCancellationRequest.h"
#include <cassert>
#include <chrono>
namespace ix
@ -13,6 +14,8 @@ namespace ix
CancellationRequest makeCancellationRequestWithTimeout(
int secs, std::atomic<bool>& requestInitCancellation)
{
assert(secs > 0);
auto start = std::chrono::system_clock::now();
auto timeout = std::chrono::seconds(secs);

View File

@ -31,6 +31,11 @@ namespace ix
return std::make_shared<ConnectionState>();
}
void ConnectionState::setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback)
{
_onSetTerminatedCallback = callback;
}
bool ConnectionState::isTerminated() const
{
return _terminated;
@ -39,5 +44,30 @@ namespace ix
void ConnectionState::setTerminated()
{
_terminated = true;
if (_onSetTerminatedCallback)
{
_onSetTerminatedCallback();
}
}
const std::string& ConnectionState::getRemoteIp()
{
return _remoteIp;
}
int ConnectionState::getRemotePort()
{
return _remotePort;
}
void ConnectionState::setRemoteIp(const std::string& remoteIp)
{
_remoteIp = remoteIp;
}
void ConnectionState::setRemotePort(int remotePort)
{
_remotePort = remotePort;
}
} // namespace ix

View File

@ -7,12 +7,15 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <stdint.h>
#include <string>
namespace ix
{
using OnSetTerminatedCallback = std::function<void()>;
class ConnectionState
{
public:
@ -25,12 +28,27 @@ namespace ix
void setTerminated();
bool isTerminated() const;
const std::string& getRemoteIp();
int getRemotePort();
static std::shared_ptr<ConnectionState> createConnectionState();
private:
void setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback);
void setRemoteIp(const std::string& remoteIp);
void setRemotePort(int remotePort);
protected:
std::atomic<bool> _terminated;
std::string _id;
OnSetTerminatedCallback _onSetTerminatedCallback;
static std::atomic<uint64_t> _globalId;
std::string _remoteIp;
int _remotePort;
friend class SocketServer;
};
} // namespace ix

View File

@ -4,12 +4,42 @@
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
//
// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to returns wchar_t
// which is different from all other platforms. We want the non unicode version.
// See https://github.com/microsoft/vcpkg/pull/11030
// We could do this in IXNetSystem.cpp but so far we are only using gai_strerror in here.
//
#ifdef _UNICODE
#undef _UNICODE
#endif
#ifdef UNICODE
#undef UNICODE
#endif
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include <chrono>
#include <string.h>
#include <thread>
#include <utility>
// mingw build quirks
#if defined(_WIN32) && defined(__GNUC__)
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV NI_NUMERICSERV
#endif
#ifndef AI_ADDRCONFIG
#define AI_ADDRCONFIG LUP_ADDRCONFIG
#endif
#endif
#ifdef __APPLE__
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
#endif
namespace ix
{
@ -25,7 +55,7 @@ namespace ix
;
}
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
DNSLookup::AddrInfoPtr DNSLookup::getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg)
{
@ -44,10 +74,10 @@ namespace ix
errMsg = gai_strerror(getaddrinfo_result);
res = nullptr;
}
return res;
return AddrInfoPtr{ res, freeaddrinfo };
}
struct addrinfo* DNSLookup::resolve(std::string& errMsg,
DNSLookup::AddrInfoPtr DNSLookup::resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable)
{
@ -55,7 +85,7 @@ namespace ix
: resolveUnCancellable(errMsg, isCancellationRequested);
}
struct addrinfo* DNSLookup::resolveUnCancellable(
DNSLookup::AddrInfoPtr DNSLookup::resolveUnCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
@ -70,7 +100,7 @@ namespace ix
return getAddrInfo(_hostname, _port, errMsg);
}
struct addrinfo* DNSLookup::resolveCancellable(
DNSLookup::AddrInfoPtr DNSLookup::resolveCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
@ -133,7 +163,7 @@ namespace ix
// gone, so we use temporary variables (res) or we pass in by copy everything that
// getAddrInfo needs to work.
std::string errMsg;
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
auto res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock())
{
@ -157,13 +187,13 @@ namespace ix
return _errMsg;
}
void DNSLookup::setRes(struct addrinfo* addr)
void DNSLookup::setRes(DNSLookup::AddrInfoPtr addr)
{
std::lock_guard<std::mutex> lock(_resMutex);
_res = addr;
_res = std::move(addr);
}
struct addrinfo* DNSLookup::getRes()
DNSLookup::AddrInfoPtr DNSLookup::getRes()
{
std::lock_guard<std::mutex> lock(_resMutex);
return _res;

View File

@ -12,6 +12,7 @@
#include "IXCancellationRequest.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include <mutex>
#include <set>
@ -24,20 +25,21 @@ namespace ix
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
{
public:
using AddrInfoPtr = std::shared_ptr<addrinfo>;
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup() = default;
struct addrinfo* resolve(std::string& errMsg,
AddrInfoPtr resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable = true);
private:
struct addrinfo* resolveCancellable(std::string& errMsg,
AddrInfoPtr resolveCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveUnCancellable(std::string& errMsg,
AddrInfoPtr resolveUnCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
static struct addrinfo* getAddrInfo(const std::string& hostname,
AddrInfoPtr getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg);
@ -46,15 +48,15 @@ namespace ix
void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg();
void setRes(struct addrinfo* addr);
struct addrinfo* getRes();
void setRes(AddrInfoPtr addr);
AddrInfoPtr getRes();
std::string _hostname;
int _port;
int64_t _wait;
const static int64_t kDefaultWait;
struct addrinfo* _res;
AddrInfoPtr _res;
std::mutex _resMutex;
std::string _errMsg;

View File

@ -1,5 +1,5 @@
/*
* IXExponentialBackoff.h
* IXExponentialBackoff.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
@ -10,16 +10,35 @@
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
uint32_t maxWaitBetweenReconnectionRetries)
uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries)
{
uint32_t wait_time = (retry_count < 26) ? (std::pow(2, retry_count) * 100) : 0;
// It's easy with a power function to go beyond 2^32, and then
// have unexpected results, so prepare for that
const uint32_t maxRetryCountWithoutOverflow = 26;
if (wait_time > maxWaitBetweenReconnectionRetries || wait_time == 0)
uint32_t waitTime = 0;
if (retryCount < maxRetryCountWithoutOverflow)
{
wait_time = maxWaitBetweenReconnectionRetries;
waitTime = std::pow(2, retryCount) * 100;
}
return wait_time;
if (waitTime < minWaitBetweenReconnectionRetries)
{
waitTime = minWaitBetweenReconnectionRetries;
}
if (waitTime > maxWaitBetweenReconnectionRetries)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
if (retryCount >= maxRetryCountWithoutOverflow)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
return waitTime;
}
} // namespace ix

View File

@ -10,6 +10,7 @@
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
uint32_t maxWaitBetweenReconnectionRetries);
uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries);
} // namespace ix

View File

@ -4,6 +4,14 @@
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
// Using inet_addr will trigger an error on uwp without this
// FIXME: use a different api
#ifdef _WIN32
#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif
#endif
#include "IXGetFreePort.h"
#include <ixwebsocket/IXNetSystem.h>
@ -23,7 +31,7 @@ namespace ix
int getAnyFreePort()
{
int sockfd;
socket_t sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
return getAnyFreePortRandom();

Some files were not shown because too many files have changed in this diff Show More