Compare commits

..

300 Commits

Author SHA1 Message Date
Benjamin Sergeant
e02679f744 remove unused _backgroundThreadRunning WebSocket member variable 2019-04-29 21:14:46 -07:00
Benjamin Sergeant
21c155339e
Merge branch 'master' into feature/no_automatic_reconnection 2019-04-29 21:05:28 -07:00
Benjamin Sergeant
1d39a9c9a9 build fix 2019-04-29 20:54:00 -07:00
Benjamin Sergeant
b588ed0fa1 tsan fixes on ubuntu xenial (what travis run) 2019-04-29 20:48:16 -07:00
Benjamin Sergeant
d9f7a138b8 dns lookup: fix race condition accessing _errMsg 2019-04-29 19:29:27 -07:00
Benjamin Sergeant
d3e04ff619 tsan linux tentative fix / copy string instead of passing a const reference 2019-04-29 17:27:53 -07:00
Benjamin Sergeant
372dd24cc7 rename _blocking to _backgroundThreadRunning and invert the naming 2019-04-29 16:54:08 -07:00
Alexandre Konieczny
a9422cf34d fix data race on _thread 2019-04-29 16:46:16 -07:00
Alexandre Konieczny
c7e52e6fcd fix data race on _useMask 2019-04-29 16:41:34 -07:00
Benjamin Sergeant
bbf34aef29 cleanup 2019-04-29 16:15:26 -07:00
Benjamin Sergeant
225aade89d unittest pass + commands behave as expected 2019-04-29 15:27:04 -07:00
Benjamin Sergeant
705e0823cb ws connect mode / add a flag to disable automatic reconnection, not hooked up yet 2019-04-29 14:31:29 -07:00
Benjamin Sergeant
8e4cf74974 enable tsan on travis for all configs 2019-04-29 09:11:16 -07:00
Benjamin Sergeant
0a7157655b initialize netSystem (aka winsock on windows) explicitely 2019-04-25 16:38:15 -07:00
Dimon4eg
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
Benjamin Sergeant
b178ba16af fix indentation of greatestCommonDivisor 2019-04-25 16:21:36 -07:00
Benjamin Sergeant
e4c09284b5 Remove commented code 2019-04-25 16:16:52 -07:00
Benjamin Sergeant
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
Benjamin Sergeant
d37ed300e2 disable failing unittest temporarily 2019-04-25 09:04:35 -07:00
Dimon4eg
3207ce37b6 Speedup build for Windows (#43)
* Speedup build for Windows

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

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

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

* add readme

* use cli11 for argument parsing

* json -> msgpack

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

* can pass a fin frame

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

* set fin only for last fragment

* cleanup

* last fragment should be of type CONTINUATION

* Add simple send and receive programs

* speedups receiving + better way to wait for thing

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

* document bug

* use chunks to receive data

* trailing spaces
2019-02-20 18:59:07 -08:00
Benjamin Sergeant
505dd6d50f document bug 2019-02-16 10:33:37 -08:00
Benjamin Sergeant
3f8027b65c unittest for sending large messages 2019-02-16 10:32:02 -08:00
Benjamin Sergeant
0f2c765f45
Update formatting in README.md 2019-02-05 23:04:45 -08:00
Benjamin Sergeant
49077f8f44 more conf in CI 2019-01-29 17:50:19 -08:00
Benjamin Sergeant
6a23b8530f get free port that can be used by non root users (> 1024) 2019-01-28 15:24:19 -08:00
Benjamin Sergeant
ae841af91a use dynamically generated port number to configure servers in unittest 2019-01-28 15:24:19 -08:00
Benjamin Sergeant
44f38849b2
Merge pull request #13 from machinezone/user/bsergeant/poll
User/bsergeant/poll
2019-01-27 10:47:38 -08:00
Benjamin Sergeant
ee12fbdb5f windows build fix 2019-01-27 10:46:02 -08:00
Benjamin Sergeant
316c630830 constexpr to declare number of fds 2019-01-26 21:01:36 -08:00
Benjamin Sergeant
1ea5db6110 linux fix 2019-01-26 20:57:48 -08:00
Benjamin Sergeant
986d9a00c0 remove shutdown call 2019-01-26 20:54:23 -08:00
Benjamin Sergeant
7a05a11014 rebase poll branch 2019-01-26 20:50:25 -08:00
Benjamin Sergeant
f09434263c insensitive string compare when validating server connection header 2019-01-25 16:17:51 -08:00
Benjamin Sergeant
335f594165
Merge pull request #12 from machinezone/user/bsergeant/heart-beat
Add an optional heartbeat
2019-01-25 16:14:28 -08:00
Benjamin Sergeant
fa7ef06f4d heartbeat correct 2019-01-25 16:11:39 -08:00
Benjamin Sergeant
3c9ec0aed0 close server socket on exit 2019-01-24 21:16:32 -08:00
Benjamin Sergeant
c665d65cba unittest fix 2019-01-24 19:54:10 -08:00
Benjamin Sergeant
5d4e897cc4 add an heartbeat test 2019-01-24 18:50:07 -08:00
Benjamin Sergeant
05033714bf hearbeat 2019-01-24 12:42:49 -08:00
Benjamin Sergeant
a02bd3f25c
Update README.md 2019-01-15 09:36:43 -08:00
Benjamin Sergeant
fdbd213fa2 check and validate the Connection: Upgrade header in client/server 2019-01-15 09:31:37 -08:00
Benjamin Sergeant
da64d349c8
Merge pull request #10 from tonylin0826/master
Fix missing "Upgrade" header error
2019-01-15 09:22:11 -08:00
Tony Lin
17b01a8c66 Fix missing upgrade header error 2019-01-15 15:35:37 +08:00
Benjamin Sergeant
79dd766fab C++14 + use make_unique and make_shared to make shared pointers 2019-01-11 21:25:06 -08:00
Benjamin Sergeant
8375b28747 add travis badge 2019-01-08 10:13:23 -08:00
Benjamin Sergeant
e12551f309 travis -> osx 2019-01-08 10:04:47 -08:00
Benjamin Sergeant
6102f81710 Revert "Revert "try asan on Linux"" [Back to asan on Linux]
This reverts commit 02a704a8c713af0968e8b4b54f71d480f6b4c0f7.
2019-01-07 21:13:48 -08:00
Benjamin Sergeant
9f678e5962 travis-ci: try to use clang on Linux 2019-01-07 20:49:03 -08:00
Benjamin Sergeant
02a704a8c7 Revert "try asan on Linux"
This reverts commit dd2360ed7049e7be3cd9c739783ae744df62df2f.
2019-01-07 20:47:25 -08:00
Benjamin Sergeant
dd2360ed70 try asan on Linux 2019-01-07 18:29:44 -08:00
Benjamin Sergeant
c4ab996470 build with osx on travis 2019-01-07 18:16:29 -08:00
Benjamin Sergeant
6c54b07d92 fix simple compile error in test/IXTest.h 2019-01-07 18:08:11 -08:00
Benjamin Sergeant
7f9bef3b8d add a travis file for real 2019-01-07 18:05:55 -08:00
Benjamin Sergeant
12d1c5d956 add a travis file 2019-01-07 18:04:28 -08:00
Benjamin Sergeant
e9a4bd5617 update test remote ws url 2019-01-07 11:28:53 -08:00
Benjamin Sergeant
f34ccbfdb5 remove cmake sanitizer submodule 2019-01-07 11:26:23 -08:00
Benjamin Sergeant
1fa75d7fb2 check select errors better 2019-01-07 11:18:00 -08:00
Benjamin Sergeant
39140ef98c sanitizer cmake stuff 2019-01-06 18:54:16 -08:00
Benjamin Sergeant
e30ef4a87c DNSLookup _id member does not need to be an atomic 2019-01-06 18:32:19 -08:00
Benjamin Sergeant
9fc94f0487 DNSLookup: fix #8 2019-01-06 18:27:26 -08:00
Benjamin Sergeant
121acdab6f DNSLookup: copy hostname and port instead of accessing member 2019-01-06 18:17:12 -08:00
Benjamin Sergeant
6deaa03114 return false -> return -1 2019-01-06 18:10:39 -08:00
Benjamin Sergeant
f4f30686c5 add new unittest 2019-01-06 15:14:13 -08:00
Benjamin Sergeant
a21aae521f remove dead file 2019-01-06 14:26:11 -08:00
Benjamin Sergeant
aed2356fc1 remove openssl testing bits for apple build 2019-01-06 14:21:49 -08:00
Benjamin Sergeant
a478f734f6 gcc linux compile fix 2019-01-06 12:12:39 -08:00
Benjamin Sergeant
98c579da03 make a class hierarchy for server code (IXWebSocketServer <- IXSocketServer) 2019-01-06 12:09:31 -08:00
Benjamin Sergeant
e80def0cd0 add log 2019-01-05 21:16:13 -08:00
Benjamin Sergeant
cc8a9e883e unittest + compiler warnings 2019-01-05 21:10:08 -08:00
Benjamin Sergeant
4d587e35d8 windows compile fix 2019-01-05 21:02:55 -08:00
Benjamin Sergeant
50f4fd1115 int -> ssize_t for socker recv and send 2019-01-05 20:53:50 -08:00
Benjamin Sergeant
06d2b68696 header refactoring 2019-01-05 20:38:43 -08:00
Benjamin Sergeant
bf6f057777 windows connect (compile fix) 2019-01-05 17:35:50 -08:00
Benjamin Sergeant
b57c1d69f2 windows connect potential fix 2019-01-05 17:32:21 -08:00
Benjamin Sergeant
ff265d83f9 more accurate description of errors 2019-01-05 17:18:43 -08:00
Benjamin Sergeant
5b1c97b774 SocketTest / more debug info 2019-01-05 17:10:01 -08:00
Benjamin Sergeant
c8c81366f7 windows (compile) fix 2019-01-05 17:04:09 -08:00
Benjamin Sergeant
9a37fd56d1 windows fix 2019-01-05 17:02:39 -08:00
Benjamin Sergeant
7ecaff8c5d test failure is not noticed 2019-01-05 16:30:22 -08:00
Benjamin Sergeant
e4b0286a25 fix gcc warning 2019-01-05 16:26:11 -08:00
Benjamin Sergeant
7ae6972306 makefile tweak 2019-01-05 14:43:21 -08:00
Benjamin Sergeant
59cea0372b add dns lookup test 2019-01-05 14:40:17 -08:00
Benjamin Sergeant
78d88a8520 openssl cleanup 2019-01-05 11:42:25 -08:00
Benjamin Sergeant
273af25d57
Merge pull request #7 from bsergean/user/bsergeant/appveyor_first
unittest on appveyor
2019-01-04 17:29:23 -08:00
Benjamin Sergeant
46d00360a8 unittest on appveyor 2019-01-04 17:28:13 -08:00
Benjamin Sergeant
3f5935a284 windows fixes 2019-01-04 15:23:57 -08:00
Benjamin Sergeant
c236ff66e9
Merge pull request #6 from machinezone/user/bsergeant/server
Add support for writing websocket servers (IXWebSocketServer)
2019-01-03 18:47:30 -08:00
Benjamin Sergeant
af3df5e519 Socket::readLine works with arbitrary long lines 2019-01-03 18:47:01 -08:00
Benjamin Sergeant
d75753ec98 timeout is configurable 2019-01-03 18:33:08 -08:00
Benjamin Sergeant
332bb87231 remove useless FIXME comment 2019-01-03 18:02:03 -08:00
Benjamin Sergeant
8adbcab441 new doc 2019-01-03 18:00:48 -08:00
Benjamin Sergeant
9bc2e95196 capture path/uri when connecting, and pass it back through callbacks in the openInfo member 2019-01-03 17:44:10 -08:00
Benjamin Sergeant
30a0aa0a0f implement a max connections (default = 32) settings 2019-01-03 17:05:44 -08:00
Benjamin Sergeant
8622ea5cb2 correct validation of the request (request line + headers) 2019-01-03 13:41:06 -08:00
Benjamin Sergeant
ed3a50d9b5 cancellation refactoring 2019-01-03 12:53:44 -08:00
Benjamin Sergeant
df6a17dcc2 rename test file 2019-01-02 21:59:06 -08:00
Benjamin Sergeant
474985e784 split handshake code into its own files, so that Transport file is less massive 2019-01-02 20:07:54 -08:00
Benjamin Sergeant
cb904416c3 server unittest for validating client request / new timeout cancellation handling (need refactoring) 2019-01-02 16:08:32 -08:00
Benjamin Sergeant
3e064ec63e add new broadcast server example 2019-01-02 08:17:03 -08:00
Benjamin Sergeant
b004769552 server per message deflate support 2019-01-02 08:12:29 -08:00
Benjamin Sergeant
17270de621 echo server example is a real echo server, not a broadcast server 2019-01-02 08:10:39 -08:00
Benjamin Sergeant
239b5bc02c refactoring + cancellation was buggy during http upgrade 2019-01-02 07:45:07 -08:00
Benjamin Sergeant
6bfabd5493 use select to detect new incoming connections 2019-01-01 22:21:07 -08:00
Benjamin Sergeant
0b90f7df1b add a way to run in blocking more, which is useful for server mode to have N*thread instead of 2N*thread for N connections 2019-01-01 21:25:15 -08:00
Benjamin Sergeant
00ca7c8fb0 more named constants 2019-01-01 19:23:27 -08:00
Benjamin Sergeant
a11952fe22 gitignore stuff 2019-01-01 17:14:31 -08:00
Benjamin Sergeant
06b9b2e649 linux fix + unittest works with Linux 2019-01-01 17:13:26 -08:00
Benjamin Sergeant
dcfdcc3e1b unittest starts a server 2019-01-01 16:34:05 -08:00
Benjamin Sergeant
b13fee16c1 crash when server failed to start 2019-01-01 16:14:46 -08:00
Benjamin Sergeant
9a7767ecb1 thread accepting connections can be cancelled/stopped externally 2019-01-01 16:11:27 -08:00
Benjamin Sergeant
9b82a33aff listen job run in its own thread, non blocking 2019-01-01 14:52:14 -08:00
Benjamin Sergeant
70ef77a5d5 (nitpick) reformat 2019-01-01 14:29:57 -08:00
Benjamin Sergeant
77903e9d90 cleanup / remove printf, add mutex, remove hardcoded values, can pass in a binding host 2019-01-01 14:28:41 -08:00
Benjamin Sergeant
de66a87a7c use shared_ptr 2019-01-01 13:53:13 -08:00
Benjamin Sergeant
5ea2028c22 unittest pass 2019-01-01 13:47:25 -08:00
Benjamin Sergeant
58a68ec0be record workers in a map instead of a vector 2018-12-31 14:52:59 -08:00
Benjamin Sergeant
a39278f7be add a print statement when the connection is closed / still need to terminate server thread 2018-12-31 12:47:42 -08:00
Benjamin Sergeant
f8373dc666 more cleanup to propagate server connection error and let onOpen callback execute 2018-12-31 12:43:47 -08:00
Benjamin Sergeant
3febc2431d only bind to localhost 2018-12-31 11:48:49 -08:00
Benjamin Sergeant
0bf736831a server code has a callback that takes a websocket 2018-12-30 22:12:13 -08:00
Benjamin Sergeant
7710bf793f cleanup / use a websocket instead of raw websockettransport 2018-12-30 22:00:49 -08:00
Benjamin Sergeant
a6a43bd361 can accept multiple connection / server can send data back to client 2018-12-30 21:16:05 -08:00
Benjamin Sergeant
a39209a895 proof of concept server implementation 2018-12-29 23:15:27 -08:00
Benjamin Sergeant
24c9e0abc3 can create a socket from a fd 2018-12-29 21:53:33 -08:00
Benjamin Sergeant
9cc324d78d add simple unittest 2018-12-29 18:34:08 -08:00
Benjamin Sergeant
8574beceb1 add missing src files (IXSetThreadName.{cpp,h}) ... 2018-12-23 14:19:30 -08:00
Benjamin Sergeant
0349b7f1c7 fix warning: field '_eventCallback' will be initialized after field '_publishMode' 2018-12-23 14:18:53 -08:00
Benjamin Sergeant
ce1ba20db5 Fix warning: field '_done' will be initialized after field '_wait' [-Wreorder] _done(false), 2018-12-23 14:17:30 -08:00
Benjamin Sergeant
395d823f41 set thread name / rename example 2018-12-23 14:14:38 -08:00
Benjamin Sergeant
6884f9f74f async dns lookup fix 2018-12-14 17:49:42 -08:00
Benjamin Sergeant
b34eccd749 non blocking dns lookup 2018-12-14 16:28:17 -08:00
Benjamin Sergeant
50b638f7fd add cancellation support while connecting, to speed up WebSocket::stop 2018-12-09 17:56:20 -08:00
Benjamin Sergeant
5bf1b91528 http upgrade and connections use non blocking sockets 2018-12-09 14:07:40 -08:00
Benjamin Sergeant
f77ececc92 threading race condition fixes, detected by TSAN 2018-12-06 08:27:28 -08:00
Benjamin Sergeant
58cccbdcf9 cleanup 2018-11-14 15:52:28 -08:00
Benjamin Sergeant
5710ffba6a per-message deflate compression fixes 2018-11-13 17:46:05 -08:00
Benjamin Sergeant
ccd4522b8f move files around 2018-11-12 17:56:59 -08:00
Benjamin Sergeant
28f29b7385 update readme / remove reference to missing compression support now that it is supported ... 2018-11-12 09:01:42 -08:00
Benjamin Sergeant
a7a422d6ed tweaks doc / license + send proper error code when closing the connecion 2018-11-12 09:00:55 -08:00
Benjamin Sergeant
43fcf93584 per message deflate support (with zlib) 2018-11-09 18:42:09 -08:00
Benjamin Sergeant
32f4c8305e (satori_publisher) better error handling 2018-11-07 14:54:44 -08:00
Benjamin Sergeant
3cf44c8078 Add some example shell scripts to build on Linux 2018-11-07 12:33:33 -08:00
Benjamin Sergeant
9e899fde2f Add new example folder for publishing events to satori, with a minimal satori sdk 2018-11-07 12:26:32 -08:00
Benjamin Sergeant
ffd4f1d322 Add missing files ... 2018-11-07 12:25:38 -08:00
Benjamin Sergeant
10dd13deb3 Add DockerFile + parse rsv1 field 2018-11-07 11:45:17 -08:00
Benjamin Sergeant
c1ed83a005 stopping connection on Linux does not close the socket, which can create problem when re-starting the connection 2018-11-01 17:02:49 -07:00
Benjamin Sergeant
7117c74142 add stop and start directives to ws_connect + display close info 2018-10-31 10:27:17 -07:00
Benjamin Sergeant
dd06a3fb25 update readme.md 2018-10-27 11:46:11 -07:00
Benjamin Sergeant
45b579447e Handle Sec-WebSocket-Accept correctly 2018-10-27 10:24:48 -07:00
Benjamin Sergeant
bb0b1836cd capture an error code and a reason when the server closes the connection 2018-10-25 18:51:19 -07:00
Benjamin Sergeant
d5c8815438 add doc about ping/pong 2018-10-25 15:14:31 -07:00
Benjamin Sergeant
ac500ed079 ping pong example: more error handling 2018-10-25 14:46:23 -07:00
Benjamin Sergeant
2bc38acbb1 ping / pong support / fix bug in dispatching received message type 2018-10-25 14:43:35 -07:00
Benjamin Sergeant
977feae1d6 Better ping/pong support 2018-10-25 14:43:35 -07:00
Benjamin Sergeant
9c872fcc3e New ws_connect example. Close to wscat node.js tool. 2018-10-25 14:43:35 -07:00
Benjamin Sergeant
ec1ca3c55e
Update README.md 2018-10-08 21:50:55 -07:00
Benjamin Sergeant
16805759d3 Windows support (no TLS yet) 2018-10-08 21:44:54 -07:00
Benjamin Sergeant
88c2e1f6de make TLS support optional 2018-10-08 15:24:36 -07:00
Benjamin Sergeant
1dc9b559e9 move examples around 2018-10-08 15:24:36 -07:00
Benjamin Sergeant
d31ecfc64e
Update IXWebSocket.h
Remove dead code
2018-10-07 15:49:07 -07:00
Benjamin Sergeant
4813a40f2a
Update README.md
Advanced usage -> API
2018-10-07 15:47:38 -07:00
Benjamin Sergeant
ea81470f4a more ssl peer validation stuff 2018-10-05 18:45:44 -07:00
Benjamin Sergeant
2a6b1d5f15
Update README.md 2018-10-05 14:35:09 -07:00
582 changed files with 117521 additions and 40261 deletions

View File

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

View File

@ -1,5 +1,3 @@
build build
CMakeCache.txt CMakeCache.txt
ws/CMakeCache.txt ws/CMakeCache.txt
test/build
makefile

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

11
.gitignore vendored
View File

@ -1,12 +1 @@
build build
*.pyc
venv
ixsnake/ixsnake/.certs/
site/
ws/.certs/
ws/.srl
ixhttpd
makefile
a.out
.idea/
cmake-build-debug/

View File

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

17
.travis.yml Normal file
View File

@ -0,0 +1,17 @@
language: cpp
dist: xenial
compiler:
- gcc
- clang
os:
- linux
- osx
matrix:
exclude:
# GCC fails on recent Travis OSX images.
- compiler: gcc
os: osx
script: python test/run.py

View File

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

View File

@ -1,16 +0,0 @@
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
# mbedtls-3.0 changed headers files, and we need to ifdef'out a few things
find_path(MBEDTLS_VERSION_GREATER_THAN_3 mbedtls/build_info.h)
find_library(MBEDTLS_LIBRARY mbedtls)
find_library(MBEDX509_LIBRARY mbedx509)
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
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

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

View File

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

1
DOCKER_VERSION Normal file
View File

@ -0,0 +1 @@
1.4.3

View File

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

491
README.md
View File

@ -1,152 +1,421 @@
## Hello world # General
(note from the main developer, sadly I don't have too much time to devote to this library anymore, maybe it's time to pass the maintenance to someone else more motivated ?) ![Alt text](https://travis-ci.org/machinezone/IXWebSocket.svg?branch=master)
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing. ## Introduction
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness. [*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 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.
```cpp * macOS
/* * iOS
* main.cpp * Linux
* Author: Benjamin Sergeant * Android
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
*
* On macOS
* $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install)
* $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
* $ ./a.out
*
* Or use cmake -DBUILD_DEMO=ON option for other platforms
*/
#include <ixwebsocket/IXNetSystem.h> The code was made to compile once on Windows but support is currently broken on this platform.
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXUserAgent.h>
#include <iostream>
int main() ## Examples
{
// Required on Windows
ix::initNetSystem();
// Our websocket object 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.
```
ix::WebSocket webSocket; ix::WebSocket webSocket;
// Connect to a server with encryption std::string url("ws://localhost:8080/");
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
// https://github.com/machinezone/IXWebSocket/issues/386#issuecomment-1105235227 (self signed certificates)
std::string url("wss://echo.websocket.org");
webSocket.setUrl(url); webSocket.setUrl(url);
std::cout << "Connecting to " << url << "..." << std::endl; // 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);
// Setup a callback to be fired (in a background thread, watch out for race conditions !) // Setup a callback to be fired when a message or an event (open, close, error) is received
// when a message or an event (open, close, error) is received webSocket.setOnMessageCallback(
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) [](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{ {
if (msg->type == ix::WebSocketMessageType::Message) if (messageType == ix::WebSocket_MessageType_Message)
{ {
std::cout << "received message: " << msg->str << std::endl; std::cout << str << std::endl;
std::cout << "> " << std::flush;
} }
else if (msg->type == ix::WebSocketMessageType::Open) });
{
std::cout << "Connection established" << std::endl;
std::cout << "> " << std::flush;
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
// Maybe SSL is not configured properly
std::cout << "Connection error: " << msg->errorInfo.reason << std::endl;
std::cout << "> " << std::flush;
}
}
);
// Now that our callback is setup, we can start our background thread and receive messages // Now that our callback is setup, we can start our background thread and receive messages
webSocket.start(); webSocket.start();
// Send a message to the server (default to TEXT mode) // Send a message to the server (default to BINARY mode)
webSocket.send("hello world"); webSocket.send("hello world");
// Display a prompt // The message can be sent in TEXT mode
std::cout << "> " << std::flush; webSocket.sendText("hello again");
std::string text; // ... finally ...
// Read text from the console and send messages in text mode.
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
while (std::getline(std::cin, text))
{
webSocket.send(text);
std::cout << "> " << std::flush;
}
return 0; // Stop the connection
} webSocket.stop()
``` ```
Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem. Here is what the server API looks like. Note that server support is very recent and subject to changes.
IXWebSocket is actively being developed, check out the [changelog](https://machinezone.github.io/IXWebSocket/CHANGELOG/) to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at [cobra](https://github.com/machinezone/cobra). ```
// Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters.
ix::WebSocketServer server(port);
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code. server.setOnConnectionCallback(
[&server](std::shared_ptr<WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &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;
Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible. // 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;
## Users // The uri the client did connect to.
std::cerr << "Uri: " << openInfo.uri << std::endl;
If your company or project is using this library, feel free to open an issue or PR to amend this list. std::cerr << "Headers:" << std::endl;
for (auto it : openInfo.headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_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);
}
}
);
}
);
- [Machine Zone](https://www.mz.com) auto res = server.listen();
- [Tokio](https://github.com/liz3/tokio), a discord library focused on audio playback with node bindings. if (!res.first)
- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework. {
- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod // Error handling
- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper (archived as of Oct 8, 2021) return 1;
- [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots }
- [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar
- [Abaddon](https://github.com/uowuo/abaddon), An alternative Discord client made with C++/gtkmm
- [NovaCoin](https://github.com/novacoin-project/novacoin), a hybrid scrypt PoW + PoS based cryptocurrency.
- [Candy](https://github.com/lanthora/candy), A WebSocket and TUN based VPN for Linux
- [ITGmania](https://github.com/itgmania/itgmania), a cross platform Dance Dance Revolution-like emulator.
## Alternative libraries // Run the server in the background. Server can be stoped by calling server.stop()
server.start();
There are plenty of great websocket libraries out there, which might work for you. Here are a couple of serious ones. // Block until server.stop() is called.
server.wait();
* [websocketpp](https://github.com/zaphoyd/websocketpp) - C++ ```
* [beast](https://github.com/boostorg/beast) - C++
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C++
* [libwebsockets](https://libwebsockets.org/) - C
* [wslay](https://github.com/tatsuhiro-t/wslay) - C
[uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv. Here is what the HTTP client API looks like. Note that HTTP client support is very recent and subject to changes.
To check the performance of a websocket library, you can look at the [autoroute](https://github.com/bsergean/autoroute) project. ```
//
// Preparation
//
HttpClient httpClient;
HttpRequestArgs args;
## Continuous Integration // Custom headers can be set
WebSocketHttpHeaders headers;
headers["Foo"] = "bar";
args.extraHeaders = headers;
| OS | TLS | Sanitizer | Status | // Timeout options
|-------------------|-------------------|-------------------|-------------------| args.connectTimeout = connectTimeout;
| Linux | OpenSSL | None | [![Build2][1]][0] | args.transferTimeout = transferTimeout;
| macOS | Secure Transport | Thread Sanitizer | [![Build2][2]][0] |
| macOS | OpenSSL | Thread Sanitizer | [![Build2][3]][0] |
| macOS | MbedTLS | Thread Sanitizer | [![Build2][4]][0] |
| Windows | Disabled | None | [![Build2][5]][0] |
| UWP | Disabled | None | [![Build2][6]][0] |
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] |
* Some tests are disabled on Windows/UWP because of a pathing problem // Redirect options
* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg. args.followRedirects = followRedirects;
args.maxRedirects = maxRedirects;
[0]: https://github.com/machinezone/IXWebSocket // Misc
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg args.compress = compress; // Enable gzip compression
[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg args.verbose = verbose;
[3]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg args.logger = [](const std::string& msg)
[4]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg {
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg std::cout << msg;
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg };
[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg
//
// Request
//
HttpResponse out;
std::string url = "https://www.google.com";
// HEAD request
out = httpClient.head(url, args);
// GET request
out = httpClient.get(url, args);
// POST request with parameters
HttpParameters httpParameters;
httpParameters["foo"] = "bar";
out = httpClient.post(url, httpParameters, args);
// POST request with a body
out = httpClient.post(url, std::string("foo=bar"), args);
//
// Result
//
auto statusCode = std::get<0>(out);
auto errorCode = std::get<1>(out);
auto responseHeaders = std::get<2>(out);
auto payload = std::get<3>(out);
auto errorMsg = std::get<4>(out);
auto uploadSize = std::get<5>(out);
auto downloadSize = std::get<6>(out);
```
## 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. Otherwise the usual way will suffice.
```
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.
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
### Per Message Deflate compression.
The per message deflate compression option is supported. It can lead to very nice bandbwith savings (20x !) if your messages are similar, which is often the case for example for chat applications. All features of the spec should be supported.
### TLS/SSL
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, and OpenSSL is used on Android and Linux.
### Polling and background thread work
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes. However concurrent writes need to be protected with mutex.
### Automatic reconnection
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds.
### Large messages
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully.
## Limitations
* 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 is not as scalable as strategies using epoll or kqueue.
## C++ code organization
Here is a simplistic diagram which explains how the code is structured in term of class/modules.
```
+-----------------------+ --- Public
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
| |
+-----------------------+
| |
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
| | Each connection is handled in a new OS thread.
| |
+-----------------------+ --- Private
| |
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
| |
+-----------------------+
| |
| IXWebSocketHandshake | Establish the connection between client and server.
| |
+-----------------------+
| |
| IXWebSocket | ws:// Unencrypted Socket handler
| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS
| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux
| | Can be used on macOS too.
+-----------------------+
| |
| IXSocketConnect | Connect to the remote host (client).
| |
+-----------------------+
| |
| IXDNSLookup | Does DNS resolution asynchronously so that it can be interrupted.
| |
+-----------------------+
```
## API
### Sending messages
`websocket.send("foo")` will send a message.
If the connection was closed and sending failed, the return value will be set to false.
### ReadyState
`getReadyState()` returns the state of the connection. There are 4 possible states.
1. WebSocket_ReadyState_Connecting - The connection is not yet open.
2. WebSocket_ReadyState_Open - The connection is open and ready to communicate.
3. WebSocket_ReadyState_Closing - The connection is in the process of closing.
4. WebSocket_MessageType_Close - The connection is closed or could not be opened.
### Open and Close notifications
The onMessage event will be fired when the connection is opened or closed. This is similar to the [Javascript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
```
webSocket.setOnMessageCallback(
[](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::cout << "send greetings" << std::endl;
// Headers can be inspected (pairs of string/string)
std::cout << "Handshake Headers:" << std::endl;
for (auto it : headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (messageType == ix::WebSocket_MessageType_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;
}
}
);
```
### Error notification
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocket_MessageType_Error`. Multiple fields will be available on the event to describe the error.
```
webSocket.setOnMessageCallback(
[](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_Error)
{
std::stringstream ss;
ss << "Error: " << error.reason << std::endl;
ss << "#retries: " << event.retries << std::endl;
ss << "Wait time(ms): " << event.wait_time << std::endl;
ss << "HTTP Status: " << event.http_status << std::endl;
std::cout << ss.str() << std::endl;
}
}
);
```
### start, stop
1. `websocket.start()` connect to the remote server and starts the message receiving background thread.
2. `websocket.stop()` disconnect from the remote server and closes the background thread.
### Configuring the remote url
The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url.
```
std::string url("wss://example.com");
websocket.configure(url);
```
### Ping/Pong support
Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec.
```
webSocket.setOnMessageCallback(
[](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_Ping ||
messageType == ix::WebSocket_MessageType_Pong)
{
std::cout << "pong data: " << str << std::endl;
}
}
);
```
A ping message can be sent to the server, with an optional data string.
```
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
```
### Heartbeat.
You can configure an optional heart beat / keep-alive, sent every 45 seconds
when there is no any traffic to make sure that load balancers do not kill an
idle connection.
```
webSocket.setHeartBeatPeriod(45);
```

View File

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

10
appveyor.yml Normal file
View File

@ -0,0 +1,10 @@
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
build: off

View File

@ -1,11 +1,33 @@
version: "3.3" version: "3"
services: services:
push: snake:
entrypoint: ws push_server --host 0.0.0.0 image: bsergean/ws:build
image: ${DOCKER_REPO}/ws:build entrypoint: ws snake --port 8765 --host 0.0.0.0 --redis_hosts redis1
ports:
autoroute: - "8765:8765"
entrypoint: ws autoroute ws://push:8008 networks:
image: ${DOCKER_REPO}/ws:build - ws-net
depends_on: depends_on:
- push - 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
networks:
ws-net:

View File

@ -1,39 +0,0 @@
FROM alpine:3.12 as build
RUN apk add --no-cache \
gcc g++ musl-dev linux-headers \
cmake mbedtls-dev make zlib-dev python3-dev ninja git
RUN addgroup -S app && \
adduser -S -G app app && \
chown -R app:app /opt && \
chown -R app:app /usr/local
# There is a bug in CMake where we cannot build from the root top folder
# So we build from /opt
COPY --chown=app:app . /opt
WORKDIR /opt
USER app
RUN make -f makefile.dev ws_mbedtls_install && \
sh tools/trim_repo_for_docker.sh
FROM alpine:3.12 as runtime
RUN apk add --no-cache libstdc++ mbedtls ca-certificates python3 strace && \
addgroup -S app && \
adduser -S -G app app
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
# COPY --chown=app:app --from=build /opt /opt
RUN chmod +x /usr/local/bin/ws && \
ldd /usr/local/bin/ws
# Now run in usermode
USER app
WORKDIR /home/app
ENTRYPOINT ["ws"]
EXPOSE 8008

View File

@ -1,41 +0,0 @@
FROM centos:8 as build
RUN yum install -y gcc-c++ make cmake zlib-devel openssl-devel redhat-rpm-config
RUN yum install -y epel-release
RUN yum install -y mbedtls-devel
RUN groupadd app && useradd -g app app
RUN chown -R app:app /opt
RUN chown -R app:app /usr/local
# There is a bug in CMake where we cannot build from the root top folder
# So we build from /opt
COPY --chown=app:app . /opt
WORKDIR /opt
USER app
RUN [ "make", "ws_mbedtls_install" ]
RUN [ "rm", "-rf", "build" ]
FROM centos:8 as runtime
RUN yum install -y gdb strace
RUN yum install -y epel-release
RUN yum install -y mbedtls
RUN groupadd app && useradd -g app app
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
RUN chmod +x /usr/local/bin/ws
RUN ldd /usr/local/bin/ws
# Copy source code for gcc
COPY --chown=app:app --from=build /opt /opt
# Now run in usermode
USER app
WORKDIR /home/app
ENTRYPOINT ["ws"]
EXPOSE 8008

View File

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

View File

@ -16,7 +16,6 @@ ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
RUN yum install -y python RUN yum install -y python
RUN yum install -y libtsan RUN yum install -y libtsan
RUN yum install -y zlib-devel
COPY . . COPY . .
# RUN ["make", "test"] # RUN ["make", "test"]

View File

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

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,93 +0,0 @@
## Build
### CMake
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.
```
mkdir build # make a build dir so that you can build out of tree.
cd build
cmake -DUSE_TLS=1 ..
make -j
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
```
Headers and a static library will be installed to the target dir.
There is a unittest which can be executed by typing `make test`.
Options for building:
* `-DBUILD_SHARED_LIBS=ON` will build the unittest as a shared libary instead of a static library, which is the default
* `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension
* `-DUSE_TLS=1` will enable TLS support
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows). When using a custom version of openssl (say a prebuilt version, odd runtime problems can happens, as in #319, and special cmake trickery will be required (see this [comment](https://github.com/machinezone/IXWebSocket/issues/175#issuecomment-620231032))
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support
* `-DUSE_WS=1` will build the ws interactive command line tool
* `-DUSE_TEST=1` will build the unittest
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/unittest_windows.yml) which have instructions for building dependencies.
It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:
```
ExternalProject_Add(
IXWebSocket
GIT_REPOSITORY https://github.com/machinezone/IXWebSocket.git
...
)
```
### vcpkg
It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/microsoft/vcpkg).
```
vcpkg install ixwebsocket
```
To use the installed package within a cmake project, use the following:
```cmake
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") # this is super important in order for cmake to include the vcpkg search/lib paths!
# find library and its headers
find_path(IXWEBSOCKET_INCLUDE_DIR ixwebsocket/IXWebSocket.h)
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
# include headers
include_directories(${IXWEBSOCKET_INCLUDE_DIR})
# ...
target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUND
```
### Conan
[ ![Download](https://api.bintray.com/packages/conan/conan-center/ixwebsocket%3A_/images/download.svg) ](https://bintray.com/conan/conan-center/ixwebsocket%3A_/_latestVersion)
Conan is currently supported through a recipe in [Conan Center](https://github.com/conan-io/conan-center-index/tree/master/recipes/ixwebsocket) ([Bintray entry](https://bintray.com/conan/conan-center/ixwebsocket%3A_)).
Package reference
* Conan 1.21.0 and up: `ixwebsocket/7.9.2`
* Earlier versions: `ixwebsocket/7.9.2@_/_`
Note that the version listed here might not be the latest one. See Bintray or the recipe itself for the latest version. If you're migrating from the previous, custom Bintray remote, note that the package reference _has_ to be lower-case.
### Docker
There is a Dockerfile for running the unittest on Linux, and to run the `ws` tool. It is also available on the docker registry.
```
docker run docker.pkg.github.com/machinezone/ixwebsocket/ws:latest --help
```
To use docker-compose you must make a docker container first.
```
$ make docker
...
$ docker compose up &
...
$ docker exec -it ixwebsocket_ws_1 bash
app@ca2340eb9106:~$ ws --help
ws is a websocket tool
...
```

View File

@ -1,76 +0,0 @@
## Implementation details
### Per Message Deflate compression.
The per message deflate compression option is supported. It can lead to very nice bandbwith savings (20x !) if your messages are similar, which is often the case for example for chat applications. All features of the spec should be supported.
### TLS/SSL
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL and mbedTLS can be used on Android, Linux and Windows.
If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x.
### Polling and background thread work
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes.
### Automatic reconnection
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds. This behavior can be disabled.
### Large messages
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully.
### Testing
The library has an interactive tool which is handy for testing compatibility ith other libraries. We have tested our client against Python, Erlang, Node.js, and C++ websocket server libraries.
The unittest tries to be comprehensive, and has been running on multiple platforms, with different sanitizers such as a thread sanitizer to catch data races or the undefined behavior sanitizer.
The regression test is running after each commit on github actions for multiple configurations.
## Limitations
* On some configuration (mostly Android) certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. With mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
## C++ code organization
Here is a simplistic diagram which explains how the code is structured in term of class/modules.
```
+-----------------------+ --- Public
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
| |
+-----------------------+
| |
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
| | Each connection is handled in a new OS thread.
| |
+-----------------------+ --- Private
| |
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
| |
+-----------------------+
| |
| IXWebSocketHandshake | Establish the connection between client and server.
| |
+-----------------------+
| |
| IXWebSocket | ws:// Unencrypted Socket handler
| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS
| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux
| | Can be used on macOS too.
+-----------------------+
| |
| IXSocketConnect | Connect to the remote host (client).
| |
+-----------------------+
| |
| IXDNSLookup | Does DNS resolution asynchronously so that it can be interrupted.
| |
+-----------------------+
```

View File

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

View File

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

View File

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

View File

@ -1,643 +0,0 @@
# Examples
The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage.
## Windows note
To use the network system on Windows, you need to initialize it once with *WSAStartup()* and clean it up with *WSACleanup()*. We have helpers for that which you can use, see below. This init would typically take place in your main function.
```cpp
#include <ixwebsocket/IXNetSystem.h>
int main()
{
ix::initNetSystem();
...
ix::uninitNetSystem();
return 0;
}
```
## WebSocket client API
```cpp
#include <ixwebsocket/IXWebSocket.h>
...
// Our websocket object
ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
webSocket.setUrl(url);
// Optional heart beat, sent every 45 seconds when there is not any traffic
// to make sure that load balancers do not kill an idle connection.
webSocket.setPingInterval(45);
// Per message deflate connection is enabled by default. You can tweak its parameters or disable it
webSocket.disablePerMessageDeflate();
// Setup a callback to be fired when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Message)
{
std::cout << msg->str << std::endl;
}
}
);
// Now that our callback is setup, we can start our background thread and receive messages
webSocket.start();
// Send a message to the server (default to TEXT mode)
webSocket.send("hello world");
// The message can be sent in BINARY mode (useful if you send MsgPack data for example)
webSocket.sendBinary("some serialized binary data");
// ... finally ...
// Stop the connection
webSocket.stop()
```
### Sending messages
`WebSocketSendInfo result = websocket.send("foo")` will send a message.
If the connection was closed, sending will fail, and the success field of the result object will be set to false. There could also be a compression error in which case the compressError field will be set to true. The payloadSize field and wireSize fields will tell you respectively how much bytes the message weight, and how many bytes were sent on the wire (potentially compressed + counting the message header (a few bytes).
There is an optional progress callback that can be passed in as the second argument. If a message is large it will be fragmented into chunks which will be sent independantly. Everytime the we can write a fragment into the OS network cache, the callback will be invoked. If a user wants to cancel a slow send, false should be returned from within the callback.
Here is an example code snippet copied from the ws send sub-command. Each fragment weights 32K, so the total integer is the wireSize divided by 32K. As an example if you are sending 32M of data, uncompressed, total will be 1000. current will be set to 0 for the first fragment, then 1, 2 etc...
```
auto result =
_webSocket.sendBinary(serializedMsg, [this, throttle](int current, int total) -> bool {
spdlog::info("ws_send: Step {} out of {}", current + 1, total);
if (throttle)
{
std::chrono::duration<double, std::milli> duration(10);
std::this_thread::sleep_for(duration);
}
return _connected;
});
```
The `send()` and `sendText()` methods check that the string contains only valid UTF-8 characters. If you know that the string is a valid UTF-8 string you can skip that step and use the `sendUtf8Text` method instead.
With the IXWebSocketSendData overloads of `sendUtf8Text` and `sendBinary` it is possible to not only send std::string but also `std::vector<char>`, `std::vector<uint8_t>` and `char*`.
```
std::vector<uint8_t> data({1, 2, 3, 4});
auto result = webSocket.sendBinary(data);
const char* text = "Hello World!";
result = webSocket.sendUtf8Text(IXWebSocketSendData(text, strlen(text)));
```
### ReadyState
`getReadyState()` returns the state of the connection. There are 4 possible states.
1. ReadyState::Connecting - The connection is not yet open.
2. ReadyState::Open - The connection is open and ready to communicate.
3. ReadyState::Closing - The connection is in the process of closing.
4. ReadyState::Closed - The connection is closed or could not be opened.
### Open and Close notifications
The onMessage event will be fired when the connection is opened or closed. This is similar to the [JavaScript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
```cpp
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "send greetings" << std::endl;
// Headers can be inspected (pairs of string/string)
std::cout << "Handshake Headers:" << std::endl;
for (auto it : msg->headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
std::cout << "disconnected" << std::endl;
// The server can send an explicit code and reason for closing.
// This data can be accessed through the closeInfo object.
std::cout << msg->closeInfo.code << std::endl;
std::cout << msg->closeInfo.reason << std::endl;
}
}
);
```
### Error notification
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
```cpp
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Error)
{
std::stringstream ss;
ss << "Error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->errorInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
std::cout << ss.str() << std::endl;
}
}
);
```
### start, stop
1. `websocket.start()` connect to the remote server and starts the message receiving background thread.
2. `websocket.stop()` disconnect from the remote server and closes the background thread.
### Configuring the remote url
The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url.
```cpp
std::string url("wss://example.com");
websocket.configure(url);
```
### Ping/Pong support
Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec.
```cpp
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Ping ||
msg->type == ix::WebSocketMessageType::Pong)
{
std::cout << "pong data: " << msg->str << std::endl;
}
}
);
```
A ping message can be sent to the server, with an optional data string.
```cpp
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
```
### Heartbeat.
You can configure an optional heart beat / keep-alive, sent every 45 seconds
when there is no any traffic to make sure that load balancers do not kill an
idle connection.
```cpp
webSocket.setPingInterval(45);
```
### Supply extra HTTP headers.
You can set extra HTTP headers to be sent during the WebSocket handshake.
```cpp
WebSocketHttpHeaders headers;
headers["foo"] = "bar";
webSocket.setExtraHeaders(headers);
```
### Subprotocols
You can specify subprotocols to be set during the WebSocket handshake. For more info you can refer to [this doc](https://hpbn.co/websocket/#subprotocol-negotiation).
```cpp
webSocket.addSubprotocol("appProtocol-v1");
webSocket.addSubprotocol("appProtocol-v2");
```
The protocol that the server did accept is available in the open info `protocol` field.
```cpp
std::cout << "protocol: " << msg->openInfo.protocol << std::endl;
```
### Automatic reconnection
Automatic reconnection kicks in when the connection is disconnected without the user consent. This feature is on by default and can be turned off.
```cpp
webSocket.enableAutomaticReconnection(); // turn on
webSocket.disableAutomaticReconnection(); // turn off
bool enabled = webSocket.isAutomaticReconnectionEnabled(); // query state
```
The technique to calculate wait time is called [exponential
backoff](https://docs.aws.amazon.com/general/latest/gr/api-retries.html). Here
are the default waiting times between attempts (from connecting with `ws connect ws://foo.com`)
```
> Connection error: Got bad status connecting to foo.com, status: 301, HTTP Status line: HTTP/1.1 301 Moved Permanently
#retries: 1
Wait time(ms): 100
#retries: 2
Wait time(ms): 200
#retries: 3
Wait time(ms): 400
#retries: 4
Wait time(ms): 800
#retries: 5
Wait time(ms): 1600
#retries: 6
Wait time(ms): 3200
#retries: 7
Wait time(ms): 6400
#retries: 8
Wait time(ms): 10000
```
The waiting time is capped by default at 10s between 2 attempts, but that value
can be changed and queried. The minimum waiting time can also be set.
```cpp
webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s
uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
webSocket.setMinWaitBetweenReconnectionRetries(1000); // 1000ms = 1s
uint32_t m = webSocket.getMinWaitBetweenReconnectionRetries();
```
## Handshake timeout
You can control how long to wait until timing out while waiting for the websocket handshake to be performed.
```
int handshakeTimeoutSecs = 1;
setHandshakeTimeout(handshakeTimeoutSecs);
```
## WebSocket server API
### Legacy api
This api was actually changed to take a weak_ptr<WebSocket> as the first argument to setOnConnectionCallback ; previously it would take a shared_ptr<WebSocket> which was creating cycles and then memory leaks problems.
```cpp
#include <ixwebsocket/IXWebSocketServer.h>
...
// Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters.
int port = 8008;
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
ix::WebSocketServer server(port, host);
server.setOnConnectionCallback(
[&server](std::weak_ptr<WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
std::cout << "Remote ip: " << connectionState->remoteIp << std::endl;
auto ws = webSocket.lock();
if (ws)
{
ws->setOnMessageCallback(
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "New connection" << std::endl;
// A connection state object is available, and has a default id
// You can subclass ConnectionState and pass an alternate factory
// to override it. It is useful if you want to store custom
// attributes per connection (authenticated bool flag, attributes, etc...)
std::cout << "id: " << connectionState->getId() << std::endl;
// The uri the client did connect to.
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
std::cout << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
// For an echo server, we just send back to the client whatever was received by the server
// All connected clients are available in an std::set. See the broadcast cpp example.
// Second parameter tells whether we are sending the message in binary or text mode.
// Here we send it in the same mode as it was received.
auto ws = webSocket.lock();
if (ws)
{
ws->send(msg->str, msg->binary);
}
}
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
// Error handling
return 1;
}
// Per message deflate connection is enabled by default. It can be disabled
// which might be helpful when running on low power devices such as a Rasbery Pi
server.disablePerMessageDeflate();
// Run the server in the background. Server can be stoped by calling server.stop()
server.start();
// Block until server.stop() is called.
server.wait();
```
### New api
The new API does not require to use 2 nested callbacks, which is a bit annoying. The real fix is that there was a memory leak due to a shared_ptr cycle, due to passing down a shared_ptr<WebSocket> down to the callbacks.
The webSocket reference is guaranteed to be always valid ; by design the callback will never be invoked with a null webSocket object.
```cpp
#include <ixwebsocket/IXWebSocketServer.h>
...
// Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters.
int port = 8008;
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
ix::WebSocketServer server(port, host);
server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
// The ConnectionState object contains information about the connection,
// at this point only the client ip address and the port.
std::cout << "Remote ip: " << connectionState->getRemoteIp() << std::endl;
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "New connection" << std::endl;
// A connection state object is available, and has a default id
// You can subclass ConnectionState and pass an alternate factory
// to override it. It is useful if you want to store custom
// attributes per connection (authenticated bool flag, attributes, etc...)
std::cout << "id: " << connectionState->getId() << std::endl;
// The uri the client did connect to.
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
std::cout << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers)
{
std::cout << "\t" << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
// For an echo server, we just send back to the client whatever was received by the server
// All connected clients are available in an std::set. See the broadcast cpp example.
// Second parameter tells whether we are sending the message in binary or text mode.
// Here we send it in the same mode as it was received.
std::cout << "Received: " << msg->str << std::endl;
webSocket.send(msg->str, msg->binary);
}
});
auto res = server.listen();
if (!res.first)
{
// Error handling
return 1;
}
// Per message deflate connection is enabled by default. It can be disabled
// which might be helpful when running on low power devices such as a Rasbery Pi
server.disablePerMessageDeflate();
// Run the server in the background. Server can be stoped by calling server.stop()
server.start();
// Block until server.stop() is called.
server.wait();
```
### Heartbeat
You can configure an optional heartbeat / keep-alive for the WebSocket server. The heartbeat interval can be adjusted or disabled when constructing the `WebSocketServer`. Setting the interval to `-1` disables the heartbeat feature; this is the default setting. The parameter you set will be applied to every `WebSocket` object that the server creates.
To enable a 45 second heartbeat on a `WebSocketServer`:
```cpp
int pingIntervalSeconds = 45;
ix::WebSocketServer server(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily, pingIntervalSeconds);
```
## HTTP client API
```cpp
#include <ixwebsocket/IXHttpClient.h>
...
//
// Preparation
//
HttpClient httpClient;
HttpRequestArgsPtr args = httpClient.createRequest();
// Custom headers can be set
WebSocketHttpHeaders headers;
headers["Foo"] = "bar";
args->extraHeaders = headers;
// Timeout options
args->connectTimeout = connectTimeout;
args->transferTimeout = transferTimeout;
// Redirect options
args->followRedirects = followRedirects;
args->maxRedirects = maxRedirects;
// Misc
args->compress = compress; // Enable gzip compression
args->verbose = verbose;
args->logger = [](const std::string& msg)
{
std::cout << msg;
};
//
// Synchronous Request
//
HttpResponsePtr out;
std::string url = "https://www.google.com";
// HEAD request
out = httpClient.head(url, args);
// GET request
out = httpClient.get(url, args);
// POST request with parameters
HttpParameters httpParameters;
httpParameters["foo"] = "bar";
// HTTP form data can be passed in as well, for multi-part upload of files
HttpFormDataParameters httpFormDataParameters;
httpParameters["baz"] = "booz";
out = httpClient.post(url, httpParameters, httpFormDataParameters, args);
// POST request with a body
out = httpClient.post(url, std::string("foo=bar"), args);
// PUT and PATCH are available too.
//
// Result
//
auto statusCode = response->statusCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
auto errorCode = response->errorCode; // 200, 404, etc...
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string)
auto body = response->body; // All the bytes from the response as an std::string
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
auto uploadSize = response->uploadSize; // Byte count of uploaded data
auto downloadSize = response->downloadSize; // Byte count of downloaded data
//
// Asynchronous Request
//
bool async = true;
HttpClient httpClient(async);
auto args = httpClient.createRequest(url, HttpClient::kGet);
// If you define a chunk callback it will be called repeteadly with the
// incoming data. This allows to process data on the go or write it to disk
// instead of accumulating the data in memory.
args.onChunkCallback = [](const std::string& data)
{
// process data
};
// Push the request to a queue,
bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
{
// This callback execute in a background thread. Make sure you uses appropriate protection such as mutex
auto statusCode = response->statusCode; // acess results
// response->body is empty if onChunkCallback was used
}
);
// ok will be false if your httpClient is not async
// A request in progress can be cancelled by setting the cancel flag. It does nothing if the request already completed.
args->cancel = true;
```
See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart.
## HTTP server API
```cpp
#include <ixwebsocket/IXHttpServer.h>
ix::HttpServer server(port, hostname);
auto res = server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
return 1;
}
server.start();
server.wait();
```
If you want to handle how requests are processed, implement the setOnConnectionCallback callback, which takes an HttpRequestPtr as input, and returns an HttpResponsePtr. You can look at HttpServer::setDefaultConnectionCallback for a slightly more advanced callback example.
```cpp
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
// Build a string for the response
std::stringstream ss;
ss << connectionState->getRemoteIp();
<< " "
<< request->method
<< " "
<< request->uri;
std::string content = ss.str();
return std::make_shared<HttpResponse>(200, "OK",
HttpErrorCode::Ok,
WebSocketHttpHeaders(),
content);
}
```
## TLS support and configuration
To leverage TLS features, the library must be compiled with the option `USE_TLS=1`.
If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x.
Then, secure sockets are automatically used when connecting to a `wss://*` url.
Additional TLS options can be configured by passing a `ix::SocketTLSOptions` instance to the
`setTLSOptions` on `ix::WebSocket` (or `ix::WebSocketServer` or `ix::HttpServer`)
```cpp
webSocket.setTLSOptions({
.certFile = "path/to/cert/file.pem",
.keyFile = "path/to/key/file.pem",
.caFile = "path/to/trust/bundle/file.pem", // as a file, or in memory buffer in PEM format
.tls = true // required in server mode
});
```
Specifying `certFile` and `keyFile` configures the certificate that will be used to communicate with TLS peers.
On a client, this is only necessary for connecting to servers that require a client certificate.
On a server, this is necessary for TLS support.
Specifying `caFile` configures the trusted roots bundle file (in PEM format) that will be used to verify peer certificates.
- The special value of `SYSTEM` (the default) indicates that the system-configured trust bundle should be used; this is generally what you want when connecting to any publicly exposed API/server.
- The special value of `NONE` can be used to disable peer verification; this is only recommended to rule out certificate verification when testing connectivity.
- If the value contain the special value `-----BEGIN CERTIFICATE-----`, the value will be read from memory, and not from a file. This is convenient on platforms like Android where reading / writing to the file system can be challenging without proper permissions, or without knowing the location of a temp directory.
For a client, specifying `caFile` can be used if connecting to a server that uses a self-signed cert, or when using a custom CA in an internal environment.
For a server, specifying `caFile` implies that:
1. You require clients to present a certificate
1. It must be signed by one of the trusted roots in the file
By default, a destination's hostname is always validated against the certificate that it presents. To accept certificates with any hostname, set `ix::SocketTLSOptions::disable_hostname_validation` to `true`.

View File

@ -1,308 +0,0 @@
## General
ws is a command line tool that should exercise most of the IXWebSocket code, and provide example code.
```
ws is a websocket tool
Usage: ws [OPTIONS] SUBCOMMAND
Options:
-h,--help Print this help message and exit
Subcommands:
send Send a file
receive Receive a file
transfer Broadcasting server
connect Connect to a remote server
chat Group chat
echo_server Echo server
broadcast_server Broadcasting server
ping Ping pong
curl HTTP Client
httpd HTTP server
```
## curl
The curl subcommand try to be compatible with the curl syntax, to fetch http pages.
Making a HEAD request with the -I parameter.
```
$ ws curl -I https://www.google.com/
Accept-Ranges: none
Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
Date: Tue, 08 Oct 2019 21:36:57 GMT
Expires: -1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
Set-Cookie: NID=188=ASwfz8GrXQrHCLqAz-AndLOMLcz0rC9yecnf3h0yXZxRL3rTufTU_GDDwERp7qQL7LZ_EB8gCRyPXGERyOSAgaqgnrkoTmvWrwFemRLMaOZ896GrHobi5fV7VLklnSG2w48Gj8xMlwxfP7Z-bX-xR9UZxep1tHM6UmFQdD_GkBE; expires=Wed, 08-Apr-2020 21:36:57 GMT; path=/; domain=.google.com; HttpOnly
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0
Upload size: 143
Download size: 0
Status: 200
```
Making a POST request with the -F parameter.
```
$ ws curl -F foo=bar https://httpbin.org/post
foo: bar
Downloaded 438 bytes out of 438
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Encoding:
Content-Length: 438
Content-Type: application/json
Date: Tue, 08 Oct 2019 21:47:54 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Upload size: 219
Download size: 438
Status: 200
payload: {
"args": {},
"data": "",
"files": {},
"form": {
"foo": "bar"
},
"headers": {
"Accept": "*/*",
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "ixwebsocket/7.0.0 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11"
},
"json": null,
"origin": "155.94.127.118, 155.94.127.118",
"url": "https://httpbin.org/post"
}
```
Passing in a custom header with -H.
```
$ ws curl -F foo=bar -H 'my_custom_header: baz' https://httpbin.org/post
my_custom_header: baz
foo: bar
Downloaded 470 bytes out of 470
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Encoding:
Content-Length: 470
Content-Type: application/json
Date: Tue, 08 Oct 2019 21:50:25 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Upload size: 243
Download size: 470
Status: 200
payload: {
"args": {},
"data": "",
"files": {},
"form": {
"foo": "bar"
},
"headers": {
"Accept": "*/*",
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"My-Custom-Header": "baz",
"User-Agent": "ixwebsocket/7.0.0 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11"
},
"json": null,
"origin": "155.94.127.118, 155.94.127.118",
"url": "https://httpbin.org/post"
}
```
## connect
The connect command connects to a websocket endpoint, and starts an interactive prompt. Line editing, such as using the direction keys to fetch the last thing you tried to type) is provided. That command is pretty useful to try to send random data to an endpoint and verify that the service handles it with grace (such as sending invalid json).
```
ws connect wss://echo.websocket.org
Type Ctrl-D to exit prompt...
Connecting to url: wss://echo.websocket.org
> ws_connect: connected
Uri: /
Handshake Headers:
Connection: Upgrade
Date: Tue, 08 Oct 2019 21:38:44 GMT
Sec-WebSocket-Accept: 2j6LBScZveqrMx1W/GJkCWvZo3M=
sec-websocket-extensions:
Server: Kaazing Gateway
Upgrade: websocket
Received ping
Received ping
Received ping
Hello world !
> Received 13 bytes
ws_connect: received message: Hello world !
> Hello world !
> Received 13 bytes
ws_connect: received message: Hello world !
```
```
ws connect 'ws://jeanserge.com/v2?appkey=_pubsub'
Type Ctrl-D to exit prompt...
Connecting to url: ws://jeanserge.com/v2?appkey=_pubsub
> ws_connect: connected
Uri: /v2?appkey=_pubsub
Handshake Headers:
Connection: Upgrade
Date: Tue, 08 Oct 2019 21:45:28 GMT
Sec-WebSocket-Accept: LYHmjh9Gsu/Yw7aumQqyPObOEV4=
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
Server: Python/3.7 websockets/8.0.2
Upgrade: websocket
bababababababab
> ws_connect: connection closed: code 1000 reason
ws_connect: connected
Uri: /v2?appkey=_pubsub
Handshake Headers:
Connection: Upgrade
Date: Tue, 08 Oct 2019 21:45:44 GMT
Sec-WebSocket-Accept: I1rqxdLgTU+opPi5/zKPBTuXdLw=
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
Server: Python/3.7 websockets/8.0.2
Upgrade: websocket
```
It is possible to pass custom HTTP header when doing the connection handshake,
the remote server might process them to implement a simple authorization
scheme.
```
src$ ws connect -H Authorization:supersecret ws://localhost:8008
Type Ctrl-D to exit prompt...
[2020-12-17 22:35:08.732] [info] Authorization: supersecret
Connecting to url: ws://localhost:8008
> [2020-12-17 22:35:08.736] [info] ws_connect: connected
[2020-12-17 22:35:08.736] [info] Uri: /
[2020-12-17 22:35:08.736] [info] Headers:
[2020-12-17 22:35:08.736] [info] Connection: Upgrade
[2020-12-17 22:35:08.736] [info] Sec-WebSocket-Accept: 2yaTFcdwn8KL6IzSMj2u6Le7KTg=
[2020-12-17 22:35:08.736] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
[2020-12-17 22:35:08.736] [info] Server: ixwebsocket/11.0.4 macos ssl/SecureTransport zlib 1.2.11
[2020-12-17 22:35:08.736] [info] Upgrade: websocket
[2020-12-17 22:35:08.736] [info] Received 25 bytes
ws_connect: received message: Authorization suceeded!
[2020-12-17 22:35:08.736] [info] Received pong ixwebsocket::heartbeat::30s::0
hello
> [2020-12-17 22:35:25.157] [info] Received 7 bytes
ws_connect: received message: hello
```
If the wrong header is passed in, the server would close the connection with a custom close code (>4000, and <4999).
```
[2020-12-17 22:39:37.044] [info] Upgrade: websocket
ws_connect: connection closed: code 4001 reason Permission denied
```
## echo server
The ws echo server will respond what the client just sent him. If we use the
simple --http_authorization_header we can enforce that client need to pass a
special value in the Authorization header to connect.
```
$ ws echo_server --http_authorization_header supersecret
[2020-12-17 22:35:06.192] [info] Listening on 127.0.0.1:8008
[2020-12-17 22:35:08.735] [info] New connection
[2020-12-17 22:35:08.735] [info] remote ip: 127.0.0.1
[2020-12-17 22:35:08.735] [info] id: 0
[2020-12-17 22:35:08.735] [info] Uri: /
[2020-12-17 22:35:08.735] [info] Headers:
[2020-12-17 22:35:08.735] [info] Authorization: supersecret
[2020-12-17 22:35:08.735] [info] Connection: Upgrade
[2020-12-17 22:35:08.735] [info] Host: localhost:8008
[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Key: eFF2Gf25dC7eC15Ab1135G==
[2020-12-17 22:35:08.735] [info] Sec-WebSocket-Version: 13
[2020-12-17 22:35:08.735] [info] Upgrade: websocket
[2020-12-17 22:35:08.735] [info] User-Agent: ixwebsocket/11.0.4 macos ssl/SecureTransport zlib 1.2.11
[2020-12-17 22:35:25.157] [info] Received 7 bytes
```
## Websocket proxy
```
ws proxy_server --remote_host ws://127.0.0.1:9000 -v
Listening on 127.0.0.1:8008
```
If you connect to ws://127.0.0.1:8008, the proxy will connect to ws://127.0.0.1:9000 and pass all traffic to this server.
You can also use a more complex setup if you want to redirect to different websocket servers based on the hostname your client is trying to connect to. If you have multiple CNAME aliases that point to the same server.
A JSON config file is used to express that mapping ; here connecting to echo.jeanserge.com will proxy the client to ws://localhost:8008 on the local machine (which actually runs ws echo_server), while connecting to bavarde.jeanserge.com will proxy the client to ws://localhost:5678 where a cobra python server is running. As a side note you will need a wildcard SSL certificate if you want to have SSL enabled on that machine.
```
echo.jeanserge.com=ws://localhost:8008
bavarde.jeanserge.com=ws://localhost:5678
```
The --config_path option is required to instruct ws proxy_server to read that file.
```
ws proxy_server --config_path proxyConfig.json --port 8765
```
## File transfer
```
# Start transfer server, which is just a broadcast server at this point
ws transfer # running on port 8080.
# Start receiver first
ws receive ws://localhost:8080
# Then send a file. File will be received and written to disk by the receiver process
ws send ws://localhost:8080 /file/to/path
```
## HTTP Client
```
$ ws curl --help
HTTP Client
Usage: ws curl [OPTIONS] url
Positionals:
url TEXT REQUIRED Connection url
Options:
-h,--help Print this help message and exit
-d TEXT Form data
-F TEXT Form data
-H TEXT Header
--output TEXT Output file
-I Send a HEAD request
-L Follow redirects
--max-redirects INT Max Redirects
-v Verbose
-O Save output to disk
--compress Enable gzip compression
--connect-timeout INT Connection timeout
--transfer-timeout INT Transfer timeout
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,20 +6,18 @@
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include <cassert>
#include <chrono> #include <chrono>
namespace ix namespace ix
{ {
CancellationRequest makeCancellationRequestWithTimeout( CancellationRequest makeCancellationRequestWithTimeout(int secs,
int secs, std::atomic<bool>& requestInitCancellation) std::atomic<bool>& requestInitCancellation)
{ {
assert(secs > 0);
auto start = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now();
auto timeout = std::chrono::seconds(secs); auto timeout = std::chrono::seconds(secs);
auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool { auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool
{
// Was an explicit cancellation requested ? // Was an explicit cancellation requested ?
if (requestInitCancellation) return true; if (requestInitCancellation) return true;
@ -32,4 +30,4 @@ namespace ix
return isCancellationRequested; return isCancellationRequested;
} }
} // namespace ix }

View File

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

View File

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

View File

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

View File

@ -4,58 +4,39 @@
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved. * Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/ */
//
// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to returns wchar_t
// which is different from all other platforms. We want the non unicode version.
// See https://github.com/microsoft/vcpkg/pull/11030
// We could do this in IXNetSystem.cpp but so far we are only using gai_strerror in here.
//
#ifdef _UNICODE
#undef _UNICODE
#endif
#ifdef UNICODE
#undef UNICODE
#endif
#include "IXDNSLookup.h" #include "IXDNSLookup.h"
#include "IXNetSystem.h" #include "IXNetSystem.h"
#include <chrono>
#include <string.h> #include <string.h>
#include <thread> #include <chrono>
#include <utility>
// mingw build quirks
#if defined(_WIN32) && defined(__GNUC__)
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV NI_NUMERICSERV
#endif
#ifndef AI_ADDRCONFIG
#define AI_ADDRCONFIG LUP_ADDRCONFIG
#endif
#endif
#ifdef __APPLE__
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
#endif
namespace ix namespace ix
{ {
const int64_t DNSLookup::kDefaultWait = 1; // ms const int64_t DNSLookup::kDefaultWait = 10; // ms
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) std::atomic<uint64_t> DNSLookup::_nextId(0);
: _hostname(hostname) std::set<uint64_t> DNSLookup::_activeJobs;
, _port(port) std::mutex DNSLookup::_activeJobsMutex;
, _wait(wait)
, _res(nullptr) DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
, _done(false) _port(port),
_wait(wait),
_res(nullptr),
_done(false),
_id(_nextId++)
{ {
; setHostname(hostname);
} }
DNSLookup::AddrInfoPtr DNSLookup::getAddrInfo(const std::string& hostname, DNSLookup::~DNSLookup()
{
// Remove this job from the active jobs list
std::lock_guard<std::mutex> lock(_activeJobsMutex);
_activeJobs.erase(_id);
}
// we want hostname to be copied, not passed as a const reference
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
int port, int port,
std::string& errMsg) std::string& errMsg)
{ {
@ -68,40 +49,41 @@ namespace ix
std::string sport = std::to_string(port); std::string sport = std::to_string(port);
struct addrinfo* res; struct addrinfo* res;
int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res); int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(),
&hints, &res);
if (getaddrinfo_result) if (getaddrinfo_result)
{ {
errMsg = gai_strerror(getaddrinfo_result); errMsg = gai_strerror(getaddrinfo_result);
res = nullptr; res = nullptr;
} }
return AddrInfoPtr{ res, freeaddrinfo }; return res;
} }
DNSLookup::AddrInfoPtr DNSLookup::resolve(std::string& errMsg, struct addrinfo* DNSLookup::resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested, const CancellationRequest& isCancellationRequested,
bool cancellable) bool blocking)
{ {
return cancellable ? resolveCancellable(errMsg, isCancellationRequested) return blocking ? resolveBlocking(errMsg, isCancellationRequested)
: resolveUnCancellable(errMsg, isCancellationRequested); : resolveAsync(errMsg, isCancellationRequested);
} }
DNSLookup::AddrInfoPtr DNSLookup::resolveUnCancellable( struct addrinfo* DNSLookup::resolveBlocking(std::string& errMsg,
std::string& errMsg, const CancellationRequest& isCancellationRequested) const CancellationRequest& isCancellationRequested)
{ {
errMsg = "no error"; errMsg = "no error";
// Maybe a cancellation request got in before the background thread terminated ? // Maybe a cancellation request got in before the background thread terminated ?
if (isCancellationRequested()) if (isCancellationRequested && isCancellationRequested())
{ {
errMsg = "cancellation requested"; errMsg = "cancellation requested";
return nullptr; return nullptr;
} }
return getAddrInfo(_hostname, _port, errMsg); return getAddrInfo(getHostname(), _port, errMsg);
} }
DNSLookup::AddrInfoPtr DNSLookup::resolveCancellable( struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
std::string& errMsg, const CancellationRequest& isCancellationRequested) const CancellationRequest& isCancellationRequested)
{ {
errMsg = "no error"; errMsg = "no error";
@ -113,31 +95,33 @@ namespace ix
// if you need a second lookup. // if you need a second lookup.
} }
// Record job in the active Job set
{
std::lock_guard<std::mutex> lock(_activeJobsMutex);
_activeJobs.insert(_id);
}
// //
// Good resource on thread forced termination // Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread // https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
// //
auto ptr = shared_from_this(); _thread = std::thread(&DNSLookup::run, this, _id, getHostname(), _port);
std::weak_ptr<DNSLookup> self(ptr); _thread.detach();
int port = _port; std::unique_lock<std::mutex> lock(_conditionVariableMutex);
std::string hostname(_hostname);
// We make the background thread doing the work a shared pointer
// instead of a member variable, because it can keep running when
// this object goes out of scope, in case of cancellation
auto t = std::make_shared<std::thread>(&DNSLookup::run, this, self, hostname, port);
t->detach();
while (!_done) while (!_done)
{ {
// Wait for 1 milliseconds, to see if the bg thread has terminated. // Wait for 10 milliseconds on the condition variable, to see
// We do not use a condition variable to wait, as destroying this one // if the bg thread has terminated.
// if the bg thread is alive can cause undefined behavior. if (_condition.wait_for(lock, std::chrono::milliseconds(_wait)) == std::cv_status::no_timeout)
std::this_thread::sleep_for(std::chrono::milliseconds(_wait)); {
// Background thread has terminated, so we can break of this loop
break;
}
// Were we cancelled ? // Were we cancelled ?
if (isCancellationRequested()) if (isCancellationRequested && isCancellationRequested())
{ {
errMsg = "cancellation requested"; errMsg = "cancellation requested";
return nullptr; return nullptr;
@ -145,7 +129,7 @@ namespace ix
} }
// Maybe a cancellation request got in before the bg terminated ? // Maybe a cancellation request got in before the bg terminated ?
if (isCancellationRequested()) if (isCancellationRequested && isCancellationRequested())
{ {
errMsg = "cancellation requested"; errMsg = "cancellation requested";
return nullptr; return nullptr;
@ -155,24 +139,41 @@ namespace ix
return getRes(); return getRes();
} }
void DNSLookup::run(std::weak_ptr<DNSLookup> self, void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
std::string hostname,
int port) // thread runner
{ {
// We don't want to read or write into members variables of an object that could be // 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 // gone, so we use temporary variables (res) or we pass in by copy everything that
// getAddrInfo needs to work. // getAddrInfo needs to work.
std::string errMsg; std::string errMsg;
auto res = getAddrInfo(hostname, port, errMsg); struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock()) // if this isn't an active job, and the control thread is gone
// there is nothing to do, and we don't want to touch the defunct
// object data structure such as _errMsg or _condition
std::lock_guard<std::mutex> lock(_activeJobsMutex);
if (_activeJobs.count(id) == 0)
{ {
return;
}
// Copy result into the member variables // Copy result into the member variables
setRes(res); setRes(res);
setErrMsg(errMsg); setErrMsg(errMsg);
_condition.notify_one();
_done = true; _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) void DNSLookup::setErrMsg(const std::string& errMsg)
@ -187,15 +188,15 @@ namespace ix
return _errMsg; return _errMsg;
} }
void DNSLookup::setRes(DNSLookup::AddrInfoPtr addr) void DNSLookup::setRes(struct addrinfo* addr)
{ {
std::lock_guard<std::mutex> lock(_resMutex); std::lock_guard<std::mutex> lock(_resMutex);
_res = std::move(addr); _res = addr;
} }
DNSLookup::AddrInfoPtr DNSLookup::getRes() struct addrinfo* DNSLookup::getRes()
{ {
std::lock_guard<std::mutex> lock(_resMutex); std::lock_guard<std::mutex> lock(_resMutex);
return _res; return _res;
} }
} // namespace ix }

View File

@ -11,57 +11,71 @@
#pragma once #pragma once
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include <mutex>
#include <set>
#include <string> #include <string>
#include <thread>
#include <atomic>
#include <condition_variable>
#include <set>
struct addrinfo; struct addrinfo;
namespace ix namespace ix
{ {
class DNSLookup : public std::enable_shared_from_this<DNSLookup> class DNSLookup {
{
public: public:
using AddrInfoPtr = std::shared_ptr<addrinfo>; DNSLookup(const std::string& hostname,
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait); int port,
~DNSLookup() = default; int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup();
AddrInfoPtr resolve(std::string& errMsg, struct addrinfo* resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested, const CancellationRequest& isCancellationRequested,
bool cancellable = true); bool blocking = false);
private: private:
AddrInfoPtr resolveCancellable(std::string& errMsg, struct addrinfo* resolveAsync(std::string& errMsg,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
AddrInfoPtr resolveUnCancellable(std::string& errMsg, struct addrinfo* resolveBlocking(std::string& errMsg,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
AddrInfoPtr getAddrInfo(const std::string& hostname, static struct addrinfo* getAddrInfo(const std::string& hostname,
int port, int port,
std::string& errMsg); std::string& errMsg);
void run(std::weak_ptr<DNSLookup> self, std::string hostname, int port); // thread runner void run(uint64_t id, const std::string& hostname, int port); // thread runner
void setHostname(const std::string& hostname);
const std::string& getHostname();
void setErrMsg(const std::string& errMsg); void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg(); const std::string& getErrMsg();
void setRes(AddrInfoPtr addr); void setRes(struct addrinfo* addr);
AddrInfoPtr getRes(); struct addrinfo* getRes();
std::string _hostname; std::string _hostname;
std::mutex _hostnameMutex;
int _port; int _port;
int64_t _wait;
const static int64_t kDefaultWait;
AddrInfoPtr _res; int64_t _wait;
struct addrinfo* _res;
std::mutex _resMutex; std::mutex _resMutex;
std::string _errMsg; std::string _errMsg;
std::mutex _errMsgMutex; std::mutex _errMsgMutex;
std::atomic<bool> _done; 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,44 +0,0 @@
/*
* IXExponentialBackoff.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXExponentialBackoff.h"
#include <cmath>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries)
{
// It's easy with a power function to go beyond 2^32, and then
// have unexpected results, so prepare for that
const uint32_t maxRetryCountWithoutOverflow = 26;
uint32_t waitTime = 0;
if (retryCount < maxRetryCountWithoutOverflow)
{
waitTime = std::pow(2, retryCount) * 100;
}
if (waitTime < minWaitBetweenReconnectionRetries)
{
waitTime = minWaitBetweenReconnectionRetries;
}
if (waitTime > maxWaitBetweenReconnectionRetries)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
if (retryCount >= maxRetryCountWithoutOverflow)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
return waitTime;
}
} // namespace ix

View File

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

View File

@ -1,97 +0,0 @@
/*
* IXGetFreePort.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
// Using inet_addr will trigger an error on uwp without this
// FIXME: use a different api
#ifdef _WIN32
#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif
#endif
#include "IXGetFreePort.h"
#include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXSocket.h>
#include <random>
#include <string>
namespace ix
{
int getAnyFreePortRandom()
{
std::random_device rd;
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
return dist(rd);
}
int getAnyFreePort()
{
socket_t sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
return getAnyFreePortRandom();
}
int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0)
{
return getAnyFreePortRandom();
}
// Bind to port 0. This is the standard way to get a free port.
struct sockaddr_in server; // server address information
server.sin_family = AF_INET;
server.sin_port = htons(0);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
struct sockaddr_in sa; // server address information
socklen_t len = sizeof(sa);
if (getsockname(sockfd, (struct sockaddr*) &sa, &len) < 0)
{
Socket::closeSocket(sockfd);
return getAnyFreePortRandom();
}
int port = ntohs(sa.sin_port);
Socket::closeSocket(sockfd);
return port;
}
int getFreePort()
{
while (true)
{
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
int port = getAnyFreePortRandom();
#else
int port = getAnyFreePort();
#endif
#else
int port = getAnyFreePort();
#endif
//
// Only port above 1024 can be used by non root users, but for some
// reason I got port 7 returned with macOS when binding on port 0...
//
if (port > 1024)
{
return port;
}
}
return -1;
}
} // namespace ix

View File

@ -1,12 +0,0 @@
/*
* IXGetFreePort.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#pragma once
namespace ix
{
int getFreePort();
} // namespace ix

View File

@ -1,120 +0,0 @@
/*
* IXGzipCodec.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXGzipCodec.h"
#include "IXBench.h"
#include <array>
#include <string.h>
#ifdef IXWEBSOCKET_USE_ZLIB
#include <zlib.h>
#endif
namespace ix
{
std::string gzipCompress(const std::string& str)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return std::string();
#else
z_stream zs; // z_stream is zlib's control structure
memset(&zs, 0, sizeof(zs));
// deflateInit2 configure the file format: request gzip instead of deflate
const int windowBits = 15;
const int GZIP_ENCODING = 16;
deflateInit2(&zs,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
windowBits | GZIP_ENCODING,
8,
Z_DEFAULT_STRATEGY);
zs.next_in = (Bytef*) str.data();
zs.avail_in = (uInt) str.size(); // set the z_stream's input
int ret;
char outbuffer[32768];
std::string outstring;
// retrieve the compressed bytes blockwise
do
{
zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
zs.avail_out = sizeof(outbuffer);
ret = deflate(&zs, Z_FINISH);
if (outstring.size() < zs.total_out)
{
// append the block to the output string
outstring.append(outbuffer, zs.total_out - outstring.size());
}
} while (ret == Z_OK);
deflateEnd(&zs);
return outstring;
#endif // IXWEBSOCKET_USE_ZLIB
}
#ifdef IXWEBSOCKET_USE_DEFLATE
static uint32_t loadDecompressedGzipSize(const uint8_t* p)
{
return ((uint32_t) p[0] << 0) | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) |
((uint32_t) p[3] << 24);
}
#endif
bool gzipDecompress(const std::string& in, std::string& out)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return false;
#else
z_stream inflateState;
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::array<unsigned char, kBufferSize> compressBuffer;
do
{
inflateState.avail_out = (uInt) kBufferSize;
inflateState.next_out = &compressBuffer.front();
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.front()),
kBufferSize - inflateState.avail_out);
} while (inflateState.avail_out == 0);
inflateEnd(&inflateState);
return true;
#endif // IXWEBSOCKET_USE_ZLIB
}
} // namespace ix

View File

@ -1,15 +0,0 @@
/*
* IXGzipCodec.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
std::string gzipCompress(const std::string& str);
bool gzipDecompress(const std::string& in, std::string& out);
} // namespace ix

View File

@ -1,217 +0,0 @@
/*
* IXHttp.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttp.h"
#include "IXCancellationRequest.h"
#include "IXGzipCodec.h"
#include "IXSocket.h"
#include <sstream>
#include <vector>
namespace ix
{
std::string Http::trim(const std::string& str)
{
std::string out;
for (auto c : str)
{
if (c != ' ' && c != '\n' && c != '\r')
{
out += c;
}
}
return out;
}
std::pair<std::string, int> Http::parseStatusLine(const std::string& line)
{
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
std::string token;
std::stringstream tokenStream(line);
std::vector<std::string> tokens;
// Split by ' '
while (std::getline(tokenStream, token, ' '))
{
tokens.push_back(token);
}
std::string httpVersion;
if (tokens.size() >= 1)
{
httpVersion = trim(tokens[0]);
}
int statusCode = -1;
if (tokens.size() >= 2)
{
std::stringstream ss;
ss << trim(tokens[1]);
ss >> statusCode;
}
return std::make_pair(httpVersion, statusCode);
}
std::tuple<std::string, std::string, std::string> Http::parseRequestLine(
const std::string& line)
{
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
std::string token;
std::stringstream tokenStream(line);
std::vector<std::string> tokens;
// Split by ' '
while (std::getline(tokenStream, token, ' '))
{
tokens.push_back(token);
}
std::string method;
if (tokens.size() >= 1)
{
method = trim(tokens[0]);
}
std::string requestUri;
if (tokens.size() >= 2)
{
requestUri = trim(tokens[1]);
}
std::string httpVersion;
if (tokens.size() >= 3)
{
httpVersion = trim(tokens[2]);
}
return std::make_tuple(method, requestUri, httpVersion);
}
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(
std::unique_ptr<Socket>& socket, int timeoutSecs)
{
HttpRequestPtr httpRequest;
std::atomic<bool> requestInitCancellation(false);
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);
}
std::string body;
if (headers.find("Content-Length") != headers.end())
{
int contentLength = 0;
{
const char* p = headers["Content-Length"].c_str();
char* p_end{};
errno = 0;
long val = std::strtol(p, &p_end, 10);
if (p_end == p // invalid argument
|| errno == ERANGE // out of range
|| val < std::numeric_limits<int>::min()
|| val > std::numeric_limits<int>::max()) {
return std::make_tuple(
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
}
contentLength = val;
}
if (contentLength < 0)
{
return std::make_tuple(
false, "Error: 'Content-Length' should be a positive integer", httpRequest);
}
auto res = socket->readBytes(contentLength, nullptr, nullptr, isCancellationRequested);
if (!res.first)
{
return std::make_tuple(
false, std::string("Error reading request: ") + res.second, httpRequest);
}
body = res.second;
}
// If the content was compressed with gzip, decode it
if (headers["Content-Encoding"] == "gzip")
{
#ifdef IXWEBSOCKET_USE_ZLIB
std::string decompressedPayload;
if (!gzipDecompress(body, decompressedPayload))
{
return std::make_tuple(
false, std::string("Error during gzip decompression of the body"), httpRequest);
}
body = decompressedPayload;
#else
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
return std::make_tuple(false, errorMsg, httpRequest);
#endif
}
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, body, headers);
return std::make_tuple(true, "", httpRequest);
}
bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket)
{
// Write the response to the socket
std::stringstream ss;
ss << "HTTP/1.1 ";
ss << response->statusCode;
ss << " ";
ss << response->description;
ss << "\r\n";
if (!socket->writeBytes(ss.str(), nullptr))
{
return false;
}
// Write headers
ss.str("");
ss << "Content-Length: " << response->body.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->body.empty() ? true : socket->writeBytes(response->body, nullptr);
}
} // namespace ix

View File

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

View File

@ -5,242 +5,96 @@
*/ */
#include "IXHttpClient.h" #include "IXHttpClient.h"
#include "IXGzipCodec.h"
#include "IXSocketFactory.h"
#include "IXUrlParser.h" #include "IXUrlParser.h"
#include "IXUserAgent.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include <assert.h> #include "IXSocketFactory.h"
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <random>
#include <sstream> #include <sstream>
#include <iomanip>
#include <vector> #include <vector>
#include <cstring>
#include <zlib.h>
namespace ix namespace ix
{ {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
const std::string HttpClient::kPost = "POST"; const std::string HttpClient::kPost = "POST";
const std::string HttpClient::kGet = "GET"; const std::string HttpClient::kGet = "GET";
const std::string HttpClient::kHead = "HEAD"; const std::string HttpClient::kHead = "HEAD";
const std::string HttpClient::kDelete = "DELETE";
const std::string HttpClient::kPut = "PUT";
const std::string HttpClient::kPatch = "PATCH";
HttpClient::HttpClient(bool async) HttpClient::HttpClient()
: _async(async)
, _stop(false)
, _forceBody(false)
{ {
if (!_async) return;
_thread = std::thread(&HttpClient::run, this);
} }
HttpClient::~HttpClient() HttpClient::~HttpClient()
{ {
if (!_thread.joinable()) return;
_stop = true;
_condition.notify_one();
_thread.join();
} }
void HttpClient::setTLSOptions(const SocketTLSOptions& tlsOptions) HttpResponse HttpClient::request(
{ const std::string& url,
_tlsOptions = tlsOptions;
}
void HttpClient::setForceBody(bool value)
{
_forceBody = value;
}
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
{
auto request = std::make_shared<HttpRequestArgs>();
request->url = url;
request->verb = verb;
return request;
}
bool HttpClient::performRequest(HttpRequestArgsPtr args,
const OnResponseCallback& onResponseCallback)
{
assert(_async && "HttpClient needs its async parameter set to true "
"in order to call performRequest");
if (!_async) return false;
// Enqueue the task
{
// acquire lock
std::unique_lock<std::mutex> lock(_queueMutex);
// add the task
_queue.push(std::make_pair(args, onResponseCallback));
} // release lock
// wake up one thread
_condition.notify_one();
return true;
}
void HttpClient::run()
{
while (true)
{
HttpRequestArgsPtr args;
OnResponseCallback onResponseCallback;
{
std::unique_lock<std::mutex> lock(_queueMutex);
while (!_stop && _queue.empty())
{
_condition.wait(lock);
}
if (_stop) return;
auto p = _queue.front();
_queue.pop();
args = p.first;
onResponseCallback = p.second;
}
if (_stop) return;
HttpResponsePtr response = request(args->url, args->verb, args->body, args);
onResponseCallback(response);
if (_stop) return;
}
}
HttpResponsePtr HttpClient::request(const std::string& url,
const std::string& verb, const std::string& verb,
const std::string& body, const std::string& body,
HttpRequestArgsPtr args, const HttpRequestArgs& args,
int redirects) int redirects)
{ {
// We only have one socket connection, so we cannot
// make multiple requests concurrently.
std::lock_guard<std::recursive_mutex> lock(_mutex);
uint64_t uploadSize = 0; uint64_t uploadSize = 0;
uint64_t downloadSize = 0; uint64_t downloadSize = 0;
int code = 0; int code = 0;
WebSocketHttpHeaders headers; WebSocketHttpHeaders headers;
std::string payload; std::string payload;
std::string description;
std::string protocol, host, path, query; std::string protocol, host, path, query;
int port; int port;
bool isProtocolDefaultPort; bool websocket = false;
if (!UrlParser::parse(url, protocol, host, path, query, port, isProtocolDefaultPort)) if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
{ {
std::stringstream ss; std::stringstream ss;
ss << "Cannot parse url: " << url; ss << "Cannot parse url: " << url;
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_UrlMalformed,
description, headers, payload, ss.str(),
HttpErrorCode::UrlMalformed, uploadSize, downloadSize);
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
} }
bool tls = protocol == "https"; bool tls = protocol == "https";
std::string errorMsg; std::string errorMsg;
_socket = createSocket(tls, -1, errorMsg, _tlsOptions); _socket = createSocket(tls, errorMsg);
if (!_socket) if (!_socket)
{ {
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_CannotCreateSocket,
description, headers, payload, errorMsg,
HttpErrorCode::CannotCreateSocket, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
// Build request string // Build request string
std::stringstream ss; std::stringstream ss;
ss << verb << " " << path << " HTTP/1.1\r\n"; ss << verb << " " << path << " HTTP/1.1\r\n";
ss << "Host: " << host; ss << "Host: " << host << "\r\n";
if (!isProtocolDefaultPort) ss << "User-Agent: ixwebsocket/1.0.0" << "\r\n";
{ ss << "Accept: */*" << "\r\n";
ss << ":" << port;
}
ss << "\r\n";
#ifdef IXWEBSOCKET_USE_ZLIB if (args.compress)
if (args->compress && !args->onChunkCallback)
{ {
ss << "Accept-Encoding: gzip" ss << "Accept-Encoding: gzip" << "\r\n";
<< "\r\n";
} }
#endif
// Append extra headers // Append extra headers
for (auto&& it : args->extraHeaders) for (auto&& it : args.extraHeaders)
{ {
ss << it.first << ": " << it.second << "\r\n"; ss << it.first << ": " << it.second << "\r\n";
} }
// Set a default Accept header if none is present if (verb == kPost)
if (args->extraHeaders.find("Accept") == args->extraHeaders.end())
{ {
ss << "Accept: */*"
<< "\r\n";
}
// Set a default User agent if none is present
if (args->extraHeaders.find("User-Agent") == args->extraHeaders.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
// Set an origin header if missing
if (args->extraHeaders.find("Origin") == args->extraHeaders.end())
{
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
}
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
{
// Set request compression header
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compressRequest)
{
ss << "Content-Encoding: gzip"
<< "\r\n";
}
#endif
ss << "Content-Length: " << body.size() << "\r\n"; ss << "Content-Length: " << body.size() << "\r\n";
// Set default Content-Type if unspecified // Set default Content-Type if unspecified
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end()) if (args.extraHeaders.find("Content-Type") == args.extraHeaders.end())
{ {
if (args->multipartBoundary.empty()) ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
{
ss << "Content-Type: application/x-www-form-urlencoded"
<< "\r\n";
}
else
{
ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary
<< "\r\n";
}
} }
ss << "\r\n"; ss << "\r\n";
ss << body; ss << body;
@ -252,41 +106,35 @@ namespace ix
std::string req(ss.str()); std::string req(ss.str());
std::string errMsg; std::string errMsg;
std::atomic<bool> requestInitCancellation(false);
// Make a cancellation object dealing with connection timeout // Make a cancellation object dealing with connection timeout
auto cancelled = makeCancellationRequestWithTimeout(args->connectTimeout, args->cancel); auto isCancellationRequested =
makeCancellationRequestWithTimeout(args.connectTimeout, requestInitCancellation);
auto isCancellationRequested = [&]() {
return cancelled() || _stop;
};
bool success = _socket->connect(host, port, errMsg, isCancellationRequested); bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success) if (!success)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotConnect;
std::stringstream ss; std::stringstream ss;
ss << "Cannot connect to url: " << url << " / error : " << errMsg; ss << "Cannot connect to url: " << url;
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_CannotConnect,
description, headers, payload, ss.str(),
errorCode, uploadSize, downloadSize);
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
} }
// Make a new cancellation object dealing with transfer timeout // Make a new cancellation object dealing with transfer timeout
cancelled = makeCancellationRequestWithTimeout(args->transferTimeout, args->cancel); isCancellationRequested =
makeCancellationRequestWithTimeout(args.transferTimeout, requestInitCancellation);
if (args->verbose) if (args.verbose)
{ {
std::stringstream ss; std::stringstream ss;
ss << "Sending " << verb << " request " ss << "Sending " << verb << " request "
<< "to " << host << ":" << port << std::endl << "to " << host << ":" << port << std::endl
<< "request size: " << req.size() << " bytes" << std::endl << "request size: " << req.size() << " bytes" << std::endl
<< "=============" << std::endl << "=============" << std::endl
<< req << "=============" << std::endl << req
<< "=============" << std::endl
<< std::endl; << std::endl;
log(ss.str(), args); log(ss.str(), args);
@ -294,16 +142,10 @@ namespace ix
if (!_socket->writeBytes(req, isCancellationRequested)) if (!_socket->writeBytes(req, isCancellationRequested))
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::SendError;
std::string errorMsg("Cannot send request"); std::string errorMsg("Cannot send request");
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_SendError,
description, headers, payload, errorMsg,
errorCode, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
uploadSize = req.size(); uploadSize = req.size();
@ -314,19 +156,13 @@ namespace ix
if (!lineValid) if (!lineValid)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotReadStatusLine;
std::string errorMsg("Cannot retrieve status line"); std::string errorMsg("Cannot retrieve status line");
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_CannotReadStatusLine,
description, headers, payload, errorMsg,
errorCode, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
if (args->verbose) if (args.verbose)
{ {
std::stringstream ss; std::stringstream ss;
ss << "Status line " << line; ss << "Status line " << line;
@ -336,14 +172,9 @@ namespace ix
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1) if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{ {
std::string errorMsg("Cannot parse response code from status line"); std::string errorMsg("Cannot parse response code from status line");
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_MissingStatus,
description, headers, payload, errorMsg,
HttpErrorCode::MissingStatus, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
auto result = parseHttpHeaders(_socket, isCancellationRequested); auto result = parseHttpHeaders(_socket, isCancellationRequested);
@ -352,46 +183,30 @@ namespace ix
if (!headersValid) if (!headersValid)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::HeaderParsingError;
std::string errorMsg("Cannot parse http headers"); std::string errorMsg("Cannot parse http headers");
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_HeaderParsingError,
description, headers, payload, errorMsg,
errorCode, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
// Redirect ? // Redirect ?
if ((code >= 301 && code <= 308) && args->followRedirects) if ((code >= 301 && code <= 308) && args.followRedirects)
{ {
if (headers.find("Location") == headers.end()) if (headers.find("Location") == headers.end())
{ {
std::string errorMsg("Missing location header for redirect"); std::string errorMsg("Missing location header for redirect");
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_MissingLocation,
description, headers, payload, errorMsg,
HttpErrorCode::MissingLocation, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
if (redirects >= args->maxRedirects) if (redirects >= args.maxRedirects)
{ {
std::stringstream ss; std::stringstream ss;
ss << "Too many redirects: " << redirects; ss << "Too many redirects: " << redirects;
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_TooManyRedirects,
description, headers, payload, ss.str(),
HttpErrorCode::TooManyRedirects, uploadSize, downloadSize);
headers,
payload,
ss.str(),
uploadSize,
downloadSize);
} }
// Recurse // Recurse
@ -401,14 +216,9 @@ namespace ix
if (verb == "HEAD") if (verb == "HEAD")
{ {
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_Ok,
description, headers, payload, std::string(),
HttpErrorCode::Ok, uploadSize, downloadSize);
headers,
payload,
std::string(),
uploadSize,
downloadSize);
} }
// Parse response: // Parse response:
@ -419,30 +229,20 @@ namespace ix
ss << headers["Content-Length"]; ss << headers["Content-Length"];
ss >> contentLength; ss >> contentLength;
payload.reserve(contentLength);
auto chunkResult = _socket->readBytes(contentLength, auto chunkResult = _socket->readBytes(contentLength,
args->onProgressCallback, args.onProgressCallback,
args->onChunkCallback,
isCancellationRequested); isCancellationRequested);
if (!chunkResult.first) if (!chunkResult.first)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk"; errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_ChunkReadError,
description, headers, payload, errorMsg,
errorCode, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
if (!args->onChunkCallback)
{
payload.reserve(contentLength);
payload += chunkResult.second; payload += chunkResult.second;
} }
}
else if (headers.find("Transfer-Encoding") != headers.end() && else if (headers.find("Transfer-Encoding") != headers.end() &&
headers["Transfer-Encoding"] == "chunked") headers["Transfer-Encoding"] == "chunked")
{ {
@ -450,20 +250,14 @@ namespace ix
while (true) while (true)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
lineResult = _socket->readLine(isCancellationRequested); lineResult = _socket->readLine(isCancellationRequested);
line = lineResult.second; line = lineResult.second;
if (!lineResult.first) if (!lineResult.first)
{ {
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_ChunkReadError,
description, headers, payload, errorMsg,
errorCode, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
uint64_t chunkSize; uint64_t chunkSize;
@ -471,52 +265,37 @@ namespace ix
ss << std::hex << line; ss << std::hex << line;
ss >> chunkSize; ss >> chunkSize;
if (args->verbose) if (args.verbose)
{ {
std::stringstream oss; std::stringstream oss;
oss << "Reading " << chunkSize << " bytes" << std::endl; oss << "Reading " << chunkSize << " bytes"
<< std::endl;
log(oss.str(), args); log(oss.str(), args);
} }
payload.reserve(payload.size() + chunkSize);
// Read a chunk // Read a chunk
auto chunkResult = _socket->readBytes((size_t) chunkSize, auto chunkResult = _socket->readBytes(chunkSize,
args->onProgressCallback, args.onProgressCallback,
args->onChunkCallback,
isCancellationRequested); isCancellationRequested);
if (!chunkResult.first) if (!chunkResult.first)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk"; errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_ChunkReadError,
description, headers, payload, errorMsg,
errorCode, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
if (!args->onChunkCallback)
{
payload.reserve(payload.size() + (size_t) chunkSize);
payload += chunkResult.second; payload += chunkResult.second;
}
// Read the line that terminates the chunk (\r\n) // Read the line that terminates the chunk (\r\n)
lineResult = _socket->readLine(isCancellationRequested); lineResult = _socket->readLine(isCancellationRequested);
if (!lineResult.first) if (!lineResult.first)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError; return std::make_tuple(code, HttpErrorCode_ChunkReadError,
return std::make_shared<HttpResponse>(code, headers, payload, errorMsg,
description, uploadSize, downloadSize);
errorCode,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
if (chunkSize == 0) break; if (chunkSize == 0) break;
@ -529,14 +308,9 @@ namespace ix
else else
{ {
std::string errorMsg("Cannot read http body"); std::string errorMsg("Cannot read http body");
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_CannotReadBody,
description, headers, payload, errorMsg,
HttpErrorCode::CannotReadBody, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
downloadSize = payload.size(); downloadSize = payload.size();
@ -544,141 +318,56 @@ namespace ix
// If the content was compressed with gzip, decode it // If the content was compressed with gzip, decode it
if (headers["Content-Encoding"] == "gzip") if (headers["Content-Encoding"] == "gzip")
{ {
#ifdef IXWEBSOCKET_USE_ZLIB
std::string decompressedPayload; std::string decompressedPayload;
if (!gzipDecompress(payload, decompressedPayload)) if (!gzipInflate(payload, decompressedPayload))
{ {
std::string errorMsg("Error decompressing payload"); std::string errorMsg("Error decompressing payload");
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_Gzip,
description, headers, payload, errorMsg,
HttpErrorCode::Gzip, uploadSize, downloadSize);
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
} }
payload = decompressedPayload; payload = decompressedPayload;
#else
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::Gzip,
headers,
payload,
errorMsg,
uploadSize,
downloadSize);
#endif
} }
return std::make_shared<HttpResponse>(code, return std::make_tuple(code, HttpErrorCode_Ok,
description, headers, payload, std::string(),
HttpErrorCode::Ok, uploadSize, downloadSize);
headers,
payload,
std::string(),
uploadSize,
downloadSize);
} }
HttpResponsePtr HttpClient::get(const std::string& url, HttpRequestArgsPtr args) HttpResponse HttpClient::get(const std::string& url,
const HttpRequestArgs& args)
{ {
return request(url, kGet, std::string(), args); return request(url, kGet, std::string(), args);
} }
HttpResponsePtr HttpClient::head(const std::string& url, HttpRequestArgsPtr args) HttpResponse HttpClient::head(const std::string& url,
const HttpRequestArgs& args)
{ {
return request(url, kHead, std::string(), args); return request(url, kHead, std::string(), args);
} }
HttpResponsePtr HttpClient::Delete(const std::string& url, HttpRequestArgsPtr args) HttpResponse HttpClient::post(const std::string& url,
{
return request(url, kDelete, std::string(), args);
}
HttpResponsePtr HttpClient::request(const std::string& url,
const std::string& verb,
const HttpParameters& httpParameters, const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters, const HttpRequestArgs& args)
HttpRequestArgsPtr args)
{ {
std::string body; return request(url, kPost, serializeHttpParameters(httpParameters), args);
if (httpFormDataParameters.empty())
{
body = serializeHttpParameters(httpParameters);
}
else
{
std::string multipartBoundary = generateMultipartBoundary();
args->multipartBoundary = multipartBoundary;
body = serializeHttpFormDataParameters(
multipartBoundary, httpFormDataParameters, httpParameters);
} }
#ifdef IXWEBSOCKET_USE_ZLIB HttpResponse HttpClient::post(const std::string& url,
if (args->compressRequest)
{
body = gzipCompress(body);
}
#endif
return request(url, verb, body, args);
}
HttpResponsePtr HttpClient::post(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPost, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::post(const std::string& url,
const std::string& body, const std::string& body,
HttpRequestArgsPtr args) const HttpRequestArgs& args)
{ {
return request(url, kPost, body, args); return request(url, kPost, body, args);
} }
HttpResponsePtr HttpClient::put(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPut, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::put(const std::string& url,
const std::string& body,
const HttpRequestArgsPtr args)
{
return request(url, kPut, body, args);
}
HttpResponsePtr HttpClient::patch(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args)
{
return request(url, kPatch, httpParameters, httpFormDataParameters, args);
}
HttpResponsePtr HttpClient::patch(const std::string& url,
const std::string& body,
const HttpRequestArgsPtr args)
{
return request(url, kPatch, body, args);
}
std::string HttpClient::urlEncode(const std::string& value) std::string HttpClient::urlEncode(const std::string& value)
{ {
std::ostringstream escaped; std::ostringstream escaped;
escaped.fill('0'); escaped.fill('0');
escaped << std::hex; escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) for (std::string::const_iterator i = value.begin(), n = value.end();
i != n; ++i)
{ {
std::string::value_type c = (*i); std::string::value_type c = (*i);
@ -706,7 +395,9 @@ namespace ix
for (auto&& it : httpParameters) for (auto&& it : httpParameters)
{ {
ss << urlEncode(it.first) << "=" << urlEncode(it.second); ss << urlEncode(it.first)
<< "="
<< urlEncode(it.second);
if (i++ < (count-1)) if (i++ < (count-1))
{ {
@ -716,70 +407,61 @@ namespace ix
return ss.str(); return ss.str();
} }
std::string HttpClient::serializeHttpFormDataParameters( bool HttpClient::gzipInflate(
const std::string& multipartBoundary, const std::string& in,
const HttpFormDataParameters& httpFormDataParameters, std::string& out)
const HttpParameters& httpParameters)
{ {
// z_stream inflateState;
// --AaB03x std::memset(&inflateState, 0, sizeof(inflateState));
// Content-Disposition: form-data; name="submit-name"
// Larry inflateState.zalloc = Z_NULL;
// --AaB03x inflateState.zfree = Z_NULL;
// Content-Disposition: form-data; name="foo.txt"; filename="file1.txt" inflateState.opaque = Z_NULL;
// Content-Type: text/plain inflateState.avail_in = 0;
inflateState.next_in = Z_NULL;
// ... contents of file1.txt ... if (inflateInit2(&inflateState, 16+MAX_WBITS) != Z_OK)
// --AaB03x--
//
std::stringstream ss;
for (auto&& it : httpFormDataParameters)
{ {
ss << "--" << multipartBoundary << "\r\n" return false;
<< "Content-Disposition:"
<< " form-data; name=\"" << it.first << "\";"
<< " filename=\"" << it.first << "\""
<< "\r\n"
<< "Content-Type: application/octet-stream"
<< "\r\n"
<< "\r\n"
<< it.second << "\r\n";
} }
for (auto&& it : httpParameters) 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
{ {
ss << "--" << multipartBoundary << "\r\n" inflateState.avail_out = (uInt) kBufferSize;
<< "Content-Disposition:" inflateState.next_out = compressBuffer.get();
<< " form-data; name=\"" << it.first << "\";"
<< "\r\n"
<< "\r\n"
<< it.second << "\r\n";
}
ss << "--" << multipartBoundary << "--\r\n"; int ret = inflate(&inflateState, Z_SYNC_FLUSH);
return ss.str(); if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
}
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
{ {
if (args->logger) 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,
const HttpRequestArgs& args)
{ {
args->logger(msg); if (args.logger)
}
}
std::string HttpClient::generateMultipartBoundary()
{ {
std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); args.logger(msg);
}
static std::random_device rd; }
static std::mt19937 generator(rd());
std::shuffle(str.begin(), str.end(), generator);
return str;
} }
} // namespace ix

View File

@ -6,118 +6,102 @@
#pragma once #pragma once
#include "IXHttp.h"
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketHttpHeaders.h"
#include <algorithm> #include <algorithm>
#include <atomic>
#include <condition_variable>
#include <functional> #include <functional>
#include <map>
#include <memory>
#include <mutex> #include <mutex>
#include <queue> #include <atomic>
#include <thread> #include <tuple>
#include <memory>
#include <map>
#include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h"
namespace ix namespace ix
{ {
class HttpClient enum HttpErrorCode
{ {
HttpErrorCode_Ok = 0,
HttpErrorCode_CannotConnect = 1,
HttpErrorCode_Timeout = 2,
HttpErrorCode_Gzip = 3,
HttpErrorCode_UrlMalformed = 4,
HttpErrorCode_CannotCreateSocket = 5,
HttpErrorCode_SendError = 6,
HttpErrorCode_ReadError = 7,
HttpErrorCode_CannotReadStatusLine = 8,
HttpErrorCode_MissingStatus = 9,
HttpErrorCode_HeaderParsingError = 10,
HttpErrorCode_MissingLocation = 11,
HttpErrorCode_TooManyRedirects = 12,
HttpErrorCode_ChunkReadError = 13,
HttpErrorCode_CannotReadBody = 14
};
using HttpResponse = std::tuple<int, // status
HttpErrorCode, // error code
WebSocketHttpHeaders,
std::string, // payload
std::string, // error msg
uint64_t, // upload size
uint64_t>; // download size
using HttpParameters = std::map<std::string, std::string>;
using Logger = std::function<void(const std::string&)>;
struct HttpRequestArgs
{
std::string url;
WebSocketHttpHeaders extraHeaders;
std::string body;
int connectTimeout;
int transferTimeout;
bool followRedirects;
int maxRedirects;
bool verbose;
bool compress;
Logger logger;
OnProgressCallback onProgressCallback;
};
class HttpClient {
public: public:
HttpClient(bool async = false); HttpClient();
~HttpClient(); ~HttpClient();
HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args); HttpResponse get(const std::string& url,
HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args); const HttpRequestArgs& args);
HttpResponsePtr Delete(const std::string& url, HttpRequestArgsPtr args); HttpResponse head(const std::string& url,
const HttpRequestArgs& args);
HttpResponsePtr post(const std::string& url, HttpResponse post(const std::string& url,
const HttpParameters& httpParameters, const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters, const HttpRequestArgs& args);
HttpRequestArgsPtr args); HttpResponse post(const std::string& url,
HttpResponsePtr post(const std::string& url,
const std::string& body, const std::string& body,
HttpRequestArgsPtr args); const HttpRequestArgs& args);
HttpResponsePtr put(const std::string& url, private:
const HttpParameters& httpParameters, HttpResponse request(const std::string& url,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args);
HttpResponsePtr put(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args);
HttpResponsePtr patch(const std::string& url,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args);
HttpResponsePtr patch(const std::string& url,
const std::string& body,
HttpRequestArgsPtr args);
HttpResponsePtr request(const std::string& url,
const std::string& verb, const std::string& verb,
const std::string& body, const std::string& body,
HttpRequestArgsPtr args, const HttpRequestArgs& args,
int redirects = 0); int redirects = 0);
HttpResponsePtr request(const std::string& url,
const std::string& verb,
const HttpParameters& httpParameters,
const HttpFormDataParameters& httpFormDataParameters,
HttpRequestArgsPtr args);
void setForceBody(bool value);
// Async API
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
const std::string& verb = HttpClient::kGet);
bool performRequest(HttpRequestArgsPtr request,
const OnResponseCallback& onResponseCallback);
// TLS
void setTLSOptions(const SocketTLSOptions& tlsOptions);
std::string serializeHttpParameters(const HttpParameters& httpParameters); std::string serializeHttpParameters(const HttpParameters& httpParameters);
std::string serializeHttpFormDataParameters(
const std::string& multipartBoundary,
const HttpFormDataParameters& httpFormDataParameters,
const HttpParameters& httpParameters = HttpParameters());
std::string generateMultipartBoundary();
std::string urlEncode(const std::string& value); std::string urlEncode(const std::string& value);
void log(const std::string& msg, const HttpRequestArgs& args);
bool gzipInflate(
const std::string& in,
std::string& out);
std::shared_ptr<Socket> _socket;
const static std::string kPost; const static std::string kPost;
const static std::string kGet; const static std::string kGet;
const static std::string kHead; const static std::string kHead;
const static std::string kDelete;
const static std::string kPut;
const static std::string kPatch;
private:
void log(const std::string& msg, HttpRequestArgsPtr args);
// Async API background thread runner
void run();
// Async API
bool _async;
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
mutable std::mutex _queueMutex;
std::condition_variable _condition;
std::atomic<bool> _stop;
std::thread _thread;
std::unique_ptr<Socket> _socket;
std::recursive_mutex _mutex; // to protect accessing the _socket (only one socket per
// client) the mutex needs to be recursive as this function
// might be called recursively to follow HTTP redirections
SocketTLSOptions _tlsOptions;
bool _forceBody;
}; };
} // namespace ix }

View File

@ -1,244 +0,0 @@
/*
* IXHttpServer.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXHttpServer.h"
#include "IXGzipCodec.h"
#include "IXNetSystem.h"
#include "IXSocketConnect.h"
#include "IXUserAgent.h"
#include <cstdint>
#include <cstring>
#include <fstream>
#include <sstream>
#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()));
}
std::string response_head_file(const std::string& file_name){
if (std::string::npos != file_name.find(".html") || std::string::npos != file_name.find(".htm"))
return "text/html";
else if (std::string::npos != file_name.find(".css"))
return "text/css";
else if (std::string::npos != file_name.find(".js") || std::string::npos != file_name.find(".mjs"))
return "application/x-javascript";
else if (std::string::npos != file_name.find(".ico"))
return "image/x-icon";
else if (std::string::npos != file_name.find(".png"))
return "image/png";
else if (std::string::npos != file_name.find(".jpg") || std::string::npos != file_name.find(".jpeg"))
return "image/jpeg";
else if (std::string::npos != file_name.find(".gif"))
return "image/gif";
else if (std::string::npos != file_name.find(".svg"))
return "image/svg+xml";
else
return "application/octet-stream";
}
} // namespace
namespace ix
{
const int HttpServer::kDefaultTimeoutSecs(30);
HttpServer::HttpServer(int port,
const std::string& host,
int backlog,
size_t maxConnections,
int addressFamily,
int timeoutSecs,
int handshakeTimeoutSecs)
: WebSocketServer(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily)
, _timeoutSecs(timeoutSecs)
{
setDefaultConnectionCallback();
}
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
}
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
{
auto ret = Http::parseRequest(socket, _timeoutSecs);
// FIXME: handle errors in parseRequest
if (std::get<0>(ret))
{
auto request = std::get<2>(ret);
std::shared_ptr<ix::HttpResponse> response;
if (request->headers["Upgrade"] == "websocket")
{
WebSocketServer::handleUpgrade(std::move(socket), connectionState, request);
}
else
{
auto response = _onConnectionCallback(request, connectionState);
if (!Http::sendResponse(response, socket))
{
logError("Cannot send response");
}
}
}
connectionState->setTerminated();
}
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";
}
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();
headers["Content-Type"] = response_head_file(uri);
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;
#ifdef IXWEBSOCKET_USE_ZLIB
std::string acceptEncoding = request->headers["Accept-encoding"];
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
{
content = gzipCompress(content);
headers["Content-Encoding"] = "gzip";
}
headers["Accept-Encoding"] = "gzip";
#endif
// Log request
std::stringstream ss;
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
<< " " << request->method << " " << request->headers["User-Agent"] << " "
<< request->uri << " " << content.size();
logInfo(ss.str());
// FIXME: check extensions to set the content type
// headers["Content-Type"] = "application/octet-stream";
headers["Accept-Ranges"] = "none";
return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, content);
});
}
void HttpServer::makeRedirectServer(const std::string& redirectUrl)
{
//
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
//
setOnConnectionCallback(
[this, redirectUrl](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();
// Log request
std::stringstream ss;
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
<< " " << request->method << " " << request->headers["User-Agent"] << " "
<< request->uri;
logInfo(ss.str());
if (request->method == "POST")
{
return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, std::string());
}
headers["Location"] = redirectUrl;
return std::make_shared<HttpResponse>(
301, "OK", HttpErrorCode::Ok, headers, std::string());
});
}
//
// Display the client parameter and body on the console
//
void HttpServer::makeDebugServer()
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();
// Log request
std::stringstream ss;
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
<< " " << request->method << " " << request->headers["User-Agent"] << " "
<< request->uri;
logInfo(ss.str());
logInfo("== Headers == ");
for (auto&& it : request->headers)
{
std::ostringstream oss;
oss << it.first << ": " << it.second;
logInfo(oss.str());
}
logInfo("");
logInfo("== Body == ");
logInfo(request->body);
logInfo("");
return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
});
}
int HttpServer::getTimeoutSecs()
{
return _timeoutSecs;
}
} // namespace ix

View File

@ -1,57 +0,0 @@
/*
* IXHttpServer.h
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXHttp.h"
#include "IXWebSocket.h"
#include "IXWebSocketServer.h"
#include <functional>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix
{
class HttpServer final : public WebSocketServer
{
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,
int addressFamily = SocketServer::kDefaultAddressFamily,
int timeoutSecs = HttpServer::kDefaultTimeoutSecs,
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
void setOnConnectionCallback(const OnConnectionCallback& callback);
void makeRedirectServer(const std::string& redirectUrl);
void makeDebugServer();
int getTimeoutSecs();
private:
// Member variables
OnConnectionCallback _onConnectionCallback;
const static int kDefaultTimeoutSecs;
int _timeoutSecs;
// Methods
virtual void handleConnection(std::unique_ptr<Socket>,
std::shared_ptr<ConnectionState> connectionState) final;
void setDefaultConnectionCallback();
};
} // namespace ix

View File

@ -1,21 +1,10 @@
/* /*
* IXNetSystem.cpp * IXNetSystem.cpp
* Author: Korchynskyi Dmytro * Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone. All rights reserved. * Copyright (c) 2019 Machine Zone. All rights reserved.
*/ */
#include "IXNetSystem.h" #include "IXNetSystem.h"
#include <cstdint>
#include <cstdio>
#ifdef _WIN32
#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT 102
#endif
#ifndef ENOSPC
#define ENOSPC 28
#endif
#include <vector>
#endif
namespace ix namespace ix
{ {
@ -26,8 +15,9 @@ namespace ix
WSADATA wsaData; WSADATA wsaData;
int err; int err;
// Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2); wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData); err = WSAStartup(wVersionRequested, &wsaData);
return err == 0; return err == 0;
@ -40,422 +30,10 @@ namespace ix
{ {
#ifdef _WIN32 #ifdef _WIN32
int err = WSACleanup(); int err = WSACleanup();
return err == 0; return err == 0;
#else #else
return true; return true;
#endif #endif
} }
#ifdef _WIN32
struct WSAEvent
{
public:
WSAEvent(struct pollfd* fd)
: _fd(fd)
{
_event = WSACreateEvent();
} }
WSAEvent(WSAEvent&& source) noexcept
{
_event = source._event;
source._event = WSA_INVALID_EVENT; // invalidate the event in the source
_fd = source._fd;
}
~WSAEvent()
{
if (_event != WSA_INVALID_EVENT)
{
// We must deselect the networkevents from the socket event. Otherwise the
// socket will report states that aren't there.
if (_fd != nullptr && (int)_fd->fd != -1)
WSAEventSelect(_fd->fd, _event, 0);
WSACloseEvent(_event);
}
}
operator HANDLE()
{
return _event;
}
operator struct pollfd*()
{
return _fd;
}
private:
HANDLE _event;
struct pollfd* _fd;
};
#endif
//
// That function could 'return WSAPoll(pfd, nfds, timeout);'
// but WSAPoll is said to have weird behaviors on the internet
// (the curl folks have had problems with it).
//
// So we make it a select wrapper
//
// UPDATE: WSAPoll was fixed in Windows 10 Version 2004
//
// The optional "event" is set to nullptr if it wasn't signaled.
int poll(struct pollfd* fds, nfds_t nfds, int timeout, void** event)
{
#ifdef _WIN32
if (event && *event)
{
HANDLE interruptEvent = reinterpret_cast<HANDLE>(*event);
*event = nullptr; // the event wasn't signaled yet
if (nfds < 0 || nfds >= MAXIMUM_WAIT_OBJECTS - 1)
{
WSASetLastError(WSAEINVAL);
return SOCKET_ERROR;
}
std::vector<WSAEvent> socketEvents;
std::vector<HANDLE> handles;
// put the interrupt event as first element, making it highest priority
handles.push_back(interruptEvent);
// create the WSAEvents for the sockets
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
fd->revents = 0;
if (fd->fd >= 0)
{
// create WSAEvent and add it to the vectors
socketEvents.push_back(std::move(WSAEvent(fd)));
HANDLE handle = socketEvents.back();
if (handle == WSA_INVALID_EVENT)
{
WSASetLastError(WSAENOBUFS);
return SOCKET_ERROR;
}
handles.push_back(handle);
// mapping
long networkEvents = 0;
if (fd->events & (POLLIN )) networkEvents |= FD_READ | FD_ACCEPT;
if (fd->events & (POLLOUT /*| POLLWRNORM | POLLWRBAND*/)) networkEvents |= FD_WRITE | FD_CONNECT;
//if (fd->events & (POLLPRI | POLLRDBAND )) networkEvents |= FD_OOB;
if (WSAEventSelect(fd->fd, handle, networkEvents) != 0)
{
fd->revents = POLLNVAL;
socketEvents.pop_back();
handles.pop_back();
}
}
}
DWORD n = WSAWaitForMultipleEvents(handles.size(), handles.data(), FALSE, timeout != -1 ? static_cast<DWORD>(timeout) : WSA_INFINITE, FALSE);
if (n == WSA_WAIT_FAILED) return SOCKET_ERROR;
if (n == WSA_WAIT_TIMEOUT) return 0;
if (n == WSA_WAIT_EVENT_0)
{
// the interrupt event was signaled
*event = reinterpret_cast<void*>(interruptEvent);
return 1;
}
int handleIndex = n - WSA_WAIT_EVENT_0;
int socketIndex = handleIndex - 1;
WSANETWORKEVENTS netEvents;
int count = 0;
// WSAWaitForMultipleEvents returns the index of the first signaled event. And to emulate WSAPoll()
// all the signaled events must be processed.
while (socketIndex < (int)socketEvents.size())
{
struct pollfd* fd = socketEvents[socketIndex];
memset(&netEvents, 0, sizeof(netEvents));
if (WSAEnumNetworkEvents(fd->fd, socketEvents[socketIndex], &netEvents) != 0)
{
fd->revents = POLLERR;
}
else if (netEvents.lNetworkEvents != 0)
{
// mapping
if (netEvents.lNetworkEvents & (FD_READ | FD_ACCEPT | FD_OOB)) fd->revents |= POLLIN;
if (netEvents.lNetworkEvents & (FD_WRITE | FD_CONNECT )) fd->revents |= POLLOUT;
for (int i = 0; i < FD_MAX_EVENTS; ++i)
{
if (netEvents.iErrorCode[i] != 0)
{
fd->revents |= POLLERR;
break;
}
}
if (fd->revents != 0)
{
// only signaled sockets count
count++;
}
}
socketIndex++;
}
return count;
}
else
{
if (event && *event) *event = nullptr;
socket_t maxfd = 0;
fd_set readfds, writefds, errorfds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&errorfds);
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
if (fd->fd > maxfd)
{
maxfd = fd->fd;
}
if ((fd->events & POLLIN))
{
FD_SET(fd->fd, &readfds);
}
if ((fd->events & POLLOUT))
{
FD_SET(fd->fd, &writefds);
}
if ((fd->events & POLLERR))
{
FD_SET(fd->fd, &errorfds);
}
}
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL);
if (ret < 0)
{
return ret;
}
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
fd->revents = 0;
if (FD_ISSET(fd->fd, &readfds))
{
fd->revents |= POLLIN;
}
if (FD_ISSET(fd->fd, &writefds))
{
fd->revents |= POLLOUT;
}
if (FD_ISSET(fd->fd, &errorfds))
{
fd->revents |= POLLERR;
}
}
return ret;
}
#else
if (event && *event) *event = nullptr;
//
// It was reported that on Android poll can fail and return -1 with
// errno == EINTR, which should be a temp error and should typically
// be handled by retrying in a loop.
// Maybe we need to put all syscall / C functions in
// a new IXSysCalls.cpp and wrap them all.
//
// The style from libuv is as such.
//
int ret = -1;
do
{
ret = ::poll(fds, nfds, timeout);
} while (ret == -1 && errno == EINTR);
return ret;
#endif
}
//
// mingw does not have inet_ntop, which were taken as is from the musl C library.
//
const char* inet_ntop(int af, const void* a0, char* s, socklen_t l)
{
#if defined(_WIN32) && defined(__GNUC__)
const unsigned char* a = (const unsigned char*) a0;
int i, j, max, best;
char buf[100];
switch (af)
{
case AF_INET:
if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l) return s;
break;
case AF_INET6:
if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12))
snprintf(buf,
sizeof buf,
"%x:%x:%x:%x:%x:%x:%x:%x",
256 * a[0] + a[1],
256 * a[2] + a[3],
256 * a[4] + a[5],
256 * a[6] + a[7],
256 * a[8] + a[9],
256 * a[10] + a[11],
256 * a[12] + a[13],
256 * a[14] + a[15]);
else
snprintf(buf,
sizeof buf,
"%x:%x:%x:%x:%x:%x:%d.%d.%d.%d",
256 * a[0] + a[1],
256 * a[2] + a[3],
256 * a[4] + a[5],
256 * a[6] + a[7],
256 * a[8] + a[9],
256 * a[10] + a[11],
a[12],
a[13],
a[14],
a[15]);
/* Replace longest /(^0|:)[:0]{2,}/ with "::" */
for (i = best = 0, max = 2; buf[i]; i++)
{
if (i && buf[i] != ':') continue;
j = strspn(buf + i, ":0");
if (j > max) best = i, max = j;
}
if (max > 3)
{
buf[best] = buf[best + 1] = ':';
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
}
if (strlen(buf) < (size_t)l)
{
strcpy(s, buf);
return s;
}
break;
default: errno = EAFNOSUPPORT; return 0;
}
errno = ENOSPC;
return 0;
#else
return ::inet_ntop(af, a0, s, l);
#endif
}
#if defined(_WIN32) && defined(__GNUC__)
static int hexval(unsigned c)
{
if (c - '0' < 10) return c - '0';
c |= 32;
if (c - 'a' < 6) return c - 'a' + 10;
return -1;
}
#endif
//
// mingw does not have inet_pton, which were taken as is from the musl C library.
//
int inet_pton(int af, const char* s, void* a0)
{
#if defined(_WIN32) && defined(__GNUC__)
uint16_t ip[8];
unsigned char* a = (unsigned char*) a0;
int i, j, v, d, brk = -1, need_v4 = 0;
if (af == AF_INET)
{
for (i = 0; i < 4; i++)
{
for (v = j = 0; j < 3 && isdigit(s[j]); j++)
v = 10 * v + s[j] - '0';
if (j == 0 || (j > 1 && s[0] == '0') || v > 255) return 0;
a[i] = v;
if (s[j] == 0 && i == 3) return 1;
if (s[j] != '.') return 0;
s += j + 1;
}
return 0;
}
else if (af != AF_INET6)
{
errno = EAFNOSUPPORT;
return -1;
}
if (*s == ':' && *++s != ':') return 0;
for (i = 0;; i++)
{
if (s[0] == ':' && brk < 0)
{
brk = i;
ip[i & 7] = 0;
if (!*++s) break;
if (i == 7) return 0;
continue;
}
for (v = j = 0; j < 4 && (d = hexval(s[j])) >= 0; j++)
v = 16 * v + d;
if (j == 0) return 0;
ip[i & 7] = v;
if (!s[j] && (brk >= 0 || i == 7)) break;
if (i == 7) return 0;
if (s[j] != ':')
{
if (s[j] != '.' || (i < 6 && brk < 0)) return 0;
need_v4 = 1;
i++;
break;
}
s += j + 1;
}
if (brk >= 0)
{
memmove(ip + brk + 7 - i, ip + brk, 2 * (i + 1 - brk));
for (j = 0; j < 7 - i; j++)
ip[brk + j] = 0;
}
for (j = 0; j < 8; j++)
{
*a++ = ip[j] >> 8;
*a++ = ip[j];
}
if (need_v4 && inet_pton(AF_INET, (const char*) s, a - 4) <= 0) return 0;
return 1;
#else
return ::inet_pton(af, s, a0);
#endif
}
// Convert network bytes to host bytes. Copied from the ASIO library
unsigned short network_to_host_short(unsigned short value)
{
#if defined(_WIN32)
unsigned char* value_p = reinterpret_cast<unsigned char*>(&value);
unsigned short result = (static_cast<unsigned short>(value_p[0]) << 8)
| static_cast<unsigned short>(value_p[1]);
return result;
#else // defined(_WIN32)
return ntohs(value);
#endif // defined(_WIN32)
}
} // namespace ix

View File

@ -6,66 +6,17 @@
#pragma once #pragma once
#include <cstdint>
#ifdef __FreeBSD__
#include <sys/types.h>
#endif
#ifdef _WIN32 #ifdef _WIN32
# include <WS2tcpip.h>
#ifndef WIN32_LEAN_AND_MEAN # include <WinSock2.h>
#define WIN32_LEAN_AND_MEAN
#endif
#include <ws2tcpip.h>
#include <winsock2.h>
# include <basetsd.h> # include <basetsd.h>
# include <io.h> # include <io.h>
# include <ws2def.h> # include <ws2def.h>
#include <cerrno>
#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
// Define our own poll on Windows, as a wrapper on top of select
typedef unsigned long int nfds_t;
// pollfd is not defined by some versions of mingw64 since _WIN32_WINNT is too low
#if _WIN32_WINNT < 0x0600
struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
#define POLLIN 0x001 /* There is data to read. */
#define POLLOUT 0x004 /* Writing now will not block. */
#define POLLERR 0x008 /* Error condition. */
#define POLLHUP 0x010 /* Hung up. */
#define POLLNVAL 0x020 /* Invalid polling request. */
#endif
#else #else
# include <arpa/inet.h> # include <arpa/inet.h>
# include <errno.h> # include <errno.h>
#include <fcntl.h>
# include <netdb.h> # include <netdb.h>
#include <netinet/in.h>
#include <netinet/ip.h>
# include <netinet/tcp.h> # include <netinet/tcp.h>
#include <poll.h>
# include <sys/select.h> # include <sys/select.h>
# include <sys/socket.h> # include <sys/socket.h>
# include <sys/stat.h> # include <sys/stat.h>
@ -75,19 +26,6 @@ struct pollfd
namespace ix namespace ix
{ {
#ifdef _WIN32
typedef SOCKET socket_t;
#else
typedef int socket_t;
#endif
bool initNetSystem(); bool initNetSystem();
bool uninitNetSystem(); bool uninitNetSystem();
}
int poll(struct pollfd* fds, nfds_t nfds, int timeout, void** event);
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
int inet_pton(int af, const char* src, void* dst);
unsigned short network_to_host_short(unsigned short value);
} // namespace ix

View File

@ -7,10 +7,8 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <string>
namespace ix namespace ix
{ {
using OnProgressCallback = std::function<bool(int current, int total)>; using OnProgressCallback = std::function<bool(int current, int total)>;
using OnChunkCallback = std::function<void(const std::string&)>;
} }

View File

@ -8,9 +8,6 @@
namespace ix namespace ix
{ {
const uint64_t SelectInterrupt::kSendRequest = 1;
const uint64_t SelectInterrupt::kCloseRequest = 2;
SelectInterrupt::SelectInterrupt() SelectInterrupt::SelectInterrupt()
{ {
; ;
@ -45,9 +42,5 @@ namespace ix
{ {
return -1; return -1;
} }
void* SelectInterrupt::getEvent() const
{
return nullptr;
} }
} // namespace ix

View File

@ -6,14 +6,12 @@
#pragma once #pragma once
#include <cstdint> #include <stdint.h>
#include <memory>
#include <string> #include <string>
namespace ix namespace ix
{ {
class SelectInterrupt class SelectInterrupt {
{
public: public:
SelectInterrupt(); SelectInterrupt();
virtual ~SelectInterrupt(); virtual ~SelectInterrupt();
@ -24,12 +22,7 @@ namespace ix
virtual bool clear(); virtual bool clear();
virtual uint64_t read(); virtual uint64_t read();
virtual int getFd() const; virtual int getFd() const;
virtual void* getEvent() const;
// Used as special codes for pipe communication
static const uint64_t kSendRequest;
static const uint64_t kCloseRequest;
}; };
}
using SelectInterruptPtr = std::unique_ptr<SelectInterrupt>;
} // namespace ix

View File

@ -1,85 +0,0 @@
/*
* IXSelectInterruptEvent.cpp
*/
//
// On Windows we use a Windows Event to wake up ix::poll() (WSAWaitForMultipleEvents).
// And on any other platform that doesn't support pipe file descriptors we
// emulate the interrupt event by using a short timeout with ix::poll() and
// read from the SelectInterrupt. (see Socket::poll() "Emulation mode")
//
#include <algorithm>
#include "IXSelectInterruptEvent.h"
namespace ix
{
SelectInterruptEvent::SelectInterruptEvent()
{
#ifdef _WIN32
_event = CreateEvent(NULL, TRUE, FALSE, NULL);
#endif
}
SelectInterruptEvent::~SelectInterruptEvent()
{
#ifdef _WIN32
CloseHandle(_event);
#endif
}
bool SelectInterruptEvent::init(std::string& /*errorMsg*/)
{
return true;
}
bool SelectInterruptEvent::notify(uint64_t value)
{
std::lock_guard<std::mutex> lock(_valuesMutex);
// WebSocket implementation detail: We only need one of the values in the queue
if (std::find(_values.begin(), _values.end(), value) == _values.end())
_values.push_back(value);
#ifdef _WIN32
SetEvent(_event); // wake up
#endif
return true;
}
uint64_t SelectInterruptEvent::read()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
if (_values.size() > 0)
{
uint64_t value = _values.front();
_values.pop_front();
#ifdef _WIN32
// signal the event if there is still data in the queue
if (_values.size() == 0)
ResetEvent(_event);
#endif
return value;
}
return 0;
}
bool SelectInterruptEvent::clear()
{
std::lock_guard<std::mutex> lock(_valuesMutex);
_values.clear();
#ifdef _WIN32
ResetEvent(_event);
#endif
return true;
}
void* SelectInterruptEvent::getEvent() const
{
#ifdef _WIN32
return reinterpret_cast<void*>(_event);
#else
return nullptr;
#endif
}
} // namespace ix

View File

@ -1,39 +0,0 @@
/*
* IXSelectInterruptEvent.h
*/
#pragma once
#include "IXSelectInterrupt.h"
#include <cstdint>
#include <mutex>
#include <string>
#include <deque>
#ifdef _WIN32
#include <windows.h>
#endif
namespace ix
{
class SelectInterruptEvent final : public SelectInterrupt
{
public:
SelectInterruptEvent();
virtual ~SelectInterruptEvent();
bool init(std::string& /*errorMsg*/) final;
bool notify(uint64_t value) final;
bool clear() final;
uint64_t read() final;
void* getEvent() const final;
private:
// contains every value only once, new values are inserted at the begin, nu
std::deque<uint64_t> _values;
std::mutex _valuesMutex;
#ifdef _WIN32
// Windows Event to wake up the socket poll
HANDLE _event;
#endif
};
} // 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,32 @@
/*
* 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 : 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;
};
}

View File

@ -6,21 +6,20 @@
#include "IXSelectInterruptFactory.h" #include "IXSelectInterruptFactory.h"
#include "IXUniquePtr.h" #if defined(__linux__) || defined(__APPLE__)
#if _WIN32 # include <ixwebsocket/IXSelectInterruptPipe.h>
#include "IXSelectInterruptEvent.h"
#else #else
#include "IXSelectInterruptPipe.h" # include <ixwebsocket/IXSelectInterrupt.h>
#endif #endif
namespace ix namespace ix
{ {
SelectInterruptPtr createSelectInterrupt() std::shared_ptr<SelectInterrupt> createSelectInterrupt()
{ {
#ifdef _WIN32 #if defined(__linux__) || defined(__APPLE__)
return ix::make_unique<SelectInterruptEvent>(); return std::make_shared<SelectInterruptPipe>();
#else #else
return ix::make_unique<SelectInterruptPipe>(); return std::make_shared<SelectInterrupt>();
#endif #endif
} }
} // namespace ix }

View File

@ -11,6 +11,5 @@
namespace ix namespace ix
{ {
class SelectInterrupt; class SelectInterrupt;
using SelectInterruptPtr = std::unique_ptr<SelectInterrupt>; std::shared_ptr<SelectInterrupt> createSelectInterrupt();
SelectInterruptPtr createSelectInterrupt(); }
} // namespace ix

View File

@ -5,19 +5,17 @@
*/ */
// //
// On UNIX we use pipes to wake up select. There is no way to do that // On macOS we use UNIX pipes to wake up select.
// on Windows so this file is compiled out on Windows.
// //
#ifndef _WIN32
#include "IXSelectInterruptPipe.h" #include "IXSelectInterruptPipe.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <sstream>
#include <string.h> // for strerror
#include <unistd.h> // for write #include <unistd.h> // for write
#include <string.h> // for strerror
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sstream>
namespace ix namespace ix
{ {
@ -34,20 +32,14 @@ namespace ix
SelectInterruptPipe::~SelectInterruptPipe() SelectInterruptPipe::~SelectInterruptPipe()
{ {
if (-1 != _fildes[kPipeReadIndex]) {
::close(_fildes[kPipeReadIndex]); ::close(_fildes[kPipeReadIndex]);
}
if (-1 != _fildes[kPipeWriteIndex]) {
::close(_fildes[kPipeWriteIndex]); ::close(_fildes[kPipeWriteIndex]);
}
_fildes[kPipeReadIndex] = -1; _fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1; _fildes[kPipeWriteIndex] = -1;
} }
bool SelectInterruptPipe::init(std::string& errorMsg) bool SelectInterruptPipe::init(std::string& errorMsg)
{ {
std::lock_guard<std::mutex> lock(_fildesMutex);
// calling init twice is a programming error // calling init twice is a programming error
assert(_fildes[kPipeReadIndex] == -1); assert(_fildes[kPipeReadIndex] == -1);
assert(_fildes[kPipeWriteIndex] == -1); assert(_fildes[kPipeWriteIndex] == -1);
@ -116,35 +108,20 @@ namespace ix
bool SelectInterruptPipe::notify(uint64_t value) bool SelectInterruptPipe::notify(uint64_t value)
{ {
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeWriteIndex]; int fd = _fildes[kPipeWriteIndex];
if (fd == -1) return false; if (fd == -1) return false;
ssize_t ret = -1;
do
{
ret = ::write(fd, &value, sizeof(value));
} while (ret == -1 && errno == EINTR);
// we should write 8 bytes for an uint64_t // we should write 8 bytes for an uint64_t
return ret == 8; return write(fd, &value, sizeof(value)) == 8;
} }
// TODO: return max uint64_t for errors ? // TODO: return max uint64_t for errors ?
uint64_t SelectInterruptPipe::read() uint64_t SelectInterruptPipe::read()
{ {
std::lock_guard<std::mutex> lock(_fildesMutex);
int fd = _fildes[kPipeReadIndex]; int fd = _fildes[kPipeReadIndex];
uint64_t value = 0; uint64_t value = 0;
::read(fd, &value, sizeof(value));
ssize_t ret = -1;
do
{
ret = ::read(fd, &value, sizeof(value));
} while (ret == -1 && errno == EINTR);
return value; return value;
} }
@ -156,10 +133,6 @@ namespace ix
int SelectInterruptPipe::getFd() const int SelectInterruptPipe::getFd() const
{ {
std::lock_guard<std::mutex> lock(_fildesMutex);
return _fildes[kPipeReadIndex]; return _fildes[kPipeReadIndex];
} }
} // namespace ix }
#endif // !_WIN32

View File

@ -7,15 +7,13 @@
#pragma once #pragma once
#include "IXSelectInterrupt.h" #include "IXSelectInterrupt.h"
#include <cstdint>
#include <mutex>
#include <stdint.h> #include <stdint.h>
#include <string> #include <string>
namespace ix namespace ix
{ {
class SelectInterruptPipe final : public SelectInterrupt class SelectInterruptPipe : public SelectInterrupt {
{
public: public:
SelectInterruptPipe(); SelectInterruptPipe();
virtual ~SelectInterruptPipe(); virtual ~SelectInterruptPipe();
@ -32,10 +30,10 @@ namespace ix
// happens between a control thread and a background thread, which is // happens between a control thread and a background thread, which is
// blocked on select. // blocked on select.
int _fildes[2]; int _fildes[2];
mutable std::mutex _fildesMutex;
// Used to identify the read/write idx // Used to identify the read/write idx
static const int kPipeReadIndex; static const int kPipeReadIndex;
static const int kPipeWriteIndex; static const int kPipeWriteIndex;
}; };
} // namespace ix }

View File

@ -1,87 +0,0 @@
/*
* IXSetThreadName.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 2020 Machine Zone, Inc. All rights reserved.
*/
#include "IXSetThreadName.h"
// unix systems
#if defined(__APPLE__) || defined(__linux__) || defined(BSD)
#include <pthread.h>
#endif
// freebsd needs this header as well
#if defined(BSD)
#include <pthread_np.h>
#endif
#ifdef __APPLE__
#include <AvailabilityMacros.h>
#endif
// Windows
#ifdef _WIN32
#include <windows.h>
#endif
namespace ix
{
#ifdef _WIN32
const DWORD MS_VC_EXCEPTION = 0x406D1388;
#pragma pack(push, 8)
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)
void SetThreadName(DWORD dwThreadID, const char* threadName)
{
#ifndef __GNUC__
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = threadName;
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
__try
{
RaiseException(
MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
#endif
}
#endif
void setThreadName(const std::string& name)
{
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)
//
// Apple reserves 16 bytes for its thread names
// Notice that the Apple version of pthread_setname_np
// does not take a pthread_t argument
//
pthread_setname_np(name.substr(0, 63).c_str());
#elif defined(__linux__)
//
// Linux only reserves 16 bytes for its thread names
// See prctl and PR_SET_NAME property in
// http://man7.org/linux/man-pages/man2/prctl.2.html
//
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
#elif defined(_WIN32)
SetThreadName(-1, name.c_str());
#elif defined(BSD)
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
#else
// ... assert here ?
#endif
}
} // namespace ix

View File

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

View File

@ -5,20 +5,21 @@
*/ */
#include "IXSocket.h" #include "IXSocket.h"
#include "IXSocketConnect.h"
#include "IXNetSystem.h" #include "IXNetSystem.h"
#include "IXSelectInterrupt.h" #include "IXSelectInterrupt.h"
#include "IXSelectInterruptFactory.h" #include "IXSelectInterruptFactory.h"
#include "IXSocketConnect.h"
#include <algorithm>
#include <array>
#include <assert.h>
#include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
#include <vector>
#include <algorithm>
#include <iostream>
#ifdef min #ifdef min
#undef min #undef min
@ -28,10 +29,13 @@ namespace ix
{ {
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout; 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) Socket::Socket(int fd) :
: _sockfd(fd) _sockfd(fd),
, _selectInterrupt(createSelectInterrupt()) _selectInterrupt(createSelectInterrupt())
{ {
; ;
} }
@ -41,61 +45,45 @@ namespace ix
close(); close();
} }
PollResultType Socket::poll(bool readyToRead, PollResultType Socket::poll(int timeoutSecs)
int timeoutMs,
int sockfd,
const SelectInterruptPtr& selectInterrupt)
{ {
PollResultType pollResult = PollResultType::ReadyForRead; if (_sockfd == -1)
{
return PollResultType::Error;
}
// return isReadyToRead(1000 * timeoutSecs);
// We used to use ::select to poll but on Android 9 we get large fds out of }
// ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching
// to ::poll does fix that.
//
// However poll isn't as portable as select and has bugs on Windows, so we
// have a shim to fallback to select on those platforms. See
// https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
//
nfds_t nfds = 1;
struct pollfd fds[2];
memset(fds, 0, sizeof(fds));
fds[0].fd = sockfd; PollResultType Socket::select(bool readyToRead, int timeoutMs)
fds[0].events = (readyToRead) ? POLLIN : POLLOUT; {
fd_set rfds;
fd_set wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
// this is ignored by poll, but our select based poll wrapper on Windows needs it fd_set* fds = (readyToRead) ? &rfds : & wfds;
fds[0].events |= POLLERR; FD_SET(_sockfd, fds);
// File descriptor used to interrupt select when needed // File descriptor used to interrupt select when needed
int interruptFd = -1; int interruptFd = _selectInterrupt->getFd();
void* interruptEvent = nullptr;
if (selectInterrupt)
{
interruptFd = selectInterrupt->getFd();
interruptEvent = selectInterrupt->getEvent();
if (interruptFd != -1) if (interruptFd != -1)
{ {
nfds = 2; FD_SET(interruptFd, fds);
fds[1].fd = interruptFd;
fds[1].events = POLLIN;
}
else if (interruptEvent == nullptr)
{
// Emulation mode: SelectInterrupt neither supports file descriptors nor events
// Check the selectInterrupt for requests before doing the poll().
if (readSelectInterruptRequest(selectInterrupt, &pollResult))
{
return pollResult;
}
}
} }
void* event = interruptEvent; // ix::poll will set event to nullptr if it wasn't signaled struct timeval timeout;
int ret = ix::poll(fds, nfds, timeoutMs, &event); timeout.tv_sec = timeoutMs / 1000;
timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
// Compute the highest fd.
int sockfd = _sockfd;
int nfds = (std::max)(sockfd, interruptFd);
int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
(timeoutMs < 0) ? nullptr : &timeout);
PollResultType pollResult = PollResultType::ReadyForRead;
if (ret < 0) if (ret < 0)
{ {
pollResult = PollResultType::Error; pollResult = PollResultType::Error;
@ -103,120 +91,50 @@ namespace ix
else if (ret == 0) else if (ret == 0)
{ {
pollResult = PollResultType::Timeout; pollResult = PollResultType::Timeout;
if (selectInterrupt && interruptFd == -1 && interruptEvent == nullptr) }
else if (interruptFd != -1 && FD_ISSET(interruptFd, &rfds))
{ {
// Emulation mode: SelectInterrupt neither supports fd nor events uint64_t value = _selectInterrupt->read();
// Check the selectInterrupt for requests if (value == kSendRequest)
readSelectInterruptRequest(selectInterrupt, &pollResult);
}
}
else if ((interruptFd != -1 && fds[1].revents & POLLIN) || (interruptEvent != nullptr && event != nullptr))
{ {
// The InterruptEvent was signaled pollResult = PollResultType::SendRequest;
readSelectInterruptRequest(selectInterrupt, &pollResult);
} }
else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN) else if (value == kCloseRequest)
{
pollResult = PollResultType::CloseRequest;
}
}
else if (sockfd != -1 && readyToRead && FD_ISSET(sockfd, &rfds))
{ {
pollResult = PollResultType::ReadyForRead; pollResult = PollResultType::ReadyForRead;
} }
else if (sockfd != -1 && !readyToRead && fds[0].revents & POLLOUT) else if (sockfd != -1 && !readyToRead && FD_ISSET(sockfd, &wfds))
{ {
pollResult = PollResultType::ReadyForWrite; pollResult = PollResultType::ReadyForWrite;
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (fds[0].revents & POLLERR)
{
pollResult = PollResultType::Error;
}
#else
int optval = -1;
socklen_t optlen = sizeof(optval);
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 || optval != 0)
{
pollResult = PollResultType::Error;
// set errno to optval so that external callers can have an
// appropriate error description when calling strerror
errno = optval;
}
#endif
}
else if (sockfd != -1 && (fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
fds[0].revents & POLLNVAL))
{
pollResult = PollResultType::Error;
} }
return pollResult; return pollResult;
} }
bool Socket::readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt,
PollResultType* pollResult)
{
uint64_t value = selectInterrupt->read();
if (value == SelectInterrupt::kSendRequest)
{
*pollResult = PollResultType::SendRequest;
return true;
}
else if (value == SelectInterrupt::kCloseRequest)
{
*pollResult = PollResultType::CloseRequest;
return true;
}
return false;
}
PollResultType Socket::isReadyToRead(int timeoutMs) PollResultType Socket::isReadyToRead(int timeoutMs)
{ {
if (_sockfd == -1)
{
return PollResultType::Error;
}
bool readyToRead = true; bool readyToRead = true;
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt); return select(readyToRead, timeoutMs);
} }
PollResultType Socket::isReadyToWrite(int timeoutMs) PollResultType Socket::isReadyToWrite(int timeoutMs)
{ {
if (_sockfd == -1)
{
return PollResultType::Error;
}
bool readyToRead = false; bool readyToRead = false;
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt); return select(readyToRead, timeoutMs);
} }
// Wake up from poll/select by writing to the pipe which is watched by select // Wake up from poll/select by writing to the pipe which is watched by select
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode) bool Socket::wakeUpFromPoll(uint8_t wakeUpCode)
{ {
return _selectInterrupt->notify(wakeUpCode); return _selectInterrupt->notify(wakeUpCode);
} }
bool Socket::isWakeUpFromPollSupported()
{
return _selectInterrupt->getFd() != -1 || _selectInterrupt->getEvent() != nullptr;
}
bool Socket::accept(std::string& errMsg)
{
if (_sockfd == -1)
{
errMsg = "Socket is uninitialized";
return false;
}
return true;
}
bool Socket::connect(const std::string& host, bool Socket::connect(const std::string& host,
int port, int port,
std::string& errMsg, std::string& errMsg,
@ -242,6 +160,8 @@ namespace ix
ssize_t Socket::send(char* buffer, size_t length) ssize_t Socket::send(char* buffer, size_t length)
{ {
std::lock_guard<std::mutex> lock(_socketMutex);
int flags = 0; int flags = 0;
#ifdef MSG_NOSIGNAL #ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL; flags = MSG_NOSIGNAL;
@ -257,6 +177,8 @@ namespace ix
ssize_t Socket::recv(void* buffer, size_t length) ssize_t Socket::recv(void* buffer, size_t length)
{ {
std::lock_guard<std::mutex> lock(_socketMutex);
int flags = 0; int flags = 0;
#ifdef MSG_NOSIGNAL #ifdef MSG_NOSIGNAL
flags = MSG_NOSIGNAL; flags = MSG_NOSIGNAL;
@ -267,27 +189,11 @@ namespace ix
int Socket::getErrno() int Socket::getErrno()
{ {
int err;
#ifdef _WIN32 #ifdef _WIN32
err = WSAGetLastError(); return WSAGetLastError();
#else #else
err = errno; return errno;
#endif #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) void Socket::closeSocket(int fd)
@ -307,31 +213,23 @@ namespace ix
bool Socket::writeBytes(const std::string& str, bool Socket::writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested) const CancellationRequest& isCancellationRequested)
{ {
int offset = 0;
int len = (int) str.size();
while (true) while (true)
{ {
if (isCancellationRequested && isCancellationRequested()) return false; if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret = send((char*) &str[offset], len); char* buffer = const_cast<char*>(str.c_str());
int len = (int) str.size();
ssize_t ret = send(buffer, len);
// We wrote some bytes, as needed, all good. // We wrote some bytes, as needed, all good.
if (ret > 0) if (ret > 0)
{ {
if (ret == len) return ret == len;
{
return true;
}
else
{
offset += ret;
len -= ret;
continue;
}
} }
// There is possibly something to be writen, try again // There is possibly something to be writen, try again
else if (ret < 0 && Socket::isWaitNeeded()) else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
{ {
continue; continue;
} }
@ -343,7 +241,8 @@ namespace ix
} }
} }
bool Socket::readByte(void* buffer, const CancellationRequest& isCancellationRequested) bool Socket::readByte(void* buffer,
const CancellationRequest& isCancellationRequested)
{ {
while (true) while (true)
{ {
@ -358,7 +257,8 @@ namespace ix
return true; return true;
} }
// There is possibly something to be read, try again // There is possibly something to be read, try again
else if (ret < 0 && Socket::isWaitNeeded()) else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
getErrno() == EAGAIN))
{ {
// Wait with a 1ms timeout until the socket is ready to read. // Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping // This way we are not busy looping
@ -399,54 +299,48 @@ namespace ix
std::pair<bool, std::string> Socket::readBytes( std::pair<bool, std::string> Socket::readBytes(
size_t length, size_t length,
const OnProgressCallback& onProgressCallback, const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested) const CancellationRequest& isCancellationRequested)
{ {
std::array<uint8_t, 1 << 14> readBuffer; if (_readBuffer.empty())
std::vector<uint8_t> output; {
size_t bytesRead = 0; _readBuffer.resize(kChunkSize);
}
while (bytesRead != length) std::vector<uint8_t> output;
while (output.size() != length)
{ {
if (isCancellationRequested && isCancellationRequested()) if (isCancellationRequested && isCancellationRequested())
{ {
const std::string errorMsg("Cancellation Requested"); return std::make_pair(false, std::string());
return std::make_pair(false, errorMsg);
} }
size_t size = std::min(readBuffer.size(), length - bytesRead); size_t size = std::min(kChunkSize, length - output.size());
ssize_t ret = recv((char*) &readBuffer[0], size); ssize_t ret = recv((char*)&_readBuffer[0], size);
if (ret > 0) if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
getErrno() != EAGAIN))
{ {
if (onChunkCallback) // Error
{ return std::make_pair(false, std::string());
std::string chunk(readBuffer.begin(), readBuffer.begin() + ret);
onChunkCallback(chunk);
} }
else else if (ret > 0)
{ {
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret); output.insert(output.end(),
} _readBuffer.begin(),
bytesRead += ret; _readBuffer.begin() + ret);
}
else if (ret <= 0 && !Socket::isWaitNeeded())
{
const std::string errorMsg("Recv Error");
return std::make_pair(false, errorMsg);
} }
if (onProgressCallback) onProgressCallback((int) bytesRead, (int) length); if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
// Wait with a 1ms timeout until the socket is ready to read. // Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping // This way we are not busy looping
if (isReadyToRead(1) == PollResultType::Error) if (isReadyToRead(1) == PollResultType::Error)
{ {
const std::string errorMsg("Poll Error"); return std::make_pair(false, std::string());
return std::make_pair(false, errorMsg);
} }
} }
return std::make_pair(true, std::string(output.begin(), output.end())); return std::make_pair(true, std::string(output.begin(),
output.end()));
}
} }
} // namespace ix

View File

@ -6,30 +6,25 @@
#pragma once #pragma once
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <string> #include <string>
#include <functional>
#ifdef __APPLE__ #include <mutex>
#include <sys/types.h> #include <atomic>
#endif #include <vector>
#include <memory>
#ifdef _WIN32 #ifdef _WIN32
#include <basetsd.h> #include <BaseTsd.h>
#ifdef _MSC_VER
typedef SSIZE_T ssize_t; typedef SSIZE_T ssize_t;
#endif #endif
#endif
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include "IXProgressCallback.h" #include "IXProgressCallback.h"
#include "IXSelectInterrupt.h"
namespace ix namespace ix
{ {
class SelectInterrupt;
enum class PollResultType enum class PollResultType
{ {
ReadyForRead = 0, ReadyForRead = 0,
@ -40,65 +35,68 @@ namespace ix
CloseRequest = 5 CloseRequest = 5
}; };
class Socket class Socket {
{
public: public:
Socket(int fd = -1); Socket(int fd = -1);
virtual ~Socket(); virtual ~Socket();
bool init(std::string& errorMsg); bool init(std::string& errorMsg);
void configure();
// Functions to check whether there is activity on the socket // Functions to check whether there is activity on the socket
PollResultType poll(int timeoutMs = kDefaultPollTimeout); PollResultType poll(int timeoutSecs = kDefaultPollTimeout);
bool wakeUpFromPoll(uint64_t wakeUpCode); bool wakeUpFromPoll(uint8_t wakeUpCode);
bool isWakeUpFromPollSupported();
PollResultType isReadyToWrite(int timeoutMs); PollResultType isReadyToWrite(int timeoutMs);
PollResultType isReadyToRead(int timeoutMs); PollResultType isReadyToRead(int timeoutMs);
// Virtual methods // Virtual methods
virtual bool accept(std::string& errMsg); virtual bool connect(const std::string& url,
virtual bool connect(const std::string& host,
int port, int port,
std::string& errMsg, std::string& errMsg,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
virtual void close(); virtual void close();
virtual ssize_t send(char* buffer, size_t length); virtual ssize_t send(char* buffer, size_t length);
ssize_t send(const std::string& buffer); virtual ssize_t send(const std::string& buffer);
virtual ssize_t recv(void* buffer, size_t length); virtual ssize_t recv(void* buffer, size_t length);
// Blocking and cancellable versions, working with socket that can be set // Blocking and cancellable versions, working with socket that can be set
// to non blocking mode. Used during HTTP upgrade. // to non blocking mode. Used during HTTP upgrade.
bool readByte(void* buffer, const CancellationRequest& isCancellationRequested); bool readByte(void* buffer,
bool writeBytes(const std::string& str, const CancellationRequest& isCancellationRequested); 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> readLine(
std::pair<bool, std::string> readBytes(size_t length, const CancellationRequest& isCancellationRequested);
std::pair<bool, std::string> readBytes(
size_t length,
const OnProgressCallback& onProgressCallback, const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
static int getErrno(); static int getErrno();
static bool isWaitNeeded();
static void closeSocket(int fd);
static PollResultType poll(bool readyToRead, // Used as special codes for pipe communication
int timeoutMs, static const uint64_t kSendRequest;
int sockfd, static const uint64_t kCloseRequest;
const SelectInterruptPtr& selectInterrupt);
protected: protected:
void closeSocket(int fd);
std::atomic<int> _sockfd; std::atomic<int> _sockfd;
std::mutex _socketMutex; std::mutex _socketMutex;
static bool readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt,
PollResultType* pollResult);
private: private:
PollResultType select(bool readyToRead, int timeoutMs);
static const int kDefaultPollTimeout; static const int kDefaultPollTimeout;
static const int kDefaultPollNoTimeout; static const int kDefaultPollNoTimeout;
SelectInterruptPtr _selectInterrupt; // 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

@ -1,20 +1,16 @@
/* /*
* IXSocketAppleSSL.cpp * IXSocketAppleSSL.cpp
* Author: Benjamin Sergeant * Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
* *
* Adapted from Satori SDK Apple SSL code. * Adapted from Satori SDK Apple SSL code.
*/ */
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
#include "IXSocketAppleSSL.h" #include "IXSocketAppleSSL.h"
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <netdb.h> #include <netdb.h>
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -22,54 +18,22 @@
#include <sys/time.h> #include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <stdint.h>
#include <iostream>
#include <errno.h>
#define socketerrno errno #define socketerrno errno
#include <Security/SecureTransport.h> #include <Security/SecureTransport.h>
namespace ix namespace {
{
SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _sslContext(nullptr)
, _tlsOptions(tlsOptions)
{
;
}
SocketAppleSSL::~SocketAppleSSL() OSStatus read_from_socket(SSLConnectionRef connection, void *data, size_t *len)
{
SocketAppleSSL::close();
}
std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
{
std::string errMsg("Unknown SSL error.");
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
if (error)
{
CFStringRef message = CFErrorCopyDescription(error);
if (message)
{
char localBuffer[128];
Boolean success;
success = CFStringGetCString(message, localBuffer, 128, kCFStringEncodingUTF8);
if (success)
{
errMsg = localBuffer;
}
CFRelease(message);
}
CFRelease(error);
}
return errMsg;
}
OSStatus SocketAppleSSL::readFromSocket(SSLConnectionRef connection, void* data, size_t* len)
{ {
int fd = (int) (long) connection; int fd = (int) (long) connection;
if (fd < 0) return errSSLInternal; if (fd < 0)
return errSSLInternal;
assert(data != nullptr); assert(data != nullptr);
assert(len != nullptr); assert(len != nullptr);
@ -82,15 +46,11 @@ namespace ix
{ {
*len = (size_t) status; *len = (size_t) status;
if (requested_sz > *len) if (requested_sz > *len)
{
return errSSLWouldBlock; return errSSLWouldBlock;
}
else else
{
return noErr; return noErr;
} }
} else if (0 == status)
else if (status == 0)
{ {
*len = 0; *len = 0;
return errSSLClosedGraceful; return errSSLClosedGraceful;
@ -98,26 +58,27 @@ namespace ix
else else
{ {
*len = 0; *len = 0;
switch (errno) switch (errno) {
{ case ENOENT:
case ENOENT: return errSSLClosedGraceful; return errSSLClosedGraceful;
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx case EAGAIN:
case EINPROGRESS: return errSSLWouldBlock; return errSSLWouldBlock;
case ECONNRESET: return errSSLClosedAbort; case ECONNRESET:
return errSSLClosedAbort;
default: return errSecIO; default:
return errSecIO;
} }
} }
} }
OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection, OSStatus write_to_socket(SSLConnectionRef connection, const void *data, size_t *len)
const void* data,
size_t* len)
{ {
int fd = (int) (long) connection; int fd = (int) (long) connection;
if (fd < 0) return errSSLInternal; if (fd < 0)
return errSSLInternal;
assert(data != nullptr); assert(data != nullptr);
assert(len != nullptr); assert(len != nullptr);
@ -129,15 +90,11 @@ namespace ix
{ {
*len = (size_t) status; *len = (size_t) status;
if (to_write_sz > *len) if (to_write_sz > *len)
{
return errSSLWouldBlock; return errSSLWouldBlock;
}
else else
{
return noErr; return noErr;
} }
} else if (0 == status)
else if (status == 0)
{ {
*len = 0; *len = 0;
return errSSLClosedGraceful; return errSSLClosedGraceful;
@ -145,45 +102,56 @@ namespace ix
else else
{ {
*len = 0; *len = 0;
switch (errno) if (EAGAIN == errno)
{ {
case ENOENT: return errSSLClosedGraceful; return errSSLWouldBlock;
}
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx else
case EINPROGRESS: return errSSLWouldBlock; {
return errSecIO;
case ECONNRESET: return errSSLClosedAbort;
default: return errSecIO;
} }
} }
} }
std::string getSSLErrorDescription(OSStatus status)
bool SocketAppleSSL::accept(std::string& errMsg)
{ {
errMsg = "TLS not supported yet in server mode with apple ssl backend"; std::string errMsg("Unknown SSL error.");
return false;
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
if (error)
{
CFStringRef message = CFErrorCopyDescription(error);
if (message)
{
char localBuffer[128];
Boolean success;
success = CFStringGetCString(message, localBuffer, 128,
CFStringGetSystemEncoding());
if (success)
{
errMsg = localBuffer;
}
CFRelease(message);
}
CFRelease(error);
} }
OSStatus SocketAppleSSL::tlsHandShake(std::string& errMsg, return errMsg;
const CancellationRequest& isCancellationRequested)
{
OSStatus status;
do
{
status = SSLHandshake(_sslContext);
// Interrupt the handshake
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
return errSSLInternal;
} }
} while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted);
return status; } // anonymous namespace
namespace ix
{
SocketAppleSSL::SocketAppleSSL(int fd) : Socket(fd),
_sslContext(nullptr)
{
;
}
SocketAppleSSL::~SocketAppleSSL()
{
SocketAppleSSL::close();
} }
// No wait support // No wait support
@ -201,34 +169,18 @@ namespace ix
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType); _sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
SSLSetIOFuncs( SSLSetIOFuncs(_sslContext, read_from_socket, write_to_socket);
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef) (long) _sockfd); SSLSetConnection(_sslContext, (SSLConnectionRef) (long) _sockfd);
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12); SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
if (!_tlsOptions.disable_hostname_validation)
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size()); SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
if (_tlsOptions.isPeerVerifyDisabled()) do {
{ status = SSLHandshake(_sslContext);
Boolean option(1); } while (errSSLWouldBlock == status ||
SSLSetSessionOption(_sslContext, kSSLSessionOptionBreakOnServerAuth, option); errSSLServerAuthCompleted == status);
status = tlsHandShake(errMsg, isCancellationRequested);
if (status == errSSLServerAuthCompleted)
{
// proceed with the handshake
status = tlsHandShake(errMsg, isCancellationRequested);
}
}
else
{
status = tlsHandShake(errMsg, isCancellationRequested);
}
} }
if (status != noErr) if (noErr != status)
{ {
errMsg = getSSLErrorDescription(status); errMsg = getSSLErrorDescription(status);
close(); close();
@ -253,48 +205,44 @@ namespace ix
ssize_t SocketAppleSSL::send(char* buf, size_t nbyte) ssize_t SocketAppleSSL::send(char* buf, size_t nbyte)
{ {
OSStatus status = errSSLWouldBlock; ssize_t ret = 0;
while (status == errSSLWouldBlock) OSStatus status;
{ do {
size_t processed = 0; size_t processed = 0;
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
status = SSLWrite(_sslContext, buf, nbyte, &processed); status = SSLWrite(_sslContext, buf, nbyte, &processed);
ret += processed;
buf += processed;
nbyte -= processed;
} while (nbyte > 0 && errSSLWouldBlock == status);
if (processed > 0) return (ssize_t) processed; if (ret == 0 && errSSLClosedAbort != status)
ret = -1;
return ret;
}
// The connection was reset, inform the caller that this ssize_t SocketAppleSSL::send(const std::string& buffer)
// Socket should close
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
status == errSSLClosedAbort)
{ {
errno = ECONNRESET; return send((char*)&buffer[0], buffer.size());
return -1;
}
if (status == errSSLWouldBlock)
{
errno = EWOULDBLOCK;
return -1;
}
}
return -1;
} }
// No wait support // No wait support
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte) ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
{ {
OSStatus status = errSSLWouldBlock; OSStatus status = errSSLWouldBlock;
while (status == errSSLWouldBlock) while (errSSLWouldBlock == status)
{ {
size_t processed = 0; size_t processed = 0;
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
status = SSLRead(_sslContext, buf, nbyte, &processed); status = SSLRead(_sslContext, buf, nbyte, &processed);
if (processed > 0) return (ssize_t) processed; if (processed > 0)
return (ssize_t) processed;
// The connection was reset, inform the caller that this // The connection was reset, inform the caller that this
// Socket should close // Socket should close
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify || if (status == errSSLClosedGraceful ||
status == errSSLClosedNoNotify ||
status == errSSLClosedAbort) status == errSSLClosedAbort)
{ {
errno = ECONNRESET; errno = ECONNRESET;
@ -310,6 +258,4 @@ namespace ix
return -1; return -1;
} }
} // namespace ix }
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT

View File

@ -1,29 +1,27 @@
/* /*
* IXSocketAppleSSL.h * IXSocketAppleSSL.h
* Author: Benjamin Sergeant * Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/ */
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
#pragma once #pragma once
#include "IXCancellationRequest.h"
#include "IXSocket.h" #include "IXSocket.h"
#include "IXSocketTLSOptions.h" #include "IXCancellationRequest.h"
#include <Security/SecureTransport.h>
#include <Security/Security.h> #include <Security/Security.h>
#include <Security/SecureTransport.h>
#include <mutex> #include <mutex>
namespace ix namespace ix
{ {
class SocketAppleSSL final : public Socket class SocketAppleSSL : public Socket
{ {
public: public:
SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd = -1); SocketAppleSSL(int fd = -1);
~SocketAppleSSL(); ~SocketAppleSSL();
virtual bool accept(std::string& errMsg) final;
virtual bool connect(const std::string& host, virtual bool connect(const std::string& host,
int port, int port,
std::string& errMsg, std::string& errMsg,
@ -31,22 +29,12 @@ namespace ix
virtual void close() final; virtual void close() final;
virtual ssize_t send(char* 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; virtual ssize_t recv(void* buffer, size_t length) final;
private: private:
static std::string getSSLErrorDescription(OSStatus status);
static OSStatus writeToSocket(SSLConnectionRef connection, const void* data, size_t* len);
static OSStatus readFromSocket(SSLConnectionRef connection, void* data, size_t* len);
OSStatus tlsHandShake(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
SSLContextRef _sslContext; SSLContextRef _sslContext;
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
SocketTLSOptions _tlsOptions;
}; };
} // namespace ix }
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT

View File

@ -5,14 +5,11 @@
*/ */
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include "IXDNSLookup.h" #include "IXDNSLookup.h"
#include "IXNetSystem.h" #include "IXNetSystem.h"
#include "IXSelectInterrupt.h"
#include "IXSocket.h"
#include "IXUniquePtr.h"
#include <fcntl.h>
#include <string.h> #include <string.h>
#include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP // Android needs extra headers for TCP_NODELAY and IPPROTO_TCP
@ -20,15 +17,25 @@
# include <linux/in.h> # include <linux/in.h>
# include <linux/tcp.h> # include <linux/tcp.h>
#endif #endif
#include <ixwebsocket/IXSelectInterruptFactory.h>
namespace
{
void closeSocket(int fd)
{
#ifdef _WIN32
closesocket(fd);
#else
::close(fd);
#endif
}
}
namespace ix namespace ix
{ {
// //
// This function can be cancelled every 50 ms // This function can be cancelled every 50 ms
// This is important so that we don't block the main UI thread when shutting down a // This is important so that we don't block the main UI thread when shutting down a connection which is
// connection which is already trying to reconnect, and can be blocked waiting for // already trying to reconnect, and can be blocked waiting for ::connect to respond.
// ::connect to respond.
// //
int SocketConnect::connectToAddress(const struct addrinfo *address, int SocketConnect::connectToAddress(const struct addrinfo *address,
std::string& errMsg, std::string& errMsg,
@ -36,7 +43,9 @@ namespace ix
{ {
errMsg = "no error"; errMsg = "no error";
socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); int fd = socket(address->ai_family,
address->ai_socktype,
address->ai_protocol);
if (fd < 0) if (fd < 0)
{ {
errMsg = "Cannot create a socket"; errMsg = "Cannot create a socket";
@ -47,12 +56,11 @@ namespace ix
// block us for too long // block us for too long
SocketConnect::configure(fd); SocketConnect::configure(fd);
int res = ::connect(fd, address->ai_addr, address->ai_addrlen); if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
&& errno != EINPROGRESS && errno != 0)
if (res == -1 && !Socket::isWaitNeeded())
{ {
errMsg = strerror(Socket::getErrno()); errMsg = strerror(errno);
Socket::closeSocket(fd); closeSocket(fd);
return -1; return -1;
} }
@ -60,38 +68,63 @@ namespace ix
{ {
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
{ {
Socket::closeSocket(fd); closeSocket(fd);
errMsg = "Cancelled"; errMsg = "Cancelled";
return -1; return -1;
} }
int timeoutMs = 10; // Use select to check the status of the new connection
bool readyToRead = false; struct timeval timeout;
SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt(); timeout.tv_sec = 0;
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt); timeout.tv_usec = 10 * 1000; // 10ms timeout
fd_set wfds;
fd_set efds;
if (pollResult == PollResultType::Timeout) FD_ZERO(&wfds);
FD_SET(fd, &wfds);
FD_ZERO(&efds);
FD_SET(fd, &efds);
if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
{ {
continue; closeSocket(fd);
} errMsg = std::string("Connect error, select error: ") + strerror(errno);
else if (pollResult == PollResultType::Error)
{
Socket::closeSocket(fd);
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1; return -1;
} }
else if (pollResult == PollResultType::ReadyForWrite)
// Nothing was written to the socket, wait again.
if (!FD_ISSET(fd, &wfds)) continue;
// Something was written to the socket. Check for errors.
int optval = -1;
socklen_t optlen = sizeof(optval);
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (FD_ISSET(fd, &efds))
#else
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
optval != 0)
#endif
{ {
return fd; closeSocket(fd);
errMsg = strerror(optval);
return -1;
} }
else else
{ {
Socket::closeSocket(fd); // Success !
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno()); return fd;
}
}
closeSocket(fd);
errMsg = "connect timed out after 60 seconds";
return -1; return -1;
} }
}
}
int SocketConnect::connect(const std::string& hostname, int SocketConnect::connect(const std::string& hostname,
int port, int port,
@ -101,8 +134,8 @@ namespace ix
// //
// First do DNS resolution // First do DNS resolution
// //
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port); DNSLookup dnsLookup(hostname, port);
auto res = dnsLookup->resolve(errMsg, isCancellationRequested); struct addrinfo *res = dnsLookup.resolve(errMsg, isCancellationRequested);
if (res == nullptr) if (res == nullptr)
{ {
return -1; return -1;
@ -112,7 +145,7 @@ namespace ix
// iterate through the records to find a working peer // iterate through the records to find a working peer
struct addrinfo *address; struct addrinfo *address;
for (address = res.get(); address != nullptr; address = address->ai_next) for (address = res; address != nullptr; address = address->ai_next)
{ {
// //
// Second try to connect to the remote host // Second try to connect to the remote host
@ -124,6 +157,7 @@ namespace ix
} }
} }
freeaddrinfo(res);
return sockfd; return sockfd;
} }
@ -145,7 +179,8 @@ namespace ix
// 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect // 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
#ifdef SO_NOSIGPIPE #ifdef SO_NOSIGPIPE
int value = 1; int value = 1;
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value)); setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
(void *)&value, sizeof(value));
#endif #endif
} }
} // namespace ix }

View File

@ -7,14 +7,14 @@
#pragma once #pragma once
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include <string> #include <string>
struct addrinfo; struct addrinfo;
namespace ix namespace ix
{ {
class SocketConnect class SocketConnect {
{
public: public:
static int connect(const std::string& hostname, static int connect(const std::string& hostname,
int port, int port,
@ -28,4 +28,5 @@ namespace ix
std::string& errMsg, std::string& errMsg,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
}; };
} // namespace ix }

View File

@ -6,47 +6,43 @@
#include "IXSocketFactory.h" #include "IXSocketFactory.h"
#include "IXUniquePtr.h"
#ifdef IXWEBSOCKET_USE_TLS #ifdef IXWEBSOCKET_USE_TLS
#ifdef IXWEBSOCKET_USE_MBED_TLS # ifdef __APPLE__
#include "IXSocketMbedTLS.h" # include <ixwebsocket/IXSocketAppleSSL.h>
#elif defined(IXWEBSOCKET_USE_OPEN_SSL) # elif defined(_WIN32)
#include "IXSocketOpenSSL.h" # include <ixwebsocket/IXSocketSChannel.h>
#elif __APPLE__ # else
#include "IXSocketAppleSSL.h" # include <ixwebsocket/IXSocketOpenSSL.h>
# endif # endif
#else #else
#include "IXSocket.h" #include <ixwebsocket/IXSocket.h>
#endif #endif
namespace ix namespace ix
{ {
std::unique_ptr<Socket> createSocket(bool tls, std::shared_ptr<Socket> createSocket(bool tls,
int fd, std::string& errorMsg)
std::string& errorMsg,
const SocketTLSOptions& tlsOptions)
{ {
(void) tlsOptions;
errorMsg.clear(); errorMsg.clear();
std::unique_ptr<Socket> socket; std::shared_ptr<Socket> socket;
if (!tls) if (!tls)
{ {
socket = ix::make_unique<Socket>(fd); socket = std::make_shared<Socket>();
} }
else else
{ {
#ifdef IXWEBSOCKET_USE_TLS #ifdef IXWEBSOCKET_USE_TLS
#if defined(IXWEBSOCKET_USE_MBED_TLS) # ifdef __APPLE__
socket = ix::make_unique<SocketMbedTLS>(tlsOptions, fd); socket = std::make_shared<SocketAppleSSL>();
#elif defined(IXWEBSOCKET_USE_OPEN_SSL) # elif defined(_WIN32)
socket = ix::make_unique<SocketOpenSSL>(tlsOptions, fd); socket = std::make_shared<SocketSChannel>();
#elif defined(__APPLE__) # else
socket = ix::make_unique<SocketAppleSSL>(tlsOptions, fd); socket = std::make_shared<SocketOpenSSL>();
# endif # endif
#else #else
errorMsg = "TLS support is not enabled on this platform."; errorMsg = "TLS support is not enabled on this platform.";
@ -61,4 +57,18 @@ namespace ix
return socket; return socket;
} }
} // namespace ix
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

@ -7,15 +7,15 @@
#pragma once #pragma once
#include "IXSocketTLSOptions.h"
#include <memory> #include <memory>
#include <string> #include <string>
namespace ix namespace ix
{ {
class Socket; class Socket;
std::unique_ptr<Socket> createSocket(bool tls, std::shared_ptr<Socket> createSocket(bool tls,
int fd, std::string& errorMsg);
std::string& errorMsg,
const SocketTLSOptions& tlsOptions); std::shared_ptr<Socket> createSocket(int fd,
} // namespace ix std::string& errorMsg);
}

View File

@ -1,377 +0,0 @@
/*
* IXSocketMbedTLS.cpp
* Author: Benjamin Sergeant, Max Weisel
* Copyright (c) 2019-2020 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
*/
#ifdef IXWEBSOCKET_USE_MBED_TLS
#include "IXSocketMbedTLS.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include "IXSocketConnect.h"
#include <cstdint>
#include <string.h>
#ifdef _WIN32
// For manipulating the certificate store
#include <wincrypt.h>
#endif
namespace ix
{
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _tlsOptions(tlsOptions)
{
initMBedTLS();
}
SocketMbedTLS::~SocketMbedTLS()
{
SocketMbedTLS::close();
}
void SocketMbedTLS::initMBedTLS()
{
std::lock_guard<std::mutex> lock(_mutex);
mbedtls_ssl_init(&_ssl);
mbedtls_ssl_config_init(&_conf);
mbedtls_ctr_drbg_init(&_ctr_drbg);
mbedtls_entropy_init(&_entropy);
mbedtls_x509_crt_init(&_cacert);
mbedtls_x509_crt_init(&_cert);
mbedtls_pk_init(&_pkey);
// Initialize the PSA Crypto API if required by the version of Mbed TLS (3.6.0).
// This allows the X.509/TLS libraries to use PSA for crypto operations.
// See: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/use-psa-crypto.md
if (MBEDTLS_VERSION_MAJOR >= 3 && MBEDTLS_VERSION_MINOR >= 6 && MBEDTLS_VERSION_PATCH >= 0)
{
psa_crypto_init();
}
}
bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg)
{
#ifdef _WIN32
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
CERT_SYSTEM_STORE_CURRENT_USER;
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
if (!systemStore)
{
errorMsg = "CertOpenStore failed with ";
errorMsg += std::to_string(GetLastError());
return false;
}
PCCERT_CONTEXT certificateIterator = NULL;
int certificateCount = 0;
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
{
if (certificateIterator->dwCertEncodingType & X509_ASN_ENCODING)
{
int ret = mbedtls_x509_crt_parse(&_cacert,
certificateIterator->pbCertEncoded,
certificateIterator->cbCertEncoded);
if (ret == 0)
{
++certificateCount;
}
}
}
CertFreeCertificateContext(certificateIterator);
CertCloseStore(systemStore, 0);
if (certificateCount == 0)
{
errorMsg = "No certificates found";
return false;
}
return true;
#else
// On macOS we can query the system cert location from the keychain
// On Linux we could try to fetch some local files based on the distribution
// On Android we could use JNI to get to the system certs
return false;
#endif
}
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
{
initMBedTLS();
std::lock_guard<std::mutex> lock(_mutex);
const char* pers = "IXSocketMbedTLS";
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,
(isClient) ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
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);
if (_tlsOptions.hasCertAndKey())
{
if (mbedtls_x509_crt_parse_file(&_cert, _tlsOptions.certFile.c_str()) < 0)
{
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
return false;
}
#ifdef IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "", mbedtls_ctr_drbg_random, &_ctr_drbg) < 0)
#else
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
#endif
{
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
return false;
}
if (mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey) < 0)
{
errMsg = "Problem configuring cert '" + _tlsOptions.certFile + "'";
return false;
}
}
if (_tlsOptions.isPeerVerifyDisabled())
{
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
}
else
{
// FIXME: should we call mbedtls_ssl_conf_verify ?
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
if (_tlsOptions.isUsingSystemDefaults())
{
if (!loadSystemCertificates(errMsg))
{
return false;
}
}
else
{
if (_tlsOptions.isUsingInMemoryCAs())
{
const char* buffer = _tlsOptions.caFile.c_str();
size_t bufferSize =
_tlsOptions.caFile.size() + 1; // Needs to include null terminating
// character otherwise mbedtls will fail.
if (mbedtls_x509_crt_parse(
&_cacert, (const unsigned char*) buffer, bufferSize) < 0)
{
errMsg = "Cannot parse CA from memory.";
return false;
}
}
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
{
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
return false;
}
}
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
}
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
{
errMsg = "SSL setup failed";
return false;
}
if (!_tlsOptions.disable_hostname_validation)
{
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
{
errMsg = "SNI setup failed";
return false;
}
}
return true;
}
bool SocketMbedTLS::accept(std::string& errMsg)
{
bool isClient = false;
bool initialized = init(std::string(), isClient, errMsg);
if (!initialized)
{
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;
if (res == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
{
char verifyBuf[512];
uint32_t flags = mbedtls_ssl_get_verify_result(&_ssl);
mbedtls_x509_crt_verify_info(verifyBuf, sizeof(verifyBuf), " ! ", flags);
errMsg += " : ";
errMsg += verifyBuf;
}
close();
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;
}
bool isClient = true;
bool initialized = init(host, isClient, errMsg);
if (!initialized)
{
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);
}
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
close();
return false;
}
} 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);
mbedtls_x509_crt_free(&_cacert);
mbedtls_x509_crt_free(&_cert);
Socket::close();
}
ssize_t SocketMbedTLS::send(char* buf, size_t nbyte)
{
std::lock_guard<std::mutex> lock(_mutex);
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
if (res > 0)
{
return res;
}
else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{
errno = EWOULDBLOCK;
return -1;
}
else
{
return -1;
}
}
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 == 0)
{
errno = ECONNRESET;
}
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{
errno = EWOULDBLOCK;
}
return -1;
}
}
} // namespace ix
#endif // IXWEBSOCKET_USE_MBED_TLS

View File

@ -1,60 +0,0 @@
/*
* IXSocketMbedTLS.h
* Author: Benjamin Sergeant
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
*/
#ifdef IXWEBSOCKET_USE_MBED_TLS
#pragma once
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/debug.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/platform.h>
#include <mbedtls/x509.h>
#include <mbedtls/x509_crt.h>
#include <mutex>
namespace ix
{
class SocketMbedTLS final : public Socket
{
public:
SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd = -1);
~SocketMbedTLS();
virtual bool accept(std::string& errMsg) final;
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 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;
mbedtls_x509_crt _cacert;
mbedtls_x509_crt _cert;
mbedtls_pk_context _pkey;
std::mutex _mutex;
SocketTLSOptions _tlsOptions;
bool init(const std::string& host, bool isClient, std::string& errMsg);
void initMBedTLS();
bool loadSystemCertificates(std::string& errMsg);
};
} // namespace ix
#endif // IXWEBSOCKET_USE_MBED_TLS

View File

@ -1,105 +1,31 @@
/* /*
* IXSocketOpenSSL.cpp * IXSocketOpenSSL.cpp
* Author: Benjamin Sergeant, Matt DeBoer, Max Weisel * Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
* *
* Adapted from Satori SDK OpenSSL code. * Adapted from Satori SDK OpenSSL code.
*/ */
#ifdef IXWEBSOCKET_USE_OPEN_SSL
#include "IXSocketOpenSSL.h" #include "IXSocketOpenSSL.h"
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include "IXUniquePtr.h"
#include <cassert> #include <cassert>
#include <errno.h> #include <iostream>
#include <vector>
#ifdef _WIN32
#include <shlwapi.h>
#else
#include <fnmatch.h>
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#include <openssl/x509v3.h> #include <openssl/x509v3.h>
#endif
#include <fnmatch.h>
#include <errno.h>
#define socketerrno errno #define socketerrno errno
#ifdef _WIN32
// For manipulating the certificate store
#include <windows.h>
#include <wincrypt.h>
#endif
#ifdef _WIN32
namespace
{
bool loadWindowsSystemCertificates(SSL_CTX* ssl, std::string& errorMsg)
{
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
CERT_SYSTEM_STORE_CURRENT_USER;
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
if (!systemStore)
{
errorMsg = "CertOpenStore failed with ";
errorMsg += std::to_string(GetLastError());
return false;
}
PCCERT_CONTEXT certificateIterator = NULL;
X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl);
int certificateCount = 0;
while ((certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator)))
{
X509* x509 = d2i_X509(NULL,
(const unsigned char**) &certificateIterator->pbCertEncoded,
certificateIterator->cbCertEncoded);
if (x509)
{
if (X509_STORE_add_cert(opensslStore, x509) == 1)
{
++certificateCount;
}
X509_free(x509);
}
}
CertFreeCertificateContext(certificateIterator);
CertCloseStore(systemStore, 0);
if (certificateCount == 0)
{
errorMsg = "No certificates found";
return false;
}
return true;
}
} // namespace
#endif
namespace ix namespace ix
{ {
const std::string kDefaultCiphers =
"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA "
"ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384 "
"ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA "
"ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 "
"DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA "
"DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 AES128-SHA";
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false); std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
std::once_flag SocketOpenSSL::_openSSLInitFlag; std::once_flag SocketOpenSSL::_openSSLInitFlag;
std::vector<std::unique_ptr<std::mutex>> openSSLMutexes;
SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd) SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
: Socket(fd) _ssl_connection(nullptr),
, _ssl_connection(nullptr) _ssl_context(nullptr)
, _ssl_context(nullptr)
, _tlsOptions(tlsOptions)
{ {
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this); std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
} }
@ -115,16 +41,6 @@ namespace ix
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return; if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
#else #else
(void) OPENSSL_config(nullptr); (void) OPENSSL_config(nullptr);
if (CRYPTO_get_locking_callback() == nullptr)
{
openSSLMutexes.clear();
for (int i = 0; i < CRYPTO_num_locks(); ++i)
{
openSSLMutexes.push_back(ix::make_unique<std::mutex>());
}
CRYPTO_set_locking_callback(SocketOpenSSL::openSSLLockingCallback);
}
#endif #endif
(void) OpenSSL_add_ssl_algorithms(); (void) OpenSSL_add_ssl_algorithms();
@ -133,21 +49,6 @@ namespace ix
_openSSLInitializationSuccessful = true; _openSSLInitializationSuccessful = true;
} }
void SocketOpenSSL::openSSLLockingCallback(int mode,
int type,
const char* /*file*/,
int /*line*/)
{
if (mode & CRYPTO_LOCK)
{
openSSLMutexes[type]->lock();
}
else
{
openSSLMutexes[type]->unlock();
}
}
std::string SocketOpenSSL::getSSLError(int ret) std::string SocketOpenSSL::getSSLError(int ret)
{ {
unsigned long e; unsigned long e;
@ -214,105 +115,29 @@ namespace ix
SSL_CTX* ctx = SSL_CTX_new(_ssl_method); SSL_CTX* ctx = SSL_CTX_new(_ssl_method);
if (ctx) if (ctx)
{ {
SSL_CTX_set_mode(ctx, // To skip verification, pass in SSL_VERIFY_NONE
SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
[](int preverify, X509_STORE_CTX*) -> int
{
return preverify;
});
int options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE; SSL_CTX_set_verify_depth(ctx, 4);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
#ifdef SSL_OP_NO_TLSv1_3
// (partially?) work around hang in openssl 1.1.1b, by disabling TLS V1.3
// https://github.com/openssl/openssl/issues/7967
options |= SSL_OP_NO_TLSv1_3;
#endif
SSL_CTX_set_options(ctx, options);
} }
return ctx; return ctx;
} }
bool SocketOpenSSL::openSSLAddCARootsFromString(const std::string roots)
{
// Create certificate store
X509_STORE* certificate_store = SSL_CTX_get_cert_store(_ssl_context);
if (certificate_store == nullptr) return false;
// Configure to allow intermediate certs
X509_STORE_set_flags(certificate_store,
X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_PARTIAL_CHAIN);
// Create a new buffer and populate it with the roots
BIO* buffer = BIO_new_mem_buf((void*) roots.c_str(), static_cast<int>(roots.length()));
if (buffer == nullptr) return false;
// Read each root in the buffer and add to the certificate store
bool success = true;
size_t number_of_roots = 0;
while (true)
{
// Read the next root in the buffer
X509* root = PEM_read_bio_X509_AUX(buffer, nullptr, nullptr, (void*) "");
if (root == nullptr)
{
// No more certs left in the buffer, we're done.
ERR_clear_error();
break;
}
// Try adding the root to the certificate store
ERR_clear_error();
if (!X509_STORE_add_cert(certificate_store, root))
{
// Failed to add. If the error is unrelated to the x509 lib or the cert already
// exists, we're safe to continue.
unsigned long error = ERR_get_error();
if (ERR_GET_LIB(error) != ERR_LIB_X509 ||
ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE)
{
// Failed. Clean up and bail.
success = false;
X509_free(root);
break;
}
}
// Clean up and loop
X509_free(root);
number_of_roots++;
}
// Clean up buffer
BIO_free(buffer);
// Make sure we loaded at least one certificate.
if (number_of_roots == 0) success = false;
return success;
}
/** /**
* Check whether a hostname matches a pattern * Check whether a hostname matches a pattern
*/ */
bool SocketOpenSSL::checkHost(const std::string& host, const char *pattern) bool SocketOpenSSL::checkHost(const std::string& host, const char *pattern)
{ {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
return true;
#else
#ifdef _WIN32
return PathMatchSpecA(host.c_str(), pattern);
#else
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH; return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
#endif
#endif
} }
bool SocketOpenSSL::openSSLCheckServerCert(SSL *ssl, bool SocketOpenSSL::openSSLCheckServerCert(SSL *ssl,
#if OPENSSL_VERSION_NUMBER < 0x10100000L
const std::string& hostname, const std::string& hostname,
#else
const std::string& /* hostname */,
#endif
std::string& errMsg) std::string& errMsg)
{ {
X509 *server_cert = SSL_get_peer_certificate(ssl); X509 *server_cert = SSL_get_peer_certificate(ssl);
@ -325,8 +150,9 @@ namespace ix
#if OPENSSL_VERSION_NUMBER < 0x10100000L #if OPENSSL_VERSION_NUMBER < 0x10100000L
// Check server name // Check server name
bool hostname_verifies_ok = false; bool hostname_verifies_ok = false;
STACK_OF(GENERAL_NAME)* san_names = (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i( STACK_OF(GENERAL_NAME) *san_names =
(X509*) server_cert, NID_subject_alt_name, NULL, NULL); (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert,
NID_subject_alt_name, NULL, NULL);
if (san_names) if (san_names)
{ {
for (int i=0; i<sk_GENERAL_NAME_num(san_names); i++) for (int i=0; i<sk_GENERAL_NAME_num(san_names); i++)
@ -348,14 +174,14 @@ namespace ix
if (!hostname_verifies_ok) if (!hostname_verifies_ok)
{ {
int cn_pos = X509_NAME_get_index_by_NID( int cn_pos = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *)server_cert),
X509_get_subject_name((X509*) server_cert), NID_commonName, -1); NID_commonName, -1);
if (cn_pos >= 0) if (cn_pos)
{ {
X509_NAME_ENTRY* cn_entry = X509_NAME_ENTRY *cn_entry = X509_NAME_get_entry(
X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos); X509_get_subject_name((X509 *)server_cert), cn_pos);
if (cn_entry != nullptr) if (cn_entry)
{ {
ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
char *cn = (char *)ASN1_STRING_data(cn_asn1); char *cn = (char *)ASN1_STRING_data(cn_asn1);
@ -380,9 +206,7 @@ namespace ix
return true; return true;
} }
bool SocketOpenSSL::openSSLClientHandshake(const std::string& host, bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg)
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{ {
while (true) while (true)
{ {
@ -391,21 +215,10 @@ namespace ix
return false; return false;
} }
if (isCancellationRequested())
{
errMsg = "Cancellation requested";
return false;
}
ERR_clear_error(); ERR_clear_error();
int connect_result = SSL_connect(_ssl_connection); int connect_result = SSL_connect(_ssl_connection);
if (connect_result == 1) if (connect_result == 1)
{ {
if (_tlsOptions.disable_hostname_validation)
{
return true;
}
return openSSLCheckServerCert(_ssl_connection, host, errMsg); return openSSLCheckServerCert(_ssl_connection, host, errMsg);
} }
int reason = SSL_get_error(_ssl_connection, connect_result); int reason = SSL_get_error(_ssl_connection, connect_result);
@ -428,301 +241,7 @@ namespace ix
} }
} }
bool SocketOpenSSL::openSSLServerHandshake(std::string& errMsg) // No wait support
{
while (true)
{
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return false;
}
ERR_clear_error();
int accept_result = SSL_accept(_ssl_connection);
if (accept_result == 1)
{
return true;
}
int reason = SSL_get_error(_ssl_connection, accept_result);
bool rc = false;
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
rc = true;
}
else
{
errMsg = getSSLError(accept_result);
rc = false;
}
if (!rc)
{
return false;
}
}
}
bool SocketOpenSSL::handleTLSOptions(std::string& errMsg)
{
ERR_clear_error();
if (_tlsOptions.hasCertAndKey())
{
if (SSL_CTX_use_certificate_chain_file(_ssl_context, _tlsOptions.certFile.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" +
_tlsOptions.certFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else if (SSL_CTX_use_PrivateKey_file(
_ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" + _tlsOptions.keyFile +
"\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else if (!SSL_CTX_check_private_key(_ssl_context))
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - cert/key mismatch(\"" + _tlsOptions.certFile + ", " +
_tlsOptions.keyFile + "\")";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
ERR_clear_error();
if (!_tlsOptions.isPeerVerifyDisabled())
{
if (_tlsOptions.isUsingSystemDefaults())
{
#ifdef _WIN32
if (!loadWindowsSystemCertificates(_ssl_context, errMsg))
{
return false;
}
#else
if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
#endif
}
else
{
if (_tlsOptions.isUsingInMemoryCAs())
{
// Load from memory
openSSLAddCARootsFromString(_tlsOptions.caFile);
}
else
{
if (SSL_CTX_load_verify_locations(
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
_tlsOptions.caFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
}
}
SSL_CTX_set_verify(_ssl_context,
SSL_VERIFY_PEER,
[](int preverify, X509_STORE_CTX*) -> int { return preverify; });
SSL_CTX_set_verify_depth(_ssl_context, 4);
}
else
{
SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr);
}
if (_tlsOptions.isUsingDefaultCiphers())
{
if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" + kDefaultCiphers +
"\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
}
else if (SSL_CTX_set_cipher_list(_ssl_context, _tlsOptions.ciphers.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" + _tlsOptions.ciphers +
"\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
return false;
}
return true;
}
bool SocketOpenSSL::accept(std::string& errMsg)
{
bool handshakeSuccessful = false;
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_openSSLInitializationSuccessful)
{
errMsg = "OPENSSL_init_ssl failure";
return false;
}
if (_sockfd == -1)
{
return false;
}
{
const SSL_METHOD* method = SSLv23_server_method();
if (method == nullptr)
{
errMsg = "SSLv23_server_method failure";
_ssl_context = nullptr;
}
else
{
_ssl_method = method;
_ssl_context = SSL_CTX_new(_ssl_method);
if (_ssl_context)
{
SSL_CTX_set_mode(_ssl_context, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_mode(_ssl_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_options(_ssl_context,
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
}
}
}
if (_ssl_context == nullptr)
{
return false;
}
ERR_clear_error();
if (_tlsOptions.hasCertAndKey())
{
if (SSL_CTX_use_certificate_chain_file(_ssl_context,
_tlsOptions.certFile.c_str()) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" +
_tlsOptions.certFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else if (SSL_CTX_use_PrivateKey_file(
_ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" +
_tlsOptions.keyFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
ERR_clear_error();
if (!_tlsOptions.isPeerVerifyDisabled())
{
if (_tlsOptions.isUsingSystemDefaults())
{
if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
else
{
if (_tlsOptions.isUsingInMemoryCAs())
{
// Load from memory
openSSLAddCARootsFromString(_tlsOptions.caFile);
}
else
{
const char* root_ca_file = _tlsOptions.caFile.c_str();
STACK_OF(X509_NAME) * rootCAs;
rootCAs = SSL_load_client_CA_file(root_ca_file);
if (rootCAs == NULL)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" +
_tlsOptions.caFile + "') failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
else
{
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
if (SSL_CTX_load_verify_locations(
_ssl_context, root_ca_file, nullptr) != 1)
{
auto sslErr = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
_tlsOptions.caFile + "\") failed: ";
errMsg += ERR_error_string(sslErr, nullptr);
}
}
}
}
SSL_CTX_set_verify(
_ssl_context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
SSL_CTX_set_verify_depth(_ssl_context, 4);
}
else
{
SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr);
}
if (_tlsOptions.isUsingDefaultCiphers())
{
if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) != 1)
{
return false;
}
}
else if (SSL_CTX_set_cipher_list(_ssl_context, _tlsOptions.ciphers.c_str()) != 1)
{
return false;
}
_ssl_connection = SSL_new(_ssl_context);
if (_ssl_connection == nullptr)
{
errMsg = "OpenSSL failed to connect";
SSL_CTX_free(_ssl_context);
_ssl_context = nullptr;
return false;
}
SSL_set_ecdh_auto(_ssl_connection, 1);
SSL_set_fd(_ssl_connection, _sockfd);
handshakeSuccessful = openSSLServerHandshake(errMsg);
}
if (!handshakeSuccessful)
{
close();
return false;
}
return true;
}
bool SocketOpenSSL::connect(const std::string& host, bool SocketOpenSSL::connect(const std::string& host,
int port, int port,
std::string& errMsg, std::string& errMsg,
@ -747,9 +266,13 @@ namespace ix
return false; return false;
} }
if (!handleTLSOptions(errMsg)) ERR_clear_error();
int cert_load_result = SSL_CTX_set_default_verify_paths(_ssl_context);
if (cert_load_result == 0)
{ {
return false; unsigned long ssl_err = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
errMsg += ERR_error_string(ssl_err, nullptr);
} }
_ssl_connection = SSL_new(_ssl_context); _ssl_connection = SSL_new(_ssl_context);
@ -768,15 +291,13 @@ namespace ix
#if OPENSSL_VERSION_NUMBER >= 0x10002000L #if OPENSSL_VERSION_NUMBER >= 0x10002000L
// Support for server name verification // Support for server name verification
// (The docs say that this should work from 1.0.2, and is the default from // (The docs say that this should work from 1.0.2, and is the default from
// 1.1.0, but it does not. To be on the safe side, the manual test // 1.1.0, but it does not. To be on the safe side, the manual test below is
// below is enabled for all versions prior to 1.1.0.) // enabled for all versions prior to 1.1.0.)
if (!_tlsOptions.disable_hostname_validation)
{
X509_VERIFY_PARAM *param = SSL_get0_param(_ssl_connection); X509_VERIFY_PARAM *param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size()); X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
}
#endif #endif
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
handshakeSuccessful = openSSLHandshake(host, errMsg);
} }
if (!handshakeSuccessful) if (!handshakeSuccessful)
@ -807,6 +328,10 @@ namespace ix
} }
ssize_t SocketOpenSSL::send(char* buf, size_t nbyte) ssize_t SocketOpenSSL::send(char* buf, size_t nbyte)
{
ssize_t sent = 0;
while (nbyte > 0)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
@ -816,24 +341,28 @@ namespace ix
} }
ERR_clear_error(); ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf, (int) nbyte); ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, (int) write_result); int reason = SSL_get_error(_ssl_connection, (int) write_result);
if (reason == SSL_ERROR_NONE) if (reason == SSL_ERROR_NONE) {
{ nbyte -= write_result;
return write_result; sent += write_result;
} } else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) {
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
errno = EWOULDBLOCK; errno = EWOULDBLOCK;
return -1; return -1;
} } else {
else
{
return -1; return -1;
} }
} }
return sent;
}
ssize_t SocketOpenSSL::send(const std::string& buffer)
{
return send((char*)&buffer[0], buffer.size());
}
// No wait support
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte) ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
{ {
while (true) while (true)
@ -863,6 +392,4 @@ namespace ix
} }
} }
} // namespace ix }
#endif // IXWEBSOCKET_USE_OPEN_SSL

View File

@ -1,32 +1,30 @@
/* /*
* IXSocketOpenSSL.h * IXSocketOpenSSL.h
* Author: Benjamin Sergeant, Matt DeBoer * Author: Benjamin Sergeant
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved. * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/ */
#ifdef IXWEBSOCKET_USE_OPEN_SSL
#pragma once #pragma once
#include "IXCancellationRequest.h"
#include "IXSocket.h" #include "IXSocket.h"
#include "IXSocketTLSOptions.h" #include "IXCancellationRequest.h"
#include <mutex>
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/hmac.h>
#include <openssl/conf.h> #include <openssl/conf.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/hmac.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <mutex>
namespace ix namespace ix
{ {
class SocketOpenSSL final : public Socket class SocketOpenSSL : public Socket
{ {
public: public:
SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd = -1); SocketOpenSSL(int fd = -1);
~SocketOpenSSL(); ~SocketOpenSSL();
virtual bool accept(std::string& errMsg) final;
virtual bool connect(const std::string& host, virtual bool connect(const std::string& host,
int port, int port,
std::string& errMsg, std::string& errMsg,
@ -34,35 +32,26 @@ namespace ix
virtual void close() final; virtual void close() final;
virtual ssize_t send(char* 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; virtual ssize_t recv(void* buffer, size_t length) final;
private: private:
void openSSLInitialize(); void openSSLInitialize();
std::string getSSLError(int ret); std::string getSSLError(int ret);
SSL_CTX* openSSLCreateContext(std::string& errMsg); SSL_CTX* openSSLCreateContext(std::string& errMsg);
bool openSSLAddCARootsFromString(const std::string roots); bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
bool openSSLClientHandshake(const std::string& hostname, bool openSSLCheckServerCert(SSL *ssl,
std::string& errMsg, const std::string& hostname,
const CancellationRequest& isCancellationRequested); std::string& errMsg);
bool openSSLCheckServerCert(SSL* ssl, const std::string& hostname, std::string& errMsg);
bool checkHost(const std::string& host, const char *pattern); bool checkHost(const std::string& host, const char *pattern);
bool handleTLSOptions(std::string& errMsg);
bool openSSLServerHandshake(std::string& errMsg);
// Required for OpenSSL < 1.1
static void openSSLLockingCallback(int mode, int type, const char* /*file*/, int /*line*/);
SSL* _ssl_connection; SSL* _ssl_connection;
SSL_CTX* _ssl_context; SSL_CTX* _ssl_context;
const SSL_METHOD* _ssl_method; const SSL_METHOD* _ssl_method;
SocketTLSOptions _tlsOptions;
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
static std::once_flag _openSSLInitFlag; static std::once_flag _openSSLInitFlag;
static std::atomic<bool> _openSSLInitializationSuccessful; static std::atomic<bool> _openSSLInitializationSuccessful;
}; };
} // namespace ix }
#endif // IXWEBSOCKET_USE_OPEN_SSL

View File

@ -0,0 +1,106 @@
/*
* IXSocketSChannel.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*
* See https://docs.microsoft.com/en-us/windows/desktop/WinSock/using-secure-socket-extensions
*
* https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/netds/winsock/securesocket/stcpclient/tcpclient.c
*
* This is the right example to look at:
* https://www.codeproject.com/Articles/1000189/A-Working-TCP-Client-and-Server-With-SSL
*/
#include "IXSocketSChannel.h"
#ifdef _WIN32
# include <basetsd.h>
# include <WinSock2.h>
# include <ws2def.h>
# include <WS2tcpip.h>
# include <schannel.h>
# include <io.h>
#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <winsock2.h>
#include <mstcpip.h>
#include <ws2tcpip.h>
#include <rpc.h>
#include <ntdsapi.h>
#include <stdio.h>
#include <tchar.h>
#define RECV_DATA_BUF_SIZE 256
// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")
// link with fwpuclnt.lib for Winsock secure socket extensions
#pragma comment(lib, "fwpuclnt.lib")
// link with ntdsapi.lib for DsMakeSpn function
#pragma comment(lib, "ntdsapi.lib")
// The following function assumes that Winsock
// has already been initialized
#else
# error("This file should only be built on Windows")
#endif
namespace ix
{
SocketSChannel::SocketSChannel()
{
;
}
SocketSChannel::~SocketSChannel()
{
}
bool SocketSChannel::connect(const std::string& host,
int port,
std::string& errMsg)
{
return Socket::connect(host, port, errMsg, nullptr);
}
void SocketSChannel::secureSocket()
{
// there will be a lot to do here ...
}
void SocketSChannel::close()
{
Socket::close();
}
ssize_t SocketSChannel::send(char* buf, size_t nbyte)
{
return Socket::send(buf, nbyte);
}
ssize_t SocketSChannel::send(const std::string& buffer)
{
return Socket::send(buffer);
}
ssize_t SocketSChannel::recv(void* buf, size_t nbyte)
{
return Socket::recv(buf, nbyte);
}
}

View File

@ -0,0 +1,34 @@
/*
* IXSocketSChannel.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXSocket.h"
namespace ix
{
class SocketSChannel : public Socket
{
public:
SocketSChannel();
~SocketSChannel();
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 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:
};
}

View File

@ -5,40 +5,35 @@
*/ */
#include "IXSocketServer.h" #include "IXSocketServer.h"
#include "IXNetSystem.h"
#include "IXSelectInterrupt.h"
#include "IXSelectInterruptFactory.h"
#include "IXSetThreadName.h"
#include "IXSocket.h" #include "IXSocket.h"
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include "IXSocketFactory.h" #include "IXNetSystem.h"
#include <assert.h>
#include <iostream>
#include <sstream> #include <sstream>
#include <stdio.h> #include <future>
#include <string.h> #include <string.h>
#include <assert.h>
namespace ix namespace ix
{ {
const int SocketServer::kDefaultPort(8080); const int SocketServer::kDefaultPort(8080);
const std::string SocketServer::kDefaultHost("127.0.0.1"); const std::string SocketServer::kDefaultHost("127.0.0.1");
const int SocketServer::kDefaultTcpBacklog(5); const int SocketServer::kDefaultTcpBacklog(5);
const size_t SocketServer::kDefaultMaxConnections(128); const size_t SocketServer::kDefaultMaxConnections(32);
const int SocketServer::kDefaultAddressFamily(AF_INET);
SocketServer::SocketServer( SocketServer::SocketServer(int port,
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily) const std::string& host,
: _port(port) int backlog,
, _host(host) size_t maxConnections) :
, _backlog(backlog) _port(port),
, _maxConnections(maxConnections) _host(host),
, _addressFamily(addressFamily) _backlog(backlog),
, _serverFd(-1) _maxConnections(maxConnections),
, _stop(false) _stop(false),
, _stopGc(false) _connectionStateFactory(&ConnectionState::createConnectionState)
, _connectionStateFactory(&ConnectionState::createConnectionState)
, _acceptSelectInterrupt(createSelectInterrupt())
{ {
} }
SocketServer::~SocketServer() SocketServer::~SocketServer()
@ -49,113 +44,66 @@ namespace ix
void SocketServer::logError(const std::string& str) void SocketServer::logError(const std::string& str)
{ {
std::lock_guard<std::mutex> lock(_logMutex); std::lock_guard<std::mutex> lock(_logMutex);
fprintf(stderr, "%s\n", str.c_str()); std::cerr << str << std::endl;
} }
void SocketServer::logInfo(const std::string& str) void SocketServer::logInfo(const std::string& str)
{ {
std::lock_guard<std::mutex> lock(_logMutex); std::lock_guard<std::mutex> lock(_logMutex);
fprintf(stdout, "%s\n", str.c_str()); std::cout << str << std::endl;
} }
std::pair<bool, std::string> SocketServer::listen() std::pair<bool, std::string> SocketServer::listen()
{ {
std::string acceptSelectInterruptInitErrorMsg; struct sockaddr_in server; // server address information
if (!_acceptSelectInterrupt->init(acceptSelectInterruptInitErrorMsg))
{
std::stringstream ss;
ss << "SocketServer::listen() error in SelectInterrupt::init: "
<< acceptSelectInterruptInitErrorMsg;
return std::make_pair(false, ss.str());
}
if (_addressFamily != AF_INET && _addressFamily != AF_INET6)
{
std::string errMsg("SocketServer::listen() AF_INET and AF_INET6 are currently "
"the only supported address families");
return std::make_pair(false, errMsg);
}
// Get a socket for accepting connections. // Get a socket for accepting connections.
if ((_serverFd = socket(_addressFamily, SOCK_STREAM, 0)) < 0) if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{ {
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::listen() error creating socket): " << strerror(Socket::getErrno()); ss << "SocketServer::listen() error creating socket): "
<< strerror(Socket::getErrno());
return std::make_pair(false, ss.str()); return std::make_pair(false, ss.str());
} }
// Make that socket reusable. (allow restarting this server at will) // Make that socket reusable. (allow restarting this server at will)
int enable = 1; int enable = 1;
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0) if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR,
(char*) &enable, sizeof(enable)) < 0)
{ {
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) " ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) "
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno()); << "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd); ::close(_serverFd);
return std::make_pair(false, ss.str()); return std::make_pair(false, ss.str());
} }
if (_addressFamily == AF_INET) // Bind the socket to the server address.
{ server.sin_family = AF_INET;
struct sockaddr_in server;
server.sin_family = _addressFamily;
server.sin_port = htons(_port); server.sin_port = htons(_port);
if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_addr) <= 0) // Using INADDR_ANY trigger a pop-up box as binding to any address is detected
{ // by the osx firewall. We need to codesign the binary with a self-signed cert
std::stringstream ss; // to allow that, but this is a bit of a pain. (this is what node or python would do).
ss << "SocketServer::listen() error calling inet_pton " //
<< "at address " << _host << ":" << _port << " : " // Using INADDR_LOOPBACK also does not work ... while it should.
<< strerror(Socket::getErrno()); // We default to 127.0.0.1 (localhost)
//
server.sin_addr.s_addr = inet_addr(_host.c_str());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0) if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0)
{ {
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::listen() error calling bind " ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port << " : " << "at address " << _host << ":" << _port
<< strerror(Socket::getErrno()); << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd); ::close(_serverFd);
return std::make_pair(false, ss.str()); return std::make_pair(false, ss.str());
} }
}
else // AF_INET6
{
struct sockaddr_in6 server;
server.sin6_family = _addressFamily;
server.sin6_port = htons(_port);
if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <= 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling inet_pton "
<< "at address " << _host << ":" << _port << " : "
<< strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port << " : "
<< strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
}
// //
// Listen for connections. Specify the tcp backlog. // Listen for connections. Specify the tcp backlog.
@ -164,9 +112,10 @@ namespace ix
{ {
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::listen() error calling listen " ss << "SocketServer::listen() error calling listen "
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno()); << "at address " << _host << ":" << _port
<< " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd); ::close(_serverFd);
return std::make_pair(false, ss.str()); return std::make_pair(false, ss.str());
} }
@ -175,19 +124,11 @@ namespace ix
void SocketServer::start() void SocketServer::start()
{ {
_stop = false; 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() void SocketServer::wait()
{ {
std::unique_lock<std::mutex> lock(_conditionVariableMutex); std::unique_lock<std::mutex> lock(_conditionVariableMutex);
@ -201,35 +142,24 @@ namespace ix
void SocketServer::stop() void SocketServer::stop()
{ {
// Stop accepting connections, and close the 'accept' thread while (true)
if (_thread.joinable())
{ {
_stop = true; if (closeTerminatedThreads()) break;
// Wake up select
if (!_acceptSelectInterrupt->notify(SelectInterrupt::kCloseRequest)) // wait 10ms and try again later.
{ // we could have a timeout, but if we exit of here
logError("SocketServer::stop: Cannot wake up from select"); // we leaked threads, it is quite bad.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
if (!_thread.joinable()) return; // nothing to do
_stop = true;
_thread.join(); _thread.join();
_stop = false; _stop = false;
}
// Join all threads and make sure that all connections are terminated
if (_gcThread.joinable())
{
_stopGc = true;
{
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
_canContinueGC = true;
}
_conditionVariableGC.notify_one();
_gcThread.join();
_stopGc = false;
}
_conditionVariable.notify_one(); _conditionVariable.notify_one();
Socket::closeSocket(_serverFd); ::close(_serverFd);
} }
void SocketServer::setConnectionStateFactory( void SocketServer::setConnectionStateFactory(
@ -245,7 +175,7 @@ namespace ix
// field becomes true, and we can use that to know that we can join that thread // 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). // and remove it from our _connectionsThreads data structure (a list).
// //
void SocketServer::closeTerminatedThreads() bool SocketServer::closeTerminatedThreads()
{ {
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex); std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
auto it = _connectionsThreads.begin(); auto it = _connectionsThreads.begin();
@ -265,6 +195,8 @@ namespace ix
if (thread.joinable()) thread.join(); if (thread.joinable()) thread.join();
it = _connectionsThreads.erase(it); it = _connectionsThreads.erase(it);
} }
return _connectionsThreads.empty();
} }
void SocketServer::run() void SocketServer::run()
@ -272,55 +204,55 @@ namespace ix
// Set the socket to non blocking mode, so that accept calls are not blocking // Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd); SocketConnect::configure(_serverFd);
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:ac:" + std::to_string(_port));
for (;;) for (;;)
{ {
if (_stop) return; if (_stop) return;
// Use poll to check whether a new connection is in progress // Garbage collection to shutdown/join threads for closed connections.
int timeoutMs = -1; // We could run this in its own thread, so that we dont need to accept
#ifdef _WIN32 // a new connection to close a thread.
// select cannot be interrupted on Windows so we need to pass a small timeout // We could also use a condition variable to be notify when we need to do this
timeoutMs = 10; closeTerminatedThreads();
#endif
bool readyToRead = true; // Use select to check whether a new connection is in progress
PollResultType pollResult = fd_set rfds;
Socket::poll(readyToRead, timeoutMs, _serverFd, _acceptSelectInterrupt); struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
if (pollResult == PollResultType::Error) FD_ZERO(&rfds);
FD_SET(_serverFd, &rfds);
if (select(_serverFd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
{ {
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::run() error in select: " << strerror(Socket::getErrno()); ss << "SocketServer::run() error in select: "
<< strerror(Socket::getErrno());
logError(ss.str()); logError(ss.str());
continue; continue;
} }
if (pollResult != PollResultType::ReadyForRead) if (!FD_ISSET(_serverFd, &rfds))
{ {
// We reached the select timeout, and no new connections are pending
continue; continue;
} }
// Accept a connection. // Accept a connection.
// FIXME: Is this working for ipv6 ?
struct sockaddr_in client; // client address information struct sockaddr_in client; // client address information
int clientFd; // socket connected to client int clientFd; // socket connected to client
socklen_t addressLen = sizeof(client); socklen_t addressLen = sizeof(socklen_t);
memset(&client, 0, sizeof(client)); memset(&client, 0, sizeof(client));
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0) if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
{ {
if (!Socket::isWaitNeeded()) if (Socket::getErrno() != EWOULDBLOCK)
{ {
// FIXME: that error should be propagated // FIXME: that error should be propagated
int err = Socket::getErrno();
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::run() error accepting connection: " << err << ", " ss << "SocketServer::run() error accepting connection: "
<< strerror(err); << strerror(Socket::getErrno());
logError(ss.str()); logError(ss.str());
} }
continue; continue;
@ -329,178 +261,33 @@ namespace ix
if (getConnectedClientsCount() >= _maxConnections) if (getConnectedClientsCount() >= _maxConnections)
{ {
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::run() reached max connections = " << _maxConnections << ". " ss << "SocketServer::run() reached max connections = "
<< _maxConnections << ". "
<< "Not accepting connection"; << "Not accepting connection";
logError(ss.str()); logError(ss.str());
Socket::closeSocket(clientFd); ::close(clientFd);
continue; continue;
} }
// Retrieve connection info, the ip address of the remote peer/client)
std::string remoteIp;
int remotePort;
if (_addressFamily == AF_INET)
{
char remoteIp4[INET_ADDRSTRLEN];
if (ix::inet_ntop(AF_INET, &client.sin_addr, remoteIp4, INET_ADDRSTRLEN) == nullptr)
{
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error calling inet_ntop (ipv4): " << err << ", "
<< strerror(err);
logError(ss.str());
Socket::closeSocket(clientFd);
continue;
}
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp4;
}
else // AF_INET6
{
char remoteIp6[INET6_ADDRSTRLEN];
if (ix::inet_ntop(AF_INET6, &client.sin_addr, remoteIp6, INET6_ADDRSTRLEN) ==
nullptr)
{
int err = Socket::getErrno();
std::stringstream ss;
ss << "SocketServer::run() error calling inet_ntop (ipv6): " << err << ", "
<< strerror(err);
logError(ss.str());
Socket::closeSocket(clientFd);
continue;
}
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp6;
}
std::shared_ptr<ConnectionState> connectionState; std::shared_ptr<ConnectionState> connectionState;
if (_connectionStateFactory) if (_connectionStateFactory)
{ {
connectionState = _connectionStateFactory(); connectionState = _connectionStateFactory();
} }
connectionState->setOnSetTerminatedCallback([this] { onSetTerminatedCallback(); });
connectionState->setRemoteIp(remoteIp);
connectionState->setRemotePort(remotePort);
if (_stop) return; if (_stop) return;
// create socket
std::string errorMsg;
bool tls = _socketTLSOptions.tls;
auto socket = createSocket(tls, clientFd, errorMsg, _socketTLSOptions);
if (socket == nullptr)
{
logError("SocketServer::run() cannot create socket: " + errorMsg);
Socket::closeSocket(clientFd);
continue;
}
// Set the socket to non blocking mode + other tweaks
SocketConnect::configure(clientFd);
if (!socket->accept(errorMsg))
{
logError("SocketServer::run() tls accept failed: " + errorMsg);
Socket::closeSocket(clientFd);
continue;
}
// Launch the handleConnection work asynchronously in its own thread. // Launch the handleConnection work asynchronously in its own thread.
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex); std::lock_guard<std::mutex> lock(_conditionVariableMutex);
_connectionsThreads.push_back(std::make_pair( _connectionsThreads.push_back(std::make_pair(
connectionState, connectionState,
std::thread( std::thread(&SocketServer::handleConnection,
&SocketServer::handleConnection, this, std::move(socket), connectionState))); this,
} clientFd,
} connectionState)));
size_t SocketServer::getConnectionsThreadsCount()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
return _connectionsThreads.size();
}
void SocketServer::runGC()
{
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:gc:" + std::to_string(_port));
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;
}
// Unless we are stopping the server, wait for a connection
// to be terminated to run the threads GC, instead of busy waiting
// with a sleep
if (!_stopGc)
{
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
if(!_canContinueGC) {
_conditionVariableGC.wait(lock, [this]{ return _canContinueGC; });
}
_canContinueGC = false;
} }
} }
} }
void SocketServer::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
{
_socketTLSOptions = socketTLSOptions;
}
void SocketServer::onSetTerminatedCallback()
{
// a connection got terminated, we can run the connection thread GC,
// so wake up the thread responsible for that
{
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
_canContinueGC = true;
}
_conditionVariableGC.notify_one();
}
int SocketServer::getPort()
{
return _port;
}
std::string SocketServer::getHost()
{
return _host;
}
int SocketServer::getBacklog()
{
return _backlog;
}
std::size_t SocketServer::getMaxConnections()
{
return _maxConnections;
}
int SocketServer::getAddressFamily()
{
return _addressFamily;
}
} // namespace ix

View File

@ -7,39 +7,33 @@
#pragma once #pragma once
#include "IXConnectionState.h" #include "IXConnectionState.h"
#include "IXNetSystem.h"
#include "IXSelectInterrupt.h" #include <utility> // pair
#include "IXSocketTLSOptions.h" #include <string>
#include <set>
#include <thread>
#include <list>
#include <mutex>
#include <functional>
#include <memory>
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <utility> // pair
namespace ix namespace ix
{ {
class Socket; class SocketServer {
class SocketServer
{
public: public:
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>; using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
// Each connection is handled by its own worker thread. // Each connection is handled by its own worker thread.
// We use a list as we only care about remove and append operations. // We use a list as we only care about remove and append operations.
using ConnectionThreads = using ConnectionThreads = std::list<std::pair<std::shared_ptr<ConnectionState>,
std::list<std::pair<std::shared_ptr<ConnectionState>, std::thread>>; std::thread>>;
SocketServer(int port = SocketServer::kDefaultPort, SocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost, const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog, int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections, size_t maxConnections = SocketServer::kDefaultMaxConnections);
int addressFamily = SocketServer::kDefaultAddressFamily);
virtual ~SocketServer(); virtual ~SocketServer();
virtual void stop(); virtual void stop();
@ -52,20 +46,13 @@ namespace ix
const static std::string kDefaultHost; const static std::string kDefaultHost;
const static int kDefaultTcpBacklog; const static int kDefaultTcpBacklog;
const static size_t kDefaultMaxConnections; const static size_t kDefaultMaxConnections;
const static int kDefaultAddressFamily;
void start(); void start();
std::pair<bool, std::string> listen(); std::pair<bool, std::string> listen();
void wait(); void wait();
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
int getPort();
std::string getHost();
int getBacklog();
std::size_t getMaxConnections();
int getAddressFamily();
protected: protected:
// Logging // Logging
void logError(const std::string& str); void logError(const std::string& str);
void logInfo(const std::string& str); void logInfo(const std::string& str);
@ -78,24 +65,15 @@ namespace ix
std::string _host; std::string _host;
int _backlog; int _backlog;
size_t _maxConnections; size_t _maxConnections;
int _addressFamily;
// socket for accepting connections // socket for accepting connections
socket_t _serverFd; int _serverFd;
std::atomic<bool> _stop;
std::mutex _logMutex; std::mutex _logMutex;
// background thread to wait for incoming connections // background thread to wait for incoming connections
std::atomic<bool> _stop;
std::thread _thread; std::thread _thread;
void run();
void onSetTerminatedCallback();
// background thread to cleanup (join) terminated threads
std::atomic<bool> _stopGc;
std::thread _gcThread;
void runGC();
// the list of (connectionState, threads) for each connections // the list of (connectionState, threads) for each connections
ConnectionThreads _connectionsThreads; ConnectionThreads _connectionsThreads;
@ -109,23 +87,13 @@ namespace ix
// the factory to create ConnectionState objects // the factory to create ConnectionState objects
ConnectionStateFactory _connectionStateFactory; ConnectionStateFactory _connectionStateFactory;
virtual void handleConnection(std::unique_ptr<Socket>, // Methods
void run();
virtual void handleConnection(int fd,
std::shared_ptr<ConnectionState> connectionState) = 0; std::shared_ptr<ConnectionState> connectionState) = 0;
virtual size_t getConnectedClientsCount() = 0; virtual size_t getConnectedClientsCount() = 0;
// Returns true if all connection threads are joined // Returns true if all connection threads are joined
void closeTerminatedThreads(); bool closeTerminatedThreads();
size_t getConnectionsThreadsCount();
SocketTLSOptions _socketTLSOptions;
// to wake up from select
SelectInterruptPtr _acceptSelectInterrupt;
// used by the gc thread, to know that a thread needs to be garbage collected
// as a connection
std::condition_variable _conditionVariableGC;
std::mutex _conditionVariableMutexGC;
bool _canContinueGC{ false };
}; };
} // namespace ix }

View File

@ -1,93 +0,0 @@
/*
* IXSocketTLSOptions.h
* Author: Matt DeBoer
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXSocketTLSOptions.h"
#include <assert.h>
#include <fstream>
#include <sstream>
namespace ix
{
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
const char* kTLSCAFileDisableVerify = "NONE";
const char* kTLSCiphersUseDefault = "DEFAULT";
const char* kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
bool SocketTLSOptions::isValid() const
{
if (!_validated)
{
if (!certFile.empty() && !std::ifstream(certFile))
{
_errMsg = "certFile not found: " + certFile;
return false;
}
if (!keyFile.empty() && !std::ifstream(keyFile))
{
_errMsg = "keyFile not found: " + keyFile;
return false;
}
if (!caFile.empty() && caFile != kTLSCAFileDisableVerify &&
caFile != kTLSCAFileUseSystemDefaults && !std::ifstream(caFile))
{
_errMsg = "caFile not found: " + caFile;
return false;
}
if (certFile.empty() != keyFile.empty())
{
_errMsg = "certFile and keyFile must be both present, or both absent";
return false;
}
_validated = true;
}
return true;
}
bool SocketTLSOptions::hasCertAndKey() const
{
return !certFile.empty() && !keyFile.empty();
}
bool SocketTLSOptions::isUsingSystemDefaults() const
{
return caFile == kTLSCAFileUseSystemDefaults;
}
bool SocketTLSOptions::isUsingInMemoryCAs() const
{
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
}
bool SocketTLSOptions::isPeerVerifyDisabled() const
{
return caFile == kTLSCAFileDisableVerify;
}
bool SocketTLSOptions::isUsingDefaultCiphers() const
{
return ciphers.empty() || ciphers == kTLSCiphersUseDefault;
}
const std::string& SocketTLSOptions::getErrorMsg() const
{
return _errMsg;
}
std::string SocketTLSOptions::getDescription() const
{
std::stringstream ss;
ss << "TLS Options:" << std::endl;
ss << " certFile = " << certFile << std::endl;
ss << " keyFile = " << keyFile << std::endl;
ss << " caFile = " << caFile << std::endl;
ss << " ciphers = " << ciphers << std::endl;
ss << " tls = " << tls << std::endl;
return ss.str();
}
} // namespace ix

View File

@ -1,57 +0,0 @@
/*
* IXSocketTLSOptions.h
* Author: Matt DeBoer
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
struct SocketTLSOptions
{
public:
// check validity of the object
bool isValid() const;
// the certificate presented to peers
std::string certFile;
// the key used for signing/encryption
std::string keyFile;
// the ca certificate (or certificate bundle) file containing
// certificates to be trusted by peers; use 'SYSTEM' to
// leverage the system defaults, use 'NONE' to disable peer verification
std::string caFile = "SYSTEM";
// list of ciphers (rsa, etc...)
std::string ciphers = "DEFAULT";
// whether tls is enabled, used for server code
bool tls = false;
// whether to skip validating the peer's hostname against the certificate presented
bool disable_hostname_validation = false;
bool hasCertAndKey() const;
bool isUsingSystemDefaults() const;
bool isUsingInMemoryCAs() const;
bool isPeerVerifyDisabled() const;
bool isUsingDefaultCiphers() const;
const std::string& getErrorMsg() const;
std::string getDescription() const;
private:
mutable std::string _errMsg;
mutable bool _validated = false;
};
} // namespace ix

View File

@ -1,37 +0,0 @@
/*
* IXStrCaseCompare.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone. All rights reserved.
*/
#include "IXStrCaseCompare.h"
#include <algorithm>
#include <locale>
namespace ix
{
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1,
const unsigned char& c2) const
{
#if defined(_WIN32) && !defined(__GNUC__)
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
#else
return std::tolower(c1) < std::tolower(c2);
#endif
}
bool CaseInsensitiveLess::cmp(const std::string& s1, const std::string& s2)
{
return std::lexicographical_compare(s1.begin(),
s1.end(), // source range
s2.begin(),
s2.end(), // dest range
NocaseCompare()); // comparison
}
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const
{
return CaseInsensitiveLess::cmp(s1, s2);
}
} // namespace ix

View File

@ -1,25 +0,0 @@
/*
* IXStrCaseCompare.h
* Author: Benjamin Sergeant
* Copyright (c) 2020 Machine Zone. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
struct CaseInsensitiveLess
{
// Case Insensitive compare_less binary function
struct NocaseCompare
{
bool operator()(const unsigned char& c1, const unsigned char& c2) const;
};
static bool cmp(const std::string& s1, const std::string& s2);
bool operator()(const std::string& s1, const std::string& s2) const;
};
} // namespace ix

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