Compare commits

..

754 Commits

Author SHA1 Message Date
74833f95e4 (Windows) OpenSSL can be used for SSL communication 2020-01-09 11:59:34 -08:00
1a47656ba0 Allow custom OpenSSL lib, only include openssl/x509v3.h when used. (#143)
* Allow custom OpenSSL lib, only include openssl/x509v3.h when used.

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

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

* Added option for OpenSSL support.

* Fixed elseif/else mixup from 5ebad24040
2020-01-07 14:57:47 -08:00
925eb2f1d0 Fixed build error with vcpkg installed. (#141) 2020-01-07 10:40:36 -08:00
5020870cdf (apple ssl) unify read and write ssl utility code 2020-01-06 15:19:14 -08:00
f75684a412 apple ssl: aesthetic change (get rid of yoda comparisons/conditionals) 2020-01-06 14:45:05 -08:00
7c63232157 (websocket client) better error propagation when errors are detected while sending data + (ws send) detect failures to send big files, terminate in those cases and report error (troubleshooting #140) 2020-01-06 14:34:43 -08:00
c10ff1d210 add reference ssl echo server using websockets and nginx reverse proxy to terminate ssl 2020-01-05 15:26:35 -08:00
40e344a958 Merge commit 'c992cb4e42cc223f67ede0e48d7ff3f4947af0c6' as 'test/compatibility/C/uWebSockets' 2020-01-04 15:41:03 -08:00
0bc4c4c136 add another compatibility python client 2020-01-04 15:37:40 -08:00
196f3d4b8a Add a simple python program to send a file (debugging #140) 2020-01-04 15:18:02 -08:00
162e228b34 (ws send) add option (-x) to disable per message deflate compression 2020-01-04 15:08:36 -08:00
e34d960b28 fix typo in ws embedded help 2020-01-04 14:37:53 -08:00
d62a102aba (ws send + receive) handle all message types (ping + pong + fragment) / investigate #140 2020-01-04 13:45:07 -08:00
18091f49ab Install source code when making a Docker container 2020-01-04 13:44:02 -08:00
24cd529693 add user list in readme 2019-12-30 22:16:29 -08:00
4ba92832ce correct version number 2019-12-30 22:14:53 -08:00
0520329350 tag version 2019-12-30 17:17:28 -08:00
ba88a05b74 Update IXSocketMbedTLS.cpp (#139)
fix bug with mbedtls server certificate loading.
2019-12-30 16:11:34 -08:00
72f8e76369 Update IXSocketMbedTLS.cpp (#138)
fix bug just introduced.

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

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

* Update IXSocketMbedTLS.cpp

removed newline on 46
2019-12-30 14:38:25 -08:00
ac0c218455 clang-format 2019-12-30 08:46:18 -08:00
299dc0452e (ws cobra to sentry/statsd) fix for handling null events properly for empty queues + use queue to send data to statsd 2019-12-28 17:28:05 -08:00
f4af84dc06 (ws cobra to sentry) handle null events for empty queues 2019-12-28 10:16:18 -08:00
6522bc06ba (ws cobra to sentry) game is picked in a fair manner, so that all games get the same share of sent events 2019-12-27 19:10:15 -08:00
50bea7dffa (ws cobra to sentry) refactor queue related code into a class 2019-12-27 18:24:45 -08:00
c4e9abfe80 (ws cobra to sentry) bound the queue size used to hold up cobra messages before they are sent to sentry. Default queue size is a 100 messages. Without such limit the program runs out of memory when a subscriber receive a lot of messages that cannot make it to sentry 2019-12-25 22:15:57 -08:00
a805270d02 (ws client) use correct compilation defines so that spdlog is not used as a header only library (reduce binary size and increase compilation speed) 2019-12-25 09:03:57 -08:00
e13b57c73b (ws client) all commands use spdlog instead of std::cerr or std::cout for logging 2019-12-24 21:55:34 -08:00
5be84926ef (cobra client) send a websocket ping every 30s to keep the connection opened 2019-12-24 17:16:41 -08:00
33e7271b85 (tls / apple) minor refactoring, move functions out of the anonymous namespace to become static member functions 2019-12-23 16:30:38 -08:00
d72e5e70f6 socket tls options: display ciphers 2019-12-23 12:25:25 -08:00
e2c5f751bd (doc) fix typo 2019-12-22 20:33:14 -08:00
351b86e266 v7.6.4 2019-12-22 20:32:10 -08:00
d0cbff4f4e (client) error handling, quote url in error case when failing to parse on 2019-12-22 20:30:29 -08:00
cbfc9b9f94 (ws) ws_cobra_publish: register callbacks before connecting 2019-12-22 20:29:37 -08:00
ca816d801f (doc) mention mbedtls in supported ssl server backend 2019-12-22 20:28:44 -08:00
2f354d31eb update gitignore file 2019-12-20 15:21:36 -08:00
2c6c1edd37 (tls) add a simple description of the TLS configuration routine for debugging 2019-12-20 15:18:04 -08:00
9799e7e84b (mbedtls) correct support for using own certificate and private key 2019-12-20 15:13:26 -08:00
81be970679 (ws commands) in websocket proxy, disable automatic reconnections + in Dockerfile, use alpine 3.11 2019-12-20 09:51:21 -08:00
52221906f6 (cobra) Add TLS options to all cobra commands and classes. Add example to the doc. 2019-12-19 20:49:28 -08:00
3e786fe23a formatting 2019-12-19 19:13:55 -08:00
de24aac7d5 (cobra-to-sentry) capture application version from device field 2019-12-18 15:41:59 -08:00
cd4b0ccf6f IXSentryClient: remove duplicated line 2019-12-18 15:29:53 -08:00
4e1888ac19 (tls) Experimental TLS server support with mbedtls (windows) + process cert tlsoption (client + server) 2019-12-18 11:51:02 -08:00
237ede56aa (tls servers) Make it clear that apple ssl and mbedtls backends do not support SSL in server mode 2019-12-18 10:43:05 -08:00
ba3b1c1a0f (tls options client) TLSOptions struct _validated member should be initialized to false 2019-12-17 14:10:28 -08:00
c60c606e0f (websocket client) improve the error message when connecting to a non websocket server 2019-12-16 17:57:43 -08:00
5897de6bd9 (server) attempt at fixing #131 by using blocking writes in server mode 2019-12-12 12:17:29 -08:00
cca304fc18 (ws) cobra to sentry - created events with sentry tags based on tags present in the cobra messages 2019-12-11 17:28:11 -08:00
432624df0d update spdlog 2019-12-06 22:05:12 -08:00
9c047fac72 (mac) convert SSL errors to utf8 2019-12-06 16:45:49 -08:00
615df9cef0 Add script to extract the version from the header file and remove DOCKER_VERSION 2019-12-06 16:44:05 -08:00
094d16304d (ws) cobra to sentry. Handle Error 429 Too Many Requests and politely wait before sending more data to sentry 2019-12-05 15:59:29 -08:00
c2377e8747 Using Alpine edge distribution to live on the edge 2019-12-05 15:46:01 -08:00
a63c0d6e78 sentry minidump upload timeout 2019-12-05 15:19:27 -08:00
aa3bface30 bunch of docker compose dev changes 2019-12-05 15:18:02 -08:00
5d75a3aac3 (ws) #125 / fix build problem when jsoncpp is not installed locally 2019-12-03 17:18:16 -08:00
c75959fcb5 (ws) #125 / cmake detects an already installed jsoncpp and will try to use this one if present 2019-12-03 16:01:46 -08:00
bfe0212250 Meta: documentation fixes (#127)
* Clarify versions, minor punctuation fix

* Copyediting, borked URL

* Fix Python comments in C++ code

* Copyediting

* Pretty code

* Copyediting, pretty code

* Typo

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

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

* Better aproach suggested by Benjamin.
2019-11-20 11:12:24 -08:00
2cfadd93b5 add a python websocket proxy which works on Linux, while ws proxy_server does not 2019-11-18 13:46:11 -08:00
8607dc1a4a ws proxy_server / remote server close not forwarded to the client 2019-11-16 14:21:44 -08:00
521286ae88 fix android build + proxy work 2019-11-16 06:51:53 -08:00
d122e12a1f bump version 2019-11-15 17:19:06 -08:00
c26c3b6892 document new proxy command 2019-11-15 17:18:32 -08:00
ed75d14c86 proxy works but crash when the connection is refused 2019-11-15 17:07:31 -08:00
0841fcec44 add stub code for ws proxy server 2019-11-15 14:30:20 -08:00
4e717abdb8 fix typo 2019-11-15 14:28:30 -08:00
451d2b4253 update readme / add contributing notes 2019-11-15 14:21:28 -08:00
10b4ee353d update changelog 2019-11-06 23:12:45 -08:00
07822625b7 update readme 2019-11-06 23:12:45 -08:00
c943e72c7b check max frame size (#119) 2019-10-28 21:53:31 -07:00
ebb31b4e87 docker build fixes 2019-10-26 11:47:08 -07:00
6904dd3f4c Add unittest to IXSentryClient to lua backtrace parsing code 2019-10-26 10:54:47 -07:00
0e73fe51e9 move sentry code around and add a stub unittest for it 2019-10-25 14:54:31 -07:00
7e67598360 ws cobra to sentry / simplify sent and received message statistic reporting 2019-10-25 14:34:48 -07:00
91a95dc5f6 remove unused quiet argument of ws cobra_metrics_to_redis command 2019-10-25 14:02:56 -07:00
c40033b6d9 Add cobra_metrics_to_redis sub-command to create streams for each cobra metric event being received. 2019-10-24 14:42:36 -07:00
adf83f3255 Create SECURITY.md 2019-10-17 06:58:22 -07:00
8fda7cb131 remove unused code in ws cobra_publish 2019-10-14 11:15:14 -07:00
0e9cf863cf Add client support for websocket subprotocol. Look for the new addSubProtocol method for details 2019-10-13 13:37:34 -07:00
279f6fbfed OpenSSL: add an extra cipher to the default cipher set, which let us connect to wss//echo.websocket.org 2019-10-10 09:37:27 -07:00
f8e7b34bf0 add more docs about ws 2019-10-09 22:42:03 -07:00
d2cf616737 Freebsd (#117)
* add file

* CMake freebsd fix
2019-10-09 17:00:32 -07:00
11a3b64657 (freebsd compile fix) add some missing socket related headers 2019-10-09 15:38:40 -07:00
bab2295fc3 make sure the unittest pass withouth SSL 2019-10-03 09:41:17 -07:00
adcbf0d208 add a target for building wihout ssl + take Matt Boer updated script to run ws test with SSL (still broken for large payload) 2019-10-03 07:47:34 -07:00
19150115bb ws: Signal handling code isn't include on Windows 2019-10-01 16:12:32 -07:00
d93bd9b58b bump version 2019-10-01 16:01:32 -07:00
13801dff8a Add mbed tls version in user agent string + set user agent properly when enabling openssl on macOS 2019-10-01 15:58:35 -07:00
de87fa34dc Implement SSL server with OpenSSL backend / still flaky 2019-10-01 15:43:37 -07:00
d60f5de231 Add --tls option to pass to ws server command, to enable/disable tls 2019-10-01 13:54:46 -07:00
22b4e6a8fb Socket Factory has only one function which works for server and client code, and can do tls for both 2019-09-30 22:06:46 -07:00
1ed39677ce SocketServer::handleConnection takes an std::shared_ptr<Socket> instead of a file descriptor 2019-09-30 21:48:55 -07:00
562d7484e4 openSSLHandshake -> openSSLClientHandshake 2019-09-30 21:24:25 -07:00
58d6e4bb26 all ws subcommands propagate tls options to servers (unimplemented) or ws or http client (implemented) (contributed by Matt DeBoer) 2019-09-30 18:21:20 -07:00
0539d2df2e clang-format 2019-09-30 17:52:39 -07:00
e023dd9c36 ws has a --version option 2019-09-30 17:31:33 -07:00
a95cf727b1 bump version number 2019-09-29 22:10:07 -07:00
b96a65031e fix windows compile error in include/spdlog/details/pattern_formatter-inl.h 2019-09-29 22:00:57 -07:00
2a838d01a7 docs: WITH_TLS => USE_TLS 2019-09-29 21:31:13 -07:00
b0afd36cec document basic usage 2019-09-29 21:29:28 -07:00
77863c0e8b unittest / specify a cacert for tls client tests 2019-09-29 21:24:22 -07:00
2229159bd2 ws curl + http client tls option handling + ca cert processing for mbedtls 2019-09-29 21:13:11 -07:00
89d2606b1d update copyright dates and authors 2019-09-29 20:09:51 -07:00
a7a41c51d9 openssl client: handle TLS options 2019-09-29 20:07:53 -07:00
4de7cb191b most ws command take tls options, no-op for now (contributed by Matt DeBoer) 2019-09-29 18:29:51 -07:00
b3784b4c60 SocketTLSOptions: more methods (contributed by Matt DeBoer) 2019-09-29 17:35:18 -07:00
816c53e3a3 ws transfer + send + receive / improved logging (contributed by Matt DeBoer) 2019-09-29 17:21:52 -07:00
28c4b83ab9 Add ability to use OpenSSL on apple platforms. 2019-09-29 15:34:58 -07:00
3a91894d62 update and change how we build with spdlog 2019-09-29 11:13:24 -07:00
3c8cd6289b ixcobra / fix crash in CobraConnection::publishNext when the queue is empty + handle CobraConnection_PublishMode_Batch in CobraMetricsThreadedPublisher 2019-09-28 10:36:47 -07:00
06297ac756 DNS lookup test works on windows 2019-09-27 14:34:47 -07:00
1b6584ccba mbedtls fixes / the unittest now pass on macOS, and hopefully will on Windows/AppVeyor as well. 2019-09-27 14:07:01 -07:00
0499a80c55 Export port 8008 for Docker + test_ws.sh is /bin/sh compatible 2019-09-26 14:36:14 -07:00
f18980d010 http server unittest + refactoring 2019-09-26 09:45:59 -07:00
2fb0ebb05b http server: in redirect mode, POST request are given a 200 status code and an empty response 2019-09-26 09:27:27 -07:00
7495c9ebb8 Http server: add options to ws https to redirect all requests to a given url. 2019-09-26 09:10:30 -07:00
b26d463bad Stop having ws send subcommand send a binary message in text mode, which would cause error in make ws_test shell script test 2019-09-25 15:39:43 -07:00
f8a581aa69 fix doc 2019-09-24 15:42:28 -07:00
01f3340718 speedup base64 code by reserving memory 2019-09-24 14:17:03 -07:00
a9b8b6decd wrong mutex being used ... 2019-09-24 14:10:41 -07:00
ea83327261 Fix 2 race conditions detected with TSan, one in CobraMetricsPublisher::push and another one in WebSocketTransport::sendData (that one was bad). 2019-09-24 11:46:54 -07:00
39c0fb0072 try to enable more tests on windows 2019-09-23 21:52:32 -07:00
733b414b3b fix tsan errors on macOS when running the unittest 2019-09-23 21:51:55 -07:00
c32067013a fix warning + add redis server logging 2019-09-23 21:14:20 -07:00
fbf80f4ab1 Add simple Redis Server which is only capable of doing publish / subscribe. New ws redis_server sub-command to use it. The server is used in the unittest, so that we can run on CI in environment where redis isn not available like github actions env. 2019-09-23 21:04:01 -07:00
8f8385f8f8 fix linux compilation error, by ordering dependant libraries properly 2019-09-23 12:32:04 -07:00
122118196b move snake code to its own subfolder like ixcobra, ixcrypto, etc... 2019-09-23 11:46:16 -07:00
6f2fe49a7b reformat everything with clang-format 2019-09-23 10:25:23 -07:00
b667c0ad40 fix unittest 2019-09-22 19:40:33 -07:00
283cf83d47 fix unittest compiler warnings 2019-09-22 19:22:48 -07:00
ab1b5cd665 compile fixes 2019-09-22 18:52:57 -07:00
dbf6d00249 add gihub actions 2019-09-22 18:45:30 -07:00
d0963f4af0 compiled fixes on mac and windows 2019-09-22 18:43:57 -07:00
dd01f734c6 WIP: support configurable certificates/keys, and root trust CAs (#114)
* wip: tls options implemented in openssl

* update naming, remove #define guard

* assert compiled with USE_TLS for tls options

* apply autoformatter

* include tls options impl

* style cleanup; auto ssl_err

* ssl_err -> sslErr

* be explicit about SSL_VERIFY_NONE
2019-09-22 18:06:15 -07:00
1769199d32 Fix crash in the Linux unittest in the HTTP client code, in Socket::readBytes. Cobra Metrics Publisher code returns the message id of the message that got published, to be used to validated that it got sent properly when receiving an ack. 2019-09-21 09:23:58 -07:00
8821183aea missing file in ws tool 2019-09-19 12:51:34 -07:00
a7cf151639 In DNS lookup code, make sure the weak pointer we use lives through the expected scope (if branch) 2019-09-19 12:51:11 -07:00
f7a12f52f8 On error while doing a client handshake, additionally display port number next to the host name 2019-09-17 12:08:52 -07:00
1be3b8f4b1 rename test file 2019-09-17 12:07:31 -07:00
0b844d8361 make test target does not try to install anything into /usr/local 2019-09-12 11:45:31 -07:00
57086e28d8 fix unittest warnings + remove trailing spaces 2019-09-12 11:43:52 -07:00
a55d4cdb76 update pre-commit file 2019-09-10 22:18:16 -07:00
40a45717db update clang format file 2019-09-10 22:17:08 -07:00
e853d9ac60 build fixes 2019-09-10 14:05:07 -07:00
4ec0d9b113 update appveyor file to new directory structure 2019-09-10 12:33:47 -07:00
0fde169aa4 restructure project 2019-09-10 12:19:22 -07:00
c09015e870 update ws CLI11 (our command line argument parsing library) to the latest, which fix a compiler bug about optional 2019-09-09 21:25:33 -07:00
7bfa6e8478 improve some websocket error messages + add a utility function with unittest to parse status line and stop using scanf which triggers warnings on Windows 2019-09-09 21:23:57 -07:00
983df2d8f9 improve some websocket error messages + add a utility function with unittest to parse status line and stop using scanf which triggers warnings on Windows 2019-09-09 17:34:36 -07:00
6beba16ca7 websocket and http server: server does not close the bound client socket in many cases 2019-09-09 16:48:26 -07:00
48cefe5525 move poll wrapper on top of select (only used on Windows) to the ix namespace 2019-09-08 11:15:08 -07:00
ae3856c10f Fix Windows CI with appveyor (#110)
Fix windows CI with appveyor + minor tweaks.
2019-09-07 14:07:00 -07:00
260a94d3b0 README: update link to the doc 2019-09-06 10:42:48 -07:00
88c6d6c4cb ci 2019-09-05 22:32:54 -07:00
d5a4931c92 travis linux 2019-09-05 22:29:00 -07:00
11f4e90bc6 ci tweak / install redis 2019-09-05 22:14:55 -07:00
2ce65e7a77 cobra metrics publisher test uses random free port 2019-09-05 22:05:00 -07:00
e81c2c3e5c cobra chat test uses random free port 2019-09-05 22:02:10 -07:00
e40dda7549 add cobra metrics publisher + server unittest 2019-09-05 21:57:05 -07:00
d959d73261 Add new cobra unittest, using server and client 2019-09-05 20:49:58 -07:00
07b7e37a92 snake unsubscription fixes 2019-09-05 20:47:15 -07:00
eb7888347a Fix compiler warning 2019-09-05 20:29:14 -07:00
d8664f4988 ws snake (cobra simple server) add basic support for unsubscription + subscribe send the proper subscription data + redis client subscription can be cancelled 2019-09-05 20:28:34 -07:00
5e94791b13 IXCobraConnection / pdu handlers can crash if they receive json data which is not an object 2019-09-05 20:24:42 -07:00
3e3f7171fc cobra publish fix 2019-09-05 14:31:28 -07:00
308fda0b37 Update README.md 2019-09-05 14:30:51 -07:00
66ed7577b1 all client autobahn test should pass ! last failing one was ...
+- zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9/ (fix autobahn test 13.X which uses 8 for the windows size)
2019-09-04 21:01:30 -07:00
cae23c764f Fragmentation: for sent messages which are compressed, the continuation fragments should not have the rsv1 bit set (fix all autobahn tests for zlib compression 12.X)
Websocket Server / do a case insensitive string search when looking for an Upgrade header whose value is websocket. (some client use WebSocket with some upper-case characters)
2019-09-04 18:23:56 -07:00
f25b2af6eb ws autobahn / use condition variables for stopping test case + add more logging on errors 2019-09-04 12:21:54 -07:00
508d372df1 ws autobahn / report progress with spdlog::info to get timing info 2019-09-04 10:16:32 -07:00
12c3275c36 truncate module 2019-09-03 20:14:35 -07:00
98189c23dc Per message deflate/compression: handle fragmented messages (fix autobahn test: 12.1.X and probably others) 2019-09-03 17:42:48 -07:00
ec55b4a82a Receiving invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling) 2019-09-03 16:07:48 -07:00
5d58982f77 IXWebSocketTransport message processing refactoring 2019-09-03 15:48:55 -07:00
57665ca825 Validate close codes. Autobahn 7.9.* 2019-09-03 15:43:16 -07:00
deaa753657 Validate that the close reason is proper utf-8. Autobahn 7.5.1 2019-09-03 14:35:40 -07:00
7c7c877621 Sending invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling) 2019-09-03 14:12:40 -07:00
afa71a6b4b Framentation: data and continuation blocks received out of order (fix autobahn test: 5.9 through 5.20 Fragmentation) 2019-09-03 12:02:56 -07:00
172cd39702 Sending invalid UTF-8 TEXT message should fail and close the connection (fix **tons** of autobahn test: 6.X UTF-8 Handling) 2019-09-03 10:30:22 -07:00
82213fd3a5 Message type (TEXT or BINARY) is invalid for received fragmented messages (fix autobahn test: 5.3 through 5.8 Fragmentation) 2019-09-03 09:13:38 -07:00
a32bf885ba bump version 2019-09-02 10:14:15 -07:00
61eb662e5f Ping and Pong messages cannot be fragmented (autobahn test: 5.1 and 5.2 Fragmentation) 2019-09-02 10:13:40 -07:00
2887370666 Close connections when reserved bits are used (autobahn test: 3 Reserved Bits) 2019-09-01 16:23:00 -07:00
8826d62075 changelog 2019-09-01 11:39:00 -07:00
fae284e2e1 readme 2019-09-01 11:38:39 -07:00
2408617ed9 doc 2019-09-01 11:28:27 -07:00
cc10b7f998 compute test case count properly 2019-09-01 11:17:28 -07:00
3c97d5f668 refactoring 2019-09-01 11:10:27 -07:00
0accf24320 condition variable instead of busy looping 2019-09-01 10:50:16 -07:00
8ec2ef345c quiet mode 2019-09-01 10:45:51 -07:00
10dbe2d44d +add utf-8 validation code, not hooked up properly yet
+ws autobahn / Add code to test websocket client compliance with the autobahn test-suite
+Ping received with a payload too large (> 125 bytes) trigger a connection closure
+cobra / add tracking about published messages
+cobra / publish returns a message id, that can be used when
+cobra / new message type in the message received handler when publish/ok is received (can be used to implement an ack system).
2019-08-31 16:47:10 -07:00
6b2cdb6b54 user agent 2019-08-30 12:50:56 -07:00
06bc795133 New option to cap the max wait between reconnection attempts. Still default to 10s. (setMaxWaitBetweenReconnectionRetries) (#108) 2019-08-30 12:46:35 -07:00
239a08ff9b readme 2019-08-26 22:49:40 -07:00
41dd8d2184 readme 2019-08-26 22:29:10 -07:00
57b4b13b65 doc / bring back detailed APIs 2019-08-26 22:11:35 -07:00
a66b116aad one last tweak 2019-08-26 22:02:24 -07:00
5c4102c0be readme tweaks 2019-08-26 21:57:05 -07:00
ebb7318895 new simple readme 2019-08-26 21:55:00 -07:00
b11876096b Add md doc made with mkdocs 2019-08-26 21:25:45 -07:00
d603a74c6f fix #104 - change ZLIB find_package to be optional 2019-08-26 14:51:33 -07:00
95d633e71e tentative gcc build fix 2019-08-26 14:29:16 -07:00
217d0650f4 bump version 2019-08-26 10:20:01 -07:00
45d7bb34d7 ws connect has a new option to send HTTP headers + use WebSocketHttpHeaders instead of unordered_map<string, string> 2019-08-26 10:19:09 -07:00
2e32319236 CobraConnection: sets a unique id field for all messages sent to [cobra](https://github.com/machinezone/cobra).
CobraConnection: sets a counter as a field for each event published.
2019-08-26 09:51:37 -07:00
8eb0d0b7c3 put windows poll in the global namespace, not ix namespace 2019-08-26 09:51:37 -07:00
f18f04c0ee Add client handshake extra headers (#105)
Even though 6455 defines all the necessary headers needed for
client/server handshake, in practice most of the cases websocket servers
expect few more headers. Therefore adding this functionality.
2019-08-26 09:37:40 -07:00
193da820b2 Windows: use select instead of WSAPoll, through a poll wrapper 2019-08-22 10:34:17 -07:00
c6198305d4 add new makefile target to make git tags 2019-08-20 09:21:30 -07:00
c77d6ae3f5 bump version + talk about Windows fix in the changelog 2019-08-20 09:20:02 -07:00
c72b2dbd6b add poll alias to WSAPoll on Windows 2019-08-19 22:26:25 -07:00
835523f77b fix #101 / wrong include in IXSocket.cpp on Windows 2019-08-19 22:19:39 -07:00
ec8a35b587 README tweaks 2019-08-19 20:35:26 -07:00
aca18995d1 README / formatting 2019-08-19 20:33:56 -07:00
f9178f58aa README.md: add reference to WSAStartup to initialize the networking system 2019-08-19 09:47:59 -07:00
2477946e68 (CI) linux: install libmbedtls 2019-08-14 21:49:43 -07:00
7c4d040384 (CI) try to build Linux on Ubuntu Bionic 2019-08-14 21:44:49 -07:00
197cf8ed36 bump version 2019-08-14 21:36:20 -07:00
dd0d7c268f CobraMetricThreadedPublisher _enable flag is an atomic, and CobraMetricsPublisher is enabled by default 2019-08-14 19:54:30 -07:00
b2bfccac0a clang format 2019-08-13 10:59:18 -07:00
8b8b352e61 fix #99 / Connect error descriptions are invalid 2019-08-13 10:49:11 -07:00
0403dd354b update readme 2019-08-06 20:55:44 -07:00
b78b453504 fix #98 2019-08-02 17:11:53 -07:00
f8fef833b8 new options for cobra commands
- ws cobra_subscribe has a new -q (quiet) option
- ws cobra_subscribe knows to and display msg stats (count and # of messages received per second)
- ws cobra_subscribe, cobra_to_statsd and cobra_to_sentry commands have a new option, --filter to restrict the events they want to receive
2019-08-01 15:22:24 -07:00
fc4068f2e5 ws connect command has a new option to send in binary mode (still default to text) 2019-07-25 15:48:45 -07:00
c300866dcc add better line editing capability to ws connect, thanks to linenoise-cpp 2019-07-25 11:54:50 -07:00
18485a74e5 README.md / cosmetic 2019-07-23 14:04:45 -07:00
4dd5950406 fix typo in README 2019-07-23 13:52:16 -07:00
98de54106d README: add reference to conan/vcpk to the build section 2019-07-22 20:41:06 -07:00
4d64272a1a do not update homebrew when installing a package 2019-07-03 14:49:39 -07:00
0ccece908b ci / get mbedtls from homebrew on mac 2019-07-03 14:46:05 -07:00
64cd725060 do not use mbed tls for the unittest 2019-07-03 14:39:46 -07:00
cc2fa55608 add new docker file to run the unittest with tsan on latest Ubuntu 2019-06-30 23:37:25 -07:00
4fb268585c dns / use cancellable instead of blocking 2019-06-30 23:26:14 -07:00
3a2495c456 make IXDNSLookup more robust 2019-06-26 19:12:48 -07:00
1d4d058ed0 simplify IXDNSLookup 2019-06-26 16:25:07 -07:00
15a1347531 use poll instead of select in SocketServer 2019-06-25 17:18:24 -07:00
4cbfa71338 switch from select to poll to deal with Android 9 giving us high socket fds when calling ::connect 2019-06-25 17:11:27 -07:00
705625af0a refactor select code + add protection against large fds (cf Android 9) 2019-06-25 15:41:39 -07:00
01bc6654cb Add extra check in IXWebSocketCloseTest.cpp 2019-06-25 14:10:39 -07:00
eea42bff66 select refactoring IXSocket::select -> IXSocket::poll 2019-06-25 10:16:40 -07:00
06b4762c19 disable CI on Windows 2019-06-25 00:28:11 -07:00
1ee9479009 cmake use_tls fix 2019-06-24 23:34:31 -07:00
73e94ed03a do not build mbedtls on ci 2019-06-24 23:28:35 -07:00
1883519e82 try to disable TLS for unittesting 2019-06-24 23:27:44 -07:00
6f6c1f85ef CI / build zlib and mbedtls locally 2019-06-24 23:17:19 -07:00
c55ff3cb1b CI work 2019-06-24 10:17:57 -07:00
08006ddd97 try to activate CI on windows again 2019-06-23 18:32:18 -07:00
fa4aee6ddc bump docker version 2019-06-23 18:17:24 -07:00
691502d7ad Feature/httpd (#94)
* Stub code for http server

* can send a response, cannot process body yet

* write headers to the response

* remove commented code

* add simple test + set default http handler

* tweak CI + unittest

* add missing file

* rewrite http::trim in a simple way

* doc
2019-06-23 14:54:21 -07:00
43deaba547 IXDNSLookup. Uses weak pointer + smart_ptr + shared_from_this instead of static sets + mutex to handle object going away before dns lookup has resolved 2019-06-19 00:43:59 -07:00
2d02ae0f0c cobra_to_sentry / backtraces are reversed and line number is not extracted correctly 2019-06-13 10:18:40 -07:00
a8879da4fc disable windows on CI for now 2019-06-10 22:10:43 -07:00
5f4a430845 disable building ws on windows on travis 2019-06-10 22:01:19 -07:00
b9231be305 (cmake) missing find_package(Threads) on UNIX 2019-06-10 13:39:22 -07:00
7cb5cc05e4 Add -DUSE_VENDORED_THIRD_PARTY=1 to build ws 2019-06-10 13:26:41 -07:00
750a752ac0 - mbedtls and zlib are searched with find_package, and we use the vendored version if nothing is found 2019-06-10 11:18:27 -07:00
61e5f52286 - travis CI uses g++ on Linux 2019-06-09 14:27:45 -07:00
ce0b716f54 compile error in IXWebSocketMessageQTest 2019-06-09 12:25:36 -07:00
aae8e5ec65 fix IXWebSocketMessageQTest.cpp 2019-06-09 12:08:00 -07:00
2723e8466e fix changelog 2019-06-09 12:02:38 -07:00
f13c610352 update README to reflect the new API 2019-06-09 12:02:02 -07:00
55c65b08bf - WebSocket::send() sends message in TEXT mode by default
- WebSocketMessage sets a new binary field, which tells whether the received incoming message is binary or text
2019-06-09 11:56:47 -07:00
a11aa3e0dd WebSocket::send takes a third arg, binary which default to true (can be text too) 2019-06-09 11:35:31 -07:00
de0bf5ebcd WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg 2019-06-09 11:33:17 -07:00
15369e1ae9 ... 2019-06-09 10:22:27 -07:00
d4115880b9 Add explicite WebSocket::sendBinary
New headers + WebSocketMessage class to hold message data, still not used across the board
2019-06-09 10:10:33 -07:00
3c80c75e4a Add test/compatibility folder with small servers and clients written in different languages and different libraries to test compatibility. 2019-06-08 09:46:26 -07:00
5cb72dce4c ws echo_server has a -g option to print a greeting message on connect 2019-06-08 09:16:33 -07:00
d2747487e3 IXSocketMbedTLS: better error handling in close and connect 2019-06-06 14:59:22 -07:00
12e664fc61 add a changelog 2019-06-06 13:59:12 -07:00
cbf21b4008 add an option to easily disable per message deflate compression 2019-06-06 13:48:53 -07:00
68c1bf7017 fix Dockerfile link 2019-06-05 19:38:44 -07:00
257c901255 cobra to sentry / more error handling 2019-06-05 19:37:51 -07:00
15d8c663da cobra to sentry fixes 2019-06-05 18:47:48 -07:00
d50125c62d Feature/http async (#90)
* unittest working / uses shared_ptr for a bunch of things 🗿

* fix command line tools

* fix ws + add doc

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

* cmake invocation fixed on windows 🐝

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

* build only windows on travis 🕢

* run msbuild 💷

* proper command to build 🕛

* some build fixes

* change weird sizeof call 🐙

* warning and missing includes fixes 💮

* ifdef out statsd code on windows 🐍

* logic invertion in ifdef 👄

* bring back makefile 📜

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

* add stubs socket class

* some boilterplate, read and write function implemented

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

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

* cleanup / close implemented

* tweak CMakefiles

* typo in include

* update readme

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

* try fix test

* try fix test

* Update IXWebSocketTransport.cpp

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* change state immediately, and send close frame after

* add immediate close test

* disable test for windows

* reenable ping / ping timeout tests

* add time to let windows close client

* reenable ping timeout test

* add 100ms more

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

* try fix test

* try fix test

* Update IXWebSocketTransport.cpp

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* add log to find issue on CI

* change state immediately, and send close frame after

* add immediate close test

* disable test for windows
2019-05-21 09:34:08 -07:00
aa3f201ced one cpu on windows for executing tests 2019-05-17 15:45:31 -07:00
83c261977d add back IXWebSocketMessageQueue, with its unittest disabled 2019-05-16 22:41:39 -07:00
6ca28d96bf Linux build fix: strncpy needs <string.h> 2019-05-16 22:21:15 -07:00
c4a5647b62 Revert "Merge branch 'Dimon4eg-message-queue'"
This reverts commit 13fa325134, reversing
changes made to aecd5e9c94.
2019-05-16 22:15:17 -07:00
720d5593a5 Fix Address Sanitizer heap-buffer-overflow in WebSocketHandshakeKeyGen::generate
=================================================================
==5077==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6070000077e0 at pc 0x00010ba18c54 bp 0x70000dd45b10 sp 0x70000dd45b08
READ of size 1 at 0x6070000077e0 thread T12
    #0 0x10ba18c53 in WebSocketHandshakeKeyGen::generate(char const*, char*) libwshandshake.hpp:113
    #1 0x10ba2065a in ix::WebSocketHandshake::serverHandshake(int, int) IXWebSocketHandshake.cpp:356
    #2 0x10b9c4952 in ix::WebSocketTransport::connectToSocket(int, int) IXWebSocketTransport.cpp:190
    #3 0x10b97e4c2 in ix::WebSocket::connectToSocket(int, int) IXWebSocket.cpp:193
2019-05-16 21:59:03 -07:00
13fa325134 Merge branch 'Dimon4eg-message-queue' 2019-05-16 19:26:45 -07:00
773cbb4907 bring back socket mutex which is needed, some CI failures are happening without it 2019-05-16 19:23:32 -07:00
a696264b48 disable socket mutex usage in WebSocketTransport 2019-05-16 19:23:32 -07:00
b7db5f77fb remove dead code 2019-05-16 19:23:32 -07:00
b11678e636 refactor connect unittest so that it hits a local server instead of a remote server 2019-05-16 19:23:32 -07:00
f746070944 travis makefile fix 2019-05-16 19:23:32 -07:00
3323a51ab5 try to run ws test on linux + macOS on travis 2019-05-16 19:23:32 -07:00
0e59927384 Add constants for closing code and messages 2019-05-16 19:23:32 -07:00
5c4840f129 first socket test hit a local server instead of a remote server / this can help with a windows intermittent failure 2019-05-16 19:23:32 -07:00
9ac02323ad build ws on travis (mac + linux) 2019-05-16 19:23:32 -07:00
cdbed26d1f use a regular mutex instead of a recursive one + stop properly 2019-05-16 19:23:32 -07:00
23f171f34d adding logging to IXWebSocketTestConnectionDisconnection makes it fails reliably 2019-05-16 19:23:32 -07:00
20b625e483 Update README.md 2019-05-16 19:23:32 -07:00
f1604c6460 Update README.md 2019-05-16 19:23:32 -07:00
ba0e007c05 -j option actually work ... 2019-05-16 19:23:32 -07:00
643e1bf20f unittest / add options to set the number of jobs 2019-05-16 19:23:32 -07:00
24a32a0603 enum class HttpErrorCode derives from int 2019-05-16 19:23:32 -07:00
c5caf32b77 try to re-enable some tests 2019-05-16 19:23:32 -07:00
09956d7500 recursive mutex + enable test that was breaking on Ubuntu Xenial + gcc + tsan 2019-05-16 19:23:32 -07:00
d91c896e46 comment failing test 2019-05-16 19:23:32 -07:00
042e6a22b8 comment failing test 2019-05-16 19:23:32 -07:00
14ec12d1f0 do not build ws for now on travis 2019-05-16 19:23:32 -07:00
288b05a048 more protection against socket when closing 2019-05-16 19:23:32 -07:00
5af3096070 fix compile errors with C++ enum class 2019-05-16 19:23:32 -07:00
570fa01c04 close and stop with code and reason + docker = ubuntu xenial 2019-05-16 19:23:32 -07:00
2a69038c4c add isEnabledAutomaticReconnection (#75)
* add isEnabledAutomaticReconnection

* test isEnabledAutomaticReconnection

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

* ...

* different generator

* core size = 1

* disable more tests to get something working on windows

* try to enable another test on windows

* enable all OS

* set proper version of linux

* another try

* try again with just env variables

* Revert "core size = 1"

This reverts commit 29af74bba6.

* add windows and mac

* Revert "close with params"

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

* move call into setReadyState

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

* fix condition

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

* run.py: fix Windows support

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

* test isEnabledAutomaticReconnection

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

* ...

* different generator

* core size = 1

* disable more tests to get something working on windows

* try to enable another test on windows

* enable all OS

* set proper version of linux

* another try

* try again with just env variables

* Revert "core size = 1"

This reverts commit 29af74bba6.

* add windows and mac

* Revert "close with params"

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

* move call into setReadyState

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

* fix condition

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

* run.py: fix Windows support

* fix test listing
2019-05-12 18:37:22 -07:00
a41d08343c Merge branch 'master' into message-queue 2019-05-12 22:00:10 +03:00
156288b17b all derived class use final keyword 2019-05-12 11:43:21 -07:00
6467f98241 add setOnMessageCallback with r-value 2019-05-12 20:59:18 +03:00
b24e4334f6 correct style 2019-05-12 20:16:02 +03:00
bf8abcbf4a fix warnings 2019-05-12 20:05:28 +03:00
bb484414b1 update comment 2019-05-12 20:00:15 +03:00
fc75b13fae update test 2019-05-12 19:57:31 +03:00
78f59b4207 added message queue test 2019-05-12 01:50:41 +03:00
7c5567db56 Added WebSocketMessageQueue 2019-05-12 01:49:06 +03:00
ed0e23e8a5 bump version to 2.0.0 2019-05-11 14:22:41 -07:00
4c4f99606e use C++11 enums (#67)
* use C++11 enums

* small rename

* update tests

* update tests

* update ws

* update ws

* update README.md
2019-05-11 14:22:06 -07:00
a61586c846 add comment about why a unittest is disabled 2019-05-11 12:25:40 -07:00
d64d50c978 remove irrelevant comment 2019-05-11 12:24:11 -07:00
a64b7b0c4a minor improvements (#66)
* minor improvements

* fix build

* improve tests code
2019-05-11 12:20:58 -07:00
0caeb81327 minor tweaks to have full feature parity before unittest broke 2019-05-11 11:54:21 -07:00
edac7a0171 fix race condition in SelectInteruptPipe, where _fildes are not protected (caught by fedora tsan) 2019-05-11 11:45:26 -07:00
abfadad2e9 remove more iostream includes (#65) 2019-05-11 11:27:58 -07:00
2dc1547bbd rename some variables, minor cleanup 2019-05-11 10:24:28 -07:00
5eb23c9764 uncomment test 2019-05-11 10:15:22 -07:00
9f4b2856b0 fix crash on close 2019-05-11 10:15:22 -07:00
b5fc10326e fix crash on close 2019-05-11 10:12:33 -07:00
8d3a47a873 Fix crash during closing on Windows (#64)
* fix crash on close

* Improve calculateRetryWaitMilliseconds (#63)

* improve calculateRetryWaitMilliseconds

* update comment

* cout -> spdlog

* fix crash on close

* uncomment test

* Revert "uncomment test"

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

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

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

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

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

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

* fixing one TC

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

* add 1005 code if no status code received

* fixes for 1005 code

* fix test issue

* fix macOS issue

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

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

* fix tests for windows

* qf for linux

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

* cleanup
2019-04-29 21:12:34 -07:00
1d39a9c9a9 build fix 2019-04-29 20:54:00 -07:00
b588ed0fa1 tsan fixes on ubuntu xenial (what travis run) 2019-04-29 20:48:16 -07:00
d9f7a138b8 dns lookup: fix race condition accessing _errMsg 2019-04-29 19:29:27 -07:00
d3e04ff619 tsan linux tentative fix / copy string instead of passing a const reference 2019-04-29 17:27:53 -07:00
372dd24cc7 rename _blocking to _backgroundThreadRunning and invert the naming 2019-04-29 16:54:08 -07:00
a9422cf34d fix data race on _thread 2019-04-29 16:46:16 -07:00
c7e52e6fcd fix data race on _useMask 2019-04-29 16:41:34 -07:00
705e0823cb ws connect mode / add a flag to disable automatic reconnection, not hooked up yet 2019-04-29 14:31:29 -07:00
8e4cf74974 enable tsan on travis for all configs 2019-04-29 09:11:16 -07:00
0a7157655b initialize netSystem (aka winsock on windows) explicitely 2019-04-25 16:38:15 -07:00
58d65926bb Fixes for windows (#45)
* init Net system on Windows

* propagate DNS error

* Add zlib 1.2.11 sources

* link zlib statically for windows

* remove not implemented function declaration

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

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

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

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

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

* missing mutex

* wip

* renaming and fixes

* renaming, fixes

* added enablePong/disablePong, add tests

* added new test cases

* add 1 test case

* fix gcd name to greatestCommonDivisor

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

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

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

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

* fix for Unix

* Fix build if TLS is OFF

* add OpenSSL req to ws

* Fix build on Mac

* fix tests for windows
2019-04-16 08:51:57 -07:00
dac18fcabf move security framework linking to ixwebsocket (#26) 2019-04-14 17:13:24 -07:00
d8e83caffc fix warning 2019-04-13 21:16:04 -07:00
fbf80b9f50 ws: new command to subscribe to a cobra server and send an event to a sentry server 2019-04-11 16:03:05 -07:00
c2a9139d41 (ws) add subcommands: cobra subscribe, and cobra subscribe to statsd bridge 2019-04-08 21:56:01 -07:00
6e3dff149a linux ci tentative fix 2019-04-03 22:02:10 -07:00
1bacbe38f4 better unittest runner / can run through lldb and produce a junit XML artifact 2019-03-29 15:54:05 -07:00
2e9c610ac9 Bump sleep time in test shell script 2019-03-29 09:36:56 -07:00
eb063ec60a (redis_subscribe) in verbose mode, received message gets printed with a 'received: ' header 2019-03-29 09:35:19 -07:00
37fb14646d Add clarification notice about third party modules 2019-03-29 09:34:17 -07:00
ae543518d3 offline version of remark-latest 2019-03-28 16:06:43 -07:00
c865d64608 redis conf slides 2019-03-28 14:17:19 -07:00
3004422cb6 slides 2019-03-27 16:27:52 -07:00
0c46a17443 add redis-conf slides 2019-03-27 15:53:55 -07:00
497373d976 ws redis command improvements + test script 2019-03-27 13:41:46 -07:00
91198aca0d (ws) redis_subscribe and redis_publish can take a password + display subscribe response 2019-03-26 09:33:22 -07:00
b17a5e5f0b update doc 2019-03-24 21:48:14 -07:00
3f0ef59f65 remove Formula folder
Homebrew stuff is at https://github.com/bsergean/homebrew-IXWebSocket
2019-03-24 21:43:38 -07:00
1e96edc293 (server) fix masking bug 2019-03-22 15:33:04 -07:00
0afb77393b can send TEXT message (we only support BINARY messages now) 2019-03-22 14:24:22 -07:00
7614b642bb unmasked code is broken 2019-03-22 14:24:15 -07:00
bc89580dfe remove printf + unittest fix 2019-03-22 09:56:28 -07:00
358ae13a88 (server) server should not mask data when sending to client (some python client libraries enforce that and assert) 2019-03-22 09:53:56 -07:00
ccf9dcba70 (server) HTTP response is malformed 2019-03-22 09:52:19 -07:00
94604fad61 minor cleanup 2019-03-21 13:51:25 -07:00
5c4cc7c50d HTTP/1.1 response should contains a reason (websocket server)
Fix compatibility problem with websockets python library, where the response does not contains a reason

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

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

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

* fix typo

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

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

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

* simple redis clients

* can publish to redis

* redis subscribe

* display messages received per second

* verbose flag

* (cmake) use clang only compile option -Wshorten-64-to-32 when compiling with clang
2019-03-20 14:29:02 -07:00
afe8b966ad Fixed heartbeat typos (#22) 2019-03-19 21:31:43 -07:00
310724c961 make PollResultType an enum class 2019-03-19 09:29:57 -07:00
ceba8ae620 fix bug with isReadyToWrite 2019-03-18 22:05:04 -07:00
fead661ab7 workaround bug in Socket::isReadyToWrite 2019-03-18 20:37:33 -07:00
9c8c17f577 use milliseconds 2019-03-18 20:17:44 -07:00
a04f83930f ws / log subcommand name 2019-03-18 17:54:06 -07:00
c421d19800 disable sigpipe on osx when writing/reading into a dead pipe 2019-03-18 17:52:01 -07:00
521f02c90e edit homebrew install steps 2019-03-18 15:45:33 -07:00
c86b6074f2 add an install target 2019-03-18 15:11:08 -07:00
d5d1a2c5f4 no default parameters for isReadyToWrite and isReadyToRead 2019-03-18 14:31:21 -07:00
2a90e3f478 when trying to flush the send buffer, use select to wait until it is possible instead of using sleep to retry at a given frequency 2019-03-18 14:25:27 -07:00
1d49ba41ea Fix typo (#19) 2019-03-17 16:08:28 -07:00
e1de1f6682 remove unused gitmodule file 2019-03-17 10:38:48 -07:00
47ed5e4d4d remove unused folder 2019-03-17 10:38:19 -07:00
d77f6f5659 linux hangs when closing 2019-03-16 11:38:23 -07:00
05f0045d5d edit README 2019-03-16 11:32:46 -07:00
c4afb84f6e use pipe to abort select on Linux as well as macOS 2019-03-15 17:46:40 -07:00
b0b2f9b6d2 missing assert include on Linux 2019-03-15 11:43:27 -07:00
ee37feb489 cleanup 2019-03-15 11:41:57 -07:00
6b8337596f unittest fix 2019-03-14 18:58:16 -07:00
250665b92e linux compile fix 2019-03-14 18:55:33 -07:00
86b83c889e linux fixes 2019-03-14 18:54:47 -07:00
c9c657c07b build fix 2019-03-14 18:53:21 -07:00
4f2babaf54 select interrupt cleanup 2019-03-14 18:37:38 -07:00
1b03bf4555 linux build fix 2019-03-14 15:17:17 -07:00
977b995af9 replace uint8_t with uint64_t for the send/close requests types / use named variable to index into the _fildes array 2019-03-14 15:03:57 -07:00
310ab990bd set a default close reason string 2019-03-14 14:52:51 -07:00
d6b49b54d4 do not busy loop while sending 2019-03-14 14:48:08 -07:00
f00cf39462 remove docker folder 2019-03-14 14:48:02 -07:00
18550cf1cb send optimization + ws file transfer test 2019-03-14 14:47:53 -07:00
168918f807 Update README.md
Stop lying about Windows support ...
2019-03-13 23:10:40 -07:00
2750df8aa7 send can fail silently when sending would block (EWOULDBLOCK return for send) (#18)
* try to use a pipe for communication

* flush send buffer on the background thread

* cleanup

* linux fix / linux still use event fd for now

* cleanup
2019-03-13 23:09:45 -07:00
d6597d9f52 websocket send: make sure all data in the kernel buffer is sent 2019-03-11 22:16:55 -07:00
892ea375e3 add new message type when receiving message fragments 2019-03-11 11:12:43 -07:00
03abe77b5f ws broacast_server / can set serving hostname 2019-03-10 16:36:44 -07:00
e46eb8aa49 debian 9 unittest build fix 2019-03-10 16:07:48 -07:00
2c4862e0f1 asan test suite fix 2019-03-09 10:45:40 -08:00
fd69efa45c unittest + warning fix 2019-03-09 10:37:14 -08:00
e8aa15917f add ability to run with asan on macOS 2019-03-05 17:07:28 -08:00
b3d77f8902 fix compiler warnings in ws command line tool 2019-03-04 13:56:30 -08:00
9c3b0b08ec Socket code refactoring, plus stop polling with a 1s timeout in readBytes while we only want to poll with a 1ms timeout 2019-03-04 13:40:15 -08:00
fe7d94194c readBytes does not read bytes one by one but in chunks 2019-03-02 21:11:16 -08:00
d6c26d6aa8 create a blocking + cancellable Socket::readBytes method 2019-03-02 15:16:46 -08:00
8a74ddcd13 create a blocking + cancellable Socket::readBytes method 2019-03-02 11:01:51 -08:00
18e7189a07 more ws doc 2019-02-28 22:07:45 -08:00
785dd42c84 more ws doc 2019-02-28 22:03:48 -08:00
0cff5065d9 Feature/http (#16)
* add skeleton and broken http client code.

GET returns "Resource temporarily unavailable" errors...

* linux compile fix

* can GET some pages

* Update formatting in README.md

* unittest for sending large messages

* document bug

* Feature/send large message (#14)

* introduce send fragment

* can pass a fin frame

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

* set fin only for last fragment

* cleanup

* last fragment should be of type CONTINUATION

* Add simple send and receive programs

* speedups receiving + better way to wait for thing

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

* document bug

* use chunks to receive data

* trailing spaces

* Update README.md

Add note about message fragmentation.

* Feature/ws cli (#15)

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

* add readme

* use cli11 for argument parsing

* json -> msgpack

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

* add target for building with homebrew

* all CMakeLists are referenced by the top level one

* add ws_chat and ws_connect sub commands to ws

* cleanup

* add echo and broadcast server as ws sub-commands

* add gitignore

* comments

* ping pong added to ws

* mv cobra_publisher under ws folder

* Update README.md

* linux build fix

* linux build fix

* move http_client to a ws sub-command

* simple HTTP post support (urlencode parameters)

* can specify extra headers

* chunk encoding / simple redirect support / -I option

* follow redirects is optional

* make README vim markdown plugin friendly

* cleanup argument parsing + add socket creation factory

* add missing file

* http gzip compression

* cleanup

* doc

* Feature/send large message (#14)

* introduce send fragment

* can pass a fin frame

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

* set fin only for last fragment

* cleanup

* last fragment should be of type CONTINUATION

* Add simple send and receive programs

* speedups receiving + better way to wait for thing

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

* document bug

* use chunks to receive data

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

View File

@ -2,7 +2,6 @@ name: C/C++ CI
on: [push]
# fake comment to trigger an action 1
jobs:
linux:
runs-on: ubuntu-latest
@ -17,16 +16,16 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: install redis
run: brew install redis
- name: start redis server
run: brew services start redis
- name: make test
run: make test
# We don't need to have redis running anymore, as we have our fake limited one
# - name: install redis
# run: brew install redis
#
# - name: start redis server
# run: brew services start redis
# # Windows does not work yet, I'm stuck at getting CMake to run + finding vcpkg
# win:
# runs-on: windows-2016

View File

@ -129,6 +129,9 @@ if (USE_TLS)
elseif (APPLE AND NOT USE_OPEN_SSL)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
elseif (WIN32 AND NOT USE_OPEN_SSL)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
else()
set(USE_OPEN_SSL ON)
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)

View File

@ -38,7 +38,7 @@ Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If t
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.
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/IXWebSocket/autobahn/index.html). Some tests are still failing in the server code.
## Users

View File

@ -21,7 +21,6 @@ FROM alpine:3.11 as runtime
RUN apk add --no-cache libstdc++
RUN apk add --no-cache strace
RUN apk add --no-cache gdb
RUN addgroup -S app && adduser -S -G app app
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
@ -37,3 +36,4 @@ WORKDIR /home/app
ENTRYPOINT ["ws"]
EXPOSE 8008
C

View File

@ -1,73 +1,9 @@
# Changelog
All changes to this project will be documented in this file.
## [8.1.4] - 2020-02-22
## [7.9.3] - 2020-01-08
(websocket server) fix regression from 8.1.2, where per-deflate message compression was always disabled
## [8.1.3] - 2020-02-21
(client + server) Fix #155 / http header parser should treat the space(s) after the : delimiter as optional. Fixing this bug made us discover that websocket sub-protocols are not properly serialiazed, but start with a ,
## [8.1.2] - 2020-02-18
(WebSocketServer) add option to disable deflate compression, exposed with the -x option to ws echo_server
## [8.1.1] - 2020-02-18
(ws cobra to statsd and sentry sender) exit if no messages are received for one minute, which is a sign that something goes wrong on the server side. That should be changed to be configurable in the future
## [8.1.0] - 2020-02-13
(http client + sentry minidump upload) Multipart stream closing boundary is invalid + mark some options as mandatory in the command line tools
## [8.0.7] - 2020-02-12
(build) remove the unused subtree which was causing some way of installing to break
## [8.0.6] - 2020-01-31
(snake) add an option to disable answering pongs as response to pings, to test cobra client behavior with hanged connections
## [8.0.5] - 2020-01-31
(IXCobraConnection) set a ping timeout of 90 seconds. If no pong messages are received as responses to ping for a while, give up and close the connection
## [8.0.4] - 2020-01-31
(cobra to sentry) remove noisy logging
## [8.0.3] - 2020-01-30
(ixcobra) check if we are authenticated in publishNext before trying to publish a message
## [8.0.2] - 2020-01-28
Extract severity level when emitting messages to sentry
## [8.0.1] - 2020-01-28
Fix bug #151 - If a socket connection is interrupted, calling stop() on the IXWebSocket object blocks until the next retry
## [8.0.0] - 2020-01-26
(SocketServer) add ability to bind on an ipv6 address
## [7.9.6] - 2020-01-22
(ws) add a dnslookup sub-command, to get the ip address of a remote host
## [7.9.5] - 2020-01-14
(windows) fix #144, get rid of stubbed/un-implemented windows schannel ssl backend
## [7.9.4] - 2020-01-12
(openssl + mbedssl) fix #140, can send large files with ws send over ssl / still broken with apple ssl
## [7.9.3] - 2020-01-10
(apple ssl) model write method after the OpenSSL one for consistency
(Windows) OpenSSL can be used for SSL communication
## [7.9.2] - 2020-01-06

View File

@ -23,16 +23,6 @@ Options for building:
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file that has instructions for building dependencies.
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).
@ -43,16 +33,11 @@ vcpkg install ixwebsocket
### Conan
[ ![Download](https://api.bintray.com/packages/conan/conan-center/ixwebsocket%3A_/images/download.svg) ](https://bintray.com/conan/conan-center/ixwebsocket%3A_/_latestVersion)
Support for building with conan was contributed by Olivia Zoe (thanks!). The package name to reference is `IXWebSocket/5.0.0@LunarWatcher/stable`, and a list of the uploaded versions is available on [Bintray](https://bintray.com/oliviazoe0/conan-packages/IXWebSocket%3ALunarWatcher). The package is in the process to be published to the official conan package repo, but in the meantime, it can be accessed by adding a new remote
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.
```
conan remote add remote_name_here https://api.bintray.com/conan/oliviazoe0/conan-packages
```
### Docker

View File

@ -265,15 +265,7 @@ namespace ix
_webSocket->setUrl(url);
_webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
_webSocket->setTLSOptions(socketTLSOptions);
// Send a websocket ping every N seconds (N = 30) now
// This should keep the connection open and prevent some load balancers such as
// the Amazon one from shutting it down
_webSocket->setPingInterval(kPingIntervalSecs);
// If we don't receive a pong back, declare loss after 3 * N seconds
// (will be 90s now), and close and restart the connection
_webSocket->setPingTimeout(3 * kPingIntervalSecs);
}
//
@ -514,7 +506,7 @@ namespace ix
if (_messageQueue.empty()) return true;
auto&& msg = _messageQueue.back();
if (!_authenticated || !publishMessage(msg))
if (!publishMessage(msg))
{
return false;
}

View File

@ -166,17 +166,6 @@ namespace ix
tags.append(tag);
}
}
if (msg["data"]["info"].isMember("level_str"))
{
// https://docs.sentry.io/enriching-error-data/context/?platform=python#setting-the-level
std::string level = msg["data"]["info"]["level_str"].asString();
if (level == "critical")
{
level = "fatal";
}
payload["level"] = level;
}
}
else
{

View File

@ -32,7 +32,6 @@ namespace snake
// Misc
bool verbose;
bool disablePong;
};
bool isAppKeyValid(const AppConfig& appConfig, std::string appkey);

View File

@ -17,8 +17,8 @@
namespace ix
{
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections)
: SocketServer(port, host, backlog, maxConnections)
, _connectedClientsCount(0)
, _stopHandlingConnections(false)
{

View File

@ -25,8 +25,7 @@ namespace ix
RedisServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily);
size_t maxConnections = SocketServer::kDefaultMaxConnections);
virtual ~RedisServer();
virtual void stop() final;

View File

@ -21,15 +21,6 @@ namespace snake
, _server(appConfig.port, appConfig.hostname)
{
_server.setTLSOptions(appConfig.socketTLSOptions);
if (appConfig.disablePong)
{
_server.disablePong();
}
std::stringstream ss;
ss << "Listening on " << appConfig.hostname << ":" << appConfig.port;
ix::IXCoreLogger::Log(ss.str().c_str());
}
//

View File

@ -648,7 +648,7 @@ namespace ix
<< it.second << "\r\n";
}
ss << "--" << multipartBoundary << "--\r\n";
ss << "--" << multipartBoundary << "\r\n";
return ss.str();
}

View File

@ -42,9 +42,8 @@ namespace
namespace ix
{
HttpServer::HttpServer(
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
HttpServer::HttpServer(int port, const std::string& host, int backlog, size_t maxConnections)
: SocketServer(port, host, backlog, maxConnections)
, _connectedClientsCount(0)
{
setDefaultConnectionCallback();

View File

@ -28,8 +28,7 @@ namespace ix
HttpServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily);
size_t maxConnections = SocketServer::kDefaultMaxConnections);
virtual ~HttpServer();
virtual void stop() final;

View File

@ -54,17 +54,14 @@ namespace ix
// 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
// should write 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;
fds[0].events = (readyToRead) ? POLLIN : POLLOUT;
// this is ignored by poll, but our select based poll wrapper on Windows needs it
fds[0].events |= POLLERR;
// File descriptor used to interrupt select when needed
@ -135,11 +132,6 @@ namespace ix
}
#endif
}
else if (sockfd != -1 && (fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
fds[0].revents & POLLNVAL))
{
pollResult = PollResultType::Error;
}
return pollResult;
}

View File

@ -73,7 +73,7 @@ namespace ix
virtual void close();
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);
// Blocking and cancellable versions, working with socket that can be set

View File

@ -265,6 +265,11 @@ namespace ix
return -1;
}
ssize_t SocketAppleSSL::send(const std::string& buffer)
{
return send((char*) &buffer[0], buffer.size());
}
// No wait support
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
{

View File

@ -30,6 +30,7 @@ namespace ix
virtual void close() final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:

View File

@ -14,6 +14,8 @@
#include "IXSocketOpenSSL.h"
#elif __APPLE__
#include "IXSocketAppleSSL.h"
#elif defined(_WIN32)
#include "IXSocketSChannel.h"
#endif
#else
@ -44,6 +46,8 @@ namespace ix
socket = std::make_shared<SocketMbedTLS>(tlsOptions, fd);
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
socket = std::make_shared<SocketOpenSSL>(tlsOptions, fd);
#elif defined(_WIN32)
socket = std::make_shared<SocketSChannel>(tlsOptions, fd);
#elif defined(__APPLE__)
socket = std::make_shared<SocketAppleSSL>(tlsOptions, fd);
#endif

View File

@ -230,23 +230,35 @@ namespace ix
ssize_t SocketMbedTLS::send(char* buf, size_t nbyte)
{
std::lock_guard<std::mutex> lock(_mutex);
ssize_t sent = 0;
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
while (nbyte > 0)
{
std::lock_guard<std::mutex> lock(_mutex);
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 res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
if (res > 0)
{
nbyte -= res;
sent += res;
}
else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{
errno = EWOULDBLOCK;
return -1;
}
else
{
return -1;
}
}
return sent;
}
ssize_t SocketMbedTLS::send(const std::string& buffer)
{
return send((char*) &buffer[0], buffer.size());
}
ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte)

View File

@ -35,6 +35,7 @@ namespace ix
virtual void close() final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:

View File

@ -603,30 +603,42 @@ namespace ix
ssize_t SocketOpenSSL::send(char* buf, size_t nbyte)
{
std::lock_guard<std::mutex> lock(_mutex);
ssize_t sent = 0;
if (_ssl_connection == nullptr || _ssl_context == nullptr)
while (nbyte > 0)
{
return 0;
}
std::lock_guard<std::mutex> lock(_mutex);
ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, (int) write_result);
if (_ssl_connection == nullptr || _ssl_context == nullptr)
{
return 0;
}
if (reason == SSL_ERROR_NONE)
{
return write_result;
}
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
errno = EWOULDBLOCK;
return -1;
}
else
{
return -1;
ERR_clear_error();
ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
int reason = SSL_get_error(_ssl_connection, (int) write_result);
if (reason == SSL_ERROR_NONE)
{
nbyte -= write_result;
sent += write_result;
}
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
{
errno = EWOULDBLOCK;
return -1;
}
else
{
return -1;
}
}
return sent;
}
ssize_t SocketOpenSSL::send(const std::string& buffer)
{
return send((char*) &buffer[0], buffer.size());
}
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)

View File

@ -33,6 +33,7 @@ namespace ix
virtual void close() final;
virtual ssize_t send(char* buffer, size_t length) final;
virtual ssize_t send(const std::string& buffer) final;
virtual ssize_t recv(void* buffer, size_t length) final;
private:

View File

@ -0,0 +1,103 @@
/*
* 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
*
* Similar code is available from this git repo
* https://github.com/david-maw/StreamSSL
*/
#include "IXSocketSChannel.h"
#ifdef _WIN32
#include <WS2tcpip.h>
#include <WinSock2.h>
#include <basetsd.h>
#include <io.h>
#include <schannel.h>
#include <ws2def.h>
#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif
#include <mstcpip.h>
#include <ntdsapi.h>
#include <rpc.h>
#include <stdio.h>
#include <tchar.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.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);
}
} // namespace ix

View File

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

View File

@ -21,15 +21,15 @@ namespace ix
const std::string SocketServer::kDefaultHost("127.0.0.1");
const int SocketServer::kDefaultTcpBacklog(5);
const size_t SocketServer::kDefaultMaxConnections(32);
const int SocketServer::kDefaultAddressFamily(AF_INET);
SocketServer::SocketServer(
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
SocketServer::SocketServer(int port,
const std::string& host,
int backlog,
size_t maxConnections)
: _port(port)
, _host(host)
, _backlog(backlog)
, _maxConnections(maxConnections)
, _addressFamily(addressFamily)
, _serverFd(-1)
, _stop(false)
, _stopGc(false)
@ -56,15 +56,10 @@ namespace ix
std::pair<bool, std::string> SocketServer::listen()
{
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);
}
struct sockaddr_in server; // server address information
// 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;
ss << "SocketServer::listen() error creating socket): " << strerror(Socket::getErrno());
@ -84,63 +79,27 @@ namespace ix
return std::make_pair(false, ss.str());
}
if (_addressFamily == AF_INET)
// Bind the socket to the server address.
server.sin_family = AF_INET;
server.sin_port = htons(_port);
// 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
// to allow that, but this is a bit of a pain. (this is what node or python would do).
//
// Using INADDR_LOOPBACK also does not work ... while it should.
// We default to 127.0.0.1 (localhost)
//
server.sin_addr.s_addr = inet_addr(_host.c_str());
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
struct sockaddr_in server;
server.sin_family = _addressFamily;
server.sin_port = htons(_port);
std::stringstream ss;
ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
if (inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_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());
}
}
else // AF_INET6
{
struct sockaddr_in6 server;
server.sin6_family = _addressFamily;
server.sin6_port = htons(_port);
if (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());
}
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
//

View File

@ -36,8 +36,7 @@ namespace ix
SocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily);
size_t maxConnections = SocketServer::kDefaultMaxConnections);
virtual ~SocketServer();
virtual void stop();
@ -50,7 +49,6 @@ namespace ix
const static std::string kDefaultHost;
const static int kDefaultTcpBacklog;
const static size_t kDefaultMaxConnections;
const static int kDefaultAddressFamily;
void start();
std::pair<bool, std::string> listen();
@ -71,7 +69,6 @@ namespace ix
std::string _host;
int _backlog;
size_t _maxConnections;
int _addressFamily;
// socket for accepting connections
int _serverFd;

View File

@ -134,13 +134,6 @@ namespace ix
_enablePong = false;
}
void WebSocket::enablePerMessageDeflate()
{
std::lock_guard<std::mutex> lock(_configMutex);
WebSocketPerMessageDeflateOptions perMessageDeflateOptions(true);
_perMessageDeflateOptions = perMessageDeflateOptions;
}
void WebSocket::disablePerMessageDeflate()
{
std::lock_guard<std::mutex> lock(_configMutex);
@ -176,7 +169,6 @@ namespace ix
// wait until working thread will exit
// it will exit after close operation is finished
_stop = true;
_sleepCondition.notify_one();
_thread.join();
_stop = false;
}
@ -198,19 +190,9 @@ namespace ix
auto subProtocols = getSubProtocols();
if (!subProtocols.empty())
{
//
// Sub Protocol strings are comma separated.
// Python code to do that is:
// >>> ','.join(['json', 'msgpack'])
// 'json,msgpack'
//
int i = 0;
for (auto subProtocol : subProtocols)
{
if (i++ != 0)
{
subProtocolsHeader += ",";
}
subProtocolsHeader += ",";
subProtocolsHeader += subProtocol;
}
headers["Sec-WebSocket-Protocol"] = subProtocolsHeader;
@ -300,13 +282,8 @@ namespace ix
// Only sleep if we are retrying
if (duration.count() > 0)
{
std::unique_lock<std::mutex> lock(_sleepMutex);
_sleepCondition.wait_for(lock, duration);
}
if (_stop)
{
break;
// to do: make sleeping conditional
std::this_thread::sleep_for(duration);
}
// Try to connect synchronously

View File

@ -19,7 +19,6 @@
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketTransport.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>
@ -57,7 +56,6 @@ namespace ix
void setPingTimeout(int pingTimeoutSecs);
void enablePong();
void disablePong();
void enablePerMessageDeflate();
void disablePerMessageDeflate();
void addSubProtocol(const std::string& subProtocol);
@ -142,10 +140,6 @@ namespace ix
static const uint32_t kDefaultMaxWaitBetweenReconnectionRetries;
uint32_t _maxWaitBetweenReconnectionRetries;
// Make the sleeping in the automatic reconnection cancellable
std::mutex _sleepMutex;
std::condition_variable _sleepCondition;
std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs;

View File

@ -12,7 +12,6 @@
#include "IXUserAgent.h"
#include "libwshandshake.hpp"
#include <algorithm>
#include <iostream>
#include <random>
#include <sstream>
@ -336,9 +335,8 @@ namespace ix
std::string header = headers["sec-websocket-extensions"];
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
// If the client has requested that extension,
// and the server does not prevent it, enable it.
if (_enablePerMessageDeflate && webSocketPerMessageDeflateOptions.enabled())
// If the client has requested that extension, enable it.
if (webSocketPerMessageDeflateOptions.enabled())
{
_enablePerMessageDeflate = true;

View File

@ -66,23 +66,12 @@ namespace ix
{
line[i] = '\0';
std::string lineStr(line);
// colon is ':', usually colon+1 is ' ', and colon+2 is the start of the value.
// some webservers do not put a space after the colon character, so
// the start of the value might be farther than colon+2.
// The spec says that space after the : should be discarded.
// colon is ':', colon+1 is ' ', colon+2 is the start of the value.
// i is end of string (\0), i-colon is length of string minus key;
// subtract 1 for '\0', 1 for '\n', 1 for '\r',
// 1 for the ' ' after the ':', and total is -4
// since we use an std::string later on and don't account for '\0',
// plus the optional first space, total is -2
int start = colon + 1;
while (lineStr[start] == ' ')
{
start++;
}
std::string name(lineStr.substr(0, colon));
std::string value(lineStr.substr(start, lineStr.size() - start - 2));
std::string value(lineStr.substr(colon + 2, i - colon - 4));
headers[name] = value;
}

View File

@ -23,12 +23,10 @@ namespace ix
const std::string& host,
int backlog,
size_t maxConnections,
int handshakeTimeoutSecs,
int addressFamily)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
int handshakeTimeoutSecs)
: SocketServer(port, host, backlog, maxConnections)
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
, _enablePong(kDefaultEnablePong)
, _enablePerMessageDeflate(true)
{
}
@ -60,11 +58,6 @@ namespace ix
_enablePong = false;
}
void WebSocketServer::disablePerMessageDeflate()
{
_enablePerMessageDeflate = false;
}
void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
@ -79,22 +72,9 @@ namespace ix
webSocket->disableAutomaticReconnection();
if (_enablePong)
{
webSocket->enablePong();
}
else
{
webSocket->disablePong();
}
if (_enablePerMessageDeflate)
{
webSocket->enablePerMessageDeflate();
}
else
{
webSocket->disablePerMessageDeflate();
}
// Add this client to our client set
{
@ -126,6 +106,7 @@ namespace ix
}
}
logInfo("WebSocketServer::handleConnection() done");
connectionState->setTerminated();
}

View File

@ -29,33 +29,29 @@ namespace ix
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
int addressFamily = SocketServer::kDefaultAddressFamily);
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
virtual ~WebSocketServer();
virtual void stop() final;
void enablePong();
void disablePong();
void disablePerMessageDeflate();
void setOnConnectionCallback(const OnConnectionCallback& callback);
// Get all the connected clients
std::set<std::shared_ptr<WebSocket>> getClients();
const static int kDefaultHandShakeTimeoutSecs;
private:
// Member variables
int _handshakeTimeoutSecs;
bool _enablePong;
bool _enablePerMessageDeflate;
OnConnectionCallback _onConnectionCallback;
std::mutex _clientsMutex;
std::set<std::shared_ptr<WebSocket>> _clients;
const static int kDefaultHandShakeTimeoutSecs;
const static bool kDefaultEnablePong;
// Methods

View File

@ -350,9 +350,28 @@ namespace ix
}
else if (pollResult == PollResultType::ReadyForRead)
{
if (!receiveFromSocket())
while (true)
{
return PollResult::AbnormalClose;
ssize_t ret = _socket->recv((char*) &_readbuf[0], _readbuf.size());
if (ret < 0 && Socket::isWaitNeeded())
{
break;
}
else if (ret <= 0)
{
// if there are received data pending to be processed, then delay the abnormal
// closure to after dispatch (other close code/reason could be read from the
// buffer)
closeSocket();
return PollResult::AbnormalClose;
}
else
{
_rxbuf.insert(_rxbuf.end(), _readbuf.begin(), _readbuf.begin() + ret);
}
}
}
else if (pollResult == PollResultType::Error)
@ -720,7 +739,7 @@ namespace ix
// if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in
// the received and processed data then close the connection
if (pollResult != PollResult::Succeeded)
if (pollResult == PollResult::AbnormalClose)
{
_rxbuf.clear();
@ -1034,17 +1053,19 @@ namespace ix
wsheader_type::TEXT_FRAME, message, _enablePerMessageDeflate, onProgressCallback);
}
ssize_t WebSocketTransport::send()
{
std::lock_guard<std::mutex> lock(_socketMutex);
return _socket->send((char*) &_txbuf[0], _txbuf.size());
}
bool WebSocketTransport::sendOnSocket()
{
std::lock_guard<std::mutex> lock(_txbufMutex);
while (_txbuf.size())
{
ssize_t ret = 0;
{
std::lock_guard<std::mutex> lock(_socketMutex);
ret = _socket->send((char*) &_txbuf[0], _txbuf.size());
}
ssize_t ret = send();
if (ret < 0 && Socket::isWaitNeeded())
{
@ -1065,34 +1086,6 @@ namespace ix
return true;
}
bool WebSocketTransport::receiveFromSocket()
{
while (true)
{
ssize_t ret = _socket->recv((char*) &_readbuf[0], _readbuf.size());
if (ret < 0 && Socket::isWaitNeeded())
{
break;
}
else if (ret <= 0)
{
// if there are received data pending to be processed, then delay the abnormal
// closure to after dispatch (other close code/reason could be read from the
// buffer)
closeSocket();
return false;
}
else
{
_rxbuf.insert(_rxbuf.end(), _readbuf.begin(), _readbuf.begin() + ret);
}
}
return true;
}
void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason)
{
bool compress = false;

View File

@ -99,6 +99,7 @@ namespace ix
bool remote = false);
void closeSocket();
ssize_t send();
ReadyState getReadyState() const;
void setReadyState(ReadyState readyState);
@ -244,8 +245,6 @@ namespace ix
bool flushSendBuffer();
bool sendOnSocket();
bool receiveFromSocket();
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
const std::string& message,
bool compress,

View File

@ -6,4 +6,4 @@
#pragma once
#define IX_WEBSOCKET_VERSION "8.1.4"
#define IX_WEBSOCKET_VERSION "7.9.3"

View File

@ -49,9 +49,6 @@ BUILD := ${NAME}:build
print_version:
@echo 'IXWebSocket version =>' ${TAG}
set_version:
sh tools/update_version.sh ${VERSION}
docker_test:
docker build -f docker/Dockerfile.debian -t bsergean/ixwebsocket_test:build .

View File

@ -55,12 +55,12 @@ set (SOURCES
IXDNSLookupTest.cpp
IXWebSocketSubProtocolTest.cpp
IXSentryClientTest.cpp
IXWebSocketChatTest.cpp
)
# Some unittest don't work on windows yet
if (UNIX)
list(APPEND SOURCES
IXWebSocketChatTest.cpp
IXWebSocketCloseTest.cpp
)
endif()

View File

@ -59,14 +59,10 @@ TEST_CASE("http server", "[httpd]")
REQUIRE(response->errorCode == HttpErrorCode::Ok);
REQUIRE(response->statusCode == 200);
REQUIRE(response->headers["Accept-Encoding"] == "gzip");
server.stop();
}
}
TEST_CASE("http server redirection", "[httpd_redirect]")
{
SECTION("Connect to a local HTTP server, with redirection enabled")
{
int port = getFreePort();

View File

@ -0,0 +1,26 @@
name: C++ CI
on: [push]
jobs:
build_linux:
runs-on: ubuntu-latest
steps:
- name: Clone source
run: git clone --recursive https://github.com/uNetworking/uWebSockets.git
- name: Build source
run: make -C uWebSockets
build_osx:
runs-on: macos-latest
steps:
- name: Clone source
run: git clone --recursive https://github.com/uNetworking/uWebSockets.git
- name: Build source
run: make -C uWebSockets

View File

@ -0,0 +1,3 @@
[submodule "uSockets"]
path = uSockets
url = https://github.com/uNetworking/uSockets.git

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,40 @@
EXAMPLE_FILES := HelloWorld EchoServer BroadcastingEchoServer
THREADED_EXAMPLE_FILES := HelloWorldThreaded EchoServerThreaded
override CXXFLAGS += -lpthread -Wconversion -std=c++17 -Isrc -IuSockets/src
override LDFLAGS += uSockets/*.o -lz
# WITH_OPENSSL=1 enables OpenSSL 1.1+ support
ifeq ($(WITH_OPENSSL),1)
# With problems on macOS, make sure to pass needed LDFLAGS required to find these
override LDFLAGS += -lssl -lcrypto
else
# WITH_WOLFSSL=1 enables WolfSSL 4.2.0 support (mutually exclusive with OpenSSL)
ifeq ($(WITH_WOLFSSL),1)
override LDFLAGS += -L/usr/local/lib -lwolfssl
endif
endif
# WITH_LIBUV=1 builds with libuv as event-loop
ifeq ($(WITH_LIBUV),1)
override LDFLAGS += -luv
endif
# WITH_ASAN builds with sanitizers
ifeq ($(WITH_ASAN),1)
override CXXFLAGS += -fsanitize=address
override LDFLAGS += -lasan
endif
.PHONY: examples
examples:
cd uSockets && make
$(foreach FILE,$(EXAMPLE_FILES),$(CXX) -flto -O3 $(CXXFLAGS) examples/$(FILE).cpp -o $(FILE) $(LDFLAGS);)
$(foreach FILE,$(THREADED_EXAMPLE_FILES),$(CXX) -pthread -flto -O3 $(CXXFLAGS) examples/$(FILE).cpp -o $(FILE) $(LDFLAGS);)
all:
make examples
make -C fuzzing
make -C benchmarks
clean:
rm -rf $(EXAMPLE_FILES) $(THREADED_EXAMPLE_FILES)
rm -rf fuzzing/*.o benchmarks/*.o

View File

@ -0,0 +1,65 @@
<div align="center">
<img src="misc/logo.svg" height="180" />
*µWebSockets™ (it's "[micro](https://en.wikipedia.org/wiki/Micro-)") is simple, secure*<sup>[[1]](fuzzing)</sup> *& standards compliant*<sup>[[2]](https://unetworking.github.io/uWebSockets.js/report.pdf)</sup> *web I/O for the most demanding*<sup>[[3]](benchmarks)</sup> *of applications.*
• [Read more](misc/READMORE.md) • [Read about uSockets](https://github.com/uNetworking/uSockets) • [See uWebSockets.js](https://github.com/uNetworking/uWebSockets.js)
*© 2016-2019, >39,632,272 downloads*
</div>
#### Express yourself briefly.
```c++
uWS::SSLApp({
/* There are tons of SSL options */
.cert_file_name = "cert.pem",
.key_file_name = "key.pem"
}).get("/hello", [](auto *res, auto *req) {
/* You can efficiently stream huge files too */
res->writeHeader("Content-Type", "text/html; charset=utf-8")->end("Hello HTTP!");
}).ws<UserData>("/*", {
/* Just a few of the available handlers */
.open = [](auto *ws, auto *req) {
ws->subscribe("buzzword weekly");
},
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
ws->send(message, opCode);
}
}).listen(9001, [](auto *token) {
if (token) {
std::cout << "Listening on port " << 9001 << std::endl;
}
}).run();
```
Don't miss the [user manual](https://github.com/uNetworking/uWebSockets/blob/master/misc/READMORE.md#user-manual), the [C++ examples](https://github.com/uNetworking/uWebSockets/tree/master/examples) or the [JavaScript examples](https://github.com/uNetworking/uWebSockets.js/tree/master/examples). JavaScript examples are very applicable to C++ developers, so go through them as well.
#### Pay what you want.
A free & open source ([permissive](LICENSE)) project since 2016. Kindly sponsored by [BitMEX](https://bitmex.com), [Bitfinex](https://bitfinex.com) & [Coinbase](https://www.coinbase.com/) in 2018 and/or 2019. Individual donations are always accepted via [PayPal](https://paypal.me/uwebsockets).
<div align="center"><img src="misc/2018.png"/></div>
*Code is provided as-is, do not expect or demand **free** consulting services, personal tutoring, advice or debugging.*
#### Deploy like a boss.
Commercial support is available via a per-hourly consulting plan or as otherwise negotiated. If you're stuck, worried about design or just in need of help don't hesitate throwing [me, the author](https://github.com/alexhultman) a mail and we'll figure out what's best for both parties. I want your business to have a proper understanding of the problem before rushing in to one of the many pitfalls.
#### Excel across the board.
All that glitters is not gold. Especially so in a market driven by flashy logos, hype and pointless badges.
Http | WebSockets
--- | ---
![](misc/bigshot_lineup.png) | ![](misc/websocket_lineup.png)
#### Keep it legal.
Intellectual property, all rights reserved.
*You are forbidden to use logos, product names, texts, names or otherwise perceived brand identity, of copyright holder, in any way that might state or imply that the copyright holder endorses your distribution or in any way that might state or imply that you created the original software. Modified distributions must carry, from the original distribution, significantly different names and must not be confused with the original distribution.*

View File

@ -0,0 +1,4 @@
default:
clang -flto -O3 -DLIBUS_USE_OPENSSL -I../uSockets/src ../uSockets/src/*.c ../uSockets/src/eventing/*.c ../uSockets/src/crypto/*.c broadcast_test.c -o broadcast_test -lssl -lcrypto
clang -flto -O3 -DLIBUS_USE_OPENSSL -I../uSockets/src ../uSockets/src/*.c ../uSockets/src/eventing/*.c ../uSockets/src/crypto/*.c load_test.c -o load_test -lssl -lcrypto
clang -flto -O3 -DLIBUS_USE_OPENSSL -I../uSockets/src ../uSockets/src/*.c ../uSockets/src/eventing/*.c ../uSockets/src/crypto/*.c scale_test.c -o scale_test -lssl -lcrypto

View File

@ -0,0 +1,17 @@
# Benchmark-driven development
Just like testing code for correctness and stability, testing for performance is just as important if performance is a goal. You cannot really argue or reason about performance without having tests for it.
* Do not trust anyone who claims performance of any kind unless they provide benchmarks. Do not listen to people who talk about performance without having actual scientific data to back their claims up.
* Never accept absolute numbers without a direct comparison with an alternative solution. Many projects can give you a number, X, which can be "50 billion messages per second". How much is this? What kind of worth does this number have? Impossible to know without a comparison. Absolute numbers mean nothing, relative comparisons are what you should look for.
* Make sure to benchmark the correct thing. This is an extremely common mistake, done by many of the most well-known developers out there. If you measure for CPU-time efficiency (which you do) then normalizing for spent CPU-time is the difference between a completely invalid, botched and bogus test and something that might be valid.
Here are the current relative comparisons:
Http | WebSockets
--- | ---
![](../misc/bigshot_lineup.png) | ![](../misc/websocket_lineup.png)
Over the period of a few years I have never come across any web server which can score as high as µWebSockets do. This is not to say that µWebSockets is fastest, as "fastest" is a silly superlative nobody should *ever* use.
Never trust anyone using superlatives to describe their work; they are more often wrong than right.

View File

@ -0,0 +1,196 @@
/* This benchmark establishes _connections_ number of WebSocket
clients, then iteratively performs the following:
1. Send one message for every client.
2. Wait for the quadratic (_connections_^2) amount of responses from the server.
3. Once received all expected bytes, repeat by going to step 1.
Every 4 seconds we print the current average "iterations per second".
*/
#include <libusockets.h>
int SSL;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned char web_socket_request[26] = {130, 128 | 20, 1, 2, 3, 4};
char request[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
"Host: server.example.com\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
char *host;
int port;
int connections;
int satisfied_sockets;
int iterations;
struct http_socket {
/* How far we have streamed our websocket request */
int offset;
/* How far we have streamed our upgrade request */
int upgrade_offset;
/* Are we upgraded? */
int is_upgraded;
/* Bytes received */
int bytes_received;
};
/* We track upgraded websockets */
void **web_sockets;
int num_web_sockets;
/* We don't need any of these */
void noop(struct us_loop_t *loop) {
}
void start_iteration() {
for (int i = 0; i < num_web_sockets; i++) {
struct us_socket_t *s = (struct us_socket_t *) web_sockets[i];
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, sizeof(web_socket_request), 0);
}
}
void next_connection(struct us_socket_t *s) {
/* Add this connection to our array */
web_sockets[num_web_sockets++] = s;
/* We could wait with this until properly upgraded */
if (--connections) {
us_socket_context_connect(SSL, us_socket_context(SSL, s), host, port, 0, sizeof(struct http_socket));
} else {
printf("Running benchmark now...\n");
start_iteration();
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
}
}
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Are we still not upgraded yet? */
if (http_socket->upgrade_offset < sizeof(request) - 1) {
http_socket->upgrade_offset += us_socket_write(SSL, s, request + http_socket->upgrade_offset, sizeof(request) - 1 - http_socket->upgrade_offset, 0);
} else {
/* Stream whatever is remaining of the request */
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, sizeof(web_socket_request) - http_socket->offset, 0);
}
return s;
}
struct us_socket_t *on_http_socket_close(struct us_socket_t *s) {
printf("Client was disconnected, exiting!\n");
exit(-1);
return s;
}
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
return us_socket_close(SSL, s);
}
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
/* Get socket extension and the socket's context's extension */
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Are we already upgraded? */
if (http_socket->is_upgraded) {
http_socket->bytes_received += length;
if (http_socket->bytes_received == (sizeof(web_socket_request) - 4) * num_web_sockets) {
satisfied_sockets++;
http_socket->bytes_received = 0;
if (satisfied_sockets == num_web_sockets) {
iterations++;
satisfied_sockets = 0;
start_iteration();
}
}
} else {
/* We assume the server is not sending anything immediately following upgrade and that we get rnrn in one chunk */
if (length >= 4 && data[length - 1] == '\n' && data[length - 2] == '\r' && data[length - 3] == '\n' && data[length - 4] == '\r') {
http_socket->is_upgraded = 1;
next_connection(s);
}
}
return s;
}
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Reset offsets */
http_socket->offset = 0;
http_socket->is_upgraded = 0;
http_socket->bytes_received = 0;
/* Send an upgrade request */
http_socket->upgrade_offset = us_socket_write(SSL, s, request, sizeof(request) - 1, 0);
return s;
}
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
/* Print current statistics */
printf("Iterations/second (%d clients): %f\n", num_web_sockets, ((float)iterations) / LIBUS_TIMEOUT_GRANULARITY);
iterations = 0;
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
return s;
}
int main(int argc, char **argv) {
/* Parse host and port */
if (argc != 5) {
printf("Usage: connections host port ssl\n");
return 0;
}
port = atoi(argv[3]);
host = malloc(strlen(argv[2]) + 1);
memcpy(host, argv[2], strlen(argv[2]) + 1);
connections = atoi(argv[1]);
SSL = atoi(argv[4]);
/* Allocate room for every socket */
web_sockets = (void **) malloc(sizeof(void *) * connections);
/* Create the event loop */
struct us_loop_t *loop = us_create_loop(0, noop, noop, noop, 0);
/* Create a socket context for HTTP */
struct us_socket_context_options_t options = {};
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
/* Set up event handlers */
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
/* Start making HTTP connections */
us_socket_context_connect(SSL, http_context, host, port, 0, sizeof(struct http_socket));
us_loop_run(loop);
}

View File

@ -0,0 +1,161 @@
/* This is a simple yet efficient WebSocket server benchmark much like WRK */
#include <libusockets.h>
int SSL;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// request eller upgradeRequest samt webSocketFrame
unsigned char web_socket_request[26] = {130, 128 | 20, 1, 2, 3, 4};
char request[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
"Host: server.example.com\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
char *host;
int port;
int connections;
int responses;
struct http_socket {
/* How far we have streamed our websocket request */
int offset;
/* How far we have streamed our upgrade request */
int upgrade_offset;
};
/* We don't need any of these */
void on_wakeup(struct us_loop_t *loop) {
}
void on_pre(struct us_loop_t *loop) {
}
/* This is not HTTP POST, it is merely an event emitted post loop iteration */
void on_post(struct us_loop_t *loop) {
}
void next_connection(struct us_socket_t *s) {
/* We could wait with this until properly upgraded */
if (--connections) {
us_socket_context_connect(SSL, us_socket_context(SSL, s), host, port, 0, sizeof(struct http_socket));
} else {
printf("Running benchmark now...\n");
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
}
}
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Are we still not upgraded yet? */
if (http_socket->upgrade_offset < sizeof(request) - 1) {
http_socket->upgrade_offset += us_socket_write(SSL, s, request + http_socket->upgrade_offset, sizeof(request) - 1 - http_socket->upgrade_offset, 0);
/* Now we should be */
if (http_socket->upgrade_offset == sizeof(request) - 1) {
next_connection(s);
}
} else {
/* Stream whatever is remaining of the request */
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, sizeof(web_socket_request) - http_socket->offset, 0);
}
return s;
}
struct us_socket_t *on_http_socket_close(struct us_socket_t *s) {
printf("Closed!\n");
return s;
}
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
return us_socket_close(SSL, s);
}
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
/* Get socket extension and the socket's context's extension */
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
//struct http_context *http_context = (struct http_context *) us_socket_context_ext(SSL, us_socket_context(SSL, s));
/* We treat all data events as a response */
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, sizeof(web_socket_request), 0);
/* */
responses++;
return s;
}
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Reset offsets */
http_socket->offset = 0;
/* Send an upgrade request */
http_socket->upgrade_offset = us_socket_write(SSL, s, request, sizeof(request) - 1, 0);
if (http_socket->upgrade_offset == sizeof(request) - 1) {
next_connection(s);
}
return s;
}
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
/* Print current statistics */
printf("Msg/sec: %f\n", ((float)responses) / LIBUS_TIMEOUT_GRANULARITY);
responses = 0;
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
return s;
}
int main(int argc, char **argv) {
/* Parse host and port */
if (argc != 5) {
printf("Usage: connections host port ssl\n");
return 0;
}
port = atoi(argv[3]);
host = malloc(strlen(argv[2]) + 1);
memcpy(host, argv[2], strlen(argv[2]) + 1);
connections = atoi(argv[1]);
SSL = atoi(argv[4]);
/* Create the event loop */
struct us_loop_t *loop = us_create_loop(0, on_wakeup, on_pre, on_post, 0);
/* Create a socket context for HTTP */
struct us_socket_context_options_t options = {};
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
/* Set up event handlers */
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
/* Start making HTTP connections */
us_socket_context_connect(SSL, http_context, host, port, 0, sizeof(struct http_socket));
us_loop_run(loop);
}

View File

@ -0,0 +1,185 @@
/* This is a scalability test for testing million(s) of pinging connections */
#include <libusockets.h>
int SSL;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned char web_socket_request[26] = {130, 128 | 20, 1, 2, 3, 4};
char request[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
"Host: server.example.com\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
char *host;
int port;
int connections;
/* Send ping every 16 seconds */
int WEBSOCKET_PING_INTERVAL = 16;
/* We only establish 20k connections per address */
int CONNECTIONS_PER_ADDRESS = 20000;
/* How many connections a time */
int BATCH_CONNECT = 1;
/* Currently open and alive connections */
int opened_connections;
/* Dead connections */
int closed_connections;
struct http_socket {
/* How far we have streamed our websocket request */
int offset;
/* How far we have streamed our upgrade request */
int upgrade_offset;
};
/* We don't need any of these */
void on_wakeup(struct us_loop_t *loop) {
}
void on_pre(struct us_loop_t *loop) {
}
/* This is not HTTP POST, it is merely an event emitted post loop iteration */
void on_post(struct us_loop_t *loop) {
}
void next_connection(struct us_socket_t *s) {
/* We could wait with this until properly upgraded */
if (--connections/* > BATCH_CONNECT*/) {
/* Swap address */
int address = opened_connections / CONNECTIONS_PER_ADDRESS + 1;
char buf[16];
sprintf(buf, "127.0.0.%d", address);
us_socket_context_connect(SSL, us_socket_context(SSL, s), buf, port, 0, sizeof(struct http_socket));
}
}
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Are we still not upgraded yet? */
if (http_socket->upgrade_offset < sizeof(request) - 1) {
http_socket->upgrade_offset += us_socket_write(SSL, s, request + http_socket->upgrade_offset, sizeof(request) - 1 - http_socket->upgrade_offset, 0);
/* Now we should be */
if (http_socket->upgrade_offset == sizeof(request) - 1) {
next_connection(s);
/* Make sure to send ping */
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
}
} else {
/* Stream whatever is remaining of the request */
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, sizeof(web_socket_request) - http_socket->offset, 0);
if (http_socket->offset == sizeof(web_socket_request)) {
/* Reset timeout if we managed to */
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
}
}
return s;
}
struct us_socket_t *on_http_socket_close(struct us_socket_t *s) {
closed_connections++;
if (closed_connections % 1000 == 0) {
printf("Alive: %d, dead: %d\n", opened_connections, closed_connections);
}
return s;
}
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
return us_socket_close(SSL, s);
}
// should never get a response!
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
return s;
}
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Display number of opened connections */
opened_connections++;
if (opened_connections % 1000 == 0) {
printf("Alive: %d, dead: %d\n", opened_connections, closed_connections);
}
/* Send an upgrade request */
http_socket->upgrade_offset = us_socket_write(SSL, s, request, sizeof(request) - 1, 0);
if (http_socket->upgrade_offset == sizeof(request) - 1) {
next_connection(s);
/* Make sure to send ping */
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
}
return s;
}
// here we should send a message as ping (part of the test)
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Send ping here */
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, sizeof(web_socket_request), 0);
if (http_socket->offset == sizeof(web_socket_request)) {
/* Reset timeout if we managed to */
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
}
return s;
}
int main(int argc, char **argv) {
/* Parse host and port */
if (argc != 5) {
printf("Usage: connections host port ssl\n");
return 0;
}
port = atoi(argv[3]);
host = malloc(strlen(argv[2]) + 1);
memcpy(host, argv[2], strlen(argv[2]) + 1);
connections = atoi(argv[1]);
SSL = atoi(argv[4]);
/* Create the event loop */
struct us_loop_t *loop = us_create_loop(0, on_wakeup, on_pre, on_post, 0);
/* Create a socket context for HTTP */
struct us_socket_context_options_t options = {};
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
/* Set up event handlers */
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
/* Start making HTTP connections */
for (int i = 0; i < BATCH_CONNECT; i++) {
us_socket_context_connect(SSL, http_context, host, port, 0, sizeof(struct http_socket));
}
us_loop_run(loop);
}

View File

@ -0,0 +1,52 @@
#include "App.h"
struct us_listen_socket_t *listen_socket;
int main() {
/* ws->getUserData returns one of these */
struct PerSocketData {
};
/* Very simple WebSocket broadcasting echo server */
uWS::App().ws<PerSocketData>("/*", {
/* Settings */
.compression = uWS::SHARED_COMPRESSOR,
.maxPayloadLength = 16 * 1024 * 1024,
.idleTimeout = 10,
.maxBackpressure = 1 * 1024 * 1204,
/* Handlers */
.open = [](auto *ws, auto *req) {
/* Let's make every connection subscribe to the "broadcast" topic */
ws->subscribe("broadcast");
},
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
/* Exit gracefully if we get a closedown message (ASAN debug) */
if (message == "closedown") {
/* Bye bye */
us_listen_socket_close(0, listen_socket);
ws->close();
}
/* Simply broadcast every single message we get */
ws->publish("broadcast", message, opCode);
},
.drain = [](auto *ws) {
/* Check getBufferedAmount here */
},
.ping = [](auto *ws) {
},
.pong = [](auto *ws) {
},
.close = [](auto *ws, int code, std::string_view message) {
/* We automatically unsubscribe from any topic here */
}
}).listen(9001, [](auto *token) {
listen_socket = token;
if (token) {
std::cout << "Listening on port " << 9001 << std::endl;
}
}).run();
}

View File

@ -0,0 +1,50 @@
/* We simply call the root header file "App.h", giving you uWS::App and uWS::SSLApp */
#include "App.h"
/* This is a simple WebSocket echo server example.
* You may compile it with "WITH_OPENSSL=1 make" or with "make" */
int main() {
/* ws->getUserData returns one of these */
struct PerSocketData {
/* Fill with user data */
};
/* Keep in mind that uWS::SSLApp({options}) is the same as uWS::App() when compiled without SSL support.
* You may swap to using uWS:App() if you don't need SSL */
uWS::SSLApp({
/* There are example certificates in uWebSockets.js repo */
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
}).ws<PerSocketData>("/*", {
/* Settings */
.compression = uWS::SHARED_COMPRESSOR,
.maxPayloadLength = 16 * 1024,
.idleTimeout = 10,
.maxBackpressure = 1 * 1024 * 1204,
/* Handlers */
.open = [](auto *ws, auto *req) {
/* Open event here, you may access ws->getUserData() which points to a PerSocketData struct */
},
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
ws->send(message, opCode);
},
.drain = [](auto *ws) {
/* Check ws->getBufferedAmount() here */
},
.ping = [](auto *ws) {
/* Not implemented yet */
},
.pong = [](auto *ws) {
/* Not implemented yet */
},
.close = [](auto *ws, int code, std::string_view message) {
/* You may access ws->getUserData() here */
}
}).listen(9001, [](auto *token) {
if (token) {
std::cout << "Listening on port " << 9001 << std::endl;
}
}).run();
}

View File

@ -0,0 +1,57 @@
#include "App.h"
#include <thread>
#include <algorithm>
int main() {
/* ws->getUserData returns one of these */
struct PerSocketData {
};
/* Simple echo websocket server, using multiple threads */
std::vector<std::thread *> threads(std::thread::hardware_concurrency());
std::transform(threads.begin(), threads.end(), threads.begin(), [](std::thread *t) {
return new std::thread([]() {
/* Very simple WebSocket echo server */
uWS::App().ws<PerSocketData>("/*", {
/* Settings */
.compression = uWS::SHARED_COMPRESSOR,
.maxPayloadLength = 16 * 1024,
.idleTimeout = 10,
.maxBackpressure = 1 * 1024 * 1204,
/* Handlers */
.open = [](auto *ws, auto *req) {
},
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
ws->send(message, opCode);
},
.drain = [](auto *ws) {
/* Check getBufferedAmount here */
},
.ping = [](auto *ws) {
},
.pong = [](auto *ws) {
},
.close = [](auto *ws, int code, std::string_view message) {
}
}).listen(9001, [](auto *token) {
if (token) {
std::cout << "Thread " << std::this_thread::get_id() << " listening on port " << 9001 << std::endl;
} else {
std::cout << "Thread " << std::this_thread::get_id() << " failed to listen on port 9001" << std::endl;
}
}).run();
});
});
std::for_each(threads.begin(), threads.end(), [](std::thread *t) {
t->join();
});
}

View File

@ -0,0 +1,20 @@
#include "App.h"
/* Note that uWS::SSLApp({options}) is the same as uWS::App() when compiled without SSL support */
int main() {
/* Overly simple hello world app */
uWS::SSLApp({
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
}).get("/*", [](auto *res, auto *req) {
res->end("Hello world!");
}).listen(3000, [](auto *token) {
if (token) {
std::cout << "Listening on port " << 3000 << std::endl;
}
}).run();
std::cout << "Failed to listen on port 3000" << std::endl;
}

View File

@ -0,0 +1,28 @@
#include "App.h"
#include <thread>
#include <algorithm>
int main() {
/* Overly simple hello world app, using multiple threads */
std::vector<std::thread *> threads(std::thread::hardware_concurrency());
std::transform(threads.begin(), threads.end(), threads.begin(), [](std::thread *t) {
return new std::thread([]() {
uWS::App().get("/*", [](auto *res, auto *req) {
res->end("Hello world!");
}).listen(3000, [](auto *token) {
if (token) {
std::cout << "Thread " << std::this_thread::get_id() << " listening on port " << 3000 << std::endl;
} else {
std::cout << "Thread " << std::this_thread::get_id() << " failed to listen on port 3000" << std::endl;
}
}).run();
});
});
std::for_each(threads.begin(), threads.end(), [](std::thread *t) {
t->join();
});
}

View File

@ -0,0 +1,90 @@
/* This is a simple HTTP(S) web server much like Python's SimpleHTTPServer */
#include <App.h>
/* Helpers for this example */
#include "helpers/AsyncFileReader.h"
#include "helpers/AsyncFileStreamer.h"
#include "helpers/Middleware.h"
/* optparse */
#define OPTPARSE_IMPLEMENTATION
#include "helpers/optparse.h"
int main(int argc, char **argv) {
int option;
struct optparse options;
optparse_init(&options, argv);
struct optparse_long longopts[] = {
{"port", 'p', OPTPARSE_REQUIRED},
{"help", 'h', OPTPARSE_NONE},
{"passphrase", 'a', OPTPARSE_REQUIRED},
{"key", 'k', OPTPARSE_REQUIRED},
{"cert", 'c', OPTPARSE_REQUIRED},
{"dh_params", 'd', OPTPARSE_REQUIRED},
{0}
};
int port = 3000;
struct us_socket_context_options_t ssl_options = {};
while ((option = optparse_long(&options, longopts, nullptr)) != -1) {
switch (option) {
case 'p':
port = atoi(options.optarg);
break;
case 'a':
ssl_options.passphrase = options.optarg;
break;
case 'c':
ssl_options.cert_file_name = options.optarg;
break;
case 'k':
ssl_options.key_file_name = options.optarg;
break;
case 'd':
ssl_options.dh_params_file_name = options.optarg;
break;
case 'h':
case '?':
fail:
std::cout << "Usage: " << argv[0] << " [--help] [--port <port>] [--key <ssl key>] [--cert <ssl cert>] [--passphrase <ssl key passphrase>] [--dh_params <ssl dh params file>] <public root>" << std::endl;
return 0;
}
}
char *root = optparse_arg(&options);
if (!root) {
goto fail;
}
AsyncFileStreamer asyncFileStreamer(root);
/* Either serve over HTTP or HTTPS */
struct us_socket_context_options_t empty_ssl_options = {};
if (memcmp(&ssl_options, &empty_ssl_options, sizeof(empty_ssl_options))) {
/* HTTPS */
uWS::SSLApp(ssl_options).get("/*", [&asyncFileStreamer](auto *res, auto *req) {
serveFile(res, req);
asyncFileStreamer.streamFile(res, req->getUrl());
}).listen(port, [port, root](auto *token) {
if (token) {
std::cout << "Serving " << root << " over HTTPS a " << port << std::endl;
}
}).run();
} else {
/* HTTP */
uWS::App().get("/*", [&asyncFileStreamer](auto *res, auto *req) {
serveFile(res, req);
asyncFileStreamer.streamFile(res, req->getUrl());
}).listen(port, [port, root](auto *token) {
if (token) {
std::cout << "Serving " << root << " over HTTP a " << port << std::endl;
}
}).run();
}
std::cout << "Failed to listen to port " << port << std::endl;
}

View File

@ -0,0 +1,130 @@
#include <map>
#include <cstring>
#include <fstream>
#include <sstream>
#include <iostream>
#include <future>
/* This is just a very simple and inefficient demo of async responses,
* please do roll your own variant or use a database or Node.js's async
* features instead of this really bad demo */
struct AsyncFileReader {
private:
/* The cache we have in memory for this file */
std::string cache;
int cacheOffset;
bool hasCache;
/* The pending async file read (yes we only support one pending read) */
std::function<void(std::string_view)> pendingReadCb;
int fileSize;
std::string fileName;
std::ifstream fin;
uWS::Loop *loop;
public:
/* Construct a demo async. file reader for fileName */
AsyncFileReader(std::string fileName) : fileName(fileName) {
fin.open(fileName, std::ios::binary);
// get fileSize
fin.seekg(0, fin.end);
fileSize = fin.tellg();
//std::cout << "File size is: " << fileSize << std::endl;
// cache up 1 mb!
cache.resize(1024 * 1024);
//std::cout << "Caching 1 MB at offset = " << 0 << std::endl;
fin.seekg(0, fin.beg);
fin.read(cache.data(), cache.length());
cacheOffset = 0;
hasCache = true;
// get loop for thread
loop = uWS::Loop::get();
}
/* Returns any data already cached for this offset */
std::string_view peek(int offset) {
/* Did we hit the cache? */
if (hasCache && offset >= cacheOffset && ((offset - cacheOffset) < cache.length())) {
/* Cache hit */
//std::cout << "Cache hit!" << std::endl;
/*if (fileSize - offset < cache.length()) {
std::cout << "LESS THAN WHAT WE HAVE!" << std::endl;
}*/
int chunkSize = std::min<int>(fileSize - offset, cache.length() - offset + cacheOffset);
return std::string_view(cache.data() + offset - cacheOffset, chunkSize);
} else {
/* Cache miss */
//std::cout << "Cache miss!" << std::endl;
return std::string_view(nullptr, 0);
}
}
/* Asynchronously request more data at offset */
void request(int offset, std::function<void(std::string_view)> cb) {
// in this case, what do we do?
// we need to queue up this chunk request and callback!
// if queue is full, either block or close the connection via abort!
if (!hasCache) {
// already requesting a chunk!
std::cout << "ERROR: already requesting a chunk!" << std::endl;
return;
}
// disable cache
hasCache = false;
std::async(std::launch::async, [this, cb, offset]() {
//std::cout << "ASYNC Caching 1 MB at offset = " << offset << std::endl;
// den har stängts! öppna igen!
if (!fin.good()) {
fin.close();
//std::cout << "Reopening fin!" << std::endl;
fin.open(fileName, std::ios::binary);
}
fin.seekg(offset, fin.beg);
fin.read(cache.data(), cache.length());
cacheOffset = offset;
loop->defer([this, cb, offset]() {
int chunkSize = std::min<int>(cache.length(), fileSize - offset);
// båda dessa sker, wtf?
if (chunkSize == 0) {
std::cout << "Zero size!?" << std::endl;
}
if (chunkSize != cache.length()) {
std::cout << "LESS THAN A CACHE 1 MB!" << std::endl;
}
hasCache = true;
cb(std::string_view(cache.data(), chunkSize));
});
});
}
/* Abort any pending async. request */
void abort() {
}
int getFileSize() {
return fileSize;
}
};

View File

@ -0,0 +1,84 @@
#include <filesystem>
struct AsyncFileStreamer {
std::map<std::string_view, AsyncFileReader *> asyncFileReaders;
std::string root;
AsyncFileStreamer(std::string root) : root(root) {
// for all files in this path, init the map of AsyncFileReaders
updateRootCache();
}
void updateRootCache() {
// todo: if the root folder changes, we want to reload the cache
for(auto &p : std::filesystem::recursive_directory_iterator(root)) {
std::string url = p.path().string().substr(root.length());
if (url == "/index.html") {
url = "/";
}
char *key = new char[url.length()];
memcpy(key, url.data(), url.length());
asyncFileReaders[std::string_view(key, url.length())] = new AsyncFileReader(p.path().string());
}
}
template <bool SSL>
void streamFile(uWS::HttpResponse<SSL> *res, std::string_view url) {
auto it = asyncFileReaders.find(url);
if (it == asyncFileReaders.end()) {
std::cout << "Did not find file: " << url << std::endl;
} else {
streamFile(res, it->second);
}
}
template <bool SSL>
static void streamFile(uWS::HttpResponse<SSL> *res, AsyncFileReader *asyncFileReader) {
/* Peek from cache */
std::string_view chunk = asyncFileReader->peek(res->getWriteOffset());
if (!chunk.length() || res->tryEnd(chunk, asyncFileReader->getFileSize()).first) {
/* Request new chunk */
// todo: we need to abort this callback if peer closed!
// this also means Loop::defer needs to support aborting (functions should embedd an atomic boolean abort or something)
// Loop::defer(f) -> integer
// Loop::abort(integer)
// hmm? no?
// us_socket_up_ref eftersom vi delar ägandeskapet
if (chunk.length() < asyncFileReader->getFileSize()) {
asyncFileReader->request(res->getWriteOffset(), [res, asyncFileReader](std::string_view chunk) {
// check if we were closed in the mean time
//if (us_socket_is_closed()) {
// free it here
//return;
//}
/* We were aborted for some reason */
if (!chunk.length()) {
// todo: make sure to check for is_closed internally after all callbacks!
res->close();
} else {
AsyncFileStreamer::streamFile(res, asyncFileReader);
}
});
}
} else {
/* We failed writing everything, so let's continue when we can */
res->onWritable([res, asyncFileReader](int offset) {
// här kan skiten avbrytas!
AsyncFileStreamer::streamFile(res, asyncFileReader);
// todo: I don't really know what this is supposed to mean?
return false;
})->onAborted([]() {
std::cout << "ABORTED!" << std::endl;
});
}
}
};

View File

@ -0,0 +1,19 @@
/* Middleware to fill out content-type */
inline bool hasExt(std::string_view file, std::string_view ext) {
if (ext.size() > file.size()) {
return false;
}
return std::equal(ext.rbegin(), ext.rend(), file.rbegin());
}
/* This should be a filter / middleware like app.use(handler) */
template <bool SSL>
uWS::HttpResponse<SSL> *serveFile(uWS::HttpResponse<SSL> *res, uWS::HttpRequest *req) {
res->writeStatus(uWS::HTTP_200_OK);
if (hasExt(req->getUrl(), ".svg")) {
res->writeHeader("Content-Type", "image/svg+xml");
}
return res;
}

View File

@ -0,0 +1,407 @@
/* Nicked from third-party https://github.com/skeeto/optparse 2018-09-24 */
/* µWebSockets is not the origin of this software file */
/* ------------------------------------------------------ */
/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
*
* This is free and unencumbered software released into the public domain.
*
* To get the implementation, define OPTPARSE_IMPLEMENTATION.
* Optionally define OPTPARSE_API to control the API's visibility
* and/or linkage (static, __attribute__, __declspec).
*
* The POSIX getopt() option parser has three fatal flaws. These flaws
* are solved by Optparse.
*
* 1) Parser state is stored entirely in global variables, some of
* which are static and inaccessible. This means only one thread can
* use getopt(). It also means it's not possible to recursively parse
* nested sub-arguments while in the middle of argument parsing.
* Optparse fixes this by storing all state on a local struct.
*
* 2) The POSIX standard provides no way to properly reset the parser.
* This means for portable code that getopt() is only good for one
* run, over one argv with one option string. It also means subcommand
* options cannot be processed with getopt(). Most implementations
* provide a method to reset the parser, but it's not portable.
* Optparse provides an optparse_arg() function for stepping over
* subcommands and continuing parsing of options with another option
* string. The Optparse struct itself can be passed around to
* subcommand handlers for additional subcommand option parsing. A
* full reset can be achieved by with an additional optparse_init().
*
* 3) Error messages are printed to stderr. This can be disabled with
* opterr, but the messages themselves are still inaccessible.
* Optparse solves this by writing an error message in its errmsg
* field. The downside to Optparse is that this error message will
* always be in English rather than the current locale.
*
* Optparse should be familiar with anyone accustomed to getopt(), and
* it could be a nearly drop-in replacement. The option string is the
* same and the fields have the same names as the getopt() global
* variables (optarg, optind, optopt).
*
* Optparse also supports GNU-style long options with optparse_long().
* The interface is slightly different and simpler than getopt_long().
*
* By default, argv is permuted as it is parsed, moving non-option
* arguments to the end. This can be disabled by setting the `permute`
* field to 0 after initialization.
*/
#ifndef OPTPARSE_H
#define OPTPARSE_H
#ifndef OPTPARSE_API
# define OPTPARSE_API
#endif
struct optparse {
char **argv;
int permute;
int optind;
int optopt;
char *optarg;
char errmsg[64];
int subopt;
};
enum optparse_argtype {
OPTPARSE_NONE,
OPTPARSE_REQUIRED,
OPTPARSE_OPTIONAL
};
struct optparse_long {
const char *longname;
int shortname;
enum optparse_argtype argtype;
};
/**
* Initializes the parser state.
*/
OPTPARSE_API
void optparse_init(struct optparse *options, char **argv);
/**
* Read the next option in the argv array.
* @param optstring a getopt()-formatted option string.
* @return the next option character, -1 for done, or '?' for error
*
* Just like getopt(), a character followed by no colons means no
* argument. One colon means the option has a required argument. Two
* colons means the option takes an optional argument.
*/
OPTPARSE_API
int optparse(struct optparse *options, const char *optstring);
/**
* Handles GNU-style long options in addition to getopt() options.
* This works a lot like GNU's getopt_long(). The last option in
* longopts must be all zeros, marking the end of the array. The
* longindex argument may be NULL.
*/
OPTPARSE_API
int optparse_long(struct optparse *options,
const struct optparse_long *longopts,
int *longindex);
/**
* Used for stepping over non-option arguments.
* @return the next non-option argument, or NULL for no more arguments
*
* Argument parsing can continue with optparse() after using this
* function. That would be used to parse the options for the
* subcommand returned by optparse_arg(). This function allows you to
* ignore the value of optind.
*/
OPTPARSE_API
char *optparse_arg(struct optparse *options);
/* Implementation */
#ifdef OPTPARSE_IMPLEMENTATION
#define OPTPARSE_MSG_INVALID "invalid option"
#define OPTPARSE_MSG_MISSING "option requires an argument"
#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
static int
optparse_error(struct optparse *options, const char *msg, const char *data)
{
unsigned p = 0;
const char *sep = " -- '";
while (*msg)
options->errmsg[p++] = *msg++;
while (*sep)
options->errmsg[p++] = *sep++;
while (p < sizeof(options->errmsg) - 2 && *data)
options->errmsg[p++] = *data++;
options->errmsg[p++] = '\'';
options->errmsg[p++] = '\0';
return '?';
}
OPTPARSE_API
void
optparse_init(struct optparse *options, char **argv)
{
options->argv = argv;
options->permute = 1;
options->optind = 1;
options->subopt = 0;
options->optarg = 0;
options->errmsg[0] = '\0';
}
static int
optparse_is_dashdash(const char *arg)
{
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
}
static int
optparse_is_shortopt(const char *arg)
{
return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
}
static int
optparse_is_longopt(const char *arg)
{
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
}
static void
optparse_permute(struct optparse *options, int index)
{
char *nonoption = options->argv[index];
int i;
for (i = index; i < options->optind - 1; i++)
options->argv[i] = options->argv[i + 1];
options->argv[options->optind - 1] = nonoption;
}
static int
optparse_argtype(const char *optstring, char c)
{
int count = OPTPARSE_NONE;
if (c == ':')
return -1;
for (; *optstring && c != *optstring; optstring++);
if (!*optstring)
return -1;
if (optstring[1] == ':')
count += optstring[2] == ':' ? 2 : 1;
return count;
}
OPTPARSE_API
int
optparse(struct optparse *options, const char *optstring)
{
int type;
char *next;
char *option = options->argv[options->optind];
options->errmsg[0] = '\0';
options->optopt = 0;
options->optarg = 0;
if (option == 0) {
return -1;
} else if (optparse_is_dashdash(option)) {
options->optind++; /* consume "--" */
return -1;
} else if (!optparse_is_shortopt(option)) {
if (options->permute) {
int index = options->optind++;
int r = optparse(options, optstring);
optparse_permute(options, index);
options->optind--;
return r;
} else {
return -1;
}
}
option += options->subopt + 1;
options->optopt = option[0];
type = optparse_argtype(optstring, option[0]);
next = options->argv[options->optind + 1];
switch (type) {
case -1: {
char str[2] = {0, 0};
str[0] = option[0];
options->optind++;
return optparse_error(options, OPTPARSE_MSG_INVALID, str);
}
case OPTPARSE_NONE:
if (option[1]) {
options->subopt++;
} else {
options->subopt = 0;
options->optind++;
}
return option[0];
case OPTPARSE_REQUIRED:
options->subopt = 0;
options->optind++;
if (option[1]) {
options->optarg = option + 1;
} else if (next != 0) {
options->optarg = next;
options->optind++;
} else {
char str[2] = {0, 0};
str[0] = option[0];
options->optarg = 0;
return optparse_error(options, OPTPARSE_MSG_MISSING, str);
}
return option[0];
case OPTPARSE_OPTIONAL:
options->subopt = 0;
options->optind++;
if (option[1])
options->optarg = option + 1;
else
options->optarg = 0;
return option[0];
}
return 0;
}
OPTPARSE_API
char *
optparse_arg(struct optparse *options)
{
char *option = options->argv[options->optind];
options->subopt = 0;
if (option != 0)
options->optind++;
return option;
}
static int
optparse_longopts_end(const struct optparse_long *longopts, int i)
{
return !longopts[i].longname && !longopts[i].shortname;
}
static void
optparse_from_long(const struct optparse_long *longopts, char *optstring)
{
char *p = optstring;
int i;
for (i = 0; !optparse_longopts_end(longopts, i); i++) {
if (longopts[i].shortname) {
int a;
*p++ = longopts[i].shortname;
for (a = 0; a < (int)longopts[i].argtype; a++)
*p++ = ':';
}
}
*p = '\0';
}
/* Unlike strcmp(), handles options containing "=". */
static int
optparse_longopts_match(const char *longname, const char *option)
{
const char *a = option, *n = longname;
if (longname == 0)
return 0;
for (; *a && *n && *a != '='; a++, n++)
if (*a != *n)
return 0;
return *n == '\0' && (*a == '\0' || *a == '=');
}
/* Return the part after "=", or NULL. */
static char *
optparse_longopts_arg(char *option)
{
for (; *option && *option != '='; option++);
if (*option == '=')
return option + 1;
else
return 0;
}
static int
optparse_long_fallback(struct optparse *options,
const struct optparse_long *longopts,
int *longindex)
{
int result;
char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
optparse_from_long(longopts, optstring);
result = optparse(options, optstring);
if (longindex != 0) {
*longindex = -1;
if (result != -1) {
int i;
for (i = 0; !optparse_longopts_end(longopts, i); i++)
if (longopts[i].shortname == options->optopt)
*longindex = i;
}
}
return result;
}
OPTPARSE_API
int
optparse_long(struct optparse *options,
const struct optparse_long *longopts,
int *longindex)
{
int i;
char *option = options->argv[options->optind];
if (option == 0) {
return -1;
} else if (optparse_is_dashdash(option)) {
options->optind++; /* consume "--" */
return -1;
} else if (optparse_is_shortopt(option)) {
return optparse_long_fallback(options, longopts, longindex);
} else if (!optparse_is_longopt(option)) {
if (options->permute) {
int index = options->optind++;
int r = optparse_long(options, longopts, longindex);
optparse_permute(options, index);
options->optind--;
return r;
} else {
return -1;
}
}
/* Parse as long option. */
options->errmsg[0] = '\0';
options->optopt = 0;
options->optarg = 0;
option += 2; /* skip "--" */
options->optind++;
for (i = 0; !optparse_longopts_end(longopts, i); i++) {
const char *name = longopts[i].longname;
if (optparse_longopts_match(name, option)) {
char *arg;
if (longindex)
*longindex = i;
options->optopt = longopts[i].shortname;
arg = optparse_longopts_arg(option);
if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
} if (arg != 0) {
options->optarg = arg;
} else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
options->optarg = options->argv[options->optind];
if (options->optarg == 0)
return optparse_error(options, OPTPARSE_MSG_MISSING, name);
else
options->optind++;
}
return options->optopt;
}
}
return optparse_error(options, OPTPARSE_MSG_INVALID, option);
}
#endif /* OPTPARSE_IMPLEMENTATION */
#endif /* OPTPARSE_H */

View File

@ -0,0 +1,39 @@
/* This is a fuzz test of the websocket extensions parser */
#define WIN32_EXPORT
#include <cstdio>
#include <string>
/* We test the websocket extensions parser */
#include "../src/WebSocketExtensions.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
{
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::PERMESSAGE_DEFLATE);
extensionsNegotiator.readOffer({(char *) data, size});
extensionsNegotiator.generateOffer();
extensionsNegotiator.getNegotiatedOptions();
}
{
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::NO_OPTIONS);
extensionsNegotiator.readOffer({(char *) data, size});
extensionsNegotiator.generateOffer();
extensionsNegotiator.getNegotiatedOptions();
}
{
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::CLIENT_NO_CONTEXT_TAKEOVER);
extensionsNegotiator.readOffer({(char *) data, size});
extensionsNegotiator.generateOffer();
extensionsNegotiator.getNegotiatedOptions();
}
return 0;
}

View File

@ -0,0 +1,20 @@
/* This is a fuzz test of the websocket handshake generator */
#define WIN32_EXPORT
#include <cstdio>
#include <string>
/* We test the websocket handshake generator */
#include "../src/WebSocketHandshake.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char output[28];
if (size >= 24) {
uWS::WebSocketHandshake::generate((char *) data, output);
}
return 0;
}

View File

@ -0,0 +1,124 @@
/* This is a fuzz test of the http parser */
#define WIN32_EXPORT
#include "helpers.h"
/* We test the websocket parser */
#include "../src/HttpParser.h"
/* And the router */
#include "../src/HttpRouter.h"
struct StaticData {
struct RouterData {
};
uWS::HttpRouter<RouterData> router;
StaticData() {
router.add({"get"}, "/:hello/:hi", [](auto *h) mutable {
auto [paramsTop, params] = h->getParameters();
/* Something is horribly wrong */
if (paramsTop != 1 || !params[0].length() || !params[1].length()) {
exit(-1);
}
/* This route did handle it */
return true;
});
router.add({"post"}, "/:hello/:hi/*", [](auto *h) mutable {
auto [paramsTop, params] = h->getParameters();
/* Something is horribly wrong */
if (paramsTop != 1 || !params[0].length() || !params[1].length()) {
exit(-1);
}
/* This route did handle it */
return true;
});
router.add({"get"}, "/*", [](auto *h) mutable {
auto [paramsTop, params] = h->getParameters();
/* Something is horribly wrong */
if (paramsTop != -1) {
exit(-1);
}
/* This route did not handle it */
return false;
});
router.add({"get"}, "/hi", [](auto *h) mutable {
auto [paramsTop, params] = h->getParameters();
/* Something is horribly wrong */
if (paramsTop != -1) {
exit(-1);
}
/* This route did handle it */
return true;
});
}
} staticData;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
/* Create parser */
uWS::HttpParser httpParser;
/* User data */
void *user = (void *) 13;
/* Iterate the padded fuzz as chunks */
makeChunked(makePadded(data, size), size, [&httpParser, user](const uint8_t *data, size_t size) {
/* We need at least 1 byte post padding */
if (size) {
size--;
} else {
/* We might be given zero length chunks */
return;
}
/* Parse it */
httpParser.consumePostPadded((char *) data, size, user, [](void *s, uWS::HttpRequest *httpRequest) -> void * {
readBytes(httpRequest->getHeader(httpRequest->getUrl()));
readBytes(httpRequest->getMethod());
readBytes(httpRequest->getQuery());
/* Route the method and URL in two passes */
staticData.router.getUserData() = {};
if (!staticData.router.route(httpRequest->getMethod(), httpRequest->getUrl())) {
/* It was not handled */
return nullptr;
}
for (auto p : *httpRequest) {
}
/* Return ok */
return s;
}, [](void *user, std::string_view data, bool fin) -> void * {
/* Return ok */
return user;
}, [](void *user) {
/* Return break */
return nullptr;
});
});
return 0;
}

View File

@ -0,0 +1,21 @@
# You can select which sanitizer to use by setting this
SANITIZER ?= address
# These are set by OSS-Fuzz, we default to AddressSanitizer
CXXFLAGS ?= -DLIBUS_NO_SSL -fsanitize=$(SANITIZER),fuzzer
CFLAGS ?= -DLIBUS_NO_SSL
OUT ?= .
oss-fuzz:
# "Unit tests"
$(CXX) $(CXXFLAGS) -std=c++17 -O3 WebSocket.cpp -o $(OUT)/WebSocket $(LIB_FUZZING_ENGINE)
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Http.cpp -o $(OUT)/Http $(LIB_FUZZING_ENGINE)
$(CXX) $(CXXFLAGS) -std=c++17 -O3 PerMessageDeflate.cpp -o $(OUT)/PerMessageDeflate $(LIB_FUZZING_ENGINE) -lz
# "Integration tests"
$(CC) $(CFLAGS) -DLIBUS_NO_SSL -c -O3 uSocketsMock.c
$(CXX) $(CXXFLAGS) -std=c++17 -O3 -DLIBUS_NO_SSL -I../src -I../uSockets/src MockedHelloWorld.cpp uSocketsMock.o -lz -o $(OUT)/MockedHelloWorld $(LIB_FUZZING_ENGINE)
$(CXX) $(CXXFLAGS) -std=c++17 -O3 -DLIBUS_NO_SSL -I../src -I../uSockets/src MockedEchoServer.cpp uSocketsMock.o -lz -o $(OUT)/MockedEchoServer $(LIB_FUZZING_ENGINE)
broken:
# Too small tests, failing coverage test
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Extensions.cpp -o $(OUT)/Extensions $(LIB_FUZZING_ENGINE)
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Handshake.cpp -o $(OUT)/Handshake $(LIB_FUZZING_ENGINE)

View File

@ -0,0 +1,75 @@
#include "App.h"
#include "helpers.h"
/* This function pushes data to the uSockets mock */
extern "C" void us_loop_read_mocked_data(struct us_loop *loop, char *data, unsigned int size);
uWS::TemplatedApp<false> *app;
us_listen_socket_t *listenSocket;
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
/* ws->getUserData returns one of these */
struct PerSocketData {
int nothing;
};
/* Very simple WebSocket echo server */
app = new uWS::TemplatedApp<false>(uWS::App().ws<PerSocketData>("/*", {
/* Settings */
.compression = uWS::SHARED_COMPRESSOR,
/* We want this to be low so that we can hit it, yet bigger than 256 */
.maxPayloadLength = 300,
.idleTimeout = 10,
/* Handlers */
.open = [](auto *ws, auto *req) {
if (req->getHeader("close_me").length()) {
ws->close();
} else if (req->getHeader("end_me").length()) {
ws->end(1006);
}
},
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
if (message.length() > 300) {
/* Inform the sanitizer of the fault */
fprintf(stderr, "Too long message passed\n");
free((void *) -1);
}
if (message.length() && message[0] == 'C') {
ws->close();
} else if (message.length() && message[0] == 'E') {
ws->end(1006);
} else {
ws->send(message, opCode, true);
}
},
.drain = [](auto *ws) {
/* Check getBufferedAmount here */
},
.ping = [](auto *ws) {
},
.pong = [](auto *ws) {
},
.close = [](auto *ws, int code, std::string_view message) {
}
}).listen(9001, [](us_listen_socket_t *listenSocket) {
if (listenSocket) {
std::cout << "Listening on port " << 9001 << std::endl;
::listenSocket = listenSocket;
}
}));
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
us_loop_read_mocked_data((struct us_loop *) uWS::Loop::get(), (char *) makePadded(data, size), size);
return 0;
}

View File

@ -0,0 +1,47 @@
#include "App.h"
#include "helpers.h"
/* This function pushes data to the uSockets mock */
extern "C" void us_loop_read_mocked_data(struct us_loop *loop, char *data, unsigned int size);
uWS::TemplatedApp<false> *app;
us_listen_socket_t *listenSocket;
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
app = new uWS::TemplatedApp<false>(uWS::App().get("/*", [](auto *res, auto *req) {
if (req->getHeader("use_write").length()) {
res->writeStatus("200 OK")->writeHeader("write", "true")->write("Hello");
res->write(" world!");
res->end();
} else if (req->getQuery().length()) {
res->close();
} else {
res->end("Hello world!");
}
})/*.post("/*", [](auto *res, auto *req) {
res->onAborted([]() {
});
res->onData([res](std::string_view chunk, bool isEnd) {
if (isEnd) {
res->end(chunk);
}
});
})*/.listen(9001, [](us_listen_socket_t *listenSocket) {
if (listenSocket) {
std::cout << "Listening on port " << 9001 << std::endl;
::listenSocket = listenSocket;
}
}));
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
us_loop_read_mocked_data((struct us_loop *) uWS::Loop::get(), (char *) makePadded(data, size), size);
return 0;
}

View File

@ -0,0 +1,38 @@
/* This is a fuzz test of the permessage-deflate module */
#define WIN32_EXPORT
#include <cstdio>
#include <string>
/* We test the permessage deflate module */
#include "../src/PerMessageDeflate.h"
#include "helpers.h"
struct StaticData {
uWS::ZlibContext zlibContext;
uWS::InflationStream inflationStream;
uWS::DeflationStream deflationStream;
} staticData;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
/* Why is this padded? */
makeChunked(makePadded(data, size), size, [](const uint8_t *data, size_t size) {
std::string_view inflation = staticData.inflationStream.inflate(&staticData.zlibContext, std::string_view((char *) data, size), 256);
if (inflation.length() > 256) {
/* Cause ASAN to freak out */
delete (int *) (void *) 1;
}
});
makeChunked(makePadded(data, size), size, [](const uint8_t *data, size_t size) {
/* Always reset */
staticData.deflationStream.deflate(&staticData.zlibContext, std::string_view((char *) data, size), true);
});
return 0;
}

View File

@ -0,0 +1,29 @@
# Fuzz-testing of various parsers and mocked examples
A secure web server must be capable of receiving mass amount of malicious input without misbehaving or performing illegal actions, such as stepping outside of a memory block or otherwise spilling the beans.
### Continuous fuzzing under various sanitizers is done as part of the [Google OSS-Fuzz](https://github.com/google/oss-fuzz#oss-fuzz---continuous-fuzzing-for-open-source-software) project:
* UndefinedBehaviorSanitizer
* AddressSanitizer
* MemorySanitizer
### Currently the following parts are individually fuzzed:
* WebSocket handshake generator
* WebSocket message parser
* WebSocket extensions parser & negotiator
* WebSocket permessage-deflate compression/inflation helper
* Http parser
* Http method/url router
### While entire (mocked) examples are fuzzed:
* HelloWorld
* EchoServer
No defects or issues are left unfixed, covered up or otherwise neglected. In fact we **cannot** cover up security issues as OSS-Fuzz automatically and publicly reports security issues as they happen.
Currently we are at ~80% total fuzz coverage and OSS-Fuzz is reporting **zero** issues whatsoever. The goal is to approach 90% total coverage.
### Security awards
Google have sent us thousands of USD for the integration with OSS-Fuzz - we continue working on bettering the testing with every new release.

View File

@ -0,0 +1,59 @@
/* This is a fuzz test of the websocket parser */
#define WIN32_EXPORT
#include "helpers.h"
/* We test the websocket parser */
#include "../src/WebSocketProtocol.h"
struct Impl {
static bool refusePayloadLength(uint64_t length, uWS::WebSocketState<true> *wState, void *s) {
/* We need a limit */
if (length > 16000) {
return true;
}
/* Return ok */
return false;
}
static bool setCompressed(uWS::WebSocketState<true> *wState, void *s) {
/* We support it */
return true;
}
static void forceClose(uWS::WebSocketState<true> *wState, void *s) {
}
static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, uWS::WebSocketState<true> *webSocketState, void *s) {
if (opCode == uWS::TEXT) {
if (!uWS::protocol::isValidUtf8((unsigned char *)data, length)) {
/* Return break */
return true;
}
} else if (opCode == uWS::CLOSE) {
uWS::protocol::parseClosePayload((char *)data, length);
}
/* Return ok */
return false;
}
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
/* Create the parser state */
uWS::WebSocketState<true> state;
makeChunked(makePadded(data, size), size, [&state](const uint8_t *data, size_t size) {
/* Parse it */
uWS::WebSocketProtocol<true, Impl>::consume((char *) data, size, &state, nullptr);
});
return 0;
}

View File

@ -0,0 +1,51 @@
#ifndef HELPERS_H
#define HELPERS_H
/* Common helpers for fuzzing */
#include <functional>
#include <string_view>
#include <cstring>
/* We use this to pad the fuzz */
static inline const uint8_t *makePadded(const uint8_t *data, size_t size) {
static int paddedLength = 512 * 1024;
static char *padded = new char[128 + paddedLength + 128];
/* Increase landing area if required */
if (paddedLength < size) {
delete [] padded;
paddedLength = size;
padded = new char [128 + paddedLength + 128];
}
memcpy(padded + 128, data, size);
return (uint8_t *) padded + 128;
}
/* Splits the fuzz data in one or many chunks */
static inline void makeChunked(const uint8_t *data, size_t size, std::function<void(const uint8_t *data, size_t size)> cb) {
/* First byte determines chunk size; 0 is all that remains, 1-255 is small chunk */
for (int i = 0; i < size; ) {
unsigned int chunkSize = data[i++];
if (!chunkSize) {
chunkSize = size - i;
} else {
chunkSize = std::min<int>(chunkSize, size - i);
}
cb(data + i, chunkSize);
i += chunkSize;
}
}
/* Reads all bytes to trigger invalid reads */
static inline void readBytes(std::string_view s) {
volatile int sum = 0;
for (int i = 0; i < s.size(); i++) {
sum += s[i];
}
}
#endif

View File

@ -0,0 +1,285 @@
/* uSockets is entierly opaque so we can use the real header straight up */
#include "../uSockets/src/libusockets.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdalign.h>
#include <string.h>
struct us_loop_t {
/* We only support one listen socket */
alignas(16) struct us_listen_socket_t *listen_socket;
/* The list of closed sockets */
struct us_socket_t *close_list;
};
struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t *loop), void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop), unsigned int ext_size) {
struct us_loop_t *loop = (struct us_loop_t *) malloc(sizeof(struct us_loop_t) + ext_size);
loop->listen_socket = 0;
loop->close_list = 0;
return loop;
}
void us_loop_free(struct us_loop_t *loop) {
free(loop);
}
void *us_loop_ext(struct us_loop_t *loop) {
return loop + 1;
}
void us_loop_run(struct us_loop_t *loop) {
}
struct us_socket_context_t {
alignas(16) struct us_loop_t *loop;
struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length);
struct us_socket_t *(*on_close)(struct us_socket_t *s);
struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length);
struct us_socket_t *(*on_writable)(struct us_socket_t *s);
struct us_socket_t *(*on_timeout)(struct us_socket_t *s);
struct us_socket_t *(*on_end)(struct us_socket_t *s);
};
struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *loop, int ext_size, struct us_socket_context_options_t options) {
struct us_socket_context_t *socket_context = (struct us_socket_context_t *) malloc(sizeof(struct us_socket_context_t) + ext_size);
socket_context->loop = loop;
//printf("us_create_socket_context: %p\n", socket_context);
return socket_context;
}
void us_socket_context_free(int ssl, struct us_socket_context_t *context) {
//printf("us_socket_context_free: %p\n", context);
free(context);
}
void us_socket_context_on_open(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length)) {
context->on_open = on_open;
}
void us_socket_context_on_close(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_close)(struct us_socket_t *s)) {
context->on_close = on_close;
}
void us_socket_context_on_data(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length)) {
context->on_data = on_data;
}
void us_socket_context_on_writable(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_writable)(struct us_socket_t *s)) {
context->on_writable = on_writable;
}
void us_socket_context_on_timeout(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_timeout)(struct us_socket_t *s)) {
context->on_timeout = on_timeout;
}
void us_socket_context_on_end(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_end)(struct us_socket_t *s)) {
context->on_end = on_end;
}
void *us_socket_context_ext(int ssl, struct us_socket_context_t *context) {
return context + 1;
}
struct us_listen_socket_t {
int socket_ext_size;
struct us_socket_context_t *context;
};
struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size) {
struct us_listen_socket_t *listen_socket = (struct us_listen_socket_t *) malloc(sizeof(struct us_listen_socket_t));
listen_socket->socket_ext_size = socket_ext_size;
listen_socket->context = context;
context->loop->listen_socket = listen_socket;
return listen_socket;
}
void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls) {
free(ls);
}
struct us_socket_t {
alignas(16) struct us_socket_context_t *context;
int closed;
int shutdown;
int wants_writable;
//struct us_socket_t *next;
};
struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size) {
//printf("us_socket_context_connect\n");
return 0;
}
struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context) {
return context->loop;
}
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int ext_size) {
struct us_socket_t *new_s = (struct us_socket_t *) realloc(s, sizeof(struct us_socket_t) + ext_size);
new_s->context = context;
return new_s;
}
struct us_socket_context_t *us_create_child_socket_context(int ssl, struct us_socket_context_t *context, int context_ext_size) {
/* We simply create a new context in this mock */
struct us_socket_context_options_t options = {};
struct us_socket_context_t *child_context = us_create_socket_context(ssl, context->loop, context_ext_size, options);
return child_context;
}
int us_socket_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more) {
if (!length) {
return 0;
}
/* Last byte determines if we send everything or not, to stress the buffering mechanism */
if (data[length - 1] % 2 == 0) {
/* Send only half, but first set our outgoing flag */
s->wants_writable = 1;
return length / 2;
}
/* Send everything */
return length;
}
void us_socket_timeout(int ssl, struct us_socket_t *s, unsigned int seconds) {
}
void *us_socket_ext(int ssl, struct us_socket_t *s) {
return s + 1;
}
struct us_socket_context_t *us_socket_context(int ssl, struct us_socket_t *s) {
return s->context;
}
void us_socket_flush(int ssl, struct us_socket_t *s) {
}
void us_socket_shutdown(int ssl, struct us_socket_t *s) {
s->shutdown = 1;
}
int us_socket_is_shut_down(int ssl, struct us_socket_t *s) {
return s->shutdown;
}
int us_socket_is_closed(int ssl, struct us_socket_t *s) {
return s->closed;
}
struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s) {
if (!us_socket_is_closed(0, s)) {
/* Emit close event */
s = s->context->on_close(s);
}
/* We are now closed */
s->closed = 1;
/* Add us to the close list */
return s;
}
void us_socket_remote_address(int ssl, struct us_socket_t *s, char *buf, int *length) {
printf("us_socket_remote_address\n");
}
/* We expose this function to let fuzz targets push data to uSockets */
void us_loop_read_mocked_data(struct us_loop_t *loop, char *data, unsigned int size) {
/* We are unwound so let's free all closed polls here */
/* We have one listen socket */
int socket_ext_size = loop->listen_socket->socket_ext_size;
/* Create a socket with information from the listen socket */
struct us_socket_t *s = (struct us_socket_t *) malloc(sizeof(struct us_socket_t) + socket_ext_size);
s->context = loop->listen_socket->context;
s->closed = 0;
s->shutdown = 0;
s->wants_writable = 0;
/* Emit open event */
s = s->context->on_open(s, 0, 0, 0);
if (!us_socket_is_closed(0, s) && !us_socket_is_shut_down(0, s)) {
/* Trigger writable event if we want it */
if (s->wants_writable) {
s->wants_writable = 0;
s = s->context->on_writable(s);
/* Check if we closed inside of writable */
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
goto done;
}
}
/* Loop over the data, emitting it in chunks of 0-255 bytes */
for (int i = 0; i < size; ) {
unsigned char chunkLength = data[i++];
if (i + chunkLength > size) {
chunkLength = size - i;
}
/* Copy the data chunk to a properly padded buffer */
static char *paddedBuffer;
if (!paddedBuffer) {
paddedBuffer = malloc(128 + 255 + 128);
memset(paddedBuffer, 0, 128 + 255 + 128);
}
memcpy(paddedBuffer + 128, data + i, chunkLength);
/* Emit a bunch of data events here */
s = s->context->on_data(s, paddedBuffer + 128, chunkLength);
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
break;
}
/* Also trigger it here */
if (s->wants_writable) {
s->wants_writable = 0;
s = s->context->on_writable(s);
/* Check if we closed inside of writable */
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
goto done;
}
}
i += chunkLength;
}
}
done:
if (!us_socket_is_closed(0, s)) {
/* Emit close event */
s = s->context->on_close(s);
}
/* Free the socket */
free(s);
}

View File

@ -0,0 +1,41 @@
TEMPLATE = app
CONFIG += console c++1z
CONFIG -= app_bundle
CONFIG -= qt
SOURCES += \
main.cpp \
../uSockets/src/eventing/epoll.c \
../uSockets/src/context.c \
../uSockets/src/socket.c \
../uSockets/src/eventing/libuv.c \
../uSockets/src/ssl.c \
../uSockets/src/loop.c
HEADERS += \
../src/HttpRouter.h \
../src/HttpParser.h \
../src/libwshandshake.hpp \
../src/WebSocketProtocol.h \
../src/HttpContext.h \
../src/HttpContextData.h \
../src/HttpResponseData.h \
../src/HttpResponse.h \
../src/LoopData.h \
../src/AsyncSocket.h \
../src/AsyncSocketData.h \
../src/Loop.h \
../src/App.h \
../src/Utilities.h \
../src/WebSocket.h \
../src/WebSocketData.h \
../src/WebSocketContext.h \
../src/WebSocketContextData.h \
../src/WebSocketExtensions.h \
../src/PerMessageDeflate.h \
../src/TopicTree.h
INCLUDEPATH += ../uSockets/src ../src
QMAKE_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter -Wno-unused-but-set-parameter -fsanitize=address
QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-variable
LIBS += -lasan -pthread -lssl -lcrypto -lz -lstdc++fs

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,19 @@
## Build systems
Q:
```
Hi Alex,
I've used your library and I like it, however, to integrate it in my process it needs to use cmake and conan. I have written those for my own project, would you like me to submit a pull request?
Regards,
Ioannis
```
A:
```
Hi Ioannis,
This is a very common request. I don't accept any specific build systems because I know from experience that doing so opens up hell. People simply cannot agree on which build system to use, or even how to use one particular build system.
That's why I no longer accept any such PRs. I've had too many CMake PRs dragging in completely different directions to know this is the only solution.
You'll have to clone the repo and create a new project in whatever build system you want to use.
```

View File

@ -0,0 +1,205 @@
# µWebSockets v0.17 user manual
For a list of frequently asked questions you may filter the GitHub issue tracker by label FAQ. Please don't misuse the issue tracker as your personal Q&A.
## Motivation and goals
µWebSockets is a simple to use yet thoroughly optimized implementation of HTTP and WebSockets.
It comes with built-in pub/sub support, HTTP routing, TLS 1.3, IPv6, permessage-deflate and is battle tested as one of the most popular implementations, reaching many end-users daily.
Unlike other "pub/sub brokers", µWS does not assume or push any particular protocol but only operates over standard WebSockets.
The implementation is header-only C++17, cross-platform and compiles down to a tiny binary of a handful kilobytes.
It depends on µSockets, which is a standard C project for Linux, macOS & Windows.
Performance wise you can expect to outperform, or equal, just about anything similar out there, that's the fundamental goal of the project.
I can show certain cases where µWS with SSL significantly outperforms Golang servers running non-SSL. You get the SSL for free in a sense, well, in this particular case at least.
Another goal of the project is minimalism, simplicity and elegance.
Design wise it follows an ExpressJS-like interface where you attach callbacks to different URL routes.
This way you can easily build complete REST/WebSocket services in a few lines of code.
The project is async only and runs local to one thread. You scale it as individual threads much like Node.js scales as individual processes. That is, the implementation only sees a single thread and is not thread-safe. There are simple ways to do threading via async delegates though, if you really need to.
## Compiling
µWebSockets is 100% standard header-only C++17 - it compiles on any platform. However, it depends on µSockets in all cases, which is platform-specific C code that runs on Linux, Windows and macOS.
There are a few compilation flags for µSockets (see its documentation), but common between µSockets and µWebSockets flags are as follows:
* LIBUS_NO_SSL - disable OpenSSL dependency/functionality for uSockets and uWebSockets builds
* UWS_NO_ZLIB - disable Zlib dependency/functionality for uWebSockets
## Node.js
V8 addon is developed over at https://github.com/uNetworking/uWebSockets.js.
## User manual
### uWS::App & uWS::SSLApp
You begin your journey by constructing an "App". Either an SSL-app or a regular TCP-only App. The uWS::SSLApp constructor takes a struct holding SSL options such as cert and key. Interfaces for both apps are identical, so let's call them both "App" from now on.
Apps follow the builder pattern, member functions return the App so that you can chain calls.
### App.get, post, put, [...] and any routes
You attach behavior to "URL routes". A lambda is paired with a "method" (Http method that is) and a pattern (the URL matching pattern).
Methods are many, but most common are probably get & post. They all have the same signature, let's look at one example:
```c++
uWS::App().get("/hello", [](auto *res, auto *req) {
res->end("Hello World!");
});
```
Important for all routes is that "req", the `uWS::HttpRequest *` dies with return. In other words, req is stack allocated so don't keep it in your pocket.
res, the `uWS::HttpResponse<SSL> *` will be alive and accessible until either its .onAborted callback emits, or you've responded to the request via res.end or res.tryEnd.
In other words, you either respond to the request immediately and return, or you attach lambdas to the res (which may hold captured data), and respond later on in some other async callback.
Data that you capture in a res follows RAII and is move-only so you can properly move-in for instance std::string buffers that you may use to, for instance, buffer upp streaming POST data. It's pretty cool, check out mutable lambdas with move-only captures.
The "any" route will match any method.
#### Pattern matching
Routes are matched in **order of specificity**, not by the order you register them:
* Highest priority - static routes, think "/hello/this/is/static".
* Middle priority - parameter routes, think "/candy/:kind", where value of :kind is retrieved by req.getParameter(0).
* Lowest priority - wildcard routes, think "/hello/*".
In other words, the more specific a route is, the earlier it will match. This allows you to define wildcard routes that match a wide range of URLs and then "carve" out more specific behavior from that.
"Any" routes, those who match any HTTP method, will match with lower priority than routes which specify their specific HTTP method (such as GET) if and only if the two routes otherwise are equally specific.
#### Streaming data
You should never call res.end(huge buffer). res.end guarantees sending so backpressure will probably spike. Instead you should use res.tryEnd to stream huge data part by part. Use in combination with res.onWritable and res.onAborted callbacks.
Tip: Check out the JavaScript project, it has many useful examples of async streaming of huge data.
#### Corking
It is very important to understand the corking mechanism, as that is responsible for efficiently formatting, packing and sending data. Without corking your app will still work reliably, but can perform very bad and use excessive networking. In some cases the performance can be dreadful without proper corking.
That's why your sockets will be corked by default in most simple cases, including all of the examples provided. However there are cases where default corking cannot happen automatically.
* Whenever your registered business logic (your callbacks) are called from the library, such as when receiving a message or when a socket opens, you'll be corked by default. Whatever you do with the socket inside of that callback will be efficient and properly corked.
* If you have callbacks registered to some other library, say libhiredis, those callbacks will not be called with corked sockets (how could **we** know when to cork the socket if we don't control the third-party library!?).
* Only one single socket can be corked at any point in time (isolated per thread, of course). It is efficient to cork-and-uncork.
* Whenever your callback is a coroutine, such as the JavaScript async/await, automatic corking can only happen in the very first portion of the coroutine (consider await a separator which essentially cuts the coroutine into smaller segments). Only the first "segment" of the coroutine will be called from µWS, the following async segments will be called by the JavaScript runtime at a later point in time and will thus not be under our control with default corking enabled.
* Corking is important even for calls which seem to be "atomic" and only send one chunk. res->end, res->tryEnd, res->writeStatus, res->writeHeader will most likely send multiple chunks of data and is very important to properly cork.
You can make sure corking is enabled, even for cases where default corking would be enabled, by wrapping whatever sending function calls in a lambda like so:
```c++
res->cork([]() {
res->end("This Http response will be properly corked and efficient in all cases");
});
```
The above res->end call will actually call three separate send functions; res->writeStatus, res->writeHeader and whatever it does itself. By wrapping the call in res->cork you make sure these three send functions are efficient and only result in one single send syscall and one single SSL block if using SSL.
Keep this in mind, corking is by far the single most important performance trick to use. Even when streaming huge amounts of data it can be useful to cork. At least in the very tip of the response, as that holds the headers and status.
### The App.ws route
WebSocket "routes" are registered similarly, but not identically.
Every websocket route has the same pattern and pattern matching as for Http, but instead of one single callback you have a whole set of them, here's an example:
```c++
uWS::App().ws<PerSocketData>("/*", {
/* Settings */
.compression = uWS::SHARED_COMPRESSOR,
.maxPayloadLength = 16 * 1024,
.idleTimeout = 10,
/* Handlers */
.open = [](auto *ws, auto *req) {
/* Here you can use req just like as for Http */
},
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
ws->send(message, opCode);
},
.drain = [](auto *ws) {
/* Check getBufferedAmount here */
},
.ping = [](auto *ws) {
},
.pong = [](auto *ws) {
},
.close = [](auto *ws, int code, std::string_view message) {
}
});
```
WebSocket routes specify a user data type that should be used to keep per-websocket data. Many times people tend to attach user data
which should belong to the websocket by putting the pointer and the user data in a std::map. That's wrong! Don't do that!
#### Use the WebSocket.getUserData() feature
You should use the provided user data feature to store and attach any per-socket user data. Going from user data to WebSocket is possible if you make your user data hold a pointer to WebSocket, and hook things up in the WebSocket open handler. Your user data memory is valid while your WebSocket is.
If you want to create something more elaborate you could have the user data hold a pointer to some dynamically allocated memory block that keeps a boolean whether the WebSocket is still valid or not. Sky is the limit here, you should never need any std::map for this.
#### WebSockets are valid from open to close
All given WebSocket pointers are guaranteed to live from open event (where you got your WebSocket) until close event is called. So is the user data memory. One open event will always end in exactly one close event, they are 1-to-1 and will always be balanced no matter what. Use them to drive your RAII data types, they can be seen as constructor and destructor.
Message events will never emit outside of open/close. Calling WebSocket.close or WebSocket.end will immediately call the close handler.
#### Backpressure in websockets
Similarly to for Http, methods such as ws.send(...) can cause backpressure. Make sure to check ws.getBufferedAmount() before sending, and check the return value of ws.send before sending any more data. WebSockets do not have .onWritable, but instead make use of the .drain handler of the websocket route handler.
Inside of .drain event you should check ws.getBufferedAmount(), it might have drained, or even increased. Most likely drained but don't assume that it has, .drain event is only a hint that it has changed.
#### Settings
Compression (permessage-deflate) has three options, uWS::DISABLED, uWS::SHARED_COMPRESSOR and uWS::DEDICATED_COMPRESSOR. Disabled and shared options require no memory, while dedicated compressor requires somewhere close to 300kb per socket, a very significant cost.
Compressing using shared means that every WebSocket message is an isolated compression stream, it does not have a sliding compression window, kept between multiple send calls.
Shared compression is my personal favorite, since it doesn't change memory usage while still provide decent compression, especially for larger messages.
* idleTimeout is roughly the amount of seconds that may pass between messages. Being idle for more than this, and the connection is severed. This means you should make your clients send small ping messages every now and then, to keep the connection alive. You can also make the server send ping messages but I would definitely put that labor on the client side.
### Listening on a port
Once you have defined your routes and their behavior, it is time to start listening for new connections. You do this by calling
```c++
App.listen(port, [](auto *listenSocket) {
/* listenSocket is either nullptr or us_listen_socket */
})
```
Cancelling listenning is done with the uSockets function call `us_listen_socket_close`.
### App.run and fallthrough
When you are done and want to enter the event loop, you call, once and only once, App.run.
This will block the calling thread until "fallthrough". The event loop will block until no more async work is scheduled, just like for Node.js.
Many users ask how they should stop the event loop. That's not how it is done, you never stop it, you let it fall through. By closing all sockets, stopping the listen socket, removing any timers, etc, the loop will automatically cause App.run to return gracefully, with no memory leaks.
Because the App itself is under RAII control, once the blocking .run call returns and the App goes out of scope, all memory will gracefully be deleted.
### Putting it all toghether
```c++
int main() {
uWS::App().get("/*", [](auto *res, auto *req) {
res->end("Hello World!");
}).listen(9001, [](auto *listenSocket) {
if (listenSocket) {
std::cout << "Listening for connections..." << std::endl;
}
}).run();
std::cout << "Shoot! We failed to listen and the App fell through, exiting now!" << std::endl;
}
```
### Scaling up
One event-loop per thread, isolated and without shared data. That's the design here. Just like Node.js, but instead of per-process, it's per thread (well, obviously you can do it per-process also).
If you want to, you can simply take the previous example, put it inside of a few `std::thread` and listen to separate ports, or share the same port (works on Linux). More features like these will probably come, such as master/slave set-ups but it really isn't that hard to understand the concept - keep things isolated and spawn multiple instances of whatever code you have.
Recent Node.js versions may scale using multiple threads, via the new Worker threads support. Scaling using that feature is identical to scaling using multiple threads in C++.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,119 @@
#include "App.h"
//#include "../examples/helpers/AsyncFileReader.h"
//#include "../examples/helpers/AsyncFileStreamer.h"
us_listen_socket *token;
int main(int argc, char **argv) {
struct PerSocketData {
char pad[256];
int hello;
};
auto app = uWS::/*SSL*/App({
.key_file_name = "/home/alexhultman/key.pem",
.cert_file_name = "/home/alexhultman/cert.pem",
.passphrase = "1234"
}).any("/anything", [](auto *res, auto *req) {
std::cout << "Any route with method: " << req->getMethod() << std::endl;
res->end("Hello Any route!");
}).get("/exit", [](auto *res, auto *req) {
if (!token) {
res->end("Server already closed down!");
return;
}
res->end("Closing down server now");
/* Use this route to signal stop listening */
us_listen_socket_close(token);
token = nullptr;
}).ws<PerSocketData>("/*", {
/* Settings */
.compression = uWS::DEDICATED_COMPRESSOR,
.maxPayloadLength = 16 * 1024 * 1024,
.idleTimeout = 10,
/* Handlers */
.open = [](auto *ws, auto *req) {
std::cout << "WebSocket connected" << std::endl;
/* Access per socket data */
PerSocketData *perSocketData = (PerSocketData *) ws->getUserData();
perSocketData->hello = 13;
},
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
ws->send(message, opCode, true);
PerSocketData *perSocketData = (PerSocketData *) ws->getUserData();
std::cout << "OK per socket data: " << (perSocketData->hello == 13) << std::endl;
},
.drain = [](auto *ws) {
std::cout << "Drainage: " << ws->getBufferedAmount() << std::endl;
},
.ping = [](auto *ws) {
std::cout << "Ping" << std::endl;
},
.pong = [](auto *ws) {
std::cout << "Pong" << std::endl;
},
.close = [](auto *ws, int code, std::string_view message) {
std::cout << "WebSocket disconnected: " << code << "[" << message << "]" << std::endl;
/* Access per socket data */
PerSocketData *perSocketData = (PerSocketData *) ws->getUserData();
std::cout << "OK per socket data: " << (perSocketData->hello == 13) << std::endl;
}
}).listen(9001, [](auto *token) {
::token = token;
if (token) {
std::cout << "Listening on port " << 3000 << std::endl;
}
}).run();
std::cout << "Everything fine, falling through" << std::endl;
// return 0;
// AsyncFileStreamer *asyncFileStreamer = new AsyncFileStreamer("/home/alexhultman/v0.15/public");
// uWS::/*SSL*/App(/*{
// .key_file_name = "/home/alexhultman/uWebSockets/misc/ssl/key.pem",
// .cert_file_name = "/home/alexhultman/uWebSockets/misc/ssl/cert.pem",
// .dh_params_file_name = "/home/alexhultman/dhparams.pem",
// .passphrase = "1234"
// }*/)/*.get("/*", [](auto *res, auto *req) {
// res->end("GET /WILDCARD");
// })*/.get("/:param1/:param2", [](auto *res, auto *req) {
// res->write("GET /:param1/:param2 = ");
// res->write(req->getParameter(0));
// res->write(" and ");
// res->end(req->getParameter(1));
// }).post("/hello", [asyncFileStreamer](auto *res, auto *req) {
// // depending on the file type we want to also add mime!
// //asyncFileStreamer->streamFile(res, req->getUrl());
// res->end("POST /hello");
// }).get("/hello", [](auto *res, auto *req) {
// res->end("GET /hello");
// }).unhandled([](auto *res, auto *req) {
// res->writeStatus("404 Not Found");
// res->writeHeader("Content-Type", "text/html; charset=utf-8");
// res->end("<h1>404 Not Found</h1><i>µWebSockets v0.15</i>");
// }).listen(3000, [](auto *token) {
// if (token) {
// std::cout << "Listening on port " << 3000 << std::endl;
// }
// }).run();
}

View File

@ -0,0 +1,94 @@
#include "HttpParser.h"
#include <chrono>
#include <iostream>
// todo: random test of chunked http parsing of randomly generated requests
void testHttpParser() {
char headers[] = "GET /hello.htm HTTP/1.1\r\n"
"User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)\r\n"
"Host: www.tutorialspoint.com\r\n"
"Accept-Language: en-us\r\n"
"Accept-Encoding: gzip, deflate\r\n"
"Connection: Keep-Alive\r\n"
"Content-length: 1048576\r\n\r\n";
const int requestLength = sizeof(headers) - 1 + 1048576;
char *request = (char *) malloc(requestLength + 32);
memset(request, 0, requestLength);
memcpy(request, headers, sizeof(headers) - 1);
char *data = (char *) malloc(requestLength * 10);
int length = requestLength * 10;
int maxChunkSize = 10000;
char *paddedBuffer = (char *) malloc(maxChunkSize + 32);
// dela upp dessa 10 i 5 segment
HttpParser httpParser;
int validRequests = 0, numDataEmits = 0, numChunks = 0;
size_t dataBytes = 0;
for (int i = 0; i < 10; i++) {
memcpy(data + requestLength * i, request, requestLength);
}
for (int j = 0; j < 1000; j++) {
for (int currentOffset = 0; currentOffset != length; ) {
int chunkSize = rand() % 10000;
if (currentOffset + chunkSize > length) {
chunkSize = length - currentOffset;
}
memcpy(paddedBuffer, data + currentOffset, chunkSize);
httpParser.consumePostPadded(paddedBuffer, chunkSize, nullptr, [&validRequests](void *user, HttpRequest *req) {
validRequests++;
if (req->getUrl() != "/hello.htm") {
std::cout << "WRONG URL!" << std::endl;
exit(-1);
}
}, [&dataBytes, &numDataEmits](void *, std::string_view data) {
numDataEmits++;
dataBytes += data.length();
}, [](void *) {
std::cout << "Error!" << std::endl;
return;
});
numChunks++;
currentOffset += chunkSize;
}
}
std::cout << "validRequests: " << validRequests << std::endl;
std::cout << "Data bytes: " << dataBytes << std::endl;
std::cout << "Data emits: " << numDataEmits << std::endl;
std::cout << "Chunks parsed: " << numChunks << std::endl;
validRequests = 0;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000000; i++) {
httpParser.consumePostPadded(request, requestLength, nullptr, [&validRequests](void *user, HttpRequest *req) {
validRequests++;
}, [](void *, std::string_view data) {
}, [](void *) {
});
}
auto stop = std::chrono::high_resolution_clock::now();
std::cout << "Parsed " << validRequests << " in " << std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count() << "ms" << std::endl;
}
int main() {
testHttpParser();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,348 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_APP_H
#define UWS_APP_H
/* An app is a convenience wrapper of some of the most used fuctionalities and allows a
* builder-pattern kind of init. Apps operate on the implicit thread local Loop */
#include "HttpContext.h"
#include "HttpResponse.h"
#include "WebSocketContext.h"
#include "WebSocket.h"
#include "WebSocketExtensions.h"
#include "WebSocketHandshake.h"
namespace uWS {
/* Compress options (really more like PerMessageDeflateOptions) */
enum CompressOptions {
/* Compression disabled */
DISABLED = 0,
/* We compress using a shared non-sliding window. No added memory usage, worse compression. */
SHARED_COMPRESSOR = 1,
/* We compress using a dedicated sliding window. Major memory usage added, better compression of similarly repeated messages. */
DEDICATED_COMPRESSOR = 2
};
template <bool SSL>
struct TemplatedApp {
private:
/* The app always owns at least one http context, but creates websocket contexts on demand */
HttpContext<SSL> *httpContext;
std::vector<WebSocketContext<SSL, true> *> webSocketContexts;
public:
/* Attaches a "filter" function to track socket connections/disconnections */
void filter(fu2::unique_function<void(HttpResponse<SSL> *, int)> &&filterHandler) {
httpContext->filter(std::move(filterHandler));
}
/* Publishes a message to all websocket contexts */
void publish(std::string_view topic, std::string_view message, OpCode opCode, bool compress = false) {
for (auto *webSocketContext : webSocketContexts) {
webSocketContext->getExt()->publish(topic, message, opCode, compress);
}
}
~TemplatedApp() {
/* Let's just put everything here */
if (httpContext) {
httpContext->free();
for (auto *webSocketContext : webSocketContexts) {
webSocketContext->free();
}
}
}
/* Disallow copying, only move */
TemplatedApp(const TemplatedApp &other) = delete;
TemplatedApp(TemplatedApp &&other) {
/* Move HttpContext */
httpContext = other.httpContext;
other.httpContext = nullptr;
/* Move webSocketContexts */
webSocketContexts = std::move(other.webSocketContexts);
}
TemplatedApp(us_socket_context_options_t options = {}) {
httpContext = uWS::HttpContext<SSL>::create(uWS::Loop::get(), options);
}
bool constructorFailed() {
return !httpContext;
}
struct WebSocketBehavior {
CompressOptions compression = DISABLED;
int maxPayloadLength = 16 * 1024;
int idleTimeout = 120;
int maxBackpressure = 1 * 1024 * 1204;
fu2::unique_function<void(uWS::WebSocket<SSL, true> *, HttpRequest *)> open = nullptr;
fu2::unique_function<void(uWS::WebSocket<SSL, true> *, std::string_view, uWS::OpCode)> message = nullptr;
fu2::unique_function<void(uWS::WebSocket<SSL, true> *)> drain = nullptr;
fu2::unique_function<void(uWS::WebSocket<SSL, true> *)> ping = nullptr;
fu2::unique_function<void(uWS::WebSocket<SSL, true> *)> pong = nullptr;
fu2::unique_function<void(uWS::WebSocket<SSL, true> *, int, std::string_view)> close = nullptr;
};
template <typename UserData>
TemplatedApp &&ws(std::string pattern, WebSocketBehavior &&behavior) {
/* Don't compile if alignment rules cannot be satisfied */
static_assert(alignof(UserData) <= LIBUS_EXT_ALIGNMENT,
"µWebSockets cannot satisfy UserData alignment requirements. You need to recompile µSockets with LIBUS_EXT_ALIGNMENT adjusted accordingly.");
/* Every route has its own websocket context with its own behavior and user data type */
auto *webSocketContext = WebSocketContext<SSL, true>::create(Loop::get(), (us_socket_context_t *) httpContext);
/* We need to clear this later on */
webSocketContexts.push_back(webSocketContext);
/* Quick fix to disable any compression if set */
#ifdef UWS_NO_ZLIB
behavior.compression = uWS::DISABLED;
#endif
/* If we are the first one to use compression, initialize it */
if (behavior.compression) {
LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(SSL, webSocketContext->getSocketContext()));
/* Initialize loop's deflate inflate streams */
if (!loopData->zlibContext) {
loopData->zlibContext = new ZlibContext;
loopData->inflationStream = new InflationStream;
loopData->deflationStream = new DeflationStream;
}
}
/* Copy all handlers */
webSocketContext->getExt()->messageHandler = std::move(behavior.message);
webSocketContext->getExt()->drainHandler = std::move(behavior.drain);
webSocketContext->getExt()->closeHandler = std::move([closeHandler = std::move(behavior.close)](WebSocket<SSL, true> *ws, int code, std::string_view message) mutable {
closeHandler(ws, code, message);
/* Destruct user data after returning from close handler */
((UserData *) ws->getUserData())->~UserData();
});
/* Copy settings */
webSocketContext->getExt()->maxPayloadLength = behavior.maxPayloadLength;
webSocketContext->getExt()->idleTimeout = behavior.idleTimeout;
webSocketContext->getExt()->maxBackpressure = behavior.maxBackpressure;
httpContext->onHttp("get", pattern, [webSocketContext, httpContext = this->httpContext, behavior = std::move(behavior)](auto *res, auto *req) mutable {
/* If we have this header set, it's a websocket */
std::string_view secWebSocketKey = req->getHeader("sec-websocket-key");
if (secWebSocketKey.length() == 24) {
/* Note: OpenSSL can be used here to speed this up somewhat */
char secWebSocketAccept[29] = {};
WebSocketHandshake::generate(secWebSocketKey.data(), secWebSocketAccept);
res->writeStatus("101 Switching Protocols")
->writeHeader("Upgrade", "websocket")
->writeHeader("Connection", "Upgrade")
->writeHeader("Sec-WebSocket-Accept", secWebSocketAccept);
/* Select first subprotocol if present */
std::string_view secWebSocketProtocol = req->getHeader("sec-websocket-protocol");
if (secWebSocketProtocol.length()) {
res->writeHeader("Sec-WebSocket-Protocol", secWebSocketProtocol.substr(0, secWebSocketProtocol.find(',')));
}
/* Negotiate compression */
bool perMessageDeflate = false;
bool slidingDeflateWindow = false;
if (behavior.compression != DISABLED) {
std::string_view extensions = req->getHeader("sec-websocket-extensions");
if (extensions.length()) {
/* We never support client context takeover (the client cannot compress with a sliding window). */
int wantedOptions = PERMESSAGE_DEFLATE | CLIENT_NO_CONTEXT_TAKEOVER;
/* Shared compressor is the default */
if (behavior.compression == SHARED_COMPRESSOR) {
/* Disable per-socket compressor */
wantedOptions |= SERVER_NO_CONTEXT_TAKEOVER;
}
/* isServer = true */
ExtensionsNegotiator<true> extensionsNegotiator(wantedOptions);
extensionsNegotiator.readOffer(extensions);
/* Todo: remove these mid string copies */
std::string offer = extensionsNegotiator.generateOffer();
if (offer.length()) {
res->writeHeader("Sec-WebSocket-Extensions", offer);
}
/* Did we negotiate permessage-deflate? */
if (extensionsNegotiator.getNegotiatedOptions() & PERMESSAGE_DEFLATE) {
perMessageDeflate = true;
}
/* Is the server allowed to compress with a sliding window? */
if (!(extensionsNegotiator.getNegotiatedOptions() & SERVER_NO_CONTEXT_TAKEOVER)) {
slidingDeflateWindow = true;
}
}
}
/* This will add our mark */
res->upgrade();
/* Move any backpressure */
std::string backpressure(std::move(((AsyncSocketData<SSL> *) res->getHttpResponseData())->buffer));
/* Keep any fallback buffer alive until we returned from open event, keeping req valid */
std::string fallback(std::move(res->getHttpResponseData()->salvageFallbackBuffer()));
/* Destroy HttpResponseData */
res->getHttpResponseData()->~HttpResponseData();
/* Adopting a socket invalidates it, do not rely on it directly to carry any data */
WebSocket<SSL, true> *webSocket = (WebSocket<SSL, true> *) us_socket_context_adopt_socket(SSL,
(us_socket_context_t *) webSocketContext, (us_socket_t *) res, sizeof(WebSocketData) + sizeof(UserData));
/* Update corked socket in case we got a new one (assuming we always are corked in handlers). */
webSocket->AsyncSocket<SSL>::cork();
/* Initialize websocket with any moved backpressure intact */
httpContext->upgradeToWebSocket(
webSocket->init(perMessageDeflate, slidingDeflateWindow, std::move(backpressure))
);
/* Emit open event and start the timeout */
if (behavior.open) {
us_socket_timeout(SSL, (us_socket_t *) webSocket, behavior.idleTimeout);
/* Default construct the UserData right before calling open handler */
new (webSocket->getUserData()) UserData;
behavior.open(webSocket, req);
}
/* We are going to get uncorked by the Http get return */
/* We do not need to check for any close or shutdown here as we immediately return from get handler */
} else {
/* Tell the router that we did not handle this request */
req->setYield(true);
}
}, true);
return std::move(*this);
}
TemplatedApp &&get(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("get", pattern, std::move(handler));
return std::move(*this);
}
TemplatedApp &&post(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("post", pattern, std::move(handler));
return std::move(*this);
}
TemplatedApp &&options(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("options", pattern, std::move(handler));
return std::move(*this);
}
TemplatedApp &&del(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("delete", pattern, std::move(handler));
return std::move(*this);
}
TemplatedApp &&patch(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("patch", pattern, std::move(handler));
return std::move(*this);
}
TemplatedApp &&put(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("put", pattern, std::move(handler));
return std::move(*this);
}
TemplatedApp &&head(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("head", pattern, std::move(handler));
return std::move(*this);
}
TemplatedApp &&connect(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("connect", pattern, std::move(handler));
return std::move(*this);
}
TemplatedApp &&trace(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("trace", pattern, std::move(handler));
return std::move(*this);
}
/* This one catches any method */
TemplatedApp &&any(std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
httpContext->onHttp("*", pattern, std::move(handler));
return std::move(*this);
}
/* Host, port, callback */
TemplatedApp &&listen(std::string host, int port, fu2::unique_function<void(us_listen_socket_t *)> &&handler) {
if (!host.length()) {
return listen(port, std::move(handler));
}
handler(httpContext->listen(host.c_str(), port, 0));
return std::move(*this);
}
/* Host, port, options, callback */
TemplatedApp &&listen(std::string host, int port, int options, fu2::unique_function<void(us_listen_socket_t *)> &&handler) {
if (!host.length()) {
return listen(port, options, std::move(handler));
}
handler(httpContext->listen(host.c_str(), port, options));
return std::move(*this);
}
/* Port, callback */
TemplatedApp &&listen(int port, fu2::unique_function<void(us_listen_socket_t *)> &&handler) {
handler(httpContext->listen(nullptr, port, 0));
return std::move(*this);
}
/* Port, options, callback */
TemplatedApp &&listen(int port, int options, fu2::unique_function<void(us_listen_socket_t *)> &&handler) {
handler(httpContext->listen(nullptr, port, options));
return std::move(*this);
}
TemplatedApp &&run() {
uWS::run();
return std::move(*this);
}
};
typedef TemplatedApp<false> App;
typedef TemplatedApp<true> SSLApp;
}
#endif // UWS_APP_H

View File

@ -0,0 +1,228 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_ASYNCSOCKET_H
#define UWS_ASYNCSOCKET_H
/* This class implements async socket memory management strategies */
#include "LoopData.h"
#include "AsyncSocketData.h"
namespace uWS {
template <bool, bool> struct WebSocketContext;
template <bool SSL>
struct AsyncSocket {
template <bool> friend struct HttpContext;
template <bool, bool> friend struct WebSocketContext;
template <bool> friend struct WebSocketContextData;
friend struct TopicTree;
protected:
/* Get loop data for socket */
LoopData *getLoopData() {
return (LoopData *) us_loop_ext(us_socket_context_loop(SSL, us_socket_context(SSL, (us_socket_t *) this)));
}
/* Get socket extension */
AsyncSocketData<SSL> *getAsyncSocketData() {
return (AsyncSocketData<SSL> *) us_socket_ext(SSL, (us_socket_t *) this);
}
/* Socket timeout */
void timeout(unsigned int seconds) {
us_socket_timeout(SSL, (us_socket_t *) this, seconds);
}
/* Shutdown socket without any automatic drainage */
void shutdown() {
us_socket_shutdown(SSL, (us_socket_t *) this);
}
/* Immediately close socket */
us_socket_t *close() {
return us_socket_close(SSL, (us_socket_t *) this);
}
/* Cork this socket. Only one socket may ever be corked per-loop at any given time */
void cork() {
/* What if another socket is corked? */
getLoopData()->corkedSocket = this;
}
/* Returns wheter we are corked or not */
bool isCorked() {
return getLoopData()->corkedSocket == this;
}
/* Returns whether we could cork (it is free) */
bool canCork() {
return getLoopData()->corkedSocket == nullptr;
}
/* Returns a suitable buffer for temporary assemblation of send data */
std::pair<char *, bool> getSendBuffer(size_t size) {
/* If we are corked and we have room, return the cork buffer itself */
LoopData *loopData = getLoopData();
if (loopData->corkedSocket == this && loopData->corkOffset + size < LoopData::CORK_BUFFER_SIZE) {
char *sendBuffer = loopData->corkBuffer + loopData->corkOffset;
loopData->corkOffset += (int) size;
return {sendBuffer, false};
} else {
/* Slow path for now, we want to always be corked if possible */
return {(char *) malloc(size), true};
}
}
/* Returns the user space backpressure. */
int getBufferedAmount() {
return (int) getAsyncSocketData()->buffer.size();
}
/* Returns the remote IP address or empty string on failure */
std::string_view getRemoteAddress() {
static thread_local char buf[16];
int ipLength = 16;
us_socket_remote_address(SSL, (us_socket_t *) this, buf, &ipLength);
return std::string_view(buf, ipLength);
}
/* Write in three levels of prioritization: cork-buffer, syscall, socket-buffer. Always drain if possible.
* Returns pair of bytes written (anywhere) and wheter or not this call resulted in the polling for
* writable (or we are in a state that implies polling for writable). */
std::pair<int, bool> write(const char *src, int length, bool optionally = false, int nextLength = 0) {
/* Fake success if closed, simple fix to allow uncork of closed socket to succeed */
if (us_socket_is_closed(SSL, (us_socket_t *) this)) {
return {length, false};
}
LoopData *loopData = getLoopData();
AsyncSocketData<SSL> *asyncSocketData = getAsyncSocketData();
/* We are limited if we have a per-socket buffer */
if (asyncSocketData->buffer.length()) {
/* Write off as much as we can */
int written = us_socket_write(SSL, (us_socket_t *) this, asyncSocketData->buffer.data(), (int) asyncSocketData->buffer.length(), /*nextLength != 0 | */length);
/* On failure return, otherwise continue down the function */
if ((unsigned int) written < asyncSocketData->buffer.length()) {
/* Update buffering (todo: we can do better here if we keep track of what happens to this guy later on) */
asyncSocketData->buffer = asyncSocketData->buffer.substr(written);
if (optionally) {
/* Thankfully we can exit early here */
return {0, true};
} else {
/* This path is horrible and points towards erroneous usage */
asyncSocketData->buffer.append(src, length);
return {length, true};
}
}
/* At this point we simply have no buffer and can continue as normal */
asyncSocketData->buffer.clear();
}
if (length) {
if (loopData->corkedSocket == this) {
/* We are corked */
if (LoopData::CORK_BUFFER_SIZE - loopData->corkOffset >= length) {
/* If the entire chunk fits in cork buffer */
memcpy(loopData->corkBuffer + loopData->corkOffset, src, length);
loopData->corkOffset += length;
/* Fall through to default return */
} else {
/* Strategy differences between SSL and non-SSL regarding syscall minimizing */
if constexpr (SSL) {
/* Cork up as much as we can */
int stripped = LoopData::CORK_BUFFER_SIZE - loopData->corkOffset;
memcpy(loopData->corkBuffer + loopData->corkOffset, src, stripped);
loopData->corkOffset = LoopData::CORK_BUFFER_SIZE;
auto [written, failed] = uncork(src + stripped, length - stripped, optionally);
return {written + stripped, failed};
}
/* For non-SSL we take the penalty of two syscalls */
return uncork(src, length, optionally);
}
} else {
/* We are not corked */
int written = us_socket_write(SSL, (us_socket_t *) this, src, length, nextLength != 0);
/* Did we fail? */
if (written < length) {
/* If the write was optional then just bail out */
if (optionally) {
return {written, true};
}
/* Fall back to worst possible case (should be very rare for HTTP) */
/* At least we can reserve room for next chunk if we know it up front */
if (nextLength) {
asyncSocketData->buffer.reserve(asyncSocketData->buffer.length() + length - written + nextLength);
}
/* Buffer this chunk */
asyncSocketData->buffer.append(src + written, length - written);
/* Return the failure */
return {length, true};
}
/* Fall through to default return */
}
}
/* Default fall through return */
return {length, false};
}
/* Uncork this socket and flush or buffer any corked and/or passed data. It is essential to remember doing this. */
/* It does NOT count bytes written from cork buffer (they are already accounted for in the write call responsible for its corking)! */
std::pair<int, bool> uncork(const char *src = nullptr, int length = 0, bool optionally = false) {
LoopData *loopData = getLoopData();
if (loopData->corkedSocket == this) {
loopData->corkedSocket = nullptr;
if (loopData->corkOffset) {
/* Corked data is already accounted for via its write call */
auto [written, failed] = write(loopData->corkBuffer, loopData->corkOffset, false, length);
loopData->corkOffset = 0;
if (failed) {
/* We do not need to care for buffering here, write does that */
return {0, true};
}
}
/* We should only return with new writes, not things written to cork already */
return write(src, length, optionally, 0);
} else {
/* We are not even corked! */
return {0, false};
}
}
};
}
#endif // UWS_ASYNCSOCKET_H

View File

@ -0,0 +1,43 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_ASYNCSOCKETDATA_H
#define UWS_ASYNCSOCKETDATA_H
#include <string>
namespace uWS {
/* Depending on how we want AsyncSocket to function, this will need to change */
template <bool SSL>
struct AsyncSocketData {
/* This will do for now */
std::string buffer;
/* Allow move constructing us */
AsyncSocketData(std::string &&backpressure) : buffer(std::move(backpressure)) {
}
/* Or emppty */
AsyncSocketData() = default;
};
}
#endif // UWS_ASYNCSOCKETDATA_H

View File

@ -0,0 +1,384 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_HTTPCONTEXT_H
#define UWS_HTTPCONTEXT_H
/* This class defines the main behavior of HTTP and emits various events */
#include "Loop.h"
#include "HttpContextData.h"
#include "HttpResponseData.h"
#include "AsyncSocket.h"
#include <string_view>
#include <iostream>
#include "f2/function2.hpp"
namespace uWS {
template<bool> struct HttpResponse;
template <bool SSL>
struct HttpContext {
template<bool> friend struct TemplatedApp;
private:
HttpContext() = delete;
/* Maximum delay allowed until an HTTP connection is terminated due to outstanding request or rejected data (slow loris protection) */
static const int HTTP_IDLE_TIMEOUT_S = 10;
us_socket_context_t *getSocketContext() {
return (us_socket_context_t *) this;
}
static us_socket_context_t *getSocketContext(us_socket_t *s) {
return (us_socket_context_t *) us_socket_context(SSL, s);
}
HttpContextData<SSL> *getSocketContextData() {
return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext());
}
static HttpContextData<SSL> *getSocketContextDataS(us_socket_t *s) {
return (HttpContextData<SSL> *) us_socket_context_ext(SSL, getSocketContext(s));
}
/* Init the HttpContext by registering libusockets event handlers */
HttpContext<SSL> *init() {
/* Handle socket connections */
us_socket_context_on_open(SSL, getSocketContext(), [](us_socket_t *s, int is_client, char *ip, int ip_length) {
/* Any connected socket should timeout until it has a request */
us_socket_timeout(SSL, s, HTTP_IDLE_TIMEOUT_S);
/* Init socket ext */
new (us_socket_ext(SSL, s)) HttpResponseData<SSL>;
/* Call filter */
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
for (auto &f : httpContextData->filterHandlers) {
f((HttpResponse<SSL> *) s, 1);
}
return s;
});
/* Handle socket disconnections */
us_socket_context_on_close(SSL, getSocketContext(), [](us_socket_t *s) {
/* Get socket ext */
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, s);
/* Call filter */
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
for (auto &f : httpContextData->filterHandlers) {
f((HttpResponse<SSL> *) s, -1);
}
/* Signal broken HTTP request only if we have a pending request */
if (httpResponseData->onAborted) {
httpResponseData->onAborted();
}
/* Destruct socket ext */
httpResponseData->~HttpResponseData<SSL>();
return s;
});
/* Handle HTTP data streams */
us_socket_context_on_data(SSL, getSocketContext(), [](us_socket_t *s, char *data, int length) {
// total overhead is about 210k down to 180k
// ~210k req/sec is the original perf with write in data
// ~200k req/sec is with cork and formatting
// ~190k req/sec is with http parsing
// ~180k - 190k req/sec is with varying routing
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
/* Do not accept any data while in shutdown state */
if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) {
return s;
}
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, s);
/* Cork this socket */
((AsyncSocket<SSL> *) s)->cork();
// clients need to know the cursor after http parse, not servers!
// how far did we read then? we need to know to continue with websocket parsing data? or?
/* The return value is entirely up to us to interpret. The HttpParser only care for whether the returned value is DIFFERENT or not from passed user */
void *returnedSocket = httpResponseData->consumePostPadded(data, length, s, [httpContextData](void *s, uWS::HttpRequest *httpRequest) -> void * {
/* For every request we reset the timeout and hang until user makes action */
/* Warning: if we are in shutdown state, resetting the timer is a security issue! */
us_socket_timeout(SSL, (us_socket_t *) s, 0);
/* Reset httpResponse */
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext(SSL, (us_socket_t *) s);
httpResponseData->offset = 0;
/* Are we not ready for another request yet? Terminate the connection. */
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) {
us_socket_close(SSL, (us_socket_t *) s);
return nullptr;
}
/* Mark pending request and emit it */
httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
/* Route the method and URL */
httpContextData->router.getUserData() = {(HttpResponse<SSL> *) s, httpRequest};
if (!httpContextData->router.route(httpRequest->getMethod(), httpRequest->getUrl())) {
/* We have to force close this socket as we have no handler for it */
us_socket_close(SSL, (us_socket_t *) s);
return nullptr;
}
/* First of all we need to check if this socket was deleted due to upgrade */
if (httpContextData->upgradedWebSocket) {
/* We differ between closed and upgraded below */
return nullptr;
}
/* Was the socket closed? */
if (us_socket_is_closed(SSL, (struct us_socket_t *) s)) {
return nullptr;
}
/* We absolutely have to terminate parsing if shutdown */
if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) {
return nullptr;
}
/* Returning from a request handler without responding or attaching an onAborted handler is ill-use */
if (!((HttpResponse<SSL> *) s)->hasResponded() && !httpResponseData->onAborted) {
/* Throw exception here? */
std::cerr << "Error: Returning from a request handler without responding or attaching an abort handler is forbidden!" << std::endl;
std::terminate();
}
/* If we have not responded and we have a data handler, we need to timeout to enfore client sending the data */
if (!((HttpResponse<SSL> *) s)->hasResponded() && httpResponseData->inStream) {
us_socket_timeout(SSL, (us_socket_t *) s, HTTP_IDLE_TIMEOUT_S);
}
/* Continue parsing */
return s;
}, [httpResponseData](void *user, std::string_view data, bool fin) -> void * {
/* We always get an empty chunk even if there is no data */
if (httpResponseData->inStream) {
/* Todo: can this handle timeout for non-post as well? */
if (fin) {
/* If we just got the last chunk (or empty chunk), disable timeout */
us_socket_timeout(SSL, (struct us_socket_t *) user, 0);
} else {
/* We still have some more data coming in later, so reset timeout */
us_socket_timeout(SSL, (struct us_socket_t *) user, HTTP_IDLE_TIMEOUT_S);
}
/* We might respond in the handler, so do not change timeout after this */
httpResponseData->inStream(data, fin);
/* Was the socket closed? */
if (us_socket_is_closed(SSL, (struct us_socket_t *) user)) {
return nullptr;
}
/* We absolutely have to terminate parsing if shutdown */
if (us_socket_is_shut_down(SSL, (us_socket_t *) user)) {
return nullptr;
}
/* If we were given the last data chunk, reset data handler to ensure following
* requests on the same socket won't trigger any previously registered behavior */
if (fin) {
httpResponseData->inStream = nullptr;
}
}
return user;
}, [](void *user) {
/* Close any socket on HTTP errors */
us_socket_close(SSL, (us_socket_t *) user);
return nullptr;
});
/* We need to uncork in all cases, except for nullptr (closed socket, or upgraded socket) */
if (returnedSocket != nullptr) {
/* Timeout on uncork failure */
auto [written, failed] = ((AsyncSocket<SSL> *) returnedSocket)->uncork();
if (failed) {
/* All Http sockets timeout by this, and this behavior match the one in HttpResponse::cork */
/* Warning: both HTTP_IDLE_TIMEOUT_S and HTTP_TIMEOUT_S are 10 seconds and both are used the same */
((AsyncSocket<SSL> *) s)->timeout(HTTP_IDLE_TIMEOUT_S);
}
return (us_socket_t *) returnedSocket;
}
/* If we upgraded, check here (differ between nullptr close and nullptr upgrade) */
if (httpContextData->upgradedWebSocket) {
/* This path is only for upgraded websockets */
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) httpContextData->upgradedWebSocket;
/* Uncork here as well (note: what if we failed to uncork and we then pub/sub before we even upgraded?) */
/*auto [written, failed] = */asyncSocket->uncork();
/* Reset upgradedWebSocket before we return */
httpContextData->upgradedWebSocket = nullptr;
/* Return the new upgraded websocket */
return (us_socket_t *) asyncSocket;
}
/* It is okay to uncork a closed socket and we need to */
((AsyncSocket<SSL> *) s)->uncork();
/* We cannot return nullptr to the underlying stack in any case */
return s;
});
/* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */
us_socket_context_on_writable(SSL, getSocketContext(), [](us_socket_t *s) {
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) asyncSocket->getAsyncSocketData();
/* Ask the developer to write data and return success (true) or failure (false), OR skip sending anything and return success (true). */
if (httpResponseData->onWritable) {
/* We are now writable, so hang timeout again, the user does not have to do anything so we should hang until end or tryEnd rearms timeout */
us_socket_timeout(SSL, s, 0);
/* We expect the developer to return whether or not write was successful (true).
* If write was never called, the developer should still return true so that we may drain. */
bool success = httpResponseData->onWritable(httpResponseData->offset);
/* The developer indicated that their onWritable failed. */
if (!success) {
/* Skip testing if we can drain anything since that might perform an extra syscall */
return s;
}
/* We don't want to fall through since we don't want to mess with timeout.
* It makes little sense to drain any backpressure when the user has registered onWritable. */
return s;
}
/* Drain any socket buffer, this might empty our backpressure and thus finish the request */
/*auto [written, failed] = */asyncSocket->write(nullptr, 0, true, 0);
/* Expect another writable event, or another request within the timeout */
asyncSocket->timeout(HTTP_IDLE_TIMEOUT_S);
return s;
});
/* Handle FIN, HTTP does not support half-closed sockets, so simply close */
us_socket_context_on_end(SSL, getSocketContext(), [](us_socket_t *s) {
/* We do not care for half closed sockets */
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
return asyncSocket->close();
});
/* Handle socket timeouts, simply close them so to not confuse client with FIN */
us_socket_context_on_timeout(SSL, getSocketContext(), [](us_socket_t *s) {
/* Force close rather than gracefully shutdown and risk confusing the client with a complete download */
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
return asyncSocket->close();
});
return this;
}
/* Used by App in its WebSocket handler */
void upgradeToWebSocket(void *newSocket) {
HttpContextData<SSL> *httpContextData = getSocketContextData();
httpContextData->upgradedWebSocket = newSocket;
}
public:
/* Construct a new HttpContext using specified loop */
static HttpContext *create(Loop *loop, us_socket_context_options_t options = {}) {
HttpContext *httpContext;
httpContext = (HttpContext *) us_create_socket_context(SSL, (us_loop_t *) loop, sizeof(HttpContextData<SSL>), options);
if (!httpContext) {
return nullptr;
}
/* Init socket context data */
new ((HttpContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *) httpContext)) HttpContextData<SSL>();
return httpContext->init();
}
/* Destruct the HttpContext, it does not follow RAII */
void free() {
/* Destruct socket context data */
HttpContextData<SSL> *httpContextData = getSocketContextData();
httpContextData->~HttpContextData<SSL>();
/* Free the socket context in whole */
us_socket_context_free(SSL, getSocketContext());
}
void filter(fu2::unique_function<void(HttpResponse<SSL> *, int)> &&filterHandler) {
getSocketContextData()->filterHandlers.emplace_back(std::move(filterHandler));
}
/* Register an HTTP route handler acording to URL pattern */
void onHttp(std::string method, std::string pattern, fu2::unique_function<void(HttpResponse<SSL> *, HttpRequest *)> &&handler, bool upgrade = false) {
HttpContextData<SSL> *httpContextData = getSocketContextData();
/* Todo: This is ugly, fix */
std::vector<std::string> methods;
if (method == "*") {
methods = httpContextData->router.methods;
} else {
methods = {method};
}
httpContextData->router.add(methods, pattern, [handler = std::move(handler)](auto *r) mutable {
auto user = r->getUserData();
user.httpRequest->setYield(false);
user.httpRequest->setParameters(r->getParameters());
handler(user.httpResponse, user.httpRequest);
/* If any handler yielded, the router will keep looking for a suitable handler. */
if (user.httpRequest->getYield()) {
return false;
}
return true;
}, method == "*" ? httpContextData->router.LOW_PRIORITY : (upgrade ? httpContextData->router.HIGH_PRIORITY : httpContextData->router.MEDIUM_PRIORITY));
}
/* Listen to port using this HttpContext */
us_listen_socket_t *listen(const char *host, int port, int options) {
return us_socket_context_listen(SSL, getSocketContext(), host, port, options, sizeof(HttpResponseData<SSL>));
}
};
}
#endif // UWS_HTTPCONTEXT_H

View File

@ -0,0 +1,48 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_HTTPCONTEXTDATA_H
#define UWS_HTTPCONTEXTDATA_H
#include "HttpRouter.h"
#include <vector>
#include "f2/function2.hpp"
namespace uWS {
template<bool> struct HttpResponse;
struct HttpRequest;
template <bool SSL>
struct alignas(16) HttpContextData {
template <bool> friend struct HttpContext;
template <bool> friend struct HttpResponse;
private:
std::vector<fu2::unique_function<void(HttpResponse<SSL> *, int)>> filterHandlers;
struct RouterData {
HttpResponse<SSL> *httpResponse;
HttpRequest *httpRequest;
};
HttpRouter<RouterData> router;
void *upgradedWebSocket = nullptr;
};
}
#endif // UWS_HTTPCONTEXTDATA_H

View File

@ -0,0 +1,334 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_HTTPPARSER_H
#define UWS_HTTPPARSER_H
// todo: HttpParser is in need of a few clean-ups and refactorings
/* The HTTP parser is an independent module subject to unit testing / fuzz testing */
#include <string>
#include <cstring>
#include <algorithm>
#include "f2/function2.hpp"
namespace uWS {
/* We require at least this much post padding */
static const int MINIMUM_HTTP_POST_PADDING = 32;
struct HttpRequest {
friend struct HttpParser;
private:
const static int MAX_HEADERS = 50;
struct Header {
std::string_view key, value;
} headers[MAX_HEADERS];
int querySeparator;
bool didYield;
std::pair<int, std::string_view *> currentParameters;
public:
bool getYield() {
return didYield;
}
/* Iteration over headers (key, value) */
struct HeaderIterator {
Header *ptr;
bool operator!=(const HeaderIterator &other) const {
/* Comparison with end is a special case */
if (ptr != other.ptr) {
return other.ptr || ptr->key.length();
}
return false;
}
HeaderIterator &operator++() {
ptr++;
return *this;
}
std::pair<std::string_view, std::string_view> operator*() const {
return {ptr->key, ptr->value};
}
};
HeaderIterator begin() {
return {headers + 1};
}
HeaderIterator end() {
return {nullptr};
}
/* If you do not want to handle this route */
void setYield(bool yield) {
didYield = yield;
}
std::string_view getHeader(std::string_view lowerCasedHeader) {
for (Header *h = headers; (++h)->key.length(); ) {
if (h->key.length() == lowerCasedHeader.length() && !strncmp(h->key.data(), lowerCasedHeader.data(), lowerCasedHeader.length())) {
return h->value;
}
}
return std::string_view(nullptr, 0);
}
std::string_view getUrl() {
return std::string_view(headers->value.data(), querySeparator);
}
std::string_view getMethod() {
return std::string_view(headers->key.data(), headers->key.length());
}
std::string_view getQuery() {
if (querySeparator < (int) headers->value.length()) {
/* Strip the initial ? */
return std::string_view(headers->value.data() + querySeparator + 1, headers->value.length() - querySeparator - 1);
} else {
return std::string_view(nullptr, 0);
}
}
void setParameters(std::pair<int, std::string_view *> parameters) {
currentParameters = parameters;
}
std::string_view getParameter(unsigned int index) {
if (currentParameters.first < (int) index) {
return {};
} else {
return currentParameters.second[index];
}
}
};
struct HttpParser {
private:
std::string fallback;
unsigned int remainingStreamingBytes = 0;
const size_t MAX_FALLBACK_SIZE = 1024 * 4;
static unsigned int toUnsignedInteger(std::string_view str) {
unsigned int unsignedIntegerValue = 0;
for (unsigned char c : str) {
unsignedIntegerValue = unsignedIntegerValue * 10 + (c - '0');
}
return unsignedIntegerValue;
}
static unsigned int getHeaders(char *postPaddedBuffer, char *end, struct HttpRequest::Header *headers) {
char *preliminaryKey, *preliminaryValue, *start = postPaddedBuffer;
for (unsigned int i = 0; i < HttpRequest::MAX_HEADERS; i++) {
for (preliminaryKey = postPaddedBuffer; (*postPaddedBuffer != ':') & (*postPaddedBuffer > 32); *(postPaddedBuffer++) |= 32);
if (*postPaddedBuffer == '\r') {
if ((postPaddedBuffer != end) & (postPaddedBuffer[1] == '\n') & (i > 0)) {
headers->key = std::string_view(nullptr, 0);
return (unsigned int) ((postPaddedBuffer + 2) - start);
} else {
return 0;
}
} else {
headers->key = std::string_view(preliminaryKey, (size_t) (postPaddedBuffer - preliminaryKey));
for (postPaddedBuffer++; (*postPaddedBuffer == ':' || *postPaddedBuffer < 33) && *postPaddedBuffer != '\r'; postPaddedBuffer++);
preliminaryValue = postPaddedBuffer;
postPaddedBuffer = (char *) memchr(postPaddedBuffer, '\r', end - postPaddedBuffer);
if (postPaddedBuffer && postPaddedBuffer[1] == '\n') {
headers->value = std::string_view(preliminaryValue, (size_t) (postPaddedBuffer - preliminaryValue));
postPaddedBuffer += 2;
headers++;
} else {
return 0;
}
}
}
return 0;
}
// the only caller of getHeaders
template <int CONSUME_MINIMALLY>
std::pair<int, void *> fenceAndConsumePostPadded(char *data, int length, void *user, HttpRequest *req, fu2::unique_function<void *(void *, HttpRequest *)> &requestHandler, fu2::unique_function<void *(void *, std::string_view, bool)> &dataHandler) {
int consumedTotal = 0;
data[length] = '\r';
for (int consumed; length && (consumed = getHeaders(data, data + length, req->headers)); ) {
data += consumed;
length -= consumed;
consumedTotal += consumed;
req->headers->value = std::string_view(req->headers->value.data(), std::max<int>(0, (int) req->headers->value.length() - 9));
/* Parse query */
const char *querySeparatorPtr = (const char *) memchr(req->headers->value.data(), '?', req->headers->value.length());
req->querySeparator = (int) ((querySeparatorPtr ? querySeparatorPtr : req->headers->value.data() + req->headers->value.length()) - req->headers->value.data());
/* If returned socket is not what we put in we need
* to break here as we either have upgraded to
* WebSockets or otherwise closed the socket. */
void *returnedUser = requestHandler(user, req);
if (returnedUser != user) {
/* We are upgraded to WebSocket or otherwise broken */
return {consumedTotal, returnedUser};
}
// todo: do not check this for GET (get should not have a body)
// todo: also support reading chunked streams
std::string_view contentLengthString = req->getHeader("content-length");
if (contentLengthString.length()) {
remainingStreamingBytes = toUnsignedInteger(contentLengthString);
if (!CONSUME_MINIMALLY) {
unsigned int emittable = std::min<unsigned int>(remainingStreamingBytes, length);
dataHandler(user, std::string_view(data, emittable), emittable == remainingStreamingBytes);
remainingStreamingBytes -= emittable;
data += emittable;
length -= emittable;
consumedTotal += emittable;
}
} else {
/* Still emit an empty data chunk to signal no data */
dataHandler(user, {}, true);
}
if (CONSUME_MINIMALLY) {
break;
}
}
return {consumedTotal, user};
}
public:
/* We do this to prolong the validity of parsed headers by keeping only the fallback buffer alive */
std::string &&salvageFallbackBuffer() {
return std::move(fallback);
}
void *consumePostPadded(char *data, int length, void *user, fu2::unique_function<void *(void *, HttpRequest *)> &&requestHandler, fu2::unique_function<void *(void *, std::string_view, bool)> &&dataHandler, fu2::unique_function<void *(void *)> &&errorHandler) {
HttpRequest req;
if (remainingStreamingBytes) {
// this is exactly the same as below!
// todo: refactor this
if (remainingStreamingBytes >= (unsigned int) length) {
void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == (unsigned int) length);
remainingStreamingBytes -= length;
return returnedUser;
} else {
void *returnedUser = dataHandler(user, std::string_view(data, remainingStreamingBytes), true);
data += remainingStreamingBytes;
length -= remainingStreamingBytes;
remainingStreamingBytes = 0;
if (returnedUser != user) {
return returnedUser;
}
}
} else if (fallback.length()) {
int had = (int) fallback.length();
int maxCopyDistance = (int) std::min(MAX_FALLBACK_SIZE - fallback.length(), (size_t) length);
/* We don't want fallback to be short string optimized, since we want to move it */
fallback.reserve(fallback.length() + maxCopyDistance + std::max<int>(MINIMUM_HTTP_POST_PADDING, sizeof(std::string)));
fallback.append(data, maxCopyDistance);
// break here on break
std::pair<int, void *> consumed = fenceAndConsumePostPadded<true>(fallback.data(), (int) fallback.length(), user, &req, requestHandler, dataHandler);
if (consumed.second != user) {
return consumed.second;
}
if (consumed.first) {
fallback.clear();
data += consumed.first - had;
length -= consumed.first - had;
if (remainingStreamingBytes) {
// this is exactly the same as above!
if (remainingStreamingBytes >= (unsigned int) length) {
void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == (unsigned int) length);
remainingStreamingBytes -= length;
return returnedUser;
} else {
void *returnedUser = dataHandler(user, std::string_view(data, remainingStreamingBytes), true);
data += remainingStreamingBytes;
length -= remainingStreamingBytes;
remainingStreamingBytes = 0;
if (returnedUser != user) {
return returnedUser;
}
}
}
} else {
if (fallback.length() == MAX_FALLBACK_SIZE) {
// note: you don't really need error handler, just return something strange!
// we could have it return a constant pointer to denote error!
return errorHandler(user);
}
return user;
}
}
std::pair<int, void *> consumed = fenceAndConsumePostPadded<false>(data, length, user, &req, requestHandler, dataHandler);
if (consumed.second != user) {
return consumed.second;
}
data += consumed.first;
length -= consumed.first;
if (length) {
if ((unsigned int) length < MAX_FALLBACK_SIZE) {
fallback.append(data, length);
} else {
return errorHandler(user);
}
}
// added for now
return user;
}
};
}
#endif // UWS_HTTPPARSER_H

View File

@ -0,0 +1,317 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_HTTPRESPONSE_H
#define UWS_HTTPRESPONSE_H
/* An HttpResponse is the channel on which you send back a response */
#include "AsyncSocket.h"
#include "HttpResponseData.h"
#include "HttpContextData.h"
#include "Utilities.h"
#include "f2/function2.hpp"
/* todo: tryWrite is missing currently, only send smaller segments with write */
namespace uWS {
/* Some pre-defined status constants to use with writeStatus */
static const char *HTTP_200_OK = "200 OK";
/* The general timeout for HTTP sockets */
static const int HTTP_TIMEOUT_S = 10;
template <bool SSL>
struct HttpResponse : public AsyncSocket<SSL> {
/* Solely used for getHttpResponseData() */
template <bool> friend struct TemplatedApp;
typedef AsyncSocket<SSL> Super;
private:
HttpResponseData<SSL> *getHttpResponseData() {
return (HttpResponseData<SSL> *) Super::getAsyncSocketData();
}
/* Write an unsigned 32-bit integer in hex */
void writeUnsignedHex(unsigned int value) {
char buf[10];
int length = utils::u32toaHex(value, buf);
/* For now we do this copy */
Super::write(buf, length);
}
/* Write an unsigned 32-bit integer */
void writeUnsigned(unsigned int value) {
char buf[10];
int length = utils::u32toa(value, buf);
/* For now we do this copy */
Super::write(buf, length);
}
/* When we are done with a response we mark it like so */
void markDone(HttpResponseData<SSL> *httpResponseData) {
httpResponseData->onAborted = nullptr;
/* Also remove onWritable so that we do not emit when draining behind the scenes. */
httpResponseData->onWritable = nullptr;
/* We are done with this request */
httpResponseData->state &= ~HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
}
/* Called only once per request */
void writeMark() {
writeHeader("uWebSockets", "v0.17");
}
/* Returns true on success, indicating that it might be feasible to write more data.
* Will start timeout if stream reaches totalSize or write failure. */
bool internalEnd(std::string_view data, int totalSize, bool optional, bool allowContentLength = true) {
/* Write status if not already done */
writeStatus(HTTP_200_OK);
/* If no total size given then assume this chunk is everything */
if (!totalSize) {
totalSize = (int) data.length();
}
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED) {
/* We do not have tryWrite-like functionalities, so ignore optional in this path */
/* Do not allow sending 0 chunk here */
if (data.length()) {
Super::write("\r\n", 2);
writeUnsignedHex((unsigned int) data.length());
Super::write("\r\n", 2);
/* Ignoring optional for now */
Super::write(data.data(), (int) data.length());
}
/* Terminating 0 chunk */
Super::write("\r\n0\r\n\r\n", 7);
markDone(httpResponseData);
/* tryEnd can never fail when in chunked mode, since we do not have tryWrite (yet), only write */
Super::timeout(HTTP_TIMEOUT_S);
return true;
} else {
/* Write content-length on first call */
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_END_CALLED)) {
/* Write mark, this propagates to WebSockets too */
writeMark();
/* WebSocket upgrades does not allow content-length */
if (allowContentLength) {
/* Even zero is a valid content-length */
Super::write("Content-Length: ", 16);
writeUnsigned(totalSize);
Super::write("\r\n\r\n", 4);
} else {
Super::write("\r\n", 2);
}
/* Mark end called */
httpResponseData->state |= HttpResponseData<SSL>::HTTP_END_CALLED;
}
/* Even if we supply no new data to write, its failed boolean is useful to know
* if it failed to drain any prior failed header writes */
/* Write as much as possible without causing backpressure */
auto [written, failed] = Super::write(data.data(), (int) data.length(), optional);
httpResponseData->offset += written;
/* Success is when we wrote the entire thing without any failures */
bool success = (unsigned int) written == data.length() && !failed;
/* If we are now at the end, start a timeout. Also start a timeout if we failed. */
if (!success || httpResponseData->offset == totalSize) {
Super::timeout(HTTP_TIMEOUT_S);
}
/* Remove onAborted function if we reach the end */
if (httpResponseData->offset == totalSize) {
markDone(httpResponseData);
}
return success;
}
}
/* This call is identical to end, but will never write content-length and is thus suitable for upgrades */
void upgrade() {
internalEnd({nullptr, 0}, 0, false, false);
}
public:
/* Immediately terminate this Http response */
using Super::close;
using Super::getRemoteAddress;
/* Note: Headers are not checked in regards to timeout.
* We only check when you actively push data or end the request */
/* Write the HTTP status */
HttpResponse *writeStatus(std::string_view status) {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
/* Do not allow writing more than one status */
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_STATUS_CALLED) {
return this;
}
/* Update status */
httpResponseData->state |= HttpResponseData<SSL>::HTTP_STATUS_CALLED;
Super::write("HTTP/1.1 ", 9);
Super::write(status.data(), (int) status.length());
Super::write("\r\n", 2);
return this;
}
/* Write an HTTP header with string value */
HttpResponse *writeHeader(std::string_view key, std::string_view value) {
writeStatus(HTTP_200_OK);
Super::write(key.data(), (int) key.length());
Super::write(": ", 2);
Super::write(value.data(), (int) value.length());
Super::write("\r\n", 2);
return this;
}
/* Write an HTTP header with unsigned int value */
HttpResponse *writeHeader(std::string_view key, unsigned int value) {
Super::write(key.data(), (int) key.length());
Super::write(": ", 2);
writeUnsigned(value);
Super::write("\r\n", 2);
return this;
}
/* End the response with an optional data chunk. Always starts a timeout. */
void end(std::string_view data = {}) {
internalEnd(data, (int) data.length(), false);
}
/* Try and end the response. Returns [true, true] on success.
* Starts a timeout in some cases. Returns [ok, hasResponded] */
std::pair<bool, bool> tryEnd(std::string_view data, int totalSize = 0) {
return {internalEnd(data, totalSize, true), hasResponded()};
}
/* Write parts of the response in chunking fashion. Starts timeout if failed. */
bool write(std::string_view data) {
writeStatus(HTTP_200_OK);
/* Do not allow sending 0 chunks, they mark end of response */
if (!data.length()) {
/* If you called us, then according to you it was fine to call us so it's fine to still call us */
return true;
}
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
/* Write mark on first call to write */
writeMark();
writeHeader("Transfer-Encoding", "chunked");
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
}
Super::write("\r\n", 2);
writeUnsignedHex((unsigned int) data.length());
Super::write("\r\n", 2);
auto [written, failed] = Super::write(data.data(), (int) data.length());
if (failed) {
Super::timeout(HTTP_TIMEOUT_S);
}
/* If we did not fail the write, accept more */
return !failed;
}
/* Get the current byte write offset for this Http response */
int getWriteOffset() {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
return httpResponseData->offset;
}
/* Checking if we have fully responded and are ready for another request */
bool hasResponded() {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
return !(httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING);
}
/* Corks the response if possible. Leaves already corked socket be. */
HttpResponse *cork(fu2::unique_function<void()> &&handler) {
if (!Super::isCorked() && Super::canCork()) {
Super::cork();
handler();
/* Timeout on uncork failure, since most writes will succeed while corked */
auto [written, failed] = Super::uncork();
if (failed) {
/* For now we only have one single timeout so let's use it */
/* This behavior should equal the behavior in HttpContext when uncorking fails */
Super::timeout(HTTP_TIMEOUT_S);
}
} else {
/* We are already corked, or can't cork so let's just call the handler */
handler();
}
return this;
}
/* Attach handler for writable HTTP response */
HttpResponse *onWritable(fu2::unique_function<bool(int)> &&handler) {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
httpResponseData->onWritable = std::move(handler);
return this;
}
/* Attach handler for aborted HTTP request */
HttpResponse *onAborted(fu2::unique_function<void()> &&handler) {
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
httpResponseData->onAborted = std::move(handler);
return this;
}
/* Attach a read handler for data sent. Will be called with FIN set true if last segment. */
void onData(fu2::unique_function<void(std::string_view, bool)> &&handler) {
HttpResponseData<SSL> *data = getHttpResponseData();
data->inStream = std::move(handler);
}
};
}
#endif // UWS_HTTPRESPONSE_H

View File

@ -0,0 +1,57 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_HTTPRESPONSEDATA_H
#define UWS_HTTPRESPONSEDATA_H
/* This data belongs to the HttpResponse */
#include "HttpParser.h"
#include "AsyncSocketData.h"
#include "f2/function2.hpp"
namespace uWS {
template <bool SSL>
struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
template <bool> friend struct HttpResponse;
template <bool> friend struct HttpContext;
private:
/* Bits of status */
enum {
HTTP_STATUS_CALLED = 1, // used
HTTP_WRITE_CALLED = 2, // used
HTTP_END_CALLED = 4, // used
HTTP_RESPONSE_PENDING = 8, // used
HTTP_ENDED_STREAM_OUT = 16 // not used
};
/* Per socket event handlers */
fu2::unique_function<bool(int)> onWritable;
fu2::unique_function<void()> onAborted;
fu2::unique_function<void(std::string_view, bool)> inStream; // onData
/* Outgoing offset */
int offset = 0;
/* Current state (content-length sent, status sent, write called, etc */
int state = 0;
};
}
#endif // UWS_HTTPRESPONSEDATA_H

View File

@ -0,0 +1,233 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_HTTPROUTER_HPP
#define UWS_HTTPROUTER_HPP
#include <map>
#include <vector>
#include <cstring>
#include <string_view>
#include <string>
#include <algorithm>
#include <memory>
#include "f2/function2.hpp"
namespace uWS {
template <class USERDATA>
struct HttpRouter {
/* These are public for now */
std::vector<std::string> methods = {"get", "post", "head", "put", "delete", "connect", "options", "trace", "patch"};
static const uint32_t HIGH_PRIORITY = 0xd0000000, MEDIUM_PRIORITY = 0xe0000000, LOW_PRIORITY = 0xf0000000;
private:
USERDATA userData;
static const unsigned int MAX_URL_SEGMENTS = 100;
/* Handler ids are 32-bit */
static const uint32_t HANDLER_MASK = 0x0fffffff;
/* Methods and their respective priority */
std::map<std::string, int> priority;
/* List of handlers */
std::vector<fu2::unique_function<bool(HttpRouter *)>> handlers;
/* Current URL cache */
std::string_view currentUrl;
std::string_view urlSegmentVector[MAX_URL_SEGMENTS];
int urlSegmentTop;
/* The matching tree */
struct Node {
std::string name;
std::vector<std::unique_ptr<Node>> children;
std::vector<uint32_t> handlers;
} root = {"rootNode"};
/* Advance from parent to child, adding child if necessary */
Node *getNode(Node *parent, std::string child) {
for (std::unique_ptr<Node> &node : parent->children) {
if (node->name == child) {
return node.get();
}
}
/* Insert sorted, but keep order if parent is root (we sort methods by priority elsewhere) */
std::unique_ptr<Node> newNode(new Node({child}));
return parent->children.emplace(std::upper_bound(parent->children.begin(), parent->children.end(), newNode, [parent, this](auto &a, auto &b) {
return b->name.length() && (parent != &root) && (b->name < a->name);
}), std::move(newNode))->get();
}
/* Basically a pre-allocated stack */
struct RouteParameters {
friend struct HttpRouter;
private:
std::string_view params[MAX_URL_SEGMENTS];
int paramsTop;
void reset() {
paramsTop = -1;
}
void push(std::string_view param) {
/* We check these bounds indirectly via the urlSegments limit */
params[++paramsTop] = param;
}
void pop() {
/* Same here, we cannot pop outside */
paramsTop--;
}
} routeParameters;
/* Set URL for router. Will reset any URL cache */
inline void setUrl(std::string_view url) {
/* Remove / from input URL */
currentUrl = url.substr(std::min<unsigned int>((unsigned int) url.length(), 1));
urlSegmentTop = -1;
}
/* Lazily parse or read from cache */
inline std::string_view getUrlSegment(int urlSegment) {
if (urlSegment > urlSegmentTop) {
/* Return empty segment if we are out of URL or stack space, but never for first url segment */
if (!currentUrl.length() || urlSegment > 99) {
return {};
}
auto segmentLength = currentUrl.find('/');
if (segmentLength == std::string::npos) {
segmentLength = currentUrl.length();
/* Push to url segment vector */
urlSegmentVector[urlSegment] = currentUrl.substr(0, segmentLength);
urlSegmentTop++;
/* Update currentUrl */
currentUrl = currentUrl.substr(segmentLength);
} else {
/* Push to url segment vector */
urlSegmentVector[urlSegment] = currentUrl.substr(0, segmentLength);
urlSegmentTop++;
/* Update currentUrl */
currentUrl = currentUrl.substr(segmentLength + 1);
}
}
/* In any case we return it */
return urlSegmentVector[urlSegment];
}
/* Executes as many handlers it can */
bool executeHandlers(Node *parent, int urlSegment, USERDATA &userData) {
/* If we have no more URL and not on first round, return where we may stand */
if (urlSegment && !getUrlSegment(urlSegment).length()) {
/* We have reached accross the entire URL with no stoppage, execute */
for (int handler : parent->handlers) {
if (handlers[handler & HANDLER_MASK](this)) {
return true;
}
}
/* We reached the end, so go back */
return false;
}
for (auto &p : parent->children) {
if (p->name.length() && p->name[0] == '*') {
/* Wildcard match (can be seen as a shortcut) */
for (int handler : p->handlers) {
if (handlers[handler & HANDLER_MASK](this)) {
return true;
}
}
} else if (p->name.length() && p->name[0] == ':' && getUrlSegment(urlSegment).length()) {
/* Parameter match */
routeParameters.push(getUrlSegment(urlSegment));
if (executeHandlers(p.get(), urlSegment + 1, userData)) {
return true;
}
routeParameters.pop();
} else if (p->name == getUrlSegment(urlSegment)) {
/* Static match */
if (executeHandlers(p.get(), urlSegment + 1, userData)) {
return true;
}
}
}
return false;
}
public:
HttpRouter() {
int p = 0;
for (std::string &method : methods) {
priority[method] = p++;
}
}
std::pair<int, std::string_view *> getParameters() {
return {routeParameters.paramsTop, routeParameters.params};
}
USERDATA &getUserData() {
return userData;
}
/* Fast path */
bool route(std::string_view method, std::string_view url) {
/* Reset url parsing cache */
setUrl(url);
routeParameters.reset();
/* Begin by finding the method node */
for (auto &p : root.children) {
if (p->name == method) {
/* Then route the url */
return executeHandlers(p.get(), 0, userData);
}
}
/* We did not find any handler for this method and url */
return false;
}
/* Adds the corresponding entires in matching tree and handler list */
void add(std::vector<std::string> methods, std::string pattern, fu2::unique_function<bool(HttpRouter *)> &&handler, int priority = MEDIUM_PRIORITY) {
for (std::string method : methods) {
/* Lookup method */
Node *node = getNode(&root, method);
/* Iterate over all segments */
setUrl(pattern);
for (int i = 0; getUrlSegment(i).length() || i == 0; i++) {
node = getNode(node, std::string(getUrlSegment(i)));
}
/* Insert handler in order sorted by priority (most significant 1 byte) */
node->handlers.insert(std::upper_bound(node->handlers.begin(), node->handlers.end(), (uint32_t) (priority | handlers.size())), (uint32_t) (priority | handlers.size()));
}
/* Alloate this handler */
handlers.emplace_back(std::move(handler));
}
};
}
#endif // UWS_HTTPROUTER_HPP

View File

@ -0,0 +1,169 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_LOOP_H
#define UWS_LOOP_H
/* The loop is lazily created per-thread and run with uWS::run() */
#include "LoopData.h"
#include <libusockets.h>
namespace uWS {
struct Loop {
private:
static void wakeupCb(us_loop_t *loop) {
LoopData *loopData = (LoopData *) us_loop_ext(loop);
/* Swap current deferQueue */
loopData->deferMutex.lock();
int oldDeferQueue = loopData->currentDeferQueue;
loopData->currentDeferQueue = (loopData->currentDeferQueue + 1) % 2;
loopData->deferMutex.unlock();
/* Drain the queue */
for (auto &x : loopData->deferQueues[oldDeferQueue]) {
x();
}
loopData->deferQueues[oldDeferQueue].clear();
}
static void preCb(us_loop_t *loop) {
LoopData *loopData = (LoopData *) us_loop_ext(loop);
for (auto &p : loopData->preHandlers) {
p.second((Loop *) loop);
}
}
static void postCb(us_loop_t *loop) {
LoopData *loopData = (LoopData *) us_loop_ext(loop);
for (auto &p : loopData->postHandlers) {
p.second((Loop *) loop);
}
}
Loop() = delete;
~Loop() = default;
Loop *init() {
new (us_loop_ext((us_loop_t *) this)) LoopData;
return this;
}
static Loop *create(void *hint) {
return ((Loop *) us_create_loop(hint, wakeupCb, preCb, postCb, sizeof(LoopData)))->init();
}
/* What to do with loops created with existingNativeLoop? */
struct LoopCleaner {
~LoopCleaner() {
if(loop && cleanMe) {
loop->free();
}
}
Loop *loop = nullptr;
bool cleanMe = false;
};
public:
/* Lazily initializes a per-thread loop and returns it.
* Will automatically free all initialized loops at exit. */
static Loop *get(void *existingNativeLoop = nullptr) {
static thread_local LoopCleaner lazyLoop;
if (!lazyLoop.loop) {
/* If we are given a native loop pointer we pass that to uSockets and let it deal with it */
if (existingNativeLoop) {
/* Todo: here we want to pass the pointer, not a boolean */
lazyLoop.loop = create(existingNativeLoop);
/* We cannot register automatic free here, must be manually done */
} else {
lazyLoop.loop = create(nullptr);
lazyLoop.cleanMe = true;
}
}
return lazyLoop.loop;
}
/* Freeing the default loop should be done once */
void free() {
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);
loopData->~LoopData();
/* uSockets will track whether this loop is owned by us or a borrowed alien loop */
us_loop_free((us_loop_t *) this);
}
void addPostHandler(void *key, fu2::unique_function<void(Loop *)> &&handler) {
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);
loopData->postHandlers.emplace(key, std::move(handler));
}
/* Bug: what if you remove a handler while iterating them? */
void removePostHandler(void *key) {
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);
loopData->postHandlers.erase(key);
}
void addPreHandler(void *key, fu2::unique_function<void(Loop *)> &&handler) {
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);
loopData->preHandlers.emplace(key, std::move(handler));
}
/* Bug: what if you remove a handler while iterating them? */
void removePreHandler(void *key) {
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);
loopData->preHandlers.erase(key);
}
/* Defer this callback on Loop's thread of execution */
void defer(fu2::unique_function<void()> &&cb) {
LoopData *loopData = (LoopData *) us_loop_ext((us_loop_t *) this);
//if (std::thread::get_id() == ) // todo: add fast path for same thread id
loopData->deferMutex.lock();
loopData->deferQueues[loopData->currentDeferQueue].emplace_back(std::move(cb));
loopData->deferMutex.unlock();
us_wakeup_loop((us_loop_t *) this);
}
/* Actively block and run this loop */
void run() {
us_loop_run((us_loop_t *) this);
}
/* Passively integrate with the underlying default loop */
/* Used to seamlessly integrate with third parties such as Node.js */
void integrate() {
us_loop_integrate((us_loop_t *) this);
}
};
/* Can be called from any thread to run the thread local loop */
inline void run() {
Loop::get()->run();
}
}
#endif // UWS_LOOP_H

View File

@ -0,0 +1,72 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_LOOPDATA_H
#define UWS_LOOPDATA_H
#include <thread>
#include <functional>
#include <vector>
#include <mutex>
#include <map>
#include "PerMessageDeflate.h"
#include "f2/function2.hpp"
namespace uWS {
struct Loop;
struct alignas(16) LoopData {
friend struct Loop;
private:
std::mutex deferMutex;
int currentDeferQueue = 0;
std::vector<fu2::unique_function<void()>> deferQueues[2];
/* Map from void ptr to handler */
std::map<void *, fu2::unique_function<void(Loop *)>> postHandlers, preHandlers;
public:
~LoopData() {
/* If we have had App.ws called with compression we need to clear this */
if (zlibContext) {
delete zlibContext;
delete inflationStream;
delete deflationStream;
}
delete [] corkBuffer;
}
/* Good 16k for SSL perf. */
static const int CORK_BUFFER_SIZE = 16 * 1024;
/* Cork data */
char *corkBuffer = new char[CORK_BUFFER_SIZE];
int corkOffset = 0;
void *corkedSocket = nullptr;
/* Per message deflate data */
ZlibContext *zlibContext = nullptr;
InflationStream *inflationStream = nullptr;
DeflationStream *deflationStream = nullptr;
};
}
#endif // UWS_LOOPDATA_H

View File

@ -0,0 +1,187 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* This standalone module implements deflate / inflate streams */
#ifndef UWS_PERMESSAGEDEFLATE_H
#define UWS_PERMESSAGEDEFLATE_H
#ifndef UWS_NO_ZLIB
#include <zlib.h>
#endif
#include <string>
namespace uWS {
/* Do not compile this module if we don't want it */
#ifdef UWS_NO_ZLIB
struct ZlibContext {};
struct InflationStream {
std::string_view inflate(ZlibContext *zlibContext, std::string_view compressed, size_t maxPayloadLength) {
return compressed;
}
};
struct DeflationStream {
std::string_view deflate(ZlibContext *zlibContext, std::string_view raw, bool reset) {
return raw;
}
};
#else
#define LARGE_BUFFER_SIZE 1024 * 16 // todo: fix this
struct ZlibContext {
/* Any returned data is valid until next same-class call.
* We need to have two classes to allow inflation followed
* by many deflations without modifying the inflation */
std::string dynamicDeflationBuffer;
std::string dynamicInflationBuffer;
char *deflationBuffer;
char *inflationBuffer;
ZlibContext() {
deflationBuffer = (char *) malloc(LARGE_BUFFER_SIZE);
inflationBuffer = (char *) malloc(LARGE_BUFFER_SIZE);
}
~ZlibContext() {
free(deflationBuffer);
free(inflationBuffer);
}
};
struct DeflationStream {
z_stream deflationStream = {};
DeflationStream() {
deflateInit2(&deflationStream, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
}
/* Deflate and optionally reset */
std::string_view deflate(ZlibContext *zlibContext, std::string_view raw, bool reset) {
/* Odd place to clear this one, fix */
zlibContext->dynamicDeflationBuffer.clear();
deflationStream.next_in = (Bytef *) raw.data();
deflationStream.avail_in = (unsigned int) raw.length();
/* This buffer size has to be at least 6 bytes for Z_SYNC_FLUSH to work */
const int DEFLATE_OUTPUT_CHUNK = LARGE_BUFFER_SIZE;
int err;
do {
deflationStream.next_out = (Bytef *) zlibContext->deflationBuffer;
deflationStream.avail_out = DEFLATE_OUTPUT_CHUNK;
err = ::deflate(&deflationStream, Z_SYNC_FLUSH);
if (Z_OK == err && deflationStream.avail_out == 0) {
zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out);
continue;
} else {
break;
}
} while (true);
/* This must not change avail_out */
if (reset) {
deflateReset(&deflationStream);
}
if (zlibContext->dynamicDeflationBuffer.length()) {
zlibContext->dynamicDeflationBuffer.append(zlibContext->deflationBuffer, DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out);
return {(char *) zlibContext->dynamicDeflationBuffer.data(), zlibContext->dynamicDeflationBuffer.length() - 4};
}
return {
zlibContext->deflationBuffer,
DEFLATE_OUTPUT_CHUNK - deflationStream.avail_out - 4
};
}
~DeflationStream() {
deflateEnd(&deflationStream);
}
};
struct InflationStream {
z_stream inflationStream = {};
InflationStream() {
inflateInit2(&inflationStream, -15);
}
~InflationStream() {
inflateEnd(&inflationStream);
}
std::string_view inflate(ZlibContext *zlibContext, std::string_view compressed, size_t maxPayloadLength) {
/* We clear this one here, could be done better */
zlibContext->dynamicInflationBuffer.clear();
inflationStream.next_in = (Bytef *) compressed.data();
inflationStream.avail_in = (unsigned int) compressed.length();
int err;
do {
inflationStream.next_out = (Bytef *) zlibContext->inflationBuffer;
inflationStream.avail_out = LARGE_BUFFER_SIZE;
err = ::inflate(&inflationStream, Z_SYNC_FLUSH);
if (err == Z_OK && inflationStream.avail_out) {
break;
}
zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
} while (inflationStream.avail_out == 0 && zlibContext->dynamicInflationBuffer.length() <= maxPayloadLength);
inflateReset(&inflationStream);
if ((err != Z_BUF_ERROR && err != Z_OK) || zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) {
return {nullptr, 0};
}
if (zlibContext->dynamicInflationBuffer.length()) {
zlibContext->dynamicInflationBuffer.append(zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out);
/* Let's be strict about the max size */
if (zlibContext->dynamicInflationBuffer.length() > maxPayloadLength) {
return {nullptr, 0};
}
return {zlibContext->dynamicInflationBuffer.data(), zlibContext->dynamicInflationBuffer.length()};
}
/* Let's be strict about the max size */
if ((LARGE_BUFFER_SIZE - inflationStream.avail_out) > maxPayloadLength) {
return {nullptr, 0};
}
return {zlibContext->inflationBuffer, LARGE_BUFFER_SIZE - inflationStream.avail_out};
}
};
#endif
}
#endif // UWS_PERMESSAGEDEFLATE_H

View File

@ -0,0 +1,429 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_TOPICTREE_H
#define UWS_TOPICTREE_H
#include <iostream>
#include <vector>
#include <map>
#include <string_view>
#include <functional>
#include <set>
#include <chrono>
#include <list>
namespace uWS {
/* A Subscriber is an extension of a socket */
struct Subscriber {
std::list<struct Topic *> subscriptions;
void *user;
Subscriber(void *user) : user(user) {}
};
struct Topic {
/* Memory for our name */
char *name;
size_t length;
/* Our parent or nullptr */
Topic *parent = nullptr;
/* Next triggered Topic */
bool triggered = false;
/* Exact string matches */
std::map<std::string_view, Topic *> children;
/* Wildcard child */
Topic *wildcardChild = nullptr;
/* Terminating wildcard child */
Topic *terminatingWildcardChild = nullptr;
/* What we published */
std::map<unsigned int, std::string> messages;
std::set<Subscriber *> subs;
};
struct TopicTree {
private:
std::function<int(Subscriber *, std::string_view)> cb;
Topic *root = new Topic;
/* Global messageId for deduplication of overlapping topics and ordering between topics */
unsigned int messageId = 0;
/* The triggered topics */
Topic *triggeredTopics[64];
int numTriggeredTopics = 0;
Subscriber *min = (Subscriber *) UINTPTR_MAX;
/* Cull or trim unused Topic nodes from leaf to root */
void trimTree(Topic *topic) {
if (!topic->subs.size() && !topic->children.size() && !topic->terminatingWildcardChild && !topic->wildcardChild) {
Topic *parent = topic->parent;
if (topic->length == 1) {
if (topic->name[0] == '#') {
parent->terminatingWildcardChild = nullptr;
} else if (topic->name[0] == '+') {
parent->wildcardChild = nullptr;
}
}
/* Erase us from our parents set (wildcards also live here) */
parent->children.erase(std::string_view(topic->name, topic->length));
/* If this node is triggered, make sure to remove it from the triggered list */
if (topic->triggered) {
Topic *tmp[64];
int length = 0;
for (int i = 0; i < numTriggeredTopics; i++) {
if (triggeredTopics[i] != topic) {
tmp[length++] = triggeredTopics[i];
}
}
for (int i = 0; i < length; i++) {
triggeredTopics[i] = tmp[i];
}
numTriggeredTopics = length;
}
/* Free various memory for the node */
delete [] topic->name;
delete topic;
if (parent != root) {
trimTree(parent);
}
}
}
/* Should be getData and commit? */
void publish(Topic *iterator, size_t start, size_t stop, std::string_view topic, std::string_view message) {
/* If we already have 64 triggered topics make sure to drain it here */
if (numTriggeredTopics == 64) {
drain();
}
for (; stop != std::string::npos; start = stop + 1) {
stop = topic.find('/', start);
std::string_view segment = topic.substr(start, stop - start);
/* Do we have a terminating wildcard child? */
if (iterator->terminatingWildcardChild) {
iterator->terminatingWildcardChild->messages[messageId] = message;
/* Add this topic to triggered */
if (!iterator->terminatingWildcardChild->triggered) {
triggeredTopics[numTriggeredTopics++] = iterator->terminatingWildcardChild;
/* Keep track of lowest subscriber */
if (*iterator->terminatingWildcardChild->subs.begin() < min) {
min = *iterator->terminatingWildcardChild->subs.begin();
}
iterator->terminatingWildcardChild->triggered = true;
}
}
/* Do we have a wildcard child? */
if (iterator->wildcardChild) {
publish(iterator->wildcardChild, stop + 1, stop, topic, message);
}
std::map<std::string_view, Topic *>::iterator it = iterator->children.find(segment);
if (it == iterator->children.end()) {
/* Stop trying to match by exact string */
return;
}
iterator = it->second;
}
/* If we went all the way we matched exactly */
iterator->messages[messageId] = message;
/* Add this topic to triggered */
if (!iterator->triggered) {
triggeredTopics[numTriggeredTopics++] = iterator;
/* Keep track of lowest subscriber */
if (*iterator->subs.begin() < min) {
min = *iterator->subs.begin();
}
iterator->triggered = true;
}
}
public:
TopicTree(std::function<int(Subscriber *, std::string_view)> cb) {
this->cb = cb;
}
~TopicTree() {
delete root;
}
void subscribe(std::string_view topic, Subscriber *subscriber) {
/* Start iterating from the root */
Topic *iterator = root;
/* Traverse the topic, inserting a node for every new segment separated by / */
for (size_t start = 0, stop = 0; stop != std::string::npos; start = stop + 1) {
stop = topic.find('/', start);
std::string_view segment = topic.substr(start, stop - start);
auto lb = iterator->children.lower_bound(segment);
if (lb != iterator->children.end() && !(iterator->children.key_comp()(segment, lb->first))) {
iterator = lb->second;
} else {
/* Allocate and insert new node */
Topic *newTopic = new Topic;
newTopic->parent = iterator;
newTopic->name = new char[segment.length()];
newTopic->length = segment.length();
newTopic->terminatingWildcardChild = nullptr;
newTopic->wildcardChild = nullptr;
memcpy(newTopic->name, segment.data(), segment.length());
/* For simplicity we do insert wildcards with text */
iterator->children.insert(lb, {std::string_view(newTopic->name, segment.length()), newTopic});
/* Store fast lookup to wildcards */
if (segment.length() == 1) {
/* If this segment is '+' it is a wildcard */
if (segment[0] == '+') {
iterator->wildcardChild = newTopic;
}
/* If this segment is '#' it is a terminating wildcard */
if (segment[0] == '#') {
iterator->terminatingWildcardChild = newTopic;
}
}
iterator = newTopic;
}
}
/* Add socket to Topic's Set */
auto [it, inserted] = iterator->subs.insert(subscriber);
/* Add Topic to list of subscriptions only if we weren't already subscribed */
if (inserted) {
subscriber->subscriptions.push_back(iterator);
}
}
void publish(std::string_view topic, std::string_view message) {
publish(root, 0, 0, topic, message);
messageId++;
}
/* Returns whether we were subscribed prior */
bool unsubscribe(std::string_view topic, Subscriber *subscriber) {
/* Subscribers are likely to have very few subscriptions (20 or fewer) */
if (subscriber) {
/* Lookup exact Topic ptr from string */
Topic *iterator = root;
for (size_t start = 0, stop = 0; stop != std::string::npos; start = stop + 1) {
stop = topic.find('/', start);
std::string_view segment = topic.substr(start, stop - start);
std::map<std::string_view, Topic *>::iterator it = iterator->children.find(segment);
if (it == iterator->children.end()) {
/* This topic does not even exist */
return false;
}
iterator = it->second;
}
/* Try and remove this topic from our list */
for (auto it = subscriber->subscriptions.begin(); it != subscriber->subscriptions.end(); it++) {
if (*it == iterator) {
/* Remove topic ptr from our list */
subscriber->subscriptions.erase(it);
/* Remove us from Topic's subs */
iterator->subs.erase(subscriber);
trimTree(iterator);
return true;
}
}
}
return false;
}
/* Can be called with nullptr, ignore it then */
void unsubscribeAll(Subscriber *subscriber) {
if (subscriber) {
for (Topic *topic : subscriber->subscriptions) {
topic->subs.erase(subscriber);
trimTree(topic);
}
subscriber->subscriptions.clear();
}
}
/* Drain the tree by emitting what to send with every Subscriber */
/* Better name would be commit() and making it public so that one can commit and shutdown, etc */
void drain() {
/* Do nothing if nothing to send */
if (!numTriggeredTopics) {
return;
}
/* bug fix: Filter triggered topics without subscribers */
int numFilteredTriggeredTopics = 0;
for (int i = 0; i < numTriggeredTopics; i++) {
if (triggeredTopics[i]->subs.size()) {
triggeredTopics[numFilteredTriggeredTopics++] = triggeredTopics[i];
}
}
numTriggeredTopics = numFilteredTriggeredTopics;
if (!numTriggeredTopics) {
return;
}
/* bug fix: update min, as the one tracked via subscribe gets invalid as you unsubscribe */
min = (Subscriber *)UINTPTR_MAX;
for (int i = 0; i < numTriggeredTopics; i++) {
if ((triggeredTopics[i]->subs.size()) && (min > *triggeredTopics[i]->subs.begin())) {
min = *triggeredTopics[i]->subs.begin();
}
}
/* Check if we really have any sockets still */
if (min != (Subscriber *)UINTPTR_MAX) {
/* Up to 64 triggered Topics per batch */
std::map<uint64_t, std::string> intersectionCache;
/* Loop over these here */
std::set<Subscriber *>::iterator it[64];
std::set<Subscriber *>::iterator end[64];
for (int i = 0; i < numTriggeredTopics; i++) {
it[i] = triggeredTopics[i]->subs.begin();
end[i] = triggeredTopics[i]->subs.end();
}
/* Empty all sets from unique subscribers */
for (int nonEmpty = numTriggeredTopics; nonEmpty; ) {
Subscriber *nextMin = (Subscriber *)UINTPTR_MAX;
/* The message sets relevant for this intersection */
std::map<unsigned int, std::string> *perSubscriberIntersectingTopicMessages[64];
int numPerSubscriberIntersectingTopicMessages = 0;
uint64_t intersection = 0;
for (int i = 0; i < numTriggeredTopics; i++) {
if ((it[i] != end[i]) && (*it[i] == min)) {
/* Mark this intersection */
intersection |= ((uint64_t)1 << i);
perSubscriberIntersectingTopicMessages[numPerSubscriberIntersectingTopicMessages++] = &triggeredTopics[i]->messages;
it[i]++;
if (it[i] == end[i]) {
nonEmpty--;
}
else {
if (nextMin > *it[i]) {
nextMin = *it[i];
}
}
}
else {
/* We need to lower nextMin to us, in the case of min being the last in a set */
if ((it[i] != end[i]) && (nextMin > *it[i])) {
nextMin = *it[i];
}
}
}
/* Generate cache for intersection */
if (intersectionCache[intersection].length() == 0) {
/* Build the union in order without duplicates */
std::map<unsigned int, std::string> complete;
for (int i = 0; i < numPerSubscriberIntersectingTopicMessages; i++) {
complete.insert(perSubscriberIntersectingTopicMessages[i]->begin(), perSubscriberIntersectingTopicMessages[i]->end());
}
/* Create the linear cache */
std::string res;
for (auto &p : complete) {
res.append(p.second);
}
cb(min, intersectionCache[intersection] = std::move(res));
}
else {
cb(min, intersectionCache[intersection]);
}
min = nextMin;
}
}
/* Clear messages of triggered Topics */
for (int i = 0; i < numTriggeredTopics; i++) {
triggeredTopics[i]->messages.clear();
triggeredTopics[i]->triggered = false;
}
numTriggeredTopics = 0;
}
void print(Topic *root = nullptr, int indentation = 1) {
if (root == nullptr) {
std::cout << "Print of tree:" << std::endl;
root = this->root;
}
for (auto p : root->children) {
for (int i = 0; i < indentation; i++) {
std::cout << " ";
}
std::cout << std::string_view(p.second->name, p.second->length) << " = " << p.second->messages.size() << " publishes, " << p.second->subs.size() << " subscribers {";
for (auto &p : p.second->subs) {
std::cout << p << " referring to socket: " << p->user << ", ";
}
std::cout << "}" << std::endl;
print(p.second, indentation + 1);
}
}
};
}
#endif

View File

@ -0,0 +1,66 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_UTILITIES_H
#define UWS_UTILITIES_H
/* Various common utilities */
#include <cstdint>
namespace uWS {
namespace utils {
inline int u32toaHex(uint32_t value, char *dst) {
char palette[] = "0123456789abcdef";
char temp[10];
char *p = temp;
do {
*p++ = palette[value % 16];
value /= 16;
} while (value > 0);
int ret = (int) (p - temp);
do {
*dst++ = *--p;
} while (p != temp);
return ret;
}
inline int u32toa(uint32_t value, char *dst) {
char temp[10];
char *p = temp;
do {
*p++ = (char) ((value % 10) + '0');
value /= 10;
} while (value > 0);
int ret = (int) (p - temp);
do {
*dst++ = *--p;
} while (p != temp);
return ret;
}
}
}
#endif // UWS_UTILITIES_H

View File

@ -0,0 +1,213 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_WEBSOCKET_H
#define UWS_WEBSOCKET_H
#include "WebSocketData.h"
#include "WebSocketProtocol.h"
#include "AsyncSocket.h"
#include "WebSocketContextData.h"
#include <string_view>
namespace uWS {
template <bool SSL, bool isServer>
struct WebSocket : AsyncSocket<SSL> {
template <bool> friend struct TemplatedApp;
private:
typedef AsyncSocket<SSL> Super;
void *init(bool perMessageDeflate, bool slidingCompression, std::string &&backpressure) {
new (us_socket_ext(SSL, (us_socket_t *) this)) WebSocketData(perMessageDeflate, slidingCompression, std::move(backpressure));
return this;
}
public:
/* Returns pointer to the per socket user data */
void *getUserData() {
WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
/* We just have it overallocated by sizeof type */
return (webSocketData + 1);
}
/* See AsyncSocket */
using Super::getBufferedAmount;
using Super::getRemoteAddress;
/* Simple, immediate close of the socket. Emits close event */
using Super::close;
/* Send or buffer a WebSocket frame, compressed or not. Returns false on increased user space backpressure. */
bool send(std::string_view message, uWS::OpCode opCode = uWS::OpCode::BINARY, bool compress = false) {
/* Transform the message to compressed domain if requested */
if (compress) {
WebSocketData *webSocketData = (WebSocketData *) Super::getAsyncSocketData();
/* Check and correct the compress hint */
if (opCode < 3 && webSocketData->compressionStatus == WebSocketData::ENABLED) {
LoopData *loopData = Super::getLoopData();
/* Compress using either shared or dedicated deflationStream */
if (webSocketData->deflationStream) {
message = webSocketData->deflationStream->deflate(loopData->zlibContext, message, false);
} else {
message = loopData->deflationStream->deflate(loopData->zlibContext, message, true);
}
} else {
compress = false;
}
}
/* Check to see if we can cork for the user */
bool automaticallyCorked = false;
if (!Super::isCorked() && Super::canCork()) {
automaticallyCorked = true;
Super::cork();
}
/* Get size, alloate size, write if needed */
size_t messageFrameSize = protocol::messageFrameSize(message.length());
auto[sendBuffer, requiresWrite] = Super::getSendBuffer(messageFrameSize);
protocol::formatMessage<isServer>(sendBuffer, message.data(), message.length(), opCode, message.length(), compress);
/* This is the slow path, when we couldn't cork for the user */
if (requiresWrite) {
auto[written, failed] = Super::write(sendBuffer, (int) messageFrameSize);
/* For now, we are slow here */
free(sendBuffer);
if (failed) {
/* Return false for failure, skipping to reset the timeout below */
return false;
}
}
/* Uncork here if we automatically corked for the user */
if (automaticallyCorked) {
auto [written, failed] = Super::uncork();
if (failed) {
return false;
}
}
/* Every successful send resets the timeout */
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL,
(us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
);
AsyncSocket<SSL>::timeout(webSocketContextData->idleTimeout);
/* Return success */
return true;
}
/* Send websocket close frame, emit close event, send FIN if successful */
void end(int code, std::string_view message = {}) {
/* Check if we already called this one */
WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
if (webSocketData->isShuttingDown) {
return;
}
/* We postpone any FIN sending to either drainage or uncorking */
webSocketData->isShuttingDown = true;
/* Format and send the close frame */
static const int MAX_CLOSE_PAYLOAD = 123;
int length = (int) std::min<size_t>(MAX_CLOSE_PAYLOAD, message.length());
char closePayload[MAX_CLOSE_PAYLOAD + 2];
int closePayloadLength = (int) protocol::formatClosePayload(closePayload, (uint16_t) code, message.data(), length);
bool ok = send(std::string_view(closePayload, closePayloadLength), OpCode::CLOSE);
/* FIN if we are ok and not corked */
WebSocket<SSL, true> *webSocket = (WebSocket<SSL, true> *) this;
if (!webSocket->isCorked()) {
if (ok) {
/* If we are not corked, and we just sent off everything, we need to FIN right here.
* In all other cases, we need to fin either if uncork was successful, or when drainage is complete. */
webSocket->shutdown();
}
}
/* Emit close event */
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL,
(us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
);
if (webSocketContextData->closeHandler) {
webSocketContextData->closeHandler(this, code, message);
}
/* Make sure to unsubscribe from any pub/sub node at exit */
webSocketContextData->topicTree.unsubscribeAll(webSocketData->subscriber);
delete webSocketData->subscriber;
webSocketData->subscriber = nullptr;
}
/* Corks the response if possible. Leaves already corked socket be. */
void cork(fu2::unique_function<void()> &&handler) {
if (!Super::isCorked() && Super::canCork()) {
Super::cork();
handler();
/* There is no timeout when failing to uncork for WebSockets,
* as that is handled by idleTimeout */
auto [written, failed] = Super::uncork();
} else {
/* We are already corked, or can't cork so let's just call the handler */
handler();
}
}
/* Subscribe to a topic according to MQTT rules and syntax */
void subscribe(std::string_view topic) {
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL,
(us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
);
/* Make us a subscriber if we aren't yet */
WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
if (!webSocketData->subscriber) {
webSocketData->subscriber = new Subscriber(this);
}
webSocketContextData->topicTree.subscribe(topic, webSocketData->subscriber);
}
/* Unsubscribe from a topic, returns true if we were subscribed */
bool unsubscribe(std::string_view topic) {
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL,
(us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
);
WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
return webSocketContextData->topicTree.unsubscribe(topic, webSocketData->subscriber);
}
/* Publish a message to a topic according to MQTT rules and syntax */
void publish(std::string_view topic, std::string_view message, OpCode opCode = OpCode::TEXT, bool compress = false) {
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL,
(us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
);
/* Is the same as publishing per websocket context */
webSocketContextData->publish(topic, message, opCode, compress);
}
};
}
#endif // UWS_WEBSOCKET_H

View File

@ -0,0 +1,380 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_WEBSOCKETCONTEXT_H
#define UWS_WEBSOCKETCONTEXT_H
#include "WebSocketContextData.h"
#include "WebSocketProtocol.h"
#include "WebSocketData.h"
#include "WebSocket.h"
namespace uWS {
template <bool SSL, bool isServer>
struct WebSocketContext {
template <bool> friend struct TemplatedApp;
template <bool, typename> friend struct WebSocketProtocol;
private:
WebSocketContext() = delete;
us_socket_context_t *getSocketContext() {
return (us_socket_context_t *) this;
}
WebSocketContextData<SSL> *getExt() {
return (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *) this);
}
/* If we have negotiated compression, set this frame compressed */
static bool setCompressed(uWS::WebSocketState<isServer> *wState, void *s) {
WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) s);
if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::ENABLED) {
webSocketData->compressionStatus = WebSocketData::CompressionStatus::COMPRESSED_FRAME;
return true;
} else {
return false;
}
}
static void forceClose(uWS::WebSocketState<isServer> *wState, void *s) {
us_socket_close(SSL, (us_socket_t *) s);
}
/* Returns true on breakage */
static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, uWS::WebSocketState<isServer> *webSocketState, void *s) {
/* WebSocketData and WebSocketContextData */
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) s);
/* Is this a non-control frame? */
if (opCode < 3) {
/* Did we get everything in one go? */
if (!remainingBytes && fin && !webSocketData->fragmentBuffer.length()) {
/* Handle compressed frame */
if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::COMPRESSED_FRAME) {
webSocketData->compressionStatus = WebSocketData::CompressionStatus::ENABLED;
LoopData *loopData = (LoopData *) us_loop_ext(us_socket_context_loop(SSL, us_socket_context(SSL, (us_socket_t *) s)));
std::string_view inflatedFrame = loopData->inflationStream->inflate(loopData->zlibContext, {data, length}, webSocketContextData->maxPayloadLength);
if (!inflatedFrame.length()) {
forceClose(webSocketState, s);
return true;
} else {
data = (char *) inflatedFrame.data();
length = inflatedFrame.length();
}
}
/* Check text messages for Utf-8 validity */
if (opCode == 1 && !protocol::isValidUtf8((unsigned char *) data, length)) {
forceClose(webSocketState, s);
return true;
}
/* Emit message event & break if we are closed or shut down when returning */
if (webSocketContextData->messageHandler) {
webSocketContextData->messageHandler((WebSocket<SSL, isServer> *) s, std::string_view(data, length), (uWS::OpCode) opCode);
if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) {
return true;
}
}
} else {
/* Allocate fragment buffer up front first time */
if (!webSocketData->fragmentBuffer.length()) {
webSocketData->fragmentBuffer.reserve(length + remainingBytes);
}
/* Fragments forming a big message are not caught until appending them */
if (refusePayloadLength(length + webSocketData->fragmentBuffer.length(), webSocketState, s)) {
forceClose(webSocketState, s);
return true;
}
webSocketData->fragmentBuffer.append(data, length);
/* Are we done now? */
// todo: what if we don't have any remaining bytes yet we are not fin? forceclose!
if (!remainingBytes && fin) {
/* Handle compression */
if (webSocketData->compressionStatus == WebSocketData::CompressionStatus::COMPRESSED_FRAME) {
webSocketData->compressionStatus = WebSocketData::CompressionStatus::ENABLED;
// what's really the story here?
webSocketData->fragmentBuffer.append("....");
LoopData *loopData = (LoopData *) us_loop_ext(
us_socket_context_loop(SSL,
us_socket_context(SSL, (us_socket_t *) s)
)
);
std::string_view inflatedFrame = loopData->inflationStream->inflate(loopData->zlibContext, {webSocketData->fragmentBuffer.data(), webSocketData->fragmentBuffer.length() - 4}, webSocketContextData->maxPayloadLength);
if (!inflatedFrame.length()) {
forceClose(webSocketState, s);
return true;
} else {
data = (char *) inflatedFrame.data();
length = inflatedFrame.length();
}
} else {
// reset length and data ptrs
length = webSocketData->fragmentBuffer.length();
data = webSocketData->fragmentBuffer.data();
}
/* Check text messages for Utf-8 validity */
if (opCode == 1 && !protocol::isValidUtf8((unsigned char *) data, length)) {
forceClose(webSocketState, s);
return true;
}
/* Emit message and check for shutdown or close */
if (webSocketContextData->messageHandler) {
webSocketContextData->messageHandler((WebSocket<SSL, isServer> *) s, std::string_view(data, length), (uWS::OpCode) opCode);
if (us_socket_is_closed(SSL, (us_socket_t *) s) || webSocketData->isShuttingDown) {
return true;
}
}
/* If we shutdown or closed, this will be taken care of elsewhere */
webSocketData->fragmentBuffer.clear();
}
}
} else {
/* Control frames need the websocket to send pings, pongs and close */
WebSocket<SSL, isServer> *webSocket = (WebSocket<SSL, isServer> *) s;
if (!remainingBytes && fin && !webSocketData->controlTipLength) {
if (opCode == CLOSE) {
auto closeFrame = protocol::parseClosePayload(data, length);
webSocket->end(closeFrame.code, std::string_view(closeFrame.message, closeFrame.length));
return true;
} else {
if (opCode == PING) {
webSocket->send(std::string_view(data, length), (OpCode) OpCode::PONG);
/*group->pingHandler(webSocket, data, length);
if (webSocket->isClosed() || webSocket->isShuttingDown()) {
return true;
}*/
} else if (opCode == PONG) {
/*group->pongHandler(webSocket, data, length);
if (webSocket->isClosed() || webSocket->isShuttingDown()) {
return true;
}*/
}
}
} else {
/* Here we never mind any size optimizations as we are in the worst possible path */
webSocketData->fragmentBuffer.append(data, length);
webSocketData->controlTipLength += (int) length;
if (!remainingBytes && fin) {
char *controlBuffer = (char *) webSocketData->fragmentBuffer.data() + webSocketData->fragmentBuffer.length() - webSocketData->controlTipLength;
if (opCode == CLOSE) {
protocol::CloseFrame closeFrame = protocol::parseClosePayload(controlBuffer, webSocketData->controlTipLength);
webSocket->end(closeFrame.code, std::string_view(closeFrame.message, closeFrame.length));
return true;
} else {
if (opCode == PING) {
webSocket->send(std::string_view(controlBuffer, webSocketData->controlTipLength), (OpCode) OpCode::PONG);
/*group->pingHandler(webSocket, controlBuffer, webSocket->controlTipLength);
if (webSocket->isClosed() || webSocket->isShuttingDown()) {
return true;
}*/
} else if (opCode == PONG) {
/*group->pongHandler(webSocket, controlBuffer, webSocket->controlTipLength);
if (webSocket->isClosed() || webSocket->isShuttingDown()) {
return true;
}*/
}
}
/* Same here, we do not care for any particular smart allocation scheme */
webSocketData->fragmentBuffer.resize(webSocketData->fragmentBuffer.length() - webSocketData->controlTipLength);
webSocketData->controlTipLength = 0;
}
}
}
return false;
}
static bool refusePayloadLength(uint64_t length, uWS::WebSocketState<isServer> *wState, void *s) {
auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
/* Return true for refuse, false for accept */
return webSocketContextData->maxPayloadLength < length;
}
WebSocketContext<SSL, isServer> *init() {
/* Adopting a socket does not trigger open event.
* We arreive as WebSocket with timeout set and
* any backpressure from HTTP state kept. */
/* Handle socket disconnections */
us_socket_context_on_close(SSL, getSocketContext(), [](auto *s) {
/* For whatever reason, if we already have emitted close event, do not emit it again */
WebSocketData *webSocketData = (WebSocketData *) (us_socket_ext(SSL, s));
if (!webSocketData->isShuttingDown) {
/* Emit close event */
auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
if (webSocketContextData->closeHandler) {
webSocketContextData->closeHandler((WebSocket<SSL, true> *) s, 1006, {});
}
/* Make sure to unsubscribe from any pub/sub node at exit */
webSocketContextData->topicTree.unsubscribeAll(webSocketData->subscriber);
delete webSocketData->subscriber;
webSocketData->subscriber = nullptr;
}
/* Destruct in-placed data struct */
webSocketData->~WebSocketData();
return s;
});
/* Handle WebSocket data streams */
us_socket_context_on_data(SSL, getSocketContext(), [](auto *s, char *data, int length) {
/* We need the websocket data */
WebSocketData *webSocketData = (WebSocketData *) (us_socket_ext(SSL, s));
/* When in websocket shutdown mode, we do not care for ANY message, whether responding close frame or not.
* We only care for the TCP FIN really, not emitting any message after closing is key */
if (webSocketData->isShuttingDown) {
return s;
}
auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
auto *asyncSocket = (AsyncSocket<SSL> *) s;
/* Every time we get data and not in shutdown state we simply reset the timeout */
asyncSocket->timeout(webSocketContextData->idleTimeout);
/* We always cork on data */
asyncSocket->cork();
/* This parser has virtually no overhead */
uWS::WebSocketProtocol<isServer, WebSocketContext<SSL, isServer>>::consume(data, length, (WebSocketState<isServer> *) webSocketData, s);
/* Uncorking a closed socekt is fine, in fact it is needed */
asyncSocket->uncork();
/* If uncorking was successful and we are in shutdown state then send TCP FIN */
if (asyncSocket->getBufferedAmount() == 0) {
/* We can now be in shutdown state */
if (webSocketData->isShuttingDown) {
/* Shutting down a closed socket is handled by uSockets and just fine */
asyncSocket->shutdown();
}
}
return s;
});
/* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */
us_socket_context_on_writable(SSL, getSocketContext(), [](auto *s) {
/* It makes sense to check for us_is_shut_down here and return if so, to avoid shutting down twice */
if (us_socket_is_shut_down(SSL, (us_socket_t *) s)) {
return s;
}
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
WebSocketData *webSocketData = (WebSocketData *)(us_socket_ext(SSL, s));
/* We store old backpressure since it is unclear whether write drained anything */
int backpressure = asyncSocket->getBufferedAmount();
/* Drain as much as possible */
asyncSocket->write(nullptr, 0);
/* Behavior: if we actively drain backpressure, always reset timeout (even if we are in shutdown) */
if (backpressure < asyncSocket->getBufferedAmount()) {
auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
asyncSocket->timeout(webSocketContextData->idleTimeout);
}
/* Are we in (WebSocket) shutdown mode? */
if (webSocketData->isShuttingDown) {
/* Check if we just now drained completely */
if (asyncSocket->getBufferedAmount() == 0) {
/* Now perform the actual TCP/TLS shutdown which was postponed due to backpressure */
asyncSocket->shutdown();
}
} else if (backpressure > asyncSocket->getBufferedAmount()) {
/* Only call drain if we actually drained backpressure */
auto *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, us_socket_context(SSL, (us_socket_t *) s));
if (webSocketContextData->drainHandler) {
webSocketContextData->drainHandler((WebSocket<SSL, isServer> *) s);
}
/* No need to check for closed here as we leave the handler immediately*/
}
return s;
});
/* Handle FIN, HTTP does not support half-closed sockets, so simply close */
us_socket_context_on_end(SSL, getSocketContext(), [](auto *s) {
/* If we get a fin, we just close I guess */
us_socket_close(SSL, (us_socket_t *) s);
return s;
});
/* Handle socket timeouts, simply close them so to not confuse client with FIN */
us_socket_context_on_timeout(SSL, getSocketContext(), [](auto *s) {
/* Timeout is very simple; we just close it */
us_socket_close(SSL, (us_socket_t *) s);
return s;
});
return this;
}
void free() {
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *) this);
webSocketContextData->~WebSocketContextData();
us_socket_context_free(SSL, (us_socket_context_t *) this);
}
public:
/* WebSocket contexts are always child contexts to a HTTP context so no SSL options are needed as they are inherited */
static WebSocketContext *create(Loop *loop, us_socket_context_t *parentSocketContext) {
WebSocketContext *webSocketContext = (WebSocketContext *) us_create_child_socket_context(SSL, parentSocketContext, sizeof(WebSocketContextData<SSL>));
if (!webSocketContext) {
return nullptr;
}
/* Init socket context data */
new ((WebSocketContextData<SSL> *) us_socket_context_ext(SSL, (us_socket_context_t *)webSocketContext)) WebSocketContextData<SSL>;
return webSocketContext->init();
}
};
}
#endif // UWS_WEBSOCKETCONTEXT_H

View File

@ -0,0 +1,100 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_WEBSOCKETCONTEXTDATA_H
#define UWS_WEBSOCKETCONTEXTDATA_H
#include "f2/function2.hpp"
#include <string_view>
#include "WebSocketProtocol.h"
#include "TopicTreeDraft.h"
namespace uWS {
template <bool, bool> struct WebSocket;
/* todo: this looks identical to WebSocketBehavior, why not just std::move that entire thing in? */
template <bool SSL>
struct WebSocketContextData {
/* The callbacks for this context */
fu2::unique_function<void(WebSocket<SSL, true> *, std::string_view, uWS::OpCode)> messageHandler = nullptr;
fu2::unique_function<void(WebSocket<SSL, true> *)> drainHandler = nullptr;
fu2::unique_function<void(WebSocket<SSL, true> *, int, std::string_view)> closeHandler = nullptr;
/* Settings for this context */
size_t maxPayloadLength = 0;
int idleTimeout = 0;
/* There needs to be a maxBackpressure which will force close everything over that limit */
size_t maxBackpressure = 0;
/* Each websocket context has a topic tree for pub/sub */
TopicTree topicTree;
~WebSocketContextData() {
/* We must unregister any loop post handler here */
Loop::get()->removePostHandler(this);
Loop::get()->removePreHandler(this);
}
WebSocketContextData() : topicTree([this](Subscriber *s, std::string_view data) -> int {
/* We rely on writing to regular asyncSockets */
auto *asyncSocket = (AsyncSocket<SSL> *) s->user;
auto [written, failed] = asyncSocket->write(data.data(), (int) data.length());
if (!failed) {
asyncSocket->timeout(this->idleTimeout);
} else {
/* Note: this assumes we are not corked, as corking will swallow things and fail later on */
/* Check if we now have too much backpressure (todo: don't buffer up before check) */
if ((unsigned int) asyncSocket->getBufferedAmount() > maxBackpressure) {
asyncSocket->close();
}
}
/* Reserved, unused */
return 0;
}) {
/* We empty for both pre and post just to make sure */
Loop::get()->addPostHandler(this, [this](Loop *loop) {
/* Commit pub/sub batches every loop iteration */
topicTree.drain();
});
Loop::get()->addPreHandler(this, [this](Loop *loop) {
/* Commit pub/sub batches every loop iteration */
topicTree.drain();
});
}
/* Helper for topictree publish, common path from app and ws */
void publish(std::string_view topic, std::string_view message, OpCode opCode, bool compress) {
/* We frame the message right here and only pass raw bytes to the pub/subber */
char *dst = (char *) malloc(protocol::messageFrameSize(message.size()));
size_t dst_length = protocol::formatMessage<true>(dst, message.data(), message.length(), opCode, message.length(), false);
topicTree.publish(topic, std::string_view(dst, dst_length));
::free(dst);
}
};
}
#endif // UWS_WEBSOCKETCONTEXTDATA_H

View File

@ -0,0 +1,70 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UWS_WEBSOCKETDATA_H
#define UWS_WEBSOCKETDATA_H
#include "WebSocketProtocol.h"
#include "AsyncSocketData.h"
#include "PerMessageDeflate.h"
#include <string>
namespace uWS {
struct WebSocketData : AsyncSocketData<false>, WebSocketState<true> {
template <bool, bool> friend struct WebSocketContext;
template <bool, bool> friend struct WebSocket;
private:
std::string fragmentBuffer;
int controlTipLength = 0;
bool isShuttingDown = 0;
enum CompressionStatus : char {
DISABLED,
ENABLED,
COMPRESSED_FRAME
} compressionStatus;
/* We might have a dedicated compressor */
DeflationStream *deflationStream = nullptr;
/* We could be a subscriber */
Subscriber *subscriber = nullptr;
public:
WebSocketData(bool perMessageDeflate, bool slidingCompression, std::string &&backpressure) : AsyncSocketData<false>(std::move(backpressure)), WebSocketState<true>() {
compressionStatus = perMessageDeflate ? ENABLED : DISABLED;
/* Initialize the dedicated sliding window */
if (perMessageDeflate && slidingCompression) {
deflationStream = new DeflationStream;
}
}
~WebSocketData() {
if (deflationStream) {
delete deflationStream;
}
if (subscriber) {
delete subscriber;
}
}
};
}
#endif // UWS_WEBSOCKETDATA_H

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