Compare commits

...

43 Commits

Author SHA1 Message Date
56f5527726 Update ixwebsocket-config.cmake.in 2022-04-26 16:24:54 -07:00
6d8495bd73 Update CHANGELOG.md 2022-04-23 22:53:36 -07:00
b8563eddd1 11.4.1 2022-04-23 22:52:32 -07:00
46bd2aa4a1 vcpkg zlib dep fix (#385)
* vcpkg zlib dep fix

* Use cmake.in file instead of write file directly

Co-authored-by: Cheney-Wang <v-xincwa@microsoft.com>
2022-04-23 18:16:13 -07:00
4420bc70b5 Revert "Export static symbols when building ws with shared library (#370)" (#383)
This reverts commit a3d2fa4b7e.
2022-04-12 08:55:43 -07:00
20921f341a Update README.md 2022-03-28 22:04:27 -07:00
2829c62ef9 Fix error handling after calling X509_NAME_get_index_by_NID
This should fix #376
2022-03-27 19:14:40 -07:00
a3d2fa4b7e Export static symbols when building ws with shared library (#370) 2022-03-19 11:41:40 -07:00
f7eb3688dd Update IXExponentialBackoffTest.cpp 2022-02-17 09:17:47 -08:00
7360333aca Handle overflow in IXExponentialBackoff.cpp 2022-02-17 09:17:02 -08:00
90f19e0280 Reference new IXExponentialBackoffTest test in CMakeLists.txt 2022-02-17 09:08:49 -08:00
b72f81540b Create IXExponentialBackoffTest.cpp 2022-02-17 09:07:46 -08:00
a77fd2d698 Update catch to v2.13.8 (#365) 2022-02-17 08:38:46 -08:00
e12a6ddbdd Update IXSocketConnect.cpp (#356)
Part of the code is never executed
warning raised by clang
2022-02-10 20:50:55 -08:00
127cc4a023 Fix for MINGW32 and clang on windows (#352) (#357)
* Update IXSocket.h

Avoid "conflicting declaration 'typedef SSIZE_T ssize_t'"

* Update IXUdpSocket.h

* Update IXNetSystem.cpp

ENOSPC and EAFNOSUPPORT are not defined for clang on windows
2022-02-10 20:49:33 -08:00
7711cb1ae7 Feature/no libdeflate (#360)
remove libdeflate code in gzip codec
2022-02-10 20:47:32 -08:00
db7057de69 Add support for streaming transfers (#353)
This change adds onChunkCallback to the request. If defined it will be
called repeatedly with the incoming data. This allows to process data on
the go or write it to disk instead of accumulating the data in memory.
2022-01-31 21:54:32 -08:00
c28b569535 Edit doc about thread safety, fix #350 2022-01-28 16:27:45 -08:00
8d661b8e81 Use Threads::Threads target (#349) 2022-01-16 17:39:21 -08:00
a951bc9cae Add an alias for ixwebsocket (#348) 2022-01-16 17:36:38 -08:00
b5cf33a582 Introduction of IXWebSocketSendData (#347)
* Introduction of IXWebSocketSendData that makes it possible to not only send std::string but also std::vector<char/uint8_t> and char* without copying them to a std::string first.

Add a sendUtf8Text() method that doesn't check for invalid UTF-8 characters. The caller must guarantee that the string only contains valid UTF-8 characters.

* Updated usage.md: sendUtf8Text() and IXWebSocketSendData
2022-01-10 10:34:24 -08:00
2bc3afcf6c Fixed missing header for gcc (9.3.0) compilation (#346) 2022-01-06 19:27:00 -08:00
60563d88f2 Feature/11.4.0 (#344)
* mbedls system certs

* missing curly brace ...

* windows uwp for appveyor

* try again uwp

* update version and changelog

* revert odd change in test/IXSocketTest.cpp

Co-authored-by: Benjamin Sergeant <bsergeant@mz.com>
2022-01-05 10:43:18 -08:00
1f2895a469 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.
2022-01-05 10:21:33 -08:00
9f00428d57 Fix "HTTP/1.1 400 Illegal character CNTL=0xf" caused by serverMaxWindowBits/clientMaxWindowBits being uint8_t (signed char). (#341) 2022-01-04 12:25:18 -08:00
f53b2f8878 Export symbols into .def files on MSVC (#339)
Fix #335
2022-01-04 12:13:38 -08:00
47d0b70ebf Include <cerrno> 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.
```
2022-01-04 12:13:19 -08:00
8c15405ed0 Add a reference to NovaCoin in the README 2021-12-22 22:52:53 -08:00
5457217503 Improved compatibility - fix mingw crossbuild (#337) 2021-12-22 22:48:20 -08:00
66cd29e747 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;
2021-12-20 23:01:55 -08:00
688f85fda6 Fix errors in example code. (#336) 2021-12-20 22:59:15 -08:00
71f73e5f6e Update README (add another project using the lib) 2021-12-02 22:49:12 -08:00
42db05a38b Update mkdocs.yml 2021-11-24 09:05:54 -08:00
6cce066021 Update index.md 2021-11-24 09:02:42 -08:00
9c6dcb24a9 Update mkdocs.yml 2021-11-24 09:01:10 -08:00
05a27c89e3 Update design.md 2021-11-24 09:00:23 -08:00
23dbc8ae63 Update mkdocs.yml
Try a new version of the checkout action.
2021-11-24 08:56:14 -08:00
5f2955ef78 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 <bsergeant@mz.com>
2021-11-24 08:45:04 -08:00
882081536c Fix IXWebSocketMessage.h:35:15: warning: ‘webSocketMessageType’ may be used uninitialized in this function [-Wmaybe-uninitialized] (#328) 2021-11-24 08:33:09 -08:00
74bb85efe9 Add getters (#327)
* Add getters for IXSocketServer class

* Add getters for IXHttpServer class

* Add getters for IXWebSocketServer class
2021-11-24 08:28:25 -08:00
e66437b560 fix compilation under mingw64 (#325) 2021-11-16 15:22:51 -08:00
97aa1f956a Fix mbedtls-3.0 problem (#322)
* Fix mbedtls-3.0 problem

This cause CI to fail on macOS.

See this migration guide => https://github.com/ARMmbed/mbedtls/blob/development/docs/3.0-migration-guide.md

* cmake change find header file

* define macro for mbedtls >= 3

* update api call

* Update IXWebSocketVersion.h

* Update CHANGELOG.md
2021-10-22 11:10:58 -07:00
3f1fc6906c Correctly convert remote port bytecode to uint16 port number. (#321)
* Correctly convert remote port bytecode to uint16 port number.

Copied the network_to_host_short function from the ASIO library to convert the remote port byte code to a uint16 number.

* Switched from uint16_t to unsigned short to work in Windows

* Updated missed uint16_t to unsigned short
2021-10-22 10:23:43 -07:00
53 changed files with 5569 additions and 1724 deletions

View File

@ -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

View File

@ -1,5 +1,8 @@
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
# mbedtls-3.0 changed headers files, and we need to ifdef'out a few things
find_path(MBEDTLS_VERSION_GREATER_THAN_3 mbedtls/build_info.h)
find_library(MBEDTLS_LIBRARY mbedtls)
find_library(MBEDX509_LIBRARY mbedx509)
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)

View File

@ -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
@ -108,6 +110,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
ixwebsocket/IXWebSocketProxyServer.h
ixwebsocket/IXWebSocketSendData.h
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h
@ -148,10 +151,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)
@ -188,55 +210,49 @@ if (USE_TLS)
add_definitions(${OPENSSL_DEFINITIONS})
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
target_link_libraries(ixwebsocket PRIVATE ${OPENSSL_LIBRARIES})
elseif (USE_MBED_TLS)
message(STATUS "TLS configured to use mbedtls")
# This MBEDTLS_FOUND check is to help find a cmake manually configured MbedTLS
if (NOT MBEDTLS_FOUND)
find_package(MbedTLS REQUIRED)
if (MBEDTLS_VERSION_GREATER_THAN_3)
target_compile_definitions(ixwebsocket PRIVATE IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3)
endif()
endif()
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${MBEDTLS_INCLUDE_DIRS}>)
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
target_link_libraries(ixwebsocket PRIVATE ${MBEDTLS_LIBRARIES})
elseif (USE_SECURE_TRANSPORT)
message(STATUS "TLS configured to use secure transport")
target_link_libraries(ixwebsocket "-framework Foundation" "-framework Security")
target_link_libraries(ixwebsocket PRIVATE "-framework Foundation" "-framework Security")
endif()
endif()
option(USE_ZLIB "Enable zlib support" TRUE)
if (USE_ZLIB)
# This ZLIB_FOUND check is to help find a cmake manually configured zlib
if (NOT ZLIB_FOUND)
find_package(ZLIB REQUIRED)
endif()
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${ZLIB_INCLUDE_DIRS}>)
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
find_package(ZLIB REQUIRED)
target_link_libraries(ixwebsocket PRIVATE ZLIB::ZLIB)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
endif()
# brew install libdeflate
find_package(Deflate)
if (DEFLATE_FOUND)
include_directories(${DEFLATE_INCLUDE_DIRS})
target_link_libraries(ixwebsocket ${DEFLATE_LIBRARIES})
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_DEFLATE)
endif()
if (WIN32)
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
target_link_libraries(ixwebsocket PRIVATE wsock32 ws2_32 shlwapi)
target_compile_definitions(ixwebsocket PRIVATE _CRT_SECURE_NO_WARNINGS)
if (USE_TLS)
target_link_libraries(ixwebsocket Crypt32)
target_link_libraries(ixwebsocket PRIVATE Crypt32)
endif()
endif()
if (UNIX)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads)
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(ixwebsocket PRIVATE Threads::Threads)
endif()
@ -258,6 +274,8 @@ target_include_directories(ixwebsocket PUBLIC
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
add_library(ixwebsocket::ixwebsocket ALIAS ixwebsocket)
option(IXWEBSOCKET_INSTALL "Install IXWebSocket" TRUE)
if (IXWEBSOCKET_INSTALL)
@ -267,10 +285,14 @@ if (IXWEBSOCKET_INSTALL)
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
)
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-config.cmake.in" "${CMAKE_BINARY_DIR}/ixwebsocket-config.cmake" @ONLY)
install(FILES "${CMAKE_BINARY_DIR}/ixwebsocket-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket")
install(EXPORT ixwebsocket
FILE ixwebsocket-config.cmake
FILE ixwebsocket-targets.cmake
NAMESPACE ixwebsocket::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket)
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket
)
endif()
if (USE_WS OR USE_TEST)

View File

@ -2,9 +2,7 @@
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Note that the MinGW compiler is not supported at this point. Two important design goals are simplicity and correctness.
A bad security bug affecting users compiling with SSL enabled and OpenSSL as the backend was just fixed in newly released version 11.0.0. Please upgrade ! (more details in the [https://github.com/machinezone/IXWebSocket/pull/250](PR).
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
```cpp
/*
@ -106,6 +104,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

View File

@ -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

View File

@ -2,6 +2,29 @@
All changes to this project will be documented in this file.
## [11.4.1] - 2022-04-23
vckpg + cmake fix, to handle zlib as a dependency better
## [11.4.0] - 2022-01-05
(Windows) Use wsa select event, which should lead to a much better behavior on Windows in general, and also when sending large payloads (#342)
Fix "HTTP/1.1 400 Illegal character CNTL=0xf" caused by serverMaxWindowBits/clientMaxWindowBits being uint8_t (signed char). (#341)
Export symbols into .def files
Export symbols into .def files on MSVC (#339)
Include <cerrno> to provide standard error constants (#338)
Improved compatibility - fix mingw crossbuild (#337)
Allow to cancel asynchronous HTTP requests (#332)
Fix errors in example code. (#336)
## [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)
## [11.3.0] - 2021-09-20
(library/cmake) Only find OpenSSL, MbedTLS, zlib if they have not already been found, make CMake install optional (#317) + Use GNUInstallDirs in cmake (#318)

View File

@ -12,7 +12,7 @@ If you are using OpenSSL, try to be on a version higher than 1.1.x as there ther
### Polling and background thread work
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes. However concurrent writes need to be protected with mutex.
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes.
### Automatic reconnection
@ -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`.

View File

@ -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

View File

@ -90,6 +90,18 @@ auto result =
});
```
The `send()` and `sendText()` methods check that the string contains only valid UTF-8 characters. If you know that the string is a valid UTF-8 string you can skip that step and use the `sendUtf8Text` method instead.
With the IXWebSocketSendData overloads of `sendUtf8Text` and `sendBinary` it is possible to not only send std::string but also `std::vector<char>`, `std::vector<uint8_t>` and `char*`.
```
std::vector<uint8_t> data({1, 2, 3, 4});
auto result = webSocket.sendBinary(data);
const char* text = "Hello World!";
result = webSocket.sendUtf8Text(IXWebSocketSendData(text, strlen(text)));
```
### ReadyState
`getReadyState()` returns the state of the connection. There are 4 possible states.
@ -141,9 +153,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;
}
}
@ -508,15 +520,28 @@ bool async = true;
HttpClient httpClient(async);
auto args = httpClient.createRequest(url, HttpClient::kGet);
// If you define a chunk callback it will be called repeteadly with the
// incoming data. This allows to process data on the go or write it to disk
// instead of accumulating the data in memory.
args.onChunkCallback = [](const std::string& data)
{
// process data
};
// Push the request to a queue,
bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
{
// This callback execute in a background thread. Make sure you uses appropriate protection such as mutex
auto statusCode = response->statusCode; // acess results
// response->body is empty if onChunkCallback was used
}
);
// ok will be false if your httpClient is not async
// A request in progress can be cancelled by setting the cancel flag. It does nothing if the request already completed.
args->cancel = true;
```
See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart.

View File

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

View File

@ -14,14 +14,27 @@ namespace ix
uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries)
{
uint32_t waitTime = (retryCount < 26) ? (std::pow(2, retryCount) * 100) : 0;
// It's easy with a power function to go beyond 2^32, and then
// have unexpected results, so prepare for that
const uint32_t maxRetryCountWithoutOverflow = 26;
uint32_t waitTime = 0;
if (retryCount < maxRetryCountWithoutOverflow)
{
waitTime = std::pow(2, retryCount) * 100;
}
if (waitTime < minWaitBetweenReconnectionRetries)
{
waitTime = minWaitBetweenReconnectionRetries;
}
if (waitTime > maxWaitBetweenReconnectionRetries || waitTime == 0)
if (waitTime > maxWaitBetweenReconnectionRetries)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
if (retryCount >= maxRetryCountWithoutOverflow)
{
waitTime = maxWaitBetweenReconnectionRetries;
}

View File

@ -14,53 +14,12 @@
#include <zlib.h>
#endif
#ifdef IXWEBSOCKET_USE_DEFLATE
#include <libdeflate.h>
#endif
namespace ix
{
std::string gzipCompress(const std::string& str)
{
#ifndef IXWEBSOCKET_USE_ZLIB
return std::string();
#else
#ifdef IXWEBSOCKET_USE_DEFLATE
int compressionLevel = 6;
struct libdeflate_compressor* compressor;
compressor = libdeflate_alloc_compressor(compressionLevel);
const void* uncompressed_data = str.data();
size_t uncompressed_size = str.size();
void* compressed_data;
size_t actual_compressed_size;
size_t max_compressed_size;
max_compressed_size = libdeflate_gzip_compress_bound(compressor, uncompressed_size);
compressed_data = malloc(max_compressed_size);
if (compressed_data == NULL)
{
return std::string();
}
actual_compressed_size = libdeflate_gzip_compress(
compressor, uncompressed_data, uncompressed_size, compressed_data, max_compressed_size);
libdeflate_free_compressor(compressor);
if (actual_compressed_size == 0)
{
free(compressed_data);
return std::string();
}
std::string out;
out.assign(reinterpret_cast<char*>(compressed_data), actual_compressed_size);
free(compressed_data);
return out;
#else
z_stream zs; // z_stream is zlib's control structure
memset(&zs, 0, sizeof(zs));
@ -101,7 +60,6 @@ namespace ix
deflateEnd(&zs);
return outstring;
#endif // IXWEBSOCKET_USE_DEFLATE
#endif // IXWEBSOCKET_USE_ZLIB
}
@ -117,26 +75,6 @@ namespace ix
{
#ifndef IXWEBSOCKET_USE_ZLIB
return false;
#else
#ifdef IXWEBSOCKET_USE_DEFLATE
struct libdeflate_decompressor* decompressor;
decompressor = libdeflate_alloc_decompressor();
const void* compressed_data = in.data();
size_t compressed_size = in.size();
// Retrieve uncompressed size from the trailer of the gziped data
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&in.front());
auto uncompressed_size = loadDecompressedGzipSize(&ptr[compressed_size - 4]);
// Use it to redimension our output buffer
out.resize(uncompressed_size);
libdeflate_result result = libdeflate_gzip_decompress(
decompressor, compressed_data, compressed_size, &out.front(), uncompressed_size, NULL);
libdeflate_free_decompressor(decompressor);
return result == LIBDEFLATE_SUCCESS;
#else
z_stream inflateState;
memset(&inflateState, 0, sizeof(inflateState));
@ -177,7 +115,6 @@ namespace ix
inflateEnd(&inflateState);
return true;
#endif // IXWEBSOCKET_USE_DEFLATE
#endif // IXWEBSOCKET_USE_ZLIB
}
} // namespace ix

View File

@ -149,7 +149,7 @@ namespace ix
false, "Error: 'Content-Length' should be a positive integer", httpRequest);
}
auto res = socket->readBytes(contentLength, nullptr, isCancellationRequested);
auto res = socket->readBytes(contentLength, nullptr, nullptr, isCancellationRequested);
if (!res.first)
{
return std::make_tuple(

View File

@ -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,8 @@ namespace ix
bool compressRequest = false;
Logger logger;
OnProgressCallback onProgressCallback;
OnChunkCallback onChunkCallback;
std::atomic<bool> cancel;
};
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;

View File

@ -176,7 +176,7 @@ namespace ix
ss << "Host: " << host << "\r\n";
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compress)
if (args->compress && !args->onChunkCallback)
{
ss << "Accept-Encoding: gzip"
<< "\r\n";
@ -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,
@ -399,23 +406,29 @@ namespace ix
ss << headers["Content-Length"];
ss >> contentLength;
payload.reserve(contentLength);
auto chunkResult = _socket->readBytes(
contentLength, args->onProgressCallback, isCancellationRequested);
auto chunkResult = _socket->readBytes(contentLength,
args->onProgressCallback,
args->onChunkCallback,
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,
uploadSize,
downloadSize);
}
payload += chunkResult.second;
if (!args->onChunkCallback)
{
payload.reserve(contentLength);
payload += chunkResult.second;
}
}
else if (headers.find("Transfer-Encoding") != headers.end() &&
headers["Transfer-Encoding"] == "chunked")
@ -424,6 +437,7 @@ namespace ix
while (true)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
lineResult = _socket->readLine(isCancellationRequested);
line = lineResult.second;
@ -431,7 +445,7 @@ namespace ix
{
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
errorCode,
headers,
payload,
errorMsg,
@ -451,33 +465,40 @@ namespace ix
log(oss.str(), args);
}
payload.reserve(payload.size() + (size_t) chunkSize);
// Read a chunk
auto chunkResult = _socket->readBytes(
(size_t) chunkSize, args->onProgressCallback, isCancellationRequested);
auto chunkResult = _socket->readBytes((size_t) chunkSize,
args->onProgressCallback,
args->onChunkCallback,
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,
uploadSize,
downloadSize);
}
payload += chunkResult.second;
if (!args->onChunkCallback)
{
payload.reserve(payload.size() + (size_t) chunkSize);
payload += chunkResult.second;
}
// Read the line that terminates the chunk (\r\n)
lineResult = _socket->readLine(isCancellationRequested);
if (!lineResult.first)
{
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
return std::make_shared<HttpResponse>(code,
description,
HttpErrorCode::ChunkReadError,
errorCode,
headers,
payload,
errorMsg,

View File

@ -226,4 +226,10 @@ namespace ix
200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
});
}
int HttpServer::getTimeoutSecs()
{
return _timeoutSecs;
}
} // namespace ix

View File

@ -40,6 +40,7 @@ namespace ix
void makeDebugServer();
int getTimeoutSecs();
private:
// Member variables
OnConnectionCallback _onConnectionCallback;

View File

@ -5,6 +5,17 @@
*/
#include "IXNetSystem.h"
#include <cstdint>
#include <cstdio>
#ifdef _WIN32
#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT 102
#endif
#ifndef ENOSPC
#define ENOSPC 28
#endif
#include <vector>
#endif
namespace ix
{
@ -35,6 +46,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
@ -42,69 +98,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
@ -278,4 +445,17 @@ namespace ix
#endif
}
// Convert network bytes to host bytes. Copied from the ASIO library
unsigned short network_to_host_short(unsigned short value)
{
#if defined(_WIN32)
unsigned char* value_p = reinterpret_cast<unsigned char*>(&value);
unsigned short result = (static_cast<unsigned short>(value_p[0]) << 8)
| static_cast<unsigned short>(value_p[1]);
return result;
#else // defined(_WIN32)
return ntohs(value);
#endif // defined(_WIN32)
}
} // namespace ix

View File

@ -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
@ -34,8 +35,8 @@
// Define our own poll on Windows, as a wrapper on top of select
typedef unsigned long int nfds_t;
// mingw does not know about poll so mock it
#if defined(__GNUC__)
// pollfd is not defined by some versions of mingw64 since _WIN32_WINNT is too low
#if _WIN32_WINNT < 0x0600
struct pollfd
{
int fd; /* file descriptor */
@ -77,8 +78,10 @@ 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);
unsigned short network_to_host_short(unsigned short value);
} // namespace ix

View File

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

View File

@ -45,4 +45,9 @@ namespace ix
{
return -1;
}
void* SelectInterrupt::getEvent() const
{
return nullptr;
}
} // namespace ix

View File

@ -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;

View File

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

View File

@ -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

View File

@ -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

View File

@ -17,7 +17,7 @@
// Windows
#ifdef _WIN32
#include <Windows.h>
#include <windows.h>
#endif
namespace ix

View File

@ -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)
@ -363,12 +400,14 @@ namespace ix
std::pair<bool, std::string> Socket::readBytes(
size_t length,
const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested)
{
std::array<uint8_t, 1 << 14> readBuffer;
std::vector<uint8_t> output;
while (output.size() != length)
size_t bytesRead = 0;
while (bytesRead != length)
{
if (isCancellationRequested && isCancellationRequested())
{
@ -376,12 +415,21 @@ namespace ix
return std::make_pair(false, errorMsg);
}
size_t size = std::min(readBuffer.size(), length - output.size());
size_t size = std::min(readBuffer.size(), length - bytesRead);
ssize_t ret = recv((char*) &readBuffer[0], size);
if (ret > 0)
{
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
if (onChunkCallback)
{
std::string chunk(readBuffer.begin(), readBuffer.begin() + ret);
onChunkCallback(chunk);
}
else
{
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
}
bytesRead += ret;
}
else if (ret <= 0 && !Socket::isWaitNeeded())
{
@ -389,7 +437,7 @@ namespace ix
return std::make_pair(false, errorMsg);
}
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
if (onProgressCallback) onProgressCallback((int) bytesRead, (int) length);
// Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping

View File

@ -13,9 +13,11 @@
#include <string>
#ifdef _WIN32
#include <BaseTsd.h>
#include <basetsd.h>
#ifdef _MSC_VER
typedef SSIZE_T ssize_t;
#endif
#endif
#include "IXCancellationRequest.h"
#include "IXProgressCallback.h"
@ -43,6 +45,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);
@ -68,6 +71,7 @@ namespace ix
std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
std::pair<bool, std::string> readBytes(size_t length,
const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested);
static int getErrno();
@ -83,6 +87,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;

View File

@ -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)
@ -90,10 +91,6 @@ namespace ix
return -1;
}
}
Socket::closeSocket(fd);
errMsg = "connect timed out after 60 seconds";
return -1;
}
int SocketConnect::connect(const std::string& hostname,

View File

@ -132,7 +132,11 @@ namespace ix
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
return false;
}
#ifdef IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "", mbedtls_ctr_drbg_random, &_ctr_drbg) < 0)
#else
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
#endif
{
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
return false;

View File

@ -13,7 +13,7 @@
#include <mbedtls/debug.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/net.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/platform.h>
#include <mbedtls/x509.h>
#include <mbedtls/x509_crt.h>

View File

@ -15,7 +15,7 @@
#include <errno.h>
#include <vector>
#ifdef _WIN32
#include <Shlwapi.h>
#include <shlwapi.h>
#else
#include <fnmatch.h>
#endif
@ -339,12 +339,12 @@ namespace ix
{
int cn_pos = X509_NAME_get_index_by_NID(
X509_get_subject_name((X509*) server_cert), NID_commonName, -1);
if (cn_pos)
if (cn_pos >= 0)
{
X509_NAME_ENTRY* cn_entry =
X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos);
if (cn_entry)
if (cn_entry != nullptr)
{
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
char* cn = (char*) ASN1_STRING_data(cn_asn1);

View File

@ -351,7 +351,7 @@ namespace ix
continue;
}
remotePort = client.sin_port;
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp4;
}
else // AF_INET6
@ -371,7 +371,7 @@ namespace ix
continue;
}
remotePort = client.sin_port;
remotePort = ix::network_to_host_short(client.sin_port);
remoteIp = remoteIp6;
}
@ -461,4 +461,29 @@ namespace ix
// so wake up the thread responsible for that
_conditionVariableGC.notify_one();
}
int SocketServer::getPort()
{
return _port;
}
std::string SocketServer::getHost()
{
return _host;
}
int SocketServer::getBacklog()
{
return _backlog;
}
std::size_t SocketServer::getMaxConnections()
{
return _maxConnections;
}
int SocketServer::getAddressFamily()
{
return _addressFamily;
}
} // namespace ix

View File

@ -60,6 +60,11 @@ namespace ix
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
int getPort();
std::string getHost();
int getBacklog();
std::size_t getMaxConnections();
int getAddressFamily();
protected:
// Logging
void logError(const std::string& str);

View File

@ -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

View File

@ -11,9 +11,11 @@
#include <string>
#ifdef _WIN32
#include <BaseTsd.h>
#include <basetsd.h>
#ifdef _MSC_VER
typedef SSIZE_T ssize_t;
#endif
#endif
#include "IXNetSystem.h"

View File

@ -385,7 +385,7 @@ namespace ix
size_t wireSize,
bool decompressionError,
WebSocketTransport::MessageKind messageKind) {
WebSocketMessageType webSocketMessageType;
WebSocketMessageType webSocketMessageType{WebSocketMessageType::Error};
switch (messageKind)
{
case WebSocketTransport::MessageKind::MSG_TEXT:
@ -467,10 +467,28 @@ namespace ix
return (binary) ? sendBinary(data, onProgressCallback) : sendText(data, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendBinary(const std::string& text,
WebSocketSendInfo WebSocket::sendBinary(const std::string& data,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Binary, onProgressCallback);
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendBinary(const IXWebSocketSendData& data,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendUtf8Text(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendUtf8Text(const IXWebSocketSendData& text,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::sendText(const std::string& text,
@ -494,7 +512,7 @@ namespace ix
return sendMessage(text, SendMessageKind::Ping);
}
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
WebSocketSendInfo WebSocket::sendMessage(const IXWebSocketSendData& message,
SendMessageKind sendMessageKind,
const OnProgressCallback& onProgressCallback)
{
@ -516,19 +534,19 @@ namespace ix
{
case SendMessageKind::Text:
{
webSocketSendInfo = _ws.sendText(text, onProgressCallback);
webSocketSendInfo = _ws.sendText(message, onProgressCallback);
}
break;
case SendMessageKind::Binary:
{
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
webSocketSendInfo = _ws.sendBinary(message, onProgressCallback);
}
break;
case SendMessageKind::Ping:
{
webSocketSendInfo = _ws.sendPing(text);
webSocketSendInfo = _ws.sendPing(message);
}
break;
}

View File

@ -17,6 +17,7 @@
#include "IXWebSocketMessage.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketSendData.h"
#include "IXWebSocketTransport.h"
#include <atomic>
#include <condition_variable>
@ -75,8 +76,16 @@ namespace ix
WebSocketSendInfo send(const std::string& data,
bool binary = false,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendBinary(const std::string& text,
WebSocketSendInfo sendBinary(const std::string& data,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendBinary(const IXWebSocketSendData& data,
const OnProgressCallback& onProgressCallback = nullptr);
// does not check for valid UTF-8 characters. Caller must check that.
WebSocketSendInfo sendUtf8Text(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
// does not check for valid UTF-8 characters. Caller must check that.
WebSocketSendInfo sendUtf8Text(const IXWebSocketSendData& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendText(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string& text);
@ -107,7 +116,7 @@ namespace ix
const std::vector<std::string>& getSubProtocols();
private:
WebSocketSendInfo sendMessage(const std::string& text,
WebSocketSendInfo sendMessage(const IXWebSocketSendData& message,
SendMessageKind sendMessageKind,
const OnProgressCallback& callback = nullptr);

View File

@ -78,6 +78,11 @@ namespace ix
_decompressor->init(inflateBits, clientNoContextTakeover);
}
bool WebSocketPerMessageDeflate::compress(const IXWebSocketSendData& in, std::string& out)
{
return _compressor->compress(in, out);
}
bool WebSocketPerMessageDeflate::compress(const std::string& in, std::string& out)
{
return _compressor->compress(in, out);

View File

@ -36,6 +36,7 @@
#include <memory>
#include <string>
#include "IXWebSocketSendData.h"
namespace ix
{
@ -50,6 +51,7 @@ namespace ix
~WebSocketPerMessageDeflate();
bool init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
bool compress(const IXWebSocketSendData& in, std::string& out);
bool compress(const std::string& in, std::string& out);
bool decompress(const std::string& in, std::string& out);

View File

@ -78,6 +78,12 @@ namespace ix
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const IXWebSocketSendData& in,
std::string& out)
{
return compressData(in, out);
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
std::vector<uint8_t>& out)
{

View File

@ -12,6 +12,7 @@
#include <array>
#include <string>
#include <vector>
#include "IXWebSocketSendData.h"
namespace ix
{
@ -22,6 +23,7 @@ namespace ix
~WebSocketPerMessageDeflateCompressor();
bool init(uint8_t deflateBits, bool clientNoContextTakeOver);
bool compress(const IXWebSocketSendData& in, std::string& out);
bool compress(const std::string& in, std::string& out);
bool compress(const std::string& in, std::vector<uint8_t>& out);
bool compress(const std::vector<uint8_t>& in, std::string& out);

View File

@ -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";

View File

@ -0,0 +1,128 @@
/*
* IXWebSocketSendData.h
*
* WebSocket (Binary/Text) send data buffer
*/
#pragma once
#include <string>
#include <vector>
#include <iterator>
namespace ix
{
/*
* IXWebSocketSendData implements a wrapper for std::string, std:vector<char/uint8_t> and char*.
* It removes the necessarity to copy the data or string into a std::string
*/
class IXWebSocketSendData {
public:
template<typename T>
struct IXWebSocketSendData_const_iterator
//: public std::iterator<std::forward_iterator_tag, T>
{
typedef IXWebSocketSendData_const_iterator<T> const_iterator;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = value_type*;
using reference = const value_type&;
pointer _ptr;
public:
IXWebSocketSendData_const_iterator() : _ptr(nullptr) {}
IXWebSocketSendData_const_iterator(pointer ptr) : _ptr(ptr) {}
~IXWebSocketSendData_const_iterator() {}
const_iterator operator++(int) { return const_iterator(_ptr++); }
const_iterator& operator++() { ++_ptr; return *this; }
reference operator* () const { return *_ptr; }
pointer operator->() const { return _ptr; }
const_iterator operator+ (const difference_type offset) const { return const_iterator(_ptr + offset); }
const_iterator operator- (const difference_type offset) const { return const_iterator(_ptr - offset); }
difference_type operator- (const const_iterator& rhs) const { return _ptr - rhs._ptr; }
bool operator==(const const_iterator& rhs) const { return _ptr == rhs._ptr; }
bool operator!=(const const_iterator& rhs) const { return _ptr != rhs._ptr; }
const_iterator& operator+=(const difference_type offset) { _ptr += offset; return *this; }
const_iterator& operator-=(const difference_type offset) { _ptr -= offset; return *this; }
};
using const_iterator = IXWebSocketSendData_const_iterator<char>;
/* The assigned std::string must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::string& str)
: _data(str.data())
, _size(str.size())
{
}
/* The assigned std::vector must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::vector<char>& v)
: _data(v.data())
, _size(v.size())
{
}
/* The assigned std::vector must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::vector<uint8_t>& v)
: _data(reinterpret_cast<const char*>(v.data()))
, _size(v.size())
{
}
/* The assigned memory must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const char* data, size_t size)
: _data(data)
, _size(data == nullptr ? 0 : size)
{
}
bool empty() const
{
return _data == nullptr || _size == 0;
}
const char* c_str() const
{
return _data;
}
const char* data() const
{
return _data;
}
size_t size() const
{
return _size;
}
inline const_iterator begin() const
{
return const_iterator(const_cast<char*>(_data));
}
inline const_iterator end() const
{
return const_iterator(const_cast<char*>(_data) + _size);
}
inline const_iterator cbegin() const
{
return begin();
}
inline const_iterator cend() const
{
return end();
}
private:
const char* _data;
const size_t _size;
};
}

View File

@ -211,4 +211,19 @@ namespace ix
start();
return true;
}
int WebSocketServer::getHandshakeTimeoutSecs()
{
return _handshakeTimeoutSecs;
}
bool WebSocketServer::isPongEnabled()
{
return _enablePong;
}
bool WebSocketServer::isPerMessageDeflateEnabled()
{
return _enablePerMessageDeflate;
}
} // namespace ix

View File

@ -52,6 +52,9 @@ namespace ix
const static int kDefaultHandShakeTimeoutSecs;
int getHandshakeTimeoutSecs();
bool isPongEnabled();
bool isPerMessageDeflateEnabled();
private:
// Member variables
int _handshakeTimeoutSecs;

View File

@ -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.
@ -778,9 +776,8 @@ namespace ix
return static_cast<unsigned>(seconds);
}
template<class T>
WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type,
const T& message,
const IXWebSocketSendData& message,
bool compress,
const OnProgressCallback& onProgressCallback)
{
@ -809,8 +806,9 @@ namespace ix
compressionError = false;
wireSize = _compressedMessage.size();
message_begin = _compressedMessage.cbegin();
message_end = _compressedMessage.cend();
IXWebSocketSendData compressedSendData(_compressedMessage);
message_begin = compressedSendData.cbegin();
message_end = compressedSendData.cend();
}
{
@ -842,8 +840,8 @@ namespace ix
//
auto steps = wireSize / kChunkSize;
std::string::const_iterator begin = message_begin;
std::string::const_iterator end = message_end;
auto begin = message_begin;
auto end = message_end;
for (uint64_t i = 0; i < steps; ++i)
{
@ -982,7 +980,7 @@ namespace ix
return sendOnSocket();
}
WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
WebSocketSendInfo WebSocketTransport::sendPing(const IXWebSocketSendData& message)
{
bool compress = false;
WebSocketSendInfo info = sendData(wsheader_type::PING, message, compress);
@ -996,7 +994,7 @@ namespace ix
return info;
}
WebSocketSendInfo WebSocketTransport::sendBinary(const std::string& message,
WebSocketSendInfo WebSocketTransport::sendBinary(const IXWebSocketSendData& message,
const OnProgressCallback& onProgressCallback)
{
@ -1004,7 +1002,7 @@ namespace ix
wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate, onProgressCallback);
}
WebSocketSendInfo WebSocketTransport::sendText(const std::string& message,
WebSocketSendInfo WebSocketTransport::sendText(const IXWebSocketSendData& message,
const OnProgressCallback& onProgressCallback)
{

View File

@ -19,6 +19,7 @@
#include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketSendData.h"
#include <atomic>
#include <functional>
#include <list>
@ -88,11 +89,11 @@ namespace ix
bool enablePerMessageDeflate);
PollResult poll();
WebSocketSendInfo sendBinary(const std::string& message,
WebSocketSendInfo sendBinary(const IXWebSocketSendData& message,
const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendText(const std::string& message,
WebSocketSendInfo sendText(const IXWebSocketSendData& message,
const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendPing(const std::string& message);
WebSocketSendInfo sendPing(const IXWebSocketSendData& message);
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage,
@ -241,9 +242,8 @@ namespace ix
bool sendOnSocket();
bool receiveFromSocket();
template<class T>
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
const T& message,
const IXWebSocketSendData& message,
bool compress,
const OnProgressCallback& onProgressCallback = nullptr);

View File

@ -6,4 +6,4 @@
#pragma once
#define IX_WEBSOCKET_VERSION "11.3.0"
#define IX_WEBSOCKET_VERSION "11.4.1"

View File

@ -23,6 +23,7 @@ set (TEST_TARGET_NAMES
IXWebSocketSubProtocolTest
# IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws
IXStrCaseCompareTest
IXExponentialBackoffTest
)
# Some unittest don't work on windows yet

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
/*
* IXExponentialBackoffTest.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2022 Machine Zone. All rights reserved.
*/
#include "IXTest.h"
#include "catch.hpp"
#include <iostream>
#include <ixwebsocket/IXExponentialBackoff.h>
#include <string.h>
using namespace ix;
namespace ix
{
TEST_CASE("exponential_backoff", "[exponential_backoff]")
{
SECTION("1")
{
// First parameter is retrycount
REQUIRE(calculateRetryWaitMilliseconds(0, 10000, 100) == 100);
REQUIRE(calculateRetryWaitMilliseconds(1, 10000, 100) == 200);
REQUIRE(calculateRetryWaitMilliseconds(2, 10000, 100) == 400);
REQUIRE(calculateRetryWaitMilliseconds(3, 10000, 100) == 800);
REQUIRE(calculateRetryWaitMilliseconds(4, 10000, 100) == 1600);
REQUIRE(calculateRetryWaitMilliseconds(5, 10000, 100) == 3200);
REQUIRE(calculateRetryWaitMilliseconds(6, 10000, 100) == 6400);
REQUIRE(calculateRetryWaitMilliseconds(20, 10000, 100) == 10000);
REQUIRE(calculateRetryWaitMilliseconds(25, 10000, 100) == 10000);
// Things get special after 26 retries
REQUIRE(calculateRetryWaitMilliseconds(26, 10000, 100) == 10000);
REQUIRE(calculateRetryWaitMilliseconds(27, 10000, 100) == 10000);
REQUIRE(calculateRetryWaitMilliseconds(27, 10000, 100) == 10000);
}
}
} // namespace ix

View File

@ -5,6 +5,7 @@
*/
#include "catch.hpp"
#include <cstdint>
#include <iostream>
#include <ixwebsocket/IXHttpClient.h>
@ -221,4 +222,124 @@ 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);
}
SECTION("Async API, streaming transfer")
{
bool async = true;
HttpClient httpClient(async);
WebSocketHttpHeaders headers;
SocketTLSOptions tlsOptions;
tlsOptions.caFile = "cacert.pem";
httpClient.setTLSOptions(tlsOptions);
std::string url("http://speedtest.belwue.net/random-100M");
auto args = httpClient.createRequest(url);
args->extraHeaders = headers;
args->connectTimeout = 60;
args->transferTimeout = 120;
args->followRedirects = true;
args->maxRedirects = 10;
args->verbose = true;
args->compress = false;
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;
};
// compute Adler-32 checksum of received data
uint32_t a = 1, b = 0;
args->onChunkCallback = [&](const std::string& data) {
for (const char c: data)
{
a = (a + (unsigned char)c) % 65521;
b = (b + a) % 65521;
}
};
std::atomic<bool> requestCompleted(false);
std::atomic<HttpErrorCode> errorCode(HttpErrorCode::Invalid);
std::atomic<int> statusCode(0);
httpClient.performRequest(
args, [&](const HttpResponsePtr& response) {
errorCode = response->errorCode;
statusCode = response->statusCode;
requestCompleted = true;
});
int wait = 0;
while (wait < 120000)
{
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::Ok);
REQUIRE(statusCode == 200);
// compare checksum with a known good value
uint32_t checksum = (b << 16) | a;
REQUIRE(checksum == 1440194471);
}
}