From 5f2955ef78edde86f284257462d5ca03416696e3 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 24 Nov 2021 08:45:04 -0800 Subject: [PATCH 01/15] Feature/version 11.3.2 (#329) * mbedls system certs * missing curly brace ... * windows uwp for appveyor * try again uwp * bump version * keep using local cacert.pem in unittest * appveyor back to normal * remove appveyor file Co-authored-by: Benjamin Sergeant --- appveyor.yml | 22 ---------------------- docs/CHANGELOG.md | 4 ++++ ixwebsocket/IXWebSocketVersion.h | 2 +- 3 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 144facec..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,22 +0,0 @@ -image: -- Visual Studio 2017 - -install: -- cd C:\Tools\vcpkg -- git pull -- .\bootstrap-vcpkg.bat -- cd %APPVEYOR_BUILD_FOLDER% -- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" -- vcpkg install zlib:x64-windows -- vcpkg install mbedtls:x64-windows -- mkdir build -- cd build -- cmake -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DUSE_WS=1 -DUSE_TEST=1 -DUSE_TLS=1 -G"NMake Makefiles" .. -- nmake -- cd .. -- cd test -- ..\build\test\ixwebsocket_unittest.exe - -cache: c:\tools\vcpkg\installed\ - -build: off diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 80d5daa2..4bd4c69d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,10 @@ All changes to this project will be documented in this file. +## [11.3.2] - 2021-11-24 + +(server) Add getters for basic Servers properties (like port, host, etc...) (#327) + fix one compiler warning + ## [11.3.1] - 2021-10-22 (library/cmake) Compatible with MbedTLS 3 + fix a bug on Windows where the incorrect remote port is computed (#320) diff --git a/ixwebsocket/IXWebSocketVersion.h b/ixwebsocket/IXWebSocketVersion.h index 5c338a7e..ccaec845 100644 --- a/ixwebsocket/IXWebSocketVersion.h +++ b/ixwebsocket/IXWebSocketVersion.h @@ -6,4 +6,4 @@ #pragma once -#define IX_WEBSOCKET_VERSION "11.3.1" +#define IX_WEBSOCKET_VERSION "11.3.2" From 23dbc8ae636710edb32f53e9e5b7cc9686df6c45 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 24 Nov 2021 08:56:14 -0800 Subject: [PATCH 02/15] Update mkdocs.yml Try a new version of the checkout action. --- .github/workflows/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index 7000643c..ecc24517 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -8,7 +8,7 @@ jobs: linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python 3.8 uses: actions/setup-python@v1 with: From 05a27c89e31cddd9b60edfbad7dd804c8c9cb482 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 24 Nov 2021 09:00:23 -0800 Subject: [PATCH 03/15] Update design.md --- docs/design.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/design.md b/docs/design.md index 5c3dbb13..2991712d 100644 --- a/docs/design.md +++ b/docs/design.md @@ -30,12 +30,6 @@ The unittest tries to be comprehensive, and has been running on multiple platfor The regression test is running after each commit on github actions for multiple configurations. -* Linux -* macOS with thread sanitizer -* macOS, with OpenSSL, with thread sanitizer -* macOS, with MbedTLS, with thread sanitizer -* Windows, with MbedTLS (the unittest is not run yet) - ## Limitations * On some configuration (mostly Android) certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. With mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`. From 9c6dcb24a919d8e4409f9e7f02135656e52eb669 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 24 Nov 2021 09:01:10 -0800 Subject: [PATCH 04/15] Update mkdocs.yml --- .github/workflows/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index ecc24517..e284c8b5 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -8,7 +8,7 @@ jobs: linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Set up Python 3.8 uses: actions/setup-python@v1 with: From 6cce06602144a8bf36b4360ff1eb96cc09b34e97 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 24 Nov 2021 09:02:42 -0800 Subject: [PATCH 05/15] Update index.md --- docs/index.md | 159 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 122 insertions(+), 37 deletions(-) diff --git a/docs/index.md b/docs/index.md index 0f1f5a01..ff07c65c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,54 +1,111 @@ -## Introduction +## Hello world -[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client and server HTTP communication. *TLS* aka *SSL* is supported. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms. +IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing. -* macOS -* iOS -* Linux -* Android -* Windows -* FreeBSD +It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Note that the MinGW compiler is not supported at this point. Two important design goals are simplicity and correctness. -## Example code +A bad security bug affecting users compiling with SSL enabled and OpenSSL as the backend was just fixed in newly released version 11.0.0. Please upgrade ! (more details in the [https://github.com/machinezone/IXWebSocket/pull/250](PR). -```c++ -// Required on Windows -ix::initNetSystem(); +```cpp +/* + * main.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2020 Machine Zone, Inc. All rights reserved. + * + * Super simple standalone example. See ws folder, unittest and doc/usage.md for more. + * + * On macOS + * $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install) + * $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation + * $ ./a.out + * + * Or use cmake -DBUILD_DEMO=ON option for other platforms + */ -// Our websocket object -ix::WebSocket webSocket; +#include +#include +#include +#include -std::string url("ws://localhost:8080/"); -webSocket.setUrl(url); +int main() +{ + // Required on Windows + ix::initNetSystem(); -// Setup a callback to be fired when a message or an event (open, close, error) is received -webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) - { - if (msg->type == ix::WebSocketMessageType::Message) + // Our websocket object + ix::WebSocket webSocket; + + // Connect to a server with encryption + // See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration + std::string url("wss://echo.websocket.org"); + webSocket.setUrl(url); + + std::cout << "Connecting to " << url << "..." << std::endl; + + // Setup a callback to be fired (in a background thread, watch out for race conditions !) + // when a message or an event (open, close, error) is received + webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) { - std::cout << msg->str << std::endl; + if (msg->type == ix::WebSocketMessageType::Message) + { + std::cout << "received message: " << msg->str << std::endl; + std::cout << "> " << std::flush; + } + else if (msg->type == ix::WebSocketMessageType::Open) + { + std::cout << "Connection established" << std::endl; + std::cout << "> " << std::flush; + } + else if (msg->type == ix::WebSocketMessageType::Error) + { + // Maybe SSL is not configured properly + std::cout << "Connection error: " << msg->errorInfo.reason << std::endl; + std::cout << "> " << std::flush; + } } + ); + + // Now that our callback is setup, we can start our background thread and receive messages + webSocket.start(); + + // Send a message to the server (default to TEXT mode) + webSocket.send("hello world"); + + // Display a prompt + std::cout << "> " << std::flush; + + std::string text; + // Read text from the console and send messages in text mode. + // Exit with Ctrl-D on Unix or Ctrl-Z on Windows. + while (std::getline(std::cin, text)) + { + webSocket.send(text); + std::cout << "> " << std::flush; } -); -// Now that our callback is setup, we can start our background thread and receive messages -webSocket.start(); - -// Send a message to the server (default to TEXT mode) -webSocket.send("hello world"); + return 0; +} ``` -## Why another library? +Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem. -There are 2 main reasons that explain why IXWebSocket got written. First, we needed a C++ cross-platform client library, which should have few dependencies. What looked like the most solid one, [websocketpp](https://github.com/zaphoyd/websocketpp) did depend on boost and this was not an option for us. Secondly, there were other available libraries with fewer dependencies (C ones), but they required calling an explicit poll routine periodically to know if a client had received data from a server, which was not elegant. +IXWebSocket is actively being developed, check out the [changelog](https://machinezone.github.io/IXWebSocket/CHANGELOG/) to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at [cobra](https://github.com/machinezone/cobra). -We started by solving those 2 problems, then we added server websocket code, then an HTTP client, and finally a very simple HTTP server. IXWebSocket comes with a command line utility named ws which is quite handy, and is now packaged with alpine linux. You can install it with `apk add ws`. +IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code. -* Few dependencies (only zlib) -* Simple to use ; uses std::string and std::function callbacks. -* Complete support of the websocket protocol, and basic http support. -* Client and Server -* TLS support +Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible. + +## Users + +If your company or project is using this library, feel free to open an issue or PR to amend this list. + +- [Machine Zone](https://www.mz.com) +- [Tokio](https://gitlab.com/HCInk/tokio), a discord library focused on audio playback with node bindings. +- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework. +- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod +- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper +- [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots +- [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar ## Alternative libraries @@ -58,7 +115,35 @@ There are plenty of great websocket libraries out there, which might work for yo * [beast](https://github.com/boostorg/beast) - C++ * [libwebsockets](https://libwebsockets.org/) - C * [µWebSockets](https://github.com/uNetworking/uWebSockets) - C +* [wslay](https://github.com/tatsuhiro-t/wslay) - C -## Contributing +[uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv. -IXWebSocket is developed on [GitHub](https://github.com/machinezone/IXWebSocket). We'd love to hear about how you use it; opening up an issue on GitHub is ok for that. If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem. +To check the performance of a websocket library, you can look at the [autoroute](https://github.com/bsergean/autoroute) project. + +## Continuous Integration + +| OS | TLS | Sanitizer | Status | +|-------------------|-------------------|-------------------|-------------------| +| Linux | OpenSSL | None | [![Build2][1]][0] | +| macOS | Secure Transport | Thread Sanitizer | [![Build2][2]][0] | +| macOS | OpenSSL | Thread Sanitizer | [![Build2][3]][0] | +| macOS | MbedTLS | Thread Sanitizer | [![Build2][4]][0] | +| Windows | Disabled | None | [![Build2][5]][0] | +| UWP | Disabled | None | [![Build2][6]][0] | +| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] | +| Mingw | Disabled | None | [![Build2][8]][0] | + +* ASAN fails on Linux because of a known problem, we need a +* Some tests are disabled on Windows/UWP because of a pathing problem +* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg. + +[0]: https://github.com/machinezone/IXWebSocket +[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg +[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg +[3]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg +[4]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg +[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg +[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg +[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg +[8]: https://github.com/machinezone/IXWebSocket/workflows/unittest_windows_gcc/badge.svg From 42db05a38b9c01f9cec16f39f2ce675a3522c68d Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 24 Nov 2021 09:05:54 -0800 Subject: [PATCH 06/15] Update mkdocs.yml --- .github/workflows/mkdocs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml index e284c8b5..9cd57b3c 100644 --- a/.github/workflows/mkdocs.yml +++ b/.github/workflows/mkdocs.yml @@ -1,6 +1,8 @@ name: mkdocs on: push: + branches: + - master paths: - 'docs/**' @@ -8,7 +10,7 @@ jobs: linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - name: Set up Python 3.8 uses: actions/setup-python@v1 with: @@ -21,6 +23,7 @@ jobs: pip install pygments - name: Build doc run: | + git checkout master git clean -dfx . git fetch git pull From 71f73e5f6ec904c1b06027a611a0e5aa0a333b75 Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Thu, 2 Dec 2021 22:49:12 -0800 Subject: [PATCH 07/15] Update README (add another project using the lib) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8f48931c..7d957b8a 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ If your company or project is using this library, feel free to open an issue or - [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper - [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots - [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar +- [Abaddon](https://github.com/uowuo/abaddon), An alternative Discord client made with C++/gtkmm ## Alternative libraries From 688f85fda67fa2de04f053548b1229ccda916a8e Mon Sep 17 00:00:00 2001 From: Matthew Gordon Date: Tue, 21 Dec 2021 01:59:15 -0500 Subject: [PATCH 08/15] Fix errors in example code. (#336) --- docs/usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index f3686ee0..62a7adf2 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -141,9 +141,9 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg) { std::stringstream ss; ss << "Error: " << msg->errorInfo.reason << std::endl; - ss << "#retries: " << msg->eventInfo.retries << std::endl; - ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl; - ss << "HTTP Status: " << msg->eventInfo.http_status << std::endl; + ss << "#retries: " << msg->errorInfo.retries << std::endl; + ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl; + ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl; std::cout << ss.str() << std::endl; } } From 66cd29e747a125240064374f8f70d8c1be92a8f6 Mon Sep 17 00:00:00 2001 From: Martin Natano Date: Tue, 21 Dec 2021 08:01:55 +0100 Subject: [PATCH 09/15] Allow to cancel asynchronous HTTP requests (#332) Usage: auto args = this->httpClient.createRequest(url, method); httpClient.performRequest(args, ...); [...] // Oops, we don't actually want to complete the request! args->cancel = true; --- docs/usage.md | 3 ++ ixwebsocket/IXHttp.h | 3 ++ ixwebsocket/IXHttpClient.cpp | 33 ++++++++++++++-------- test/IXHttpClientTest.cpp | 53 ++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 62a7adf2..ea4116bd 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -517,6 +517,9 @@ bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response) ); // ok will be false if your httpClient is not async + +// A request in progress can be cancelled by setting the cancel flag. It does nothing if the request already completed. +args->cancel = true; ``` See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart. diff --git a/ixwebsocket/IXHttp.h b/ixwebsocket/IXHttp.h index bfdaefcc..78293c7f 100644 --- a/ixwebsocket/IXHttp.h +++ b/ixwebsocket/IXHttp.h @@ -8,6 +8,7 @@ #include "IXProgressCallback.h" #include "IXWebSocketHttpHeaders.h" +#include #include #include @@ -30,6 +31,7 @@ namespace ix TooManyRedirects = 12, ChunkReadError = 13, CannotReadBody = 14, + Cancelled = 15, Invalid = 100 }; @@ -87,6 +89,7 @@ namespace ix bool compressRequest = false; Logger logger; OnProgressCallback onProgressCallback; + std::atomic cancel; }; using HttpRequestArgsPtr = std::shared_ptr; diff --git a/ixwebsocket/IXHttpClient.cpp b/ixwebsocket/IXHttpClient.cpp index 3be8b8b7..bb876db7 100644 --- a/ixwebsocket/IXHttpClient.cpp +++ b/ixwebsocket/IXHttpClient.cpp @@ -241,17 +241,21 @@ namespace ix std::string errMsg; // Make a cancellation object dealing with connection timeout - auto isCancellationRequested = - makeCancellationRequestWithTimeout(args->connectTimeout, _stop); + auto cancelled = makeCancellationRequestWithTimeout(args->connectTimeout, args->cancel); + + auto isCancellationRequested = [&]() { + return cancelled() || _stop; + }; bool success = _socket->connect(host, port, errMsg, isCancellationRequested); if (!success) { + auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotConnect; std::stringstream ss; ss << "Cannot connect to url: " << url << " / error : " << errMsg; return std::make_shared(code, description, - HttpErrorCode::CannotConnect, + errorCode, headers, payload, ss.str(), @@ -260,7 +264,7 @@ namespace ix } // Make a new cancellation object dealing with transfer timeout - isCancellationRequested = makeCancellationRequestWithTimeout(args->transferTimeout, _stop); + cancelled = makeCancellationRequestWithTimeout(args->transferTimeout, args->cancel); if (args->verbose) { @@ -277,10 +281,11 @@ namespace ix if (!_socket->writeBytes(req, isCancellationRequested)) { + auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::SendError; std::string errorMsg("Cannot send request"); return std::make_shared(code, description, - HttpErrorCode::SendError, + errorCode, headers, payload, errorMsg, @@ -296,10 +301,11 @@ namespace ix if (!lineValid) { + auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotReadStatusLine; std::string errorMsg("Cannot retrieve status line"); return std::make_shared(code, description, - HttpErrorCode::CannotReadStatusLine, + errorCode, headers, payload, errorMsg, @@ -333,10 +339,11 @@ namespace ix if (!headersValid) { + auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::HeaderParsingError; std::string errorMsg("Cannot parse http headers"); return std::make_shared(code, description, - HttpErrorCode::HeaderParsingError, + errorCode, headers, payload, errorMsg, @@ -405,10 +412,11 @@ namespace ix contentLength, args->onProgressCallback, isCancellationRequested); if (!chunkResult.first) { + auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError; errorMsg = "Cannot read chunk"; return std::make_shared(code, description, - HttpErrorCode::ChunkReadError, + errorCode, headers, payload, errorMsg, @@ -424,6 +432,7 @@ namespace ix while (true) { + auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError; lineResult = _socket->readLine(isCancellationRequested); line = lineResult.second; @@ -431,7 +440,7 @@ namespace ix { return std::make_shared(code, description, - HttpErrorCode::ChunkReadError, + errorCode, headers, payload, errorMsg, @@ -458,10 +467,11 @@ namespace ix (size_t) chunkSize, args->onProgressCallback, isCancellationRequested); if (!chunkResult.first) { + auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError; errorMsg = "Cannot read chunk"; return std::make_shared(code, description, - HttpErrorCode::ChunkReadError, + errorCode, headers, payload, errorMsg, @@ -475,9 +485,10 @@ namespace ix if (!lineResult.first) { + auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError; return std::make_shared(code, description, - HttpErrorCode::ChunkReadError, + errorCode, headers, payload, errorMsg, diff --git a/test/IXHttpClientTest.cpp b/test/IXHttpClientTest.cpp index 735dcdb1..722f2f8c 100644 --- a/test/IXHttpClientTest.cpp +++ b/test/IXHttpClientTest.cpp @@ -221,4 +221,57 @@ TEST_CASE("http_client", "[http]") REQUIRE(statusCode1 == 200); REQUIRE(statusCode2 == 200); } + + SECTION("Async API, cancel") + { + bool async = true; + HttpClient httpClient(async); + WebSocketHttpHeaders headers; + + SocketTLSOptions tlsOptions; + tlsOptions.caFile = "cacert.pem"; + httpClient.setTLSOptions(tlsOptions); + + std::string url("http://httpbin.org/delay/10"); + auto args = httpClient.createRequest(url); + + args->extraHeaders = headers; + args->connectTimeout = 60; + args->transferTimeout = 60; + args->followRedirects = true; + args->maxRedirects = 10; + args->verbose = true; + args->compress = true; + args->logger = [](const std::string& msg) { std::cout << msg; }; + args->onProgressCallback = [](int current, int total) -> bool { + std::cerr << "\r" + << "Downloaded " << current << " bytes out of " << total; + return true; + }; + + std::atomic requestCompleted(false); + std::atomic errorCode(HttpErrorCode::Invalid); + + httpClient.performRequest( + args, [&requestCompleted, &errorCode](const HttpResponsePtr& response) { + errorCode = response->errorCode; + requestCompleted = true; + }); + + // cancel immediately + args->cancel = true; + + int wait = 0; + while (wait < 5000) + { + if (requestCompleted) break; + + std::chrono::duration duration(10); + std::this_thread::sleep_for(duration); + wait += 10; + } + + std::cerr << "Done" << std::endl; + REQUIRE(errorCode == HttpErrorCode::Cancelled); + } } From 5457217503f2d9ea2cd2084a95aade0c74e0825e Mon Sep 17 00:00:00 2001 From: svost Date: Thu, 23 Dec 2021 09:48:20 +0300 Subject: [PATCH 10/15] Improved compatibility - fix mingw crossbuild (#337) --- ixwebsocket/IXNetSystem.h | 4 ++-- ixwebsocket/IXSetThreadName.cpp | 2 +- ixwebsocket/IXSocket.h | 2 +- ixwebsocket/IXSocketOpenSSL.cpp | 2 +- ixwebsocket/IXUdpSocket.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ixwebsocket/IXNetSystem.h b/ixwebsocket/IXNetSystem.h index b2220a95..21131b4e 100644 --- a/ixwebsocket/IXNetSystem.h +++ b/ixwebsocket/IXNetSystem.h @@ -12,8 +12,8 @@ #define WIN32_LEAN_AND_MEAN #endif -#include -#include +#include +#include #include #include #include diff --git a/ixwebsocket/IXSetThreadName.cpp b/ixwebsocket/IXSetThreadName.cpp index 40faf9d9..b1e77379 100644 --- a/ixwebsocket/IXSetThreadName.cpp +++ b/ixwebsocket/IXSetThreadName.cpp @@ -17,7 +17,7 @@ // Windows #ifdef _WIN32 -#include +#include #endif namespace ix diff --git a/ixwebsocket/IXSocket.h b/ixwebsocket/IXSocket.h index 5604c40d..393d5bc4 100644 --- a/ixwebsocket/IXSocket.h +++ b/ixwebsocket/IXSocket.h @@ -13,7 +13,7 @@ #include #ifdef _WIN32 -#include +#include typedef SSIZE_T ssize_t; #endif diff --git a/ixwebsocket/IXSocketOpenSSL.cpp b/ixwebsocket/IXSocketOpenSSL.cpp index c92477a1..0028707e 100644 --- a/ixwebsocket/IXSocketOpenSSL.cpp +++ b/ixwebsocket/IXSocketOpenSSL.cpp @@ -15,7 +15,7 @@ #include #include #ifdef _WIN32 -#include +#include #else #include #endif diff --git a/ixwebsocket/IXUdpSocket.h b/ixwebsocket/IXUdpSocket.h index 048f9fc2..35f8fec4 100644 --- a/ixwebsocket/IXUdpSocket.h +++ b/ixwebsocket/IXUdpSocket.h @@ -11,7 +11,7 @@ #include #ifdef _WIN32 -#include +#include typedef SSIZE_T ssize_t; #endif From 8c15405ed03987e31f0b2da056751eaf1902b6ee Mon Sep 17 00:00:00 2001 From: Benjamin Sergeant Date: Wed, 22 Dec 2021 22:52:53 -0800 Subject: [PATCH 11/15] Add a reference to NovaCoin in the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7d957b8a..a6920948 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ If your company or project is using this library, feel free to open an issue or - [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots - [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar - [Abaddon](https://github.com/uowuo/abaddon), An alternative Discord client made with C++/gtkmm +- [NovaCoin](https://github.com/novacoin-project/novacoin), a hybrid scrypt PoW + PoS based cryptocurrency. ## Alternative libraries From 47d0b70ebf8704a29a87fa714bc4346e50af1a38 Mon Sep 17 00:00:00 2001 From: CryptoManiac Date: Tue, 4 Jan 2022 23:13:19 +0300 Subject: [PATCH 12/15] Include to provide standard error constants (#338) See https://en.cppreference.com/w/cpp/header/cerrno for additional details. Some of used constants are defined in this header. Inclusion is necessary to avoid these errors: ``` /home/user/IXWebSocket/ixwebsocket/IXNetSystem.cpp:189:30: error: use of undeclared identifier 'EAFNOSUPPORT' default: errno = EAFNOSUPPORT; return 0; ^ /home/user/IXWebSocket/ixwebsocket/IXNetSystem.cpp:191:17: error: use of undeclared identifier 'ENOSPC' errno = ENOSPC; ^ /home/user/IXWebSocket/ixwebsocket/IXNetSystem.cpp:175:25: warning: implicit conversion loses integer precision: 'size_t' (aka 'unsigned long long') to 'int' [-Wshorten-64-to-32] j = strspn(buf + i, ":0"); ~ ^~~~~~~~~~~~~~~~~~~~~ /home/user/IXWebSocket/ixwebsocket/IXNetSystem.cpp:234:21: error: use of undeclared identifier 'EAFNOSUPPORT' errno = EAFNOSUPPORT; ^ 2 warnings and 3 errors generated. ``` --- ixwebsocket/IXNetSystem.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ixwebsocket/IXNetSystem.h b/ixwebsocket/IXNetSystem.h index 21131b4e..7ca58f49 100644 --- a/ixwebsocket/IXNetSystem.h +++ b/ixwebsocket/IXNetSystem.h @@ -17,6 +17,7 @@ #include #include #include +#include #undef EWOULDBLOCK #undef EAGAIN From f53b2f887845fdcc781f8573b9871458e577d365 Mon Sep 17 00:00:00 2001 From: CryptoManiac Date: Tue, 4 Jan 2022 23:13:38 +0300 Subject: [PATCH 13/15] Export symbols into .def files on MSVC (#339) Fix #335 --- CMakeLists.txt | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72e5ac45..8d8b89d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,10 +148,29 @@ if (USE_TLS) endif() endif() -add_library( ixwebsocket - ${IXWEBSOCKET_SOURCES} - ${IXWEBSOCKET_HEADERS} -) +if(BUILD_SHARED_LIBS) + # Building shared library + + if(MSVC) + # Workaround for some projects + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + endif() + + add_library( ixwebsocket SHARED + ${IXWEBSOCKET_SOURCES} + ${IXWEBSOCKET_HEADERS} + ) + + # Set library version + set_target_properties(ixwebsocket PROPERTIES VERSION 11.3.2) + +else() + # Static library + add_library( ixwebsocket + ${IXWEBSOCKET_SOURCES} + ${IXWEBSOCKET_HEADERS} + ) +endif() if (USE_TLS) target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_TLS) From 9f00428d57e604379382cfbace31347ef6b5ab22 Mon Sep 17 00:00:00 2001 From: Andreas Hausladen Date: Tue, 4 Jan 2022 21:25:18 +0100 Subject: [PATCH 14/15] Fix "HTTP/1.1 400 Illegal character CNTL=0xf" caused by serverMaxWindowBits/clientMaxWindowBits being uint8_t (signed char). (#341) --- ixwebsocket/IXSocketTLSOptions.cpp | 2 +- ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ixwebsocket/IXSocketTLSOptions.cpp b/ixwebsocket/IXSocketTLSOptions.cpp index 4156920a..5800ccdc 100644 --- a/ixwebsocket/IXSocketTLSOptions.cpp +++ b/ixwebsocket/IXSocketTLSOptions.cpp @@ -87,7 +87,7 @@ namespace ix ss << " keyFile = " << keyFile << std::endl; ss << " caFile = " << caFile << std::endl; ss << " ciphers = " << ciphers << std::endl; - ss << " ciphers = " << ciphers << std::endl; + ss << " tls = " << tls << std::endl; return ss.str(); } } // namespace ix diff --git a/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp b/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp index c41a8c3d..f7dcc35e 100644 --- a/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp +++ b/ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp @@ -127,8 +127,8 @@ namespace ix if (_clientNoContextTakeover) ss << "; client_no_context_takeover"; if (_serverNoContextTakeover) ss << "; server_no_context_takeover"; - ss << "; server_max_window_bits=" << _serverMaxWindowBits; - ss << "; client_max_window_bits=" << _clientMaxWindowBits; + ss << "; server_max_window_bits=" << static_cast(_serverMaxWindowBits); + ss << "; client_max_window_bits=" << static_cast(_clientMaxWindowBits); ss << "\r\n"; From 1f2895a4693b11176df1c410c3b9c75208f9987e Mon Sep 17 00:00:00 2001 From: Andreas Hausladen Date: Wed, 5 Jan 2022 19:21:33 +0100 Subject: [PATCH 15/15] Win wsa select event (#342) * Fix #323: Missing SelectInterrupt implementation for Windows Using WSAEventSelect, WSAWaitForMultipleEvents and WSAEnumNetworkEvents to emulate poll() with an interrupt-event. * Cleanup * Fixed incomplete comment. * Switched ifdefs to support other Unixes with pipe file descriptors * Fixed: SelectInterrupt fallback code for getFd()==-1 && getEvent()==nullptr converted a PollResultType::Timeout into a ReadyForRead causing the HttpClient to fail because it uses a hard-coded "SelectInterrupt" instance that doesn't implement getFd() and getEvent(). * Fixed gcc compile errors * - HttpClient now uses the SelectInterruptFactory - Fixed wrong ix::poll result when using Windows WSA functions * We must deselect the networkevents from the socket event. Otherwise the socket will report states that aren't there. --- CMakeLists.txt | 2 + ixwebsocket/IXNetSystem.cpp | 249 +++++++++++++++++++---- ixwebsocket/IXNetSystem.h | 2 +- ixwebsocket/IXSelectInterrupt.cpp | 5 + ixwebsocket/IXSelectInterrupt.h | 1 + ixwebsocket/IXSelectInterruptEvent.cpp | 84 ++++++++ ixwebsocket/IXSelectInterruptEvent.h | 39 ++++ ixwebsocket/IXSelectInterruptFactory.cpp | 12 +- ixwebsocket/IXSocket.cpp | 63 ++++-- ixwebsocket/IXSocket.h | 4 + ixwebsocket/IXSocketConnect.cpp | 3 +- ixwebsocket/IXWebSocketTransport.cpp | 6 +- 12 files changed, 400 insertions(+), 70 deletions(-) create mode 100644 ixwebsocket/IXSelectInterruptEvent.cpp create mode 100644 ixwebsocket/IXSelectInterruptEvent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d8b89d1..0c4fb61a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ set( IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterrupt.cpp ixwebsocket/IXSelectInterruptFactory.cpp ixwebsocket/IXSelectInterruptPipe.cpp + ixwebsocket/IXSelectInterruptEvent.cpp ixwebsocket/IXSetThreadName.cpp ixwebsocket/IXSocket.cpp ixwebsocket/IXSocketConnect.cpp @@ -80,6 +81,7 @@ set( IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterrupt.h ixwebsocket/IXSelectInterruptFactory.h ixwebsocket/IXSelectInterruptPipe.h + ixwebsocket/IXSelectInterruptEvent.h ixwebsocket/IXSetThreadName.h ixwebsocket/IXSocket.h ixwebsocket/IXSocketConnect.h diff --git a/ixwebsocket/IXNetSystem.cpp b/ixwebsocket/IXNetSystem.cpp index ab7b2a38..7b2a8541 100644 --- a/ixwebsocket/IXNetSystem.cpp +++ b/ixwebsocket/IXNetSystem.cpp @@ -7,6 +7,9 @@ #include "IXNetSystem.h" #include #include +#ifdef _WIN32 +#include +#endif namespace ix { @@ -37,6 +40,51 @@ namespace ix #endif } +#ifdef _WIN32 + struct WSAEvent + { + public: + WSAEvent(struct pollfd* fd) + : _fd(fd) + { + _event = WSACreateEvent(); + } + + WSAEvent(WSAEvent&& source) noexcept + { + _event = source._event; + source._event = WSA_INVALID_EVENT; // invalidate the event in the source + _fd = source._fd; + } + + ~WSAEvent() + { + if (_event != WSA_INVALID_EVENT) + { + // We must deselect the networkevents from the socket event. Otherwise the + // socket will report states that aren't there. + if (_fd != nullptr && _fd->fd != -1) + WSAEventSelect(_fd->fd, _event, 0); + WSACloseEvent(_event); + } + } + + operator HANDLE() + { + return _event; + } + + operator struct pollfd*() + { + return _fd; + } + + private: + HANDLE _event; + struct pollfd* _fd; + }; +#endif + // // That function could 'return WSAPoll(pfd, nfds, timeout);' // but WSAPoll is said to have weird behaviors on the internet @@ -44,69 +92,180 @@ namespace ix // // So we make it a select wrapper // - int poll(struct pollfd* fds, nfds_t nfds, int timeout) + // UPDATE: WSAPoll was fixed in Windows 10 Version 2004 + // + // The optional "event" is set to nullptr if it wasn't signaled. + int poll(struct pollfd* fds, nfds_t nfds, int timeout, void** event) { #ifdef _WIN32 - socket_t maxfd = 0; - fd_set readfds, writefds, errorfds; - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&errorfds); - for (nfds_t i = 0; i < nfds; ++i) + if (event && *event) { - struct pollfd* fd = &fds[i]; + HANDLE interruptEvent = reinterpret_cast(*event); + *event = nullptr; // the event wasn't signaled yet - if (fd->fd > maxfd) + if (nfds < 0 || nfds >= MAXIMUM_WAIT_OBJECTS - 1) { - maxfd = fd->fd; + WSASetLastError(WSAEINVAL); + return SOCKET_ERROR; } - if ((fd->events & POLLIN)) + + std::vector socketEvents; + std::vector handles; + // put the interrupt event as first element, making it highest priority + handles.push_back(interruptEvent); + + // create the WSAEvents for the sockets + for (nfds_t i = 0; i < nfds; ++i) { - FD_SET(fd->fd, &readfds); + struct pollfd* fd = &fds[i]; + fd->revents = 0; + if (fd->fd >= 0) + { + // create WSAEvent and add it to the vectors + socketEvents.push_back(std::move(WSAEvent(fd))); + HANDLE handle = socketEvents.back(); + if (handle == WSA_INVALID_EVENT) + { + WSASetLastError(WSAENOBUFS); + return SOCKET_ERROR; + } + handles.push_back(handle); + + // mapping + long networkEvents = 0; + if (fd->events & (POLLIN )) networkEvents |= FD_READ | FD_ACCEPT; + if (fd->events & (POLLOUT /*| POLLWRNORM | POLLWRBAND*/)) networkEvents |= FD_WRITE | FD_CONNECT; + //if (fd->events & (POLLPRI | POLLRDBAND )) networkEvents |= FD_OOB; + + if (WSAEventSelect(fd->fd, handle, networkEvents) != 0) + { + fd->revents = POLLNVAL; + socketEvents.pop_back(); + handles.pop_back(); + } + } } - if ((fd->events & POLLOUT)) + + DWORD n = WSAWaitForMultipleEvents(handles.size(), handles.data(), FALSE, timeout != -1 ? static_cast(timeout) : WSA_INFINITE, FALSE); + + if (n == WSA_WAIT_FAILED) return SOCKET_ERROR; + if (n == WSA_WAIT_TIMEOUT) return 0; + if (n == WSA_WAIT_EVENT_0) { - FD_SET(fd->fd, &writefds); + // the interrupt event was signaled + *event = reinterpret_cast(interruptEvent); + return 1; } - if ((fd->events & POLLERR)) + + int handleIndex = n - WSA_WAIT_EVENT_0; + int socketIndex = handleIndex - 1; + + WSANETWORKEVENTS netEvents; + int count = 0; + // WSAWaitForMultipleEvents returns the index of the first signaled event. And to emulate WSAPoll() + // all the signaled events must be processed. + while (socketIndex < socketEvents.size()) { - FD_SET(fd->fd, &errorfds); + struct pollfd* fd = socketEvents[socketIndex]; + + memset(&netEvents, 0, sizeof(netEvents)); + if (WSAEnumNetworkEvents(fd->fd, socketEvents[socketIndex], &netEvents) != 0) + { + fd->revents = POLLERR; + } + else if (netEvents.lNetworkEvents != 0) + { + // mapping + if (netEvents.lNetworkEvents & (FD_READ | FD_ACCEPT | FD_OOB)) fd->revents |= POLLIN; + if (netEvents.lNetworkEvents & (FD_WRITE | FD_CONNECT )) fd->revents |= POLLOUT; + + for (int i = 0; i < FD_MAX_EVENTS; ++i) + { + if (netEvents.iErrorCode[i] != 0) + { + fd->revents |= POLLERR; + break; + } + } + + if (fd->revents != 0) + { + // only signaled sockets count + count++; + } + } + socketIndex++; } + + return count; } - - struct timeval tv; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; - - int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL); - - if (ret < 0) + else { + if (event && *event) *event = nullptr; + + socket_t maxfd = 0; + fd_set readfds, writefds, errorfds; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&errorfds); + + for (nfds_t i = 0; i < nfds; ++i) + { + struct pollfd* fd = &fds[i]; + + if (fd->fd > maxfd) + { + maxfd = fd->fd; + } + if ((fd->events & POLLIN)) + { + FD_SET(fd->fd, &readfds); + } + if ((fd->events & POLLOUT)) + { + FD_SET(fd->fd, &writefds); + } + if ((fd->events & POLLERR)) + { + FD_SET(fd->fd, &errorfds); + } + } + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL); + + if (ret < 0) + { + return ret; + } + + for (nfds_t i = 0; i < nfds; ++i) + { + struct pollfd* fd = &fds[i]; + fd->revents = 0; + + if (FD_ISSET(fd->fd, &readfds)) + { + fd->revents |= POLLIN; + } + if (FD_ISSET(fd->fd, &writefds)) + { + fd->revents |= POLLOUT; + } + if (FD_ISSET(fd->fd, &errorfds)) + { + fd->revents |= POLLERR; + } + } return ret; } - - for (nfds_t i = 0; i < nfds; ++i) - { - struct pollfd* fd = &fds[i]; - fd->revents = 0; - - if (FD_ISSET(fd->fd, &readfds)) - { - fd->revents |= POLLIN; - } - if (FD_ISSET(fd->fd, &writefds)) - { - fd->revents |= POLLOUT; - } - if (FD_ISSET(fd->fd, &errorfds)) - { - fd->revents |= POLLERR; - } - } - - return ret; #else + if (event && *event) *event = nullptr; + // // It was reported that on Android poll can fail and return -1 with // errno == EINTR, which should be a temp error and should typically diff --git a/ixwebsocket/IXNetSystem.h b/ixwebsocket/IXNetSystem.h index 7ca58f49..96395443 100644 --- a/ixwebsocket/IXNetSystem.h +++ b/ixwebsocket/IXNetSystem.h @@ -78,7 +78,7 @@ namespace ix bool initNetSystem(); bool uninitNetSystem(); - int poll(struct pollfd* fds, nfds_t nfds, int timeout); + int poll(struct pollfd* fds, nfds_t nfds, int timeout, void** event); const char* inet_ntop(int af, const void* src, char* dst, socklen_t size); int inet_pton(int af, const char* src, void* dst); diff --git a/ixwebsocket/IXSelectInterrupt.cpp b/ixwebsocket/IXSelectInterrupt.cpp index 36dc66c9..df38bc50 100644 --- a/ixwebsocket/IXSelectInterrupt.cpp +++ b/ixwebsocket/IXSelectInterrupt.cpp @@ -45,4 +45,9 @@ namespace ix { return -1; } + + void* SelectInterrupt::getEvent() const + { + return nullptr; + } } // namespace ix diff --git a/ixwebsocket/IXSelectInterrupt.h b/ixwebsocket/IXSelectInterrupt.h index c3bb7f3f..de6db127 100644 --- a/ixwebsocket/IXSelectInterrupt.h +++ b/ixwebsocket/IXSelectInterrupt.h @@ -24,6 +24,7 @@ namespace ix virtual bool clear(); virtual uint64_t read(); virtual int getFd() const; + virtual void* getEvent() const; // Used as special codes for pipe communication static const uint64_t kSendRequest; diff --git a/ixwebsocket/IXSelectInterruptEvent.cpp b/ixwebsocket/IXSelectInterruptEvent.cpp new file mode 100644 index 00000000..d23e64ee --- /dev/null +++ b/ixwebsocket/IXSelectInterruptEvent.cpp @@ -0,0 +1,84 @@ +/* + * IXSelectInterruptEvent.cpp + */ + +// +// On Windows we use a Windows Event to wake up ix::poll() (WSAWaitForMultipleEvents). +// And on any other platform that doesn't support pipe file descriptors we +// emulate the interrupt event by using a short timeout with ix::poll() and +// read from the SelectInterrupt. (see Socket::poll() "Emulation mode") +// +#include "IXSelectInterruptEvent.h" + +namespace ix +{ + SelectInterruptEvent::SelectInterruptEvent() + { +#ifdef _WIN32 + _event = CreateEvent(NULL, TRUE, FALSE, NULL); +#endif + } + + SelectInterruptEvent::~SelectInterruptEvent() + { +#ifdef _WIN32 + CloseHandle(_event); +#endif + } + + bool SelectInterruptEvent::init(std::string& /*errorMsg*/) + { + return true; + } + + bool SelectInterruptEvent::notify(uint64_t value) + { + std::lock_guard lock(_valuesMutex); + + // WebSocket implementation detail: We only need one of the values in the queue + if (std::find(_values.begin(), _values.end(), value) == _values.end()) + _values.push_back(value); +#ifdef _WIN32 + SetEvent(_event); // wake up +#endif + return true; + } + + uint64_t SelectInterruptEvent::read() + { + std::lock_guard lock(_valuesMutex); + + if (_values.size() > 0) + { + uint64_t value = _values.front(); + _values.pop_front(); +#ifdef _WIN32 + // signal the event if there is still data in the queue + if (_values.size() == 0) + ResetEvent(_event); +#endif + return value; + } + return 0; + } + + bool SelectInterruptEvent::clear() + { + std::lock_guard lock(_valuesMutex); + _values.clear(); +#ifdef _WIN32 + ResetEvent(_event); +#endif + return true; + } + + void* SelectInterruptEvent::getEvent() const + { +#ifdef _WIN32 + return reinterpret_cast(_event); +#else + return nullptr; +#endif + } + +} // namespace ix diff --git a/ixwebsocket/IXSelectInterruptEvent.h b/ixwebsocket/IXSelectInterruptEvent.h new file mode 100644 index 00000000..d965661d --- /dev/null +++ b/ixwebsocket/IXSelectInterruptEvent.h @@ -0,0 +1,39 @@ +/* + * IXSelectInterruptEvent.h + */ + +#pragma once + +#include "IXSelectInterrupt.h" +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +namespace ix +{ + class SelectInterruptEvent final : public SelectInterrupt + { + public: + SelectInterruptEvent(); + virtual ~SelectInterruptEvent(); + + bool init(std::string& /*errorMsg*/) final; + + bool notify(uint64_t value) final; + bool clear() final; + uint64_t read() final; + void* getEvent() const final; + private: + // contains every value only once, new values are inserted at the begin, nu + std::deque _values; + std::mutex _valuesMutex; +#ifdef _WIN32 + // Windows Event to wake up the socket poll + HANDLE _event; +#endif + }; +} // namespace ix diff --git a/ixwebsocket/IXSelectInterruptFactory.cpp b/ixwebsocket/IXSelectInterruptFactory.cpp index 9018810d..c66c14c7 100644 --- a/ixwebsocket/IXSelectInterruptFactory.cpp +++ b/ixwebsocket/IXSelectInterruptFactory.cpp @@ -7,20 +7,20 @@ #include "IXSelectInterruptFactory.h" #include "IXUniquePtr.h" -#if defined(__linux__) || defined(__APPLE__) -#include "IXSelectInterruptPipe.h" +#if _WIN32 +#include "IXSelectInterruptEvent.h" #else -#include "IXSelectInterrupt.h" +#include "IXSelectInterruptPipe.h" #endif namespace ix { SelectInterruptPtr createSelectInterrupt() { -#if defined(__linux__) || defined(__APPLE__) - return ix::make_unique(); +#ifdef _WIN32 + return ix::make_unique(); #else - return ix::make_unique(); + return ix::make_unique(); #endif } } // namespace ix diff --git a/ixwebsocket/IXSocket.cpp b/ixwebsocket/IXSocket.cpp index bccfe7d9..d3469125 100644 --- a/ixwebsocket/IXSocket.cpp +++ b/ixwebsocket/IXSocket.cpp @@ -47,6 +47,8 @@ namespace ix int sockfd, const SelectInterruptPtr& selectInterrupt) { + PollResultType pollResult = PollResultType::ReadyForRead; + // // We used to use ::select to poll but on Android 9 we get large fds out of // ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching @@ -68,9 +70,11 @@ namespace ix // File descriptor used to interrupt select when needed int interruptFd = -1; + void* interruptEvent = nullptr; if (selectInterrupt) { interruptFd = selectInterrupt->getFd(); + interruptEvent = selectInterrupt->getEvent(); if (interruptFd != -1) { @@ -78,11 +82,21 @@ namespace ix fds[1].fd = interruptFd; fds[1].events = POLLIN; } + else if (interruptEvent == nullptr) + { + // Emulation mode: SelectInterrupt neither supports file descriptors nor events + + // Check the selectInterrupt for requests before doing the poll(). + if (readSelectInterruptRequest(selectInterrupt, &pollResult)) + { + return pollResult; + } + } } - int ret = ix::poll(fds, nfds, timeoutMs); + void* event = interruptEvent; // ix::poll will set event to nullptr if it wasn't signaled + int ret = ix::poll(fds, nfds, timeoutMs, &event); - PollResultType pollResult = PollResultType::ReadyForRead; if (ret < 0) { pollResult = PollResultType::Error; @@ -90,20 +104,19 @@ namespace ix else if (ret == 0) { pollResult = PollResultType::Timeout; - } - else if (interruptFd != -1 && fds[1].revents & POLLIN) - { - uint64_t value = selectInterrupt->read(); + if (selectInterrupt && interruptFd == -1 && interruptEvent == nullptr) + { + // Emulation mode: SelectInterrupt neither supports fd nor events - if (value == SelectInterrupt::kSendRequest) - { - pollResult = PollResultType::SendRequest; - } - else if (value == SelectInterrupt::kCloseRequest) - { - pollResult = PollResultType::CloseRequest; + // Check the selectInterrupt for requests + readSelectInterruptRequest(selectInterrupt, &pollResult); } } + else if ((interruptFd != -1 && fds[1].revents & POLLIN) || (interruptEvent != nullptr && event != nullptr)) + { + // The InterruptEvent was signaled + readSelectInterruptRequest(selectInterrupt, &pollResult); + } else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN) { pollResult = PollResultType::ReadyForRead; @@ -143,6 +156,25 @@ namespace ix return pollResult; } + bool Socket::readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt, + PollResultType* pollResult) + { + uint64_t value = selectInterrupt->read(); + + if (value == SelectInterrupt::kSendRequest) + { + *pollResult = PollResultType::SendRequest; + return true; + } + else if (value == SelectInterrupt::kCloseRequest) + { + *pollResult = PollResultType::CloseRequest; + return true; + } + + return false; + } + PollResultType Socket::isReadyToRead(int timeoutMs) { if (_sockfd == -1) @@ -171,6 +203,11 @@ namespace ix return _selectInterrupt->notify(wakeUpCode); } + bool Socket::isWakeUpFromPollSupported() + { + return _selectInterrupt->getFd() != -1 || _selectInterrupt->getEvent() != nullptr; + } + bool Socket::accept(std::string& errMsg) { if (_sockfd == -1) diff --git a/ixwebsocket/IXSocket.h b/ixwebsocket/IXSocket.h index 393d5bc4..ae5a560e 100644 --- a/ixwebsocket/IXSocket.h +++ b/ixwebsocket/IXSocket.h @@ -43,6 +43,7 @@ namespace ix // Functions to check whether there is activity on the socket PollResultType poll(int timeoutMs = kDefaultPollTimeout); bool wakeUpFromPoll(uint64_t wakeUpCode); + bool isWakeUpFromPollSupported(); PollResultType isReadyToWrite(int timeoutMs); PollResultType isReadyToRead(int timeoutMs); @@ -83,6 +84,9 @@ namespace ix std::atomic _sockfd; std::mutex _socketMutex; + static bool readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt, + PollResultType* pollResult); + private: static const int kDefaultPollTimeout; static const int kDefaultPollNoTimeout; diff --git a/ixwebsocket/IXSocketConnect.cpp b/ixwebsocket/IXSocketConnect.cpp index 94ebc406..f191eae8 100644 --- a/ixwebsocket/IXSocketConnect.cpp +++ b/ixwebsocket/IXSocketConnect.cpp @@ -20,6 +20,7 @@ #include #include #endif +#include namespace ix { @@ -66,7 +67,7 @@ namespace ix int timeoutMs = 10; bool readyToRead = false; - auto selectInterrupt = ix::make_unique(); + SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt(); PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt); if (pollResult == PollResultType::Timeout) diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index 8537a95e..1fcdfdeb 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -297,13 +297,11 @@ namespace ix lastingTimeoutDelayInMs = (1000 * _pingIntervalSecs) - timeSinceLastPingMs; } -#ifdef _WIN32 - // Windows does not have select interrupt capabilities, so wait with a small timeout - if (lastingTimeoutDelayInMs <= 0) + // The platform may not have select interrupt capabilities, so wait with a small timeout + if (lastingTimeoutDelayInMs <= 0 && !_socket->isWakeUpFromPollSupported()) { lastingTimeoutDelayInMs = 20; } -#endif // If we are requesting a cancellation, pass in a positive and small timeout // to never poll forever without a timeout.