Compare commits
38 Commits
feature/pr
...
bsergean-p
Author | SHA1 | Date | |
---|---|---|---|
5698ed879f | |||
e12a6ddbdd | |||
127cc4a023 | |||
7711cb1ae7 | |||
db7057de69 | |||
c28b569535 | |||
8d661b8e81 | |||
a951bc9cae | |||
b5cf33a582 | |||
2bc3afcf6c | |||
60563d88f2 | |||
1f2895a469 | |||
9f00428d57 | |||
f53b2f8878 | |||
47d0b70ebf | |||
8c15405ed0 | |||
5457217503 | |||
66cd29e747 | |||
688f85fda6 | |||
71f73e5f6e | |||
42db05a38b | |||
6cce066021 | |||
9c6dcb24a9 | |||
05a27c89e3 | |||
23dbc8ae63 | |||
5f2955ef78 | |||
882081536c | |||
74bb85efe9 | |||
e66437b560 | |||
97aa1f956a | |||
3f1fc6906c | |||
9bbd1f1b30 | |||
18c2b69633 | |||
178f218374 | |||
6a1aa27b5f | |||
e7f89ae529 | |||
cdeaf8e2be | |||
dbafa0aa07 |
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
|
||||
|
1
.github/workflows/unittest_linux.yml
vendored
1
.github/workflows/unittest_linux.yml
vendored
@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
|
1
.github/workflows/unittest_linux_asan.yml
vendored
1
.github/workflows/unittest_linux_asan.yml
vendored
@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
|
@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
mac_tsan_mbedtls:
|
||||
|
@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
mac_tsan_openssl:
|
||||
|
@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
mac_tsan_sectransport:
|
||||
|
1
.github/workflows/unittest_uwp.yml
vendored
1
.github/workflows/unittest_uwp.yml
vendored
@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
uwp:
|
||||
|
1
.github/workflows/unittest_windows.yml
vendored
1
.github/workflows/unittest_windows.yml
vendored
@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
|
1
.github/workflows/unittest_windows_gcc.yml
vendored
1
.github/workflows/unittest_windows_gcc.yml
vendored
@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
|
@ -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)
|
||||
|
111
CMakeLists.txt
111
CMakeLists.txt
@ -41,6 +41,7 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXSelectInterrupt.cpp
|
||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||
ixwebsocket/IXSelectInterruptPipe.cpp
|
||||
ixwebsocket/IXSelectInterruptEvent.cpp
|
||||
ixwebsocket/IXSetThreadName.cpp
|
||||
ixwebsocket/IXSocket.cpp
|
||||
ixwebsocket/IXSocketConnect.cpp
|
||||
@ -80,6 +81,7 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXSelectInterrupt.h
|
||||
ixwebsocket/IXSelectInterruptFactory.h
|
||||
ixwebsocket/IXSelectInterruptPipe.h
|
||||
ixwebsocket/IXSelectInterruptEvent.h
|
||||
ixwebsocket/IXSetThreadName.h
|
||||
ixwebsocket/IXSocket.h
|
||||
ixwebsocket/IXSocketConnect.h
|
||||
@ -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)
|
||||
@ -180,56 +202,61 @@ if (USE_TLS)
|
||||
# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0)
|
||||
endif()
|
||||
|
||||
# Use OPENSSL_ROOT_DIR CMake variable if you need to use your own openssl
|
||||
find_package(OpenSSL REQUIRED)
|
||||
# This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL
|
||||
if (NOT OPENSSL_FOUND)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||
|
||||
add_definitions(${OPENSSL_DEFINITIONS})
|
||||
target_include_directories(ixwebsocket PUBLIC ${OPENSSL_INCLUDE_DIR})
|
||||
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
||||
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
|
||||
target_link_libraries(ixwebsocket PRIVATE ${OPENSSL_LIBRARIES})
|
||||
elseif (USE_MBED_TLS)
|
||||
message(STATUS "TLS configured to use mbedtls")
|
||||
|
||||
find_package(MbedTLS REQUIRED)
|
||||
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
|
||||
# This MBEDTLS_FOUND check is to help find a cmake manually configured MbedTLS
|
||||
if (NOT MBEDTLS_FOUND)
|
||||
find_package(MbedTLS REQUIRED)
|
||||
|
||||
if (MBEDTLS_VERSION_GREATER_THAN_3)
|
||||
target_compile_definitions(ixwebsocket PRIVATE IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${MBEDTLS_INCLUDE_DIRS}>)
|
||||
target_link_libraries(ixwebsocket PRIVATE ${MBEDTLS_LIBRARIES})
|
||||
elseif (USE_SECURE_TRANSPORT)
|
||||
message(STATUS "TLS configured to use secure transport")
|
||||
target_link_libraries(ixwebsocket "-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)
|
||||
# Use ZLIB_ROOT CMake variable if you need to use your own zlib
|
||||
find_package(ZLIB REQUIRED)
|
||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
|
||||
# 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 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()
|
||||
|
||||
|
||||
@ -242,23 +269,31 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
target_compile_options(ixwebsocket PRIVATE /MP)
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
target_include_directories(ixwebsocket PUBLIC
|
||||
$<BUILD_INTERFACE:${IXWEBSOCKET_INCLUDE_DIRS}/>
|
||||
$<INSTALL_INTERFACE:include/ixwebsocket>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket>
|
||||
)
|
||||
|
||||
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
|
||||
|
||||
install(TARGETS ixwebsocket
|
||||
EXPORT ixwebsocket
|
||||
ARCHIVE DESTINATION lib
|
||||
PUBLIC_HEADER DESTINATION include/ixwebsocket/
|
||||
)
|
||||
add_library(ixwebsocket::ixwebsocket ALIAS ixwebsocket)
|
||||
|
||||
install(EXPORT ixwebsocket
|
||||
FILE ixwebsocket-config.cmake
|
||||
NAMESPACE ixwebsocket::
|
||||
DESTINATION lib/cmake/ixwebsocket)
|
||||
option(IXWEBSOCKET_INSTALL "Install IXWebSocket" TRUE)
|
||||
|
||||
if (IXWEBSOCKET_INSTALL)
|
||||
install(TARGETS ixwebsocket
|
||||
EXPORT ixwebsocket
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
|
||||
)
|
||||
|
||||
install(EXPORT ixwebsocket
|
||||
FILE ixwebsocket-config.cmake
|
||||
NAMESPACE ixwebsocket::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket)
|
||||
endif()
|
||||
|
||||
if (USE_WS OR USE_TEST)
|
||||
include(FetchContent)
|
||||
|
@ -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-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
|
@ -2,6 +2,29 @@
|
||||
|
||||
All changes to this project will be documented in this file.
|
||||
|
||||
## [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)
|
||||
|
||||
## [11.2.10] - 2021-07-27
|
||||
|
||||
(ws) bump CLI command line parsing library from 1.8 to 2.0
|
||||
|
@ -20,7 +20,7 @@ Options for building:
|
||||
* `-DBUILD_SHARED_LIBS=ON` will build the unittest as a shared libary instead of a static library, which is the default
|
||||
* `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension
|
||||
* `-DUSE_TLS=1` will enable TLS support
|
||||
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows)
|
||||
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows). When using a custom version of openssl (say a prebuilt version, odd runtime problems can happens, as in #319, and special cmake trickery will be required (see this [comment](https://github.com/machinezone/IXWebSocket/issues/175#issuecomment-620231032))
|
||||
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support
|
||||
* `-DUSE_WS=1` will build the ws interactive command line tool
|
||||
* `-DUSE_TEST=1` will build the unittest
|
||||
|
@ -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`.
|
||||
|
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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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>;
|
||||
|
@ -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,
|
||||
|
@ -226,4 +226,10 @@ namespace ix
|
||||
200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
|
||||
});
|
||||
}
|
||||
|
||||
int HttpServer::getTimeoutSecs()
|
||||
{
|
||||
return _timeoutSecs;
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
@ -40,6 +40,7 @@ namespace ix
|
||||
|
||||
void makeDebugServer();
|
||||
|
||||
int getTimeoutSecs();
|
||||
private:
|
||||
// Member variables
|
||||
OnConnectionCallback _onConnectionCallback;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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&)>;
|
||||
}
|
||||
|
@ -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;
|
||||
|
85
ixwebsocket/IXSelectInterruptEvent.cpp
Normal file
85
ixwebsocket/IXSelectInterruptEvent.cpp
Normal 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
|
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)
|
||||
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include <errno.h>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#include <Shlwapi.h>
|
||||
#include <shlwapi.h>
|
||||
#else
|
||||
#include <fnmatch.h>
|
||||
#endif
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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,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"
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
||||
|
128
ixwebsocket/IXWebSocketSendData.h
Normal file
128
ixwebsocket/IXWebSocketSendData.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -52,6 +52,9 @@ namespace ix
|
||||
|
||||
const static int kDefaultHandShakeTimeoutSecs;
|
||||
|
||||
int getHandshakeTimeoutSecs();
|
||||
bool isPongEnabled();
|
||||
bool isPerMessageDeflateEnabled();
|
||||
private:
|
||||
// Member variables
|
||||
int _handshakeTimeoutSecs;
|
||||
|
@ -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)
|
||||
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -6,4 +6,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define IX_WEBSOCKET_VERSION "11.2.10"
|
||||
#define IX_WEBSOCKET_VERSION "11.4.0"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
||||
|
2
ws/package-lock.json
generated
2
ws/package-lock.json
generated
@ -8,7 +8,7 @@
|
||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "6.2.0",
|
||||
"version": ">=6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz",
|
||||
"integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==",
|
||||
"requires": {
|
||||
|
Reference in New Issue
Block a user