Compare commits

...

342 Commits

Author SHA1 Message Date
967d679f17 disable Linux + mac 2019-06-23 18:33:08 -07:00
08006ddd97 try to activate CI on windows again 2019-06-23 18:32:18 -07:00
fa4aee6ddc bump docker version 2019-06-23 18:17:24 -07:00
691502d7ad 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
43deaba547 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
2d02ae0f0c cobra_to_sentry / backtraces are reversed and line number is not extracted correctly 2019-06-13 10:18:40 -07:00
a8879da4fc disable windows on CI for now 2019-06-10 22:10:43 -07:00
5f4a430845 disable building ws on windows on travis 2019-06-10 22:01:19 -07:00
b9231be305 (cmake) missing find_package(Threads) on UNIX 2019-06-10 13:39:22 -07:00
7cb5cc05e4 Add -DUSE_VENDORED_THIRD_PARTY=1 to build ws 2019-06-10 13:26:41 -07:00
750a752ac0 - 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
61e5f52286 - travis CI uses g++ on Linux 2019-06-09 14:27:45 -07:00
ce0b716f54 compile error in IXWebSocketMessageQTest 2019-06-09 12:25:36 -07:00
aae8e5ec65 fix IXWebSocketMessageQTest.cpp 2019-06-09 12:08:00 -07:00
2723e8466e fix changelog 2019-06-09 12:02:38 -07:00
f13c610352 update README to reflect the new API 2019-06-09 12:02:02 -07:00
55c65b08bf - 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
a11aa3e0dd WebSocket::send takes a third arg, binary which default to true (can be text too) 2019-06-09 11:35:31 -07:00
de0bf5ebcd WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg 2019-06-09 11:33:17 -07:00
15369e1ae9 ... 2019-06-09 10:22:27 -07:00
d4115880b9 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
3c80c75e4a 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
5cb72dce4c ws echo_server has a -g option to print a greeting message on connect 2019-06-08 09:16:33 -07:00
d2747487e3 IXSocketMbedTLS: better error handling in close and connect 2019-06-06 14:59:22 -07:00
12e664fc61 add a changelog 2019-06-06 13:59:12 -07:00
cbf21b4008 add an option to easily disable per message deflate compression 2019-06-06 13:48:53 -07:00
68c1bf7017 fix Dockerfile link 2019-06-05 19:38:44 -07:00
257c901255 cobra to sentry / more error handling 2019-06-05 19:37:51 -07:00
15d8c663da cobra to sentry fixes 2019-06-05 18:47:48 -07:00
d50125c62d 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
9262880369 Fix compile error with JSON uint64_t 🚯 2019-06-04 13:45:29 -07:00
2b111e8352 HttpResponse is a struct, not a tuple 🉐 2019-06-03 22:12:52 -07:00
a35cbdfb7c http / PUT fix 🐚 2019-06-03 21:12:39 -07:00
6a41b7389f http client: stop hardcoding Accept header, and use a default value if one is passed in 👭 2019-06-03 14:02:54 -07:00
a187e69650 Add simple HTTP and HTTPS client test ㊙️ 2019-06-03 12:23:35 -07:00
fcacddbd9f (http client) / Add DEL and PUT method, make requests and other utilities public 👐 2019-06-03 11:38:56 -07:00
fa84ade6be 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
17eaa323ed play with podmena 2019-06-02 11:03:44 -07:00
6177fe7803 add notes about mbedtls CMake 2019-06-01 18:00:25 -07:00
57976cf613 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
977e8794ec (clang format) fix indent and make (rarely) accessor/setters in class on a single line 2019-05-31 00:53:14 -07:00
c68848eecc fix cobra to sentry + change ws docker file to use alpine (much smaller footprint) 2019-05-31 00:43:22 -07:00
c6dfb14953 clang format, based on cpprest 2019-05-30 08:46:50 -07:00
5bad02ccae std::chrono::duration is not initialized to 0 units of time 2019-05-26 14:16:15 -07:00
2e379cbf2a do not select on a closed file descriptor (doing so crash on Android) 2019-05-22 18:58:22 -07:00
0e23584751 enable IXWebSocketMessageQTest.cpp on mac and windows 2019-05-22 11:03:13 -07:00
49fd2a9e53 Clean (#82)
Thanks
2019-05-21 12:14:58 -07:00
6264a8b41d 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
3990d3bcbf 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
aa3f201ced one cpu on windows for executing tests 2019-05-17 15:45:31 -07:00
83c261977d add back IXWebSocketMessageQueue, with its unittest disabled 2019-05-16 22:41:39 -07:00
6ca28d96bf Linux build fix: strncpy needs <string.h> 2019-05-16 22:21:15 -07:00
c4a5647b62 Revert "Merge branch 'Dimon4eg-message-queue'"
This reverts commit 13fa325134, reversing
changes made to aecd5e9c94.
2019-05-16 22:15:17 -07:00
720d5593a5 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
13fa325134 Merge branch 'Dimon4eg-message-queue' 2019-05-16 19:26:45 -07:00
773cbb4907 bring back socket mutex which is needed, some CI failures are happening without it 2019-05-16 19:23:32 -07:00
a696264b48 disable socket mutex usage in WebSocketTransport 2019-05-16 19:23:32 -07:00
b7db5f77fb remove dead code 2019-05-16 19:23:32 -07:00
b11678e636 refactor connect unittest so that it hits a local server instead of a remote server 2019-05-16 19:23:32 -07:00
f746070944 travis makefile fix 2019-05-16 19:23:32 -07:00
3323a51ab5 try to run ws test on linux + macOS on travis 2019-05-16 19:23:32 -07:00
0e59927384 Add constants for closing code and messages 2019-05-16 19:23:32 -07:00
5c4840f129 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
9ac02323ad build ws on travis (mac + linux) 2019-05-16 19:23:32 -07:00
cdbed26d1f use a regular mutex instead of a recursive one + stop properly 2019-05-16 19:23:32 -07:00
23f171f34d adding logging to IXWebSocketTestConnectionDisconnection makes it fails reliably 2019-05-16 19:23:32 -07:00
20b625e483 Update README.md 2019-05-16 19:23:32 -07:00
f1604c6460 Update README.md 2019-05-16 19:23:32 -07:00
ba0e007c05 -j option actually work ... 2019-05-16 19:23:32 -07:00
643e1bf20f unittest / add options to set the number of jobs 2019-05-16 19:23:32 -07:00
24a32a0603 enum class HttpErrorCode derives from int 2019-05-16 19:23:32 -07:00
c5caf32b77 try to re-enable some tests 2019-05-16 19:23:32 -07:00
09956d7500 recursive mutex + enable test that was breaking on Ubuntu Xenial + gcc + tsan 2019-05-16 19:23:32 -07:00
d91c896e46 comment failing test 2019-05-16 19:23:32 -07:00
042e6a22b8 comment failing test 2019-05-16 19:23:32 -07:00
14ec12d1f0 do not build ws for now on travis 2019-05-16 19:23:32 -07:00
288b05a048 more protection against socket when closing 2019-05-16 19:23:32 -07:00
5af3096070 fix compile errors with C++ enum class 2019-05-16 19:23:32 -07:00
570fa01c04 close and stop with code and reason + docker = ubuntu xenial 2019-05-16 19:23:32 -07:00
2a69038c4c add isEnabledAutomaticReconnection (#75)
* add isEnabledAutomaticReconnection

* test isEnabledAutomaticReconnection

* rename
2019-05-16 19:23:32 -07:00
0ba127e447 Revert "Revert "fix cast warning caught on windows""
This reverts commit 25eaf730bc.
2019-05-16 19:23:32 -07:00
7714bdf7e0 Revert "fix cast warning caught on windows"
This reverts commit 4edb7447df.
2019-05-16 19:23:32 -07:00
4e5e7ae50a fix cast warning caught on windows 2019-05-16 19:23:32 -07:00
5741b2f6c1 add more time to let client close (#73) 2019-05-16 19:23:32 -07:00
76172f92e9 build with gcc on Linux 2019-05-16 19:23:32 -07:00
f8b547c028 use spdlog for logging in the unittest 2019-05-16 19:23:32 -07:00
7ccd9e1709 fix inverted conditional 2019-05-16 19:23:31 -07:00
9217b27d40 server code / add dedicated thread to close/join terminated connection threads 2019-05-16 19:23:31 -07:00
819e9025b1 travis cmake version on macOS does not know --parallel option 2019-05-16 19:23:31 -07:00
53ceab9f91 build in parallel + stop building linux + clang 2019-05-16 19:23:31 -07:00
a7ed4fe5c3 disable ping tests for now as they are not super reliable 2019-05-16 19:23:31 -07:00
3190cd322d 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
dad2b64e15 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
e527ab1613 fix for Windows (#69)
* fix for Windows

* fix condition

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

* run.py: fix Windows support

* fix test listing
2019-05-16 19:20:29 -07:00
aecd5e9c94 bring back socket mutex which is needed, some CI failures are happening without it 2019-05-16 15:58:20 -07:00
e0edca43d5 disable socket mutex usage in WebSocketTransport 2019-05-16 15:46:32 -07:00
ce70d3d728 remove dead code 2019-05-16 15:05:20 -07:00
d9be40a0de refactor connect unittest so that it hits a local server instead of a remote server 2019-05-16 14:25:31 -07:00
e469f04c39 travis makefile fix 2019-05-16 14:02:24 -07:00
11774e6825 try to run ws test on linux + macOS on travis 2019-05-16 13:57:33 -07:00
42bdfb51c3 Add constants for closing code and messages 2019-05-16 12:46:53 -07:00
fd637bf1e1 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
8085e1416c build ws on travis (mac + linux) 2019-05-16 07:01:15 -07:00
671c9f805f use a regular mutex instead of a recursive one + stop properly 2019-05-15 19:26:02 -07:00
ace7a7ccae adding logging to IXWebSocketTestConnectionDisconnection makes it fails reliably 2019-05-15 19:26:02 -07:00
9c3bdf1a77 Update README.md 2019-05-15 19:22:05 -07:00
f5242b3102 Update README.md 2019-05-15 18:57:17 -07:00
f1272f059a -j option actually work ... 2019-05-15 18:15:45 -07:00
91595ff4c2 unittest / add options to set the number of jobs 2019-05-15 17:52:03 -07:00
3755d29a45 enum class HttpErrorCode derives from int 2019-05-15 16:50:00 -07:00
c2b75399ae try to re-enable some tests 2019-05-15 16:28:29 -07:00
a33ecd1338 recursive mutex + enable test that was breaking on Ubuntu Xenial + gcc + tsan 2019-05-15 16:01:05 -07:00
a7e29a9f36 comment failing test 2019-05-15 15:44:14 -07:00
02399dfa5c comment failing test 2019-05-15 15:37:30 -07:00
aec2941bac do not build ws for now on travis 2019-05-15 15:26:49 -07:00
9315eb5289 more protection against socket when closing 2019-05-15 15:18:46 -07:00
5b2b2ea7b0 fix compile errors with C++ enum class 2019-05-15 15:18:46 -07:00
d90b634e80 close and stop with code and reason + docker = ubuntu xenial 2019-05-15 15:18:46 -07:00
6dd8cda074 add isEnabledAutomaticReconnection (#75)
* add isEnabledAutomaticReconnection

* test isEnabledAutomaticReconnection

* rename
2019-05-14 11:26:37 -07:00
701be31554 Revert "Revert "fix cast warning caught on windows""
This reverts commit 25eaf730bc.
2019-05-13 22:16:49 -07:00
25eaf730bc Revert "fix cast warning caught on windows"
This reverts commit 4edb7447df.
2019-05-13 21:35:34 -07:00
4edb7447df fix cast warning caught on windows 2019-05-13 21:29:47 -07:00
5f3de60962 add more time to let client close (#73) 2019-05-13 21:26:34 -07:00
79c17aba49 build with gcc on Linux 2019-05-13 17:35:21 -07:00
80a90496d9 use spdlog for logging in the unittest 2019-05-13 17:32:57 -07:00
bbca803840 fix inverted conditional 2019-05-13 17:18:07 -07:00
160d3869a9 server code / add dedicated thread to close/join terminated connection threads 2019-05-13 17:17:35 -07:00
afd8f64da8 travis cmake version on macOS does not know --parallel option 2019-05-13 17:17:35 -07:00
6d2548b823 build in parallel + stop building linux + clang 2019-05-13 17:06:56 -07:00
642356d353 disable ping tests for now as they are not super reliable 2019-05-13 17:01:22 -07:00
ba0fa36c2a 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
12f6cd878d 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
9aacebbbaf fix for Windows (#69)
* fix for Windows

* fix condition

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

* run.py: fix Windows support

* fix test listing
2019-05-12 18:37:22 -07:00
a41d08343c Merge branch 'master' into message-queue 2019-05-12 22:00:10 +03:00
156288b17b all derived class use final keyword 2019-05-12 11:43:21 -07:00
6467f98241 add setOnMessageCallback with r-value 2019-05-12 20:59:18 +03:00
b24e4334f6 correct style 2019-05-12 20:16:02 +03:00
bf8abcbf4a fix warnings 2019-05-12 20:05:28 +03:00
bb484414b1 update comment 2019-05-12 20:00:15 +03:00
fc75b13fae update test 2019-05-12 19:57:31 +03:00
78f59b4207 added message queue test 2019-05-12 01:50:41 +03:00
7c5567db56 Added WebSocketMessageQueue 2019-05-12 01:49:06 +03:00
ed0e23e8a5 bump version to 2.0.0 2019-05-11 14:22:41 -07:00
4c4f99606e 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
a61586c846 add comment about why a unittest is disabled 2019-05-11 12:25:40 -07:00
d64d50c978 remove irrelevant comment 2019-05-11 12:24:11 -07:00
a64b7b0c4a minor improvements (#66)
* minor improvements

* fix build

* improve tests code
2019-05-11 12:20:58 -07:00
0caeb81327 minor tweaks to have full feature parity before unittest broke 2019-05-11 11:54:21 -07:00
edac7a0171 fix race condition in SelectInteruptPipe, where _fildes are not protected (caught by fedora tsan) 2019-05-11 11:45:26 -07:00
abfadad2e9 remove more iostream includes (#65) 2019-05-11 11:27:58 -07:00
2dc1547bbd rename some variables, minor cleanup 2019-05-11 10:24:28 -07:00
5eb23c9764 uncomment test 2019-05-11 10:15:22 -07:00
9f4b2856b0 fix crash on close 2019-05-11 10:15:22 -07:00
b5fc10326e fix crash on close 2019-05-11 10:12:33 -07:00
8d3a47a873 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
4df58f3059 fix warning in statsd_client about %m gnu only printf special char 2019-05-11 09:22:29 -07:00
06b8cb8d3b fix overflow warning in msgpack11.cpp 2019-05-10 21:17:05 -07:00
ff81f5b496 add env var to display the ws command typed in 2019-05-10 16:27:23 -07:00
c89f73006e cout -> spdlog 2019-05-10 12:33:34 -07:00
c28951f049 Improve calculateRetryWaitMilliseconds (#63)
* improve calculateRetryWaitMilliseconds

* update comment
2019-05-10 12:31:21 -07:00
dfaaaca223 fix static analyzer thing with un-used variable 2019-05-09 16:57:58 -07:00
c7f0bf3d64 use spdlog for logging in ws + unittest + remove un-needed mutex 2019-05-09 15:30:44 -07:00
234ce4c173 cout -> cerr 2019-05-09 15:06:42 -07:00
f60293b2e7 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
9441095637 tweak unittest sleep duration to fix gcc+linux CI 2019-05-09 11:10:39 -07:00
f82d38f758 Fail test GCC TSAN (#61)
* test that fails on Run 8

* commented the failing test
2019-05-09 09:33:18 -07:00
a7f42f35db warning police 2019-05-09 09:25:56 -07:00
cb1d1bfd85 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
28c3f2ea26 IXCobraMetricsThreadedPublisher.cpp uses a lambda to log instead of std::cerr 2019-05-08 18:53:32 -07:00
7ecaf1f982 rename ptr 2019-05-09 01:05:47 +03:00
d0a41f3894 simplify bindWebsocket 2019-05-09 00:23:16 +03:00
57562b234f use lock_guard 2019-05-09 00:20:26 +03:00
469d127d61 update comments 2019-05-09 00:16:37 +03:00
d6e9b61c8e Rename to WebSocketMessageQueue 2019-05-09 00:09:51 +03:00
8dc132dbd3 change default ports for the ws command line tool 2019-05-08 13:56:42 -07:00
98e2fbca6a ws connect display more accurate messages for incoming messages 2019-05-08 13:56:42 -07:00
fa7f0fadde Remove redundant iostream includes (#60) 2019-05-08 13:33:21 -07:00
7fb1b65ddd qf 2019-05-08 22:24:39 +03:00
77c7fdc636 Added IXWebSocketPoll class 2019-05-08 22:02:56 +03:00
2732dfd0f1 set thread name for Windows (#57) 2019-05-08 07:43:43 -07:00
2e4c4b72b6 update appveyor windows build file 2019-05-06 17:50:55 -07:00
fc21ad519b update README.md (#54)
Yeah !
2019-05-06 15:02:16 -07:00
c65cfd3d26 Use LUrlParser to fix issue of Windows (#53)
LGTM
2019-05-06 14:45:02 -07:00
8955462f73 added tests for IXUrlParser (#52)
* added tests for IXUrlParser

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

* fix tests for windows

* qf for linux

* clean up
2019-05-06 12:22:57 -07:00
d561e1141e Update README.md 2019-05-06 09:22:52 -07:00
753fc845ac Fix for windows (#50) 2019-05-06 09:13:42 -07:00
5dbc00bbfe doc: add reference to the conan file built at https://github.com/Zinnion/conan-IXWebSocket 2019-05-01 21:31:32 -07:00
14ec8522ef remove un-needed _backgroundThreadRunning variable 2019-05-01 11:09:25 -07:00
0c2d1c22bc Make AutomaticReconnection optional (#47)
* unittest pass + commands behave as expected

* cleanup
2019-04-29 21:12:34 -07:00
1d39a9c9a9 build fix 2019-04-29 20:54:00 -07:00
b588ed0fa1 tsan fixes on ubuntu xenial (what travis run) 2019-04-29 20:48:16 -07:00
d9f7a138b8 dns lookup: fix race condition accessing _errMsg 2019-04-29 19:29:27 -07:00
d3e04ff619 tsan linux tentative fix / copy string instead of passing a const reference 2019-04-29 17:27:53 -07:00
372dd24cc7 rename _blocking to _backgroundThreadRunning and invert the naming 2019-04-29 16:54:08 -07:00
a9422cf34d fix data race on _thread 2019-04-29 16:46:16 -07:00
c7e52e6fcd fix data race on _useMask 2019-04-29 16:41:34 -07:00
705e0823cb ws connect mode / add a flag to disable automatic reconnection, not hooked up yet 2019-04-29 14:31:29 -07:00
8e4cf74974 enable tsan on travis for all configs 2019-04-29 09:11:16 -07:00
0a7157655b initialize netSystem (aka winsock on windows) explicitely 2019-04-25 16:38:15 -07:00
58d65926bb 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
b178ba16af fix indentation of greatestCommonDivisor 2019-04-25 16:21:36 -07:00
e4c09284b5 Remove commented code 2019-04-25 16:16:52 -07:00
9367a1feff 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
d37ed300e2 disable failing unittest temporarily 2019-04-25 09:04:35 -07:00
3207ce37b6 Speedup build for Windows (#43)
* Speedup build for Windows

* add space :)
2019-04-25 07:41:01 -07:00
d036ad7138 tsan fix for the IXWebSocketServerTest test, where there's a data race for connectionId 2019-04-24 22:11:14 -07:00
4fe07579b9 Fix data races in DNSLookup (tsan) 2019-04-24 21:53:31 -07:00
f563d14134 better server termination / another try at preventing thread join failures 2019-04-24 09:45:53 -07:00
f1b3ecc738 compiler warning police 2019-04-24 09:45:03 -07:00
8387f89115 Fix #38 Add some docker doc in the README 2019-04-23 20:51:58 -07:00
773f92347f ws cobra publish stress mode fix 2019-04-23 20:51:58 -07:00
8ff1339b80 add boolean and add missing protocol error close constant (#41) 2019-04-23 04:31:55 -07:00
c85d5da111 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
9ab7bc652a (server) attempt to fix broken macOS unittest on travis CI 2019-04-22 09:36:16 -07:00
e5c724eb05 Expand build section in the main README 2019-04-21 21:11:08 -07:00
e0300903d9 Merge branch 'dhruvkakadiya-readme/fix' 2019-04-21 16:14:35 -07:00
1ef38afcf7 For #39, fixed setOnMessageCallback() in README. 2019-04-21 14:56:02 -07:00
210d19c8a0 doc cobra 2019-04-21 11:52:38 -07:00
6d24cc44b2 new target to uninstall files 2019-04-21 11:47:57 -07:00
768e8eb074 Fix #37 / add directives to install headers and library 2019-04-21 11:42:37 -07:00
3dd902e1f9 move cobra files to their own subfolder 2019-04-21 11:20:17 -07:00
f85c5002b7 add cobra metrics publisher 2019-04-21 11:16:33 -07:00
d48bf9249b indentation / comestic changes 2019-04-19 16:57:38 -07:00
0dfc66f1c7 (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
4564173b75 (socket server) wait for all connections threads to be terminated before exiting stop method 2019-04-19 16:31:33 -07:00
b60e5aaf1f default sanitizer choice 2019-04-19 15:13:59 -07:00
da67f4cb9a disable clang sanitizers in CI on any platforms but Darwin 2019-04-19 15:09:01 -07:00
b041042473 fix Linux cast warning 2019-04-19 15:03:49 -07:00
f83263d6a1 (unittest) upgrade to Catch2 version 2.7.1 2019-04-19 14:41:03 -07:00
b0139c2217 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
0ba2e2ce96 uses sh syntax to capture output 2019-04-19 12:40:39 -07:00
4a91ad80c8 (ci) verbose mode to figure out Linux build problems on travis 2019-04-19 12:10:43 -07:00
4cc715b13d Windows nmake does not have a -j flag 2019-04-19 11:58:02 -07:00
0dfd7cd543 Windows + unittest python script fixes 2019-04-19 11:54:58 -07:00
56f164ce2b fix warning / ws_cobra_subscribe does not need a verbose flag 2019-04-19 11:45:42 -07:00
65db8c9b00 (test) build dir is an absolute path 2019-04-19 11:45:02 -07:00
4c4137d9f2 (ws) fix compiler warnings 2019-04-19 09:48:46 -07:00
e433e8b5e9 fix test execution on travis which was broken / unify running test locally and on travis 2019-04-19 09:46:17 -07:00
bb442021cf fix bad merge in IXWebSocketTransport.cpp ... 2019-04-19 09:41:16 -07:00
91106b7456 Socket::Poll does not need a callback 2019-04-19 09:32:49 -07:00
309b5ee1b3 Ping timeout use constant (#36)
* use constant for ping timeout

* change close code types
2019-04-19 09:16:25 -07:00
4eded01841 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
e3d0c899d3 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
d7595b0dd0 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
f0375e59fa docker container works with SSL + fix compiler warnings in statsd third_party module 2019-04-18 09:11:12 -07:00
c367435073 docker + linux build fix 2019-04-17 22:52:03 -07:00
dc812c384e setter method does not need to return anything, make it void 2019-04-17 20:36:26 -07:00
10b2d10dbd (doc) Add more doc to SocketServer 2019-04-17 20:36:26 -07:00
f96babc6a6 websocket server: closed connection threads are joined properly 2019-04-17 20:36:26 -07:00
4e2e14fb22 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
bcf2fc1812 make closeWireSize a default parameter of WebSocketTransport::close 2019-04-16 09:55:12 -07:00
935e6791a3 close method change and fix code (#28)
* close method change and fix code

* missing mutex
2019-04-16 08:58:34 -07:00
fbb7c012a3 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
dac18fcabf move security framework linking to ixwebsocket (#26) 2019-04-14 17:13:24 -07:00
d8e83caffc fix warning 2019-04-13 21:16:04 -07:00
fbf80b9f50 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
c2a9139d41 (ws) add subcommands: cobra subscribe, and cobra subscribe to statsd bridge 2019-04-08 21:56:01 -07:00
6e3dff149a linux ci tentative fix 2019-04-03 22:02:10 -07:00
1bacbe38f4 better unittest runner / can run through lldb and produce a junit XML artifact 2019-03-29 15:54:05 -07:00
2e9c610ac9 Bump sleep time in test shell script 2019-03-29 09:36:56 -07:00
eb063ec60a (redis_subscribe) in verbose mode, received message gets printed with a 'received: ' header 2019-03-29 09:35:19 -07:00
37fb14646d Add clarification notice about third party modules 2019-03-29 09:34:17 -07:00
ae543518d3 offline version of remark-latest 2019-03-28 16:06:43 -07:00
c865d64608 redis conf slides 2019-03-28 14:17:19 -07:00
3004422cb6 slides 2019-03-27 16:27:52 -07:00
0c46a17443 add redis-conf slides 2019-03-27 15:53:55 -07:00
497373d976 ws redis command improvements + test script 2019-03-27 13:41:46 -07:00
91198aca0d (ws) redis_subscribe and redis_publish can take a password + display subscribe response 2019-03-26 09:33:22 -07:00
b17a5e5f0b update doc 2019-03-24 21:48:14 -07:00
3f0ef59f65 remove Formula folder
Homebrew stuff is at https://github.com/bsergean/homebrew-IXWebSocket
2019-03-24 21:43:38 -07:00
1e96edc293 (server) fix masking bug 2019-03-22 15:33:04 -07:00
0afb77393b can send TEXT message (we only support BINARY messages now) 2019-03-22 14:24:22 -07:00
7614b642bb unmasked code is broken 2019-03-22 14:24:15 -07:00
bc89580dfe remove printf + unittest fix 2019-03-22 09:56:28 -07:00
358ae13a88 (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
ccf9dcba70 (server) HTTP response is malformed 2019-03-22 09:52:19 -07:00
94604fad61 minor cleanup 2019-03-21 13:51:25 -07:00
5c4cc7c50d 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
9ed961ec06 cleanup, remove dead method 2019-03-21 10:06:59 -07:00
e6bd8cc8c4 (cmake) add a warning about 32/64 conversion problems. 2019-03-20 21:51:38 -07:00
ee25bd0f92 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
e77b9176f3 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
afe8b966ad Fixed heartbeat typos (#22) 2019-03-19 21:31:43 -07:00
310724c961 make PollResultType an enum class 2019-03-19 09:29:57 -07:00
ceba8ae620 fix bug with isReadyToWrite 2019-03-18 22:05:04 -07:00
fead661ab7 workaround bug in Socket::isReadyToWrite 2019-03-18 20:37:33 -07:00
9c8c17f577 use milliseconds 2019-03-18 20:17:44 -07:00
a04f83930f ws / log subcommand name 2019-03-18 17:54:06 -07:00
c421d19800 disable sigpipe on osx when writing/reading into a dead pipe 2019-03-18 17:52:01 -07:00
521f02c90e edit homebrew install steps 2019-03-18 15:45:33 -07:00
c86b6074f2 add an install target 2019-03-18 15:11:08 -07:00
d5d1a2c5f4 no default parameters for isReadyToWrite and isReadyToRead 2019-03-18 14:31:21 -07:00
2a90e3f478 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
1d49ba41ea Fix typo (#19) 2019-03-17 16:08:28 -07:00
e1de1f6682 remove unused gitmodule file 2019-03-17 10:38:48 -07:00
47ed5e4d4d remove unused folder 2019-03-17 10:38:19 -07:00
d77f6f5659 linux hangs when closing 2019-03-16 11:38:23 -07:00
05f0045d5d edit README 2019-03-16 11:32:46 -07:00
c4afb84f6e use pipe to abort select on Linux as well as macOS 2019-03-15 17:46:40 -07:00
b0b2f9b6d2 missing assert include on Linux 2019-03-15 11:43:27 -07:00
ee37feb489 cleanup 2019-03-15 11:41:57 -07:00
6b8337596f unittest fix 2019-03-14 18:58:16 -07:00
250665b92e linux compile fix 2019-03-14 18:55:33 -07:00
86b83c889e linux fixes 2019-03-14 18:54:47 -07:00
c9c657c07b build fix 2019-03-14 18:53:21 -07:00
4f2babaf54 select interrupt cleanup 2019-03-14 18:37:38 -07:00
1b03bf4555 linux build fix 2019-03-14 15:17:17 -07:00
977b995af9 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
310ab990bd set a default close reason string 2019-03-14 14:52:51 -07:00
d6b49b54d4 do not busy loop while sending 2019-03-14 14:48:08 -07:00
f00cf39462 remove docker folder 2019-03-14 14:48:02 -07:00
18550cf1cb send optimization + ws file transfer test 2019-03-14 14:47:53 -07:00
168918f807 Update README.md
Stop lying about Windows support ...
2019-03-13 23:10:40 -07:00
2750df8aa7 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
d6597d9f52 websocket send: make sure all data in the kernel buffer is sent 2019-03-11 22:16:55 -07:00
892ea375e3 add new message type when receiving message fragments 2019-03-11 11:12:43 -07:00
03abe77b5f ws broacast_server / can set serving hostname 2019-03-10 16:36:44 -07:00
e46eb8aa49 debian 9 unittest build fix 2019-03-10 16:07:48 -07:00
2c4862e0f1 asan test suite fix 2019-03-09 10:45:40 -08:00
fd69efa45c unittest + warning fix 2019-03-09 10:37:14 -08:00
e8aa15917f add ability to run with asan on macOS 2019-03-05 17:07:28 -08:00
b3d77f8902 fix compiler warnings in ws command line tool 2019-03-04 13:56:30 -08:00
9c3b0b08ec 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
fe7d94194c readBytes does not read bytes one by one but in chunks 2019-03-02 21:11:16 -08:00
d6c26d6aa8 create a blocking + cancellable Socket::readBytes method 2019-03-02 15:16:46 -08:00
8a74ddcd13 create a blocking + cancellable Socket::readBytes method 2019-03-02 11:01:51 -08:00
18e7189a07 more ws doc 2019-02-28 22:07:45 -08:00
785dd42c84 more ws doc 2019-02-28 22:03:48 -08:00
0cff5065d9 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
e881b82511 Update README.md 2019-02-22 21:53:29 -08:00
d5551e5d68 mv cobra_publisher under ws folder 2019-02-22 21:51:03 -08:00
e8583000b8 ping pong added to ws 2019-02-22 21:47:57 -08:00
d642ef1a89 comments 2019-02-22 21:27:49 -08:00
2df118022d add gitignore 2019-02-22 21:26:25 -08:00
95457c8f4c add echo and broadcast server as ws sub-commands 2019-02-22 21:25:56 -08:00
0a45b7787f cleanup 2019-02-22 20:51:22 -08:00
b8c397e180 add ws_chat and ws_connect sub commands to ws 2019-02-22 20:49:26 -08:00
90105fa2b3 all CMakeLists are referenced by the top level one 2019-02-21 22:21:29 -08:00
2203 changed files with 562658 additions and 15189 deletions

46
.clang-format Normal file
View File

@ -0,0 +1,46 @@
# 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: InlineOnly
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
Standard: Cpp11
UseTab: Never

View File

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

View File

@ -1,2 +1,2 @@
venv
build
*.pyc

View File

@ -1,17 +1,41 @@
language: cpp
dist: xenial
language: bash
compiler:
- gcc
- clang
os:
- linux
- osx
# See https://github.com/amaiorano/vectrexy/blob/master/.travis.yml
# for ideas on installing vcpkg
matrix:
exclude:
# GCC fails on recent Travis OSX images.
- compiler: gcc
os: osx
include:
# macOS
# - os: osx
# compiler: clang
# script:
# - python test/run.py
# - make ws
script: python test/run.py
# # Linux
# - os: linux
# dist: xenial
# script:
# - python test/run.py
# - make ws
# env:
# - CC=gcc
# - CXX=g++
# Clang + Linux disabled for now
# - os: linux
# dist: xenial
# script: python test/run.py
# env:
# - CC=clang
# - CXX=clang++
Windows
- os: windows
env:
- CMAKE_PATH="/c/Program Files/CMake/bin"
script:
- export PATH=$CMAKE_PATH:$PATH
# - cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 -DUSE_VENDORED_THIRD_PARTY=1 .
# - cmake --build --parallel .
- python test/run.py

34
CHANGELOG.md Normal file
View File

@ -0,0 +1,34 @@
# Changelog
All notable changes to this project will be documented in this file.
## [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

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)

View File

@ -4,54 +4,94 @@
#
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)
# -Wshorten-64-to-32 does not work with clang
if (NOT WIN32)
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/IXEventFd.cpp
ixwebsocket/IXSocket.cpp
ixwebsocket/IXSocketServer.cpp
ixwebsocket/IXSocketConnect.cpp
ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXConnectionState.cpp
ixwebsocket/IXDNSLookup.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/IXUrlParser.cpp
ixwebsocket/IXWebSocket.cpp
ixwebsocket/IXWebSocketServer.cpp
ixwebsocket/IXWebSocketTransport.cpp
ixwebsocket/IXWebSocketCloseConstants.cpp
ixwebsocket/IXWebSocketHandshake.cpp
ixwebsocket/IXWebSocketHttpHeaders.cpp
ixwebsocket/IXWebSocketMessageQueue.cpp
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
ixwebsocket/IXWebSocketServer.cpp
ixwebsocket/IXWebSocketTransport.cpp
ixwebsocket/LUrlParser.cpp
)
set( IXWEBSOCKET_HEADERS
ixwebsocket/IXEventFd.h
ixwebsocket/IXSocket.h
ixwebsocket/IXSocketServer.h
ixwebsocket/IXSocketConnect.h
ixwebsocket/IXSetThreadName.h
ixwebsocket/IXDNSLookup.h
ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h
ixwebsocket/IXDNSLookup.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/IXUrlParser.h
ixwebsocket/IXWebSocket.h
ixwebsocket/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h
ixwebsocket/IXWebSocketHandshake.h
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketCloseConstants.h
ixwebsocket/IXWebSocketCloseInfo.h
ixwebsocket/IXWebSocketErrorInfo.h
ixwebsocket/IXWebSocketHandshake.h
ixwebsocket/IXWebSocketHttpHeaders.h
ixwebsocket/IXWebSocketMessage.h
ixwebsocket/IXWebSocketMessageQueue.h
ixwebsocket/IXWebSocketMessageType.h
ixwebsocket/IXWebSocketOpenInfo.h
ixwebsocket/IXWebSocketPerMessageDeflate.h
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
ixwebsocket/IXWebSocketHttpHeaders.h
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h
ixwebsocket/LUrlParser.h
ixwebsocket/libwshandshake.hpp
)
if (UNIX)
# Linux, Mac, iOS, Android
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.cpp )
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.h )
endif()
# Platform specific code
if (APPLE)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
@ -59,18 +99,31 @@ elseif (WIN32)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
else()
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
endif()
if (WIN32)
set(USE_MBED_TLS TRUE)
endif()
set(USE_OPEN_SSL FALSE)
if (USE_TLS)
add_definitions(-DIXWEBSOCKET_USE_TLS)
if (APPLE)
if (USE_MBED_TLS)
add_definitions(-DIXWEBSOCKET_USE_MBED_TLS)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
elseif (APPLE)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
elseif (WIN32)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
else()
add_definitions(-DIXWEBSOCKET_USE_OPEN_SSL)
set(USE_OPEN_SSL TRUE)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
endif()
@ -81,35 +134,66 @@ add_library( ixwebsocket STATIC
${IXWEBSOCKET_HEADERS}
)
# gcc/Linux needs -pthread
find_package(Threads)
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS)
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
endif()
if(UNIX AND NOT APPLE)
if (UNIX)
find_package(Threads)
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
endif()
if (USE_OPEN_SSL)
find_package(OpenSSL REQUIRED)
add_definitions(${OPENSSL_DEFINITIONS})
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
endif()
if (WIN32)
get_filename_component(libz_path
${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/release_dynamic/x64/lib/zlib.lib
ABSOLUTE)
add_library(libz STATIC IMPORTED)
set_target_properties(libz PROPERTIES IMPORTED_LOCATION
${libz_path})
if (USE_MBED_TLS)
if (USE_VENDORED_THIRD_PARTY)
set (ENABLE_PROGRAMS OFF)
add_subdirectory(third_party/mbedtls)
include_directories(third_party/mbedtls/include)
include_directories(${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/include)
target_link_libraries(ixwebsocket mbedtls)
else()
find_package(MbedTLS REQUIRED)
include_directories(${MBEDTLS_INCLUDE_DIRS})
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
endif()
endif()
target_link_libraries(ixwebsocket libz wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
find_package(ZLIB REQUIRED)
if (ZLIB_FOUND)
include_directories(${ZLIB_INCLUDE_DIRS})
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
else()
target_link_libraries(ixwebsocket
z ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
add_subdirectory(third_party/zlib)
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
target_link_libraries(ixwebsocket zlibstatic wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
set( IXWEBSOCKET_INCLUDE_DIRS
.
../../shared/OpenSSL/include)
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
)
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)
add_subdirectory(ws)
endif()

1
DOCKER_VERSION Normal file
View File

@ -0,0 +1 @@
5.0.0

View File

@ -1 +1 @@
docker/Dockerfile.debian
docker/Dockerfile.alpine

301
README.md
View File

@ -4,18 +4,17 @@
## Introduction
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication. 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.
[*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. 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 (no TLS support yet)
* Android
* Windows
## Examples
The examples folder countains a simple chat program, using a node.js broadcast server.
The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage.
Here is what the client API looks like.
@ -25,31 +24,32 @@ ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
webSocket.setUrl(url);
// Optional heart beat, sent every 45 seconds when there isn't any traffic
// 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.setHeartBeatPeriod(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(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
[](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");
// 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
@ -64,31 +64,38 @@ Here is what the server API looks like. Note that server support is very recent
ix::WebSocketServer server(port);
server.setOnConnectionCallback(
[&server](std::shared_ptr<ix::WebSocket> webSocket)
[&server](std::shared_ptr<WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, &server](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
{
if (messageType == ix::WebSocket_MessageType_Open)
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cerr << "New connection" << std::endl;
std::cerr << "Uri: " << openInfo.uri << 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 : openInfo.headers)
for (auto it : msg->openInfo.headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_Message)
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.
webSocket->send(str);
// 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);
}
}
);
@ -110,11 +117,160 @@ server.wait();
```
Here is what the HTTP client API looks like.
```
//
// 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 errorCode = response->errorCode; // 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
```
Here is what the HTTP server API looks like. Note that HTTP server support is very, very recent and subject to changes.
```
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.
```
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);
}
```
## Build
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.
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.
There is a Dockerfile for running some code on Linux, and a unittest which can be executed by typing `make test`.
```
mkdir build # make a build dir so that you can build out of tree.
cd build
cmake ..
make -j
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
```
Headers and a static library will be installed to the target dir.
A [conan](https://conan.io/) file is available at [conan-IXWebSocket](https://github.com/Zinnion/conan-IXWebSocket).
There is a unittest which can be executed by typing `make test`.
There is a Dockerfile for running some code on Linux. To use docker-compose you must make a docker container first.
```
$ make docker
...
$ docker compose up &
...
$ docker exec -it ixwebsocket_ws_1 bash
app@ca2340eb9106:~$ ws --help
ws is a websocket tool
...
```
Finally you can build and install the `ws command line tool` with Homebrew. The homebrew version might be slightly out of date.
```
brew tap bsergean/IXWebSocket
brew install IXWebSocket
```
## Implementation details
@ -124,7 +280,7 @@ The per message deflate compression option is supported. It can lead to very nic
### 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, and OpenSSL is used on Android and Linux.
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 is used on Android and Linux, mbedTLS is used on Windows.
### Polling and background thread work
@ -132,43 +288,35 @@ No manual polling to fetch data is required. Data is sent and received instantly
### 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.
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 500M were sent and received succesfully.
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.
## Limitations
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
* On Windows TLS is not setup yet to validate certificates.
* There is no convenient way to embed a ca cert.
* No utf-8 validation is made when sending TEXT message with sendText()
* 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 isn't as scalable as strategies using epoll or kqueue.
## 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. `./cmd_websocket_chat bob`
6. Bring up a third terminal. `./cmd_websocket_chat bill`
7. Start typing things in any of those terminals. Hopefully you should see your message being received on the other end.
* 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's a simplistic diagram which explains how the code is structured in term of class/modules.
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
+-----------------------+ --- Private
| |
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
| |
@ -205,10 +353,10 @@ If the connection was closed and sending failed, the return value will be set to
`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.
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
@ -216,32 +364,27 @@ The onMessage event will be fired when the connection is opened or closed. This
```
webSocket.setOnMessageCallback(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
[](const ix::WebSocketMessagePtr& msg)
{
if (messageType == ix::WebSocket_MessageType_Open)
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 : headers)
for (auto it : msg->headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
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 << closeInfo.code << std::endl;
std::cout << closeInfo.reason << std::endl;
std::cout << msg->closeInfo.code << std::endl;
std::cout << msg->closeInfo.reason << std::endl;
}
}
);
@ -249,24 +392,19 @@ webSocket.setOnMessageCallback(
### 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.
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
```
webSocket.setOnMessageCallback(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
[](const ix::WebSocketMessagePtr& msg)
{
if (messageType == ix::WebSocket_MessageType_Error)
if (msg->type == ix::WebSocketMessageType::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;
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;
}
}
@ -293,17 +431,12 @@ Ping/pong messages are used to implement keep-alive. 2 message types exists to i
```
webSocket.setOnMessageCallback(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketCloseInfo& closeInfo,
const ix::WebSocketHttpHeaders& headers)
[](const ix::WebSocketMessagePtr& msg)
{
if (messageType == ix::WebSocket_MessageType_Ping ||
messageType == ix::WebSocket_MessageType_Pong)
if (msg->type == ix::WebSocketMessageType::Ping ||
msg->type == ix::WebSocketMessageType::Pong)
{
std::cout << "pong data: " << str << std::endl;
std::cout << "pong data: " << msg->str << std::endl;
}
}
);
@ -318,7 +451,7 @@ websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes l
### Heartbeat.
You can configure an optional heart beat / keep-alive, sent every 45 seconds
when there isn't any traffic to make sure that load balancers do not kill an
when there is no any traffic to make sure that load balancers do not kill an
idle connection.
```

View File

@ -1,10 +1,14 @@
image:
- Visual Studio 2017
- Ubuntu
install:
- ls -al
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
- python test/run.py
- cd test
- mkdir build
- cd build
- cmake -G"NMake Makefiles" ..
- nmake
- ixwebsocket_unittest.exe
build: off

43
docker-compose.yml Normal file
View File

@ -0,0 +1,43 @@
version: "3"
services:
snake:
image: bsergean/ws:build
entrypoint: ws snake --port 8765 --host 0.0.0.0 --redis_hosts redis1
ports:
- "8765:8765"
networks:
- ws-net
depends_on:
- redis1
ws:
security_opt:
- seccomp:unconfined
cap_add:
- SYS_PTRACE
stdin_open: true
tty: true
image: bsergean/ws:build
entrypoint: bash
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
networks:
ws-net:

View File

@ -1,16 +0,0 @@
FROM debian:stretch
# RUN yum install -y gcc-c++ make cmake openssl-devel gdb
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install g++
RUN apt-get -y install libssl-dev
RUN apt-get -y install gdb
RUN apt-get -y install screen
RUN apt-get -y install procps
RUN apt-get -y install lsof
COPY . .
WORKDIR examples/ws_connect
RUN ["sh", "build_linux.sh"]

View File

@ -1,11 +1,33 @@
FROM alpine:3.8
FROM alpine as build
RUN apk add --no-cache g++ musl-dev make cmake openssl-dev
RUN apk add --no-cache gcc g++ musl-dev linux-headers cmake openssl-dev
RUN apk add --no-cache make
RUN apk add --no-cache zlib-dev
COPY . .
RUN addgroup -S app && adduser -S -G app app
RUN chown -R app:app /opt
RUN chown -R app:app /usr/local
WORKDIR examples/ws_connect
RUN ["sh", "build_linux.sh"]
# 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
EXPOSE 8765
CMD ["ws_connect"]
USER app
RUN [ "make" ]
FROM alpine as runtime
RUN apk add --no-cache libstdc++
RUN addgroup -S app && adduser -S -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
ENTRYPOINT ["ws"]
CMD ["--help"]

View File

@ -1,11 +0,0 @@
FROM alpine:3.8
RUN apk add --no-cache g++ musl-dev make cmake openssl-dev
COPY . .
WORKDIR examples/ws_connect
RUN ["sh", "build_linux.sh"]
EXPOSE 8765
CMD ["ws_connect"]

View File

@ -1,22 +1,52 @@
FROM debian:stretch
# 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 gdb
RUN apt-get -y install screen
RUN apt-get -y install procps
RUN apt-get -y install lsof
RUN apt-get -y install libz-dev
RUN apt-get -y install vim
RUN apt-get -y install make
RUN apt-get -y install cmake
COPY . .
WORKDIR ws
RUN ["sh", "docker_build.sh"]
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
EXPOSE 8765
CMD ["/ws/ws", "transfer", "8765"]
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

@ -1,8 +0,0 @@
FROM gcc:8
# RUN yum install -y gcc-c++ make cmake openssl-devel gdb
COPY . .
WORKDIR examples/ws_connect
RUN ["sh", "build_linux.sh"]

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: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"]

View File

@ -1,9 +0,0 @@
CMakeCache.txt
package-lock.json
CMakeFiles
ixwebsocket_unittest
cmake_install.cmake
node_modules
ixwebsocket
Makefile
build

View File

@ -1,30 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (broadcast_server)
# There's -Weverything too for clang
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
include_directories(broadcast_server .)
add_executable(broadcast_server
broadcast_server.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(broadcast_server "-framework foundation" "-framework security")
endif()
target_link_libraries(broadcast_server ixwebsocket)
install(TARGETS broadcast_server DESTINATION bin)

View File

@ -1,75 +0,0 @@
/*
* broadcast_server.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <ixwebsocket/IXWebSocketServer.h>
int main(int argc, char** argv)
{
int port = 8080;
if (argc == 2)
{
std::stringstream ss;
ss << argv[1];
ss >> port;
}
ix::WebSocketServer server(port);
server.setOnConnectionCallback(
[&server](std::shared_ptr<ix::WebSocket> webSocket)
{
webSocket->setOnMessageCallback(
[webSocket, &server](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Open)
{
std::cerr << "New connection" << std::endl;
std::cerr << "Uri: " << openInfo.uri << std::endl;
std::cerr << "Headers:" << std::endl;
for (auto it : openInfo.headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
std::cerr << "Closed connection" << std::endl;
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
std::cerr << "Received " << wireSize << " bytes" << std::endl;
for (auto&& client : server.getClients())
{
if (client != webSocket)
{
client->send(str);
}
}
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
return 1;
}
server.start();
server.wait();
return 0;
}

View File

@ -1,3 +0,0 @@
build
venv
node_modules

View File

@ -1,23 +0,0 @@
#
# cmd_websocket_chat.cpp
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (cmd_websocket_chat)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(cmd_websocket_chat cmd_websocket_chat.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(cmd_websocket_chat "-framework foundation" "-framework security")
endif()
target_link_libraries(cmd_websocket_chat ixwebsocket)
install(TARGETS cmd_websocket_chat DESTINATION bin)

View File

@ -1,39 +0,0 @@
# Building
1. cmake -G .
2. make
## Disable TLS
chat$ cmake -DUSE_TLS=OFF .
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/bsergeant/src/foss/ixwebsocket/examples/chat
chat$ make
Scanning dependencies of target ixwebsocket
[ 16%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocket.cpp.o
[ 33%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocket.cpp.o
[ 50%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocketTransport.cpp.o
[ 66%] Linking CXX static library libixwebsocket.a
[ 66%] Built target ixwebsocket
[ 83%] Linking CXX executable cmd_websocket_chat
[100%] Built target cmd_websocket_chat
## Enable TLS (default)
```
chat$ cmake -DUSE_TLS=ON .
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/bsergeant/src/foss/ixwebsocket/examples/chat
(venv) chat$ make
Scanning dependencies of target ixwebsocket
[ 14%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocket.cpp.o
[ 28%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocket.cpp.o
[ 42%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocketTransport.cpp.o
[ 57%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocketAppleSSL.cpp.o
[ 71%] Linking CXX static library libixwebsocket.a
[ 71%] Built target ixwebsocket
[ 85%] Linking CXX executable cmd_websocket_chat
[100%] Built target cmd_websocket_chat
```

View File

@ -1,15 +0,0 @@
#!/bin/sh
#
# Author: Benjamin Sergeant
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
#
# 'manual' way of building. You can also use cmake.
g++ --std=c++11 \
../../ixwebsocket/IXSocket.cpp \
../../ixwebsocket/IXWebSocketTransport.cpp \
../../ixwebsocket/IXWebSocket.cpp \
-I ../.. \
cmd_websocket_chat.cpp \
-o cmd_websocket_chat

View File

@ -1,17 +0,0 @@
#!/bin/sh
#
# Author: Benjamin Sergeant
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
#
# 'manual' way of building. You can also use cmake.
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,31 +0,0 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
},
"ws": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
"requires": {
"async-limiter": "1.0.0",
"safe-buffer": "5.1.2",
"ultron": "1.1.1"
}
}
}
}

View File

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

View File

@ -1,3 +0,0 @@
venv
build
node_modules

View File

@ -1,40 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (cobra_publisher)
# There's -Weverything too for clang
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
include_directories(cobra_publisher ${OPENSSL_PREFIX}/include)
include_directories(cobra_publisher .)
add_executable(cobra_publisher
jsoncpp/jsoncpp.cpp
ixcrypto/IXHMac.cpp
ixcrypto/IXBase64.cpp
IXCobraConnection.cpp
cobra_publisher.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(cobra_publisher "-framework foundation" "-framework security")
endif()
get_filename_component(crypto_lib_path ${OPENSSL_PREFIX}/lib/libcrypto.a ABSOLUTE)
add_library(lib_crypto STATIC IMPORTED)
set_target_properties(lib_crypto PROPERTIES IMPORTED_LOCATION ${crypto_lib_path})
link_directories(/usr/local/opt/openssl/lib)
target_link_libraries(cobra_publisher ixwebsocket lib_crypto)
install(TARGETS cobra_publisher DESTINATION bin)

View File

@ -1,6 +0,0 @@
```
mkdir build
cd build
cmake ..
make && (cd .. ; sh cobra_publisher.sh)
```

View File

@ -1,123 +0,0 @@
/*
* cobra_publisher.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <fstream>
#include <atomic>
#include <ixwebsocket/IXWebSocket.h>
#include "IXCobraConnection.h"
#include "jsoncpp/json/json.h"
void msleep(int ms)
{
std::chrono::duration<double, std::milli> duration(ms);
std::this_thread::sleep_for(duration);
}
int main(int argc, char* argv[])
{
if (argc != 7)
{
std::cerr << "Usage error: need 6 arguments." << std::endl;
}
std::string endpoint = argv[1];
std::string appkey = argv[2];
std::string channel = argv[3];
std::string rolename = argv[4];
std::string rolesecret = argv[5];
std::string path = argv[6];
std::atomic<size_t> incomingBytes(0);
std::atomic<size_t> outgoingBytes(0);
ix::CobraConnection::setTrafficTrackerCallback(
[&incomingBytes, &outgoingBytes](size_t size, bool incoming)
{
if (incoming)
{
incomingBytes += size;
}
else
{
outgoingBytes += size;
}
}
);
bool done = false;
ix::CobraConnection cobraConnection;
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
true, false, false, 15, 15);
cobraConnection.configure(appkey, endpoint, rolename, rolesecret,
webSocketPerMessageDeflateOptions);
cobraConnection.connect();
cobraConnection.setEventCallback(
[&cobraConnection, channel, path, &done]
(ix::CobraConnectionEventType eventType,
const std::string& errMsg,
const ix::WebSocketHttpHeaders& headers)
{
if (eventType == ix::CobraConnection_EventType_Open)
{
std::cout << "Handshake Headers:" << std::endl;
for (auto it : headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (eventType == ix::CobraConnection_EventType_Authenticated)
{
std::cout << "Authenticated" << std::endl;
std::string line;
std::ifstream f(path);
if (!f.is_open())
{
std::cerr << "Error while opening file: " << path << std::endl;
}
int n = 0;
while (getline(f, line))
{
Json::Value value;
Json::Reader reader;
reader.parse(line, value);
cobraConnection.publish(channel, value);
n++;
}
std::cerr << "#published messages: " << n << std::endl;
if (f.bad())
{
std::cerr << "Error while opening file: " << path << std::endl;
}
done = true;
}
else if (eventType == ix::CobraConnection_EventType_Error)
{
std::cerr << "Cobra Error received: " << errMsg << std::endl;
done = true;
}
else if (eventType == ix::CobraConnection_EventType_Closed)
{
std::cerr << "Cobra connection closed" << std::endl;
}
}
);
while (!done)
{
msleep(1);
}
std::cout << "Incoming bytes: " << incomingBytes << std::endl;
std::cout << "Outgoing bytes: " << outgoingBytes << std::endl;
return 0;
}

View File

@ -1,11 +0,0 @@
#!/bin/sh
endpoint="ws://127.0.0.1:8765"
endpoint="ws://127.0.0.1:5678"
appkey="appkey"
channel="foo"
rolename="a_role"
rolesecret="a_secret"
filename=${FILENAME:=events.jsonl}
build/cobra_publisher $endpoint $appkey $channel $rolename $rolesecret $filename

View File

@ -1,45 +0,0 @@
/*
* devnull_server.js
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
const WebSocket = require('ws');
let wss = new WebSocket.Server({ port: 5678, perMessageDeflate: true })
wss.on('connection', (ws) => {
let handshake = false
let authenticated = false
ws.on('message', (data) => {
console.log(data.toString('utf-8'))
if (!handshake) {
let response = {
"action": "auth/handshake/ok",
"body": {
"data": {
"nonce": "MTI0Njg4NTAyMjYxMzgxMzgzMg==",
"version": "0.0.24"
}
},
"id": 1
}
ws.send(JSON.stringify(response))
handshake = true
} else if (!authenticated) {
let response = {
"action": "auth/authenticate/ok",
"body": {},
"id": 2
}
ws.send(JSON.stringify(response))
authenticated = true
} else {
console.log(data)
}
});
})

View File

@ -1,43 +0,0 @@
#!/usr/bin/env python
import os
import json
import asyncio
import websockets
async def echo(websocket, path):
handshake = False
authenticated = False
async for message in websocket:
print(message)
if not handshake:
response = {
"action": "auth/handshake/ok",
"body": {
"data": {
"nonce": "MTI0Njg4NTAyMjYxMzgxMzgzMg==",
"version": "0.0.24"
}
},
"id": 1
}
await websocket.send(json.dumps(response))
handshake = True
elif not authenticated:
response = {
"action": "auth/authenticate/ok",
"body": {},
"id": 2
}
await websocket.send(json.dumps(response))
authenticated = True
asyncio.get_event_loop().run_until_complete(
websockets.serve(echo, 'localhost', 5678))
asyncio.get_event_loop().run_forever()

View File

@ -1,3 +0,0 @@
{"array":[1,2,3],"boolean":true,"color":"#82b92c","null":null,"number":123,"object":{"a":"b","c":"d","e":"f"},"string":"Foo"}
{"array":[1,2,3],"boolean":true,"color":"#82b92c","null":null,"number":123,"object":{"a":"b","c":"d","e":"f"},"string":"Bar"}
{"array":[1,2,3],"boolean":true,"color":"#82b92c","null":null,"number":123,"object":{"a":"b","c":"d","e":"f"},"string":"Baz"}

View File

@ -1,333 +0,0 @@
/// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/).
/// It is intended to be used with #include "json/json-forwards.h"
/// This header provides forward declaration for all JsonCpp types.
// //////////////////////////////////////////////////////////////////////
// Beginning of content of file: LICENSE
// //////////////////////////////////////////////////////////////////////
/*
The JsonCpp library's source code, including accompanying documentation,
tests and demonstration applications, are licensed under the following
conditions...
Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all
jurisdictions which recognize such a disclaimer. In such jurisdictions,
this software is released into the Public Domain.
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and
The JsonCpp Authors, and is released under the terms of the MIT License (see below).
In jurisdictions which recognize Public Domain property, the user of this
software may choose to accept it either as 1) Public Domain, 2) under the
conditions of the MIT License (see below), or 3) under the terms of dual
Public Domain/MIT License conditions described here, as they choose.
The MIT License is about as close to Public Domain as a license can get, and is
described in clear, concise terms at:
http://en.wikipedia.org/wiki/MIT_License
The full text of the MIT License follows:
========================================================================
Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
(END LICENSE TEXT)
The MIT license is compatible with both the GPL and commercial
software, affording one all of the rights of Public Domain with the
minor nuisance of being required to keep the above copyright notice
and license text in the source code. Note also that by accepting the
Public Domain "license" you can re-license your copy using whatever
license you like.
*/
// //////////////////////////////////////////////////////////////////////
// End of content of file: LICENSE
// //////////////////////////////////////////////////////////////////////
#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
# define JSON_FORWARD_AMALGATED_H_INCLUDED
/// If defined, indicates that the source file is amalgated
/// to prevent private header inclusion.
#define JSON_IS_AMALGAMATION
// //////////////////////////////////////////////////////////////////////
// Beginning of content of file: include/json/config.h
// //////////////////////////////////////////////////////////////////////
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
// Distributed under MIT license, or public domain if desired and
// recognized in your jurisdiction.
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
#ifndef JSON_CONFIG_H_INCLUDED
#define JSON_CONFIG_H_INCLUDED
#include <stddef.h>
#include <string> //typedef String
#include <stdint.h> //typedef int64_t, uint64_t
/// If defined, indicates that json library is embedded in CppTL library.
//# define JSON_IN_CPPTL 1
/// If defined, indicates that json may leverage CppTL library
//# define JSON_USE_CPPTL 1
/// If defined, indicates that cpptl vector based map should be used instead of
/// std::map
/// as Value container.
//# define JSON_USE_CPPTL_SMALLMAP 1
// If non-zero, the library uses exceptions to report bad input instead of C
// assertion macros. The default is to use exceptions.
#ifndef JSON_USE_EXCEPTION
#define JSON_USE_EXCEPTION 1
#endif
/// If defined, indicates that the source file is amalgated
/// to prevent private header inclusion.
/// Remarks: it is automatically defined in the generated amalgated header.
// #define JSON_IS_AMALGAMATION
#ifdef JSON_IN_CPPTL
#include <cpptl/config.h>
#ifndef JSON_USE_CPPTL
#define JSON_USE_CPPTL 1
#endif
#endif
#ifdef JSON_IN_CPPTL
#define JSON_API CPPTL_API
#elif defined(JSON_DLL_BUILD)
#if defined(_MSC_VER) || defined(__MINGW32__)
#define JSON_API __declspec(dllexport)
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
#endif // if defined(_MSC_VER)
#elif defined(JSON_DLL)
#if defined(_MSC_VER) || defined(__MINGW32__)
#define JSON_API __declspec(dllimport)
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
#endif // if defined(_MSC_VER)
#endif // ifdef JSON_IN_CPPTL
#if !defined(JSON_API)
#define JSON_API
#endif
// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
// integer
// Storages, and 64 bits integer support is disabled.
// #define JSON_NO_INT64 1
#if defined(_MSC_VER) // MSVC
# if _MSC_VER <= 1200 // MSVC 6
// Microsoft Visual Studio 6 only support conversion from __int64 to double
// (no conversion from unsigned __int64).
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
// characters in the debug information)
// All projects I've ever seen with VS6 were using this globally (not bothering
// with pragma push/pop).
# pragma warning(disable : 4786)
# endif // MSVC 6
# if _MSC_VER >= 1500 // MSVC 2008
/// Indicates that the following function is deprecated.
# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
# endif
#endif // defined(_MSC_VER)
// In c++11 the override keyword allows you to explicity define that a function
// is intended to override the base-class version. This makes the code more
// managable and fixes a set of common hard-to-find bugs.
#if __cplusplus >= 201103L
# define JSONCPP_OVERRIDE override
# define JSONCPP_NOEXCEPT noexcept
#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900
# define JSONCPP_OVERRIDE override
# define JSONCPP_NOEXCEPT throw()
#elif defined(_MSC_VER) && _MSC_VER >= 1900
# define JSONCPP_OVERRIDE override
# define JSONCPP_NOEXCEPT noexcept
#else
# define JSONCPP_OVERRIDE
# define JSONCPP_NOEXCEPT throw()
#endif
#ifndef JSON_HAS_RVALUE_REFERENCES
#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010
#define JSON_HAS_RVALUE_REFERENCES 1
#endif // MSVC >= 2010
#ifdef __clang__
#if __has_feature(cxx_rvalue_references)
#define JSON_HAS_RVALUE_REFERENCES 1
#endif // has_feature
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L)
#define JSON_HAS_RVALUE_REFERENCES 1
#endif // GXX_EXPERIMENTAL
#endif // __clang__ || __GNUC__
#endif // not defined JSON_HAS_RVALUE_REFERENCES
#ifndef JSON_HAS_RVALUE_REFERENCES
#define JSON_HAS_RVALUE_REFERENCES 0
#endif
#ifdef __clang__
# if __has_extension(attribute_deprecated_with_message)
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
# endif
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
# endif // GNUC version
#endif // __clang__ || __GNUC__
#if !defined(JSONCPP_DEPRECATED)
#define JSONCPP_DEPRECATED(message)
#endif // if !defined(JSONCPP_DEPRECATED)
#if __GNUC__ >= 6
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
#endif
#if !defined(JSON_IS_AMALGAMATION)
# include "version.h"
# if JSONCPP_USING_SECURE_MEMORY
# include "allocator.h" //typedef Allocator
# endif
#endif // if !defined(JSON_IS_AMALGAMATION)
namespace Json {
typedef int Int;
typedef unsigned int UInt;
#if defined(JSON_NO_INT64)
typedef int LargestInt;
typedef unsigned int LargestUInt;
#undef JSON_HAS_INT64
#else // if defined(JSON_NO_INT64)
// For Microsoft Visual use specific types as long long is not supported
#if defined(_MSC_VER) // Microsoft Visual Studio
typedef __int64 Int64;
typedef unsigned __int64 UInt64;
#else // if defined(_MSC_VER) // Other platforms, use long long
typedef int64_t Int64;
typedef uint64_t UInt64;
#endif // if defined(_MSC_VER)
typedef Int64 LargestInt;
typedef UInt64 LargestUInt;
#define JSON_HAS_INT64
#endif // if defined(JSON_NO_INT64)
#if JSONCPP_USING_SECURE_MEMORY
#define JSONCPP_STRING std::basic_string<char, std::char_traits<char>, Json::SecureAllocator<char> >
#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
#define JSONCPP_OSTREAM std::basic_ostream<char, std::char_traits<char>>
#define JSONCPP_ISTRINGSTREAM std::basic_istringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
#define JSONCPP_ISTREAM std::istream
#else
#define JSONCPP_STRING std::string
#define JSONCPP_OSTRINGSTREAM std::ostringstream
#define JSONCPP_OSTREAM std::ostream
#define JSONCPP_ISTRINGSTREAM std::istringstream
#define JSONCPP_ISTREAM std::istream
#endif // if JSONCPP_USING_SECURE_MEMORY
} // end namespace Json
#endif // JSON_CONFIG_H_INCLUDED
// //////////////////////////////////////////////////////////////////////
// End of content of file: include/json/config.h
// //////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////
// Beginning of content of file: include/json/forwards.h
// //////////////////////////////////////////////////////////////////////
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
// Distributed under MIT license, or public domain if desired and
// recognized in your jurisdiction.
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
#ifndef JSON_FORWARDS_H_INCLUDED
#define JSON_FORWARDS_H_INCLUDED
#if !defined(JSON_IS_AMALGAMATION)
#include "config.h"
#endif // if !defined(JSON_IS_AMALGAMATION)
namespace Json {
// writer.h
class FastWriter;
class StyledWriter;
// reader.h
class Reader;
// features.h
class Features;
// value.h
typedef unsigned int ArrayIndex;
class StaticString;
class Path;
class PathArgument;
class Value;
class ValueIteratorBase;
class ValueIterator;
class ValueConstIterator;
} // namespace Json
#endif // JSON_FORWARDS_H_INCLUDED
// //////////////////////////////////////////////////////////////////////
// End of content of file: include/json/forwards.h
// //////////////////////////////////////////////////////////////////////
#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (echo_server)
# There's -Weverything too for clang
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
include_directories(echo_server .)
add_executable(echo_server
echo_server.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(echo_server "-framework foundation" "-framework security")
endif()
target_link_libraries(echo_server ixwebsocket)
install(TARGETS echo_server DESTINATION bin)

View File

@ -1,68 +0,0 @@
/*
* echo_server.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <ixwebsocket/IXWebSocketServer.h>
int main(int argc, char** argv)
{
int port = 8080;
if (argc == 2)
{
std::stringstream ss;
ss << argv[1];
ss >> port;
}
ix::WebSocketServer server(port);
server.setOnConnectionCallback(
[&server](std::shared_ptr<ix::WebSocket> webSocket)
{
webSocket->setOnMessageCallback(
[webSocket, &server](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocket_MessageType_Open)
{
std::cerr << "New connection" << std::endl;
std::cerr << "Uri: " << openInfo.uri << std::endl;
std::cerr << "Headers:" << std::endl;
for (auto it : openInfo.headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
std::cerr << "Closed connection" << std::endl;
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
webSocket->send(str);
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
return 1;
}
server.start();
server.wait();
return 0;
}

View File

@ -1,27 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (ping_pong)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(ping_pong ping_pong.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(ping_pong "-framework foundation" "-framework security")
endif()
if (WIN32)
target_link_libraries(ping_pong wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
target_link_libraries(ping_pong ixwebsocket)
install(TARGETS ping_pong DESTINATION bin)

View File

@ -1,15 +0,0 @@
#!/bin/sh
#
# Author: Benjamin Sergeant
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
#
# 'manual' way of building. You can also use cmake.
g++ --std=c++11 \
../../ixwebsocket/IXSocket.cpp \
../../ixwebsocket/IXWebSocketTransport.cpp \
../../ixwebsocket/IXWebSocket.cpp \
-I ../.. \
cmd_websocket_chat.cpp \
-o cmd_websocket_chat

View File

@ -1,17 +0,0 @@
#!/usr/bin/env python
import asyncio
import websockets
async def hello(uri):
async with websockets.connect(uri) as websocket:
await websocket.send("Hello world!")
response = await websocket.recv()
print(response)
pong_waiter = await websocket.ping('coucou')
ret = await pong_waiter # only if you want to wait for the pong
print(ret)
asyncio.get_event_loop().run_until_complete(
hello('ws://localhost:5678'))

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python
import os
import asyncio
import websockets
async def echo(websocket, path):
async for message in websocket:
print(message)
await websocket.send(message)
if os.getenv('TEST_CLOSE'):
print('Closing')
# breakpoint()
await websocket.close(1001, 'close message')
# await websocket.close()
break
asyncio.get_event_loop().run_until_complete(
websockets.serve(echo, 'localhost', 5678))
asyncio.get_event_loop().run_forever()

View File

@ -1,9 +0,0 @@
#!/bin/sh
test -d build || {
mkdir -p build
cd build
cmake ..
}
(cd build ; make)
./build/ping_pong ws://localhost:5678

View File

@ -1,3 +0,0 @@
build
venv
node_modules

View File

@ -1,22 +0,0 @@
#
# Author: Benjamin Sergeant
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
#
cmake_minimum_required (VERSION 3.4.1)
project (ws_connect)
set (CMAKE_CXX_STANDARD 14)
option(USE_TLS "Add TLS support" ON)
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
add_executable(ws_connect ws_connect.cpp)
if (APPLE AND USE_TLS)
target_link_libraries(ws_connect "-framework foundation" "-framework security")
endif()
target_link_libraries(ws_connect ixwebsocket)
install(TARGETS ws_connect DESTINATION bin)

View File

@ -1,11 +0,0 @@
# Building
1. mkdir build
2. cd build
3. cmake ..
4. make
## Disable TLS
* Enable: `cmake -DUSE_TLS=OFF ..`
* Disable: `cmake -DUSE_TLS=ON ..`

View File

@ -1,25 +0,0 @@
#!/bin/sh
#
# Author: Benjamin Sergeant
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
#
# 'manual' way of building. You can also use cmake.
g++ --std=c++11 \
-DIXWEBSOCKET_USE_TLS \
-g \
../../ixwebsocket/IXEventFd.cpp \
../../ixwebsocket/IXSocket.cpp \
../../ixwebsocket/IXSetThreadName.cpp \
../../ixwebsocket/IXWebSocketTransport.cpp \
../../ixwebsocket/IXWebSocket.cpp \
../../ixwebsocket/IXDNSLookup.cpp \
../../ixwebsocket/IXSocketConnect.cpp \
../../ixwebsocket/IXSocketOpenSSL.cpp \
../../ixwebsocket/IXWebSocketPerMessageDeflate.cpp \
../../ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp \
-I ../.. \
ws_connect.cpp \
-o ws_connect \
-lcrypto -lssl -lz -lpthread

View File

@ -1,165 +0,0 @@
/*
* ws_connect.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXSocket.h>
using namespace ix;
namespace
{
void log(const std::string& msg)
{
std::cout << msg << std::endl;
}
class WebSocketConnect
{
public:
WebSocketConnect(const std::string& _url);
void subscribe(const std::string& channel);
void start();
void stop();
void sendMessage(const std::string& text);
private:
std::string _url;
ix::WebSocket _webSocket;
};
WebSocketConnect::WebSocketConnect(const std::string& url) :
_url(url)
{
;
}
void WebSocketConnect::stop()
{
_webSocket.stop();
}
void WebSocketConnect::start()
{
_webSocket.setUrl(_url);
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
true, false, false, 15, 15);
_webSocket.setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
std::stringstream ss;
log(std::string("Connecting to url: ") + _url);
_webSocket.setOnMessageCallback(
[this](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocket_MessageType_Open)
{
log("ws_connect: connected");
std::cout << "Uri: " << openInfo.uri << std::endl;
std::cout << "Handshake Headers:" << std::endl;
for (auto it : openInfo.headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_Close)
{
ss << "ws_connect: connection closed:";
ss << " code " << closeInfo.code;
ss << " reason " << closeInfo.reason << std::endl;
log(ss.str());
}
else if (messageType == ix::WebSocket_MessageType_Message)
{
ss << "ws_connect: received message: "
<< str;
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();
}
void WebSocketConnect::sendMessage(const std::string& text)
{
_webSocket.send(text);
}
void interactiveMain(const std::string& url)
{
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketConnect webSocketChat(url);
webSocketChat.start();
while (true)
{
std::string text;
std::cout << "> " << std::flush;
std::getline(std::cin, text);
if (text == "/stop")
{
std::cout << "Stopping connection..." << std::endl;
webSocketChat.stop();
continue;
}
if (text == "/start")
{
std::cout << "Starting connection..." << std::endl;
webSocketChat.start();
continue;
}
if (!std::cin)
{
break;
}
webSocketChat.sendMessage(text);
}
std::cout << std::endl;
webSocketChat.stop();
}
}
int main(int argc, char** argv)
{
if (argc != 2)
{
std::cerr << "Usage: ws_connect <url>" << std::endl;
return 1;
}
std::string url = argv[1];
Socket::init();
interactiveMain(url);
return 0;
}

View File

@ -6,14 +6,13 @@
#pragma once
#include <functional>
#include <atomic>
#include <functional>
namespace ix
{
using CancellationRequest = std::function<bool()>;
CancellationRequest makeCancellationRequestWithTimeout(int seconds,
std::atomic<bool>& requestInitCancellation);
}
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;
}
}

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

View File

@ -14,26 +14,13 @@ namespace ix
{
const int64_t DNSLookup::kDefaultWait = 10; // ms
std::atomic<uint64_t> DNSLookup::_nextId(0);
std::set<uint64_t> DNSLookup::_activeJobs;
std::mutex DNSLookup::_activeJobsMutex;
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
_hostname(hostname),
_port(port),
_wait(wait),
_res(nullptr),
_done(false),
_id(_nextId++)
_done(false)
{
}
DNSLookup::~DNSLookup()
{
// Remove this job from the active jobs list
std::unique_lock<std::mutex> lock(_activeJobsMutex);
_activeJobs.erase(_id);
setHostname(hostname);
}
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
@ -73,13 +60,13 @@ namespace ix
errMsg = "no error";
// Maybe a cancellation request got in before the background thread terminated ?
if (isCancellationRequested())
if (isCancellationRequested && isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
return getAddrInfo(_hostname, _port, errMsg);
return getAddrInfo(getHostname(), _port, errMsg);
}
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
@ -95,17 +82,14 @@ namespace ix
// if you need a second lookup.
}
// Record job in the active Job set
{
std::unique_lock<std::mutex> lock(_activeJobsMutex);
_activeJobs.insert(_id);
}
//
// Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
//
_thread = std::thread(&DNSLookup::run, this, _id, _hostname, _port);
auto ptr = shared_from_this();
std::weak_ptr<DNSLookup> self(ptr);
_thread = std::thread(&DNSLookup::run, this, self, getHostname(), _port);
_thread.detach();
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
@ -121,7 +105,7 @@ namespace ix
}
// Were we cancelled ?
if (isCancellationRequested())
if (isCancellationRequested && isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
@ -129,16 +113,17 @@ namespace ix
}
// Maybe a cancellation request got in before the bg terminated ?
if (isCancellationRequested())
if (isCancellationRequested && isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
return _res;
errMsg = getErrMsg();
return getRes();
}
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
void DNSLookup::run(std::weak_ptr<DNSLookup> self, const 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
@ -146,19 +131,50 @@ namespace ix
std::string errMsg;
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
// if this isn't an active job, and the control thread is gone
// there is not thing to do, and we don't want to touch the defunct
// object data structure such as _errMsg or _condition
std::unique_lock<std::mutex> lock(_activeJobsMutex);
if (_activeJobs.count(id) == 0)
if (self.lock())
{
return;
}
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
// Copy result into the member variables
_res = res;
_condition.notify_one();
_done = true;
}
}
void DNSLookup::setHostname(const std::string& hostname)
{
std::lock_guard<std::mutex> lock(_hostnameMutex);
_hostname = hostname;
}
const std::string& DNSLookup::getHostname()
{
std::lock_guard<std::mutex> lock(_hostnameMutex);
return _hostname;
}
void DNSLookup::setErrMsg(const std::string& errMsg)
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
_errMsg = errMsg;
_condition.notify_one();
_done = true;
}
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;
}
}

View File

@ -11,23 +11,22 @@
#pragma once
#include "IXCancellationRequest.h"
#include <string>
#include <thread>
#include <atomic>
#include <condition_variable>
#include <set>
#include <string>
#include <thread>
#include <memory>
struct addrinfo;
namespace ix
{
class DNSLookup {
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
{
public:
DNSLookup(const std::string& hostname,
int port,
int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup();
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup() = default;
struct addrinfo* resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
@ -43,24 +42,34 @@ namespace ix
int port,
std::string& errMsg);
void run(uint64_t id, const std::string& hostname, int port); // thread runner
void run(std::weak_ptr<DNSLookup> self, const std::string& hostname, int port); // thread runner
void setHostname(const std::string& hostname);
const std::string& getHostname();
void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg();
void setRes(struct addrinfo* addr);
struct addrinfo* getRes();
std::string _hostname;
std::mutex _hostnameMutex;
int _port;
int64_t _wait;
std::string _errMsg;
struct addrinfo* _res;
std::mutex _resMutex;
std::string _errMsg;
std::mutex _errMsgMutex;
std::atomic<bool> _done;
std::thread _thread;
std::condition_variable _condition;
std::mutex _conditionVariableMutex;
uint64_t _id;
static std::atomic<uint64_t> _nextId;
static std::set<uint64_t> _activeJobs;
static std::mutex _activeJobsMutex;
const static int64_t kDefaultWait;
};
}
} // namespace ix

View File

@ -1,82 +0,0 @@
/*
* IXEventFd.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
//
// Linux/Android has a special type of virtual files. select(2) will react
// when reading/writing to those files, unlike closing sockets.
//
// https://linux.die.net/man/2/eventfd
// http://www.sourcexr.com/articles/2013/10/26/lightweight-inter-process-signaling-with-eventfd
//
// eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4)
// is on Kernel 3.x
//
// cf Android/Kernel table here
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
//
#include "IXEventFd.h"
#ifdef __linux__
# include <sys/eventfd.h>
#endif
#ifndef _WIN32
#include <unistd.h> // for write
#endif
namespace ix
{
EventFd::EventFd() :
_eventfd(-1)
{
#ifdef __linux__
_eventfd = eventfd(0, 0);
#endif
}
EventFd::~EventFd()
{
#ifdef __linux__
::close(_eventfd);
#endif
}
bool EventFd::notify()
{
#if defined(__linux__)
if (_eventfd == -1) return false;
// select will wake up when a non-zero value is written to our eventfd
uint64_t value = 1;
// we should write 8 bytes for an uint64_t
return write(_eventfd, &value, sizeof(value)) == 8;
#else
return true;
#endif
}
bool EventFd::clear()
{
#if defined(__linux__)
if (_eventfd == -1) return false;
// 0 is a special value ; select will not wake up
uint64_t value = 0;
// we should write 8 bytes for an uint64_t
return write(_eventfd, &value, sizeof(value)) == 8;
#else
return true;
#endif
}
int EventFd::getFd()
{
return _eventfd;
}
}

View File

@ -1,23 +0,0 @@
/*
* IXEventFd.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
namespace ix
{
class EventFd {
public:
EventFd();
virtual ~EventFd();
bool notify();
bool clear();
int getFd();
private:
int _eventfd;
};
}

138
ixwebsocket/IXHttp.cpp Normal file
View File

@ -0,0 +1,138 @@
/*
* 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::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::shared_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::shared_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);
}
}

120
ixwebsocket/IXHttp.h Normal file
View File

@ -0,0 +1,120 @@
/*
* IXHttp.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXWebSocketHttpHeaders.h"
#include "IXProgressCallback.h"
#include <tuple>
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::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;
int connectTimeout;
int transferTimeout;
bool followRedirects;
int maxRedirects;
bool verbose;
bool compress;
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::shared_ptr<Socket> socket);
static bool sendResponse(HttpResponsePtr response, std::shared_ptr<Socket> socket);
static std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
static std::string trim(const std::string& str);
};
}

View File

@ -0,0 +1,571 @@
/*
* IXHttpClient.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttpClient.h"
#include "IXUrlParser.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXSocketFactory.h"
#include <sstream>
#include <iomanip>
#include <vector>
#include <cstring>
#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();
}
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)
{
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, errorMsg);
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: ixwebsocket" << "\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())
{
ss << "Content-Type: application/x-www-form-urlencoded" << "\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();
}
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);
}
}
}

View File

@ -0,0 +1,90 @@
/*
* IXHttpClient.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXHttp.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);
std::string serializeHttpParameters(const HttpParameters& httpParameters);
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::shared_ptr<Socket> _socket;
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
};
} // namespace ix

View File

@ -0,0 +1,158 @@
/*
* IXHttpServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttpServer.h"
#include "IXSocketConnect.h"
#include "IXSocketFactory.h"
#include "IXNetSystem.h"
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
namespace
{
std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
{
std::vector<uint8_t> memblock;
std::ifstream file(path);
if (!file.is_open()) return std::make_pair(false, memblock);
file.seekg(0, file.end);
std::streamoff size = file.tellg();
file.seekg(0, file.beg);
memblock.resize((size_t) size);
file.read((char*)&memblock.front(), static_cast<std::streamsize>(size));
return std::make_pair(true, memblock);
}
std::pair<bool, std::string> readAsString(const std::string& path)
{
auto res = load(path);
auto vec = res.second;
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
}
}
namespace ix
{
HttpServer::HttpServer(int port,
const std::string& host,
int backlog,
size_t maxConnections) : SocketServer(port, host, backlog, maxConnections),
_connectedClientsCount(0)
{
setDefaultConnectionCallback();
}
HttpServer::~HttpServer()
{
stop();
}
void HttpServer::stop()
{
stopAcceptingConnections();
// FIXME: cancelling / closing active clients ...
SocketServer::stop();
}
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
}
void HttpServer::handleConnection(
int fd,
std::shared_ptr<ConnectionState> connectionState)
{
_connectedClientsCount++;
std::string errorMsg;
auto socket = createSocket(fd, errorMsg);
// Set the socket to non blocking mode + other tweaks
SocketConnect::configure(fd);
auto ret = Http::parseRequest(socket);
// FIXME: handle errors in parseRequest
if (std::get<0>(ret))
{
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
if (!Http::sendResponse(response, socket))
{
logError("Cannot send response");
}
}
connectionState->setTerminated();
_connectedClientsCount--;
}
size_t HttpServer::getConnectedClientsCount()
{
return _connectedClientsCount;
}
void HttpServer::setDefaultConnectionCallback()
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
{
std::string uri(request->uri);
if (uri.empty() || uri == "/")
{
uri = "/index.html";
}
std::string path("." + uri);
auto res = readAsString(path);
bool found = res.first;
if (!found)
{
return std::make_shared<HttpResponse>(404, "Not Found",
HttpErrorCode::Ok,
WebSocketHttpHeaders(),
std::string());
}
std::string content = res.second;
// Log request
std::stringstream ss;
ss << request->method
<< " "
<< request->uri
<< " "
<< content.size();
logInfo(ss.str());
WebSocketHttpHeaders headers;
// FIXME: check extensions to set the content type
// headers["Content-Type"] = "application/octet-stream";
headers["Accept-Ranges"] = "none";
for (auto&& it : request->headers)
{
headers[it.first] = it.second;
}
return std::make_shared<HttpResponse>(200, "OK",
HttpErrorCode::Ok,
headers,
content);
}
);
}
}

View File

@ -0,0 +1,50 @@
/*
* IXHttpServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocketServer.h"
#include "IXWebSocket.h"
#include "IXHttp.h"
#include <functional>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix
{
class HttpServer final : public SocketServer
{
public:
using OnConnectionCallback =
std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
HttpServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections);
virtual ~HttpServer();
virtual void stop() final;
void setOnConnectionCallback(const OnConnectionCallback& callback);
private:
// Member variables
OnConnectionCallback _onConnectionCallback;
std::atomic<int> _connectedClientsCount;
// Methods
virtual void handleConnection(int fd,
std::shared_ptr<ConnectionState> connectionState) final;
virtual size_t getConnectedClientsCount() final;
void setDefaultConnectionCallback();
};
} // namespace ix

View File

@ -0,0 +1,39 @@
/*
* IXNetSystem.cpp
* Author: Korchynskyi Dmytro
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include "IXNetSystem.h"
namespace ix
{
bool initNetSystem()
{
#ifdef _WIN32
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
return err == 0;
#else
return true;
#endif
}
bool uninitNetSystem()
{
#ifdef _WIN32
int err = WSACleanup();
return err == 0;
#else
return true;
#endif
}
}

View File

@ -7,19 +7,25 @@
#pragma once
#ifdef _WIN32
# include <WS2tcpip.h>
# include <WinSock2.h>
# include <basetsd.h>
# include <io.h>
# include <ws2def.h>
#include <WS2tcpip.h>
#include <WinSock2.h>
#include <basetsd.h>
#include <io.h>
#include <ws2def.h>
#else
# include <arpa/inet.h>
# include <errno.h>
# include <netdb.h>
# include <netinet/tcp.h>
# include <sys/select.h>
# include <sys/socket.h>
# include <sys/stat.h>
# include <sys/time.h>
# include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#endif
namespace ix
{
bool initNetSystem();
bool uninitNetSystem();
} // namespace ix

View File

@ -0,0 +1,46 @@
/*
* IXSelectInterrupt.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSelectInterrupt.h"
namespace ix
{
SelectInterrupt::SelectInterrupt()
{
;
}
SelectInterrupt::~SelectInterrupt()
{
;
}
bool SelectInterrupt::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterrupt::notify(uint64_t /*value*/)
{
return true;
}
uint64_t SelectInterrupt::read()
{
return 0;
}
bool SelectInterrupt::clear()
{
return true;
}
int SelectInterrupt::getFd() const
{
return -1;
}
}

View File

@ -0,0 +1,27 @@
/*
* IXSelectInterrupt.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <stdint.h>
#include <string>
namespace ix
{
class SelectInterrupt
{
public:
SelectInterrupt();
virtual ~SelectInterrupt();
virtual bool init(std::string& errorMsg);
virtual bool notify(uint64_t value);
virtual bool clear();
virtual uint64_t read();
virtual int getFd() const;
};
} // namespace ix

View File

@ -0,0 +1,116 @@
/*
* IXSelectInterruptEventFd.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
//
// On Linux we use eventd to wake up select.
//
//
// Linux/Android has a special type of virtual files. select(2) will react
// when reading/writing to those files, unlike closing sockets.
//
// https://linux.die.net/man/2/eventfd
// http://www.sourcexr.com/articles/2013/10/26/lightweight-inter-process-signaling-with-eventfd
//
// eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4)
// is on Kernel 3.x
//
// cf Android/Kernel table here
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
//
// On macOS we use UNIX pipes to wake up select.
//
#include "IXSelectInterruptEventFd.h"
#include <sys/eventfd.h>
#include <unistd.h> // for write
#include <string.h> // for strerror
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sstream>
namespace ix
{
SelectInterruptEventFd::SelectInterruptEventFd()
{
_eventfd = -1;
}
SelectInterruptEventFd::~SelectInterruptEventFd()
{
::close(_eventfd);
}
bool SelectInterruptEventFd::init(std::string& errorMsg)
{
// calling init twice is a programming error
assert(_eventfd == -1);
_eventfd = eventfd(0, 0);
if (_eventfd < 0)
{
std::stringstream ss;
ss << "SelectInterruptEventFd::init() failed in eventfd()"
<< " : " << strerror(errno);
errorMsg = ss.str();
_eventfd = -1;
return false;
}
if (fcntl(_eventfd, F_SETFL, O_NONBLOCK) == -1)
{
std::stringstream ss;
ss << "SelectInterruptEventFd::init() failed in fcntl() call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_eventfd = -1;
return false;
}
return true;
}
bool SelectInterruptEventFd::notify(uint64_t value)
{
int fd = _eventfd;
if (fd == -1) return false;
// we should write 8 bytes for an uint64_t
return write(fd, &value, sizeof(value)) == 8;
}
// TODO: return max uint64_t for errors ?
uint64_t SelectInterruptEventFd::read()
{
int fd = _eventfd;
uint64_t value = 0;
::read(fd, &value, sizeof(value));
return value;
}
bool SelectInterruptEventFd::clear()
{
if (_eventfd == -1) return false;
// 0 is a special value ; select will not wake up
uint64_t value = 0;
// we should write 8 bytes for an uint64_t
return write(_eventfd, &value, sizeof(value)) == 8;
}
int SelectInterruptEventFd::getFd() const
{
return _eventfd;
}
}

View File

@ -0,0 +1,31 @@
/*
* IXSelectInterruptEventFd.h
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSelectInterrupt.h"
#include <stdint.h>
#include <string>
namespace ix
{
class SelectInterruptEventFd final : public SelectInterrupt
{
public:
SelectInterruptEventFd();
virtual ~SelectInterruptEventFd();
bool init(std::string& errorMsg) final;
bool notify(uint64_t value) final;
bool clear() final;
uint64_t read() final;
int getFd() const final;
private:
int _eventfd;
};
} // namespace ix

View File

@ -0,0 +1,25 @@
/*
* IXSelectInterruptFactory.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSelectInterruptFactory.h"
#if defined(__linux__) || defined(__APPLE__)
# include <ixwebsocket/IXSelectInterruptPipe.h>
#else
# include <ixwebsocket/IXSelectInterrupt.h>
#endif
namespace ix
{
std::shared_ptr<SelectInterrupt> createSelectInterrupt()
{
#if defined(__linux__) || defined(__APPLE__)
return std::make_shared<SelectInterruptPipe>();
#else
return std::make_shared<SelectInterrupt>();
#endif
}
}

View File

@ -0,0 +1,15 @@
/*
* IXSelectInterruptFactory.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <memory>
namespace ix
{
class SelectInterrupt;
std::shared_ptr<SelectInterrupt> createSelectInterrupt();
} // namespace ix

View File

@ -0,0 +1,146 @@
/*
* IXSelectInterruptPipe.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
//
// On macOS we use UNIX pipes to wake up select.
//
#include "IXSelectInterruptPipe.h"
#include <unistd.h> // for write
#include <string.h> // for strerror
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sstream>
namespace ix
{
// File descriptor at index 0 in _fildes is the read end of the pipe
// File descriptor at index 1 in _fildes is the write end of the pipe
const int SelectInterruptPipe::kPipeReadIndex = 0;
const int SelectInterruptPipe::kPipeWriteIndex = 1;
SelectInterruptPipe::SelectInterruptPipe()
{
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
}
SelectInterruptPipe::~SelectInterruptPipe()
{
::close(_fildes[kPipeReadIndex]);
::close(_fildes[kPipeWriteIndex]);
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
}
bool SelectInterruptPipe::init(std::string& errorMsg)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
// calling init twice is a programming error
assert(_fildes[kPipeReadIndex] == -1);
assert(_fildes[kPipeWriteIndex] == -1);
if (pipe(_fildes) < 0)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in pipe() call"
<< " : " << strerror(errno);
errorMsg = ss.str();
return false;
}
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
return false;
}
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
return false;
}
#ifdef F_SETNOSIGPIPE
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
return false;
}
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
{
std::stringstream ss;
ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call"
<< " : " << strerror(errno);
errorMsg = ss.str();
_fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1;
return false;
}
#endif
return true;
}
bool SelectInterruptPipe::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeWriteIndex];
if (fd == -1) return false;
// we should write 8 bytes for an uint64_t
return write(fd, &value, sizeof(value)) == 8;
}
// TODO: return max uint64_t for errors ?
uint64_t SelectInterruptPipe::read()
{
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeReadIndex];
uint64_t value = 0;
::read(fd, &value, sizeof(value));
return value;
}
bool SelectInterruptPipe::clear()
{
return true;
}
int SelectInterruptPipe::getFd() const
{
std::lock_guard<std::mutex> lock(_fildesMutex);
return _fildes[kPipeReadIndex];
}
}

View File

@ -0,0 +1,40 @@
/*
* IXSelectInterruptPipe.h
* Author: Benjamin Sergeant
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSelectInterrupt.h"
#include <mutex>
#include <stdint.h>
#include <string>
namespace ix
{
class SelectInterruptPipe final : public SelectInterrupt
{
public:
SelectInterruptPipe();
virtual ~SelectInterruptPipe();
bool init(std::string& errorMsg) final;
bool notify(uint64_t value) final;
bool clear() final;
uint64_t read() final;
int getFd() const final;
private:
// Store file descriptors used by the communication pipe. Communication
// happens between a control thread and a background thread, which is
// blocked on select.
int _fildes[2];
mutable std::mutex _fildesMutex;
// Used to identify the read/write idx
static const int kPipeReadIndex;
static const int kPipeWriteIndex;
};
} // namespace ix

View File

@ -10,4 +10,3 @@ namespace ix
{
void setThreadName(const std::string& name);
}

View File

@ -7,6 +7,8 @@
#include "IXSocket.h"
#include "IXSocketConnect.h"
#include "IXNetSystem.h"
#include "IXSelectInterrupt.h"
#include "IXSelectInterruptFactory.h"
#include <stdio.h>
#include <stdlib.h>
@ -17,17 +19,24 @@
#include <sys/types.h>
#include <algorithm>
#include <iostream>
#ifdef min
#undef min
#endif
namespace ix
{
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
const uint64_t Socket::kSendRequest = 1;
const uint64_t Socket::kCloseRequest = 2;
constexpr size_t Socket::kChunkSize;
Socket::Socket(int fd) :
_sockfd(fd)
_sockfd(fd),
_selectInterrupt(createSelectInterrupt())
{
;
}
Socket::~Socket()
@ -35,48 +44,102 @@ namespace ix
close();
}
void Socket::poll(const OnPollCallback& onPollCallback, int timeoutSecs)
PollResultType Socket::poll(int timeoutMs)
{
if (_sockfd == -1)
return isReadyToRead(timeoutMs);
}
PollResultType Socket::select(bool readyToRead, int timeoutMs)
{
fd_set rfds;
fd_set wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
fd_set* fds = (readyToRead) ? &rfds : & wfds;
if (_sockfd != -1)
{
onPollCallback(PollResultType_Error);
return;
FD_SET(_sockfd, fds);
}
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(_sockfd, &rfds);
#ifdef __linux__
FD_SET(_eventfd.getFd(), &rfds);
#endif
// File descriptor used to interrupt select when needed
int interruptFd = _selectInterrupt->getFd();
if (interruptFd != -1)
{
FD_SET(interruptFd, fds);
}
struct timeval timeout;
timeout.tv_sec = timeoutSecs;
timeout.tv_usec = 0;
timeout.tv_sec = timeoutMs / 1000;
timeout.tv_usec = 1000 * (timeoutMs % 1000);
// Compute the highest fd.
int sockfd = _sockfd;
int nfds = (std::max)(sockfd, _eventfd.getFd());
int ret = select(nfds + 1, &rfds, nullptr, nullptr,
(timeoutSecs < 0) ? nullptr : &timeout);
int nfds = (std::max)(sockfd, interruptFd);
PollResultType pollResult = PollResultType_ReadyForRead;
int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
(timeoutMs < 0) ? nullptr : &timeout);
PollResultType pollResult = PollResultType::ReadyForRead;
if (ret < 0)
{
pollResult = PollResultType_Error;
pollResult = PollResultType::Error;
}
else if (ret == 0)
{
pollResult = PollResultType_Timeout;
pollResult = PollResultType::Timeout;
}
else if (interruptFd != -1 && FD_ISSET(interruptFd, &rfds))
{
uint64_t value = _selectInterrupt->read();
if (value == kSendRequest)
{
pollResult = PollResultType::SendRequest;
}
else if (value == kCloseRequest)
{
pollResult = PollResultType::CloseRequest;
}
}
else if (sockfd != -1 && readyToRead && FD_ISSET(sockfd, &rfds))
{
pollResult = PollResultType::ReadyForRead;
}
else if (sockfd != -1 && !readyToRead && FD_ISSET(sockfd, &wfds))
{
pollResult = PollResultType::ReadyForWrite;
}
onPollCallback(pollResult);
return pollResult;
}
void Socket::wakeUpFromPoll()
PollResultType Socket::isReadyToRead(int timeoutMs)
{
// this will wake up the thread blocked on select, only needed on Linux
_eventfd.notify();
if (_sockfd == -1)
{
return PollResultType::Error;
}
bool readyToRead = true;
return select(readyToRead, timeoutMs);
}
PollResultType Socket::isReadyToWrite(int timeoutMs)
{
if (_sockfd == -1)
{
return PollResultType::Error;
}
bool readyToRead = false;
return select(readyToRead, timeoutMs);
}
// Wake up from poll/select by writing to the pipe which is watched by select
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode)
{
return _selectInterrupt->notify(wakeUpCode);
}
bool Socket::connect(const std::string& host,
@ -86,7 +149,7 @@ namespace ix
{
std::lock_guard<std::mutex> lock(_socketMutex);
if (!_eventfd.clear()) return false;
if (!_selectInterrupt->clear()) return false;
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
return _sockfd != -1;
@ -129,11 +192,27 @@ namespace ix
int Socket::getErrno()
{
int err;
#ifdef _WIN32
return WSAGetLastError();
err = WSAGetLastError();
#else
return errno;
err = errno;
#endif
return err;
}
bool Socket::isWaitNeeded()
{
int err = getErrno();
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
{
return true;
}
return false;
}
void Socket::closeSocket(int fd)
@ -145,91 +224,39 @@ namespace ix
#endif
}
bool Socket::init()
bool Socket::init(std::string& errorMsg)
{
#ifdef _WIN32
INT rc;
WSADATA wsaData;
rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
return rc != 0;
#else
return true;
#endif
}
void Socket::cleanup()
{
#ifdef _WIN32
WSACleanup();
#endif
}
bool Socket::readByte(void* buffer,
const CancellationRequest& isCancellationRequested)
{
while (true)
{
if (isCancellationRequested()) return false;
ssize_t ret;
ret = recv(buffer, 1);
// We read one byte, as needed, all good.
if (ret == 1)
{
return true;
}
// There is possibly something to be read, try again
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
{
// Wait with a timeout until something is written.
// This way we are not busy looping
fd_set rfds;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 1 * 1000; // 1ms timeout
FD_ZERO(&rfds);
FD_SET(_sockfd, &rfds);
if (select(_sockfd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
{
return false;
}
continue;
}
// There was an error during the read, abort
else
{
return false;
}
}
return _selectInterrupt->init(errorMsg);
}
bool Socket::writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested)
{
char* buffer = const_cast<char*>(str.c_str());
int len = (int) str.size();
while (true)
{
if (isCancellationRequested()) return false;
char* buffer = const_cast<char*>(str.c_str());
int len = (int) str.size();
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret = send(buffer, len);
// We wrote some bytes, as needed, all good.
if (ret > 0)
{
return ret == len;
if (ret == len)
{
return true;
}
else
{
buffer += ret;
len -= ret;
continue;
}
}
// There is possibly something to be write, try again
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
// There is possibly something to be writen, try again
else if (ret < 0 && Socket::isWaitNeeded())
{
continue;
}
@ -241,7 +268,41 @@ namespace ix
}
}
std::pair<bool, std::string> Socket::readLine(const CancellationRequest& isCancellationRequested)
bool Socket::readByte(void* buffer,
const CancellationRequest& isCancellationRequested)
{
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret;
ret = recv(buffer, 1);
// We read one byte, as needed, all good.
if (ret == 1)
{
return true;
}
// There is possibly something to be read, try again
else if (ret < 0 && Socket::isWaitNeeded())
{
// Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping
if (isReadyToRead(1) == PollResultType::Error)
{
return false;
}
}
// There was an error during the read, abort
else
{
return false;
}
}
}
std::pair<bool, std::string> Socket::readLine(
const CancellationRequest& isCancellationRequested)
{
char c;
std::string line;
@ -251,7 +312,8 @@ namespace ix
{
if (!readByte(&c, isCancellationRequested))
{
return std::make_pair(false, std::string());
// Return what we were able to read
return std::make_pair(false, line);
}
line += c;
@ -259,4 +321,51 @@ namespace ix
return std::make_pair(true, line);
}
std::pair<bool, std::string> Socket::readBytes(
size_t length,
const OnProgressCallback& onProgressCallback,
const CancellationRequest& isCancellationRequested)
{
if (_readBuffer.empty())
{
_readBuffer.resize(kChunkSize);
}
std::vector<uint8_t> output;
while (output.size() != length)
{
if (isCancellationRequested && isCancellationRequested())
{
return std::make_pair(false, std::string());
}
size_t size = std::min(kChunkSize, length - output.size());
ssize_t ret = recv((char*)&_readBuffer[0], size);
if (ret <= 0 && !Socket::isWaitNeeded())
{
// Error
return std::make_pair(false, std::string());
}
else
{
output.insert(output.end(),
_readBuffer.begin(),
_readBuffer.begin() + ret);
}
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
// Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping
if (isReadyToRead(1) == PollResultType::Error)
{
return std::make_pair(false, std::string());
}
}
return std::make_pair(true, std::string(output.begin(),
output.end()));
}
}

View File

@ -6,40 +6,62 @@
#pragma once
#include <string>
#include <functional>
#include <mutex>
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#ifdef _WIN32
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#undef EWOULDBLOCK
#undef EAGAIN
#undef EINPROGRESS
#undef EBADF
#undef EINVAL
// map to WSA error codes
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EAGAIN WSATRY_AGAIN
#define EINPROGRESS WSAEINPROGRESS
#define EBADF WSAEBADF
#define EINVAL WSAEINVAL
#endif
#include "IXEventFd.h"
#include "IXCancellationRequest.h"
#include "IXProgressCallback.h"
namespace ix
{
enum PollResultType
class SelectInterrupt;
enum class PollResultType
{
PollResultType_ReadyForRead = 0,
PollResultType_Timeout = 1,
PollResultType_Error = 2
ReadyForRead = 0,
ReadyForWrite = 1,
Timeout = 2,
Error = 3,
SendRequest = 4,
CloseRequest = 5
};
class Socket {
class Socket
{
public:
using OnPollCallback = std::function<void(PollResultType)>;
Socket(int fd = -1);
virtual ~Socket();
bool init(std::string& errorMsg);
void configure();
// Functions to check whether there is activity on the socket
PollResultType poll(int timeoutMs = kDefaultPollTimeout);
bool wakeUpFromPoll(uint64_t wakeUpCode);
virtual void poll(const OnPollCallback& onPollCallback,
int timeoutSecs = kDefaultPollTimeout);
virtual void wakeUpFromPoll();
PollResultType isReadyToWrite(int timeoutMs);
PollResultType isReadyToRead(int timeoutMs);
// Virtual methods
virtual bool connect(const std::string& url,
@ -54,25 +76,36 @@ namespace ix
// Blocking and cancellable versions, working with socket that can be set
// to non blocking mode. Used during HTTP upgrade.
bool readByte(void* buffer,
const CancellationRequest& isCancellationRequested);
bool writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested);
bool readByte(void* buffer, const CancellationRequest& isCancellationRequested);
bool writeBytes(const std::string& str, const CancellationRequest& isCancellationRequested);
std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
std::pair<bool, std::string> readBytes(size_t length,
const OnProgressCallback& onProgressCallback,
const CancellationRequest& isCancellationRequested);
static int getErrno();
static bool init(); // Required on Windows to initialize WinSocket
static void cleanup(); // Required on Windows to cleanup WinSocket
static bool isWaitNeeded();
static void closeSocket(int fd);
// Used as special codes for pipe communication
static const uint64_t kSendRequest;
static const uint64_t kCloseRequest;
protected:
void closeSocket(int fd);
std::atomic<int> _sockfd;
std::mutex _socketMutex;
EventFd _eventfd;
private:
PollResultType select(bool readyToRead, int timeoutMs);
static const int kDefaultPollTimeout;
static const int kDefaultPollNoTimeout;
// Buffer for reading from our socket. That buffer is never resized.
std::vector<uint8_t> _readBuffer;
static constexpr size_t kChunkSize = 1 << 15;
std::shared_ptr<SelectInterrupt> _selectInterrupt;
};
}
} // namespace ix

View File

@ -20,8 +20,6 @@
#include <unistd.h>
#include <stdint.h>
#include <iostream>
#include <errno.h>
#define socketerrno errno

View File

@ -6,17 +6,15 @@
#pragma once
#include "IXSocket.h"
#include "IXCancellationRequest.h"
#include <Security/Security.h>
#include "IXSocket.h"
#include <Security/SecureTransport.h>
#include <Security/Security.h>
#include <mutex>
namespace ix
{
class SocketAppleSSL : public Socket
class SocketAppleSSL final : public Socket
{
public:
SocketAppleSSL(int fd = -1);
@ -34,7 +32,7 @@ namespace ix
private:
SSLContextRef _sslContext;
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
};
}
} // namespace ix

View File

@ -7,6 +7,7 @@
#include "IXSocketConnect.h"
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include <string.h>
#include <fcntl.h>
@ -18,18 +19,6 @@
# include <linux/tcp.h>
#endif
namespace
{
void closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
}
namespace ix
{
//
@ -56,27 +45,30 @@ namespace ix
// block us for too long
SocketConnect::configure(fd);
if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
&& errno != EINPROGRESS)
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
if (res == -1 && !Socket::isWaitNeeded())
{
closeSocket(fd);
errMsg = strerror(errno);
errMsg = strerror(Socket::getErrno());
Socket::closeSocket(fd);
return -1;
}
for (;;)
{
if (isCancellationRequested()) // Must handle timeout as well
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{
closeSocket(fd);
Socket::closeSocket(fd);
errMsg = "Cancelled";
return -1;
}
// Use select to check the status of the new connection
// On Linux the timeout needs to be re-initialized everytime
// http://man7.org/linux/man-pages/man2/select.2.html
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
fd_set wfds;
fd_set efds;
@ -85,11 +77,13 @@ namespace ix
FD_ZERO(&efds);
FD_SET(fd, &efds);
if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
// Use select to check the status of the new connection
res = select(fd + 1, nullptr, &wfds, &efds, &timeout);
if (res < 0 && (Socket::getErrno() == EBADF || Socket::getErrno() == EINVAL))
{
closeSocket(fd);
errMsg = std::string("Connect error, select error: ") + strerror(errno);
Socket::closeSocket(fd);
errMsg = std::string("Connect error, select error: ") + strerror(Socket::getErrno());
return -1;
}
@ -110,7 +104,7 @@ namespace ix
optval != 0)
#endif
{
closeSocket(fd);
Socket::closeSocket(fd);
errMsg = strerror(optval);
return -1;
}
@ -121,7 +115,7 @@ namespace ix
}
}
closeSocket(fd);
Socket::closeSocket(fd);
errMsg = "connect timed out after 60 seconds";
return -1;
}
@ -134,8 +128,8 @@ namespace ix
//
// First do DNS resolution
//
DNSLookup dnsLookup(hostname, port);
struct addrinfo *res = dnsLookup.resolve(errMsg, isCancellationRequested);
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
struct addrinfo *res = dnsLookup->resolve(errMsg, isCancellationRequested);
if (res == nullptr)
{
return -1;

View File

@ -7,14 +7,14 @@
#pragma once
#include "IXCancellationRequest.h"
#include <string>
struct addrinfo;
namespace ix
{
class SocketConnect {
class SocketConnect
{
public:
static int connect(const std::string& hostname,
int port,
@ -24,9 +24,8 @@ namespace ix
static void configure(int sockfd);
private:
static int connectToAddress(const struct addrinfo *address,
static int connectToAddress(const struct addrinfo* address,
std::string& errMsg,
const CancellationRequest& isCancellationRequested);
};
}
} // namespace ix

View File

@ -0,0 +1,78 @@
/*
* IXSocketFactory.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketFactory.h"
#ifdef IXWEBSOCKET_USE_TLS
# ifdef IXWEBSOCKET_USE_MBED_TLS
# include <ixwebsocket/IXSocketMbedTLS.h>
# elif __APPLE__
# include <ixwebsocket/IXSocketAppleSSL.h>
# elif defined(_WIN32)
# include <ixwebsocket/IXSocketSChannel.h>
# elif defined(IXWEBSOCKET_USE_OPEN_SSL)
# include <ixwebsocket/IXSocketOpenSSL.h>
# endif
#else
#include <ixwebsocket/IXSocket.h>
#endif
namespace ix
{
std::shared_ptr<Socket> createSocket(bool tls,
std::string& errorMsg)
{
errorMsg.clear();
std::shared_ptr<Socket> socket;
if (!tls)
{
socket = std::make_shared<Socket>();
}
else
{
#ifdef IXWEBSOCKET_USE_TLS
# if defined(IXWEBSOCKET_USE_MBED_TLS)
socket = std::make_shared<SocketMbedTLS>();
# elif defined(__APPLE__)
socket = std::make_shared<SocketAppleSSL>();
# elif defined(_WIN32)
socket = std::make_shared<SocketSChannel>();
# else
socket = std::make_shared<SocketOpenSSL>();
# endif
#else
errorMsg = "TLS support is not enabled on this platform.";
return nullptr;
#endif
}
if (!socket->init(errorMsg))
{
socket.reset();
}
return socket;
}
std::shared_ptr<Socket> createSocket(int fd,
std::string& errorMsg)
{
errorMsg.clear();
std::shared_ptr<Socket> socket = std::make_shared<Socket>(fd);
if (!socket->init(errorMsg))
{
socket.reset();
}
return socket;
}
}

View File

@ -0,0 +1,19 @@
/*
* IXSocketFactory.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <memory>
#include <string>
namespace ix
{
class Socket;
std::shared_ptr<Socket> createSocket(bool tls, std::string& errorMsg);
std::shared_ptr<Socket> createSocket(int fd, std::string& errorMsg);
} // namespace ix

View File

@ -0,0 +1,178 @@
/*
* IXSocketMbedTLS.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*
* Some code taken from
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
* and mini_client.c example from mbedtls
*/
#include "IXSocketMbedTLS.h"
#include "IXSocketConnect.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include <string.h>
namespace ix
{
SocketMbedTLS::~SocketMbedTLS()
{
close();
}
bool SocketMbedTLS::init(const std::string& host, std::string& errMsg)
{
std::lock_guard<std::mutex> lock(_mutex);
mbedtls_ssl_init(&_ssl);
mbedtls_ssl_config_init(&_conf);
mbedtls_ctr_drbg_init(&_ctr_drbg);
const char *pers = "IXSocketMbedTLS";
mbedtls_entropy_init(&_entropy);
if (mbedtls_ctr_drbg_seed(&_ctr_drbg,
mbedtls_entropy_func,
&_entropy,
(const unsigned char *) pers,
strlen(pers)) != 0)
{
errMsg = "Setting entropy seed failed";
return false;
}
if (mbedtls_ssl_config_defaults(&_conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT ) != 0)
{
errMsg = "Setting config default failed";
return false;
}
mbedtls_ssl_conf_rng(&_conf, mbedtls_ctr_drbg_random, &_ctr_drbg);
// FIXME: cert verification is disabled
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
{
errMsg = "SSL setup failed";
return false;
}
if (mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
{
errMsg = "SNI setup failed";
return false;
}
return true;
}
bool SocketMbedTLS::connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
{
std::lock_guard<std::mutex> lock(_mutex);
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;
}
if (!init(host, errMsg))
{
close();
return false;
}
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
int res;
do
{
std::lock_guard<std::mutex> lock(_mutex);
res = mbedtls_ssl_handshake(&_ssl);
}
while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
if (res != 0)
{
char buf[256];
mbedtls_strerror(res, buf, sizeof(buf));
errMsg = "error in handshake : ";
errMsg += buf;
close();
return false;
}
return true;
}
void SocketMbedTLS::close()
{
std::lock_guard<std::mutex> lock(_mutex);
mbedtls_ssl_free(&_ssl);
mbedtls_ssl_config_free(&_conf);
mbedtls_ctr_drbg_free(&_ctr_drbg);
mbedtls_entropy_free(&_entropy);
Socket::close();
}
ssize_t SocketMbedTLS::send(char* buf, size_t nbyte)
{
ssize_t sent = 0;
while (nbyte > 0)
{
std::lock_guard<std::mutex> lock(_mutex);
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
if (res > 0) {
nbyte -= res;
sent += res;
} else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) {
errno = EWOULDBLOCK;
return -1;
} else {
return -1;
}
}
return sent;
}
ssize_t SocketMbedTLS::send(const std::string& buffer)
{
return send((char*)&buffer[0], buffer.size());
}
ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte)
{
while (true)
{
std::lock_guard<std::mutex> lock(_mutex);
ssize_t res = mbedtls_ssl_read(&_ssl, (unsigned char*) buf, (int) nbyte);
if (res > 0)
{
return res;
}
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{
errno = EWOULDBLOCK;
}
return -1;
}
}
}

View File

@ -0,0 +1,47 @@
/*
* IXSocketMbedTLS.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocket.h"
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/debug.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/net.h>
#include <mbedtls/platform.h>
#include <mutex>
namespace ix
{
class SocketMbedTLS final : public Socket
{
public:
SocketMbedTLS() = default;
~SocketMbedTLS();
virtual bool connect(const std::string& host,
int port,
std::string& errMsg,
const CancellationRequest& isCancellationRequested) final;
virtual void close() final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:
mbedtls_ssl_context _ssl;
mbedtls_ssl_config _conf;
mbedtls_entropy_context _entropy;
mbedtls_ctr_drbg_context _ctr_drbg;
std::mutex _mutex;
bool init(const std::string& host, std::string& errMsg);
};
} // namespace ix

View File

@ -10,7 +10,6 @@
#include "IXSocketConnect.h"
#include <cassert>
#include <iostream>
#include <openssl/x509v3.h>
@ -21,6 +20,7 @@
namespace ix
{
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
std::once_flag SocketOpenSSL::_openSSLInitFlag;
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
_ssl_connection(nullptr),
@ -240,7 +240,6 @@ namespace ix
}
}
// No wait support
bool SocketOpenSSL::connect(const std::string& host,
int port,
std::string& errMsg,
@ -341,7 +340,7 @@ namespace ix
ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, write_result);
int reason = SSL_get_error(_ssl_connection, (int) write_result);
if (reason == SSL_ERROR_NONE) {
nbyte -= write_result;
@ -361,7 +360,6 @@ namespace ix
return send((char*)&buffer[0], buffer.size());
}
// No wait support
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
{
while (true)
@ -381,7 +379,7 @@ namespace ix
return read_result;
}
int reason = SSL_get_error(_ssl_connection, read_result);
int reason = SSL_get_error(_ssl_connection, (int) read_result);
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{

View File

@ -6,20 +6,18 @@
#pragma once
#include "IXSocket.h"
#include "IXCancellationRequest.h"
#include "IXSocket.h"
#include <mutex>
#include <openssl/bio.h>
#include <openssl/hmac.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include <openssl/ssl.h>
#include <mutex>
namespace ix
{
class SocketOpenSSL : public Socket
class SocketOpenSSL final : public Socket
{
public:
SocketOpenSSL(int fd = -1);
@ -40,18 +38,16 @@ namespace ix
std::string getSSLError(int ret);
SSL_CTX* openSSLCreateContext(std::string& errMsg);
bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
bool openSSLCheckServerCert(SSL *ssl,
const std::string& hostname,
std::string& errMsg);
bool checkHost(const std::string& host, const char *pattern);
bool openSSLCheckServerCert(SSL* ssl, const std::string& hostname, std::string& errMsg);
bool checkHost(const std::string& host, const char* pattern);
SSL* _ssl_connection;
SSL_CTX* _ssl_context;
const SSL_METHOD* _ssl_method;
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
std::once_flag _openSSLInitFlag;
static std::once_flag _openSSLInitFlag;
static std::atomic<bool> _openSSLInitializationSuccessful;
};
}
} // namespace ix

View File

@ -18,7 +18,6 @@
# include <ws2def.h>
# include <WS2tcpip.h>
# include <schannel.h>
# include <sslsock.h>
# include <io.h>
#define WIN32_LEAN_AND_MEAN
@ -75,7 +74,7 @@ namespace ix
int port,
std::string& errMsg)
{
return Socket::connect(host, port, errMsg);
return Socket::connect(host, port, errMsg, nullptr);
}
@ -89,17 +88,17 @@ namespace ix
Socket::close();
}
int SocketSChannel::send(char* buf, size_t nbyte)
ssize_t SocketSChannel::send(char* buf, size_t nbyte)
{
return Socket::send(buf, nbyte);
}
int SocketSChannel::send(const std::string& buffer)
ssize_t SocketSChannel::send(const std::string& buffer)
{
return Socket::send(buffer);
}
int SocketSChannel::recv(void* buf, size_t nbyte)
ssize_t SocketSChannel::recv(void* buf, size_t nbyte)
{
return Socket::recv(buf, nbyte);
}

View File

@ -10,25 +10,23 @@
namespace ix
{
class SocketSChannel : public Socket
class SocketSChannel final : public Socket
{
public:
SocketSChannel();
~SocketSChannel();
virtual bool connect(const std::string& host,
int port,
std::string& errMsg) final;
virtual bool connect(const std::string& host, int port, std::string& errMsg) final;
virtual void close() final;
// The important override
virtual void secureSocket() final;
virtual int send(char* buffer, size_t length) final;
virtual int send(const std::string& buffer) final;
virtual int recv(void* buffer, size_t length) final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:
};
}
} // namespace ix

View File

@ -11,8 +11,8 @@
#include <iostream>
#include <sstream>
#include <future>
#include <string.h>
#include <assert.h>
namespace ix
{
@ -29,7 +29,10 @@ namespace ix
_host(host),
_backlog(backlog),
_maxConnections(maxConnections),
_stop(false)
_serverFd(-1),
_stop(false),
_stopGc(false),
_connectionStateFactory(&ConnectionState::createConnectionState)
{
}
@ -75,7 +78,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
@ -99,7 +102,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
@ -113,7 +116,7 @@ namespace ix
<< "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
::close(_serverFd);
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
@ -122,9 +125,15 @@ namespace ix
void SocketServer::start()
{
if (_thread.joinable()) return; // we've already been started
if (!_thread.joinable())
{
_thread = std::thread(&SocketServer::run, this);
}
_thread = std::thread(&SocketServer::run, this);
if (!_gcThread.joinable())
{
_gcThread = std::thread(&SocketServer::runGC, this);
}
}
void SocketServer::wait()
@ -133,16 +142,66 @@ namespace ix
_conditionVariable.wait(lock);
}
void SocketServer::stopAcceptingConnections()
{
_stop = true;
}
void SocketServer::stop()
{
if (!_thread.joinable()) return; // nothing to do
// Stop accepting connections, and close the 'accept' thread
if (_thread.joinable())
{
_stop = true;
_thread.join();
_stop = false;
}
_stop = true;
_thread.join();
_stop = false;
// Join all threads and make sure that all connections are terminated
if (_gcThread.joinable())
{
_stopGc = true;
_gcThread.join();
_stopGc = false;
}
_conditionVariable.notify_one();
::close(_serverFd);
Socket::closeSocket(_serverFd);
}
void SocketServer::setConnectionStateFactory(
const ConnectionStateFactory& connectionStateFactory)
{
_connectionStateFactory = connectionStateFactory;
}
//
// join the threads for connections that have been closed
//
// When a connection is closed by a client, the connection state terminated
// field becomes true, and we can use that to know that we can join that thread
// and remove it from our _connectionsThreads data structure (a list).
//
void SocketServer::closeTerminatedThreads()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
auto it = _connectionsThreads.begin();
auto itEnd = _connectionsThreads.end();
while (it != itEnd)
{
auto& connectionState = it->first;
auto& thread = it->second;
if (!connectionState->isTerminated())
{
++it;
continue;
}
if (thread.joinable()) thread.join();
it = _connectionsThreads.erase(it);
}
}
void SocketServer::run()
@ -150,9 +209,6 @@ namespace ix
// Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd);
// Return value of std::async, ignored
std::future<void> f;
for (;;)
{
if (_stop) return;
@ -185,17 +241,18 @@ namespace ix
// Accept a connection.
struct sockaddr_in client; // client address information
int clientFd; // socket connected to client
socklen_t addressLen = sizeof(socklen_t);
socklen_t addressLen = sizeof(client);
memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
{
if (Socket::getErrno() != EWOULDBLOCK)
if (!Socket::isWaitNeeded())
{
// FIXME: that error should be propagated
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error accepting connection: "
<< strerror(Socket::getErrno());
<< err << ", " << strerror(err);
logError(ss.str());
}
continue;
@ -209,19 +266,52 @@ namespace ix
<< "Not accepting connection";
logError(ss.str());
::close(clientFd);
Socket::closeSocket(clientFd);
continue;
}
std::shared_ptr<ConnectionState> connectionState;
if (_connectionStateFactory)
{
connectionState = _connectionStateFactory();
}
if (_stop) return;
// Launch the handleConnection work asynchronously in its own thread.
//
// the destructor of a future returned by std::async blocks,
// so we need to declare it outside of this loop
f = std::async(std::launch::async,
&SocketServer::handleConnection,
this,
clientFd);
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
_connectionsThreads.push_back(std::make_pair(
connectionState,
std::thread(&SocketServer::handleConnection,
this,
clientFd,
connectionState)));
}
}
size_t SocketServer::getConnectionsThreadsCount()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
return _connectionsThreads.size();
}
void SocketServer::runGC()
{
for (;;)
{
// Garbage collection to shutdown/join threads for closed connections.
closeTerminatedThreads();
// We quit this thread if all connections are closed and we received
// a stop request by setting _stopGc to true.
if (_stopGc && getConnectionsThreadsCount() == 0)
{
break;
}
// Sleep a little bit then keep cleaning up
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}

View File

@ -6,20 +6,30 @@
#pragma once
#include <utility> // pair
#include <string>
#include <set>
#include <thread>
#include <mutex>
#include <functional>
#include <memory>
#include "IXConnectionState.h"
#include <atomic>
#include <condition_variable>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix
{
class SocketServer {
class SocketServer
{
public:
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
// Each connection is handled by its own worker thread.
// We use a list as we only care about remove and append operations.
using ConnectionThreads =
std::list<std::pair<std::shared_ptr<ConnectionState>, std::thread>>;
SocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
@ -27,6 +37,11 @@ namespace ix
virtual ~SocketServer();
virtual void stop();
// It is possible to override ConnectionState through inheritance
// this method allows user to change the factory by returning an object
// that inherits from ConnectionState but has its own methods.
void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory);
const static int kDefaultPort;
const static std::string kDefaultHost;
const static int kDefaultTcpBacklog;
@ -37,11 +52,12 @@ namespace ix
void wait();
protected:
// Logging
void logError(const std::string& str);
void logInfo(const std::string& str);
void stopAcceptingConnections();
private:
// Member variables
int _port;
@ -54,15 +70,33 @@ namespace ix
std::mutex _logMutex;
// background thread to wait for incoming connections
std::atomic<bool> _stop;
std::thread _thread;
void run();
// background thread to cleanup (join) terminated threads
std::atomic<bool> _stopGc;
std::thread _gcThread;
void runGC();
// the list of (connectionState, threads) for each connections
ConnectionThreads _connectionsThreads;
std::mutex _connectionsThreadsMutex;
// used to have the main control thread for a server
// wait for a 'terminate' notification without busy polling
std::condition_variable _conditionVariable;
std::mutex _conditionVariableMutex;
// Methods
void run();
virtual void handleConnection(int fd) = 0;
// the factory to create ConnectionState objects
ConnectionStateFactory _connectionStateFactory;
virtual void handleConnection(int fd, std::shared_ptr<ConnectionState> connectionState) = 0;
virtual size_t getConnectedClientsCount() = 0;
// Returns true if all connection threads are joined
void closeTerminatedThreads();
size_t getConnectionsThreadsCount();
};
}
} // namespace ix

View File

@ -0,0 +1,67 @@
/*
* IXUrlParser.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXUrlParser.h"
#include "LUrlParser.h"
namespace ix
{
bool UrlParser::parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port)
{
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
if (!res.IsValid())
{
return false;
}
protocol = res.m_Scheme;
host = res.m_Host;
path = res.m_Path;
query = res.m_Query;
if (!res.GetPort(&port))
{
if (protocol == "ws" || protocol == "http")
{
port = 80;
}
else if (protocol == "wss" || protocol == "https")
{
port = 443;
}
else
{
// Invalid protocol. Should be caught by regex check
// but this missing branch trigger cpplint linter.
return false;
}
}
if (path.empty())
{
path = "/";
}
else if (path[0] != '/')
{
path = '/' + path;
}
if (!query.empty())
{
path += "?";
path += query;
}
return true;
}
}

23
ixwebsocket/IXUrlParser.h Normal file
View File

@ -0,0 +1,23 @@
/*
* IXUrlParser.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
class UrlParser
{
public:
static bool parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port);
};
} // namespace ix

View File

@ -8,22 +8,26 @@
#include "IXSetThreadName.h"
#include "IXWebSocketHandshake.h"
#include <iostream>
#include <cmath>
#include <cassert>
namespace
{
uint64_t calculateRetryWaitMilliseconds(uint64_t retry_count)
uint64_t calculateRetryWaitMilliseconds(uint32_t retry_count)
{
// This will overflow quite fast for large value of retry_count
// and will become 0, in which case the wait time will be none
// and we'll be constantly retrying to connect.
uint64_t wait_time = ((uint64_t) std::pow(2, retry_count) * 100L);
uint64_t wait_time;
// cap the wait time to 10s, or to retry_count == 10 for which wait_time > 10s
uint64_t tenSeconds = 10 * 1000;
return (wait_time > tenSeconds || retry_count > 10) ? tenSeconds : wait_time;
if (retry_count <= 6)
{
// max wait_time is 6400 ms (2 ^ 6 = 64)
wait_time = ((uint64_t)std::pow(2, retry_count) * 100L);
}
else
{
wait_time = 10 * 1000; // 10 sec
}
return wait_time;
}
}
@ -31,21 +35,27 @@ namespace ix
{
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
const int WebSocket::kDefaultHeartBeatPeriod(-1);
const int WebSocket::kDefaultPingIntervalSecs(-1);
const int WebSocket::kDefaultPingTimeoutSecs(-1);
const bool WebSocket::kDefaultEnablePong(true);
WebSocket::WebSocket() :
_onMessageCallback(OnMessageCallback()),
_stop(false),
_automaticReconnection(true),
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
_heartBeatPeriod(kDefaultHeartBeatPeriod)
_enablePong(kDefaultEnablePong),
_pingIntervalSecs(kDefaultPingIntervalSecs),
_pingTimeoutSecs(kDefaultPingTimeoutSecs)
{
_ws.setOnCloseCallback(
[this](uint16_t code, const std::string& reason, size_t wireSize)
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote)
{
_onMessageCallback(WebSocket_MessageType_Close, "", wireSize,
WebSocketErrorInfo(), WebSocketOpenInfo(),
WebSocketCloseInfo(code, reason));
_onMessageCallback(
std::make_shared<WebSocketMessage>(
WebSocketMessageType::Close, "", wireSize,
WebSocketErrorInfo(), WebSocketOpenInfo(),
WebSocketCloseInfo(code, reason, remote)));
}
);
}
@ -79,16 +89,59 @@ namespace ix
return _perMessageDeflateOptions;
}
void WebSocket::setHeartBeatPeriod(int hearBeatPeriod)
void WebSocket::setHeartBeatPeriod(int heartBeatPeriodSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_heartBeatPeriod = hearBeatPeriod;
_pingIntervalSecs = heartBeatPeriodSecs;
}
int WebSocket::getHeartBeatPeriod() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _heartBeatPeriod;
return _pingIntervalSecs;
}
void WebSocket::setPingInterval(int pingIntervalSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingIntervalSecs = pingIntervalSecs;
}
int WebSocket::getPingInterval() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingIntervalSecs;
}
void WebSocket::setPingTimeout(int pingTimeoutSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingTimeoutSecs = pingTimeoutSecs;
}
int WebSocket::getPingTimeout() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingTimeoutSecs;
}
void WebSocket::enablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = true;
}
void WebSocket::disablePong()
{
std::lock_guard<std::mutex> lock(_configMutex);
_enablePong = false;
}
void WebSocket::disablePerMessageDeflate()
{
std::lock_guard<std::mutex> lock(_configMutex);
WebSocketPerMessageDeflateOptions perMessageDeflateOptions(false);
_perMessageDeflateOptions = perMessageDeflateOptions;
}
void WebSocket::start()
@ -98,26 +151,19 @@ namespace ix
_thread = std::thread(&WebSocket::run, this);
}
void WebSocket::stop()
void WebSocket::stop(uint16_t code,
const std::string& reason)
{
bool automaticReconnection = _automaticReconnection;
close(code, reason);
// This value needs to be forced when shutting down, it is restored later
_automaticReconnection = false;
close();
if (!_thread.joinable())
if (_thread.joinable())
{
_automaticReconnection = automaticReconnection;
return;
// wait until working thread will exit
// it will exit after close operation is finished
_stop = true;
_thread.join();
_stop = false;
}
_stop = true;
_thread.join();
_stop = false;
_automaticReconnection = automaticReconnection;
}
WebSocketInitResult WebSocket::connect(int timeoutSecs)
@ -125,7 +171,9 @@ namespace ix
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions,
_heartBeatPeriod);
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
}
WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
@ -134,10 +182,12 @@ namespace ix
return status;
}
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo());
_onMessageCallback(
std::make_shared<WebSocketMessage>(
WebSocketMessageType::Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo()));
return status;
}
@ -145,7 +195,10 @@ namespace ix
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions, _heartBeatPeriod);
_ws.configure(_perMessageDeflateOptions,
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
}
WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
@ -154,82 +207,116 @@ namespace ix
return status;
}
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo());
_onMessageCallback(
std::make_shared<WebSocketMessage>(
WebSocketMessageType::Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo()));
return status;
}
bool WebSocket::isConnected() const
{
return getReadyState() == WebSocket_ReadyState_Open;
return getReadyState() == ReadyState::Open;
}
bool WebSocket::isClosing() const
{
return getReadyState() == WebSocket_ReadyState_Closing;
return getReadyState() == ReadyState::Closing;
}
void WebSocket::close()
void WebSocket::close(uint16_t code,
const std::string& reason)
{
_ws.close();
_ws.close(code, reason);
}
void WebSocket::reconnectPerpetuallyIfDisconnected()
void WebSocket::checkConnection(bool firstConnectionAttempt)
{
uint64_t retries = 0;
WebSocketErrorInfo connectErr;
ix::WebSocketInitResult status;
using millis = std::chrono::duration<double, std::milli>;
millis duration;
uint32_t retries = 0;
millis duration(0);
// Try to connect perpertually
while (true)
{
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
if (isConnected() || isClosing() || _stop)
{
break;
}
status = connect(_handshakeTimeoutSecs);
if (!status.success && !_stop)
if (!firstConnectionAttempt && !_automaticReconnection)
{
duration = millis(calculateRetryWaitMilliseconds(retries++));
// Do not attempt to reconnect
break;
}
connectErr.retries = retries;
connectErr.wait_time = duration.count();
connectErr.reason = status.errorStr;
connectErr.http_status = status.http_status;
_onMessageCallback(WebSocket_MessageType_Error, "", 0,
connectErr, WebSocketOpenInfo(),
WebSocketCloseInfo());
firstConnectionAttempt = false;
// Only sleep if we are retrying
if (duration.count() > 0)
{
// to do: make sleeping conditional
std::this_thread::sleep_for(duration);
}
// Try to connect synchronously
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
if (!status.success)
{
WebSocketErrorInfo connectErr;
if (_automaticReconnection)
{
duration = millis(calculateRetryWaitMilliseconds(retries++));
connectErr.wait_time = duration.count();
connectErr.retries = retries;
}
connectErr.reason = status.errorStr;
connectErr.http_status = status.http_status;
_onMessageCallback(
std::make_shared<WebSocketMessage>(
WebSocketMessageType::Error, "", 0,
connectErr, WebSocketOpenInfo(),
WebSocketCloseInfo()));
}
}
}
void WebSocket::run()
{
setThreadName(_url);
setThreadName(getUrl());
bool firstConnectionAttempt = true;
while (true)
{
if (_stop) return;
// 1. Make sure we are always connected
reconnectPerpetuallyIfDisconnected();
checkConnection(firstConnectionAttempt);
if (_stop) return;
firstConnectionAttempt = false;
// if here we are closed then checkConnection was not able to connect
if (getReadyState() == ReadyState::Closed)
{
break;
}
// We can avoid to poll if we want to stop and are not closing
if (_stop && !isClosing()) break;
// 2. Poll to see if there's any new data available
_ws.poll();
if (_stop) return;
WebSocketTransport::PollResult pollResult = _ws.poll();
// 3. Dispatch the incoming messages
_ws.dispatch(
pollResult,
[this](const std::string& msg,
size_t wireSize,
bool decompressionError,
@ -238,36 +325,41 @@ namespace ix
WebSocketMessageType webSocketMessageType;
switch (messageKind)
{
case WebSocketTransport::MSG:
case WebSocketTransport::MessageKind::MSG_TEXT:
case WebSocketTransport::MessageKind::MSG_BINARY:
{
webSocketMessageType = WebSocket_MessageType_Message;
webSocketMessageType = WebSocketMessageType::Message;
} break;
case WebSocketTransport::PING:
case WebSocketTransport::MessageKind::PING:
{
webSocketMessageType = WebSocket_MessageType_Ping;
webSocketMessageType = WebSocketMessageType::Ping;
} break;
case WebSocketTransport::PONG:
case WebSocketTransport::MessageKind::PONG:
{
webSocketMessageType = WebSocket_MessageType_Pong;
webSocketMessageType = WebSocketMessageType::Pong;
} break;
case WebSocketTransport::MessageKind::FRAGMENT:
{
webSocketMessageType = WebSocketMessageType::Fragment;
} break;
}
WebSocketErrorInfo webSocketErrorInfo;
webSocketErrorInfo.decompressionError = decompressionError;
_onMessageCallback(webSocketMessageType, msg, wireSize,
webSocketErrorInfo, WebSocketOpenInfo(),
WebSocketCloseInfo());
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
_onMessageCallback(
std::make_shared<WebSocketMessage>(
webSocketMessageType, msg, wireSize,
webSocketErrorInfo, WebSocketOpenInfo(),
WebSocketCloseInfo(), binary));
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
});
// 4. In blocking mode, getting out of this function is triggered by
// an explicit disconnection from the callback, or by the remote end
// closing the connection, ie isConnected() == false.
if (!_thread.joinable() && !isConnected() && !_automaticReconnection) return;
}
}
@ -294,10 +386,25 @@ namespace ix
}
}
WebSocketSendInfo WebSocket::send(const std::string& text,
WebSocketSendInfo WebSocket::send(const std::string& data,
bool binary,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, false, onProgressCallback);
return sendMessage(data,
(binary) ? SendMessageKind::Binary: SendMessageKind::Text,
onProgressCallback);
}
WebSocketSendInfo WebSocket::sendBinary(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendText(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::ping(const std::string& text)
@ -306,11 +413,11 @@ namespace ix
constexpr size_t pingMaxPayloadSize = 125;
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
return sendMessage(text, true);
return sendMessage(text, SendMessageKind::Ping);
}
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
bool ping,
SendMessageKind sendMessageKind,
const OnProgressCallback& onProgressCallback)
{
if (!isConnected()) return WebSocketSendInfo(false);
@ -327,13 +434,22 @@ namespace ix
std::lock_guard<std::mutex> lock(_writeMutex);
WebSocketSendInfo webSocketSendInfo;
if (ping)
switch (sendMessageKind)
{
webSocketSendInfo = _ws.sendPing(text);
}
else
{
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
case SendMessageKind::Text:
{
webSocketSendInfo = _ws.sendText(text, onProgressCallback);
} break;
case SendMessageKind::Binary:
{
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
} break;
case SendMessageKind::Ping:
{
webSocketSendInfo = _ws.sendPing(text);
} break;
}
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
@ -345,11 +461,11 @@ namespace ix
{
switch (_ws.getReadyState())
{
case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open;
case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
case ix::WebSocketTransport::CLOSING: return WebSocket_ReadyState_Closing;
case ix::WebSocketTransport::CLOSED: return WebSocket_ReadyState_Closed;
default: return WebSocket_ReadyState_Closed;
case ix::WebSocketTransport::ReadyState::OPEN : return ReadyState::Open;
case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting;
case ix::WebSocketTransport::ReadyState::CLOSING : return ReadyState::Closing;
case ix::WebSocketTransport::ReadyState::CLOSED : return ReadyState::Closed;
default: return ReadyState::Closed;
}
}
@ -357,11 +473,11 @@ namespace ix
{
switch (readyState)
{
case WebSocket_ReadyState_Open: return "OPEN";
case WebSocket_ReadyState_Connecting: return "CONNECTING";
case WebSocket_ReadyState_Closing: return "CLOSING";
case WebSocket_ReadyState_Closed: return "CLOSED";
default: return "CLOSED";
case ReadyState::Open : return "OPEN";
case ReadyState::Connecting: return "CONNECTING";
case ReadyState::Closing : return "CLOSING";
case ReadyState::Closed : return "CLOSED";
default: return "UNKNOWN";
}
}
@ -374,4 +490,14 @@ namespace ix
{
_automaticReconnection = false;
}
bool WebSocket::isAutomaticReconnectionEnabled() const
{
return _automaticReconnection;
}
size_t WebSocket::bufferedAmount() const
{
return _ws.bufferedAmount();
}
}

View File

@ -9,73 +9,31 @@
#pragma once
#include "IXProgressCallback.h"
#include "IXWebSocketCloseConstants.h"
#include "IXWebSocketErrorInfo.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketMessage.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketTransport.h"
#include <atomic>
#include <mutex>
#include <string>
#include <thread>
#include <mutex>
#include <atomic>
#include "IXWebSocketTransport.h"
#include "IXWebSocketErrorInfo.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXProgressCallback.h"
namespace ix
{
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
enum ReadyState
enum class ReadyState
{
WebSocket_ReadyState_Connecting = 0,
WebSocket_ReadyState_Open = 1,
WebSocket_ReadyState_Closing = 2,
WebSocket_ReadyState_Closed = 3
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3
};
enum WebSocketMessageType
{
WebSocket_MessageType_Message = 0,
WebSocket_MessageType_Open = 1,
WebSocket_MessageType_Close = 2,
WebSocket_MessageType_Error = 3,
WebSocket_MessageType_Ping = 4,
WebSocket_MessageType_Pong = 5
};
struct WebSocketOpenInfo
{
std::string uri;
WebSocketHttpHeaders headers;
WebSocketOpenInfo(const std::string& u = std::string(),
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
: uri(u)
, headers(h)
{
;
}
};
struct WebSocketCloseInfo
{
uint16_t code;
std::string reason;
WebSocketCloseInfo(uint16_t c = 0,
const std::string& r = std::string())
: code(c)
, reason(r)
{
;
}
};
using OnMessageCallback = std::function<void(WebSocketMessageType,
const std::string&,
size_t wireSize,
const WebSocketErrorInfo&,
const WebSocketOpenInfo&,
const WebSocketCloseInfo&)>;
using OnMessageCallback = std::function<void(const WebSocketMessagePtr&)>;
using OnTrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
@ -86,49 +44,68 @@ namespace ix
~WebSocket();
void setUrl(const std::string& url);
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setHandshakeTimeout(int handshakeTimeoutSecs);
void setHeartBeatPeriod(int hearBeatPeriod);
void setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setHeartBeatPeriod(int heartBeatPeriodSecs);
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
void setPingTimeout(int pingTimeoutSecs);
void enablePong();
void disablePong();
void disablePerMessageDeflate();
// Run asynchronously, by calling start and stop.
void start();
void stop();
// stop is synchronous
void stop(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
// Run in blocking mode, by connecting first manually, and then calling run.
WebSocketInitResult connect(int timeoutSecs);
void run();
WebSocketSendInfo send(const std::string& text,
// send is in binary mode by default
WebSocketSendInfo send(const std::string& data,
bool binary = false,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendBinary(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendText(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string& text);
void close();
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
void setOnMessageCallback(const OnMessageCallback& callback);
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
static void resetTrafficTrackerCallback();
ReadyState getReadyState() const;
static std::string readyStateToString(ReadyState readyState);
const std::string& getUrl() const;
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
int getHeartBeatPeriod() const;
int getPingInterval() const;
int getPingTimeout() const;
size_t bufferedAmount() const;
void enableAutomaticReconnection();
void disableAutomaticReconnection();
bool isAutomaticReconnectionEnabled() const;
private:
WebSocketSendInfo sendMessage(const std::string& text,
bool ping,
SendMessageKind sendMessageKind,
const OnProgressCallback& callback = nullptr);
bool isConnected() const;
bool isClosing() const;
void reconnectPerpetuallyIfDisconnected();
std::string readyStateToString(ReadyState readyState);
void checkConnection(bool firstConnectionAttempt);
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
// Server
void setSocketFileDescriptor(int fd);
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
WebSocketTransport _ws;
@ -148,10 +125,16 @@ namespace ix
std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs;
// Optional Heartbeat
int _heartBeatPeriod;
static const int kDefaultHeartBeatPeriod;
// enable or disable PONG frame response to received PING frame
bool _enablePong;
static const bool kDefaultEnablePong;
// Optional ping and pong timeout
int _pingIntervalSecs;
int _pingTimeoutSecs;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
friend class WebSocketServer;
};
}
} // namespace ix

View File

@ -0,0 +1,23 @@
/*
* IXWebSocketCloseConstants.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXWebSocketCloseConstants.h"
namespace ix
{
const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000);
const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011);
const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006);
const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002);
const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005);
const std::string WebSocketCloseConstants::kNormalClosureMessage("Normal closure");
const std::string WebSocketCloseConstants::kInternalErrorMessage("Internal error");
const std::string WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure");
const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout");
const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error");
const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code");
}

View File

@ -0,0 +1,29 @@
/*
* IXWebSocketCloseConstants.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
#include <string>
namespace ix
{
struct WebSocketCloseConstants
{
static const uint16_t kNormalClosureCode;
static const uint16_t kInternalErrorCode;
static const uint16_t kAbnormalCloseCode;
static const uint16_t kProtocolErrorCode;
static const uint16_t kNoStatusCodeErrorCode;
static const std::string kNormalClosureMessage;
static const std::string kInternalErrorMessage;
static const std::string kAbnormalCloseMessage;
static const std::string kPingTimeoutMessage;
static const std::string kProtocolErrorMessage;
static const std::string kNoStatusCodeErrorMessage;
};
} // namespace ix

View File

@ -0,0 +1,25 @@
/*
* IXWebSocketCloseInfo.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
namespace ix
{
struct WebSocketCloseInfo
{
uint16_t code;
std::string reason;
bool remote;
WebSocketCloseInfo(uint16_t c = 0, const std::string& r = std::string(), bool rem = false)
: code(c)
, reason(r)
, remote(rem)
{
;
}
};
} // namespace ix

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