Compare commits

...

949 Commits

Author SHA1 Message Date
22fc8e981d do not run run the tsan + openssl test on ci 2020-05-03 22:16:14 -07:00
fb0de53efd dummy commit 2020-05-03 19:09:23 -07:00
62a7483e41 rename lua test file + fix C++ warnings 2020-04-28 22:22:45 -07:00
36fbdd0daa can receive string message 2020-04-28 22:15:43 -07:00
feff2e38c0 can send message, set a url and start the connection 2020-04-28 19:11:06 -07:00
a040ff06e8 rename LuaPlayer.hpp to LuaWebSocket.h 2020-04-28 18:37:19 -07:00
755493eaf3 test pre-commit 2020-04-28 14:39:01 -07:00
4c61aede2e add skeleton lua bindings 2020-04-28 14:39:01 -07:00
fbca513008 bump version 2020-04-27 12:36:56 -07:00
33ebd00932 fix cmake tls backend option parsing 2020-04-27 11:29:50 -07:00
fbe5e74109 fix openssl cmake errors 2020-04-27 10:59:47 -07:00
a9f5d5353f fix cmake syntax error and convert some errors to fatal errors 2020-04-27 10:29:27 -07:00
22e0083832 CMake TLS cleanup 2020-04-27 10:09:51 -07:00
5632360fbd (http client) Set default values for most HttpRequestArgs struct members (fix #185) 2020-04-27 09:43:31 -07:00
20294841b3 ci - on windows, disable building tls as it is too slow (> 15minutes total) 2020-04-25 15:58:56 -07:00
74efdfebba remove bundled mbedtls 2020-04-25 15:41:39 -07:00
0ab04f51fe (ssl) Default to OpenSSL on Windows, since it can load the system certificates by default 2020-04-25 15:36:31 -07:00
4ed7968b05 ci / try to force an openssl 1.1 install on mac 2020-04-25 11:53:09 -07:00
287e48962f bump version number 2020-04-25 11:41:58 -07:00
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
2802cad8c4 more tls in memory certs doc + bump file format 2020-04-24 15:50:39 -07:00
9f770b10c0 clang-format 2020-04-24 15:34:00 -07:00
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
646b18bf28 core logger support multiple level + switch ixbots to user corelogger instead of spdlog 2020-04-24 15:17:50 -07:00
0670954faf unittest / remove deleted file reference 2020-04-24 14:23:38 -07:00
2469d7102e missing headers for url parsing 2020-04-24 14:13:15 -07:00
79acb915ce merge the 2 url parsing file into one, fix a silly build error 2020-04-24 14:08:59 -07:00
bad3adb6b4 cmake pb with renamed file 2020-04-24 12:55:00 -07:00
e3dd4e60c0 remove deleted IXSelectInterruptEventFd file reference in cmake 2020-04-24 12:52:13 -07:00
c70f1d09a8 include all ssl backends inside special per backend macro 2020-04-24 12:47:47 -07:00
4b2b133c10 fix #182 2020-04-22 14:26:16 -07:00
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
5860c5c80b Fixes #179 (#180) 2020-04-20 22:59:20 -07:00
36257cbfe4 update build doc 2020-04-18 03:49:26 -07:00
68ee57a6a7 fix ixbots unittest 2020-04-17 10:09:52 -07:00
9d79596629 (ixbots) display sent/receive message, per seconds as accumulated 2020-04-17 09:56:09 -07:00
0b6fd989f5 (ws) add a --logfile option to configure all logs to go to a file 2020-04-17 09:35:47 -07:00
1c19a57fef missing atomic header in IXCobraBot.h / should fix windows build 2020-04-16 22:54:43 -07:00
a2abe861d3 (cobra bots) add a utility class to factor out the common bots features (heartbeat) and move all bots to used it + convert cobra_subscribe to be a bot and add a unittest for it 2020-04-16 21:58:10 -07:00
0f5d15aa11 (cobra bots) add a utility class to factor out the common bots features (heartbeat) and move cobra to sentry bot to use it 2020-04-16 14:49:49 -07:00
ccfd196863 clang-format 2020-04-16 11:58:06 -07:00
9b8cfa0a37 (websocket) add a positive number to the heartbeat message sent, incremented each time the heartbeat is sent 2020-04-15 18:33:36 -07:00
85f6b1e0b7 fix compiler warning in ixsentry about unused parameters in uploadPayload method 2020-04-15 18:05:00 -07:00
64754df66c (ixcobra) change cobra event callback to use a struct instead of several objects, which is more flexible/extensible 2020-04-15 17:38:39 -07:00
71a421eefc remove file that does not exist yet but which is referenced in CMake 2020-04-15 17:03:34 -07:00
386ef3ab04 (ixcobra) make CobraConnection_EventType an enum class (CobraEventType) 2020-04-15 16:59:17 -07:00
2c4bf8f4bd (ixsentry) add a library method to upload a payload directly to sentry 2020-04-14 22:02:51 -07:00
3a2c446225 missing headers in IXWebSocketCloseInfo.h, ,IXWebSocketErrorInfo.h and IXWebSocketOpenInfo.h 2020-04-14 21:52:27 -07:00
35630fe7ed new makefile target + better error description in Socket::readBytes 2020-04-14 21:50:56 -07:00
bea582c208 cobra subscriber in fluentd mode insert a created_at timestamp entry 2020-04-14 15:30:30 -07:00
783d1d92dd snake server / handle invalid incoming json messages 2020-04-14 15:12:35 -07:00
415f6b4832 (unittest) remove cmake reference to deleted file 2020-04-13 22:07:18 -07:00
13d3300a40 fix unittest / simple build thing 2020-04-13 22:00:48 -07:00
432f0570f4 (websocket) WebSocketMessagePtr is a unique_ptr instead of a shared_ptr 2020-04-13 21:56:01 -07:00
37a054723a (websocket) use persistent member variable as temp variables to encode/decode zlib messages in order to reduce transient allocations 2020-04-13 21:38:15 -07:00
c57cf413fb (ws) add a --runtime option to ws cobra_subscribe to optionally limit how much time it will run 2020-04-13 19:03:53 -07:00
f1c106728b (third_party deps) fix #177, update bundled spdlog to 1.6.0 2020-04-11 13:32:16 -07:00
2eb5c9480e Create stale.yml 2020-04-06 11:20:01 -07:00
f9d75c9374 (windows) when using OpenSSL, the system store is used to populate the cacert. No need to ship a cacert.pem file with your app. 2020-04-04 18:33:01 -07:00
d1cd5e62ac update doc 2020-04-04 17:54:15 -07:00
f3b97097cd (windows) ci: windows build with TLS (mbedtls) + verify that we can be build with OpenSSL 2020-04-04 17:49:52 -07:00
605be72579 use default mkdocs theme 2020-04-04 11:05:14 -07:00
49ff3789b5 mkdocs / use codehilite engine for syntax highlighting 2020-03-31 23:18:47 -07:00
96d61c6e5b doc - add code block highlighting 2020-03-31 20:56:51 -07:00
9a23c5aaac (doc) use c++ instead of cpp to mark a block of C++ code 2020-03-31 20:29:40 -07:00
d81e4d4fc0 setHeartBeatPeriod -> setPingInterval (in doc + disabled unittests) 2020-03-31 18:36:50 -07:00
bd44d32fdb try the material theme for the documentation 2020-03-31 18:32:48 -07:00
b6abc12ecd Add documentation about how to make a pull request to get the latest version of the package in vcpkg (#173) 2020-03-31 15:58:01 -07:00
2268b743ae add broadcasting test where 10 clients exchange messages, to try to trigger threading errors 2020-03-30 22:27:41 -07:00
1d3db5f75b (cobra to statsd bot) add ability to extract a numerical value and send a timer event to statsd, with the --timer option 2020-03-30 16:08:47 -07:00
296762ce06 add a docker deploy makefile target to build docker and push the built container in one shot 2020-03-29 22:08:36 -07:00
e465f7af52 (cobra to statsd bot) bot init was missing + capture socket error 2020-03-29 22:03:27 -07:00
f8bf1fe7cd (cobra to statsd bot) add ability to extract a numerical value and send a gauge event to statsd 2020-03-29 19:32:43 -07:00
cfa5718e40 (ws cobra subscriber) use a Json::StreamWriter to write to std::cout, and save one std::string allocation for each message printed 2020-03-29 15:24:46 -07:00
40c619c1ec (docker) trim down docker image (300M -> 12M) / binary built without symbol and size optimization, and source code not copied over 2020-03-29 13:06:44 -07:00
22b02e0e5c update doc 2020-03-28 10:46:42 -07:00
738a3bf1c5 update bundled jsoncpp to 1.9.3
(still comment the deprecation warning, which we should eventually fix ...)
2020-03-28 10:44:05 -07:00
598fb071e3 have some make target compile in release with debug 2020-03-28 10:33:22 -07:00
686aface26 bump version to 9.1.3 2020-03-28 10:33:05 -07:00
3073dd3f06 alpine docker file installs ca-certificates (for TLS) 2020-03-28 10:32:25 -07:00
68c64f3f69 use alpine as the docker distribution 2020-03-27 17:38:35 -07:00
771ebb2a4c (mac ssl) rename DarwinSSL -> SecureTransport (see this too -> https://github.com/curl/curl/issues/3733) 2020-03-26 19:40:52 -07:00
0fffb1e894 (websocket) fix data race accessing _socket object without mutex protection when calling wakeUpFromPoll in WebSocketTransport.cpp 2020-03-26 19:31:59 -07:00
18164c0c38 New CI builder: Mac + MbedTLS + Thread Sanitizer 2020-03-26 19:16:04 -07:00
d2db7310ff (ixcobra) add explicit event types for handshake, authentication and subscription failure, and handle those by exiting in ws_cobra_subcribe and friends 2020-03-26 18:54:28 -07:00
09e4584fc8 New CI builder: Mac + OpenSSL + Thread Sanitizer 2020-03-26 16:46:47 -07:00
da36856d85 Only find zlib if it has not already been found. (#169) 2020-03-26 09:39:51 -07:00
dffa759f71 move IXBench code under ixwebsocker folder 2020-03-24 20:53:25 -07:00
61e789d6a4 formatting 2020-03-24 20:37:55 -07:00
37cb2cc266 (ws connect) display statistics about how much time it takes to stop the connection / cf #168 2020-03-24 20:29:09 -07:00
179e17895d unique_ptr for sockets 2020-03-24 12:48:55 -07:00
9f818c7acf (socket) selectInterrupt member is an unique_ptr instead of being a shared_ptr 2020-03-24 10:00:41 -07:00
9dcc2538ae (websocket) reset per-message deflate codec everytime we connect to a server/client 2020-03-23 18:46:30 -07:00
f41a54186c (websocket) fix #167, a long standing issue with sending empty messages with per-message deflate extension (and hopefully other zlib bug) 2020-03-23 15:21:53 -07:00
e0733d205c fix linux linker error 2020-03-22 21:59:30 -07:00
f72f845ad2 trim headers and unused code in IXUdpSocket 2020-03-22 21:51:41 -07:00
b7e7837d76 fix simple compile error 2020-03-22 19:43:43 -07:00
fe966b19c7 re-enable unittests 2020-03-22 19:39:28 -07:00
a0ffb2ba53 cobra to statsd bot ported to windows + add unittest 2020-03-22 19:37:04 -07:00
5ad54a8904 pre-commit / fix simple file trailing things 2020-03-21 19:31:38 -07:00
10e132e8ef remove std::cerr in IXRedisServer which triggers a tsan error 2020-03-20 17:50:08 -07:00
5ce846f48b indent files 2020-03-20 17:00:18 -07:00
1d6373335c (websocket+tls) fix hang in tls handshake which could lead to ANR, discovered through unittesting. 2020-03-20 16:57:27 -07:00
829751b7af (cobra) CobraMetricsPublisher can be configure with an ix::CobraConfig + more unittest use SSL in server + client 2020-03-20 12:22:00 -07:00
5691b55967 (unittest) / try to run the cobra 2 sentry bot test with SSL if the platform supports it 2020-03-19 18:50:46 -07:00
575bceb1ec add make target for ubsan, tsan and asan, and enable running the unittest on mac with tsan 2020-03-18 20:53:54 -07:00
6085839ef7 minor refactoring 2020-03-18 11:45:28 -07:00
696d802703 bump version 2020-03-18 01:15:15 -07:00
b287730c19 Simplify ping/pong based heartbeat implementation 2020-03-18 01:14:08 -07:00
d6f534de06 (ws) ws echo_server gains a new option (-p) to disable responding to pings with pongs 2020-03-18 00:01:57 -07:00
8ec515f292 (ws) ws connect gains a new option to set the interval at which to send pings 2020-03-17 23:54:32 -07:00
c6204f4d90 tweak mkdoc action 2020-03-17 10:47:06 -07:00
7dfad9c0cc more docs 2020-03-17 10:41:20 -07:00
21fac0be6c Create mkdocs.yaml github action from the web-ui 2020-03-17 10:22:58 -07:00
0bddf5e096 remove workflow created manually 2020-03-17 10:21:51 -07:00
946a8231e0 add ci to build documentation 2020-03-17 10:14:41 -07:00
49d1e8493a update build badge on doc 2020-03-17 10:09:41 -07:00
6198657dd6 do not trigger unittest when the docs is changed 2020-03-17 08:54:31 -07:00
385d6f5f4a (doc) move tls options to its own section 2020-03-17 08:49:42 -07:00
3919153a7b close #164 / add reference to tls option which must be set to true in server mode 2020-03-17 08:45:39 -07:00
e8f81776f9 (cobra to sentry bot + docker) default docker file uses mbedtls + ws cobra_to_sentry pass tls options to sentryClient. 2020-03-16 10:05:21 -07:00
0bb5462504 Feature/ci windows (#163)
* win only

* disable ixcrypto mbedtls search on windows

* ws cmakefile do not search for openssl

* ci builds files on top of cmaking

* ci builds files on top of cmaking / syntax tweak

* use gha-setup-vsdevenv syntax

* build fix and hacks

* try to run unittest on win

* try to run unittest on win (syntax error)

* unittest wip

* wip

* wip again

* wip again (working-directory)

* cleanup

* dumb compile error
2020-03-15 18:38:09 -07:00
44f599747e (cobra client) ws cobra subscribe resubscribe at latest position after being disconnected 2020-03-13 17:30:31 -07:00
9801ebdb36 (cobra client) can subscribe with a position 2020-03-13 16:06:13 -07:00
332ffb0603 (cobra client) pass the message position to the subscription data callback 2020-03-13 12:49:37 -07:00
90df3d1805 (openssl tls backend) Fix a hand in OpenSSL when using TLS v1.3 ... by disabling TLS v1.3 2020-03-12 16:27:25 -07:00
bda1bb6ab4 expose a way to set tls options for a sentry client, for testing 2020-03-12 16:18:28 -07:00
d4e1f71e3c (cobra2sentry bot) take a sentry client as input instead of a dsn 2020-03-12 12:30:58 -07:00
adf6aa1d6c (cobra2sentry bot) remove the jobs option passed to ws, and only use one sentry sender 2020-03-12 12:24:25 -07:00
cb1f9f5a44 clang formatting 2020-03-12 12:15:56 -07:00
83ae105edb minor refactoring to delete files which are not needed 2020-03-12 12:13:31 -07:00
7642ccc99e (unittest) fix silly compile error with renaming of Logger to TLogger 2020-03-12 11:15:54 -07:00
cb1ec7dc96 add unittest for cobra to sentry bots 2020-03-12 09:07:01 -07:00
09b9483ddf fix casing problem with cmake filename 2020-03-11 16:04:16 -07:00
27a8ae309f build failure on Linux 2020-03-11 15:59:48 -07:00
3df7c942d7 move sentry and statsd cobra ws commands into a new ixbots folder 2020-03-11 15:55:56 -07:00
6a4d69afc5 (cobra) IXCobraConfig struct has tlsOptions and per message deflate options 2020-03-11 12:40:32 -07:00
0a11132b07 (cobra) add IXCobraConfig struct to pass cobra config around 2020-03-11 10:48:41 -07:00
cb9f0cb968 (doc) mention that OpenSSL can be used on Windows 2020-03-11 10:18:48 -07:00
b1f30bb40f (ws cobra_subscribe) add a --fluentd option to wrap a message in an enveloppe so that fluentd can recognize it 2020-03-09 15:25:43 -07:00
4ef04b8339 (websocket server) fix regression with disabling zlib extension on the server side. If a client does not support this extension the server will handle it fine. We still need to figure out how to disable the option. cc #160 2020-03-02 16:53:08 -08:00
e581f29b42 Move zlib include_directories before add_subdirectory (#159) 2020-02-28 09:30:37 -08:00
a42f115f79 compatibility: add node.js example server 2020-02-26 12:17:34 -08:00
5ce1a596cf add a python echo server that does not close the connection after each received messages 2020-02-26 12:11:31 -08:00
21db7b6c5b add a simple pytho echo client 2020-02-26 11:50:24 -08:00
e15a2900e7 (websocket) traffic tracker received bytes is message size while it should be wire size 2020-02-26 11:24:41 -08:00
140a21c8b3 (ws_connect) display sent/received bytes statistics on exit 2020-02-26 11:23:36 -08:00
6d0c568aaa update doc / fix incorrect comment about sending defaultint to binary mode 2020-02-24 16:24:32 -08:00
c96abcef1c build status github badge 2020-02-23 09:46:08 -08:00
4a9b0b9dfd (server) give thread name to some usual worker threads / unittest is broken !! 2020-02-23 09:44:58 -08:00
8837d5e784 (websocket server) fix regression from 8.1.2, where per-deflate message compression was always disabled 2020-02-22 10:15:43 -08:00
242c945400 (client + server) Fix #155 / http header parser should treat the space(s) after the : delimiter as optional. Fixing this bug made us discover that websocket sub-protocols are not properly serialiazed, but start with a , 2020-02-21 14:05:38 -08:00
feab4dee0f split httpd test case into 2 test cases 2020-02-21 12:24:22 -08:00
8175829b4b unittest / add extra test for checking headers 2020-02-21 12:22:37 -08:00
4c66a7561e (WebSocketServer) add option to disable deflate compression, exposed with the -x option to ws echo_server 2020-02-18 21:38:28 -08:00
111475e65c (ws cobra to statsd and sentry sender) exit if no messages are received for one minute, which is a sign that something goes wrong on the server side. That should be changed to be configurable in the future 2020-02-18 12:43:07 -08:00
45061b0b14 (http client + sentry minidump upload) Multipart stream closing boundary is invalid + mark some options as mandatory in the command line toolsj 2020-02-13 12:25:18 -08:00
1bb847a51c bump version for #154 fix which was about removing the unused subtree which was causing some way of installing to break 2020-02-12 11:21:46 -08:00
ce9feeafdf try to delete subtree usocket again 2020-02-12 10:08:42 -08:00
415f0b3e6d CI/CD no need to run redis anymore, as we have our fake limited one 2020-02-07 17:15:16 -08:00
94431756ff trigger a github action 2020-02-07 17:08:25 -08:00
5f6c54bb90 (snake) add an option to disable answering pongs as response to pings, to test cobra client behavior with hanged connections 2020-01-31 16:55:54 -08:00
f994a41845 (IXCobraConnection) set a ping timeout of 90 seconds. If no pong messages are received as responses to ping for a while, give up and close the connection 2020-01-31 16:25:45 -08:00
f3760318b7 (cobra to sentry) remove noisy logging 2020-01-31 16:07:36 -08:00
c2362e6875 (ixcobra) check if we are authenticated in publishNext before trying to publish a message 2020-01-30 20:52:11 -08:00
d91b24723d version 8.0.2: add severity level for log sentry events (#152)
* script to update version

* version 8.0.2: add severity level for log sentry events
2020-01-28 14:04:18 -08:00
2d28b7d4ff bump version and update changelog 2020-01-28 10:50:03 -08:00
86d3fc8621 quit checkConnection after sleeping if requested to stop (iterate on #151) 2020-01-28 10:46:04 -08:00
422c7ff855 wait with a condition variable instead of a this_thread::sleep_for so that waiting can be cancelled when we stop/shutdown the data thread (see #151) 2020-01-28 10:04:32 -08:00
1c7ccbae12 bump version 2020-01-27 17:41:27 -08:00
ed2a81f115 Fix #148 2020-01-27 17:29:44 -08:00
7ed8ac208a bind returns zero on success (#149) 2020-01-27 17:15:44 -08:00
aa12098cb5 add -6 option to ws echo_server / cf #148 2020-01-26 16:44:44 -08:00
5d4bb90703 (SocketServer) add ability to bind on an ipv6 address 2020-01-26 16:21:56 -08:00
fad9f89846 (ws) add a dnslookup sub-command, to get the ip address of a remote host 2020-01-26 16:21:56 -08:00
527308a049 Remove 'handleConnect() done' info message (#147) 2020-01-26 10:03:17 -08:00
68b318ab97 Doc update for Conan (#146)
* Doc update for Conan

* Migration notice
2020-01-17 07:15:41 -08:00
65bae2736d fix #144 - get rid of stubbed windows ssl schannel backend 2020-01-14 13:39:48 -08:00
a923caec0b install gdb in the alpine dockerfile 2020-01-14 13:37:28 -08:00
4d7332c4ee activate chat test on Windows 2020-01-13 16:44:10 -08:00
4f3f1f3e4c simplify socket interface 2020-01-12 22:30:16 -08:00
2a954b5b5b use curl code for large apple ssl send 2020-01-12 22:04:06 -08:00
b96b3b099f test broadcast server 2020-01-12 21:32:26 -08:00
bb31612ebe more doc 2020-01-12 11:16:54 -08:00
d2c5ab1cc4 (openssl + mbedssl) fix #140, can send large files with ws send over ssl / still broken with apple ssl 2020-01-12 11:08:44 -08:00
a01584ad9d compatibility websocket server / add non ssl echo_server which does not run into the ssl error (#140) 2020-01-12 10:34:33 -08:00
9651f3823d (apple ssl) model write method after the OpenSSL one for consistency
More test code for #140.
2020-01-10 18:32:34 -08:00
0544cdedeb fix broken autobahn link 2020-01-09 15:30:33 -08:00
df0239ae68 poll: handle POLLNVAL 2020-01-09 13:45:58 -08:00
c8bf2a0d82 formatting 2020-01-09 13:45:31 -08:00
4d96804b22 ws send / detect disconnection earlier 2020-01-09 13:30:08 -08:00
ce9db42c23 socket polling / handle more error case such as hanged socket 2020-01-09 12:26:57 -08:00
a844dbc587 websocket closing / handle failure to flush send buffer as an error case 2020-01-09 12:17:37 -08:00
28952cb0b0 ws send / check that we are connected when waiting for the send buffer to be flushed, and if so, abort 2020-01-09 12:16:12 -08:00
fe29579755 refactor receiving socket code in its own method 2020-01-09 12:00:42 -08:00
b816f1fbda Allow custom OpenSSL lib, only include openssl/x509v3.h when used. (#143)
* Allow custom OpenSSL lib, only include openssl/x509v3.h when used.

Using fnmatch on Unix systems, PathMatchSpecA is the best WINAPI equivalent.

* Moved shlwapi into WIN32 block.
2020-01-08 13:57:57 -08:00
1320e4ddaf Windows ws build fix 2020-01-07 17:37:38 -08:00
f4a7277d61 using centos8 for the default Dockerfile, to get a gdb that display source code when running on OpenShift 2020-01-07 14:58:40 -08:00
34d7b18c85 Allow configuration of Mbed TLS use. (#142)
* Allow configuration of Mbed TLS use.

* Added option for OpenSSL support.

* Fixed elseif/else mixup from 5ebad24040
2020-01-07 14:57:47 -08:00
d72d516a92 Fixed build error with vcpkg installed. (#141) 2020-01-07 10:40:36 -08:00
f6c482c65d (apple ssl) unify read and write ssl utility code 2020-01-06 15:19:14 -08:00
dec8a2b9ab apple ssl: aesthetic change (get rid of yoda comparisons/conditionals) 2020-01-06 14:45:05 -08:00
a5bc39be55 (websocket client) better error propagation when errors are detected while sending data + (ws send) detect failures to send big files, terminate in those cases and report error (troubleshooting #140) 2020-01-06 14:34:43 -08:00
c62ad5f466 add reference ssl echo server using websockets and nginx reverse proxy to terminate ssl 2020-01-05 15:26:35 -08:00
effa115ed2 Merge commit 'c992cb4e42cc223f67ede0e48d7ff3f4947af0c6' as 'test/compatibility/C/uWebSockets' 2020-01-04 15:41:03 -08:00
c992cb4e42 Squashed 'test/compatibility/C/uWebSockets/' content from commit 03681cc
git-subtree-dir: test/compatibility/C/uWebSockets
git-subtree-split: 03681ccbe630eb4db6322557e6bfe8cda8f41526
2020-01-04 15:41:03 -08:00
b9504fcd44 add another compatibility python client 2020-01-04 15:37:40 -08:00
299bcd4b92 Add a simple python program to send a file (debugging #140) 2020-01-04 15:18:02 -08:00
f56098dd4c (ws send) add option (-x) to disable per message deflate compression 2020-01-04 15:08:36 -08:00
e0187b2d8e fix typo in ws embedded help 2020-01-04 14:37:53 -08:00
31682f5f2d (ws send + receive) handle all message types (ping + pong + fragment) / investigate #140 2020-01-04 13:45:07 -08:00
221087ffff Install source code when making a Docker container 2020-01-04 13:44:02 -08:00
c7fc4f0f8e add user list in readme 2019-12-30 22:16:29 -08:00
2eece1d11a correct version number 2019-12-30 22:14:53 -08:00
5ba05212ec tag version 2019-12-30 17:17:28 -08:00
9e457871b4 Update IXSocketMbedTLS.cpp (#139)
fix bug with mbedtls server certificate loading.
2019-12-30 16:11:34 -08:00
b5481262fb Update IXSocketMbedTLS.cpp (#138)
fix bug just introduced.

mbedstl_pk_setup() gets automatically called later.
2019-12-30 15:14:50 -08:00
98e98f083e [2nd try] Update IXSocketMbedTLS.cpp (#137)
* Update IXSocketMbedTLS.cpp

fix initialization of mbedtls context.
without this, crashes under certain conditions.

* Update IXSocketMbedTLS.cpp

removed newline on 46
2019-12-30 14:38:25 -08:00
d60777b9cc clang-format 2019-12-30 08:46:18 -08:00
8b5e42fe84 (ws cobra to sentry/statsd) fix for handling null events properly for empty queues + use queue to send data to statsd 2019-12-28 17:28:05 -08:00
d89d152ad7 (ws cobra to sentry) handle null events for empty queues 2019-12-28 10:16:18 -08:00
47a3736b24 (ws cobra to sentry) game is picked in a fair manner, so that all games get the same share of sent events 2019-12-27 19:10:15 -08:00
1cd7cf340a (ws cobra to sentry) refactor queue related code into a class 2019-12-27 18:24:45 -08:00
3a25a05d9c (ws cobra to sentry) bound the queue size used to hold up cobra messages before they are sent to sentry. Default queue size is a 100 messages. Without such limit the program runs out of memory when a subscriber receive a lot of messages that cannot make it to sentry 2019-12-25 22:15:57 -08:00
d2acfd5d1f (ws client) use correct compilation defines so that spdlog is not used as a header only library (reduce binary size and increase compilation speed) 2019-12-25 09:03:57 -08:00
9dfcd8ea69 (ws client) all commands use spdlog instead of std::cerr or std::cout for logging 2019-12-24 21:55:34 -08:00
ee65f95fe3 (cobra client) send a websocket ping every 30s to keep the connection opened 2019-12-24 17:16:41 -08:00
b9cc6d7e23 (tls / apple) minor refactoring, move functions out of the anonymous namespace to become static member functions 2019-12-23 16:30:38 -08:00
1e97e5e536 socket tls options: display ciphers 2019-12-23 12:25:25 -08:00
440a1058b3 (doc) fix typo 2019-12-22 20:33:14 -08:00
ff489515be v7.6.4 2019-12-22 20:32:10 -08:00
536a502b60 (client) error handling, quote url in error case when failing to parse on 2019-12-22 20:30:29 -08:00
db0ff4ecd1 (ws) ws_cobra_publish: register callbacks before connecting 2019-12-22 20:29:37 -08:00
e4aed56d72 (doc) mention mbedtls in supported ssl server backend 2019-12-22 20:28:44 -08:00
1427a97dd9 update gitignore file 2019-12-20 15:21:36 -08:00
d7318f97e6 (tls) add a simple description of the TLS configuration routine for debugging 2019-12-20 15:18:04 -08:00
d1a4cab134 (mbedtls) correct support for using own certificate and private key 2019-12-20 15:13:26 -08:00
8e7d310439 (ws commands) in websocket proxy, disable automatic reconnections + in Dockerfile, use alpine 3.11 2019-12-20 09:51:21 -08:00
9347664622 (cobra) Add TLS options to all cobra commands and classes. Add example to the doc. 2019-12-19 20:49:28 -08:00
4e240e4992 formatting 2019-12-19 19:13:55 -08:00
626e190d91 (cobra-to-sentry) capture application version from device field 2019-12-18 15:41:59 -08:00
1933da7044 IXSentryClient: remove duplicated line 2019-12-18 15:29:53 -08:00
494f408320 (tls) Experimental TLS server support with mbedtls (windows) + process cert tlsoption (client + server) 2019-12-18 11:51:02 -08:00
299afc1425 (tls servers) Make it clear that apple ssl and mbedtls backends do not support SSL in server mode 2019-12-18 10:43:05 -08:00
0679b6399f (tls options client) TLSOptions struct _validated member should be initialized to false 2019-12-17 14:10:28 -08:00
862f8ea2d4 (websocket client) improve the error message when connecting to a non websocket server 2019-12-16 17:57:43 -08:00
cae016564e (server) attempt at fixing #131 by using blocking writes in server mode 2019-12-12 12:17:29 -08:00
e223f8fac2 (ws) cobra to sentry - created events with sentry tags based on tags present in the cobra messages 2019-12-11 17:28:11 -08:00
c86fa8ad3b update spdlog 2019-12-06 22:05:12 -08:00
ba4cf75e0f (mac) convert SSL errors to utf8 2019-12-06 16:45:49 -08:00
9f98628709 Add script to extract the version from the header file and remove DOCKER_VERSION 2019-12-06 16:44:05 -08:00
0b2d816320 (ws) cobra to sentry. Handle Error 429 Too Many Requests and politely wait before sending more data to sentry 2019-12-05 15:59:29 -08:00
8b1c4ff081 Using Alpine edge distribution to live on the edge 2019-12-05 15:46:01 -08:00
fde0b717d3 sentry minidump upload timeout 2019-12-05 15:19:27 -08:00
228cdca250 bunch of docker compose dev changes 2019-12-05 15:18:02 -08:00
18386ae66f (ws) #125 / fix build problem when jsoncpp is not installed locally 2019-12-03 17:18:16 -08:00
5c5ea6dec1 (ws) #125 / cmake detects an already installed jsoncpp and will try to use this one if present 2019-12-03 16:01:46 -08:00
2e1657167f Meta: documentation fixes (#127)
* Clarify versions, minor punctuation fix

* Copyediting, borked URL

* Fix Python comments in C++ code

* Copyediting

* Pretty code

* Copyediting, pretty code

* Typo

* Pretty code
2019-12-03 09:28:18 -08:00
c91a0d2a35 (http client) use std::unordered_map instead of std::map for HttpParameters and HttpFormDataParameters class aliases 2019-12-03 09:25:00 -08:00
d465511812 [#113] Mention StreamSSL as an example windows schannel implementation 2019-12-03 09:22:27 -08:00
6b98694caa (client) internal IXDNSLookup class requires a valid cancellation request function callback to be passed in 2019-12-02 14:52:19 -08:00
d82e05f72d (client) fix an overflow in the exponential back off code 2019-12-02 13:51:45 -08:00
48622a24db ws cobra_subscribe / sleep 1s instead of 10ms in the main loop 2019-12-02 09:52:52 -08:00
94a274ced4 Improve the limitation section in the doc about TLS, cf bug #113 2019-12-02 09:52:05 -08:00
a7977cf1a5 ws cobra subcommands / channel is not a positional argument anymore 2019-11-28 15:17:13 -08:00
3fffd2ed0b add doc about using ws to run a cobra server/publisher/subscriber 2019-11-27 09:26:45 -08:00
a0d5f37402 (http client) Add support for multipart HTTP POST upload + (ixsentry) Add support for uploading a minidump to sentry 2019-11-25 21:11:11 -08:00
1b11ef006a Update README.md 2019-11-23 12:44:24 -08:00
155bbfa984 On Darwin SSL, add ability to skip peer verification. 2019-11-20 13:58:08 -08:00
89aae8b344 bump version for 32-bit fix 2019-11-20 11:35:07 -08:00
28a0ba4768 tweaks to the test python proxy code / (moved here) https://gist.github.com/bsergean/bad452fa543ec7df6b7fd496696b2cd8 2019-11-20 11:32:21 -08:00
708969c126 Compile bug (#122)
* 1) IXWebSocketTransport: BUG: int type has no warranty of number of bits. It depends on compiler and architecture. In my system (64 bit) is 32 bit.
1 << 63 is bad idea in this case because the final number is 0 by overflow.
The symptom observed is that the server can't receive messages.

2) IXSocketFactory: Compilation Warning: Variable not in use.

* Better aproach suggested by Benjamin.
2019-11-20 11:12:24 -08:00
cc492bf1a3 add a python websocket proxy which works on Linux, while ws proxy_server does not 2019-11-18 13:46:11 -08:00
901c21e499 ws proxy_server / remote server close not forwarded to the client 2019-11-16 14:21:44 -08:00
16c6f08e2d fix android build + proxy work 2019-11-16 06:51:53 -08:00
6e9f27d63c bump version 2019-11-15 17:19:06 -08:00
5b282ce3b4 document new proxy command 2019-11-15 17:18:32 -08:00
c7b2446164 proxy works but crash when the connection is refused 2019-11-15 17:07:31 -08:00
0e43c618a5 add stub code for ws proxy server 2019-11-15 14:30:20 -08:00
af6100b90f fix typo 2019-11-15 14:28:30 -08:00
a4cd248368 update readme / add contributing notes 2019-11-15 14:21:28 -08:00
6e52723c8c update changelog 2019-11-06 23:12:45 -08:00
a3ad92b9d9 update readme 2019-11-06 23:12:45 -08:00
b07827790f check max frame size (#119) 2019-10-28 21:53:31 -07:00
85e00a195c docker build fixes 2019-10-26 11:47:08 -07:00
081dd2c4bb Add unittest to IXSentryClient to lua backtrace parsing code 2019-10-26 10:54:47 -07:00
bbfa76a2c9 move sentry code around and add a stub unittest for it 2019-10-25 14:54:31 -07:00
16a060131a ws cobra to sentry / simplify sent and received message statistic reporting 2019-10-25 14:34:48 -07:00
6dabc68d29 remove unused quiet argument of ws cobra_metrics_to_redis command 2019-10-25 14:02:56 -07:00
ac0593bfb3 Add cobra_metrics_to_redis sub-command to create streams for each cobra metric event being received. 2019-10-24 14:42:36 -07:00
ac3e9eab25 Create SECURITY.md 2019-10-17 06:58:22 -07:00
6de426a574 remove unused code in ws cobra_publish 2019-10-14 11:15:14 -07:00
c3a619f114 Add client support for websocket subprotocol. Look for the new addSubProtocol method for details 2019-10-13 13:37:34 -07:00
128545cc2b OpenSSL: add an extra cipher to the default cipher set, which let us connect to wss//echo.websocket.org 2019-10-10 09:37:27 -07:00
8152898c4e add more docs about ws 2019-10-09 22:42:03 -07:00
852bf452b6 Freebsd (#117)
* add file

* CMake freebsd fix
2019-10-09 17:00:32 -07:00
b38e80f846 (freebsd compile fix) add some missing socket related headers 2019-10-09 15:38:40 -07:00
a383ac10d9 make sure the unittest pass withouth SSL 2019-10-03 09:41:17 -07:00
2c32f5c593 add a target for building wihout ssl + take Matt Boer updated script to run ws test with SSL (still broken for large payload) 2019-10-03 07:47:34 -07:00
41cbee2cd2 ws: Signal handling code isn't include on Windows 2019-10-01 16:12:32 -07:00
1f8944852a bump version 2019-10-01 16:01:32 -07:00
95dd03b298 Add mbed tls version in user agent string + set user agent properly when enabling openssl on macOS 2019-10-01 15:58:35 -07:00
a0cfaff528 Implement SSL server with OpenSSL backend / still flaky 2019-10-01 15:43:37 -07:00
d6542383ed Add --tls option to pass to ws server command, to enable/disable tls 2019-10-01 13:54:46 -07:00
afed387bcf Socket Factory has only one function which works for server and client code, and can do tls for both 2019-09-30 22:06:46 -07:00
313949f087 SocketServer::handleConnection takes an std::shared_ptr<Socket> instead of a file descriptor 2019-09-30 21:48:55 -07:00
e5c8e2e7f4 openSSLHandshake -> openSSLClientHandshake 2019-09-30 21:24:25 -07:00
845bbc5208 all ws subcommands propagate tls options to servers (unimplemented) or ws or http client (implemented) (contributed by Matt DeBoer) 2019-09-30 18:21:20 -07:00
7a26ff4de8 clang-format 2019-09-30 17:52:39 -07:00
a1f3c40a2d ws has a --version option 2019-09-30 17:31:33 -07:00
1fdbc2bc22 bump version number 2019-09-29 22:10:07 -07:00
0f4def2338 fix windows compile error in include/spdlog/details/pattern_formatter-inl.h 2019-09-29 22:00:57 -07:00
7939f7ad50 docs: WITH_TLS => USE_TLS 2019-09-29 21:31:13 -07:00
8bfc3c5ea6 document basic usage 2019-09-29 21:29:28 -07:00
bf46f3fe8f unittest / specify a cacert for tls client tests 2019-09-29 21:24:22 -07:00
55141aa875 ws curl + http client tls option handling + ca cert processing for mbedtls 2019-09-29 21:13:11 -07:00
4e4792d6dc update copyright dates and authors 2019-09-29 20:09:51 -07:00
2aca019d84 openssl client: handle TLS options 2019-09-29 20:07:53 -07:00
864249b62d most ws command take tls options, no-op for now (contributed by Matt DeBoer) 2019-09-29 18:29:51 -07:00
d1fb34694c SocketTLSOptions: more methods (contributed by Matt DeBoer) 2019-09-29 17:35:18 -07:00
d1fc31b894 ws transfer + send + receive / improved logging (contributed by Matt DeBoer) 2019-09-29 17:21:52 -07:00
f6bf2531bb Add ability to use OpenSSL on apple platforms. 2019-09-29 15:34:58 -07:00
681390f22f update and change how we build with spdlog 2019-09-29 11:13:24 -07:00
0ee675e554 ixcobra / fix crash in CobraConnection::publishNext when the queue is empty + handle CobraConnection_PublishMode_Batch in CobraMetricsThreadedPublisher 2019-09-28 10:36:47 -07:00
7e1a60e61d DNS lookup test works on windows 2019-09-27 14:34:47 -07:00
4cd11fdbc7 mbedtls fixes / the unittest now pass on macOS, and hopefully will on Windows/AppVeyor as well. 2019-09-27 14:07:01 -07:00
05c7a26e3a Export port 8008 for Docker + test_ws.sh is /bin/sh compatible 2019-09-26 14:36:14 -07:00
6762978ddf http server unittest + refactoring 2019-09-26 09:45:59 -07:00
658650cf24 http server: in redirect mode, POST request are given a 200 status code and an empty response 2019-09-26 09:27:27 -07:00
8a662b35e1 Http server: add options to ws https to redirect all requests to a given url. 2019-09-26 09:10:30 -07:00
3cd7c0194f Stop having ws send subcommand send a binary message in text mode, which would cause error in make ws_test shell script test 2019-09-25 15:39:43 -07:00
05f29639e5 fix doc 2019-09-24 15:42:28 -07:00
5c18ffdae2 speedup base64 code by reserving memory 2019-09-24 14:17:03 -07:00
d3cee46e93 wrong mutex being used ... 2019-09-24 14:10:41 -07:00
94c589f696 Fix 2 race conditions detected with TSan, one in CobraMetricsPublisher::push and another one in WebSocketTransport::sendData (that one was bad). 2019-09-24 11:46:54 -07:00
490fbf4cb5 try to enable more tests on windows 2019-09-23 21:52:32 -07:00
d46ce7eb63 fix tsan errors on macOS when running the unittest 2019-09-23 21:51:55 -07:00
169e225ccd fix warning + add redis server logging 2019-09-23 21:14:20 -07:00
ceb0c602c9 Add simple Redis Server which is only capable of doing publish / subscribe. New ws redis_server sub-command to use it. The server is used in the unittest, so that we can run on CI in environment where redis isn not available like github actions env. 2019-09-23 21:04:01 -07:00
95722e3bbb fix linux compilation error, by ordering dependant libraries properly 2019-09-23 12:32:04 -07:00
1cde26771a move snake code to its own subfolder like ixcobra, ixcrypto, etc... 2019-09-23 11:46:16 -07:00
cd3c9d879c reformat everything with clang-format 2019-09-23 10:25:23 -07:00
398c4fbf99 fix unittest 2019-09-22 19:40:33 -07:00
e7b4a985b4 fix unittest compiler warnings 2019-09-22 19:22:48 -07:00
6f76fea188 compile fixes 2019-09-22 18:52:57 -07:00
f6b8e7f234 add gihub actions 2019-09-22 18:45:30 -07:00
041fa3e340 compiled fixes on mac and windows 2019-09-22 18:43:57 -07:00
408ee41990 WIP: support configurable certificates/keys, and root trust CAs (#114)
* wip: tls options implemented in openssl

* update naming, remove #define guard

* assert compiled with USE_TLS for tls options

* apply autoformatter

* include tls options impl

* style cleanup; auto ssl_err

* ssl_err -> sslErr

* be explicit about SSL_VERIFY_NONE
2019-09-22 18:06:15 -07:00
ed4be773a2 Fix crash in the Linux unittest in the HTTP client code, in Socket::readBytes. Cobra Metrics Publisher code returns the message id of the message that got published, to be used to validated that it got sent properly when receiving an ack. 2019-09-21 09:23:58 -07:00
fcdb57f31d missing file in ws tool 2019-09-19 12:51:34 -07:00
47b3368f78 In DNS lookup code, make sure the weak pointer we use lives through the expected scope (if branch) 2019-09-19 12:51:11 -07:00
20ce498d23 On error while doing a client handshake, additionally display port number next to the host name 2019-09-17 12:08:52 -07:00
354c9b412e rename test file 2019-09-17 12:07:31 -07:00
1c08cedd8a make test target does not try to install anything into /usr/local 2019-09-12 11:45:31 -07:00
e2121d809e fix unittest warnings + remove trailing spaces 2019-09-12 11:43:52 -07:00
9c1065bc1b update pre-commit file 2019-09-10 22:18:16 -07:00
27136bbce8 update clang format file 2019-09-10 22:17:08 -07:00
c3238b7e02 build fixes 2019-09-10 14:05:07 -07:00
b11640b477 update appveyor file to new directory structure 2019-09-10 12:33:47 -07:00
2453f5b717 restructure project 2019-09-10 12:19:22 -07:00
65c5c5f894 update ws CLI11 (our command line argument parsing library) to the latest, which fix a compiler bug about optional 2019-09-09 21:25:33 -07:00
64d3c99f99 improve some websocket error messages + add a utility function with unittest to parse status line and stop using scanf which triggers warnings on Windows 2019-09-09 21:23:57 -07:00
6c0890594b improve some websocket error messages + add a utility function with unittest to parse status line and stop using scanf which triggers warnings on Windows 2019-09-09 17:34:36 -07:00
fb271953f7 websocket and http server: server does not close the bound client socket in many cases 2019-09-09 16:48:26 -07:00
7080c5679f move poll wrapper on top of select (only used on Windows) to the ix namespace 2019-09-08 11:15:08 -07:00
427db5bd59 Fix Windows CI with appveyor (#110)
Fix windows CI with appveyor + minor tweaks.
2019-09-07 14:07:00 -07:00
c09eac49c9 README: update link to the doc 2019-09-06 10:42:48 -07:00
ae6f87eb42 ci 2019-09-05 22:32:54 -07:00
82b3c5e2f3 travis linux 2019-09-05 22:29:00 -07:00
e41ba279e9 ci tweak / install redis 2019-09-05 22:14:55 -07:00
c259c918ac cobra metrics publisher test uses random free port 2019-09-05 22:05:00 -07:00
2f7438f0d5 cobra chat test uses random free port 2019-09-05 22:02:10 -07:00
37a7b362d8 add cobra metrics publisher + server unittest 2019-09-05 21:57:05 -07:00
c0f098a578 Add new cobra unittest, using server and client 2019-09-05 20:49:58 -07:00
21404c23dd snake unsubscription fixes 2019-09-05 20:47:15 -07:00
eeefc9cf4b Fix compiler warning 2019-09-05 20:29:14 -07:00
24b2475b11 ws snake (cobra simple server) add basic support for unsubscription + subscribe send the proper subscription data + redis client subscription can be cancelled 2019-09-05 20:28:34 -07:00
2defe6f597 IXCobraConnection / pdu handlers can crash if they receive json data which is not an object 2019-09-05 20:24:42 -07:00
f9dc460325 cobra publish fix 2019-09-05 14:31:28 -07:00
30b83b5ff0 Update README.md 2019-09-05 14:30:51 -07:00
003afc8b56 all client autobahn test should pass ! last failing one was ...
+- zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9/ (fix autobahn test 13.X which uses 8 for the windows size)
2019-09-04 21:01:30 -07:00
95a97a197a Fragmentation: for sent messages which are compressed, the continuation fragments should not have the rsv1 bit set (fix all autobahn tests for zlib compression 12.X)
Websocket Server / do a case insensitive string search when looking for an Upgrade header whose value is websocket. (some client use WebSocket with some upper-case characters)
2019-09-04 18:23:56 -07:00
eccd8b3c0e ws autobahn / use condition variables for stopping test case + add more logging on errors 2019-09-04 12:21:54 -07:00
a43046c921 ws autobahn / report progress with spdlog::info to get timing info 2019-09-04 10:16:32 -07:00
b360fb9ca0 truncate module 2019-09-03 20:14:35 -07:00
0bf185e143 Per message deflate/compression: handle fragmented messages (fix autobahn test: 12.1.X and probably others) 2019-09-03 17:42:48 -07:00
da3d134be0 Receiving invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling) 2019-09-03 16:07:48 -07:00
b4c4746d43 IXWebSocketTransport message processing refactoring 2019-09-03 15:48:55 -07:00
fdd1ad9b17 Validate close codes. Autobahn 7.9.* 2019-09-03 15:43:16 -07:00
1be8d9d46f Validate that the close reason is proper utf-8. Autobahn 7.5.1 2019-09-03 14:35:40 -07:00
51799353a6 Sending invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling) 2019-09-03 14:12:40 -07:00
3ad13a592d Framentation: data and continuation blocks received out of order (fix autobahn test: 5.9 through 5.20 Fragmentation) 2019-09-03 12:02:56 -07:00
55934918ff Sending invalid UTF-8 TEXT message should fail and close the connection (fix **tons** of autobahn test: 6.X UTF-8 Handling) 2019-09-03 10:30:22 -07:00
ab93e4f168 Message type (TEXT or BINARY) is invalid for received fragmented messages (fix autobahn test: 5.3 through 5.8 Fragmentation) 2019-09-03 09:13:38 -07:00
e1ad0b0889 bump version 2019-09-02 10:14:15 -07:00
cbe3e7617c Ping and Pong messages cannot be fragmented (autobahn test: 5.1 and 5.2 Fragmentation) 2019-09-02 10:13:40 -07:00
94c8966e86 Close connections when reserved bits are used (autobahn test: 3 Reserved Bits) 2019-09-01 16:23:00 -07:00
d973a062c2 changelog 2019-09-01 11:39:00 -07:00
ba41dbc69a readme 2019-09-01 11:38:39 -07:00
96380dd462 doc 2019-09-01 11:28:27 -07:00
61bd765784 compute test case count properly 2019-09-01 11:17:28 -07:00
4a0f06193b refactoring 2019-09-01 11:10:27 -07:00
826917ef17 condition variable instead of busy looping 2019-09-01 10:50:16 -07:00
4e1dbbbecf quiet mode 2019-09-01 10:45:51 -07:00
b5b0de2083 +add utf-8 validation code, not hooked up properly yet
+ws autobahn / Add code to test websocket client compliance with the autobahn test-suite
+Ping received with a payload too large (> 125 bytes) trigger a connection closure
+cobra / add tracking about published messages
+cobra / publish returns a message id, that can be used when
+cobra / new message type in the message received handler when publish/ok is received (can be used to implement an ack system).
2019-08-31 16:47:10 -07:00
a95fcbbdbf user agent 2019-08-30 12:50:56 -07:00
7a73ec7c06 New option to cap the max wait between reconnection attempts. Still default to 10s. (setMaxWaitBetweenReconnectionRetries) (#108) 2019-08-30 12:46:35 -07:00
0c1f2252a1 readme 2019-08-26 22:49:40 -07:00
98a397696c readme 2019-08-26 22:29:10 -07:00
225b7d7db7 doc / bring back detailed APIs 2019-08-26 22:11:35 -07:00
f968d4c333 one last tweak 2019-08-26 22:02:24 -07:00
59e15be524 readme tweaks 2019-08-26 21:57:05 -07:00
ccabe93ae8 new simple readme 2019-08-26 21:55:00 -07:00
56def6def4 Add md doc made with mkdocs 2019-08-26 21:25:45 -07:00
3b1a1efed2 fix #104 - change ZLIB find_package to be optional 2019-08-26 14:51:33 -07:00
185869e628 tentative gcc build fix 2019-08-26 14:29:16 -07:00
108f6238e3 bump version 2019-08-26 10:20:01 -07:00
d3e5a63fa2 ws connect has a new option to send HTTP headers + use WebSocketHttpHeaders instead of unordered_map<string, string> 2019-08-26 10:19:09 -07:00
0847e60d2a CobraConnection: sets a unique id field for all messages sent to [cobra](https://github.com/machinezone/cobra).
CobraConnection: sets a counter as a field for each event published.
2019-08-26 09:51:37 -07:00
ac60ec4320 put windows poll in the global namespace, not ix namespace 2019-08-26 09:51:37 -07:00
93debc00dc Add client handshake extra headers (#105)
Even though 6455 defines all the necessary headers needed for
client/server handshake, in practice most of the cases websocket servers
expect few more headers. Therefore adding this functionality.
2019-08-26 09:37:40 -07:00
ff75846d2d Windows: use select instead of WSAPoll, through a poll wrapper 2019-08-22 10:34:17 -07:00
53c767140d add new makefile target to make git tags 2019-08-20 09:21:30 -07:00
839a747ce8 bump version + talk about Windows fix in the changelog 2019-08-20 09:20:02 -07:00
f78a3f88ff add poll alias to WSAPoll on Windows 2019-08-19 22:26:25 -07:00
142987259c fix #101 / wrong include in IXSocket.cpp on Windows 2019-08-19 22:19:39 -07:00
c8d41f987f README tweaks 2019-08-19 20:35:26 -07:00
d139dd88e8 README / formatting 2019-08-19 20:33:56 -07:00
7898a5f4eb README.md: add reference to WSAStartup to initialize the networking system 2019-08-19 09:47:59 -07:00
53efbf3ca9 (CI) linux: install libmbedtls 2019-08-14 21:49:43 -07:00
b6e5ff2f3d (CI) try to build Linux on Ubuntu Bionic 2019-08-14 21:44:49 -07:00
ae1386a1d7 bump version 2019-08-14 21:36:20 -07:00
2f730303c2 CobraMetricThreadedPublisher _enable flag is an atomic, and CobraMetricsPublisher is enabled by default 2019-08-14 19:54:30 -07:00
e98ec9ec75 clang format 2019-08-13 10:59:18 -07:00
ffecef901a fix #99 / Connect error descriptions are invalid 2019-08-13 10:49:11 -07:00
5c13cbb08f update readme 2019-08-06 20:55:44 -07:00
64cfbe9514 fix #98 2019-08-02 17:11:53 -07:00
5cc21c87fb new options for cobra commands
- ws cobra_subscribe has a new -q (quiet) option
- ws cobra_subscribe knows to and display msg stats (count and # of messages received per second)
- ws cobra_subscribe, cobra_to_statsd and cobra_to_sentry commands have a new option, --filter to restrict the events they want to receive
2019-08-01 15:22:24 -07:00
505e0c79d9 ws connect command has a new option to send in binary mode (still default to text) 2019-07-25 15:48:45 -07:00
ca9d59c1c1 add better line editing capability to ws connect, thanks to linenoise-cpp 2019-07-25 11:54:50 -07:00
8319dbb56a README.md / cosmetic 2019-07-23 14:04:45 -07:00
b1b6697c37 fix typo in README 2019-07-23 13:52:16 -07:00
280716394d README: add reference to conan/vcpk to the build section 2019-07-22 20:41:06 -07:00
5795f72eab do not update homebrew when installing a package 2019-07-03 14:49:39 -07:00
7e16c8959b ci / get mbedtls from homebrew on mac 2019-07-03 14:46:05 -07:00
dfc188a24d do not use mbed tls for the unittest 2019-07-03 14:39:46 -07:00
d18bae0c95 add new docker file to run the unittest with tsan on latest Ubuntu 2019-06-30 23:37:25 -07:00
747746cba1 dns / use cancellable instead of blocking 2019-06-30 23:26:14 -07:00
5b73edec8c make IXDNSLookup more robust 2019-06-26 19:12:48 -07:00
3750781bce simplify IXDNSLookup 2019-06-26 16:25:07 -07:00
e646e53dac use poll instead of select in SocketServer 2019-06-25 17:18:24 -07:00
6b8aa43ec0 switch from select to poll to deal with Android 9 giving us high socket fds when calling ::connect 2019-06-25 17:11:27 -07:00
e8a20c7e8a refactor select code + add protection against large fds (cf Android 9) 2019-06-25 15:41:39 -07:00
0423ed01a6 Add extra check in IXWebSocketCloseTest.cpp 2019-06-25 14:10:39 -07:00
4a600c2611 select refactoring IXSocket::select -> IXSocket::poll 2019-06-25 10:16:40 -07:00
e5faa23d4f disable CI on Windows 2019-06-25 00:28:11 -07:00
b2f9c219b1 cmake use_tls fix 2019-06-24 23:34:31 -07:00
77d65760f0 do not build mbedtls on ci 2019-06-24 23:28:35 -07:00
98d0460af0 try to disable TLS for unittesting 2019-06-24 23:27:44 -07:00
de8d93341c CI / build zlib and mbedtls locally 2019-06-24 23:17:19 -07:00
0b6a773087 CI work 2019-06-24 10:17:57 -07:00
dc1aa676c4 try to activate CI on windows again 2019-06-23 18:32:18 -07:00
5ae9cc1cd7 bump docker version 2019-06-23 18:17:24 -07:00
f84bc53c8d Feature/httpd (#94)
* Stub code for http server

* can send a response, cannot process body yet

* write headers to the response

* remove commented code

* add simple test + set default http handler

* tweak CI + unittest

* add missing file

* rewrite http::trim in a simple way

* doc
2019-06-23 14:54:21 -07:00
b26e9d0338 IXDNSLookup. Uses weak pointer + smart_ptr + shared_from_this instead of static sets + mutex to handle object going away before dns lookup has resolved 2019-06-19 00:43:59 -07:00
dbd62b8622 cobra_to_sentry / backtraces are reversed and line number is not extracted correctly 2019-06-13 10:18:40 -07:00
20c80352bf disable windows on CI for now 2019-06-10 22:10:43 -07:00
9d70fb2b86 disable building ws on windows on travis 2019-06-10 22:01:19 -07:00
71aad26d44 (cmake) missing find_package(Threads) on UNIX 2019-06-10 13:39:22 -07:00
3a1918eb2f Add -DUSE_VENDORED_THIRD_PARTY=1 to build ws 2019-06-10 13:26:41 -07:00
a1709c07d9 - mbedtls and zlib are searched with find_package, and we use the vendored version if nothing is found 2019-06-10 11:18:27 -07:00
b8c1176c79 - travis CI uses g++ on Linux 2019-06-09 14:27:45 -07:00
03b5a57474 compile error in IXWebSocketMessageQTest 2019-06-09 12:25:36 -07:00
f58cf4826a fix IXWebSocketMessageQTest.cpp 2019-06-09 12:08:00 -07:00
d050cc5e13 fix changelog 2019-06-09 12:02:38 -07:00
6ef88b6303 update README to reflect the new API 2019-06-09 12:02:02 -07:00
3e15840b14 - WebSocket::send() sends message in TEXT mode by default
- WebSocketMessage sets a new binary field, which tells whether the received incoming message is binary or text
2019-06-09 11:56:47 -07:00
0d147cbd94 WebSocket::send takes a third arg, binary which default to true (can be text too) 2019-06-09 11:35:31 -07:00
be93f7480a WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg 2019-06-09 11:33:17 -07:00
2e5f24f1f8 ... 2019-06-09 10:22:27 -07:00
23cf4bd59b Add explicite WebSocket::sendBinary
New headers + WebSocketMessage class to hold message data, still not used across the board
2019-06-09 10:10:33 -07:00
051c34bc5d Add test/compatibility folder with small servers and clients written in different languages and different libraries to test compatibility. 2019-06-08 09:46:26 -07:00
9623ceb4d5 ws echo_server has a -g option to print a greeting message on connect 2019-06-08 09:16:33 -07:00
e1a7395880 IXSocketMbedTLS: better error handling in close and connect 2019-06-06 14:59:22 -07:00
51aeeca024 add a changelog 2019-06-06 13:59:12 -07:00
076e8bf6a3 add an option to easily disable per message deflate compression 2019-06-06 13:48:53 -07:00
73c5b9b847 fix Dockerfile link 2019-06-05 19:38:44 -07:00
f4f3eed78d cobra to sentry / more error handling 2019-06-05 19:37:51 -07:00
89909c15bc cobra to sentry fixes 2019-06-05 18:47:48 -07:00
78b3d7ff2d Feature/http async (#90)
* unittest working / uses shared_ptr for a bunch of things 🗿

* fix command line tools

* fix ws + add doc

* add more logging
2019-06-05 17:04:24 -07:00
012193c74e Fix compile error with JSON uint64_t 🚯 2019-06-04 13:45:29 -07:00
539abe5151 HttpResponse is a struct, not a tuple 🉐 2019-06-03 22:12:52 -07:00
7e5aba140e http / PUT fix 🐚 2019-06-03 21:12:39 -07:00
6b933391e5 http client: stop hardcoding Accept header, and use a default value if one is passed in 👭 2019-06-03 14:02:54 -07:00
1e2a5ee21d Add simple HTTP and HTTPS client test ㊙️ 2019-06-03 12:23:35 -07:00
225a5ef808 (http client) / Add DEL and PUT method, make requests and other utilities public 👐 2019-06-03 11:38:56 -07:00
396d0d9bdc Feature/ws windows (#86)
* try to build ws on window on travis 📮

* cmake invocation fixed on windows 🐝

* Can use mbedtls to calculate hmac + no openssl config option

* build only windows on travis 🕢

* run msbuild 💷

* proper command to build 🕛

* some build fixes

* change weird sizeof call 🐙

* warning and missing includes fixes 💮

* ifdef out statsd code on windows 🐍

* logic invertion in ifdef 👄

* bring back makefile 📜

* command line tool is built with mbedtls 🏥
2019-06-02 20:46:20 -07:00
88c8fb74bb play with podmena 2019-06-02 11:03:44 -07:00
fd902c7a58 add notes about mbedtls CMake 2019-06-01 18:00:25 -07:00
06cbebe22e Feature/mbedtls (#84)
* try to import mbedtls and build it

* add stubs socket class

* some boilterplate, read and write function implemented

* more boilterplate / current error in handshake because no CA cert is setup

* add something so skip ca verification, can ws curl https://google.com !

* cleanup / close implemented

* tweak CMakefiles

* typo in include

* update readme

* disable unittests
2019-06-01 17:41:48 -07:00
ba4a9e1586 (clang format) fix indent and make (rarely) accessor/setters in class on a single line 2019-05-31 00:53:14 -07:00
285386e47f fix cobra to sentry + change ws docker file to use alpine (much smaller footprint) 2019-05-31 00:43:22 -07:00
c65fec7271 clang format, based on cpprest 2019-05-30 08:46:50 -07:00
879a4b38aa std::chrono::duration is not initialized to 0 units of time 2019-05-26 14:16:15 -07:00
13c87e38ed do not select on a closed file descriptor (doing so crash on Android) 2019-05-22 18:58:22 -07:00
718154cfb4 enable IXWebSocketMessageQTest.cpp on mac and windows 2019-05-22 11:03:13 -07:00
26de9b9714 Clean (#82)
Thanks
2019-05-21 12:14:58 -07:00
3365facf9f Fix ping (#80)
* let poll do his job when closing

* try fix test

* try fix test

* Update IXWebSocketTransport.cpp

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* change state immediately, and send close frame after

* add immediate close test

* disable test for windows

* reenable ping / ping timeout tests

* add time to let windows close client

* reenable ping timeout test

* add 100ms more

* disable test for windows
2019-05-21 09:35:41 -07:00
8a4826164b fix close bug and tests : let poll do his job when closing (#79)
* let poll do his job when closing

* try fix test

* try fix test

* Update IXWebSocketTransport.cpp

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* change state immediately, and send close frame after

* add immediate close test

* disable test for windows
2019-05-21 09:34:08 -07:00
d6eabae4f0 one cpu on windows for executing tests 2019-05-17 15:45:31 -07:00
6bd81bb92e add back IXWebSocketMessageQueue, with its unittest disabled 2019-05-16 22:41:39 -07:00
126a91dfec Linux build fix: strncpy needs <string.h> 2019-05-16 22:21:15 -07:00
51fa147b99 Revert "Merge branch 'Dimon4eg-message-queue'"
This reverts commit 13fa325134, reversing
changes made to aecd5e9c94.
2019-05-16 22:15:17 -07:00
6160877167 Fix Address Sanitizer heap-buffer-overflow in WebSocketHandshakeKeyGen::generate
=================================================================
==5077==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6070000077e0 at pc 0x00010ba18c54 bp 0x70000dd45b10 sp 0x70000dd45b08
READ of size 1 at 0x6070000077e0 thread T12
    #0 0x10ba18c53 in WebSocketHandshakeKeyGen::generate(char const*, char*) libwshandshake.hpp:113
    #1 0x10ba2065a in ix::WebSocketHandshake::serverHandshake(int, int) IXWebSocketHandshake.cpp:356
    #2 0x10b9c4952 in ix::WebSocketTransport::connectToSocket(int, int) IXWebSocketTransport.cpp:190
    #3 0x10b97e4c2 in ix::WebSocket::connectToSocket(int, int) IXWebSocket.cpp:193
2019-05-16 21:59:03 -07:00
717f049579 Merge branch 'Dimon4eg-message-queue' 2019-05-16 19:26:45 -07:00
f71331056c bring back socket mutex which is needed, some CI failures are happening without it 2019-05-16 19:23:32 -07:00
c131ff2662 disable socket mutex usage in WebSocketTransport 2019-05-16 19:23:32 -07:00
616447e01d remove dead code 2019-05-16 19:23:32 -07:00
8c1d66bcf3 refactor connect unittest so that it hits a local server instead of a remote server 2019-05-16 19:23:32 -07:00
bea580b906 travis makefile fix 2019-05-16 19:23:32 -07:00
c513e02b24 try to run ws test on linux + macOS on travis 2019-05-16 19:23:32 -07:00
90d71deb0f Add constants for closing code and messages 2019-05-16 19:23:32 -07:00
fc0776303a first socket test hit a local server instead of a remote server / this can help with a windows intermittent failure 2019-05-16 19:23:32 -07:00
bb0c6f9a8a build ws on travis (mac + linux) 2019-05-16 19:23:32 -07:00
dae21e7681 use a regular mutex instead of a recursive one + stop properly 2019-05-16 19:23:32 -07:00
d28437ecc0 adding logging to IXWebSocketTestConnectionDisconnection makes it fails reliably 2019-05-16 19:23:32 -07:00
7fec24af67 Update README.md 2019-05-16 19:23:32 -07:00
0de3637569 Update README.md 2019-05-16 19:23:32 -07:00
f94c7cef59 -j option actually work ... 2019-05-16 19:23:32 -07:00
7734d63b1b unittest / add options to set the number of jobs 2019-05-16 19:23:32 -07:00
f894504761 enum class HttpErrorCode derives from int 2019-05-16 19:23:32 -07:00
7aa9b4ee64 try to re-enable some tests 2019-05-16 19:23:32 -07:00
a12250dc16 recursive mutex + enable test that was breaking on Ubuntu Xenial + gcc + tsan 2019-05-16 19:23:32 -07:00
d8fbe1a63e comment failing test 2019-05-16 19:23:32 -07:00
91e1760719 comment failing test 2019-05-16 19:23:32 -07:00
02c8a62e7d do not build ws for now on travis 2019-05-16 19:23:32 -07:00
0c9bcfb8ac more protection against socket when closing 2019-05-16 19:23:32 -07:00
bd4c5037c7 fix compile errors with C++ enum class 2019-05-16 19:23:32 -07:00
ccaaedf38f close and stop with code and reason + docker = ubuntu xenial 2019-05-16 19:23:32 -07:00
751f294164 add isEnabledAutomaticReconnection (#75)
* add isEnabledAutomaticReconnection

* test isEnabledAutomaticReconnection

* rename
2019-05-16 19:23:32 -07:00
e2acbe8499 Revert "Revert "fix cast warning caught on windows""
This reverts commit 25eaf730bc.
2019-05-16 19:23:32 -07:00
aba880a6b3 Revert "fix cast warning caught on windows"
This reverts commit 4edb7447df.
2019-05-16 19:23:32 -07:00
616e8da0a5 fix cast warning caught on windows 2019-05-16 19:23:32 -07:00
a220774a3b add more time to let client close (#73) 2019-05-16 19:23:32 -07:00
4fc8224264 build with gcc on Linux 2019-05-16 19:23:32 -07:00
66dae5840c use spdlog for logging in the unittest 2019-05-16 19:23:32 -07:00
89b9e6e531 fix inverted conditional 2019-05-16 19:23:31 -07:00
fc4623381a server code / add dedicated thread to close/join terminated connection threads 2019-05-16 19:23:31 -07:00
1023e925f6 travis cmake version on macOS does not know --parallel option 2019-05-16 19:23:31 -07:00
5d65365751 build in parallel + stop building linux + clang 2019-05-16 19:23:31 -07:00
ee64a6ec7e disable ping tests for now as they are not super reliable 2019-05-16 19:23:31 -07:00
9bc09105d7 Feature/windows ci (#76)
* close with params

* ...

* different generator

* core size = 1

* disable more tests to get something working on windows

* try to enable another test on windows

* enable all OS

* set proper version of linux

* another try

* try again with just env variables

* Revert "core size = 1"

This reverts commit 29af74bba6.

* add windows and mac

* Revert "close with params"

This reverts commit 6bb00b6788.
2019-05-16 19:23:31 -07:00
4b96632a69 save timepoints after connect and not in contructor, adjusted tests (#72)
* save timepoints after connect and not in contructor, adjusted tests

* move call into setReadyState

* more time to detect client close in test
2019-05-16 19:20:29 -07:00
22a806ca6f fix for Windows (#69)
* fix for Windows

* fix condition

* make condition only on Windows
2019-05-16 19:20:29 -07:00
d8dc977fc1 Fix run.py (#71)
* fix run.py

* run.py: fix Windows support

* fix test listing
2019-05-16 19:20:29 -07:00
6d900b8ffb bring back socket mutex which is needed, some CI failures are happening without it 2019-05-16 15:58:20 -07:00
5a2c070898 disable socket mutex usage in WebSocketTransport 2019-05-16 15:46:32 -07:00
58f17ddb09 remove dead code 2019-05-16 15:05:20 -07:00
47c9786bab refactor connect unittest so that it hits a local server instead of a remote server 2019-05-16 14:25:31 -07:00
e5edbeacb4 travis makefile fix 2019-05-16 14:02:24 -07:00
964fb20df9 try to run ws test on linux + macOS on travis 2019-05-16 13:57:33 -07:00
309ed80446 Add constants for closing code and messages 2019-05-16 12:46:53 -07:00
01f2eb6615 first socket test hit a local server instead of a remote server / this can help with a windows intermittent failure 2019-05-16 12:24:58 -07:00
3a55c7aaba build ws on travis (mac + linux) 2019-05-16 07:01:15 -07:00
243f41bf28 use a regular mutex instead of a recursive one + stop properly 2019-05-15 19:26:02 -07:00
934b28f5b6 adding logging to IXWebSocketTestConnectionDisconnection makes it fails reliably 2019-05-15 19:26:02 -07:00
edfc03bed2 Update README.md 2019-05-15 19:22:05 -07:00
59ce71b64d Update README.md 2019-05-15 18:57:17 -07:00
d473a7dc22 -j option actually work ... 2019-05-15 18:15:45 -07:00
efb063d600 unittest / add options to set the number of jobs 2019-05-15 17:52:03 -07:00
12fe55905c enum class HttpErrorCode derives from int 2019-05-15 16:50:00 -07:00
aed831c075 try to re-enable some tests 2019-05-15 16:28:29 -07:00
5ad15fad8b recursive mutex + enable test that was breaking on Ubuntu Xenial + gcc + tsan 2019-05-15 16:01:05 -07:00
a7d328896c comment failing test 2019-05-15 15:44:14 -07:00
1274a151d0 comment failing test 2019-05-15 15:37:30 -07:00
d93d639345 do not build ws for now on travis 2019-05-15 15:26:49 -07:00
e0d9a16985 more protection against socket when closing 2019-05-15 15:18:46 -07:00
7f1070dde6 fix compile errors with C++ enum class 2019-05-15 15:18:46 -07:00
7f1e70329c close and stop with code and reason + docker = ubuntu xenial 2019-05-15 15:18:46 -07:00
186c8fbb62 add isEnabledAutomaticReconnection (#75)
* add isEnabledAutomaticReconnection

* test isEnabledAutomaticReconnection

* rename
2019-05-14 11:26:37 -07:00
c935be6a49 Revert "Revert "fix cast warning caught on windows""
This reverts commit 25eaf730bc.
2019-05-13 22:16:49 -07:00
4ee502fa1a Revert "fix cast warning caught on windows"
This reverts commit 4edb7447df.
2019-05-13 21:35:34 -07:00
084805b248 fix cast warning caught on windows 2019-05-13 21:29:47 -07:00
eaebd258c0 add more time to let client close (#73) 2019-05-13 21:26:34 -07:00
2843a20814 build with gcc on Linux 2019-05-13 17:35:21 -07:00
08a56726a8 use spdlog for logging in the unittest 2019-05-13 17:32:57 -07:00
7cad8654e5 fix inverted conditional 2019-05-13 17:18:07 -07:00
841cfe37dd server code / add dedicated thread to close/join terminated connection threads 2019-05-13 17:17:35 -07:00
849a41293f travis cmake version on macOS does not know --parallel option 2019-05-13 17:17:35 -07:00
5b17edb3f9 build in parallel + stop building linux + clang 2019-05-13 17:06:56 -07:00
9f2047dad6 disable ping tests for now as they are not super reliable 2019-05-13 17:01:22 -07:00
c01c53c5c7 Feature/windows ci (#76)
* close with params

* ...

* different generator

* core size = 1

* disable more tests to get something working on windows

* try to enable another test on windows

* enable all OS

* set proper version of linux

* another try

* try again with just env variables

* Revert "core size = 1"

This reverts commit 29af74bba6.

* add windows and mac

* Revert "close with params"

This reverts commit 6bb00b6788.
2019-05-13 16:51:58 -07:00
27bf1684cb save timepoints after connect and not in contructor, adjusted tests (#72)
* save timepoints after connect and not in contructor, adjusted tests

* move call into setReadyState

* more time to detect client close in test
2019-05-13 09:08:46 -07:00
be2aee3354 fix for Windows (#69)
* fix for Windows

* fix condition

* make condition only on Windows
2019-05-12 22:21:56 -07:00
5f42a07d0d Fix run.py (#71)
* fix run.py

* run.py: fix Windows support

* fix test listing
2019-05-12 18:37:22 -07:00
8a94c945b7 Merge branch 'master' into message-queue 2019-05-12 22:00:10 +03:00
7740028291 all derived class use final keyword 2019-05-12 11:43:21 -07:00
7369e9c233 add setOnMessageCallback with r-value 2019-05-12 20:59:18 +03:00
8c66825a78 correct style 2019-05-12 20:16:02 +03:00
a56f8272a9 fix warnings 2019-05-12 20:05:28 +03:00
e846ca392f update comment 2019-05-12 20:00:15 +03:00
f9ec89cf7a update test 2019-05-12 19:57:31 +03:00
b935bc526a added message queue test 2019-05-12 01:50:41 +03:00
97617ced4a Added WebSocketMessageQueue 2019-05-12 01:49:06 +03:00
d575c7c2a9 bump version to 2.0.0 2019-05-11 14:22:41 -07:00
99a3bbc4f9 use C++11 enums (#67)
* use C++11 enums

* small rename

* update tests

* update tests

* update ws

* update ws

* update README.md
2019-05-11 14:22:06 -07:00
80226cb7d3 add comment about why a unittest is disabled 2019-05-11 12:25:40 -07:00
6189e0cd50 remove irrelevant comment 2019-05-11 12:24:11 -07:00
2254421ead minor improvements (#66)
* minor improvements

* fix build

* improve tests code
2019-05-11 12:20:58 -07:00
4934f5846b minor tweaks to have full feature parity before unittest broke 2019-05-11 11:54:21 -07:00
c8c1aabf20 fix race condition in SelectInteruptPipe, where _fildes are not protected (caught by fedora tsan) 2019-05-11 11:45:26 -07:00
93b901a286 remove more iostream includes (#65) 2019-05-11 11:27:58 -07:00
518a445074 rename some variables, minor cleanup 2019-05-11 10:24:28 -07:00
080a6db657 uncomment test 2019-05-11 10:15:22 -07:00
d49b1bd78a fix crash on close 2019-05-11 10:15:22 -07:00
bd96050d84 fix crash on close 2019-05-11 10:12:33 -07:00
2a90ad9e53 Fix crash during closing on Windows (#64)
* fix crash on close

* Improve calculateRetryWaitMilliseconds (#63)

* improve calculateRetryWaitMilliseconds

* update comment

* cout -> spdlog

* fix crash on close

* uncomment test

* Revert "uncomment test"

This reverts commit 27df86ee8f.
2019-05-11 09:51:26 -07:00
16758293ff fix warning in statsd_client about %m gnu only printf special char 2019-05-11 09:22:29 -07:00
e965322a98 fix overflow warning in msgpack11.cpp 2019-05-10 21:17:05 -07:00
fcf5c41b43 add env var to display the ws command typed in 2019-05-10 16:27:23 -07:00
88adbf0ca2 cout -> spdlog 2019-05-10 12:33:34 -07:00
3df142db7a Improve calculateRetryWaitMilliseconds (#63)
* improve calculateRetryWaitMilliseconds

* update comment
2019-05-10 12:31:21 -07:00
f90fc4bfa2 fix static analyzer thing with un-used variable 2019-05-09 16:57:58 -07:00
dc1f9fb243 use spdlog for logging in ws + unittest + remove un-needed mutex 2019-05-09 15:30:44 -07:00
7c30c8aa07 cout -> cerr 2019-05-09 15:06:42 -07:00
b1d13105e7 Fixed pong synchronization issue (#62)
* Fixed pong synchronization issue

* Minor optimization in lock by scoping it to necessary changes.

* Fixing compilation issues
2019-05-09 15:06:05 -07:00
287537b34a tweak unittest sleep duration to fix gcc+linux CI 2019-05-09 11:10:39 -07:00
fe04014e1c Fail test GCC TSAN (#61)
* test that fails on Run 8

* commented the failing test
2019-05-09 09:33:18 -07:00
094b5834b7 warning police 2019-05-09 09:25:56 -07:00
1eb98cc74f fix ping, fix send frame close (#49)
* fix ping, fix send frame close

* fixes for data race on _closeCode etc. and fix test

* fixing one TC

* fix waiting forever if no time to change of readyState, and poll never end

* add 1005 code if no status code received

* fixes for 1005 code

* fix test issue

* fix macOS issue

* revert to master tests and renaming
2019-05-09 09:21:05 -07:00
232aa069d2 IXCobraMetricsThreadedPublisher.cpp uses a lambda to log instead of std::cerr 2019-05-08 18:53:32 -07:00
a69408fa25 rename ptr 2019-05-09 01:05:47 +03:00
75011d0b4e simplify bindWebsocket 2019-05-09 00:23:16 +03:00
28ae70ed20 use lock_guard 2019-05-09 00:20:26 +03:00
2727d39fa4 update comments 2019-05-09 00:16:37 +03:00
467f99b3bb Rename to WebSocketMessageQueue 2019-05-09 00:09:51 +03:00
d53c9c5ecf change default ports for the ws command line tool 2019-05-08 13:56:42 -07:00
48c19b4f3c ws connect display more accurate messages for incoming messages 2019-05-08 13:56:42 -07:00
16e5b08a0f Remove redundant iostream includes (#60) 2019-05-08 13:33:21 -07:00
636a69e9e1 qf 2019-05-08 22:24:39 +03:00
45d40dc159 Added IXWebSocketPoll class 2019-05-08 22:02:56 +03:00
88abb79a96 set thread name for Windows (#57) 2019-05-08 07:43:43 -07:00
1e1d5c3f7d update appveyor windows build file 2019-05-06 17:50:55 -07:00
95e9faff95 update README.md (#54)
Yeah !
2019-05-06 15:02:16 -07:00
979ff60a6b Use LUrlParser to fix issue of Windows (#53)
LGTM
2019-05-06 14:45:02 -07:00
ea2e8f0787 added tests for IXUrlParser (#52)
* added tests for IXUrlParser

* add me as author
2019-05-06 12:47:15 -07:00
3893c12054 socket server / used wrong mutex to protect _connectionsThreads 2019-05-06 12:24:20 -07:00
8ad47a315b Fix windows (#51)
* More fixes for Windows

* fix tests for windows

* qf for linux

* clean up
2019-05-06 12:22:57 -07:00
3b576c3047 Update README.md 2019-05-06 09:22:52 -07:00
4d83dab4f3 Fix for windows (#50) 2019-05-06 09:13:42 -07:00
28a7ec4f35 doc: add reference to the conan file built at https://github.com/Zinnion/conan-IXWebSocket 2019-05-01 21:31:32 -07:00
b5aae88a0b remove un-needed _backgroundThreadRunning variable 2019-05-01 11:09:25 -07:00
bee97237d9 Make AutomaticReconnection optional (#47)
* unittest pass + commands behave as expected

* cleanup
2019-04-29 21:12:34 -07:00
8c8e950455 build fix 2019-04-29 20:54:00 -07:00
ad8b344298 tsan fixes on ubuntu xenial (what travis run) 2019-04-29 20:48:16 -07:00
6d310d417a dns lookup: fix race condition accessing _errMsg 2019-04-29 19:29:27 -07:00
9dca893ce7 tsan linux tentative fix / copy string instead of passing a const reference 2019-04-29 17:27:53 -07:00
e3444e666b rename _blocking to _backgroundThreadRunning and invert the naming 2019-04-29 16:54:08 -07:00
e37e69311b fix data race on _thread 2019-04-29 16:46:16 -07:00
6918f863b1 fix data race on _useMask 2019-04-29 16:41:34 -07:00
9ee05bf591 ws connect mode / add a flag to disable automatic reconnection, not hooked up yet 2019-04-29 14:31:29 -07:00
e15700235e enable tsan on travis for all configs 2019-04-29 09:11:16 -07:00
1c7c07e128 initialize netSystem (aka winsock on windows) explicitely 2019-04-25 16:38:15 -07:00
4fbc4e3be9 Fixes for windows (#45)
* init Net system on Windows

* propagate DNS error

* Add zlib 1.2.11 sources

* link zlib statically for windows

* remove not implemented function declaration

* fix connect on Windows
2019-04-25 16:26:53 -07:00
e251c81d43 fix indentation of greatestCommonDivisor 2019-04-25 16:21:36 -07:00
f30a5074ab Remove commented code 2019-04-25 16:16:52 -07:00
f6ae490723 Fix data race in WebSocket where _url is accessed without protection in setThreadName
Also fix with url usage + docker container uses fedora and works with tsan
2019-04-25 16:11:46 -07:00
7f96c43d6f disable failing unittest temporarily 2019-04-25 09:04:35 -07:00
52260a63fb Speedup build for Windows (#43)
* Speedup build for Windows

* add space :)
2019-04-25 07:41:01 -07:00
82b528ee30 tsan fix for the IXWebSocketServerTest test, where there's a data race for connectionId 2019-04-24 22:11:14 -07:00
a443bbdf80 Fix data races in DNSLookup (tsan) 2019-04-24 21:53:31 -07:00
26ee46b246 better server termination / another try at preventing thread join failures 2019-04-24 09:45:53 -07:00
cf37816602 compiler warning police 2019-04-24 09:45:03 -07:00
b8087f6c48 Fix #38 Add some docker doc in the README 2019-04-23 20:51:58 -07:00
28cbe8fbeb ws cobra publish stress mode fix 2019-04-23 20:51:58 -07:00
28210ee31d add boolean and add missing protocol error close constant (#41) 2019-04-23 04:31:55 -07:00
0caf875399 add example websocket C++ server snake which supports basic cobra ops (publish and subscribe without stream sql 2019-04-22 17:33:45 -07:00
323684efff (server) attempt to fix broken macOS unittest on travis CI 2019-04-22 09:36:16 -07:00
678ee0615d Expand build section in the main README 2019-04-21 21:11:08 -07:00
6c889def37 Merge branch 'dhruvkakadiya-readme/fix' 2019-04-21 16:14:35 -07:00
93586deb6f For #39, fixed setOnMessageCallback() in README. 2019-04-21 14:56:02 -07:00
b6b9ffd15c doc cobra 2019-04-21 11:52:38 -07:00
1189b5f693 new target to uninstall files 2019-04-21 11:47:57 -07:00
1c2b6d59da Fix #37 / add directives to install headers and library 2019-04-21 11:42:37 -07:00
eacc28fedf move cobra files to their own subfolder 2019-04-21 11:20:17 -07:00
5c85ee1214 add cobra metrics publisher 2019-04-21 11:16:33 -07:00
7df7453365 indentation / comestic changes 2019-04-19 16:57:38 -07:00
3d8297247e (test) / use a random number generator to get a free port, when the bind to port 0 strategy does not work out 2019-04-19 16:50:04 -07:00
662f66e501 (socket server) wait for all connections threads to be terminated before exiting stop method 2019-04-19 16:31:33 -07:00
9131cb4790 default sanitizer choice 2019-04-19 15:13:59 -07:00
3b616676c6 disable clang sanitizers in CI on any platforms but Darwin 2019-04-19 15:09:01 -07:00
cce759b8dd fix Linux cast warning 2019-04-19 15:03:49 -07:00
98b6c9b89e (unittest) upgrade to Catch2 version 2.7.1 2019-04-19 14:41:03 -07:00
5370201df8 add locks around Socket::send and Socket::recv to see if it helps with thread sanitizer error in Linux CI 2019-04-19 14:28:51 -07:00
419c395966 uses sh syntax to capture output 2019-04-19 12:40:39 -07:00
2962ce9a0f (ci) verbose mode to figure out Linux build problems on travis 2019-04-19 12:10:43 -07:00
c96398aa0c Windows nmake does not have a -j flag 2019-04-19 11:58:02 -07:00
e68ce1d680 Windows + unittest python script fixes 2019-04-19 11:54:58 -07:00
d34f10b4ea fix warning / ws_cobra_subscribe does not need a verbose flag 2019-04-19 11:45:42 -07:00
7e2c1f274b (test) build dir is an absolute path 2019-04-19 11:45:02 -07:00
9fe3811c45 (ws) fix compiler warnings 2019-04-19 09:48:46 -07:00
b74bccee0a fix test execution on travis which was broken / unify running test locally and on travis 2019-04-19 09:46:17 -07:00
a2d170f415 fix bad merge in IXWebSocketTransport.cpp ... 2019-04-19 09:41:16 -07:00
03f762db86 Socket::Poll does not need a callback 2019-04-19 09:32:49 -07:00
aea859af52 Ping timeout use constant (#36)
* use constant for ping timeout

* change close code types
2019-04-19 09:16:25 -07:00
f61fd7b7f1 Link zlib statically for windows (#35)
* Add zlib 1.2.11 sources

* link zlib statically for windows
2019-04-19 09:14:03 -07:00
5682129b1d fix close code/reason issue (#34)
* fix close code/reason issue

* added code 1006 for abnormal closure
2019-04-18 10:02:31 -07:00
c3431f19bf Real ping (#32)
* close method change and fix code

* missing mutex

* wip

* renaming and fixes

* renaming, fixes

* added enablePong/disablePong, add tests

* added new test cases

* add 1 test case

* fix gcd name to greatestCommonDivisor

* move ping and ping timeout checks into socket poll, local var in test cases and indent fixes

* indent issue
2019-04-18 09:24:16 -07:00
65b11cb968 docker container works with SSL + fix compiler warnings in statsd third_party module 2019-04-18 09:11:12 -07:00
f4f60d38b8 docker + linux build fix 2019-04-17 22:52:03 -07:00
4337345103 setter method does not need to return anything, make it void 2019-04-17 20:36:26 -07:00
52f460f66d (doc) Add more doc to SocketServer 2019-04-17 20:36:26 -07:00
d486c72e02 websocket server: closed connection threads are joined properly 2019-04-17 20:36:26 -07:00
bdfc55b951 Bug/30 server connection problem (#31)
* use threads instead of std::async, need to cleanup threads

* less buggy server connection per thread system
2019-04-16 22:19:44 -07:00
b0f6026c23 make closeWireSize a default parameter of WebSocketTransport::close 2019-04-16 09:55:12 -07:00
b2aca491b6 close method change and fix code (#28)
* close method change and fix code

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

* fix for Unix

* Fix build if TLS is OFF

* add OpenSSL req to ws

* Fix build on Mac

* fix tests for windows
2019-04-16 08:51:57 -07:00
d4b0839328 move security framework linking to ixwebsocket (#26) 2019-04-14 17:13:24 -07:00
37c64841ff fix warning 2019-04-13 21:16:04 -07:00
8f8dd076ff ws: new command to subscribe to a cobra server and send an event to a sentry server 2019-04-11 16:03:05 -07:00
51fcf65424 (ws) add subcommands: cobra subscribe, and cobra subscribe to statsd bridge 2019-04-08 21:56:01 -07:00
56b19fa2b0 linux ci tentative fix 2019-04-03 22:02:10 -07:00
fe38dab405 better unittest runner / can run through lldb and produce a junit XML artifact 2019-03-29 15:54:05 -07:00
6cb2aaab65 Bump sleep time in test shell script 2019-03-29 09:36:56 -07:00
c604c4591f (redis_subscribe) in verbose mode, received message gets printed with a 'received: ' header 2019-03-29 09:35:19 -07:00
f0f54434cb Add clarification notice about third party modules 2019-03-29 09:34:17 -07:00
f9de85c257 offline version of remark-latest 2019-03-28 16:06:43 -07:00
44f817646e redis conf slides 2019-03-28 14:17:19 -07:00
91786779f8 slides 2019-03-27 16:27:52 -07:00
27d0aed2c9 add redis-conf slides 2019-03-27 15:53:55 -07:00
7767c96a9e ws redis command improvements + test script 2019-03-27 13:41:46 -07:00
3388bb50e1 (ws) redis_subscribe and redis_publish can take a password + display subscribe response 2019-03-26 09:33:22 -07:00
1554c587b3 update doc 2019-03-24 21:48:14 -07:00
ce70e73a34 remove Formula folder
Homebrew stuff is at https://github.com/bsergean/homebrew-IXWebSocket
2019-03-24 21:43:38 -07:00
804ec9246f (server) fix masking bug 2019-03-22 15:33:04 -07:00
f029321664 can send TEXT message (we only support BINARY messages now) 2019-03-22 14:24:22 -07:00
d41b7f64e4 unmasked code is broken 2019-03-22 14:24:15 -07:00
0366d1afb9 remove printf + unittest fix 2019-03-22 09:56:28 -07:00
4ef3073ca4 (server) server should not mask data when sending to client (some python client libraries enforce that and assert) 2019-03-22 09:53:56 -07:00
68a53aa884 (server) HTTP response is malformed 2019-03-22 09:52:19 -07:00
2358b3ff26 minor cleanup 2019-03-21 13:51:25 -07:00
00ed1d2817 HTTP/1.1 response should contains a reason (websocket server)
Fix compatibility problem with websockets python library, where the response does not contains a reason

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

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

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

* fix typo

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

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

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

* simple redis clients

* can publish to redis

* redis subscribe

* display messages received per second

* verbose flag

* (cmake) use clang only compile option -Wshorten-64-to-32 when compiling with clang
2019-03-20 14:29:02 -07:00
663299c91e Fixed heartbeat typos (#22) 2019-03-19 21:31:43 -07:00
523a6e989a make PollResultType an enum class 2019-03-19 09:29:57 -07:00
13f4aee5ee fix bug with isReadyToWrite 2019-03-18 22:05:04 -07:00
bbc0e2106c workaround bug in Socket::isReadyToWrite 2019-03-18 20:37:33 -07:00
eb6ee52aaa use milliseconds 2019-03-18 20:17:44 -07:00
80e330d4c3 ws / log subcommand name 2019-03-18 17:54:06 -07:00
a3adc49d8c disable sigpipe on osx when writing/reading into a dead pipe 2019-03-18 17:52:01 -07:00
9c6eeed0f8 edit homebrew install steps 2019-03-18 15:45:33 -07:00
705e9a93f8 add an install target 2019-03-18 15:11:08 -07:00
572a217050 no default parameters for isReadyToWrite and isReadyToRead 2019-03-18 14:31:21 -07:00
d58798e36c when trying to flush the send buffer, use select to wait until it is possible instead of using sleep to retry at a given frequency 2019-03-18 14:25:27 -07:00
e98634a277 Fix typo (#19) 2019-03-17 16:08:28 -07:00
f1f08eced0 remove unused gitmodule file 2019-03-17 10:38:48 -07:00
6c2da9f0e4 remove unused folder 2019-03-17 10:38:19 -07:00
e158635f57 linux hangs when closing 2019-03-16 11:38:23 -07:00
5a241e77da edit README 2019-03-16 11:32:46 -07:00
68e397ab34 use pipe to abort select on Linux as well as macOS 2019-03-15 17:46:40 -07:00
4c78b94cd8 missing assert include on Linux 2019-03-15 11:43:27 -07:00
3a9cc9b079 cleanup 2019-03-15 11:41:57 -07:00
6ff8c6e7f3 unittest fix 2019-03-14 18:58:16 -07:00
6f90425154 linux compile fix 2019-03-14 18:55:33 -07:00
49ec9b1d9e linux fixes 2019-03-14 18:54:47 -07:00
a0e35ad644 build fix 2019-03-14 18:53:21 -07:00
b91dc77d6f select interrupt cleanup 2019-03-14 18:37:38 -07:00
b462b5a5c8 linux build fix 2019-03-14 15:17:17 -07:00
b5e7fb20b6 replace uint8_t with uint64_t for the send/close requests types / use named variable to index into the _fildes array 2019-03-14 15:03:57 -07:00
9d245add9c set a default close reason string 2019-03-14 14:52:51 -07:00
ded03ed743 do not busy loop while sending 2019-03-14 14:48:08 -07:00
6cc260c04e remove docker folder 2019-03-14 14:48:02 -07:00
5b4354a6f3 send optimization + ws file transfer test 2019-03-14 14:47:53 -07:00
34de36fe01 Update README.md
Stop lying about Windows support ...
2019-03-13 23:10:40 -07:00
08c2cdbf1d send can fail silently when sending would block (EWOULDBLOCK return for send) (#18)
* try to use a pipe for communication

* flush send buffer on the background thread

* cleanup

* linux fix / linux still use event fd for now

* cleanup
2019-03-13 23:09:45 -07:00
dedbeb3eab websocket send: make sure all data in the kernel buffer is sent 2019-03-11 22:16:55 -07:00
d88bf16500 add new message type when receiving message fragments 2019-03-11 11:12:43 -07:00
ad9c8318a7 ws broacast_server / can set serving hostname 2019-03-10 16:36:44 -07:00
f2778c0729 debian 9 unittest build fix 2019-03-10 16:07:48 -07:00
03ca73658c asan test suite fix 2019-03-09 10:45:40 -08:00
1da5f6c30c unittest + warning fix 2019-03-09 10:37:14 -08:00
bee8a99a34 add ability to run with asan on macOS 2019-03-05 17:07:28 -08:00
f5efd41dc1 fix compiler warnings in ws command line tool 2019-03-04 13:56:30 -08:00
c202f8cf1d Socket code refactoring, plus stop polling with a 1s timeout in readBytes while we only want to poll with a 1ms timeout 2019-03-04 13:40:15 -08:00
0c226c7629 readBytes does not read bytes one by one but in chunks 2019-03-02 21:11:16 -08:00
a9e772f330 create a blocking + cancellable Socket::readBytes method 2019-03-02 15:16:46 -08:00
86cc76388e create a blocking + cancellable Socket::readBytes method 2019-03-02 11:01:51 -08:00
0f4e9af172 more ws doc 2019-02-28 22:07:45 -08:00
3a1352c8ec more ws doc 2019-02-28 22:03:48 -08:00
2c86fd947f Feature/http (#16)
* add skeleton and broken http client code.

GET returns "Resource temporarily unavailable" errors...

* linux compile fix

* can GET some pages

* Update formatting in README.md

* unittest for sending large messages

* document bug

* Feature/send large message (#14)

* introduce send fragment

* can pass a fin frame

* can send messages which are a perfect multiple of the chunk size

* set fin only for last fragment

* cleanup

* last fragment should be of type CONTINUATION

* Add simple send and receive programs

* speedups receiving + better way to wait for thing

* receive speedup by using linked list of chunks instead of large array

* document bug

* use chunks to receive data

* trailing spaces

* Update README.md

Add note about message fragmentation.

* Feature/ws cli (#15)

* New command line tool for transfering files / still very beta.

* add readme

* use cli11 for argument parsing

* json -> msgpack

* stop using base64 and use binary which can be stored in message pack

* add target for building with homebrew

* all CMakeLists are referenced by the top level one

* add ws_chat and ws_connect sub commands to ws

* cleanup

* add echo and broadcast server as ws sub-commands

* add gitignore

* comments

* ping pong added to ws

* mv cobra_publisher under ws folder

* Update README.md

* linux build fix

* linux build fix

* move http_client to a ws sub-command

* simple HTTP post support (urlencode parameters)

* can specify extra headers

* chunk encoding / simple redirect support / -I option

* follow redirects is optional

* make README vim markdown plugin friendly

* cleanup argument parsing + add socket creation factory

* add missing file

* http gzip compression

* cleanup

* doc

* Feature/send large message (#14)

* introduce send fragment

* can pass a fin frame

* can send messages which are a perfect multiple of the chunk size

* set fin only for last fragment

* cleanup

* last fragment should be of type CONTINUATION

* Add simple send and receive programs

* speedups receiving + better way to wait for thing

* receive speedup by using linked list of chunks instead of large array

* document bug

* use chunks to receive data

* trailing spaces
2019-02-28 21:54:03 -08:00
927484a71f Update README.md 2019-02-22 21:53:29 -08:00
88adb8f5ef mv cobra_publisher under ws folder 2019-02-22 21:51:03 -08:00
98e7f5cb22 ping pong added to ws 2019-02-22 21:47:57 -08:00
67e5957064 comments 2019-02-22 21:27:49 -08:00
8c3473a91a add gitignore 2019-02-22 21:26:25 -08:00
1c775cb759 add echo and broadcast server as ws sub-commands 2019-02-22 21:25:56 -08:00
c4054d4984 cleanup 2019-02-22 20:51:22 -08:00
76e2f9f3ac add ws_chat and ws_connect sub commands to ws 2019-02-22 20:49:26 -08:00
41a40b8b9f all CMakeLists are referenced by the top level one 2019-02-21 22:21:29 -08:00
6b8694bfbf add target for building with homebrew 2019-02-21 22:05:30 -08:00
2d696b6806 Feature/ws cli (#15)
* New command line tool for transfering files / still very beta.

* add readme

* use cli11 for argument parsing

* json -> msgpack

* stop using base64 and use binary which can be stored in message pack
2019-02-21 21:24:53 -08:00
709a5ec89a Update README.md
Add note about message fragmentation.
2019-02-21 14:08:27 -08:00
932bb732e0 Feature/send large message (#14)
* introduce send fragment

* can pass a fin frame

* can send messages which are a perfect multiple of the chunk size

* set fin only for last fragment

* cleanup

* last fragment should be of type CONTINUATION

* Add simple send and receive programs

* speedups receiving + better way to wait for thing

* receive speedup by using linked list of chunks instead of large array

* document bug

* use chunks to receive data

* trailing spaces
2019-02-20 18:59:07 -08:00
dd4e29542c document bug 2019-02-16 10:33:37 -08:00
726e66ca66 unittest for sending large messages 2019-02-16 10:32:02 -08:00
474fd70ec7 Update formatting in README.md 2019-02-05 23:04:45 -08:00
5074dbc3c6 more conf in CI 2019-01-29 17:50:19 -08:00
eb54e7f1ae get free port that can be used by non root users (> 1024) 2019-01-28 15:24:19 -08:00
8983dd97a1 use dynamically generated port number to configure servers in unittest 2019-01-28 15:24:19 -08:00
7eaea28970 Merge pull request #13 from machinezone/user/bsergeant/poll
User/bsergeant/poll
2019-01-27 10:47:38 -08:00
907605c59c windows build fix 2019-01-27 10:46:02 -08:00
58921592c8 constexpr to declare number of fds 2019-01-26 21:01:36 -08:00
b9c49c38ed linux fix 2019-01-26 20:57:48 -08:00
76c97027c8 remove shutdown call 2019-01-26 20:54:23 -08:00
5db3620f49 rebase poll branch 2019-01-26 20:50:25 -08:00
a2e6fa0b16 insensitive string compare when validating server connection header 2019-01-25 16:17:51 -08:00
1d359f0fc4 Merge pull request #12 from machinezone/user/bsergeant/heart-beat
Add an optional heartbeat
2019-01-25 16:14:28 -08:00
885d245afb heartbeat correct 2019-01-25 16:11:39 -08:00
75d01c0c11 close server socket on exit 2019-01-24 21:16:32 -08:00
ea219e3ddd unittest fix 2019-01-24 19:54:10 -08:00
e9cd54b2f4 add an heartbeat test 2019-01-24 18:50:07 -08:00
a8b6573f96 hearbeat 2019-01-24 12:42:49 -08:00
4e158c8ba7 Update README.md 2019-01-15 09:36:43 -08:00
121c84a2d1 check and validate the Connection: Upgrade header in client/server 2019-01-15 09:31:37 -08:00
bfb76de9ab Merge pull request #10 from tonylin0826/master
Fix missing "Upgrade" header error
2019-01-15 09:22:11 -08:00
2434605c06 Fix missing upgrade header error 2019-01-15 15:35:37 +08:00
a0f15bfb56 C++14 + use make_unique and make_shared to make shared pointers 2019-01-11 21:25:06 -08:00
7fabd14a63 add travis badge 2019-01-08 10:13:23 -08:00
6b4d2aeb07 travis -> osx 2019-01-08 10:04:47 -08:00
5ab61b46b5 Revert "Revert "try asan on Linux"" [Back to asan on Linux]
This reverts commit 02a704a8c7.
2019-01-07 21:13:48 -08:00
39c9691d70 travis-ci: try to use clang on Linux 2019-01-07 20:49:03 -08:00
d00960b33f Revert "try asan on Linux"
This reverts commit dd2360ed70.
2019-01-07 20:47:25 -08:00
4a5cfac2ea try asan on Linux 2019-01-07 18:29:44 -08:00
7e1d21239f build with osx on travis 2019-01-07 18:16:29 -08:00
1a8b870a9e fix simple compile error in test/IXTest.h 2019-01-07 18:08:11 -08:00
3e150db493 add a travis file for real 2019-01-07 18:05:55 -08:00
1cf8b7e952 add a travis file 2019-01-07 18:04:28 -08:00
ea75432f12 update test remote ws url 2019-01-07 11:28:53 -08:00
85370dfd21 remove cmake sanitizer submodule 2019-01-07 11:26:23 -08:00
8a0afef825 check select errors better 2019-01-07 11:18:00 -08:00
76f196206b sanitizer cmake stuff 2019-01-06 18:54:16 -08:00
bf3e8195f7 DNSLookup _id member does not need to be an atomic 2019-01-06 18:32:19 -08:00
bce3071a12 DNSLookup: fix #8 2019-01-06 18:27:26 -08:00
911f684e4d DNSLookup: copy hostname and port instead of accessing member 2019-01-06 18:17:12 -08:00
49bc156a56 return false -> return -1 2019-01-06 18:10:39 -08:00
791c3701d7 add new unittest 2019-01-06 15:14:13 -08:00
372af54e46 remove dead file 2019-01-06 14:26:11 -08:00
20c8953e5b remove openssl testing bits for apple build 2019-01-06 14:21:49 -08:00
2f9ad54bae gcc linux compile fix 2019-01-06 12:12:39 -08:00
57c22cddb8 make a class hierarchy for server code (IXWebSocketServer <- IXSocketServer) 2019-01-06 12:09:31 -08:00
a5026849a3 add log 2019-01-05 21:16:13 -08:00
60dc765178 unittest + compiler warnings 2019-01-05 21:10:08 -08:00
5e1c150024 windows compile fix 2019-01-05 21:02:55 -08:00
0fd06bb592 int -> ssize_t for socker recv and send 2019-01-05 20:53:50 -08:00
9641c8cf49 header refactoring 2019-01-05 20:38:43 -08:00
4ca31be4a2 windows connect (compile fix) 2019-01-05 17:35:50 -08:00
667f18cbfe windows connect potential fix 2019-01-05 17:32:21 -08:00
4df5050760 more accurate description of errors 2019-01-05 17:18:43 -08:00
f50881a72f SocketTest / more debug info 2019-01-05 17:10:01 -08:00
b80696af00 windows (compile) fix 2019-01-05 17:04:09 -08:00
3cb2f6dcf7 windows fix 2019-01-05 17:02:39 -08:00
b1e2c4ce72 test failure is not noticed 2019-01-05 16:30:22 -08:00
89ff9dd5ac fix gcc warning 2019-01-05 16:26:11 -08:00
8b95b173cd makefile tweak 2019-01-05 14:43:21 -08:00
80a877ddab add dns lookup test 2019-01-05 14:40:17 -08:00
e892b21872 openssl cleanup 2019-01-05 11:42:25 -08:00
c344913ae8 Merge pull request #7 from bsergean/user/bsergeant/appveyor_first
unittest on appveyor
2019-01-04 17:29:23 -08:00
3eef8fba27 unittest on appveyor 2019-01-04 17:28:13 -08:00
d34e47f716 windows fixes 2019-01-04 15:23:57 -08:00
9bfba28d01 Merge pull request #6 from machinezone/user/bsergeant/server
Add support for writing websocket servers (IXWebSocketServer)
2019-01-03 18:47:30 -08:00
cc43357fb4 Socket::readLine works with arbitrary long lines 2019-01-03 18:47:01 -08:00
bce5ef2dca timeout is configurable 2019-01-03 18:33:08 -08:00
3021ac4b95 remove useless FIXME comment 2019-01-03 18:02:03 -08:00
385e80d185 new doc 2019-01-03 18:00:48 -08:00
bd1c8873d0 capture path/uri when connecting, and pass it back through callbacks in the openInfo member 2019-01-03 17:44:10 -08:00
6ac3bdb94a implement a max connections (default = 32) settings 2019-01-03 17:05:44 -08:00
e964a0a1f0 correct validation of the request (request line + headers) 2019-01-03 13:41:06 -08:00
97255fbd62 cancellation refactoring 2019-01-03 12:53:44 -08:00
d5041f64be rename test file 2019-01-02 21:59:06 -08:00
64f649d1f9 split handshake code into its own files, so that Transport file is less massive 2019-01-02 20:07:54 -08:00
097c7e5397 server unittest for validating client request / new timeout cancellation handling (need refactoring) 2019-01-02 16:08:32 -08:00
c6adc00eac add new broadcast server example 2019-01-02 08:17:03 -08:00
b1710bfa31 server per message deflate support 2019-01-02 08:12:29 -08:00
0e52c42970 echo server example is a real echo server, not a broadcast server 2019-01-02 08:10:39 -08:00
2b136b2981 refactoring + cancellation was buggy during http upgrade 2019-01-02 07:45:07 -08:00
b95e5e36dc use select to detect new incoming connections 2019-01-01 22:21:07 -08:00
1bc5bc7f1c add a way to run in blocking more, which is useful for server mode to have N*thread instead of 2N*thread for N connections 2019-01-01 21:25:15 -08:00
946d7015a2 more named constants 2019-01-01 19:23:27 -08:00
4adf5720f0 gitignore stuff 2019-01-01 17:14:31 -08:00
973a3f03c3 linux fix + unittest works with Linux 2019-01-01 17:13:26 -08:00
06177afd6a unittest starts a server 2019-01-01 16:34:05 -08:00
e5937638d4 crash when server failed to start 2019-01-01 16:14:46 -08:00
7c4f14f941 thread accepting connections can be cancelled/stopped externally 2019-01-01 16:11:27 -08:00
ead54d6c37 listen job run in its own thread, non blocking 2019-01-01 14:52:14 -08:00
b749f3c724 (nitpick) reformat 2019-01-01 14:29:57 -08:00
d279aecb87 cleanup / remove printf, add mutex, remove hardcoded values, can pass in a binding host 2019-01-01 14:28:41 -08:00
67de0fc8da use shared_ptr 2019-01-01 13:53:13 -08:00
8ed2399517 unittest pass 2019-01-01 13:47:25 -08:00
cf340011e2 record workers in a map instead of a vector 2018-12-31 14:52:59 -08:00
ec2ad37860 add a print statement when the connection is closed / still need to terminate server thread 2018-12-31 12:47:42 -08:00
3443e82812 more cleanup to propagate server connection error and let onOpen callback execute 2018-12-31 12:43:47 -08:00
63138507d6 only bind to localhost 2018-12-31 11:48:49 -08:00
b2eb07db14 server code has a callback that takes a websocket 2018-12-30 22:12:13 -08:00
379a845166 cleanup / use a websocket instead of raw websockettransport 2018-12-30 22:00:49 -08:00
266cf93584 can accept multiple connection / server can send data back to client 2018-12-30 21:16:05 -08:00
0ee71e9a09 proof of concept server implementation 2018-12-29 23:15:27 -08:00
ea07afcc0b can create a socket from a fd 2018-12-29 21:53:33 -08:00
43cd6d34ca add simple unittest 2018-12-29 18:34:08 -08:00
3b67032adb add missing src files (IXSetThreadName.{cpp,h}) ... 2018-12-23 14:19:30 -08:00
2d46a0605b fix warning: field '_eventCallback' will be initialized after field '_publishMode' 2018-12-23 14:18:53 -08:00
ba54664748 Fix warning: field '_done' will be initialized after field '_wait' [-Wreorder] _done(false), 2018-12-23 14:17:30 -08:00
a79f4c10a1 set thread name / rename example 2018-12-23 14:14:38 -08:00
bd04b28b9e async dns lookup fix 2018-12-14 17:49:42 -08:00
cbadecab33 non blocking dns lookup 2018-12-14 16:28:17 -08:00
8c079787f0 add cancellation support while connecting, to speed up WebSocket::stop 2018-12-09 17:56:20 -08:00
62528e6a0b http upgrade and connections use non blocking sockets 2018-12-09 14:07:40 -08:00
49bf8bd830 threading race condition fixes, detected by TSAN 2018-12-06 08:27:28 -08:00
c64bc20bb5 cleanup 2018-11-14 15:52:28 -08:00
54da891f79 per-message deflate compression fixes 2018-11-13 17:46:05 -08:00
e847716076 move files around 2018-11-12 17:56:59 -08:00
3a68bbd1b2 update readme / remove reference to missing compression support now that it is supported ... 2018-11-12 09:01:42 -08:00
9cb1d03411 tweaks doc / license + send proper error code when closing the connecion 2018-11-12 09:00:55 -08:00
4fed156b90 per message deflate support (with zlib) 2018-11-09 18:42:09 -08:00
de8bcd36e8 (satori_publisher) better error handling 2018-11-07 14:54:44 -08:00
135cfe3238 Add some example shell scripts to build on Linux 2018-11-07 12:33:33 -08:00
6dbfe28427 Add new example folder for publishing events to satori, with a minimal satori sdk 2018-11-07 12:26:32 -08:00
2b203c4616 Add missing files ... 2018-11-07 12:25:38 -08:00
f12e655cf8 Add DockerFile + parse rsv1 field 2018-11-07 11:45:17 -08:00
cf0045a483 stopping connection on Linux does not close the socket, which can create problem when re-starting the connection 2018-11-01 17:02:49 -07:00
9c81eeace0 add stop and start directives to ws_connect + display close info 2018-10-31 10:27:17 -07:00
5b333f91f6 update readme.md 2018-10-27 11:46:11 -07:00
912d926260 Handle Sec-WebSocket-Accept correctly 2018-10-27 10:24:48 -07:00
a8dfd640a7 capture an error code and a reason when the server closes the connection 2018-10-25 18:51:19 -07:00
390044b716 add doc about ping/pong 2018-10-25 15:14:31 -07:00
8ac36e6ee5 ping pong example: more error handling 2018-10-25 14:46:23 -07:00
208c693088 ping / pong support / fix bug in dispatching received message type 2018-10-25 14:43:35 -07:00
eae2f7d113 Better ping/pong support 2018-10-25 14:43:35 -07:00
45f92115f9 New ws_connect example. Close to wscat node.js tool. 2018-10-25 14:43:35 -07:00
42f3adc7a2 Update README.md 2018-10-08 21:50:55 -07:00
71b40c6d6c Windows support (no TLS yet) 2018-10-08 21:44:54 -07:00
af12089e7a make TLS support optional 2018-10-08 15:24:36 -07:00
33677c4b2b move examples around 2018-10-08 15:24:36 -07:00
376c8c2e00 Update IXWebSocket.h
Remove dead code
2018-10-07 15:49:07 -07:00
8232e9e8ce Update README.md
Advanced usage -> API
2018-10-07 15:47:38 -07:00
436bf8deb5 more ssl peer validation stuff 2018-10-05 18:45:44 -07:00
c858a1c9e5 Update README.md 2018-10-05 14:35:09 -07:00
735 changed files with 190818 additions and 1480 deletions

47
.clang-format Normal file
View File

@ -0,0 +1,47 @@
# https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormatStyleOptions.html
---
Language: Cpp
BasedOnStyle: WebKit
AlignAfterOpenBracket: Align
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Allman
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 100
ConstructorInitializerAllOnOneLineOrOnePerLine: false
Cpp11BracedListStyle: true
FixNamespaceComments: true
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^["<](stdafx|pch)\.h[">]$'
Priority: -1
- Regex: '^<Windows\.h>$'
Priority: 3
- Regex: '^<(WinIoCtl|winhttp|Shellapi)\.h>$'
Priority: 4
- Regex: '.*'
Priority: 2
IndentCaseLabels: true
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
PenaltyReturnTypeOnItsOwnLine: 1000
PointerAlignment: Left
SpaceAfterTemplateKeyword: false
SpaceAfterCStyleCast: true
Standard: Cpp11
UseTab: Never

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
build
CMakeCache.txt
ws/CMakeCache.txt
test/build

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

@ -0,0 +1,66 @@
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_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_openssl:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-vsdevenv@master
- run: |
mkdir build
cd build
cmake -DCMAKE_CXX_COMPILER=cl.exe -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

25
.github/workflows/mkdocs.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: mkdocs
on:
push:
paths:
- 'docs/**'
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install mkdocs
pip install mkdocs-material
pip install pygments
- name: Build doc
run: |
git pull
mkdocs gh-deploy

19
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,19 @@
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'

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
build
*.pyc
venv
ixsnake/ixsnake/.certs/
site/
ws/.certs/
ws/.srl

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

@ -0,0 +1,12 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.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/FindJsonCpp.cmake Normal file
View File

@ -0,0 +1,19 @@
# 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})

13
CMake/FindMbedTLS.cmake Normal file
View File

@ -0,0 +1,13 @@
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
find_library(MBEDTLS_LIBRARY mbedtls)
find_library(MBEDX509_LIBRARY mbedx509)
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}")
include(FindPackageHandleStandardArgs)
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)

19
CMake/FindSpdLog.cmake Normal file
View File

@ -0,0 +1,19 @@
# Find package structure taken from libcurl
include(FindPackageHandleStandardArgs)
find_path(SPDLOG_INCLUDE_DIRS spdlog/spdlog.h)
find_library(JSONCPP_LIBRARY spdlog)
find_package_handle_standard_args(SPDLOG
FOUND_VAR
SPDLOG_FOUND
REQUIRED_VARS
SPDLOG_LIBRARY
SPDLOG_INCLUDE_DIRS
FAIL_MESSAGE
"Could NOT find spdlog"
)
set(SPDLOG_INCLUDE_DIRS ${SPDLOG_INCLUDE_DIRS})
set(SPDLOG_LIBRARIES ${SPDLOG_LIBRARY})

257
CMakeLists.txt Normal file
View File

@ -0,0 +1,257 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
project(ixwebsocket C CXX)
set (CMAKE_CXX_STANDARD 14)
set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
endif()
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshorten-64-to-32")
endif()
set( IXWEBSOCKET_SOURCES
ixwebsocket/IXBench.cpp
ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXConnectionState.cpp
ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXExponentialBackoff.cpp
ixwebsocket/IXHttp.cpp
ixwebsocket/IXHttpClient.cpp
ixwebsocket/IXHttpServer.cpp
ixwebsocket/IXNetSystem.cpp
ixwebsocket/IXSelectInterrupt.cpp
ixwebsocket/IXSelectInterruptFactory.cpp
ixwebsocket/IXSocket.cpp
ixwebsocket/IXSocketConnect.cpp
ixwebsocket/IXSocketFactory.cpp
ixwebsocket/IXSocketServer.cpp
ixwebsocket/IXSocketTLSOptions.cpp
ixwebsocket/IXUdpSocket.cpp
ixwebsocket/IXUrlParser.cpp
ixwebsocket/IXUserAgent.cpp
ixwebsocket/IXWebSocket.cpp
ixwebsocket/IXWebSocketCloseConstants.cpp
ixwebsocket/IXWebSocketHandshake.cpp
ixwebsocket/IXWebSocketHttpHeaders.cpp
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
ixwebsocket/IXWebSocketServer.cpp
ixwebsocket/IXWebSocketTransport.cpp
)
set( IXWEBSOCKET_HEADERS
ixwebsocket/IXBench.h
ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h
ixwebsocket/IXDNSLookup.h
ixwebsocket/IXExponentialBackoff.h
ixwebsocket/IXHttp.h
ixwebsocket/IXHttpClient.h
ixwebsocket/IXHttpServer.h
ixwebsocket/IXNetSystem.h
ixwebsocket/IXProgressCallback.h
ixwebsocket/IXSelectInterrupt.h
ixwebsocket/IXSelectInterruptFactory.h
ixwebsocket/IXSetThreadName.h
ixwebsocket/IXSocket.h
ixwebsocket/IXSocketConnect.h
ixwebsocket/IXSocketFactory.h
ixwebsocket/IXSocketServer.h
ixwebsocket/IXSocketTLSOptions.h
ixwebsocket/IXUdpSocket.h
ixwebsocket/IXUrlParser.h
ixwebsocket/IXUtf8Validator.h
ixwebsocket/IXUserAgent.h
ixwebsocket/IXWebSocket.h
ixwebsocket/IXWebSocketCloseConstants.h
ixwebsocket/IXWebSocketCloseInfo.h
ixwebsocket/IXWebSocketErrorInfo.h
ixwebsocket/IXWebSocketHandshake.h
ixwebsocket/IXWebSocketHandshakeKeyGen.h
ixwebsocket/IXWebSocketHttpHeaders.h
ixwebsocket/IXWebSocketInitResult.h
ixwebsocket/IXWebSocketMessage.h
ixwebsocket/IXWebSocketMessageType.h
ixwebsocket/IXWebSocketOpenInfo.h
ixwebsocket/IXWebSocketPerMessageDeflate.h
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h
ixwebsocket/IXWebSocketVersion.h
)
if (UNIX)
# Linux, Mac, iOS, Android
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.cpp )
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.h )
endif()
# Platform specific code
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)
endif()
option(USE_TLS "Enable TLS support" FALSE)
if (USE_TLS)
# 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()
else() # default to OpenSSL on all other platforms
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
set(USE_OPEN_SSL ON)
endif()
endif()
if (USE_MBED_TLS)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
elseif (USE_SECURE_TRANSPORT)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
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 (USE_TLS)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_TLS)
if (USE_MBED_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 (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)
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 ${OPENSSL_INCLUDE_DIR})
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
elseif (USE_MBED_TLS)
message(STATUS "TLS configured to use mbedtls")
find_package(MbedTLS REQUIRED)
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
elseif (USE_SECURE_TRANSPORT)
message(STATUS "TLS configured to use secure transport")
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
endif()
endif()
# This ZLIB_FOUND check is to help find a cmake manually configured zlib
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()
if (WIN32)
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
if (USE_TLS)
target_link_libraries(ixwebsocket Crypt32)
endif()
endif()
if (UNIX)
find_package(Threads)
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
endif()
set( IXWEBSOCKET_INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}
)
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
# Build with Multiple Processes
target_compile_options(ixwebsocket PRIVATE /MP)
endif()
target_include_directories(ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS})
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/
)
if (USE_WS OR USE_TEST)
add_subdirectory(ixcore)
add_subdirectory(ixcrypto)
add_subdirectory(ixcobra)
add_subdirectory(ixsnake)
add_subdirectory(ixsentry)
add_subdirectory(ixbots)
add_subdirectory(third_party/spdlog spdlog)
if (USE_WS)
add_subdirectory(ws)
endif()
if (USE_TEST)
add_subdirectory(test)
endif()
endif()
if (USE_LUAROCKS)
add_subdirectory(luarocks)
endif()

1
Dockerfile Symbolic link
View File

@ -0,0 +1 @@
docker/Dockerfile.alpine

163
README.md
View File

@ -1,164 +1,47 @@
# General
## Hello world
## Introduction
![Build status](https://github.com/machinezone/IXWebSocket/workflows/unittest/badge.svg)
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
communication channels over a single TCP connection. This library provides a C++ library for Websocket communication. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient).
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.
## Examples
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.
The examples folder countains a simple chat program, using a node.js broadcast server.
```cpp
// Required on Windows
ix::initNetSystem();
Here is what the API looks like.
```
// Our websocket object
ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
webSocket.configure(url);
webSocket.setUrl(url);
// Setup a callback to be fired when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback(
[](ix::WebSocketMessageType messageType, const std::string& str, ix::WebSocketErrorInfo error)
// 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 (messageType == ix::WebSocket_MessageType_Message)
if (msg->type == ix::WebSocketMessageType::Message)
{
std::cout << str << std::endl;
std::cout << msg->str << std::endl;
}
});
}
);
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
// Send a message to the server
// Send a message to the server (default to TEXT mode)
webSocket.send("hello world");
// ... finally ...
// Stop the connection
webSocket:stop()
```
## Implementation details
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.
### TLS/SSL
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).
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and OpenSSL is used on Android.
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.
### Polling and background thread work
## Users
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.
If your company or project is using this library, feel free to open an issue or PR to amend this list.
### Automatic reconnection
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds.
## Limitations
* There is no per message compression support. That could be useful for retrieving large messages, but could also be implemented at the application level.
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
* 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.
## Examples
1. Bring up a terminal and jump to the examples folder.
2. Compile the example C++ code. `sh build.sh`
3. Install node.js from [here](https://nodejs.org/en/download/).
4. Type `npm install` to install the node.js dependencies. Then `node broadcast-server.js` to run the server.
5. Bring up a second terminal. `env USER=bob ./cmd_websocket_chat`
6. Bring up a third terminal. `env USER=bill ./cmd_websocket_chat`
7. Start typing things in any of those terminals. Hopefully you should see your message being received on the other end.
## C++ code organization
Here's a simplistic diagram which explains how the code is structured in term of class/modules.
```
+-----------------------+
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
| |
+-----------------------+
| |
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
| |
+-----------------------+
| |
| IXWebSocket | ws:// Unencrypted Socket handler
| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS
| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux
| | Can be used on macOS too.
+-----------------------+
```
## Advanced usage
### Sending messages
`websocket.send("foo")` will send a message.
If the connection was closed and sending failed, the return value will be set to false.
### ReadyState
`getReadyState()` returns the state of the connection. There are 4 possible states.
1. WebSocket_ReadyState_Connecting - The connection is not yet open.
2. WebSocket_ReadyState_Open - The connection is open and ready to communicate.
3. WebSocket_ReadyState_Closing - The connection is in the process of closing.
4. WebSocket_MessageType_Close - The connection is closed or couldn't be opened.
### Open and Close notifications
The onMessage event will be fired when the connection is opened or closed. This is similar to the [Javascript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
```
webSocket.setOnMessageCallback(
[this](ix::WebSocketMessageType messageType, const std::string& str, ix::WebSocketErrorInfo error)
{
if (messageType == ix::WebSocket_MessageType_Open)
{
puts("send greetings");
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
puts("disconnected");
}
}
);
```
### Error notification
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocket_MessageType_Error`. Multiple fields will be available on the event to describe the error.
```
webSocket.setOnMessageCallback(
[this](ix::WebSocketMessageType messageType, const std::string& str, ix::WebSocketErrorInfo error)
{
if (messageType == ix::WebSocket_MessageType_Error)
{
std::stringstream ss;
ss << "Error: " << error.reason << std::endl;
ss << "#retries: " << event.retries << std::endl;
ss << "Wait time(ms): " << event.wait_time << std::endl;
ss << "HTTP Status: " << event.http_status << std::endl;
std::cout << ss.str() << std::endl;
}
}
);
```
### start, stop
1. `websocket.start()` connect to the remote server and starts the message receiving background thread.
2. `websocket.stop()` disconnect from the remote server and closes the background thread.
### Configuring the remote url
The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url.
```
std::string url("wss://example.com");
websocket.configure(url);
```
- [Machine Zone](https://www.mz.com)

11
SECURITY.md Normal file
View File

@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 7.x.x | :white_check_mark: |
## Reporting a Vulnerability
Users should send an email to bsergean@gmail.com to report a vulnerability.

22
appveyor.yml Normal file
View File

@ -0,0 +1,22 @@
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

67
docker-compose.yml Normal file
View File

@ -0,0 +1,67 @@
version: "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
# 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:

39
docker/Dockerfile.alpine Normal file
View File

@ -0,0 +1,39 @@
FROM alpine:3.11 as build
RUN apk add --no-cache \
gcc g++ musl-dev linux-headers \
cmake mbedtls-dev make zlib-dev
RUN addgroup -S app && \
adduser -S -G app app && \
chown -R app:app /opt && \
chown -R app:app /usr/local
# 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_mbedtls_install && \
sh tools/trim_repo_for_docker.sh
FROM alpine:3.11 as runtime
RUN apk add --no-cache libstdc++ mbedtls ca-certificates && \
addgroup -S app && \
adduser -S -G app app
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
# COPY --chown=app:app --from=build /opt /opt
RUN chmod +x /usr/local/bin/ws && \
ldd /usr/local/bin/ws
# Now run in usermode
USER app
WORKDIR /home/app
ENTRYPOINT ["ws"]
EXPOSE 8008

41
docker/Dockerfile.centos Normal file
View File

@ -0,0 +1,41 @@
FROM centos:8 as build
RUN yum install -y gcc-c++ make cmake zlib-devel openssl-devel redhat-rpm-config
RUN yum install -y epel-release
RUN yum install -y mbedtls-devel
RUN groupadd app && useradd -g app app
RUN chown -R app:app /opt
RUN chown -R app:app /usr/local
# 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_mbedtls_install" ]
RUN [ "rm", "-rf", "build" ]
FROM centos:8 as runtime
RUN yum install -y gdb strace
RUN yum install -y epel-release
RUN yum install -y mbedtls
RUN groupadd app && useradd -g app app
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
RUN chmod +x /usr/local/bin/ws
RUN ldd /usr/local/bin/ws
# Copy source code for gcc
COPY --chown=app:app --from=build /opt /opt
# Now run in usermode
USER app
WORKDIR /home/app
ENTRYPOINT ["ws"]
EXPOSE 8008

52
docker/Dockerfile.debian Normal file
View File

@ -0,0 +1,52 @@
# Build time
FROM debian:buster as build
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install wget
RUN mkdir -p /tmp/cmake
WORKDIR /tmp/cmake
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
RUN apt-get -y install g++
RUN apt-get -y install libssl-dev
RUN apt-get -y install libz-dev
RUN apt-get -y install make
COPY . .
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
RUN ["make"]
# Runtime
FROM debian:buster as runtime
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
# Runtime
RUN apt-get install -y libssl1.1
RUN apt-get install -y ca-certificates
RUN ["update-ca-certificates"]
# Debugging
RUN apt-get install -y strace
RUN apt-get install -y procps
RUN apt-get install -y htop
RUN adduser --disabled-password --gecos '' app
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
RUN chmod +x /usr/local/bin/ws
RUN ldd /usr/local/bin/ws
# Now run in usermode
USER app
WORKDIR /home/app
COPY --chown=app:app ws/snake/appsConfig.json .
COPY --chown=app:app ws/cobraMetricsSample.json .
ENTRYPOINT ["ws"]
CMD ["--help"]

43
docker/Dockerfile.fedora Normal file
View File

@ -0,0 +1,43 @@
FROM fedora:30 as build
RUN yum install -y gcc-g++
RUN yum install -y cmake
RUN yum install -y make
RUN yum install -y openssl-devel
RUN yum install -y wget
RUN mkdir -p /tmp/cmake
WORKDIR /tmp/cmake
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
RUN yum install -y python
RUN yum install -y libtsan
RUN yum install -y zlib-devel
COPY . .
# RUN ["make", "test"]
RUN ["make"]
# Runtime
FROM fedora:30 as runtime
RUN yum install -y libtsan
RUN groupadd app && useradd -g app app
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
RUN chmod +x /usr/local/bin/ws
RUN ldd /usr/local/bin/ws
# Now run in usermode
USER app
WORKDIR /home/app
COPY --chown=app:app ws/snake/appsConfig.json .
COPY --chown=app:app ws/cobraMetricsSample.json .
ENTRYPOINT ["ws"]
CMD ["--help"]

View File

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

View File

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

View File

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

723
docs/CHANGELOG.md Normal file
View File

@ -0,0 +1,723 @@
# Changelog
All changes to this project will be documented in this file.
## [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
## [9.3.2] - 2020-04-17
(ws) add a --logfile option to configure all logs to go to a file
## [9.3.1] - 2020-04-16
(cobra bots) add a utility class to factor out the common bots features (heartbeat) and move all bots to used it + convert cobra_subscribe to be a bot and add a unittest for it
## [9.3.0] - 2020-04-15
(websocket) add a positive number to the heartbeat message sent, incremented each time the heartbeat is sent
## [9.2.9] - 2020-04-15
(ixcobra) change cobra event callback to use a struct instead of several objects, which is more flexible/extensible
## [9.2.8] - 2020-04-15
(ixcobra) make CobraConnection_EventType an enum class (CobraEventType)
## [9.2.7] - 2020-04-14
(ixsentry) add a library method to upload a payload directly to sentry
## [9.2.6] - 2020-04-14
(ixcobra) snake server / handle invalid incoming json messages + cobra subscriber in fluentd mode insert a created_at timestamp entry
## [9.2.5] - 2020-04-13
(websocket) WebSocketMessagePtr is a unique_ptr instead of a shared_ptr
## [9.2.4] - 2020-04-13
(websocket) use persistent member variable as temp variables to encode/decode zlib messages in order to reduce transient allocations
## [9.2.3] - 2020-04-13
(ws) add a --runtime option to ws cobra_subscribe to optionally limit how much time it will run
## [9.2.2] - 2020-04-04
(third_party deps) fix #177, update bundled spdlog to 1.6.0
## [9.2.1] - 2020-04-04
(windows) when using OpenSSL, the system store is used to populate the cacert. No need to ship a cacert.pem file with your app.
## [9.2.0] - 2020-04-04
(windows) ci: windows build with TLS (mbedtls) + verify that we can be build with OpenSSL
## [9.1.9] - 2020-03-30
(cobra to statsd bot) add ability to extract a numerical value and send a timer event to statsd, with the --timer option
## [9.1.8] - 2020-03-29
(cobra to statsd bot) bot init was missing + capture socket error
## [9.1.7] - 2020-03-29
(cobra to statsd bot) add ability to extract a numerical value and send a gauge event to statsd, with the --gauge option
## [9.1.6] - 2020-03-29
(ws cobra subscriber) use a Json::StreamWriter to write to std::cout, and save one std::string allocation for each message printed
## [9.1.5] - 2020-03-29
(docker) trim down docker image (300M -> 12M) / binary built without symbol and size optimization, and source code not copied over
## [9.1.4] - 2020-03-28
(jsoncpp) update bundled copy to version 1.9.3 (at sha 3beb37ea14aec1bdce1a6d542dc464d00f4a6cec)
## [9.1.3] - 2020-03-27
(docker) alpine docker build with release with debug info, and bundle ca-certificates
## [9.1.2] - 2020-03-26
(mac ssl) rename DarwinSSL -> SecureTransport (see this too -> https://github.com/curl/curl/issues/3733)
## [9.1.1] - 2020-03-26
(websocket) fix data race accessing _socket object without mutex protection when calling wakeUpFromPoll in WebSocketTransport.cpp
## [9.1.0] - 2020-03-26
(ixcobra) add explicit event types for handshake, authentication and subscription failure, and handle those by exiting in ws_cobra_subcribe and friends
## [9.0.3] - 2020-03-24
(ws connect) display statistics about how much time it takes to stop the connection
## [9.0.2] - 2020-03-24
(socket) works with unique_ptr<Socket> instead of shared_ptr<Socket> in many places
## [9.0.1] - 2020-03-24
(socket) selectInterrupt member is an unique_ptr instead of being a shared_ptr
## [9.0.0] - 2020-03-23
(websocket) reset per-message deflate codec everytime we connect to a server/client
## [8.3.4] - 2020-03-23
(websocket) fix #167, a long standing issue with sending empty messages with per-message deflate extension (and hopefully other zlib bug)
## [8.3.3] - 2020-03-22
(cobra to statsd) port to windows and add a unittest
## [8.3.2] - 2020-03-20
(websocket+tls) fix hang in tls handshake which could lead to ANR, discovered through unittesting.
## [8.3.1] - 2020-03-20
(cobra) CobraMetricsPublisher can be configure with an ix::CobraConfig + more unittest use SSL in server + client
## [8.3.0] - 2020-03-18
(websocket) Simplify ping/pong based heartbeat implementation
## [8.2.7] - 2020-03-17
(ws) ws connect gains a new option to set the interval at which to send pings
(ws) ws echo_server gains a new option (-p) to disable responding to pings with pongs
```
IXWebSocket$ ws connect --ping_interval 2 wss://echo.websocket.org
Type Ctrl-D to exit prompt...
Connecting to url: wss://echo.websocket.org
> ws_connect: connected
[2020-03-17 23:53:02.726] [info] Uri: /
[2020-03-17 23:53:02.726] [info] Headers:
[2020-03-17 23:53:02.727] [info] Connection: Upgrade
[2020-03-17 23:53:02.727] [info] Date: Wed, 18 Mar 2020 06:45:05 GMT
[2020-03-17 23:53:02.727] [info] Sec-WebSocket-Accept: 0gtqbxW0aVL/QI/ICpLFnRaiKgA=
[2020-03-17 23:53:02.727] [info] sec-websocket-extensions:
[2020-03-17 23:53:02.727] [info] Server: Kaazing Gateway
[2020-03-17 23:53:02.727] [info] Upgrade: websocket
[2020-03-17 23:53:04.894] [info] Received pong
[2020-03-17 23:53:06.859] [info] Received pong
[2020-03-17 23:53:08.881] [info] Received pong
[2020-03-17 23:53:10.848] [info] Received pong
[2020-03-17 23:53:12.898] [info] Received pong
[2020-03-17 23:53:14.865] [info] Received pong
[2020-03-17 23:53:16.890] [info] Received pong
[2020-03-17 23:53:18.853] [info] Received pong
[2020-03-17 23:53:19.388] [info]
ws_connect: connection closed: code 1000 reason Normal closure
[2020-03-17 23:53:19.502] [info] Received 208 bytes
[2020-03-17 23:53:19.502] [info] Sent 0 bytes
```
## [8.2.6] - 2020-03-16
(cobra to sentry bot + docker) default docker file uses mbedtls + ws cobra_to_sentry pass tls options to sentryClient.
## [8.2.5] - 2020-03-13
(cobra client) ws cobra subscribe resubscribe at latest position after being disconnected
## [8.2.4] - 2020-03-13
(cobra client) can subscribe with a position
## [8.2.3] - 2020-03-13
(cobra client) pass the message position to the subscription data callback
## [8.2.2] - 2020-03-12
(openssl tls backend) Fix a hand in OpenSSL when using TLS v1.3 ... by disabling TLS v1.3
## [8.2.1] - 2020-03-11
(cobra) IXCobraConfig struct has tlsOptions and per message deflate options
## [8.2.0] - 2020-03-11
(cobra) add IXCobraConfig struct to pass cobra config around
## [8.1.9] - 2020-03-09
(ws cobra_subscribe) add a --fluentd option to wrap a message in an enveloppe so that fluentd can recognize it
## [8.1.8] - 2020-03-02
(websocket server) fix regression with disabling zlib extension on the server side. If a client does not support this extension the server will handle it fine. We still need to figure out how to disable the option.
## [8.1.7] - 2020-02-26
(websocket) traffic tracker received bytes is message size while it should be wire size
## [8.1.6] - 2020-02-26
(ws_connect) display sent/received bytes statistics on exit
## [8.1.5] - 2020-02-23
(server) give thread name to some usual worker threads / unittest is broken !!
## [8.1.4] - 2020-02-22
(websocket server) fix regression from 8.1.2, where per-deflate message compression was always disabled
## [8.1.3] - 2020-02-21
(client + server) Fix #155 / http header parser should treat the space(s) after the : delimiter as optional. Fixing this bug made us discover that websocket sub-protocols are not properly serialiazed, but start with a ,
## [8.1.2] - 2020-02-18
(WebSocketServer) add option to disable deflate compression, exposed with the -x option to ws echo_server
## [8.1.1] - 2020-02-18
(ws cobra to statsd and sentry sender) exit if no messages are received for one minute, which is a sign that something goes wrong on the server side. That should be changed to be configurable in the future
## [8.1.0] - 2020-02-13
(http client + sentry minidump upload) Multipart stream closing boundary is invalid + mark some options as mandatory in the command line tools
## [8.0.7] - 2020-02-12
(build) remove the unused subtree which was causing some way of installing to break
## [8.0.6] - 2020-01-31
(snake) add an option to disable answering pongs as response to pings, to test cobra client behavior with hanged connections
## [8.0.5] - 2020-01-31
(IXCobraConnection) set a ping timeout of 90 seconds. If no pong messages are received as responses to ping for a while, give up and close the connection
## [8.0.4] - 2020-01-31
(cobra to sentry) remove noisy logging
## [8.0.3] - 2020-01-30
(ixcobra) check if we are authenticated in publishNext before trying to publish a message
## [8.0.2] - 2020-01-28
Extract severity level when emitting messages to sentry
## [8.0.1] - 2020-01-28
Fix bug #151 - If a socket connection is interrupted, calling stop() on the IXWebSocket object blocks until the next retry
## [8.0.0] - 2020-01-26
(SocketServer) add ability to bind on an ipv6 address
## [7.9.6] - 2020-01-22
(ws) add a dnslookup sub-command, to get the ip address of a remote host
## [7.9.5] - 2020-01-14
(windows) fix #144, get rid of stubbed/un-implemented windows schannel ssl backend
## [7.9.4] - 2020-01-12
(openssl + mbedssl) fix #140, can send large files with ws send over ssl / still broken with apple ssl
## [7.9.3] - 2020-01-10
(apple ssl) model write method after the OpenSSL one for consistency
## [7.9.2] - 2020-01-06
(apple ssl) unify read and write ssl utility code
## [7.9.1] - 2020-01-06
(websocket client) better error propagation when errors are detected while sending data
(ws send) detect failures to send big files, terminate in those cases and report error
## [7.9.0] - 2020-01-04
(ws send) add option (-x) to disable per message deflate compression
## [7.8.9] - 2020-01-04
(ws send + receive) handle all message types (ping + pong + fragment) / investigate #140
## [7.8.8] - 2019-12-28
(mbedtls) fix related to private key file parsing and initialization
## [7.8.6] - 2019-12-28
(ws cobra to sentry/statsd) fix for handling null events properly for empty queues + use queue to send data to statsd
## [7.8.5] - 2019-12-28
(ws cobra to sentry) handle null events for empty queues
## [7.8.4] - 2019-12-27
(ws cobra to sentry) game is picked in a fair manner, so that all games get the same share of sent events
## [7.8.3] - 2019-12-27
(ws cobra to sentry) refactor queue related code into a class
## [7.8.2] - 2019-12-25
(ws cobra to sentry) bound the queue size used to hold up cobra messages before they are sent to sentry. Default queue size is a 100 messages. Without such limit the program runs out of memory when a subscriber receive a lot of messages that cannot make it to sentry
## [7.8.1] - 2019-12-25
(ws client) use correct compilation defines so that spdlog is not used as a header only library (reduce binary size and increase compilation speed)
## [7.8.0] - 2019-12-24
(ws client) all commands use spdlog instead of std::cerr or std::cout for logging
## [7.6.5] - 2019-12-24
(cobra client) send a websocket ping every 30s to keep the connection opened
## [7.6.4] - 2019-12-22
(client) error handling, quote url in error case when failing to parse one
(ws) ws_cobra_publish: register callbacks before connecting
(doc) mention mbedtls in supported ssl server backend
## [7.6.3] - 2019-12-20
(tls) add a simple description of the TLS configuration routine for debugging
## [7.6.2] - 2019-12-20
(mbedtls) correct support for using own certificate and private key
## [7.6.1] - 2019-12-20
(ws commands) in websocket proxy, disable automatic reconnections + in Dockerfile, use alpine 3.11
## [7.6.0] - 2019-12-19
(cobra) Add TLS options to all cobra commands and classes. Add example to the doc.
## [7.5.8] - 2019-12-18
(cobra-to-sentry) capture application version from device field
## [7.5.7] - 2019-12-18
(tls) Experimental TLS server support with mbedtls (windows) + process cert tlsoption (client + server)
## [7.5.6] - 2019-12-18
(tls servers) Make it clear that apple ssl and mbedtls backends do not support SSL in server mode
## [7.5.5] - 2019-12-17
(tls options client) TLSOptions struct _validated member should be initialized to false
## [7.5.4] - 2019-12-16
(websocket client) improve the error message when connecting to a non websocket server
Before:
```
Connection error: Got bad status connecting to example.com:443, status: 200, HTTP Status line: HTTP/1.1 200 OK
```
After:
```
Connection error: Expecting status 101 (Switching Protocol), got 200 status connecting to example.com:443, HTTP Status line: HTTP/1.1 200 OK
```
## [7.5.3] - 2019-12-12
(server) attempt at fixing #131 by using blocking writes in server mode
## [7.5.2] - 2019-12-11
(ws) cobra to sentry - created events with sentry tags based on tags present in the cobra messages
## [7.5.1] - 2019-12-06
(mac) convert SSL errors to utf8
## [7.5.0] - 2019-12-05
- (ws) cobra to sentry. Handle Error 429 Too Many Requests and politely wait before sending more data to sentry.
In the example below sentry we are sending data too fast, sentry asks us to slow down which we do. Notice how the sent count stop increasing, while we are waiting for 41 seconds.
```
[2019-12-05 15:50:33.759] [info] messages received 2449 sent 3
[2019-12-05 15:50:34.759] [info] messages received 5533 sent 7
[2019-12-05 15:50:35.759] [info] messages received 8612 sent 11
[2019-12-05 15:50:36.759] [info] messages received 11562 sent 15
[2019-12-05 15:50:37.759] [info] messages received 14410 sent 19
[2019-12-05 15:50:38.759] [info] messages received 17236 sent 23
[2019-12-05 15:50:39.282] [error] Error sending data to sentry: 429
[2019-12-05 15:50:39.282] [error] Body: {"exception":[{"stacktrace":{"frames":[{"filename":"WorldScene.lua","function":"WorldScene.lua:1935","lineno":1958},{"filename":"WorldScene.lua","function":"onUpdate_WorldCam","lineno":1921},{"filename":"WorldMapTile.lua","function":"__index","lineno":239}]},"value":"noisytypes: Attempt to call nil(nil,2224139838)!"}],"platform":"python","sdk":{"name":"ws","version":"1.0.0"},"tags":[["game","niso"],["userid","107638363"],["environment","live"]],"timestamp":"2019-12-05T23:50:39Z"}
[2019-12-05 15:50:39.282] [error] Response: {"error_name":"rate_limit","error":"Creation of this event was denied due to rate limiting"}
[2019-12-05 15:50:39.282] [warning] Error 429 - Too Many Requests. ws will sleep and retry after 41 seconds
[2019-12-05 15:50:39.760] [info] messages received 18839 sent 25
[2019-12-05 15:50:40.760] [info] messages received 18839 sent 25
[2019-12-05 15:50:41.760] [info] messages received 18839 sent 25
[2019-12-05 15:50:42.761] [info] messages received 18839 sent 25
[2019-12-05 15:50:43.762] [info] messages received 18839 sent 25
[2019-12-05 15:50:44.763] [info] messages received 18839 sent 25
[2019-12-05 15:50:45.768] [info] messages received 18839 sent 25
```
## [7.4.5] - 2019-12-03
- (ws) #125 / fix build problem when jsoncpp is not installed locally
## [7.4.4] - 2019-12-03
- (ws) #125 / cmake detects an already installed jsoncpp and will try to use this one if present
## [7.4.3] - 2019-12-03
- (http client) use std::unordered_map instead of std::map for HttpParameters and HttpFormDataParameters class aliases
## [7.4.2] - 2019-12-02
- (client) internal IXDNSLookup class requires a valid cancellation request function callback to be passed in
## [7.4.1] - 2019-12-02
- (client) fix an overflow in the exponential back off code
## [7.4.0] - 2019-11-25
- (http client) Add support for multipart HTTP POST upload
- (ixsentry) Add support for uploading a minidump to sentry
## [7.3.5] - 2019-11-20
- On Darwin SSL, add ability to skip peer verification.
## [7.3.4] - 2019-11-20
- 32-bits compile fix, courtesy of @fcojavmc
## [7.3.1] - 2019-11-16
- ws proxy_server / remote server close not forwarded to the client
## [7.3.0] - 2019-11-15
- New ws command: `ws proxy_server`.
## [7.2.2] - 2019-11-01
- Tag a release + minor reformating.
## [7.2.1] - 2019-10-26
- Add unittest to IXSentryClient to lua backtrace parsing code
## [7.2.0] - 2019-10-24
- Add cobra_metrics_to_redis sub-command to create streams for each cobra metric event being received.
## [7.1.0] - 2019-10-13
- Add client support for websocket subprotocol. Look for the new addSubProtocol method for details.
## [7.0.0] - 2019-10-01
- TLS support in server code, only implemented for the OpenSSL SSL backend for now.
## [6.3.4] - 2019-09-30
- all ws subcommands propagate tls options to servers (unimplemented) or ws or http client (implemented) (contributed by Matt DeBoer)
## [6.3.3] - 2019-09-30
- ws has a --version option
## [6.3.2] - 2019-09-29
- (http + websocket clients) can specify cacert and some other tls options (not implemented on all backend). This makes it so that server certs can finally be validated on windows.
## [6.3.1] - 2019-09-29
- Add ability to use OpenSSL on apple platforms.
## [6.3.0] - 2019-09-28
- ixcobra / fix crash in CobraConnection::publishNext when the queue is empty + handle CobraConnection_PublishMode_Batch in CobraMetricsThreadedPublisher
## [6.2.9] - 2019-09-27
- mbedtls fixes / the unittest now pass on macOS, and hopefully will on Windows/AppVeyor as well.
## [6.2.8] - 2019-09-26
- Http server: add options to ws https to redirect all requests to a given url. POST requests will get a 200 and an empty response.
```
ws httpd -L --redirect_url https://www.google.com
```
## [6.2.7] - 2019-09-25
- Stop having ws send subcommand send a binary message in text mode, which would cause error in `make ws_test` shell script test.
## [6.2.6] - 2019-09-24
- Fix 2 race conditions detected with TSan, one in CobraMetricsPublisher::push and another one in WebSocketTransport::sendData (that one was bad).
## [6.2.5] - 2019-09-23
- Add simple Redis Server which is only capable of doing publish / subscribe. New ws redis_server sub-command to use it. The server is used in the unittest, so that we can run on CI in environment where redis isn not available like github actions env.
## [6.2.4] - 2019-09-22
- Add options to configure TLS ; contributed by Matt DeBoer. Only implemented for OpenSSL TLS backend for now.
## [6.2.3] - 2019-09-21
- Fix crash in the Linux unittest in the HTTP client code, in Socket::readBytes
- Cobra Metrics Publisher code returns the message id of the message that got published, to be used to validated that it got sent properly when receiving an ack.
## [6.2.2] - 2019-09-19
- In DNS lookup code, make sure the weak pointer we use lives through the expected scope (if branch)
## [6.2.1] - 2019-09-17
- On error while doing a client handshake, additionally display port number next to the host name
## [6.2.0] - 2019-09-09
- websocket and http server: server does not close the bound client socket in many cases
- improve some websocket error messages
- add a utility function with unittest to parse status line and stop using scanf which triggers warnings on Windows
- update ws CLI11 (our command line argument parsing library) to the latest, which fix a compiler bug about optional
## [6.1.0] - 2019-09-08
- move poll wrapper on top of select (only used on Windows) to the ix namespace
## [6.0.1] - 2019-09-05
- add cobra metrics publisher + server unittest
- add cobra client + server unittest
- ws snake (cobra simple server) add basic support for unsubscription + subscribe send the proper subscription data + redis client subscription can be cancelled
- IXCobraConnection / pdu handlers can crash if they receive json data which is not an object
## [6.0.0] - 2019-09-04
- all client autobahn test should pass !
- zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9/ (fix autobahn test 13.X which uses 8 for the windows size)
## [5.2.0] - 2019-09-04
- Fragmentation: for sent messages which are compressed, the continuation fragments should not have the rsv1 bit set (fix all autobahn tests for zlib compression 12.X)
- Websocket Server / do a case insensitive string search when looking for an Upgrade header whose value is websocket. (some client use WebSocket with some upper-case characters)
## [5.1.9] - 2019-09-03
- ws autobahn / report progress with spdlog::info to get timing info
- ws autobahn / use condition variables for stopping test case + add more logging on errors
## [5.1.8] - 2019-09-03
- Per message deflate/compression: handle fragmented messages (fix autobahn test: 12.1.X and probably others)
## [5.1.7] - 2019-09-03
- Receiving invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling)
## [5.1.6] - 2019-09-03
- Sending invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling)
- Fix failing unittest which was sending binary data in text mode with WebSocket::send to call properly call WebSocket::sendBinary instead.
- Validate that the reason is proper utf-8. (fix autobahn test 7.5.1)
- Validate close codes. Autobahn 7.9.*
## [5.1.5] - 2019-09-03
Framentation: data and continuation blocks received out of order (fix autobahn test: 5.9 through 5.20 Fragmentation)
## [5.1.4] - 2019-09-03
Sending invalid UTF-8 TEXT message should fail and close the connection (fix **tons** of autobahn test: 6.X UTF-8 Handling)
## [5.1.3] - 2019-09-03
Message type (TEXT or BINARY) is invalid for received fragmented messages (fix autobahn test: 5.3 through 5.8 Fragmentation)
## [5.1.2] - 2019-09-02
Ping and Pong messages cannot be fragmented (fix autobahn test: 5.1 and 5.2 Fragmentation)
## [5.1.1] - 2019-09-01
Close connections when reserved bits are used (fix autobahn test: 3.X Reserved Bits)
## [5.1.0] - 2019-08-31
- ws autobahn / Add code to test websocket client compliance with the autobahn test-suite
- add utf-8 validation code, not hooked up properly yet
- Ping received with a payload too large (> 125 bytes) trigger a connection closure
- cobra / add tracking about published messages
- cobra / publish returns a message id, that can be used when
- cobra / new message type in the message received handler when publish/ok is received (can be used to implement an ack system).
## [5.0.9] - 2019-08-30
- User-Agent header is set when not specified.
- New option to cap the max wait between reconnection attempts. Still default to 10s. (setMaxWaitBetweenReconnectionRetries).
```
ws connect --max_wait 5000 ws://example.com # will only wait 5 seconds max between reconnection attempts
```
## [5.0.7] - 2019-08-23
- WebSocket: add new option to pass in extra HTTP headers when connecting.
- `ws connect` add new option (-H, works like [curl](https://stackoverflow.com/questions/356705/how-to-send-a-header-using-a-http-request-through-a-curl-call)) to pass in extra HTTP headers when connecting
If you run against `ws echo_server` you will see the headers being received printed in the terminal.
```
ws connect -H "foo: bar" -H "baz: buz" ws://127.0.0.1:8008
```
- CobraConnection: sets a unique id field for all messages sent to [cobra](https://github.com/machinezone/cobra).
- CobraConnection: sets a counter as a field for each event published.
## [5.0.6] - 2019-08-22
- Windows: silly compile error (poll should be in the global namespace)
## [5.0.5] - 2019-08-22
- Windows: use select instead of WSAPoll, through a poll wrapper
## [5.0.4] - 2019-08-20
- Windows build fixes (there was a problem with the use of ::poll that has a different name on Windows (WSAPoll))
## [5.0.3] - 2019-08-14
- CobraMetricThreadedPublisher _enable flag is an atomic, and CobraMetricsPublisher is enabled by default
## [5.0.2] - 2019-08-01
- ws cobra_subscribe has a new -q (quiet) option
- ws cobra_subscribe knows to and display msg stats (count and # of messages received per second)
- ws cobra_subscribe, cobra_to_statsd and cobra_to_sentry commands have a new option, --filter to restrict the events they want to receive
## [5.0.1] - 2019-07-25
- ws connect command has a new option to send in binary mode (still default to text)
- ws connect command has readline history thanks to libnoise-cpp. Now ws connect one can use using arrows to lookup previous sent messages and edit them
## [5.0.0] - 2019-06-23
### Changed
- New HTTP server / still very early. ws gained a new command, httpd can run a simple webserver serving local files.
- IXDNSLookup. Uses weak pointer + smart_ptr + shared_from_this instead of static sets + mutex to handle object going away before dns lookup has resolved
- cobra_to_sentry / backtraces are reversed and line number is not extracted correctly
- mbedtls and zlib are searched with find_package, and we use the vendored version if nothing is found
- travis CI uses g++ on Linux
## [4.0.0] - 2019-06-09
### Changed
- WebSocket::send() sends message in TEXT mode by default
- WebSocketMessage sets a new binary field, which tells whether the received incoming message is binary or text
- WebSocket::send takes a third arg, binary which default to true (can be text too)
- WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg
- Add explicit WebSocket::sendBinary method
- New headers + WebSocketMessage class to hold message data, still not used across the board
- Add test/compatibility folder with small servers and clients written in different languages and different libraries to test compatibility.
- ws echo_server has a -g option to print a greeting message on connect
- IXSocketMbedTLS: better error handling in close and connect
## [3.1.2] - 2019-06-06
### Added
- ws connect has a -x option to disable per message deflate
- Add WebSocket::disablePerMessageDeflate() option.
## [3.0.0] - 2019-06-xx
### Changed
- TLS, aka SSL works on Windows (websocket and http clients)
- ws command line tool build on Windows
- Async API for HttpClient
- HttpClient API changed to use shared_ptr for response and request

78
docs/build.md Normal file
View File

@ -0,0 +1,78 @@
## Build
### CMake
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.
```
mkdir build # make a build dir so that you can build out of tree.
cd build
cmake -DUSE_TLS=1 ..
make -j
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
```
Headers and a static library will be installed to the target dir.
There is a unittest which can be executed by typing `make test`.
Options for building:
* `-DUSE_TLS=1` will enable TLS support
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows)
* `-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 (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/ccpp.yml#L40) 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:
```
ExternalProject_Add(
IXWebSocket
GIT_REPOSITORY https://github.com/machinezone/IXWebSocket.git
...
)
```
### vcpkg
It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/microsoft/vcpkg).
```
vcpkg install ixwebsocket
```
### Conan
[ ![Download](https://api.bintray.com/packages/conan/conan-center/ixwebsocket%3A_/images/download.svg) ](https://bintray.com/conan/conan-center/ixwebsocket%3A_/_latestVersion)
Conan is currently supported through a recipe in [Conan Center](https://github.com/conan-io/conan-center-index/tree/master/recipes/ixwebsocket) ([Bintray entry](https://bintray.com/conan/conan-center/ixwebsocket%3A_)).
Package reference
* Conan 1.21.0 and up: `ixwebsocket/7.9.2`
* Earlier versions: `ixwebsocket/7.9.2@_/_`
Note that the version listed here might not be the latest one. See Bintray or the recipe itself for the latest version. If you're migrating from the previous, custom Bintray remote, note that the package reference _has_ to be lower-case.
### Docker
There is a Dockerfile for running the unittest on Linux, and to run the `ws` tool. It is also available on the docker registry.
```
docker run docker.pkg.github.com/machinezone/ixwebsocket/ws:latest --help
```
To use docker-compose you must make a docker container first.
```
$ make docker
...
$ docker compose up &
...
$ docker exec -it ixwebsocket_ws_1 bash
app@ca2340eb9106:~$ ws --help
ws is a websocket tool
...
```

81
docs/cobra.md Normal file
View File

@ -0,0 +1,81 @@
## 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
```

82
docs/design.md Normal file
View File

@ -0,0 +1,82 @@
## Implementation details
### Per Message Deflate compression.
The per message deflate compression option is supported. It can lead to very nice bandbwith savings (20x !) if your messages are similar, which is often the case for example for chat applications. All features of the spec should be supported.
### TLS/SSL
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL and mbedTLS can be used on Android, Linux and Windows.
If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x.
### 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.
### Automatic reconnection
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds. This behavior can be disabled.
### Large messages
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully.
### Testing
The library has an interactive tool which is handy for testing compatibility ith other libraries. We have tested our client against Python, Erlang, Node.js, and C++ websocket server libraries.
The unittest tries to be comprehensive, and has been running on multiple platforms, with different sanitizers such as a thread sanitizer to catch data races or the undefined behavior sanitizer.
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 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.
## C++ code organization
Here is a simplistic diagram which explains how the code is structured in term of class/modules.
```
+-----------------------+ --- Public
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
| |
+-----------------------+
| |
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
| | Each connection is handled in a new OS thread.
| |
+-----------------------+ --- Private
| |
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
| |
+-----------------------+
| |
| IXWebSocketHandshake | Establish the connection between client and server.
| |
+-----------------------+
| |
| IXWebSocket | ws:// Unencrypted Socket handler
| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS
| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux
| | Can be used on macOS too.
+-----------------------+
| |
| IXSocketConnect | Connect to the remote host (client).
| |
+-----------------------+
| |
| IXDNSLookup | Does DNS resolution asynchronously so that it can be interrupted.
| |
+-----------------------+
```

66
docs/index.md Normal file
View File

@ -0,0 +1,66 @@
![Build status](https://github.com/machinezone/IXWebSocket/workflows/unittest/badge.svg)
## Introduction
[*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.
* macOS
* iOS
* Linux
* Android
* Windows
* FreeBSD
## Example code
```c++
// Required on Windows
ix::initNetSystem();
// Our websocket object
ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
webSocket.setUrl(url);
// 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)
{
std::cout << msg->str << std::endl;
}
}
);
// 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");
```
## Why another library?
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.
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`.
* 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
## 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++
* [libwebsockets](https://libwebsockets.org/) - C
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C
## Contributing
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.

94
docs/packages.md Normal file
View File

@ -0,0 +1,94 @@
Notes on how we can update the different packages for ixwebsocket.
## VCPKG
Visit the [releases](https://github.com/machinezone/IXWebSocket/releases) page on Github. A tag must have been made first.
Download the latest entry.
```
$ cd /tmp
/tmp$ curl -s -O -L https://github.com/machinezone/IXWebSocket/archive/v9.1.9.tar.gz
/tmp$
/tmp$ openssl sha512 v9.1.9.tar.gz
SHA512(v9.1.9.tar.gz)= f1fd731b5f6a9ce6d6d10bee22a5d9d9baaa8ea0564d6c4cd7eb91dcb88a45c49b2c7fdb75f8640a3589c1b30cee33ef5df8dcbb55920d013394d1e33ddd3c8e
```
Now go punch those values in the vcpkg ixwebsocket port config files. Here is what the diff look like.
```
vcpkg$ git diff
diff --git a/ports/ixwebsocket/CONTROL b/ports/ixwebsocket/CONTROL
index db9c2adc9..4acae5c3f 100644
--- a/ports/ixwebsocket/CONTROL
+++ b/ports/ixwebsocket/CONTROL
@@ -1,5 +1,5 @@
Source: ixwebsocket
-Version: 8.0.5
+Version: 9.1.9
Build-Depends: zlib
Homepage: https://github.com/machinezone/IXWebSocket
Description: Lightweight WebSocket Client and Server + HTTP Client and Server
diff --git a/ports/ixwebsocket/portfile.cmake b/ports/ixwebsocket/portfile.cmake
index de082aece..68e523a05 100644
--- a/ports/ixwebsocket/portfile.cmake
+++ b/ports/ixwebsocket/portfile.cmake
@@ -1,8 +1,8 @@
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO machinezone/IXWebSocket
- REF v8.0.5
- SHA512 9dcc20d9a0629b92c62a68a8bd7c8206f18dbd9e93289b0b687ec13c478ce9ad1f3563b38c399c8277b0d3812cc78ca725786ba1dedbc3445b9bdb9b689e8add
+ REF v9.1.9
+ SHA512 f1fd731b5f6a9ce6d6d10bee22a5d9d9baaa8ea0564d6c4cd7eb91dcb88a45c49b2c7fdb75f8640a3589c1b30cee33ef5df8dcbb55920d013394d1e33ddd3c8e
)
```
You will need a fork of the vcpkg repo to make a pull request.
```
git fetch upstream
git co master
git reset --hard upstream/master
git push origin master --force
```
Make the pull request (I use a new branch to do that).
```
vcpkg$ git co -b feature/ixwebsocket_9.1.9
M ports/ixwebsocket/CONTROL
M ports/ixwebsocket/portfile.cmake
Switched to a new branch 'feature/ixwebsocket_9.1.9'
vcpkg$
vcpkg$
vcpkg$ git commit -am 'ixwebsocket: update to 9.1.9'
[feature/ixwebsocket_9.1.9 8587a4881] ixwebsocket: update to 9.1.9
2 files changed, 3 insertions(+), 3 deletions(-)
vcpkg$
vcpkg$ git push
fatal: The current branch feature/ixwebsocket_9.1.9 has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature/ixwebsocket_9.1.9
vcpkg$ git push --set-upstream origin feature/ixwebsocket_9.1.9
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 8 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done.
Total 6 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
remote:
remote: Create a pull request for 'feature/ixwebsocket_9.1.9' on GitHub by visiting:
remote: https://github.com/bsergean/vcpkg/pull/new/feature/ixwebsocket_9.1.9
remote:
To https://github.com/bsergean/vcpkg.git
* [new branch] feature/ixwebsocket_9.1.9 -> feature/ixwebsocket_9.1.9
Branch 'feature/ixwebsocket_9.1.9' set up to track remote branch 'feature/ixwebsocket_9.1.9' from 'origin' by rebasing.
vcpkg$
```
Just visit this url, https://github.com/bsergean/vcpkg/pull/new/feature/ixwebsocket_9.1.9, printed on the console, to make the pull request.

470
docs/usage.md Normal file
View File

@ -0,0 +1,470 @@
# Examples
The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage.
## Windows note
To use the network system on Windows, you need to initialize it once with *WSAStartup()* and clean it up with *WSACleanup()*. We have helpers for that which you can use, see below. This init would typically take place in your main function.
```cpp
#include <ixwebsocket/IXNetSystem.h>
int main()
{
ix::initNetSystem();
...
ix::uninitNetSystem();
return 0;
}
```
## WebSocket client API
```cpp
#include <ixwebsocket/IXWebSocket.h>
...
// Our websocket object
ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
webSocket.setUrl(url);
// Optional heart beat, sent every 45 seconds when there is not any traffic
// to make sure that load balancers do not kill an idle connection.
webSocket.setPingInterval(45);
// Per message deflate connection is enabled by default. You can tweak its parameters or disable it
webSocket.disablePerMessageDeflate();
// Setup a callback to be fired when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Message)
{
std::cout << msg->str << std::endl;
}
}
);
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
// Send a message to the server (default to TEXT mode)
webSocket.send("hello world");
// The message can be sent in BINARY mode (useful if you send MsgPack data for example)
webSocket.sendBinary("some serialized binary data");
// ... finally ...
// Stop the connection
webSocket.stop()
```
### Sending messages
`websocket.send("foo")` will send a message.
If the connection was closed and sending failed, the return value will be set to false.
### ReadyState
`getReadyState()` returns the state of the connection. There are 4 possible states.
1. ReadyState::Connecting - The connection is not yet open.
2. ReadyState::Open - The connection is open and ready to communicate.
3. ReadyState::Closing - The connection is in the process of closing.
4. ReadyState::Closed - The connection is closed or could not be opened.
### Open and Close notifications
The onMessage event will be fired when the connection is opened or closed. This is similar to the [JavaScript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
```cpp
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "send greetings" << std::endl;
// Headers can be inspected (pairs of string/string)
std::cout << "Handshake Headers:" << std::endl;
for (auto it : msg->headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
std::cout << "disconnected" << std::endl;
// The server can send an explicit code and reason for closing.
// This data can be accessed through the closeInfo object.
std::cout << msg->closeInfo.code << std::endl;
std::cout << msg->closeInfo.reason << std::endl;
}
}
);
```
### Error notification
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
```cpp
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Error)
{
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;
std::cout << ss.str() << std::endl;
}
}
);
```
### start, stop
1. `websocket.start()` connect to the remote server and starts the message receiving background thread.
2. `websocket.stop()` disconnect from the remote server and closes the background thread.
### Configuring the remote url
The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url.
```cpp
std::string url("wss://example.com");
websocket.configure(url);
```
### Ping/Pong support
Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec.
```cpp
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Ping ||
msg->type == ix::WebSocketMessageType::Pong)
{
std::cout << "pong data: " << msg->str << std::endl;
}
}
);
```
A ping message can be sent to the server, with an optional data string.
```cpp
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
```
### Heartbeat.
You can configure an optional heart beat / keep-alive, sent every 45 seconds
when there is no any traffic to make sure that load balancers do not kill an
idle connection.
```cpp
webSocket.setPingInterval(45);
```
### Supply extra HTTP headers.
You can set extra HTTP headers to be sent during the WebSocket handshake.
```cpp
WebSocketHttpHeaders headers;
headers["foo"] = "bar";
webSocket.setExtraHeaders(headers);
```
### Subprotocols
You can specify subprotocols to be set during the WebSocket handshake. For more info you can refer to [this doc](https://hpbn.co/websocket/#subprotocol-negotiation).
```cpp
webSocket.addSubprotocol("appProtocol-v1");
webSocket.addSubprotocol("appProtocol-v2");
```
The protocol that the server did accept is available in the open info `protocol` field.
```cpp
std::cout << "protocol: " << msg->openInfo.protocol << std::endl;
```
### Automatic reconnection
Automatic reconnection kicks in when the connection is disconnected without the user consent. This feature is on by default and can be turned off.
```cpp
webSocket.enableAutomaticReconnection(); // turn on
webSocket.disableAutomaticReconnection(); // turn off
bool enabled = webSocket.isAutomaticReconnectionEnabled(); // query state
```
The technique to calculate wait time is called [exponential
backoff](https://docs.aws.amazon.com/general/latest/gr/api-retries.html). Here
are the default waiting times between attempts (from connecting with `ws connect ws://foo.com`)
```
> Connection error: Got bad status connecting to foo.com, status: 301, HTTP Status line: HTTP/1.1 301 Moved Permanently
#retries: 1
Wait time(ms): 100
#retries: 2
Wait time(ms): 200
#retries: 3
Wait time(ms): 400
#retries: 4
Wait time(ms): 800
#retries: 5
Wait time(ms): 1600
#retries: 6
Wait time(ms): 3200
#retries: 7
Wait time(ms): 6400
#retries: 8
Wait time(ms): 10000
```
The waiting time is capped by default at 10s between 2 attempts, but that value can be changed and queried.
```cpp
webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s
uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
```
## WebSocket server API
```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.
ix::WebSocketServer server(port);
server.setOnConnectionCallback(
[&server](std::shared_ptr<WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
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)
{
std::cerr << 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.
webSocket->send(msg->str, msg->binary);
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
// Error handling
return 1;
}
// Run the server in the background. Server can be stoped by calling server.stop()
server.start();
// Block until server.stop() is called.
server.wait();
```
## HTTP client API
```cpp
#include <ixwebsocket/IXHttpClient.h>
...
//
// Preparation
//
HttpClient httpClient;
HttpRequestArgsPtr args = httpClient.createRequest();
// Custom headers can be set
WebSocketHttpHeaders headers;
headers["Foo"] = "bar";
args->extraHeaders = headers;
// Timeout options
args->connectTimeout = connectTimeout;
args->transferTimeout = transferTimeout;
// Redirect options
args->followRedirects = followRedirects;
args->maxRedirects = maxRedirects;
// Misc
args->compress = compress; // Enable gzip compression
args->verbose = verbose;
args->logger = [](const std::string& msg)
{
std::cout << msg;
};
//
// Synchronous Request
//
HttpResponsePtr out;
std::string url = "https://www.google.com";
// HEAD request
out = httpClient.head(url, args);
// GET request
out = httpClient.get(url, args);
// POST request with parameters
HttpParameters httpParameters;
httpParameters["foo"] = "bar";
out = httpClient.post(url, httpParameters, args);
// POST request with a body
out = httpClient.post(url, std::string("foo=bar"), args);
//
// 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 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
//
// Asynchronous Request
//
bool async = true;
HttpClient httpClient(async);
auto args = httpClient.createRequest(url, HttpClient::kGet);
// 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
}
);
// ok will be false if your httpClient is not async
```
## HTTP server API
```cpp
#include <ixwebsocket/IXHttpServer.h>
ix::HttpServer server(port, hostname);
auto res = server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
return 1;
}
server.start();
server.wait();
```
If you want to handle how requests are processed, implement the setOnConnectionCallback callback, which takes an HttpRequestPtr as input, and returns an HttpResponsePtr. You can look at HttpServer::setDefaultConnectionCallback for a slightly more advanced callback example.
```cpp
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
{
// Build a string for the response
std::stringstream ss;
ss << request->method
<< " "
<< request->uri;
std::string content = ss.str();
return std::make_shared<HttpResponse>(200, "OK",
HttpErrorCode::Ok,
WebSocketHttpHeaders(),
content);
}
```
## TLS support and configuration
To leverage TLS features, the library must be compiled with the option `USE_TLS=1`.
If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x.
Then, secure sockets are automatically used when connecting to a `wss://*` url.
Additional TLS options can be configured by passing a `ix::SocketTLSOptions` instance to the
`setTLSOptions` on `ix::WebSocket` (or `ix::WebSocketServer` or `ix::HttpServer`)
```cpp
webSocket.setTLSOptions({
.certFile = "path/to/cert/file.pem",
.keyFile = "path/to/key/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
});
```
Specifying `certFile` and `keyFile` configures the certificate that will be used to communicate with TLS peers.
On a client, this is only necessary for connecting to servers that require a client certificate.
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

369
docs/ws.md Normal file
View File

@ -0,0 +1,369 @@
## General
ws is a command line tool that should exercise most of the IXWebSocket code, and provide example code.
```
ws is a websocket tool
Usage: ws [OPTIONS] SUBCOMMAND
Options:
-h,--help Print this help message and exit
Subcommands:
send Send a file
receive Receive a file
transfer Broadcasting server
connect Connect to a remote server
chat Group chat
echo_server Echo server
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
```
## curl
The curl subcommand try to be compatible with the curl syntax, to fetch http pages.
Making a HEAD request with the -I parameter.
```
$ ws curl -I https://www.google.com/
Accept-Ranges: none
Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
Date: Tue, 08 Oct 2019 21:36:57 GMT
Expires: -1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
Set-Cookie: NID=188=ASwfz8GrXQrHCLqAz-AndLOMLcz0rC9yecnf3h0yXZxRL3rTufTU_GDDwERp7qQL7LZ_EB8gCRyPXGERyOSAgaqgnrkoTmvWrwFemRLMaOZ896GrHobi5fV7VLklnSG2w48Gj8xMlwxfP7Z-bX-xR9UZxep1tHM6UmFQdD_GkBE; expires=Wed, 08-Apr-2020 21:36:57 GMT; path=/; domain=.google.com; HttpOnly
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0
Upload size: 143
Download size: 0
Status: 200
```
Making a POST request with the -F parameter.
```
$ ws curl -F foo=bar https://httpbin.org/post
foo: bar
Downloaded 438 bytes out of 438
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Encoding:
Content-Length: 438
Content-Type: application/json
Date: Tue, 08 Oct 2019 21:47:54 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Upload size: 219
Download size: 438
Status: 200
payload: {
"args": {},
"data": "",
"files": {},
"form": {
"foo": "bar"
},
"headers": {
"Accept": "*/*",
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "ixwebsocket/7.0.0 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11"
},
"json": null,
"origin": "155.94.127.118, 155.94.127.118",
"url": "https://httpbin.org/post"
}
```
Passing in a custom header with -H.
```
$ ws curl -F foo=bar -H 'my_custom_header: baz' https://httpbin.org/post
my_custom_header: baz
foo: bar
Downloaded 470 bytes out of 470
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Encoding:
Content-Length: 470
Content-Type: application/json
Date: Tue, 08 Oct 2019 21:50:25 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Upload size: 243
Download size: 470
Status: 200
payload: {
"args": {},
"data": "",
"files": {},
"form": {
"foo": "bar"
},
"headers": {
"Accept": "*/*",
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"My-Custom-Header": "baz",
"User-Agent": "ixwebsocket/7.0.0 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11"
},
"json": null,
"origin": "155.94.127.118, 155.94.127.118",
"url": "https://httpbin.org/post"
}
```
## connect
The connect command connects to a websocket endpoint, and starts an interactive prompt. Line editing, such as using the direction keys to fetch the last thing you tried to type) is provided. That command is pretty useful to try to send random data to an endpoint and verify that the service handles it with grace (such as sending invalid json).
```
ws connect wss://echo.websocket.org
Type Ctrl-D to exit prompt...
Connecting to url: wss://echo.websocket.org
> ws_connect: connected
Uri: /
Handshake Headers:
Connection: Upgrade
Date: Tue, 08 Oct 2019 21:38:44 GMT
Sec-WebSocket-Accept: 2j6LBScZveqrMx1W/GJkCWvZo3M=
sec-websocket-extensions:
Server: Kaazing Gateway
Upgrade: websocket
Received ping
Received ping
Received ping
Hello world !
> Received 13 bytes
ws_connect: received message: Hello world !
> Hello world !
> Received 13 bytes
ws_connect: received message: Hello world !
```
```
ws connect 'ws://jeanserge.com/v2?appkey=_pubsub'
Type Ctrl-D to exit prompt...
Connecting to url: ws://jeanserge.com/v2?appkey=_pubsub
> ws_connect: connected
Uri: /v2?appkey=_pubsub
Handshake Headers:
Connection: Upgrade
Date: Tue, 08 Oct 2019 21:45:28 GMT
Sec-WebSocket-Accept: LYHmjh9Gsu/Yw7aumQqyPObOEV4=
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
Server: Python/3.7 websockets/8.0.2
Upgrade: websocket
bababababababab
> ws_connect: connection closed: code 1000 reason
ws_connect: connected
Uri: /v2?appkey=_pubsub
Handshake Headers:
Connection: Upgrade
Date: Tue, 08 Oct 2019 21:45:44 GMT
Sec-WebSocket-Accept: I1rqxdLgTU+opPi5/zKPBTuXdLw=
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
Server: Python/3.7 websockets/8.0.2
Upgrade: websocket
```
## Websocket proxy
```
ws proxy_server --remote_host ws://127.0.0.1:9000 -v
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.
## File transfer
```
# Start transfer server, which is just a broadcast server at this point
ws transfer # running on port 8080.
# Start receiver first
ws receive ws://localhost:8080
# Then send a file. File will be received and written to disk by the receiver process
ws send ws://localhost:8080 /file/to/path
```
## HTTP Client
```
$ ws curl --help
HTTP Client
Usage: ws curl [OPTIONS] url
Positionals:
url TEXT REQUIRED Connection url
Options:
-h,--help Print this help message and exit
-d TEXT Form data
-F TEXT Form data
-H TEXT Header
--output TEXT Output file
-I Send a HEAD request
-L Follow redirects
--max-redirects INT Max Redirects
-v Verbose
-O Save output to disk
--compress Enable gzip compression
--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.

View File

@ -1,15 +0,0 @@
#!/bin/sh
#
# Author: Benjamin Sergeant
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
#
clang++ --std=c++11 --stdlib=libc++ \
../ixwebsocket/IXSocket.cpp \
../ixwebsocket/IXWebSocketTransport.cpp \
../ixwebsocket/IXSocketAppleSSL.cpp \
../ixwebsocket/IXWebSocket.cpp \
cmd_websocket_chat.cpp \
-o cmd_websocket_chat \
-framework Security \
-framework Foundation

View File

@ -1,193 +0,0 @@
/*
* cmd_websocket_chat.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
//
// Simple chat program that talks to the node.js server at
// websocket_chat_server/broacast-server.js
//
#include <iostream>
#include <sstream>
#include <queue>
#include "../ixwebsocket/IXWebSocket.h"
#include "nlohmann/json.hpp"
// for convenience
using json = nlohmann::json;
using namespace ix;
namespace
{
void log(const std::string& msg)
{
std::cout << msg << std::endl;
}
class WebSocketChat
{
public:
WebSocketChat(const std::string& user);
void subscribe(const std::string& channel);
void start();
void stop();
bool isReady() const;
void sendMessage(const std::string& text);
size_t getReceivedMessagesCount() const;
std::string encodeMessage(const std::string& text);
std::pair<std::string, std::string> decodeMessage(const std::string& str);
private:
std::string _user;
ix::WebSocket _webSocket;
std::queue<std::string> _receivedQueue;
};
WebSocketChat::WebSocketChat(const std::string& user) :
_user(user)
{
;
}
size_t WebSocketChat::getReceivedMessagesCount() const
{
return _receivedQueue.size();
}
bool WebSocketChat::isReady() const
{
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
}
void WebSocketChat::stop()
{
_webSocket.stop();
}
void WebSocketChat::start()
{
std::string url("ws://localhost:8080/");
_webSocket.configure(url);
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
_webSocket.setOnMessageCallback(
[this](ix::WebSocketMessageType messageType, const std::string& str, ix::WebSocketErrorInfo error)
{
std::stringstream ss;
if (messageType == ix::WebSocket_MessageType_Open)
{
ss << "cmd_websocket_chat: user "
<< _user
<< " Connected !";
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
ss << "cmd_websocket_chat: user "
<< _user
<< " disconnected !";
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
auto result = decodeMessage(str);
// Our "chat" / "broacast" node.js server does not send us
// the messages we send, so we don't have to filter it out.
// store text
_receivedQueue.push(result.second);
ss << std::endl
<< result.first << " > " << result.second
<< std::endl
<< _user << " > ";
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Error)
{
ss << "Connection error: " << error.reason << std::endl;
ss << "#retries: " << error.retries << std::endl;
ss << "Wait time(ms): " << error.wait_time << std::endl;
ss << "HTTP Status: " << error.http_status << std::endl;
log(ss.str());
}
else
{
ss << "Invalid ix::WebSocketMessageType";
log(ss.str());
}
});
_webSocket.start();
}
std::pair<std::string, std::string> WebSocketChat::decodeMessage(const std::string& str)
{
auto j = json::parse(str);
std::string msg_user = j["user"];
std::string msg_text = j["text"];
return std::pair<std::string, std::string>(msg_user, msg_text);
}
std::string WebSocketChat::encodeMessage(const std::string& text)
{
json j;
j["user"] = _user;
j["text"] = text;
std::string output = j.dump();
return output;
}
void WebSocketChat::sendMessage(const std::string& text)
{
_webSocket.send(encodeMessage(text));
}
void interactiveMain()
{
std::string user(getenv("USER"));
WebSocketChat webSocketChat(user);
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
webSocketChat.start();
while (true)
{
std::string text;
std::cout << user << " > " << std::flush;
std::getline(std::cin, text);
if (!std::cin)
{
break;
}
webSocketChat.sendMessage(text);
}
std::cout << std::endl;
webSocketChat.stop();
}
}
int main()
{
interactiveMain();
return 0;
}

View File

@ -1,6 +0,0 @@
{
"dependencies": {
"msgpack-js": "^0.3.0",
"ws": "^3.1.0"
}
}

44
ixbots/CMakeLists.txt Normal file
View File

@ -0,0 +1,44 @@
#
# 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()
set(IXBOTS_INCLUDE_DIRS
.
..
../ixcore
../ixwebsocket
../ixcobra
../ixsentry
${JSONCPP_INCLUDE_DIRS}
${SPDLOG_INCLUDE_DIRS})
target_include_directories( ixbots PUBLIC ${IXBOTS_INCLUDE_DIRS} )

View File

@ -0,0 +1,321 @@
/*
* IXCobraBot.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXCobraBot.h"
#include "IXQueueManager.h"
#include <ixcobra/IXCobraConnection.h>
#include <ixcore/utils/IXCoreLogger.h>
#include <algorithm>
#include <chrono>
#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);
uint64_t sentCountTotal(0);
uint64_t receivedCountTotal(0);
uint64_t sentCountPerSecs(0);
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
//
std::stringstream ss;
ss << "messages received "
<< receivedCountPerSecs
<< " "
<< receivedCountTotal
<< " sent "
<< sentCountPerSecs
<< " "
<< sentCountTotal;
CoreLogger::info(ss.str());
receivedCountPerSecs = receivedCount - receivedCountTotal;
sentCountPerSecs = sentCount - receivedCountTotal;
receivedCountTotal += receivedCountPerSecs;
sentCountTotal += sentCountPerSecs;
auto duration = std::chrono::seconds(1);
std::this_thread::sleep_for(duration);
}
CoreLogger::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)
{
CoreLogger::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);
}
CoreLogger::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)
{
CoreLogger::info("cobra bot: sending succesfull");
}
++sentCount;
}
else
{
CoreLogger::error("cobra bot: error sending");
}
if (stop) break;
}
CoreLogger::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)
{
CoreLogger::info("Subscriber connected");
for (auto&& it : event->headers)
{
CoreLogger::info(it.first + "::" + it.second);
}
}
else if (event->type == ix::CobraEventType::Closed)
{
CoreLogger::info("Subscriber closed: {}" + event->errMsg);
}
else if (event->type == ix::CobraEventType::Authenticated)
{
CoreLogger::info("Subscriber authenticated");
CoreLogger::info("Subscribing to " + channel);
CoreLogger::info("Subscribing at position " + subscriptionPosition);
CoreLogger::info("Subscribing with 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)
{
CoreLogger::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)
{
CoreLogger::info("cobra bot: sending succesfull");
}
++sentCount;
}
else
{
CoreLogger::error("cobra bot: error sending");
}
}
});
}
else if (event->type == ix::CobraEventType::Subscribed)
{
CoreLogger::info("Subscriber: subscribed to channel " + event->subscriptionId);
}
else if (event->type == ix::CobraEventType::UnSubscribed)
{
CoreLogger::info("Subscriber: unsubscribed from channel " + event->subscriptionId);
}
else if (event->type == ix::CobraEventType::Error)
{
CoreLogger::error("Subscriber: error " + event->errMsg);
}
else if (event->type == ix::CobraEventType::Published)
{
CoreLogger::error("Published message hacked: " + std::to_string(event->msgId));
}
else if (event->type == ix::CobraEventType::Pong)
{
CoreLogger::info("Received websocket pong: " + event->errMsg);
}
else if (event->type == ix::CobraEventType::HandshakeError)
{
CoreLogger::error("Subscriber: Handshake error: " + event->errMsg);
fatalCobraError = true;
}
else if (event->type == ix::CobraEventType::AuthenticationError)
{
CoreLogger::error("Subscriber: Authentication error: " + event->errMsg);
fatalCobraError = true;
}
else if (event->type == ix::CobraEventType::SubscriptionError)
{
CoreLogger::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;
}
} // namespace ix

View File

@ -0,0 +1,43 @@
/*
* IXCobraBot.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <functional>
#include <ixcobra/IXCobraConfig.h>
#include <json/json.h>
#include <stddef.h>
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;
};
} // namespace ix

View File

@ -0,0 +1,117 @@
/*
* IXCobraToSentryBot.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXCobraToSentryBot.h"
#include "IXCobraBot.h"
#include "IXQueueManager.h"
#include <ixcobra/IXCobraConnection.h>
#include <ixcore/utils/IXCoreLogger.h>
#include <chrono>
#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)
{
CoreLogger::warn("Null HTTP Response");
return false;
}
if (verbose)
{
for (auto it : response->headers)
{
CoreLogger::info(it.first + ": " + it.second);
}
CoreLogger::info("Upload size: " + std::to_string(response->uploadSize));
CoreLogger::info("Download size: " + std::to_string(response->downloadSize));
CoreLogger::info("Status: " + std::to_string(response->statusCode));
if (response->errorCode != HttpErrorCode::Ok)
{
CoreLogger::info("error message: " + response->errorMsg);
}
if (response->headers["Content-Type"] != "application/octet-stream")
{
CoreLogger::info("payload: " + response->payload);
}
}
bool success = response->statusCode == 200;
if (!success)
{
CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode));
CoreLogger::error("Body: " + ret.second);
CoreLogger::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;
CoreLogger::warn("Error parsing Retry-After header. "
"Using " + retryAfter + " for the sleep duration");
}
CoreLogger::warn("Error 429 - Too Many Requests. ws will sleep "
"and retry after " + retryAfter + " seconds");
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

@ -0,0 +1,24 @@
/*
* IXCobraToSentryBot.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <ixcobra/IXCobraConfig.h>
#include <ixsentry/IXSentryClient.h>
#include <string>
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

@ -0,0 +1,158 @@
/*
* IXCobraToStatsdBot.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXCobraToStatsdBot.h"
#include "IXCobraBot.h"
#include "IXQueueManager.h"
#include "IXStatsdClient.h"
#include <chrono>
#include <ixcobra/IXCobraConnection.h>
#include <ixcore/utils/IXCoreLogger.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
{
CoreLogger::error("Gauge " + gauge + " is not a numeric type");
fatalCobraError = true;
return false;
}
if (verbose)
{
CoreLogger::info(id + " - " + attrName + " -> " + std::to_string(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

@ -0,0 +1,28 @@
/*
* IXCobraToStatsdBot.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <ixbots/IXStatsdClient.h>
#include <ixcobra/IXCobraConfig.h>
#include <stddef.h>
#include <string>
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

@ -0,0 +1,107 @@
/*
* 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 <iostream>
#include <sstream>
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

@ -0,0 +1,25 @@
/*
* IXCobraToStdoutBot.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <ixcobra/IXCobraConfig.h>
#include <stddef.h>
#include <string>
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

@ -0,0 +1,67 @@
/*
* IXQueueManager.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXQueueManager.h"
#include <algorithm>
#include <vector>
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();
}
}
} // namespace ix

View File

@ -0,0 +1,35 @@
/*
* IXQueueManager.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <condition_variable>
#include <json/json.h>
#include <map>
#include <mutex>
#include <queue>
#include <stddef.h>
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;
};
} // namespace ix

View File

@ -0,0 +1,147 @@
/*
* 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 <iostream>
#include <ixwebsocket/IXNetSystem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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

@ -0,0 +1,57 @@
/*
* IXStatsdClient.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <deque>
#include <ixwebsocket/IXUdpSocket.h>
#include <mutex>
#include <string>
#include <thread>
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

37
ixcobra/CMakeLists.txt Normal file
View File

@ -0,0 +1,37 @@
#
# 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

@ -0,0 +1,35 @@
/*
* IXCobraConfig.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.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

@ -0,0 +1,676 @@
/*
* IXCobraConnection.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone. All rights reserved.
*/
#include "IXCobraConnection.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstring>
#include <iostream>
#include <ixcrypto/IXHMac.h>
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <ixwebsocket/IXWebSocket.h>
#include <sstream>
#include <stdexcept>
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

@ -0,0 +1,217 @@
/*
* IXCobraConnection.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone. All rights reserved.
*/
#pragma once
#include "IXCobraConfig.h"
#include "IXCobraEvent.h"
#include "IXCobraEventType.h"
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
#include <json/json.h>
#include <limits>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <unordered_map>
#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

@ -0,0 +1,41 @@
/*
* IXCobraEvent.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXCobraEventType.h"
#include <cstdint>
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
#include <memory>
#include <string>
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>;
} // namespace ix

View File

@ -0,0 +1,25 @@
/*
* 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

@ -0,0 +1,232 @@
/*
* IXCobraMetricsPublisher.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#include "IXCobraMetricsPublisher.h"
#include <algorithm>
#include <ixwebsocket/IXSocketTLSOptions.h>
#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

@ -0,0 +1,175 @@
/*
* 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

@ -0,0 +1,230 @@
/*
* IXCobraMetricsThreadedPublisher.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#include "IXCobraMetricsThreadedPublisher.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <iostream>
#include <ixcore/utils/IXCoreLogger.h>
#include <ixwebsocket/IXSetThreadName.h>
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <sstream>
#include <stdexcept>
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;
}
CoreLogger::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)
{
CoreLogger::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

@ -0,0 +1,101 @@
/*
* 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

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

19
ixcore/CMakeLists.txt Normal file
View File

@ -0,0 +1,19 @@
#
# 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

@ -0,0 +1,44 @@
/*
* IXCoreLogger.cpp
* Author: Thomas Wells, Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#include "ixcore/utils/IXCoreLogger.h"
namespace ix
{
// Default do a no-op logger
CoreLogger::LogFunc CoreLogger::_currentLogger = [](const char*, LogLevel) {};
void CoreLogger::log(const char* msg, LogLevel level)
{
_currentLogger(msg, level);
}
void CoreLogger::debug(const std::string& msg)
{
_currentLogger(msg.c_str(), LogLevel::Debug);
}
void CoreLogger::info(const std::string& msg)
{
_currentLogger(msg.c_str(), LogLevel::Info);
}
void CoreLogger::warn(const std::string& msg)
{
_currentLogger(msg.c_str(), LogLevel::Warning);
}
void CoreLogger::error(const std::string& msg)
{
_currentLogger(msg.c_str(), LogLevel::Error);
}
void CoreLogger::critical(const std::string& msg)
{
_currentLogger(msg.c_str(), LogLevel::Critical);
}
} // namespace ix

View File

@ -0,0 +1,44 @@
/*
* IXCoreLogger.h
* Author: Thomas Wells, Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <functional>
#include <string>
namespace ix
{
enum class LogLevel
{
Debug = 0,
Info = 1,
Warning = 2,
Error = 3,
Critical = 4
};
class CoreLogger
{
public:
using LogFunc = std::function<void(const char*, LogLevel level)>;
static void log(const char* msg, LogLevel level = LogLevel::Debug);
static void debug(const std::string& msg);
static void info(const std::string& msg);
static void warn(const std::string& msg);
static void error(const std::string& msg);
static void critical(const std::string& msg);
static void setLogFunction(LogFunc& func)
{
_currentLogger = func;
}
private:
static LogFunc _currentLogger;
};
} // namespace ix

49
ixcrypto/CMakeLists.txt Normal file
View File

@ -0,0 +1,49 @@
#
# 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

@ -0,0 +1,142 @@
/*
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;
}
} // namespace ix

View File

@ -0,0 +1,16 @@
/*
* 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

@ -0,0 +1,53 @@
/*
* 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());
}
} // namespace ix

View File

@ -0,0 +1,14 @@
/*
* 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

@ -0,0 +1,22 @@
/*
* 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;
}
} // namespace ix

View File

@ -0,0 +1,15 @@
/*
* 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

@ -0,0 +1,75 @@
/*
* IXUuid.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
/**
* Generate a random uuid similar to the uuid python module
*
* >>> import uuid
* >>> uuid.uuid4().hex
* 'bec08155b37d4050a1f3c3fa0276bf12'
*
* Code adapted from https://github.com/r-lyeh-archived/sole
*/
#include "IXUuid.h"
#include <iomanip>
#include <random>
#include <sstream>
#include <string>
namespace ix
{
class Uuid
{
public:
Uuid();
std::string toString() const;
private:
uint64_t _ab;
uint64_t _cd;
};
Uuid::Uuid()
{
static std::random_device rd;
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
_ab = dist(rd);
_cd = dist(rd);
_ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
_cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
}
std::string Uuid::toString() const
{
std::stringstream ss;
ss << std::hex << std::nouppercase << std::setfill('0');
uint32_t a = (_ab >> 32);
uint32_t b = (_ab & 0xFFFFFFFF);
uint32_t c = (_cd >> 32);
uint32_t d = (_cd & 0xFFFFFFFF);
ss << std::setw(8) << (a);
ss << std::setw(4) << (b >> 16);
ss << std::setw(4) << (b & 0xFFFF);
ss << std::setw(4) << (c >> 16);
ss << std::setw(4) << (c & 0xFFFF);
ss << std::setw(8) << d;
return ss.str();
}
std::string uuid4()
{
Uuid id;
return id.toString();
}
} // namespace ix

View File

@ -0,0 +1,17 @@
/*
* IXUuid.h
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
/**
* Generate a random uuid
*/
std::string uuid4();
} // namespace ix

30
ixsentry/CMakeLists.txt Normal file
View File

@ -0,0 +1,30 @@
#
# 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

@ -0,0 +1,307 @@
/*
* IXSentryClient.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include "IXSentryClient.h"
#include <chrono>
#include <fstream>
#include <iostream>
#include <ixcore/utils/IXCoreLogger.h>
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
#include <ixwebsocket/IXWebSocketVersion.h>
#include <sstream>
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) { CoreLogger::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) { CoreLogger::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) { CoreLogger::log(msg.c_str()); };
args->url = _url;
args->body = _jsonWriter.write(payload);
_httpClient->performRequest(args, onResponseCallback);
}
} // namespace ix

View File

@ -0,0 +1,67 @@
/*
* 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 <memory>
#include <regex>
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

35
ixsnake/CMakeLists.txt Normal file
View File

@ -0,0 +1,35 @@
#
# 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

@ -0,0 +1,48 @@
/*
* 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

@ -0,0 +1,45 @@
/*
* IXAppConfig.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <ixwebsocket/IXSocketTLSOptions.h>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
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

@ -0,0 +1,351 @@
/*
* 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

@ -0,0 +1,59 @@
/*
* IXRedisClient.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <functional>
#include <ixwebsocket/IXSocket.h>
#include <memory>
#include <string>
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

@ -0,0 +1,285 @@
/*
* IXRedisServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXRedisServer.h"
#include <fstream>
#include <ixwebsocket/IXCancellationRequest.h>
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXSocket.h>
#include <ixwebsocket/IXSocketConnect.h>
#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

@ -0,0 +1,64 @@
/*
* IXRedisServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocket.h"
#include "IXSocketServer.h"
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#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

@ -0,0 +1,61 @@
/*
* 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

@ -0,0 +1,297 @@
/*
* 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 <ixcore/utils/IXCoreLogger.h>
#include <ixcrypto/IXHMac.h>
#include <ixwebsocket/IXWebSocket.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::CoreLogger::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::CoreLogger::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

@ -0,0 +1,26 @@
/*
* 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

@ -0,0 +1,145 @@
/*
* 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 <ixcore/utils/IXCoreLogger.h>
#include <sstream>
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::CoreLogger::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;
ix::LogLevel logLevel = ix::LogLevel::Debug;
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;
logLevel = ix::LogLevel::Error;
}
}
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;
logLevel = ix::LogLevel::Error;
}
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::CoreLogger::log(ss.str().c_str(), logLevel);
});
});
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

@ -0,0 +1,31 @@
/*
* 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

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

44
ixwebsocket/IXBench.cpp Normal file
View File

@ -0,0 +1,44 @@
/*
* IXBench.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXBench.h"
#include <iostream>
namespace ix
{
Bench::Bench(const std::string& description)
: _description(description)
, _start(std::chrono::high_resolution_clock::now())
, _reported(false)
{
;
}
Bench::~Bench()
{
if (!_reported)
{
report();
}
}
void Bench::report()
{
auto now = std::chrono::high_resolution_clock::now();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
_ms = milliseconds.count();
std::cerr << _description << " completed in " << _ms << "ms" << std::endl;
_reported = true;
}
uint64_t Bench::getDuration() const
{
return _ms;
}
} // namespace ix

28
ixwebsocket/IXBench.h Normal file
View File

@ -0,0 +1,28 @@
/*
* IXBench.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
*/
#include <chrono>
#include <stdint.h>
#include <string>
namespace ix
{
class Bench
{
public:
Bench(const std::string& description);
~Bench();
void report();
uint64_t getDuration() const;
private:
std::string _description;
std::chrono::time_point<std::chrono::high_resolution_clock> _start;
uint64_t _ms;
bool _reported;
};
} // namespace ix

View File

@ -0,0 +1,32 @@
/*
* IXCancellationRequest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXCancellationRequest.h"
#include <chrono>
namespace ix
{
CancellationRequest makeCancellationRequestWithTimeout(
int secs, std::atomic<bool>& requestInitCancellation)
{
auto start = std::chrono::system_clock::now();
auto timeout = std::chrono::seconds(secs);
auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool {
// Was an explicit cancellation requested ?
if (requestInitCancellation) return true;
auto now = std::chrono::system_clock::now();
if ((now - start) > timeout) return true;
// No cancellation request
return false;
};
return isCancellationRequested;
}
} // namespace ix

View File

@ -0,0 +1,18 @@
/*
* IXCancellationRequest.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <functional>
namespace ix
{
using CancellationRequest = std::function<bool()>;
CancellationRequest makeCancellationRequestWithTimeout(
int seconds, std::atomic<bool>& requestInitCancellation);
} // namespace ix

View File

@ -0,0 +1,43 @@
/*
* IXConnectionState.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXConnectionState.h"
namespace ix
{
std::atomic<uint64_t> ConnectionState::_globalId(0);
ConnectionState::ConnectionState()
: _terminated(false)
{
computeId();
}
void ConnectionState::computeId()
{
_id = std::to_string(_globalId++);
}
const std::string& ConnectionState::getId() const
{
return _id;
}
std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
{
return std::make_shared<ConnectionState>();
}
bool ConnectionState::isTerminated() const
{
return _terminated;
}
void ConnectionState::setTerminated()
{
_terminated = true;
}
} // namespace ix

View File

@ -0,0 +1,36 @@
/*
* IXConnectionState.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <atomic>
#include <memory>
#include <stdint.h>
#include <string>
namespace ix
{
class ConnectionState
{
public:
ConnectionState();
virtual ~ConnectionState() = default;
virtual void computeId();
virtual const std::string& getId() const;
void setTerminated();
bool isTerminated() const;
static std::shared_ptr<ConnectionState> createConnectionState();
protected:
std::atomic<bool> _terminated;
std::string _id;
static std::atomic<uint64_t> _globalId;
};
} // namespace ix

171
ixwebsocket/IXDNSLookup.cpp Normal file
View File

@ -0,0 +1,171 @@
/*
* IXDNSLookup.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include <chrono>
#include <string.h>
#include <thread>
namespace ix
{
const int64_t DNSLookup::kDefaultWait = 1; // ms
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait)
: _hostname(hostname)
, _port(port)
, _wait(wait)
, _res(nullptr)
, _done(false)
{
;
}
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg)
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
std::string sport = std::to_string(port);
struct addrinfo* res;
int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res);
if (getaddrinfo_result)
{
errMsg = gai_strerror(getaddrinfo_result);
res = nullptr;
}
return res;
}
struct addrinfo* DNSLookup::resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable)
{
return cancellable ? resolveCancellable(errMsg, isCancellationRequested)
: resolveUnCancellable(errMsg, isCancellationRequested);
}
struct addrinfo* DNSLookup::resolveUnCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
// Maybe a cancellation request got in before the background thread terminated ?
if (isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
return getAddrInfo(_hostname, _port, errMsg);
}
struct addrinfo* DNSLookup::resolveCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
// Can only be called once, otherwise we would have to manage a pool
// of background thread which is overkill for our usage.
if (_done)
{
return nullptr; // programming error, create a second DNSLookup instance
// if you need a second lookup.
}
//
// Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
//
auto ptr = shared_from_this();
std::weak_ptr<DNSLookup> self(ptr);
int port = _port;
std::string hostname(_hostname);
// We make the background thread doing the work a shared pointer
// instead of a member variable, because it can keep running when
// this object goes out of scope, in case of cancellation
auto t = std::make_shared<std::thread>(&DNSLookup::run, this, self, hostname, port);
t->detach();
while (!_done)
{
// Wait for 1 milliseconds, to see if the bg thread has terminated.
// We do not use a condition variable to wait, as destroying this one
// if the bg thread is alive can cause undefined behavior.
std::this_thread::sleep_for(std::chrono::milliseconds(_wait));
// Were we cancelled ?
if (isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
}
// Maybe a cancellation request got in before the bg terminated ?
if (isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
errMsg = getErrMsg();
return getRes();
}
void DNSLookup::run(std::weak_ptr<DNSLookup> self,
std::string hostname,
int port) // thread runner
{
// We don't want to read or write into members variables of an object that could be
// gone, so we use temporary variables (res) or we pass in by copy everything that
// getAddrInfo needs to work.
std::string errMsg;
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock())
{
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
_done = true;
}
}
void DNSLookup::setErrMsg(const std::string& errMsg)
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
_errMsg = errMsg;
}
const std::string& DNSLookup::getErrMsg()
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
return _errMsg;
}
void DNSLookup::setRes(struct addrinfo* addr)
{
std::lock_guard<std::mutex> lock(_resMutex);
_res = addr;
}
struct addrinfo* DNSLookup::getRes()
{
std::lock_guard<std::mutex> lock(_resMutex);
return _res;
}
} // namespace ix

65
ixwebsocket/IXDNSLookup.h Normal file
View File

@ -0,0 +1,65 @@
/*
* IXDNSLookup.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*
* Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo
* Does this in a background thread so that it can be cancelled, since
* getaddrinfo is a blocking call, and we don't want to block the main thread on Mobile.
*/
#pragma once
#include "IXCancellationRequest.h"
#include <atomic>
#include <memory>
#include <mutex>
#include <set>
#include <string>
struct addrinfo;
namespace ix
{
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
{
public:
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup() = default;
struct addrinfo* resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable = true);
private:
struct addrinfo* resolveCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveUnCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
static struct addrinfo* getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg);
void run(std::weak_ptr<DNSLookup> self, std::string hostname, int port); // thread runner
void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg();
void setRes(struct addrinfo* addr);
struct addrinfo* getRes();
std::string _hostname;
int _port;
int64_t _wait;
const static int64_t kDefaultWait;
struct addrinfo* _res;
std::mutex _resMutex;
std::string _errMsg;
std::mutex _errMsgMutex;
std::atomic<bool> _done;
};
} // namespace ix

View File

@ -0,0 +1,25 @@
/*
* IXExponentialBackoff.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXExponentialBackoff.h"
#include <cmath>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
uint32_t maxWaitBetweenReconnectionRetries)
{
uint32_t wait_time = (retry_count < 26) ? (std::pow(2, retry_count) * 100) : 0;
if (wait_time > maxWaitBetweenReconnectionRetries || wait_time == 0)
{
wait_time = maxWaitBetweenReconnectionRetries;
}
return wait_time;
}
} // namespace ix

View File

@ -0,0 +1,15 @@
/*
* IXExponentialBackoff.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
uint32_t maxWaitBetweenReconnectionRetries);
} // namespace ix

168
ixwebsocket/IXHttp.cpp Normal file
View File

@ -0,0 +1,168 @@
/*
* IXHttp.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttp.h"
#include "IXCancellationRequest.h"
#include "IXSocket.h"
#include <sstream>
#include <vector>
namespace ix
{
std::string Http::trim(const std::string& str)
{
std::string out;
for (auto c : str)
{
if (c != ' ' && c != '\n' && c != '\r')
{
out += c;
}
}
return out;
}
std::pair<std::string, int> Http::parseStatusLine(const std::string& line)
{
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
std::string token;
std::stringstream tokenStream(line);
std::vector<std::string> tokens;
// Split by ' '
while (std::getline(tokenStream, token, ' '))
{
tokens.push_back(token);
}
std::string httpVersion;
if (tokens.size() >= 1)
{
httpVersion = trim(tokens[0]);
}
int statusCode = -1;
if (tokens.size() >= 2)
{
std::stringstream ss;
ss << trim(tokens[1]);
ss >> statusCode;
}
return std::make_pair(httpVersion, statusCode);
}
std::tuple<std::string, std::string, std::string> Http::parseRequestLine(
const std::string& line)
{
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
std::string token;
std::stringstream tokenStream(line);
std::vector<std::string> tokens;
// Split by ' '
while (std::getline(tokenStream, token, ' '))
{
tokens.push_back(token);
}
std::string method;
if (tokens.size() >= 1)
{
method = trim(tokens[0]);
}
std::string requestUri;
if (tokens.size() >= 2)
{
requestUri = trim(tokens[1]);
}
std::string httpVersion;
if (tokens.size() >= 3)
{
httpVersion = trim(tokens[2]);
}
return std::make_tuple(method, requestUri, httpVersion);
}
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(
std::unique_ptr<Socket>& socket)
{
HttpRequestPtr httpRequest;
std::atomic<bool> requestInitCancellation(false);
int timeoutSecs = 5; // FIXME
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
// Read first line
auto lineResult = socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid)
{
return std::make_tuple(false, "Error reading HTTP request line", httpRequest);
}
// Parse request line (GET /foo HTTP/1.1\r\n)
auto requestLine = Http::parseRequestLine(line);
auto method = std::get<0>(requestLine);
auto uri = std::get<1>(requestLine);
auto httpVersion = std::get<2>(requestLine);
// Retrieve and validate HTTP headers
auto result = parseHttpHeaders(socket, isCancellationRequested);
auto headersValid = result.first;
auto headers = result.second;
if (!headersValid)
{
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
}
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, headers);
return std::make_tuple(true, "", httpRequest);
}
bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket)
{
// Write the response to the socket
std::stringstream ss;
ss << "HTTP/1.1 ";
ss << response->statusCode;
ss << " ";
ss << response->description;
ss << "\r\n";
if (!socket->writeBytes(ss.str(), nullptr))
{
return false;
}
// Write headers
ss.str("");
ss << "Content-Length: " << response->payload.size() << "\r\n";
for (auto&& it : response->headers)
{
ss << it.first << ": " << it.second << "\r\n";
}
ss << "\r\n";
if (!socket->writeBytes(ss.str(), nullptr))
{
return false;
}
return response->payload.empty() ? true : socket->writeBytes(response->payload, nullptr);
}
} // namespace ix

126
ixwebsocket/IXHttp.h Normal file
View File

@ -0,0 +1,126 @@
/*
* IXHttp.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXProgressCallback.h"
#include "IXWebSocketHttpHeaders.h"
#include <tuple>
#include <unordered_map>
namespace ix
{
enum class HttpErrorCode : int
{
Ok = 0,
CannotConnect = 1,
Timeout = 2,
Gzip = 3,
UrlMalformed = 4,
CannotCreateSocket = 5,
SendError = 6,
ReadError = 7,
CannotReadStatusLine = 8,
MissingStatus = 9,
HeaderParsingError = 10,
MissingLocation = 11,
TooManyRedirects = 12,
ChunkReadError = 13,
CannotReadBody = 14,
Invalid = 100
};
struct HttpResponse
{
int statusCode;
std::string description;
HttpErrorCode errorCode;
WebSocketHttpHeaders headers;
std::string payload;
std::string errorMsg;
uint64_t uploadSize;
uint64_t downloadSize;
HttpResponse(int s = 0,
const std::string& des = std::string(),
const HttpErrorCode& c = HttpErrorCode::Ok,
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
const std::string& p = std::string(),
const std::string& e = std::string(),
uint64_t u = 0,
uint64_t d = 0)
: statusCode(s)
, description(des)
, errorCode(c)
, headers(h)
, payload(p)
, errorMsg(e)
, uploadSize(u)
, downloadSize(d)
{
;
}
};
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
using HttpParameters = std::unordered_map<std::string, std::string>;
using HttpFormDataParameters = std::unordered_map<std::string, std::string>;
using Logger = std::function<void(const std::string&)>;
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
struct HttpRequestArgs
{
std::string url;
std::string verb;
WebSocketHttpHeaders extraHeaders;
std::string body;
std::string multipartBoundary;
int connectTimeout = 60;
int transferTimeout = 1800;
bool followRedirects = true;
int maxRedirects = 5;
bool verbose = false;
bool compress = true;
Logger logger;
OnProgressCallback onProgressCallback;
};
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
struct HttpRequest
{
std::string uri;
std::string method;
std::string version;
WebSocketHttpHeaders headers;
HttpRequest(const std::string& u,
const std::string& m,
const std::string& v,
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
: uri(u)
, method(m)
, version(v)
, headers(h)
{
}
};
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
class Http
{
public:
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(
std::unique_ptr<Socket>& socket);
static bool sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket);
static std::pair<std::string, int> parseStatusLine(const std::string& line);
static std::tuple<std::string, std::string, std::string> parseRequestLine(
const std::string& line);
static std::string trim(const std::string& str);
};
} // namespace ix

View File

@ -0,0 +1,720 @@
/*
* IXHttpClient.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttpClient.h"
#include "IXSocketFactory.h"
#include "IXUrlParser.h"
#include "IXUserAgent.h"
#include "IXWebSocketHttpHeaders.h"
#include <assert.h>
#include <cstring>
#include <iomanip>
#include <random>
#include <sstream>
#include <vector>
#include <zlib.h>
namespace ix
{
const std::string HttpClient::kPost = "POST";
const std::string HttpClient::kGet = "GET";
const std::string HttpClient::kHead = "HEAD";
const std::string HttpClient::kDel = "DEL";
const std::string HttpClient::kPut = "PUT";
HttpClient::HttpClient(bool async)
: _async(async)
, _stop(false)
{
if (!_async) return;
_thread = std::thread(&HttpClient::run, this);
}
HttpClient::~HttpClient()
{
if (!_thread.joinable()) return;
_stop = true;
_condition.notify_one();
_thread.join();
}
void HttpClient::setTLSOptions(const SocketTLSOptions& tlsOptions)
{
_tlsOptions = tlsOptions;
}
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
{
auto request = std::make_shared<HttpRequestArgs>();
request->url = url;
request->verb = verb;
return request;
}
bool HttpClient::performRequest(HttpRequestArgsPtr args,
const OnResponseCallback& onResponseCallback)
{
assert(_async && "HttpClient needs its async parameter set to true "
"in order to call performRequest");
if (!_async) return false;
// Enqueue the task
{
// acquire lock
std::unique_lock<std::mutex> lock(_queueMutex);
// add the task
_queue.push(std::make_pair(args, onResponseCallback));
} // release lock
// wake up one thread
_condition.notify_one();
return true;
}
void HttpClient::run()
{
while (true)
{
HttpRequestArgsPtr args;
OnResponseCallback onResponseCallback;
{
std::unique_lock<std::mutex> lock(_queueMutex);
while (!_stop && _queue.empty())
{
_condition.wait(lock);
}
if (_stop) return;
auto p = _queue.front();
_queue.pop();
args = p.first;
onResponseCallback = p.second;
}
if (_stop) return;
HttpResponsePtr response = request(args->url, args->verb, args->body, args);
onResponseCallback(response);
if (_stop) return;
}
}
HttpResponsePtr HttpClient::request(const std::string& url,
const std::string& verb,
const std::string& body,
HttpRequestArgsPtr args,
int redirects)
{
// We only have one socket connection, so we cannot
// make multiple requests concurrently.
std::lock_guard<std::mutex> lock(_mutex);
uint64_t uploadSize = 0;
uint64_t downloadSize = 0;
int code = 0;
WebSocketHttpHeaders headers;
std::string payload;
std::string description;
std::string protocol, host, path, query;
int port;
if (!UrlParser::parse(url, protocol, host, path, query, port))
{
std::stringstream ss;
ss << "Cannot parse url: " << url;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::UrlMalformed,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
bool tls = protocol == "https";
std::string errorMsg;
_socket = createSocket(tls, -1, errorMsg, _tlsOptions);
if (!_socket)
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotCreateSocket,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
// Build request string
std::stringstream ss;
ss << verb << " " << path << " HTTP/1.1\r\n";
ss << "Host: " << host << "\r\n";
if (args->compress)
{
ss << "Accept-Encoding: gzip"
<< "\r\n";
}
// Append extra headers
for (auto&& it : args->extraHeaders)
{
ss << it.first << ": " << it.second << "\r\n";
}
// Set a default Accept header if none is present
if (headers.find("Accept") == headers.end())
{
ss << "Accept: */*"
<< "\r\n";
}
// Set a default User agent if none is present
if (headers.find("User-Agent") == headers.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
if (verb == kPost || verb == kPut)
{
ss << "Content-Length: " << body.size() << "\r\n";
// Set default Content-Type if unspecified
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
{
if (args->multipartBoundary.empty())
{
ss << "Content-Type: application/x-www-form-urlencoded"
<< "\r\n";
}
else
{
ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary
<< "\r\n";
}
}
ss << "\r\n";
ss << body;
}
else
{
ss << "\r\n";
}
std::string req(ss.str());
std::string errMsg;
std::atomic<bool> requestInitCancellation(false);
// Make a cancellation object dealing with connection timeout
auto isCancellationRequested =
makeCancellationRequestWithTimeout(args->connectTimeout, requestInitCancellation);
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success)
{
std::stringstream ss;
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotConnect,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
// Make a new cancellation object dealing with transfer timeout
isCancellationRequested =
makeCancellationRequestWithTimeout(args->transferTimeout, requestInitCancellation);
if (args->verbose)
{
std::stringstream ss;
ss << "Sending " << verb << " request "
<< "to " << host << ":" << port << std::endl
<< "request size: " << req.size() << " bytes" << std::endl
<< "=============" << std::endl
<< req << "=============" << std::endl
<< std::endl;
log(ss.str(), args);
}
if (!_socket->writeBytes(req, isCancellationRequested))
{
std::string errorMsg("Cannot send request");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::SendError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
uploadSize = req.size();
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid)
{
std::string errorMsg("Cannot retrieve status line");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotReadStatusLine,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (args->verbose)
{
std::stringstream ss;
ss << "Status line " << line;
log(ss.str(), args);
}
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{
std::string errorMsg("Cannot parse response code from status line");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::MissingStatus,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
headers = result.second;
if (!headersValid)
{
std::string errorMsg("Cannot parse http headers");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::HeaderParsingError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
// Redirect ?
if ((code >= 301 && code <= 308) && args->followRedirects)
{
if (headers.find("Location") == headers.end())
{
std::string errorMsg("Missing location header for redirect");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::MissingLocation,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (redirects >= args->maxRedirects)
{
std::stringstream ss;
ss << "Too many redirects: " << redirects;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::TooManyRedirects,
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
}
// Recurse
std::string location = headers["Location"];
return request(location, verb, body, args, redirects + 1);
}
if (verb == "HEAD")
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Ok,
headers,
payload,
std::string(),
uploadSize,
downloadSize);
}
// Parse response:
if (headers.find("Content-Length") != headers.end())
{
ssize_t contentLength = -1;
ss.str("");
ss << headers["Content-Length"];
ss >> contentLength;
payload.reserve(contentLength);
auto chunkResult = _socket->readBytes(
contentLength, args->onProgressCallback, isCancellationRequested);
if (!chunkResult.first)
{
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
payload += chunkResult.second;
}
else if (headers.find("Transfer-Encoding") != headers.end() &&
headers["Transfer-Encoding"] == "chunked")
{
std::stringstream ss;
while (true)
{
lineResult = _socket->readLine(isCancellationRequested);
line = lineResult.second;
if (!lineResult.first)
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
uint64_t chunkSize;
ss.str("");
ss << std::hex << line;
ss >> chunkSize;
if (args->verbose)
{
std::stringstream oss;
oss << "Reading " << chunkSize << " bytes" << std::endl;
log(oss.str(), args);
}
payload.reserve(payload.size() + (size_t) chunkSize);
// Read a chunk
auto chunkResult = _socket->readBytes(
(size_t) chunkSize, args->onProgressCallback, isCancellationRequested);
if (!chunkResult.first)
{
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
payload += chunkResult.second;
// Read the line that terminates the chunk (\r\n)
lineResult = _socket->readLine(isCancellationRequested);
if (!lineResult.first)
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
if (chunkSize == 0) break;
}
}
else if (code == 204)
{
; // 204 is NoContent response code
}
else
{
std::string errorMsg("Cannot read http body");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::CannotReadBody,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
downloadSize = payload.size();
// If the content was compressed with gzip, decode it
if (headers["Content-Encoding"] == "gzip")
{
std::string decompressedPayload;
if (!gzipInflate(payload, decompressedPayload))
{
std::string errorMsg("Error decompressing payload");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Gzip,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
}
payload = decompressedPayload;
}
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Ok,
headers,
payload,
std::string(),
uploadSize,
downloadSize);
}
HttpResponsePtr HttpClient::get(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kGet, std::string(), args);
}
HttpResponsePtr HttpClient::head(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kHead, std::string(), args);
}
HttpResponsePtr HttpClient::del(const std::string& url, HttpRequestArgsPtr args)
{
return request(url, kDel, std::string(), args);
}
HttpResponsePtr HttpClient::post(const std::string& url,
const HttpParameters& httpParameters,
HttpRequestArgsPtr args)
{
return request(url, kPost, serializeHttpParameters(httpParameters), args);
}
HttpResponsePtr HttpClient::post(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args)
{
return request(url, kPost, body, args);
}
HttpResponsePtr HttpClient::put(const std::string& url,
const HttpParameters& httpParameters,
HttpRequestArgsPtr args)
{
return request(url, kPut, serializeHttpParameters(httpParameters), args);
}
HttpResponsePtr HttpClient::put(const std::string& url,
const std::string& body,
const HttpRequestArgsPtr args)
{
return request(url, kPut, body, args);
}
std::string HttpClient::urlEncode(const std::string& value)
{
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i)
{
std::string::value_type c = (*i);
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
{
escaped << c;
continue;
}
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char) c);
escaped << std::nouppercase;
}
return escaped.str();
}
std::string HttpClient::serializeHttpParameters(const HttpParameters& httpParameters)
{
std::stringstream ss;
size_t count = httpParameters.size();
size_t i = 0;
for (auto&& it : httpParameters)
{
ss << urlEncode(it.first) << "=" << urlEncode(it.second);
if (i++ < (count - 1))
{
ss << "&";
}
}
return ss.str();
}
std::string HttpClient::serializeHttpFormDataParameters(
const std::string& multipartBoundary,
const HttpFormDataParameters& httpFormDataParameters,
const HttpParameters& httpParameters)
{
//
// --AaB03x
// Content-Disposition: form-data; name="submit-name"
// Larry
// --AaB03x
// Content-Disposition: form-data; name="foo.txt"; filename="file1.txt"
// Content-Type: text/plain
// ... contents of file1.txt ...
// --AaB03x--
//
std::stringstream ss;
for (auto&& it : httpFormDataParameters)
{
ss << "--" << multipartBoundary << "\r\n"
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
<< " filename=\"" << it.first << "\""
<< "\r\n"
<< "Content-Type: application/octet-stream"
<< "\r\n"
<< "\r\n"
<< it.second << "\r\n";
}
for (auto&& it : httpParameters)
{
ss << "--" << multipartBoundary << "\r\n"
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
<< "\r\n"
<< "\r\n"
<< it.second << "\r\n";
}
ss << "--" << multipartBoundary << "--\r\n";
return ss.str();
}
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
{
z_stream inflateState;
std::memset(&inflateState, 0, sizeof(inflateState));
inflateState.zalloc = Z_NULL;
inflateState.zfree = Z_NULL;
inflateState.opaque = Z_NULL;
inflateState.avail_in = 0;
inflateState.next_in = Z_NULL;
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK)
{
return false;
}
inflateState.avail_in = (uInt) in.size();
inflateState.next_in = (unsigned char*) (const_cast<char*>(in.data()));
const int kBufferSize = 1 << 14;
std::unique_ptr<unsigned char[]> compressBuffer =
std::make_unique<unsigned char[]>(kBufferSize);
do
{
inflateState.avail_out = (uInt) kBufferSize;
inflateState.next_out = compressBuffer.get();
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
{
inflateEnd(&inflateState);
return false;
}
out.append(reinterpret_cast<char*>(compressBuffer.get()),
kBufferSize - inflateState.avail_out);
} while (inflateState.avail_out == 0);
inflateEnd(&inflateState);
return true;
}
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
{
if (args->logger)
{
args->logger(msg);
}
}
std::string HttpClient::generateMultipartBoundary()
{
std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
static std::random_device rd;
static std::mt19937 generator(rd());
std::shuffle(str.begin(), str.end(), generator);
return str;
}
} // namespace ix

103
ixwebsocket/IXHttpClient.h Normal file
View File

@ -0,0 +1,103 @@
/*
* IXHttpClient.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXHttp.h"
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketHttpHeaders.h"
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
namespace ix
{
class HttpClient
{
public:
HttpClient(bool async = false);
~HttpClient();
HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr del(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr post(const std::string& url,
const HttpParameters& httpParameters,
HttpRequestArgsPtr args);
HttpResponsePtr post(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args);
HttpResponsePtr put(const std::string& url,
const HttpParameters& httpParameters,
HttpRequestArgsPtr args);
HttpResponsePtr put(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args);
HttpResponsePtr request(const std::string& url,
const std::string& verb,
const std::string& body,
HttpRequestArgsPtr args,
int redirects = 0);
// Async API
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
const std::string& verb = HttpClient::kGet);
bool performRequest(HttpRequestArgsPtr request,
const OnResponseCallback& onResponseCallback);
// TLS
void setTLSOptions(const SocketTLSOptions& tlsOptions);
std::string serializeHttpParameters(const HttpParameters& httpParameters);
std::string serializeHttpFormDataParameters(
const std::string& multipartBoundary,
const HttpFormDataParameters& httpFormDataParameters,
const HttpParameters& httpParameters = HttpParameters());
std::string generateMultipartBoundary();
std::string urlEncode(const std::string& value);
const static std::string kPost;
const static std::string kGet;
const static std::string kHead;
const static std::string kDel;
const static std::string kPut;
private:
void log(const std::string& msg, HttpRequestArgsPtr args);
bool gzipInflate(const std::string& in, std::string& out);
// Async API background thread runner
void run();
// Async API
bool _async;
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
mutable std::mutex _queueMutex;
std::condition_variable _condition;
std::atomic<bool> _stop;
std::thread _thread;
std::unique_ptr<Socket> _socket;
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
SocketTLSOptions _tlsOptions;
};
} // namespace ix

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