Merge branch 'master' of https://github.com/machinezone/IXWebSocket into machinezone-master
This commit is contained in:
3
.github/workflows/mkdocs.yml
vendored
3
.github/workflows/mkdocs.yml
vendored
@@ -1,6 +1,8 @@
|
||||
name: mkdocs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
@@ -21,6 +23,7 @@ jobs:
|
||||
pip install pygments
|
||||
- name: Build doc
|
||||
run: |
|
||||
git checkout master
|
||||
git clean -dfx .
|
||||
git fetch
|
||||
git pull
|
||||
|
@@ -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
|
||||
@@ -148,10 +150,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)
|
||||
|
@@ -106,6 +106,8 @@ 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
|
||||
- [NovaCoin](https://github.com/novacoin-project/novacoin), a hybrid scrypt PoW + PoS based cryptocurrency.
|
||||
|
||||
## Alternative libraries
|
||||
|
||||
|
22
appveyor.yml
22
appveyor.yml
@@ -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-uwp
|
||||
- vcpkg install mbedtls:x64-uwp
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -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
|
@@ -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)
|
||||
|
@@ -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`.
|
||||
|
159
docs/index.md
159
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 <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXUserAgent.h>
|
||||
#include <iostream>
|
||||
|
||||
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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "IXProgressCallback.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -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<bool> cancel;
|
||||
};
|
||||
|
||||
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
||||
|
@@ -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<HttpResponse>(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<HttpResponse>(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<HttpResponse>(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<HttpResponse>(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<HttpResponse>(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<HttpResponse>(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<HttpResponse>(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<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::ChunkReadError,
|
||||
errorCode,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
|
@@ -7,6 +7,9 @@
|
||||
#include "IXNetSystem.h"
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#ifdef _WIN32
|
||||
#include <vector>
|
||||
#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<HANDLE>(*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<WSAEvent> socketEvents;
|
||||
std::vector<HANDLE> handles;
|
||||
// put the interrupt event as first element, making it highest priority
|
||||
handles.push_back(interruptEvent);
|
||||
|
||||
// create the WSAEvents for the sockets
|
||||
for (nfds_t i = 0; i < nfds; ++i)
|
||||
{
|
||||
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<DWORD>(timeout) : WSA_INFINITE, FALSE);
|
||||
|
||||
if (n == WSA_WAIT_FAILED) return SOCKET_ERROR;
|
||||
if (n == WSA_WAIT_TIMEOUT) return 0;
|
||||
if (n == WSA_WAIT_EVENT_0)
|
||||
{
|
||||
FD_SET(fd->fd, &writefds);
|
||||
// the interrupt event was signaled
|
||||
*event = reinterpret_cast<void*>(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
|
||||
|
@@ -12,11 +12,12 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#include <WS2tcpip.h>
|
||||
#include <WinSock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock2.h>
|
||||
#include <basetsd.h>
|
||||
#include <io.h>
|
||||
#include <ws2def.h>
|
||||
#include <cerrno>
|
||||
|
||||
#undef EWOULDBLOCK
|
||||
#undef EAGAIN
|
||||
@@ -77,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);
|
||||
|
@@ -45,4 +45,9 @@ namespace ix
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
void* SelectInterrupt::getEvent() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace ix
|
||||
|
@@ -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;
|
||||
|
84
ixwebsocket/IXSelectInterruptEvent.cpp
Normal file
84
ixwebsocket/IXSelectInterruptEvent.cpp
Normal file
@@ -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<std::mutex> lock(_valuesMutex);
|
||||
|
||||
// WebSocket implementation detail: We only need one of the values in the queue
|
||||
if (std::find(_values.begin(), _values.end(), value) == _values.end())
|
||||
_values.push_back(value);
|
||||
#ifdef _WIN32
|
||||
SetEvent(_event); // wake up
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t SelectInterruptEvent::read()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_valuesMutex);
|
||||
|
||||
if (_values.size() > 0)
|
||||
{
|
||||
uint64_t value = _values.front();
|
||||
_values.pop_front();
|
||||
#ifdef _WIN32
|
||||
// signal the event if there is still data in the queue
|
||||
if (_values.size() == 0)
|
||||
ResetEvent(_event);
|
||||
#endif
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SelectInterruptEvent::clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_valuesMutex);
|
||||
_values.clear();
|
||||
#ifdef _WIN32
|
||||
ResetEvent(_event);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void* SelectInterruptEvent::getEvent() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return reinterpret_cast<void*>(_event);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ix
|
39
ixwebsocket/IXSelectInterruptEvent.h
Normal file
39
ixwebsocket/IXSelectInterruptEvent.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* IXSelectInterruptEvent.h
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXSelectInterrupt.h"
|
||||
#include <mutex>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SelectInterruptEvent final : public SelectInterrupt
|
||||
{
|
||||
public:
|
||||
SelectInterruptEvent();
|
||||
virtual ~SelectInterruptEvent();
|
||||
|
||||
bool init(std::string& /*errorMsg*/) final;
|
||||
|
||||
bool notify(uint64_t value) final;
|
||||
bool clear() final;
|
||||
uint64_t read() final;
|
||||
void* getEvent() const final;
|
||||
private:
|
||||
// contains every value only once, new values are inserted at the begin, nu
|
||||
std::deque<uint64_t> _values;
|
||||
std::mutex _valuesMutex;
|
||||
#ifdef _WIN32
|
||||
// Windows Event to wake up the socket poll
|
||||
HANDLE _event;
|
||||
#endif
|
||||
};
|
||||
} // namespace ix
|
@@ -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<SelectInterruptPipe>();
|
||||
#ifdef _WIN32
|
||||
return ix::make_unique<SelectInterruptEvent>();
|
||||
#else
|
||||
return ix::make_unique<SelectInterrupt>();
|
||||
return ix::make_unique<SelectInterruptPipe>();
|
||||
#endif
|
||||
}
|
||||
} // namespace ix
|
||||
|
@@ -17,7 +17,7 @@
|
||||
|
||||
// Windows
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
|
@@ -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)
|
||||
|
@@ -13,7 +13,7 @@
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <BaseTsd.h>
|
||||
#include <basetsd.h>
|
||||
typedef SSIZE_T ssize_t;
|
||||
#endif
|
||||
|
||||
@@ -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<int> _sockfd;
|
||||
std::mutex _socketMutex;
|
||||
|
||||
static bool readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt,
|
||||
PollResultType* pollResult);
|
||||
|
||||
private:
|
||||
static const int kDefaultPollTimeout;
|
||||
static const int kDefaultPollNoTimeout;
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include <linux/in.h>
|
||||
#include <linux/tcp.h>
|
||||
#endif
|
||||
#include <ixwebsocket/IXSelectInterruptFactory.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@@ -66,7 +67,7 @@ namespace ix
|
||||
|
||||
int timeoutMs = 10;
|
||||
bool readyToRead = false;
|
||||
auto selectInterrupt = ix::make_unique<SelectInterrupt>();
|
||||
SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt();
|
||||
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
|
||||
|
||||
if (pollResult == PollResultType::Timeout)
|
||||
|
@@ -15,7 +15,7 @@
|
||||
#include <errno.h>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#include <Shlwapi.h>
|
||||
#include <shlwapi.h>
|
||||
#else
|
||||
#include <fnmatch.h>
|
||||
#endif
|
||||
|
@@ -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
|
||||
|
@@ -11,7 +11,7 @@
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <BaseTsd.h>
|
||||
#include <basetsd.h>
|
||||
typedef SSIZE_T ssize_t;
|
||||
#endif
|
||||
|
||||
|
@@ -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<int>(_serverMaxWindowBits);
|
||||
ss << "; client_max_window_bits=" << static_cast<int>(_clientMaxWindowBits);
|
||||
|
||||
ss << "\r\n";
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -6,4 +6,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define IX_WEBSOCKET_VERSION "11.3.1"
|
||||
#define IX_WEBSOCKET_VERSION "11.3.2"
|
||||
|
@@ -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<bool> requestCompleted(false);
|
||||
std::atomic<HttpErrorCode> 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<double, std::milli> duration(10);
|
||||
std::this_thread::sleep_for(duration);
|
||||
wait += 10;
|
||||
}
|
||||
|
||||
std::cerr << "Done" << std::endl;
|
||||
REQUIRE(errorCode == HttpErrorCode::Cancelled);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user