Compare commits

..

6 Commits

Author SHA1 Message Date
Benjamin Sergeant
317e3c44bf
Update CHANGELOG.md 2021-10-22 11:10:42 -07:00
Benjamin Sergeant
61b90251af
Update IXWebSocketVersion.h 2021-10-22 11:09:12 -07:00
Benjamin Sergeant
851157f252
update api call 2021-10-22 10:59:51 -07:00
Benjamin Sergeant
69541cf750
define macro for mbedtls >= 3 2021-10-22 10:58:56 -07:00
Benjamin Sergeant
1e166eee57
cmake change find header file 2021-10-22 10:57:51 -07:00
Benjamin Sergeant
6860b2d09d
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
2021-10-22 10:22:57 -07:00
80 changed files with 2047 additions and 6408 deletions

View File

@ -1,8 +1,6 @@
name: mkdocs name: mkdocs
on: on:
push: push:
branches:
- master
paths: paths:
- 'docs/**' - 'docs/**'
@ -23,7 +21,6 @@ jobs:
pip install pygments pip install pygments
- name: Build doc - name: Build doc
run: | run: |
git checkout master
git clean -dfx . git clean -dfx .
git fetch git fetch
git pull git pull

19
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'

27
.github/workflows/unittest_windows.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: windows
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-vsdevenv@master
- uses: seanmiddleditch/gha-setup-ninja@master
- run: |
mkdir build
cd build
cmake -GNinja -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=OFF -DBUILD_SHARED_LIBS=OFF ..
- run: |
cd build
ninja
- run: |
cd build
ninja test
#- run: ../build/test/ixwebsocket_unittest.exe
# working-directory: test

View File

@ -11,7 +11,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-ninja@master - uses: seanmiddleditch/gha-setup-ninja@master
- uses: bsergean/setup-mingw@d79ce405bac9edef3a1726ef00554a56f0bafe66 - uses: egor-tensin/setup-mingw@v2
- run: | - run: |
mkdir build mkdir build
cd build cd build

2
.gitignore vendored
View File

@ -8,5 +8,3 @@ ws/.srl
ixhttpd ixhttpd
makefile makefile
a.out a.out
.idea/
cmake-build-debug/

View File

@ -6,12 +6,11 @@
cmake_minimum_required(VERSION 3.4.1...3.17.2) cmake_minimum_required(VERSION 3.4.1...3.17.2)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}") set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
project(ixwebsocket LANGUAGES C CXX VERSION 11.4.6) project(ixwebsocket C CXX)
set (CMAKE_CXX_STANDARD 11) set (CMAKE_CXX_STANDARD 11)
set (CXX_STANDARD_REQUIRED ON) set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF) set (CMAKE_CXX_EXTENSIONS OFF)
set (CMAKE_EXPORT_COMPILE_COMMANDS yes)
option (BUILD_DEMO OFF) option (BUILD_DEMO OFF)
@ -42,7 +41,6 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXSelectInterrupt.cpp ixwebsocket/IXSelectInterrupt.cpp
ixwebsocket/IXSelectInterruptFactory.cpp ixwebsocket/IXSelectInterruptFactory.cpp
ixwebsocket/IXSelectInterruptPipe.cpp ixwebsocket/IXSelectInterruptPipe.cpp
ixwebsocket/IXSelectInterruptEvent.cpp
ixwebsocket/IXSetThreadName.cpp ixwebsocket/IXSetThreadName.cpp
ixwebsocket/IXSocket.cpp ixwebsocket/IXSocket.cpp
ixwebsocket/IXSocketConnect.cpp ixwebsocket/IXSocketConnect.cpp
@ -67,7 +65,6 @@ set( IXWEBSOCKET_SOURCES
) )
set( IXWEBSOCKET_HEADERS set( IXWEBSOCKET_HEADERS
ixwebsocket/IXBase64.h
ixwebsocket/IXBench.h ixwebsocket/IXBench.h
ixwebsocket/IXCancellationRequest.h ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h ixwebsocket/IXConnectionState.h
@ -83,7 +80,6 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXSelectInterrupt.h ixwebsocket/IXSelectInterrupt.h
ixwebsocket/IXSelectInterruptFactory.h ixwebsocket/IXSelectInterruptFactory.h
ixwebsocket/IXSelectInterruptPipe.h ixwebsocket/IXSelectInterruptPipe.h
ixwebsocket/IXSelectInterruptEvent.h
ixwebsocket/IXSetThreadName.h ixwebsocket/IXSetThreadName.h
ixwebsocket/IXSocket.h ixwebsocket/IXSocket.h
ixwebsocket/IXSocketConnect.h ixwebsocket/IXSocketConnect.h
@ -112,7 +108,6 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
ixwebsocket/IXWebSocketProxyServer.h ixwebsocket/IXWebSocketProxyServer.h
ixwebsocket/IXWebSocketSendData.h
ixwebsocket/IXWebSocketSendInfo.h ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketServer.h ixwebsocket/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h ixwebsocket/IXWebSocketTransport.h
@ -136,7 +131,6 @@ if (USE_TLS)
else() # default to OpenSSL on all other platforms else() # default to OpenSSL on all other platforms
if (NOT USE_MBED_TLS) # Unless mbedtls is requested if (NOT USE_MBED_TLS) # Unless mbedtls is requested
set(USE_OPEN_SSL ON) set(USE_OPEN_SSL ON)
set(requires "openssl")
endif() endif()
endif() endif()
@ -154,28 +148,10 @@ if (USE_TLS)
endif() endif()
endif() endif()
if(BUILD_SHARED_LIBS) add_library( ixwebsocket
# Building shared library ${IXWEBSOCKET_SOURCES}
${IXWEBSOCKET_HEADERS}
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 ${PROJECT_VERSION})
else()
# Static library
add_library( ixwebsocket
${IXWEBSOCKET_SOURCES}
${IXWEBSOCKET_HEADERS}
)
endif()
if (USE_TLS) if (USE_TLS)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_TLS) target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_TLS)
@ -212,7 +188,7 @@ if (USE_TLS)
add_definitions(${OPENSSL_DEFINITIONS}) add_definitions(${OPENSSL_DEFINITIONS})
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>) target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
target_link_libraries(ixwebsocket PRIVATE ${OPENSSL_LIBRARIES}) target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
elseif (USE_MBED_TLS) elseif (USE_MBED_TLS)
message(STATUS "TLS configured to use mbedtls") message(STATUS "TLS configured to use mbedtls")
@ -226,35 +202,46 @@ if (USE_TLS)
endif() endif()
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${MBEDTLS_INCLUDE_DIRS}>) target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${MBEDTLS_INCLUDE_DIRS}>)
target_link_libraries(ixwebsocket PRIVATE ${MBEDTLS_LIBRARIES}) target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
elseif (USE_SECURE_TRANSPORT) elseif (USE_SECURE_TRANSPORT)
message(STATUS "TLS configured to use secure transport") message(STATUS "TLS configured to use secure transport")
target_link_libraries(ixwebsocket PRIVATE "-framework Foundation" "-framework Security") target_link_libraries(ixwebsocket "-framework Foundation" "-framework Security")
endif() endif()
endif() endif()
option(USE_ZLIB "Enable zlib support" TRUE) option(USE_ZLIB "Enable zlib support" TRUE)
if (USE_ZLIB) if (USE_ZLIB)
find_package(ZLIB REQUIRED) # This ZLIB_FOUND check is to help find a cmake manually configured zlib
target_link_libraries(ixwebsocket PRIVATE ZLIB::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})
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB) target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
endif() 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) if (WIN32)
target_link_libraries(ixwebsocket PRIVATE wsock32 ws2_32 shlwapi) target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
target_compile_definitions(ixwebsocket PRIVATE _CRT_SECURE_NO_WARNINGS) add_definitions(-D_CRT_SECURE_NO_WARNINGS)
if (USE_TLS) if (USE_TLS)
target_link_libraries(ixwebsocket PRIVATE Crypt32) target_link_libraries(ixwebsocket Crypt32)
endif() endif()
endif() endif()
if (UNIX AND NOT APPLE) if (UNIX)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads) find_package(Threads)
target_link_libraries(ixwebsocket PRIVATE Threads::Threads) target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
endif() endif()
@ -276,8 +263,6 @@ target_include_directories(ixwebsocket PUBLIC
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}") set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
add_library(ixwebsocket::ixwebsocket ALIAS ixwebsocket)
option(IXWEBSOCKET_INSTALL "Install IXWebSocket" TRUE) option(IXWEBSOCKET_INSTALL "Install IXWebSocket" TRUE)
if (IXWEBSOCKET_INSTALL) if (IXWEBSOCKET_INSTALL)
@ -287,18 +272,10 @@ if (IXWEBSOCKET_INSTALL)
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
) )
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket")
set(prefix ${CMAKE_INSTALL_PREFIX})
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
install(EXPORT ixwebsocket install(EXPORT ixwebsocket
FILE ixwebsocket-targets.cmake FILE ixwebsocket-config.cmake
NAMESPACE ixwebsocket:: NAMESPACE ixwebsocket::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket)
)
endif() endif()
if (USE_WS OR USE_TEST) if (USE_WS OR USE_TEST)

View File

@ -1,10 +1,10 @@
## Hello world ## Hello world
(note from the main developer, sadly I don't have too much time to devote to this library anymore, maybe it's time to pass the maintenance to someone else more motivated ?)
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. 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. Two important design goals are simplicity and correctness. 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).
```cpp ```cpp
/* /*
@ -37,7 +37,6 @@ int main()
// Connect to a server with encryption // Connect to a server with encryption
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration // See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
// https://github.com/machinezone/IXWebSocket/issues/386#issuecomment-1105235227 (self signed certificates)
std::string url("wss://echo.websocket.org"); std::string url("wss://echo.websocket.org");
webSocket.setUrl(url); webSocket.setUrl(url);
@ -101,16 +100,12 @@ Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible.
If your company or project is using this library, feel free to open an issue or PR to amend this list. 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) - [Machine Zone](https://www.mz.com)
- [Tokio](https://github.com/liz3/tokio), a discord library focused on audio playback with node bindings. - [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. - [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 - [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 (archived as of Oct 8, 2021) - [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 - [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 - [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.
- [Candy](https://github.com/lanthora/candy), A WebSocket and TUN based VPN for Linux
- [ITGmania](https://github.com/itgmania/itgmania), a cross platform Dance Dance Revolution-like emulator.
## Alternative libraries ## Alternative libraries
@ -118,8 +113,8 @@ There are plenty of great websocket libraries out there, which might work for yo
* [websocketpp](https://github.com/zaphoyd/websocketpp) - C++ * [websocketpp](https://github.com/zaphoyd/websocketpp) - C++
* [beast](https://github.com/boostorg/beast) - C++ * [beast](https://github.com/boostorg/beast) - C++
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C++
* [libwebsockets](https://libwebsockets.org/) - C * [libwebsockets](https://libwebsockets.org/) - C
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C
* [wslay](https://github.com/tatsuhiro-t/wslay) - C * [wslay](https://github.com/tatsuhiro-t/wslay) - C
[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. [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.
@ -137,7 +132,9 @@ To check the performance of a websocket library, you can look at the [autoroute]
| Windows | Disabled | None | [![Build2][5]][0] | | Windows | Disabled | None | [![Build2][5]][0] |
| UWP | Disabled | None | [![Build2][6]][0] | | UWP | Disabled | None | [![Build2][6]][0] |
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][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 * 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. * TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.
@ -149,4 +146,5 @@ To check the performance of a websocket library, you can look at the [autoroute]
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg [5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg [6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/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

22
appveyor.yml Normal file
View File

@ -0,0 +1,22 @@
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,37 +2,6 @@
All changes to this project will be documented in this file. All changes to this project will be documented in this file.
## [11.4.5] - 2024-06-05
New changes are documented in the Release page in the GitHub repository.
## [11.4.4] - 2023-06-05
## [11.4.3] - 2022-05-13
Set shorter thread names
BoringSSL fix with SNI
Websocket computed header is valid Base64
## [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 ## [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) (library/cmake) Compatible with MbedTLS 3 + fix a bug on Windows where the incorrect remote port is computed (#320)

View File

@ -54,7 +54,7 @@ To use the installed package within a cmake project, use the following:
# include headers # include headers
include_directories(${IXWEBSOCKET_INCLUDE_DIR}) include_directories(${IXWEBSOCKET_INCLUDE_DIR})
# ... # ...
target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUND target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUNS
``` ```

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 ### 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. 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.
### Automatic reconnection ### Automatic reconnection
@ -30,6 +30,12 @@ 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. 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 ## 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`. * 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,111 +1,54 @@
## Hello world ## Introduction
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. [*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.
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. * macOS
* iOS
* Linux
* Android
* Windows
* FreeBSD
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). ## Example code
```cpp ```c++
/* // Required on Windows
* main.cpp ix::initNetSystem();
* 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
*/
#include <ixwebsocket/IXNetSystem.h> // Our websocket object
#include <ixwebsocket/IXWebSocket.h> ix::WebSocket webSocket;
#include <ixwebsocket/IXUserAgent.h>
#include <iostream>
int main() std::string url("ws://localhost:8080/");
{ webSocket.setUrl(url);
// Required on Windows
ix::initNetSystem();
// Our websocket object // Setup a callback to be fired when a message or an event (open, close, error) is received
ix::WebSocket webSocket; webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
// 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)
{
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); if (msg->type == ix::WebSocketMessageType::Message)
std::cout << "> " << std::flush; {
std::cout << msg->str << std::endl;
}
} }
);
return 0; // 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");
``` ```
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. ## Why another library?
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). 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 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. 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`.
Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible. * Few dependencies (only zlib)
* Simple to use ; uses std::string and std::function callbacks.
## Users * Complete support of the websocket protocol, and basic http support.
* Client and Server
If your company or project is using this library, feel free to open an issue or PR to amend this list. * TLS support
- [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 ## Alternative libraries
@ -115,35 +58,7 @@ There are plenty of great websocket libraries out there, which might work for yo
* [beast](https://github.com/boostorg/beast) - C++ * [beast](https://github.com/boostorg/beast) - C++
* [libwebsockets](https://libwebsockets.org/) - C * [libwebsockets](https://libwebsockets.org/) - C
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C * [µWebSockets](https://github.com/uNetworking/uWebSockets) - C
* [wslay](https://github.com/tatsuhiro-t/wslay) - C
[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. ## Contributing
To check the performance of a websocket library, you can look at the [autoroute](https://github.com/bsergean/autoroute) project. 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.
## 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,18 +90,6 @@ 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 ### ReadyState
`getReadyState()` returns the state of the connection. There are 4 possible states. `getReadyState()` returns the state of the connection. There are 4 possible states.
@ -153,9 +141,9 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{ {
std::stringstream ss; std::stringstream ss;
ss << "Error: " << msg->errorInfo.reason << std::endl; ss << "Error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->errorInfo.retries << std::endl; ss << "#retries: " << msg->eventInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl; ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl; ss << "HTTP Status: " << msg->eventInfo.http_status << std::endl;
std::cout << ss.str() << std::endl; std::cout << ss.str() << std::endl;
} }
} }
@ -301,9 +289,7 @@ This api was actually changed to take a weak_ptr<WebSocket> as the first argumen
// Run a server on localhost at a given port. // Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters. // Bound host name, max connections and listen backlog can also be passed in as parameters.
int port = 8008; ix::WebSocketServer server(port);
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
ix::WebSocketServer server(port, host);
server.setOnConnectionCallback( server.setOnConnectionCallback(
[&server](std::weak_ptr<WebSocket> webSocket, [&server](std::weak_ptr<WebSocket> webSocket,
@ -386,9 +372,7 @@ The webSocket reference is guaranteed to be always valid ; by design the callbac
// Run a server on localhost at a given port. // Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters. // Bound host name, max connections and listen backlog can also be passed in as parameters.
int port = 8008; ix::WebSocketServer server(port);
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
ix::WebSocketServer server(port, host);
server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) { server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
// The ConnectionState object contains information about the connection, // The ConnectionState object contains information about the connection,
@ -445,17 +429,6 @@ server.wait();
``` ```
### Heartbeat
You can configure an optional heartbeat / keep-alive for the WebSocket server. The heartbeat interval can be adjusted or disabled when constructing the `WebSocketServer`. Setting the interval to `-1` disables the heartbeat feature; this is the default setting. The parameter you set will be applied to every `WebSocket` object that the server creates.
To enable a 45 second heartbeat on a `WebSocketServer`:
```cpp
int pingIntervalSeconds = 45;
ix::WebSocketServer server(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily, pingIntervalSeconds);
```
## HTTP client API ## HTTP client API
```cpp ```cpp
@ -535,28 +508,15 @@ bool async = true;
HttpClient httpClient(async); HttpClient httpClient(async);
auto args = httpClient.createRequest(url, HttpClient::kGet); 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, // Push the request to a queue,
bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response) bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
{ {
// This callback execute in a background thread. Make sure you uses appropriate protection such as mutex // This callback execute in a background thread. Make sure you uses appropriate protection such as mutex
auto statusCode = response->statusCode; // acess results auto statusCode = response->statusCode; // acess results
// response->body is empty if onChunkCallback was used
} }
); );
// ok will be false if your httpClient is not async // 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. See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart.
@ -639,5 +599,3 @@ For a client, specifying `caFile` can be used if connecting to a server that use
For a server, specifying `caFile` implies that: For a server, specifying `caFile` implies that:
1. You require clients to present a certificate 1. You require clients to present a certificate
1. It must be signed by one of the trusted roots in the file 1. It must be signed by one of the trusted roots in the file
By default, a destination's hostname is always validated against the certificate that it presents. To accept certificates with any hostname, set `ix::SocketTLSOptions::disable_hostname_validation` to `true`.

View File

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

View File

@ -1,11 +0,0 @@
prefix=@prefix@
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: ixwebsocket
Description: websocket and http client and server library, with TLS support and very few dependencies
Version: @CMAKE_PROJECT_VERSION@
Libs: -L${libdir} -lixwebsocket
Cflags: -I${includedir}
Requires: @requires@

View File

@ -1,125 +0,0 @@
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <cstdint>
#include <string>
namespace macaron {
class Base64 {
public:
static std::string Encode(const std::string data) {
static constexpr char sEncodingTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::string Decode(const std::string& input, std::string& out) {
static constexpr unsigned char kDecodingTable[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
size_t in_len = input.size();
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=') out_len--;
if (input[in_len - 2] == '=') out_len--;
out.resize(out_len);
for (size_t i = 0, j = 0; i < in_len;) {
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
}
return "";
}
};
}
#endif /* _MACARON_BASE64_H_ */

View File

@ -6,7 +6,7 @@
#pragma once #pragma once
#include <chrono> #include <chrono>
#include <cstdint> #include <stdint.h>
#include <string> #include <string>
namespace ix namespace ix

View File

@ -7,9 +7,9 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <cstdint>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <stdint.h>
#include <string> #include <string>
namespace ix namespace ix

View File

@ -23,23 +23,12 @@
#include <chrono> #include <chrono>
#include <string.h> #include <string.h>
#include <thread> #include <thread>
#include <utility>
// mingw build quirks // mingw build quirks
#if defined(_WIN32) && defined(__GNUC__) #if defined(_WIN32) && defined(__GNUC__)
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV NI_NUMERICSERV #define AI_NUMERICSERV NI_NUMERICSERV
#endif
#ifndef AI_ADDRCONFIG
#define AI_ADDRCONFIG LUP_ADDRCONFIG #define AI_ADDRCONFIG LUP_ADDRCONFIG
#endif #endif
#endif
#ifdef __APPLE__
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
#endif
namespace ix namespace ix
{ {
@ -55,7 +44,7 @@ namespace ix
; ;
} }
DNSLookup::AddrInfoPtr DNSLookup::getAddrInfo(const std::string& hostname, struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
int port, int port,
std::string& errMsg) std::string& errMsg)
{ {
@ -74,10 +63,10 @@ namespace ix
errMsg = gai_strerror(getaddrinfo_result); errMsg = gai_strerror(getaddrinfo_result);
res = nullptr; res = nullptr;
} }
return AddrInfoPtr{ res, freeaddrinfo }; return res;
} }
DNSLookup::AddrInfoPtr DNSLookup::resolve(std::string& errMsg, struct addrinfo* DNSLookup::resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested, const CancellationRequest& isCancellationRequested,
bool cancellable) bool cancellable)
{ {
@ -85,7 +74,12 @@ namespace ix
: resolveUnCancellable(errMsg, isCancellationRequested); : resolveUnCancellable(errMsg, isCancellationRequested);
} }
DNSLookup::AddrInfoPtr DNSLookup::resolveUnCancellable( void DNSLookup::release(struct addrinfo* addr)
{
freeaddrinfo(addr);
}
struct addrinfo* DNSLookup::resolveUnCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested) std::string& errMsg, const CancellationRequest& isCancellationRequested)
{ {
errMsg = "no error"; errMsg = "no error";
@ -100,7 +94,7 @@ namespace ix
return getAddrInfo(_hostname, _port, errMsg); return getAddrInfo(_hostname, _port, errMsg);
} }
DNSLookup::AddrInfoPtr DNSLookup::resolveCancellable( struct addrinfo* DNSLookup::resolveCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested) std::string& errMsg, const CancellationRequest& isCancellationRequested)
{ {
errMsg = "no error"; errMsg = "no error";
@ -163,7 +157,7 @@ namespace ix
// gone, so we use temporary variables (res) or we pass in by copy everything that // gone, so we use temporary variables (res) or we pass in by copy everything that
// getAddrInfo needs to work. // getAddrInfo needs to work.
std::string errMsg; std::string errMsg;
auto res = getAddrInfo(hostname, port, errMsg); struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock()) if (auto lock = self.lock())
{ {
@ -187,13 +181,13 @@ namespace ix
return _errMsg; return _errMsg;
} }
void DNSLookup::setRes(DNSLookup::AddrInfoPtr addr) void DNSLookup::setRes(struct addrinfo* addr)
{ {
std::lock_guard<std::mutex> lock(_resMutex); std::lock_guard<std::mutex> lock(_resMutex);
_res = std::move(addr); _res = addr;
} }
DNSLookup::AddrInfoPtr DNSLookup::getRes() struct addrinfo* DNSLookup::getRes()
{ {
std::lock_guard<std::mutex> lock(_resMutex); std::lock_guard<std::mutex> lock(_resMutex);
return _res; return _res;

View File

@ -12,7 +12,6 @@
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include <atomic> #include <atomic>
#include <cstdint>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <set> #include <set>
@ -25,21 +24,22 @@ namespace ix
class DNSLookup : public std::enable_shared_from_this<DNSLookup> class DNSLookup : public std::enable_shared_from_this<DNSLookup>
{ {
public: public:
using AddrInfoPtr = std::shared_ptr<addrinfo>;
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait); DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup() = default; ~DNSLookup() = default;
AddrInfoPtr resolve(std::string& errMsg, struct addrinfo* resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested, const CancellationRequest& isCancellationRequested,
bool cancellable = true); bool cancellable = true);
void release(struct addrinfo* addr);
private: private:
AddrInfoPtr resolveCancellable(std::string& errMsg, struct addrinfo* resolveCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
AddrInfoPtr resolveUnCancellable(std::string& errMsg, struct addrinfo* resolveUnCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
AddrInfoPtr getAddrInfo(const std::string& hostname, static struct addrinfo* getAddrInfo(const std::string& hostname,
int port, int port,
std::string& errMsg); std::string& errMsg);
@ -48,15 +48,15 @@ namespace ix
void setErrMsg(const std::string& errMsg); void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg(); const std::string& getErrMsg();
void setRes(AddrInfoPtr addr); void setRes(struct addrinfo* addr);
AddrInfoPtr getRes(); struct addrinfo* getRes();
std::string _hostname; std::string _hostname;
int _port; int _port;
int64_t _wait; int64_t _wait;
const static int64_t kDefaultWait; const static int64_t kDefaultWait;
AddrInfoPtr _res; struct addrinfo* _res;
std::mutex _resMutex; std::mutex _resMutex;
std::string _errMsg; std::string _errMsg;

View File

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

View File

@ -14,12 +14,53 @@
#include <zlib.h> #include <zlib.h>
#endif #endif
#ifdef IXWEBSOCKET_USE_DEFLATE
#include <libdeflate.h>
#endif
namespace ix namespace ix
{ {
std::string gzipCompress(const std::string& str) std::string gzipCompress(const std::string& str)
{ {
#ifndef IXWEBSOCKET_USE_ZLIB #ifndef IXWEBSOCKET_USE_ZLIB
return std::string(); 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 #else
z_stream zs; // z_stream is zlib's control structure z_stream zs; // z_stream is zlib's control structure
memset(&zs, 0, sizeof(zs)); memset(&zs, 0, sizeof(zs));
@ -60,6 +101,7 @@ namespace ix
deflateEnd(&zs); deflateEnd(&zs);
return outstring; return outstring;
#endif // IXWEBSOCKET_USE_DEFLATE
#endif // IXWEBSOCKET_USE_ZLIB #endif // IXWEBSOCKET_USE_ZLIB
} }
@ -75,6 +117,26 @@ namespace ix
{ {
#ifndef IXWEBSOCKET_USE_ZLIB #ifndef IXWEBSOCKET_USE_ZLIB
return false; 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 #else
z_stream inflateState; z_stream inflateState;
memset(&inflateState, 0, sizeof(inflateState)); memset(&inflateState, 0, sizeof(inflateState));
@ -115,6 +177,7 @@ namespace ix
inflateEnd(&inflateState); inflateEnd(&inflateState);
return true; return true;
#endif // IXWEBSOCKET_USE_DEFLATE
#endif // IXWEBSOCKET_USE_ZLIB #endif // IXWEBSOCKET_USE_ZLIB
} }
} // namespace ix } // namespace ix

View File

@ -133,27 +133,23 @@ namespace ix
if (headers.find("Content-Length") != headers.end()) if (headers.find("Content-Length") != headers.end())
{ {
int contentLength = 0; int contentLength = 0;
try
{ {
const char* p = headers["Content-Length"].c_str(); contentLength = std::stoi(headers["Content-Length"]);
char* p_end{};
errno = 0;
long val = std::strtol(p, &p_end, 10);
if (p_end == p // invalid argument
|| errno == ERANGE // out of range
|| val < std::numeric_limits<int>::min()
|| val > std::numeric_limits<int>::max()) {
return std::make_tuple(
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
}
contentLength = val;
} }
catch (const std::exception&)
{
return std::make_tuple(
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
}
if (contentLength < 0) if (contentLength < 0)
{ {
return std::make_tuple( return std::make_tuple(
false, "Error: 'Content-Length' should be a positive integer", httpRequest); false, "Error: 'Content-Length' should be a positive integer", httpRequest);
} }
auto res = socket->readBytes(contentLength, nullptr, nullptr, isCancellationRequested); auto res = socket->readBytes(contentLength, nullptr, isCancellationRequested);
if (!res.first) if (!res.first)
{ {
return std::make_tuple( return std::make_tuple(

View File

@ -8,8 +8,6 @@
#include "IXProgressCallback.h" #include "IXProgressCallback.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include <atomic>
#include <cstdint>
#include <tuple> #include <tuple>
#include <unordered_map> #include <unordered_map>
@ -32,7 +30,6 @@ namespace ix
TooManyRedirects = 12, TooManyRedirects = 12,
ChunkReadError = 13, ChunkReadError = 13,
CannotReadBody = 14, CannotReadBody = 14,
Cancelled = 15,
Invalid = 100 Invalid = 100
}; };
@ -90,8 +87,6 @@ namespace ix
bool compressRequest = false; bool compressRequest = false;
Logger logger; Logger logger;
OnProgressCallback onProgressCallback; OnProgressCallback onProgressCallback;
OnChunkCallback onChunkCallback;
std::atomic<bool> cancel;
}; };
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>; using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;

View File

@ -12,7 +12,6 @@
#include "IXUserAgent.h" #include "IXUserAgent.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include <assert.h> #include <assert.h>
#include <cstdint>
#include <cstring> #include <cstring>
#include <iomanip> #include <iomanip>
#include <random> #include <random>
@ -140,9 +139,8 @@ namespace ix
std::string protocol, host, path, query; std::string protocol, host, path, query;
int port; int port;
bool isProtocolDefaultPort;
if (!UrlParser::parse(url, protocol, host, path, query, port, isProtocolDefaultPort)) if (!UrlParser::parse(url, protocol, host, path, query, port))
{ {
std::stringstream ss; std::stringstream ss;
ss << "Cannot parse url: " << url; ss << "Cannot parse url: " << url;
@ -175,15 +173,10 @@ namespace ix
// Build request string // Build request string
std::stringstream ss; std::stringstream ss;
ss << verb << " " << path << " HTTP/1.1\r\n"; ss << verb << " " << path << " HTTP/1.1\r\n";
ss << "Host: " << host; ss << "Host: " << host << "\r\n";
if (!isProtocolDefaultPort)
{
ss << ":" << port;
}
ss << "\r\n";
#ifdef IXWEBSOCKET_USE_ZLIB #ifdef IXWEBSOCKET_USE_ZLIB
if (args->compress && !args->onChunkCallback) if (args->compress)
{ {
ss << "Accept-Encoding: gzip" ss << "Accept-Encoding: gzip"
<< "\r\n"; << "\r\n";
@ -209,12 +202,6 @@ namespace ix
ss << "User-Agent: " << userAgent() << "\r\n"; ss << "User-Agent: " << userAgent() << "\r\n";
} }
// Set an origin header if missing
if (args->extraHeaders.find("Origin") == args->extraHeaders.end())
{
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
}
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody) if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
{ {
// Set request compression header // Set request compression header
@ -254,21 +241,17 @@ namespace ix
std::string errMsg; std::string errMsg;
// Make a cancellation object dealing with connection timeout // Make a cancellation object dealing with connection timeout
auto cancelled = makeCancellationRequestWithTimeout(args->connectTimeout, args->cancel); auto isCancellationRequested =
makeCancellationRequestWithTimeout(args->connectTimeout, _stop);
auto isCancellationRequested = [&]() {
return cancelled() || _stop;
};
bool success = _socket->connect(host, port, errMsg, isCancellationRequested); bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
if (!success) if (!success)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotConnect;
std::stringstream ss; std::stringstream ss;
ss << "Cannot connect to url: " << url << " / error : " << errMsg; ss << "Cannot connect to url: " << url << " / error : " << errMsg;
return std::make_shared<HttpResponse>(code, return std::make_shared<HttpResponse>(code,
description, description,
errorCode, HttpErrorCode::CannotConnect,
headers, headers,
payload, payload,
ss.str(), ss.str(),
@ -277,7 +260,7 @@ namespace ix
} }
// Make a new cancellation object dealing with transfer timeout // Make a new cancellation object dealing with transfer timeout
cancelled = makeCancellationRequestWithTimeout(args->transferTimeout, args->cancel); isCancellationRequested = makeCancellationRequestWithTimeout(args->transferTimeout, _stop);
if (args->verbose) if (args->verbose)
{ {
@ -294,11 +277,10 @@ namespace ix
if (!_socket->writeBytes(req, isCancellationRequested)) if (!_socket->writeBytes(req, isCancellationRequested))
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::SendError;
std::string errorMsg("Cannot send request"); std::string errorMsg("Cannot send request");
return std::make_shared<HttpResponse>(code, return std::make_shared<HttpResponse>(code,
description, description,
errorCode, HttpErrorCode::SendError,
headers, headers,
payload, payload,
errorMsg, errorMsg,
@ -314,11 +296,10 @@ namespace ix
if (!lineValid) if (!lineValid)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::CannotReadStatusLine;
std::string errorMsg("Cannot retrieve status line"); std::string errorMsg("Cannot retrieve status line");
return std::make_shared<HttpResponse>(code, return std::make_shared<HttpResponse>(code,
description, description,
errorCode, HttpErrorCode::CannotReadStatusLine,
headers, headers,
payload, payload,
errorMsg, errorMsg,
@ -352,11 +333,10 @@ namespace ix
if (!headersValid) if (!headersValid)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::HeaderParsingError;
std::string errorMsg("Cannot parse http headers"); std::string errorMsg("Cannot parse http headers");
return std::make_shared<HttpResponse>(code, return std::make_shared<HttpResponse>(code,
description, description,
errorCode, HttpErrorCode::HeaderParsingError,
headers, headers,
payload, payload,
errorMsg, errorMsg,
@ -419,29 +399,23 @@ namespace ix
ss << headers["Content-Length"]; ss << headers["Content-Length"];
ss >> contentLength; ss >> contentLength;
auto chunkResult = _socket->readBytes(contentLength, payload.reserve(contentLength);
args->onProgressCallback,
args->onChunkCallback, auto chunkResult = _socket->readBytes(
isCancellationRequested); contentLength, args->onProgressCallback, isCancellationRequested);
if (!chunkResult.first) if (!chunkResult.first)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk"; errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code, return std::make_shared<HttpResponse>(code,
description, description,
errorCode, HttpErrorCode::ChunkReadError,
headers, headers,
payload, payload,
errorMsg, errorMsg,
uploadSize, uploadSize,
downloadSize); downloadSize);
} }
payload += chunkResult.second;
if (!args->onChunkCallback)
{
payload.reserve(contentLength);
payload += chunkResult.second;
}
} }
else if (headers.find("Transfer-Encoding") != headers.end() && else if (headers.find("Transfer-Encoding") != headers.end() &&
headers["Transfer-Encoding"] == "chunked") headers["Transfer-Encoding"] == "chunked")
@ -450,7 +424,6 @@ namespace ix
while (true) while (true)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
lineResult = _socket->readLine(isCancellationRequested); lineResult = _socket->readLine(isCancellationRequested);
line = lineResult.second; line = lineResult.second;
@ -458,7 +431,7 @@ namespace ix
{ {
return std::make_shared<HttpResponse>(code, return std::make_shared<HttpResponse>(code,
description, description,
errorCode, HttpErrorCode::ChunkReadError,
headers, headers,
payload, payload,
errorMsg, errorMsg,
@ -478,40 +451,33 @@ namespace ix
log(oss.str(), args); log(oss.str(), args);
} }
payload.reserve(payload.size() + (size_t) chunkSize);
// Read a chunk // Read a chunk
auto chunkResult = _socket->readBytes((size_t) chunkSize, auto chunkResult = _socket->readBytes(
args->onProgressCallback, (size_t) chunkSize, args->onProgressCallback, isCancellationRequested);
args->onChunkCallback,
isCancellationRequested);
if (!chunkResult.first) if (!chunkResult.first)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
errorMsg = "Cannot read chunk"; errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code, return std::make_shared<HttpResponse>(code,
description, description,
errorCode, HttpErrorCode::ChunkReadError,
headers, headers,
payload, payload,
errorMsg, errorMsg,
uploadSize, uploadSize,
downloadSize); 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) // Read the line that terminates the chunk (\r\n)
lineResult = _socket->readLine(isCancellationRequested); lineResult = _socket->readLine(isCancellationRequested);
if (!lineResult.first) if (!lineResult.first)
{ {
auto errorCode = args->cancel ? HttpErrorCode::Cancelled : HttpErrorCode::ChunkReadError;
return std::make_shared<HttpResponse>(code, return std::make_shared<HttpResponse>(code,
description, description,
errorCode, HttpErrorCode::ChunkReadError,
headers, headers,
payload, payload,
errorMsg, errorMsg,

View File

@ -10,7 +10,6 @@
#include "IXNetSystem.h" #include "IXNetSystem.h"
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include "IXUserAgent.h" #include "IXUserAgent.h"
#include <cstdint>
#include <cstring> #include <cstring>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
@ -41,29 +40,6 @@ namespace
auto vec = res.second; auto vec = res.second;
return std::make_pair(res.first, std::string(vec.begin(), vec.end())); return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
} }
std::string response_head_file(const std::string& file_name){
if (std::string::npos != file_name.find(".html") || std::string::npos != file_name.find(".htm"))
return "text/html";
else if (std::string::npos != file_name.find(".css"))
return "text/css";
else if (std::string::npos != file_name.find(".js") || std::string::npos != file_name.find(".mjs"))
return "application/x-javascript";
else if (std::string::npos != file_name.find(".ico"))
return "image/x-icon";
else if (std::string::npos != file_name.find(".png"))
return "image/png";
else if (std::string::npos != file_name.find(".jpg") || std::string::npos != file_name.find(".jpeg"))
return "image/jpeg";
else if (std::string::npos != file_name.find(".gif"))
return "image/gif";
else if (std::string::npos != file_name.find(".svg"))
return "image/svg+xml";
else
return "application/octet-stream";
}
} // namespace } // namespace
namespace ix namespace ix
@ -75,14 +51,28 @@ namespace ix
int backlog, int backlog,
size_t maxConnections, size_t maxConnections,
int addressFamily, int addressFamily,
int timeoutSecs, int timeoutSecs)
int handshakeTimeoutSecs) : SocketServer(port, host, backlog, maxConnections, addressFamily)
: WebSocketServer(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily) , _connectedClientsCount(0)
, _timeoutSecs(timeoutSecs) , _timeoutSecs(timeoutSecs)
{ {
setDefaultConnectionCallback(); setDefaultConnectionCallback();
} }
HttpServer::~HttpServer()
{
stop();
}
void HttpServer::stop()
{
stopAcceptingConnections();
// FIXME: cancelling / closing active clients ...
SocketServer::stop();
}
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback) void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{ {
_onConnectionCallback = callback; _onConnectionCallback = callback;
@ -91,35 +81,34 @@ namespace ix
void HttpServer::handleConnection(std::unique_ptr<Socket> socket, void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState) std::shared_ptr<ConnectionState> connectionState)
{ {
_connectedClientsCount++;
auto ret = Http::parseRequest(socket, _timeoutSecs); auto ret = Http::parseRequest(socket, _timeoutSecs);
// FIXME: handle errors in parseRequest // FIXME: handle errors in parseRequest
if (std::get<0>(ret)) if (std::get<0>(ret))
{ {
auto request = std::get<2>(ret); auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
std::shared_ptr<ix::HttpResponse> response; if (!Http::sendResponse(response, socket))
if (request->headers["Upgrade"] == "websocket")
{ {
WebSocketServer::handleUpgrade(std::move(socket), connectionState, request); logError("Cannot send response");
}
else
{
auto response = _onConnectionCallback(request, connectionState);
if (!Http::sendResponse(response, socket))
{
logError("Cannot send response");
}
} }
} }
connectionState->setTerminated(); connectionState->setTerminated();
_connectedClientsCount--;
}
size_t HttpServer::getConnectedClientsCount()
{
return _connectedClientsCount;
} }
void HttpServer::setDefaultConnectionCallback() void HttpServer::setDefaultConnectionCallback()
{ {
setOnConnectionCallback( setOnConnectionCallback(
[this](HttpRequestPtr request, [this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
{
std::string uri(request->uri); std::string uri(request->uri);
if (uri.empty() || uri == "/") if (uri.empty() || uri == "/")
{ {
@ -128,7 +117,6 @@ namespace ix
WebSocketHttpHeaders headers; WebSocketHttpHeaders headers;
headers["Server"] = userAgent(); headers["Server"] = userAgent();
headers["Content-Type"] = response_head_file(uri);
std::string path("." + uri); std::string path("." + uri);
auto res = readAsString(path); auto res = readAsString(path);
@ -148,7 +136,6 @@ namespace ix
content = gzipCompress(content); content = gzipCompress(content);
headers["Content-Encoding"] = "gzip"; headers["Content-Encoding"] = "gzip";
} }
headers["Accept-Encoding"] = "gzip";
#endif #endif
// Log request // Log request
@ -162,6 +149,11 @@ namespace ix
// headers["Content-Type"] = "application/octet-stream"; // headers["Content-Type"] = "application/octet-stream";
headers["Accept-Ranges"] = "none"; headers["Accept-Ranges"] = "none";
for (auto&& it : request->headers)
{
headers[it.first] = it.second;
}
return std::make_shared<HttpResponse>( return std::make_shared<HttpResponse>(
200, "OK", HttpErrorCode::Ok, headers, content); 200, "OK", HttpErrorCode::Ok, headers, content);
}); });
@ -173,9 +165,9 @@ namespace ix
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
// //
setOnConnectionCallback( setOnConnectionCallback(
[this, redirectUrl](HttpRequestPtr request, [this,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr redirectUrl](HttpRequestPtr request,
{ std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
WebSocketHttpHeaders headers; WebSocketHttpHeaders headers;
headers["Server"] = userAgent(); headers["Server"] = userAgent();
@ -206,8 +198,7 @@ namespace ix
{ {
setOnConnectionCallback( setOnConnectionCallback(
[this](HttpRequestPtr request, [this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
{
WebSocketHttpHeaders headers; WebSocketHttpHeaders headers;
headers["Server"] = userAgent(); headers["Server"] = userAgent();
@ -235,10 +226,4 @@ namespace ix
200, "OK", HttpErrorCode::Ok, headers, std::string("OK")); 200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
}); });
} }
int HttpServer::getTimeoutSecs()
{
return _timeoutSecs;
}
} // namespace ix } // namespace ix

View File

@ -7,8 +7,8 @@
#pragma once #pragma once
#include "IXHttp.h" #include "IXHttp.h"
#include "IXSocketServer.h"
#include "IXWebSocket.h" #include "IXWebSocket.h"
#include "IXWebSocketServer.h"
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -19,7 +19,7 @@
namespace ix namespace ix
{ {
class HttpServer final : public WebSocketServer class HttpServer final : public SocketServer
{ {
public: public:
using OnConnectionCallback = using OnConnectionCallback =
@ -30,8 +30,9 @@ namespace ix
int backlog = SocketServer::kDefaultTcpBacklog, int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections, size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily, int addressFamily = SocketServer::kDefaultAddressFamily,
int timeoutSecs = HttpServer::kDefaultTimeoutSecs, int timeoutSecs = HttpServer::kDefaultTimeoutSecs);
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs); virtual ~HttpServer();
virtual void stop() final;
void setOnConnectionCallback(const OnConnectionCallback& callback); void setOnConnectionCallback(const OnConnectionCallback& callback);
@ -39,11 +40,10 @@ namespace ix
void makeDebugServer(); void makeDebugServer();
int getTimeoutSecs();
private: private:
// Member variables // Member variables
OnConnectionCallback _onConnectionCallback; OnConnectionCallback _onConnectionCallback;
std::atomic<int> _connectedClientsCount;
const static int kDefaultTimeoutSecs; const static int kDefaultTimeoutSecs;
int _timeoutSecs; int _timeoutSecs;
@ -51,6 +51,7 @@ namespace ix
// Methods // Methods
virtual void handleConnection(std::unique_ptr<Socket>, virtual void handleConnection(std::unique_ptr<Socket>,
std::shared_ptr<ConnectionState> connectionState) final; std::shared_ptr<ConnectionState> connectionState) final;
virtual size_t getConnectedClientsCount() final;
void setDefaultConnectionCallback(); void setDefaultConnectionCallback();
}; };

View File

@ -5,17 +5,6 @@
*/ */
#include "IXNetSystem.h" #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 namespace ix
{ {
@ -46,51 +35,6 @@ namespace ix
#endif #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 && (int)_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);' // That function could 'return WSAPoll(pfd, nfds, timeout);'
// but WSAPoll is said to have weird behaviors on the internet // but WSAPoll is said to have weird behaviors on the internet
@ -98,180 +42,69 @@ namespace ix
// //
// So we make it a select wrapper // So we make it a select wrapper
// //
// UPDATE: WSAPoll was fixed in Windows 10 Version 2004 int poll(struct pollfd* fds, nfds_t nfds, int timeout)
//
// 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 #ifdef _WIN32
socket_t maxfd = 0;
fd_set readfds, writefds, errorfds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&errorfds);
if (event && *event) for (nfds_t i = 0; i < nfds; ++i)
{ {
HANDLE interruptEvent = reinterpret_cast<HANDLE>(*event); struct pollfd* fd = &fds[i];
*event = nullptr; // the event wasn't signaled yet
if (nfds < 0 || nfds >= MAXIMUM_WAIT_OBJECTS - 1) if (fd->fd > maxfd)
{ {
WSASetLastError(WSAEINVAL); maxfd = fd->fd;
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)
{ {
struct pollfd* fd = &fds[i]; FD_SET(fd->fd, &readfds);
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)
{ {
// the interrupt event was signaled FD_SET(fd->fd, &writefds);
*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 < (int)socketEvents.size())
{ {
struct pollfd* fd = socketEvents[socketIndex]; FD_SET(fd->fd, &errorfds);
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;
} }
else
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)
{ {
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; return ret;
} }
#else
if (event && *event) *event = nullptr;
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
// //
// It was reported that on Android poll can fail and return -1 with // It was reported that on Android poll can fail and return -1 with
// errno == EINTR, which should be a temp error and should typically // errno == EINTR, which should be a temp error and should typically
@ -345,7 +178,7 @@ namespace ix
buf[best] = buf[best + 1] = ':'; buf[best] = buf[best + 1] = ':';
memmove(buf + best + 2, buf + best + max, i - best - max + 1); memmove(buf + best + 2, buf + best + max, i - best - max + 1);
} }
if (strlen(buf) < (size_t)l) if (strlen(buf) < l)
{ {
strcpy(s, buf); strcpy(s, buf);
return s; return s;
@ -445,17 +278,4 @@ namespace ix
#endif #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 } // namespace ix

View File

@ -6,24 +6,17 @@
#pragma once #pragma once
#include <cstdint>
#ifdef __FreeBSD__
#include <sys/types.h>
#endif
#ifdef _WIN32 #ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
#include <ws2tcpip.h> #include <WS2tcpip.h>
#include <winsock2.h> #include <WinSock2.h>
#include <basetsd.h> #include <basetsd.h>
#include <io.h> #include <io.h>
#include <ws2def.h> #include <ws2def.h>
#include <cerrno>
#undef EWOULDBLOCK #undef EWOULDBLOCK
#undef EAGAIN #undef EAGAIN
@ -41,8 +34,8 @@
// Define our own poll on Windows, as a wrapper on top of select // Define our own poll on Windows, as a wrapper on top of select
typedef unsigned long int nfds_t; typedef unsigned long int nfds_t;
// pollfd is not defined by some versions of mingw64 since _WIN32_WINNT is too low // mingw does not know about poll so mock it
#if _WIN32_WINNT < 0x0600 #if defined(__GNUC__)
struct pollfd struct pollfd
{ {
int fd; /* file descriptor */ int fd; /* file descriptor */
@ -84,10 +77,8 @@ namespace ix
bool initNetSystem(); bool initNetSystem();
bool uninitNetSystem(); bool uninitNetSystem();
int poll(struct pollfd* fds, nfds_t nfds, int timeout, void** event); int poll(struct pollfd* fds, nfds_t nfds, int timeout);
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size); const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
int inet_pton(int af, const char* src, void* dst); int inet_pton(int af, const char* src, void* dst);
unsigned short network_to_host_short(unsigned short value);
} // namespace ix } // namespace ix

View File

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

View File

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

View File

@ -6,8 +6,8 @@
#pragma once #pragma once
#include <cstdint>
#include <memory> #include <memory>
#include <stdint.h>
#include <string> #include <string>
namespace ix namespace ix
@ -24,7 +24,6 @@ namespace ix
virtual bool clear(); virtual bool clear();
virtual uint64_t read(); virtual uint64_t read();
virtual int getFd() const; virtual int getFd() const;
virtual void* getEvent() const;
// Used as special codes for pipe communication // Used as special codes for pipe communication
static const uint64_t kSendRequest; static const uint64_t kSendRequest;

View File

@ -1,85 +0,0 @@
/*
* 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

@ -1,39 +0,0 @@
/*
* IXSelectInterruptEvent.h
*/
#pragma once
#include "IXSelectInterrupt.h"
#include <cstdint>
#include <mutex>
#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 "IXSelectInterruptFactory.h"
#include "IXUniquePtr.h" #include "IXUniquePtr.h"
#if _WIN32 #if defined(__linux__) || defined(__APPLE__)
#include "IXSelectInterruptEvent.h"
#else
#include "IXSelectInterruptPipe.h" #include "IXSelectInterruptPipe.h"
#else
#include "IXSelectInterrupt.h"
#endif #endif
namespace ix namespace ix
{ {
SelectInterruptPtr createSelectInterrupt() SelectInterruptPtr createSelectInterrupt()
{ {
#ifdef _WIN32 #if defined(__linux__) || defined(__APPLE__)
return ix::make_unique<SelectInterruptEvent>();
#else
return ix::make_unique<SelectInterruptPipe>(); return ix::make_unique<SelectInterruptPipe>();
#else
return ix::make_unique<SelectInterrupt>();
#endif #endif
} }
} // namespace ix } // namespace ix

View File

@ -34,12 +34,8 @@ namespace ix
SelectInterruptPipe::~SelectInterruptPipe() SelectInterruptPipe::~SelectInterruptPipe()
{ {
if (-1 != _fildes[kPipeReadIndex]) { ::close(_fildes[kPipeReadIndex]);
::close(_fildes[kPipeReadIndex]); ::close(_fildes[kPipeWriteIndex]);
}
if (-1 != _fildes[kPipeWriteIndex]) {
::close(_fildes[kPipeWriteIndex]);
}
_fildes[kPipeReadIndex] = -1; _fildes[kPipeReadIndex] = -1;
_fildes[kPipeWriteIndex] = -1; _fildes[kPipeWriteIndex] = -1;
} }

View File

@ -7,7 +7,6 @@
#pragma once #pragma once
#include "IXSelectInterrupt.h" #include "IXSelectInterrupt.h"
#include <cstdint>
#include <mutex> #include <mutex>
#include <stdint.h> #include <stdint.h>
#include <string> #include <string>

View File

@ -15,13 +15,9 @@
#include <pthread_np.h> #include <pthread_np.h>
#endif #endif
#ifdef __APPLE__
#include <AvailabilityMacros.h>
#endif
// Windows // Windows
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <Windows.h>
#endif #endif
namespace ix namespace ix
@ -62,7 +58,7 @@ namespace ix
void setThreadName(const std::string& name) void setThreadName(const std::string& name)
{ {
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1060) #if defined(__APPLE__)
// //
// Apple reserves 16 bytes for its thread names // Apple reserves 16 bytes for its thread names
// Notice that the Apple version of pthread_setname_np // Notice that the Apple version of pthread_setname_np

View File

@ -14,6 +14,7 @@
#include <array> #include <array>
#include <assert.h> #include <assert.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -46,8 +47,6 @@ namespace ix
int sockfd, int sockfd,
const SelectInterruptPtr& selectInterrupt) const SelectInterruptPtr& selectInterrupt)
{ {
PollResultType pollResult = PollResultType::ReadyForRead;
// //
// We used to use ::select to poll but on Android 9 we get large fds out of // 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 // ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching
@ -69,11 +68,9 @@ namespace ix
// File descriptor used to interrupt select when needed // File descriptor used to interrupt select when needed
int interruptFd = -1; int interruptFd = -1;
void* interruptEvent = nullptr;
if (selectInterrupt) if (selectInterrupt)
{ {
interruptFd = selectInterrupt->getFd(); interruptFd = selectInterrupt->getFd();
interruptEvent = selectInterrupt->getEvent();
if (interruptFd != -1) if (interruptFd != -1)
{ {
@ -81,21 +78,11 @@ namespace ix
fds[1].fd = interruptFd; fds[1].fd = interruptFd;
fds[1].events = POLLIN; 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;
}
}
} }
void* event = interruptEvent; // ix::poll will set event to nullptr if it wasn't signaled int ret = ix::poll(fds, nfds, timeoutMs);
int ret = ix::poll(fds, nfds, timeoutMs, &event);
PollResultType pollResult = PollResultType::ReadyForRead;
if (ret < 0) if (ret < 0)
{ {
pollResult = PollResultType::Error; pollResult = PollResultType::Error;
@ -103,18 +90,19 @@ namespace ix
else if (ret == 0) else if (ret == 0)
{ {
pollResult = PollResultType::Timeout; pollResult = PollResultType::Timeout;
if (selectInterrupt && interruptFd == -1 && interruptEvent == nullptr)
{
// Emulation mode: SelectInterrupt neither supports fd nor events
// Check the selectInterrupt for requests
readSelectInterruptRequest(selectInterrupt, &pollResult);
}
} }
else if ((interruptFd != -1 && fds[1].revents & POLLIN) || (interruptEvent != nullptr && event != nullptr)) else if (interruptFd != -1 && fds[1].revents & POLLIN)
{ {
// The InterruptEvent was signaled uint64_t value = selectInterrupt->read();
readSelectInterruptRequest(selectInterrupt, &pollResult);
if (value == SelectInterrupt::kSendRequest)
{
pollResult = PollResultType::SendRequest;
}
else if (value == SelectInterrupt::kCloseRequest)
{
pollResult = PollResultType::CloseRequest;
}
} }
else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN) else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN)
{ {
@ -155,25 +143,6 @@ namespace ix
return pollResult; 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) PollResultType Socket::isReadyToRead(int timeoutMs)
{ {
if (_sockfd == -1) if (_sockfd == -1)
@ -202,11 +171,6 @@ namespace ix
return _selectInterrupt->notify(wakeUpCode); return _selectInterrupt->notify(wakeUpCode);
} }
bool Socket::isWakeUpFromPollSupported()
{
return _selectInterrupt->getFd() != -1 || _selectInterrupt->getEvent() != nullptr;
}
bool Socket::accept(std::string& errMsg) bool Socket::accept(std::string& errMsg)
{ {
if (_sockfd == -1) if (_sockfd == -1)
@ -399,14 +363,12 @@ namespace ix
std::pair<bool, std::string> Socket::readBytes( std::pair<bool, std::string> Socket::readBytes(
size_t length, size_t length,
const OnProgressCallback& onProgressCallback, const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested) const CancellationRequest& isCancellationRequested)
{ {
std::array<uint8_t, 1 << 14> readBuffer; std::array<uint8_t, 1 << 14> readBuffer;
std::vector<uint8_t> output;
size_t bytesRead = 0;
while (bytesRead != length) std::vector<uint8_t> output;
while (output.size() != length)
{ {
if (isCancellationRequested && isCancellationRequested()) if (isCancellationRequested && isCancellationRequested())
{ {
@ -414,21 +376,12 @@ namespace ix
return std::make_pair(false, errorMsg); return std::make_pair(false, errorMsg);
} }
size_t size = std::min(readBuffer.size(), length - bytesRead); size_t size = std::min(readBuffer.size(), length - output.size());
ssize_t ret = recv((char*) &readBuffer[0], size); ssize_t ret = recv((char*) &readBuffer[0], size);
if (ret > 0) if (ret > 0)
{ {
if (onChunkCallback) output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
{
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()) else if (ret <= 0 && !Socket::isWaitNeeded())
{ {
@ -436,7 +389,7 @@ namespace ix
return std::make_pair(false, errorMsg); return std::make_pair(false, errorMsg);
} }
if (onProgressCallback) onProgressCallback((int) bytesRead, (int) length); if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
// Wait with a 1ms timeout until the socket is ready to read. // Wait with a 1ms timeout until the socket is ready to read.
// This way we are not busy looping // This way we are not busy looping

View File

@ -7,22 +7,15 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <cstdint>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#ifdef __APPLE__
#include <sys/types.h>
#endif
#ifdef _WIN32 #ifdef _WIN32
#include <basetsd.h> #include <BaseTsd.h>
#ifdef _MSC_VER
typedef SSIZE_T ssize_t; typedef SSIZE_T ssize_t;
#endif #endif
#endif
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include "IXProgressCallback.h" #include "IXProgressCallback.h"
@ -50,7 +43,6 @@ namespace ix
// Functions to check whether there is activity on the socket // Functions to check whether there is activity on the socket
PollResultType poll(int timeoutMs = kDefaultPollTimeout); PollResultType poll(int timeoutMs = kDefaultPollTimeout);
bool wakeUpFromPoll(uint64_t wakeUpCode); bool wakeUpFromPoll(uint64_t wakeUpCode);
bool isWakeUpFromPollSupported();
PollResultType isReadyToWrite(int timeoutMs); PollResultType isReadyToWrite(int timeoutMs);
PollResultType isReadyToRead(int timeoutMs); PollResultType isReadyToRead(int timeoutMs);
@ -76,7 +68,6 @@ namespace ix
std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested); std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
std::pair<bool, std::string> readBytes(size_t length, std::pair<bool, std::string> readBytes(size_t length,
const OnProgressCallback& onProgressCallback, const OnProgressCallback& onProgressCallback,
const OnChunkCallback& onChunkCallback,
const CancellationRequest& isCancellationRequested); const CancellationRequest& isCancellationRequested);
static int getErrno(); static int getErrno();
@ -92,9 +83,6 @@ namespace ix
std::atomic<int> _sockfd; std::atomic<int> _sockfd;
std::mutex _socketMutex; std::mutex _socketMutex;
static bool readSelectInterruptRequest(const SelectInterruptPtr& selectInterrupt,
PollResultType* pollResult);
private: private:
static const int kDefaultPollTimeout; static const int kDefaultPollTimeout;
static const int kDefaultPollNoTimeout; static const int kDefaultPollNoTimeout;

View File

@ -205,9 +205,7 @@ namespace ix
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket); _sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd); SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12); SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
if (!_tlsOptions.disable_hostname_validation)
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
if (_tlsOptions.isPeerVerifyDisabled()) if (_tlsOptions.isPeerVerifyDisabled())
{ {

View File

@ -20,7 +20,6 @@
#include <linux/in.h> #include <linux/in.h>
#include <linux/tcp.h> #include <linux/tcp.h>
#endif #endif
#include <ixwebsocket/IXSelectInterruptFactory.h>
namespace ix namespace ix
{ {
@ -67,7 +66,7 @@ namespace ix
int timeoutMs = 10; int timeoutMs = 10;
bool readyToRead = false; bool readyToRead = false;
SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt(); auto selectInterrupt = ix::make_unique<SelectInterrupt>();
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt); PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
if (pollResult == PollResultType::Timeout) if (pollResult == PollResultType::Timeout)
@ -91,6 +90,10 @@ namespace ix
return -1; return -1;
} }
} }
Socket::closeSocket(fd);
errMsg = "connect timed out after 60 seconds";
return -1;
} }
int SocketConnect::connect(const std::string& hostname, int SocketConnect::connect(const std::string& hostname,
@ -102,7 +105,7 @@ namespace ix
// First do DNS resolution // First do DNS resolution
// //
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port); auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
auto res = dnsLookup->resolve(errMsg, isCancellationRequested); struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
if (res == nullptr) if (res == nullptr)
{ {
return -1; return -1;
@ -112,7 +115,7 @@ namespace ix
// iterate through the records to find a working peer // iterate through the records to find a working peer
struct addrinfo* address; struct addrinfo* address;
for (address = res.get(); address != nullptr; address = address->ai_next) for (address = res; address != nullptr; address = address->ai_next)
{ {
// //
// Second try to connect to the remote host // Second try to connect to the remote host
@ -124,6 +127,7 @@ namespace ix
} }
} }
freeaddrinfo(res);
return sockfd; return sockfd;
} }

View File

@ -14,7 +14,6 @@
#include "IXNetSystem.h" #include "IXNetSystem.h"
#include "IXSocket.h" #include "IXSocket.h"
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include <cstdint>
#include <string.h> #include <string.h>
#ifdef _WIN32 #ifdef _WIN32
@ -47,13 +46,6 @@ namespace ix
mbedtls_x509_crt_init(&_cacert); mbedtls_x509_crt_init(&_cacert);
mbedtls_x509_crt_init(&_cert); mbedtls_x509_crt_init(&_cert);
mbedtls_pk_init(&_pkey); mbedtls_pk_init(&_pkey);
// Initialize the PSA Crypto API if required by the version of Mbed TLS (3.6.0).
// This allows the X.509/TLS libraries to use PSA for crypto operations.
// See: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/use-psa-crypto.md
if (MBEDTLS_VERSION_MAJOR >= 3 && MBEDTLS_VERSION_MINOR >= 6 && MBEDTLS_VERSION_PATCH >= 0)
{
psa_crypto_init();
}
} }
bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg) bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg)
@ -203,13 +195,10 @@ namespace ix
return false; return false;
} }
if (!_tlsOptions.disable_hostname_validation) if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
{ {
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0) errMsg = "SNI setup failed";
{ return false;
errMsg = "SNI setup failed";
return false;
}
} }
return true; return true;
@ -359,11 +348,6 @@ namespace ix
return res; return res;
} }
if (res == 0)
{
errno = ECONNRESET;
}
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
{ {
errno = EWOULDBLOCK; errno = EWOULDBLOCK;

View File

@ -15,7 +15,7 @@
#include <errno.h> #include <errno.h>
#include <vector> #include <vector>
#ifdef _WIN32 #ifdef _WIN32
#include <shlwapi.h> #include <Shlwapi.h>
#else #else
#include <fnmatch.h> #include <fnmatch.h>
#endif #endif
@ -26,7 +26,6 @@
#ifdef _WIN32 #ifdef _WIN32
// For manipulating the certificate store // For manipulating the certificate store
#include <windows.h>
#include <wincrypt.h> #include <wincrypt.h>
#endif #endif
@ -50,7 +49,7 @@ namespace
X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl); X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl);
int certificateCount = 0; int certificateCount = 0;
while ((certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))) while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
{ {
X509* x509 = d2i_X509(NULL, X509* x509 = d2i_X509(NULL,
(const unsigned char**) &certificateIterator->pbCertEncoded, (const unsigned char**) &certificateIterator->pbCertEncoded,
@ -294,25 +293,15 @@ namespace ix
*/ */
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern) bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
{ {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
return true;
#else
#ifdef _WIN32 #ifdef _WIN32
return PathMatchSpecA(host.c_str(), pattern); return PathMatchSpecA(host.c_str(), pattern);
#else #else
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH; return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
#endif
#endif #endif
} }
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl, bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
#if OPENSSL_VERSION_NUMBER < 0x10100000L
const std::string& hostname, const std::string& hostname,
#else
const std::string& /* hostname */,
#endif
std::string& errMsg) std::string& errMsg)
{ {
X509* server_cert = SSL_get_peer_certificate(ssl); X509* server_cert = SSL_get_peer_certificate(ssl);
@ -350,12 +339,12 @@ namespace ix
{ {
int cn_pos = X509_NAME_get_index_by_NID( int cn_pos = X509_NAME_get_index_by_NID(
X509_get_subject_name((X509*) server_cert), NID_commonName, -1); X509_get_subject_name((X509*) server_cert), NID_commonName, -1);
if (cn_pos >= 0) if (cn_pos)
{ {
X509_NAME_ENTRY* cn_entry = X509_NAME_ENTRY* cn_entry =
X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos); X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos);
if (cn_entry != nullptr) if (cn_entry)
{ {
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
char* cn = (char*) ASN1_STRING_data(cn_asn1); char* cn = (char*) ASN1_STRING_data(cn_asn1);
@ -401,11 +390,6 @@ namespace ix
int connect_result = SSL_connect(_ssl_connection); int connect_result = SSL_connect(_ssl_connection);
if (connect_result == 1) if (connect_result == 1)
{ {
if (_tlsOptions.disable_hostname_validation)
{
return true;
}
return openSSLCheckServerCert(_ssl_connection, host, errMsg); return openSSLCheckServerCert(_ssl_connection, host, errMsg);
} }
int reason = SSL_get_error(_ssl_connection, connect_result); int reason = SSL_get_error(_ssl_connection, connect_result);
@ -770,11 +754,8 @@ namespace ix
// (The docs say that this should work from 1.0.2, and is the default from // (The docs say that this should work from 1.0.2, and is the default from
// 1.1.0, but it does not. To be on the safe side, the manual test // 1.1.0, but it does not. To be on the safe side, the manual test
// below is enabled for all versions prior to 1.1.0.) // below is enabled for all versions prior to 1.1.0.)
if (!_tlsOptions.disable_hostname_validation) X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
{ X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size());
}
#endif #endif
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested); handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
} }

View File

@ -219,10 +219,6 @@ namespace ix
if (_gcThread.joinable()) if (_gcThread.joinable())
{ {
_stopGc = true; _stopGc = true;
{
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
_canContinueGC = true;
}
_conditionVariableGC.notify_one(); _conditionVariableGC.notify_one();
_gcThread.join(); _gcThread.join();
_stopGc = false; _stopGc = false;
@ -272,10 +268,7 @@ namespace ix
// Set the socket to non blocking mode, so that accept calls are not blocking // Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd); SocketConnect::configure(_serverFd);
// Use a cryptic name to stay within the 16 bytes limit thread name limitation setThreadName("SocketServer::accept");
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:ac:" + std::to_string(_port));
for (;;) for (;;)
{ {
@ -358,7 +351,7 @@ namespace ix
continue; continue;
} }
remotePort = ix::network_to_host_short(client.sin_port); remotePort = client.sin_port;
remoteIp = remoteIp4; remoteIp = remoteIp4;
} }
else // AF_INET6 else // AF_INET6
@ -378,7 +371,7 @@ namespace ix
continue; continue;
} }
remotePort = ix::network_to_host_short(client.sin_port); remotePort = client.sin_port;
remoteIp = remoteIp6; remoteIp = remoteIp6;
} }
@ -432,10 +425,7 @@ namespace ix
void SocketServer::runGC() void SocketServer::runGC()
{ {
// Use a cryptic name to stay within the 16 bytes limit thread name limitation setThreadName("SocketServer::GC");
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:gc:" + std::to_string(_port));
for (;;) for (;;)
{ {
@ -455,10 +445,7 @@ namespace ix
if (!_stopGc) if (!_stopGc)
{ {
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC); std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
if(!_canContinueGC) { _conditionVariableGC.wait(lock);
_conditionVariableGC.wait(lock, [this]{ return _canContinueGC; });
}
_canContinueGC = false;
} }
} }
} }
@ -472,35 +459,6 @@ namespace ix
{ {
// a connection got terminated, we can run the connection thread GC, // a connection got terminated, we can run the connection thread GC,
// so wake up the thread responsible for that // so wake up the thread responsible for that
{
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
_canContinueGC = true;
}
_conditionVariableGC.notify_one(); _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 } // namespace ix

View File

@ -60,11 +60,6 @@ namespace ix
void setTLSOptions(const SocketTLSOptions& socketTLSOptions); void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
int getPort();
std::string getHost();
int getBacklog();
std::size_t getMaxConnections();
int getAddressFamily();
protected: protected:
// Logging // Logging
void logError(const std::string& str); void logError(const std::string& str);
@ -126,6 +121,5 @@ namespace ix
// as a connection // as a connection
std::condition_variable _conditionVariableGC; std::condition_variable _conditionVariableGC;
std::mutex _conditionVariableMutexGC; std::mutex _conditionVariableMutexGC;
bool _canContinueGC{ false };
}; };
} // namespace ix } // namespace ix

View File

@ -87,7 +87,7 @@ namespace ix
ss << " keyFile = " << keyFile << std::endl; ss << " keyFile = " << keyFile << std::endl;
ss << " caFile = " << caFile << std::endl; ss << " caFile = " << caFile << std::endl;
ss << " ciphers = " << ciphers << std::endl; ss << " ciphers = " << ciphers << std::endl;
ss << " tls = " << tls << std::endl; ss << " ciphers = " << ciphers << std::endl;
return ss.str(); return ss.str();
} }
} // namespace ix } // namespace ix

View File

@ -33,9 +33,6 @@ namespace ix
// whether tls is enabled, used for server code // whether tls is enabled, used for server code
bool tls = false; bool tls = false;
// whether to skip validating the peer's hostname against the certificate presented
bool disable_hostname_validation = false;
bool hasCertAndKey() const; bool hasCertAndKey() const;
bool isUsingSystemDefaults() const; bool isUsingSystemDefaults() const;

View File

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

View File

@ -180,7 +180,7 @@ namespace
bHasUserName = true; bHasUserName = true;
break; break;
} }
else if (*LocalString == '/' || *LocalString == '?') else if (*LocalString == '/')
{ {
// end of <host>:<port> specification // end of <host>:<port> specification
bHasUserName = false; bHasUserName = false;
@ -242,7 +242,7 @@ namespace
LocalString++; LocalString++;
break; break;
} }
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/' || *LocalString == '?')) else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
{ {
// port number is specified // port number is specified
break; break;
@ -280,14 +280,12 @@ namespace
} }
// skip '/' // skip '/'
if (*CurrentString != '/' && *CurrentString != '?') if (*CurrentString != '/')
{ {
return clParseURL(LUrlParserError_NoSlash); return clParseURL(LUrlParserError_NoSlash);
} }
if (*CurrentString != '?') { CurrentString++;
CurrentString++;
}
// parse the path // parse the path
LocalString = CurrentString; LocalString = CurrentString;
@ -335,19 +333,6 @@ namespace
return Result; return Result;
} }
int getProtocolPort(const std::string& protocol)
{
if (protocol == "ws" || protocol == "http")
{
return 80;
}
else if (protocol == "wss" || protocol == "https")
{
return 443;
}
return -1;
}
} // namespace } // namespace
namespace ix namespace ix
@ -358,18 +343,6 @@ namespace ix
std::string& path, std::string& path,
std::string& query, std::string& query,
int& port) int& port)
{
bool isProtocolDefaultPort;
return parse(url, protocol, host, path, query, port, isProtocolDefaultPort);
}
bool UrlParser::parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port,
bool& isProtocolDefaultPort)
{ {
clParseURL res = clParseURL::ParseURL(url); clParseURL res = clParseURL::ParseURL(url);
@ -383,12 +356,23 @@ namespace ix
path = res.m_Path; path = res.m_Path;
query = res.m_Query; query = res.m_Query;
const auto protocolPort = getProtocolPort(protocol);
if (!res.GetPort(&port)) if (!res.GetPort(&port))
{ {
port = protocolPort; if (protocol == "ws" || protocol == "http")
{
port = 80;
}
else if (protocol == "wss" || protocol == "https")
{
port = 443;
}
else
{
// Invalid protocol. Should be caught by regex check
// but this missing branch trigger cpplint linter.
return false;
}
} }
isProtocolDefaultPort = port == protocolPort;
if (path.empty()) if (path.empty())
{ {

View File

@ -19,13 +19,5 @@ namespace ix
std::string& path, std::string& path,
std::string& query, std::string& query,
int& port); int& port);
static bool parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port,
bool& isProtocolDefaultPort);
}; };
} // namespace ix } // namespace ix

View File

@ -16,7 +16,6 @@
#include "IXUuid.h" #include "IXUuid.h"
#include <cstdint>
#include <iomanip> #include <iomanip>
#include <random> #include <random>
#include <sstream> #include <sstream>

View File

@ -13,7 +13,6 @@
#include "IXWebSocketHandshake.h" #include "IXWebSocketHandshake.h"
#include <cassert> #include <cassert>
#include <cmath> #include <cmath>
#include <cstdint>
namespace namespace
@ -40,12 +39,9 @@ namespace ix
, _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs) , _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
, _enablePong(kDefaultEnablePong) , _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs) , _pingIntervalSecs(kDefaultPingIntervalSecs)
, _pingType(SendMessageKind::Ping)
, _autoThreadName(true)
{ {
_ws.setOnCloseCallback( _ws.setOnCloseCallback(
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) [this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
{
_onMessageCallback( _onMessageCallback(
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close, ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
emptyMsg, emptyMsg,
@ -104,17 +100,6 @@ namespace ix
return _perMessageDeflateOptions; return _perMessageDeflateOptions;
} }
void WebSocket::setPingMessage(const std::string& sendMessage, SendMessageKind pingType)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingMessage = sendMessage;
_ws.setPingMessage(_pingMessage, pingType);
}
const std::string WebSocket::getPingMessage() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingMessage;
}
void WebSocket::setPingInterval(int pingIntervalSecs) void WebSocket::setPingInterval(int pingIntervalSecs)
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
@ -209,7 +194,7 @@ namespace ix
WebSocketHttpHeaders headers(_extraHeaders); WebSocketHttpHeaders headers(_extraHeaders);
std::string subProtocolsHeader; std::string subProtocolsHeader;
const auto &subProtocols = getSubProtocols(); auto subProtocols = getSubProtocols();
if (!subProtocols.empty()) if (!subProtocols.empty())
{ {
// //
@ -219,7 +204,7 @@ namespace ix
// 'json,msgpack' // 'json,msgpack'
// //
int i = 0; int i = 0;
for (const auto & subProtocol : subProtocols) for (auto subProtocol : subProtocols)
{ {
if (i++ != 0) if (i++ != 0)
{ {
@ -247,7 +232,7 @@ namespace ix
if (_pingIntervalSecs > 0) if (_pingIntervalSecs > 0)
{ {
// Send a heart beat right away // Send a heart beat right away
_ws.sendHeartBeat(_pingType); _ws.sendHeartBeat();
} }
return status; return status;
@ -255,8 +240,7 @@ namespace ix
WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket, WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs, int timeoutSecs,
bool enablePerMessageDeflate, bool enablePerMessageDeflate)
HttpRequestPtr request)
{ {
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
@ -265,7 +249,7 @@ namespace ix
} }
WebSocketInitResult status = WebSocketInitResult status =
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate, request); _ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate);
if (!status.success) if (!status.success)
{ {
return status; return status;
@ -282,7 +266,7 @@ namespace ix
if (_pingIntervalSecs > 0) if (_pingIntervalSecs > 0)
{ {
// Send a heart beat right away // Send a heart beat right away
_ws.sendHeartBeat(_pingType); _ws.sendHeartBeat();
} }
return status; return status;
@ -371,10 +355,7 @@ namespace ix
void WebSocket::run() void WebSocket::run()
{ {
if (_autoThreadName) setThreadName(getUrl());
{
setThreadName(getUrl());
}
bool firstConnectionAttempt = true; bool firstConnectionAttempt = true;
@ -403,9 +384,8 @@ namespace ix
[this](const std::string& msg, [this](const std::string& msg,
size_t wireSize, size_t wireSize,
bool decompressionError, bool decompressionError,
WebSocketTransport::MessageKind messageKind) WebSocketTransport::MessageKind messageKind) {
{ WebSocketMessageType webSocketMessageType;
WebSocketMessageType webSocketMessageType {WebSocketMessageType::Error};
switch (messageKind) switch (messageKind)
{ {
case WebSocketTransport::MessageKind::MSG_TEXT: case WebSocketTransport::MessageKind::MSG_TEXT:
@ -487,28 +467,10 @@ namespace ix
return (binary) ? sendBinary(data, onProgressCallback) : sendText(data, onProgressCallback); return (binary) ? sendBinary(data, onProgressCallback) : sendText(data, onProgressCallback);
} }
WebSocketSendInfo WebSocket::sendBinary(const std::string& data, WebSocketSendInfo WebSocket::sendBinary(const std::string& text,
const OnProgressCallback& onProgressCallback) const OnProgressCallback& onProgressCallback)
{ {
return sendMessage(data, SendMessageKind::Binary, onProgressCallback); return sendMessage(text, 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, WebSocketSendInfo WebSocket::sendText(const std::string& text,
@ -523,16 +485,16 @@ namespace ix
return sendMessage(text, SendMessageKind::Text, onProgressCallback); return sendMessage(text, SendMessageKind::Text, onProgressCallback);
} }
WebSocketSendInfo WebSocket::ping(const std::string& text, SendMessageKind pingType) WebSocketSendInfo WebSocket::ping(const std::string& text)
{ {
// Standard limit ping message size // Standard limit ping message size
constexpr size_t pingMaxPayloadSize = 125; constexpr size_t pingMaxPayloadSize = 125;
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false); if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
return sendMessage(text, pingType); return sendMessage(text, SendMessageKind::Ping);
} }
WebSocketSendInfo WebSocket::sendMessage(const IXWebSocketSendData& message, WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
SendMessageKind sendMessageKind, SendMessageKind sendMessageKind,
const OnProgressCallback& onProgressCallback) const OnProgressCallback& onProgressCallback)
{ {
@ -554,19 +516,19 @@ namespace ix
{ {
case SendMessageKind::Text: case SendMessageKind::Text:
{ {
webSocketSendInfo = _ws.sendText(message, onProgressCallback); webSocketSendInfo = _ws.sendText(text, onProgressCallback);
} }
break; break;
case SendMessageKind::Binary: case SendMessageKind::Binary:
{ {
webSocketSendInfo = _ws.sendBinary(message, onProgressCallback); webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
} }
break; break;
case SendMessageKind::Ping: case SendMessageKind::Ping:
{ {
webSocketSendInfo = _ws.sendPing(message); webSocketSendInfo = _ws.sendPing(text);
} }
break; break;
} }
@ -631,9 +593,4 @@ namespace ix
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
return _subProtocols; return _subProtocols;
} }
void WebSocket::setAutoThreadName(bool enabled)
{
_autoThreadName = enabled;
}
} // namespace ix } // namespace ix

View File

@ -16,12 +16,10 @@
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketMessage.h" #include "IXWebSocketMessage.h"
#include "IXWebSocketPerMessageDeflateOptions.h" #include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendData.h"
#include "IXWebSocketSendInfo.h" #include "IXWebSocketSendInfo.h"
#include "IXWebSocketTransport.h" #include "IXWebSocketTransport.h"
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
#include <cstdint>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
@ -54,8 +52,6 @@ namespace ix
void setPerMessageDeflateOptions( void setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions); const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setTLSOptions(const SocketTLSOptions& socketTLSOptions); void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
void setPingMessage(const std::string& sendMessage,
SendMessageKind pingType = SendMessageKind::Ping);
void setPingInterval(int pingIntervalSecs); void setPingInterval(int pingIntervalSecs);
void enablePong(); void enablePong();
void disablePong(); void disablePong();
@ -79,19 +75,11 @@ namespace ix
WebSocketSendInfo send(const std::string& data, WebSocketSendInfo send(const std::string& data,
bool binary = false, bool binary = false,
const OnProgressCallback& onProgressCallback = nullptr); const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendBinary(const std::string& data, WebSocketSendInfo sendBinary(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr); 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, WebSocketSendInfo sendText(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr); const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string& text,SendMessageKind pingType = SendMessageKind::Ping); WebSocketSendInfo ping(const std::string& text);
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage); const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
@ -106,7 +94,6 @@ namespace ix
const std::string getUrl() const; const std::string getUrl() const;
const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const; const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const;
const std::string getPingMessage() const;
int getPingInterval() const; int getPingInterval() const;
size_t bufferedAmount() const; size_t bufferedAmount() const;
@ -119,10 +106,8 @@ namespace ix
uint32_t getMinWaitBetweenReconnectionRetries() const; uint32_t getMinWaitBetweenReconnectionRetries() const;
const std::vector<std::string>& getSubProtocols(); const std::vector<std::string>& getSubProtocols();
void setAutoThreadName(bool enabled);
private: private:
WebSocketSendInfo sendMessage(const IXWebSocketSendData& message, WebSocketSendInfo sendMessage(const std::string& text,
SendMessageKind sendMessageKind, SendMessageKind sendMessageKind,
const OnProgressCallback& callback = nullptr); const OnProgressCallback& callback = nullptr);
@ -134,8 +119,7 @@ namespace ix
// Server // Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket>, WebSocketInitResult connectToSocket(std::unique_ptr<Socket>,
int timeoutSecs, int timeoutSecs,
bool enablePerMessageDeflate, bool enablePerMessageDeflate);
HttpRequestPtr request = nullptr);
WebSocketTransport _ws; WebSocketTransport _ws;
@ -176,17 +160,12 @@ namespace ix
// Optional ping and pong timeout // Optional ping and pong timeout
int _pingIntervalSecs; int _pingIntervalSecs;
int _pingTimeoutSecs; int _pingTimeoutSecs;
std::string _pingMessage;
SendMessageKind _pingType;
static const int kDefaultPingIntervalSecs; static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs; static const int kDefaultPingTimeoutSecs;
// Subprotocols // Subprotocols
std::vector<std::string> _subProtocols; std::vector<std::string> _subProtocols;
// enable or disable auto set thread name
bool _autoThreadName;
friend class WebSocketServer; friend class WebSocketServer;
}; };
} // namespace ix } // namespace ix

View File

@ -6,7 +6,6 @@
#include "IXWebSocketHandshake.h" #include "IXWebSocketHandshake.h"
#include "IXBase64.h"
#include "IXHttp.h" #include "IXHttp.h"
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include "IXStrCaseCompare.h" #include "IXStrCaseCompare.h"
@ -18,6 +17,7 @@
#include <random> #include <random>
#include <sstream> #include <sstream>
namespace ix namespace ix
{ {
WebSocketHandshake::WebSocketHandshake( WebSocketHandshake::WebSocketHandshake(
@ -87,7 +87,6 @@ namespace ix
WebSocketInitResult WebSocketHandshake::clientHandshake( WebSocketInitResult WebSocketHandshake::clientHandshake(
const std::string& url, const std::string& url,
const WebSocketHttpHeaders& extraHeaders, const WebSocketHttpHeaders& extraHeaders,
const std::string& protocol,
const std::string& host, const std::string& host,
const std::string& path, const std::string& path,
int port, int port,
@ -107,10 +106,15 @@ namespace ix
return WebSocketInitResult(false, 0, ss.str()); return WebSocketInitResult(false, 0, ss.str());
} }
// Generate a random 16 bytes string and base64 encode it. //
// Generate a random 24 bytes string which looks like it is base64 encoded
// y3JJHMbDL1EzLkh9GBhXDw==
// 0cb3Vd9HkbpVVumoS3Noka==
// //
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for // See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
std::string secWebSocketKey = macaron::Base64::Encode(genRandomString(16)); //
std::string secWebSocketKey = genRandomString(22);
secWebSocketKey += "==";
std::stringstream ss; std::stringstream ss;
ss << "GET " << path << " HTTP/1.1\r\n"; ss << "GET " << path << " HTTP/1.1\r\n";
@ -126,12 +130,6 @@ namespace ix
ss << "User-Agent: " << userAgent() << "\r\n"; ss << "User-Agent: " << userAgent() << "\r\n";
} }
// Set an origin header if missing
if (extraHeaders.find("Origin") == extraHeaders.end())
{
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
}
for (auto& it : extraHeaders) for (auto& it : extraHeaders)
{ {
ss << it.first << ": " << it.second << "\r\n"; ss << it.first << ": " << it.second << "\r\n";
@ -247,42 +245,28 @@ namespace ix
} }
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs, WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate, bool enablePerMessageDeflate)
HttpRequestPtr request)
{ {
_requestInitCancellation = false; _requestInitCancellation = false;
auto isCancellationRequested = auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation); makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
std::string method; // Read first line
std::string uri; auto lineResult = _socket->readLine(isCancellationRequested);
std::string httpVersion; auto lineValid = lineResult.first;
auto line = lineResult.second;
if (request) if (!lineValid)
{ {
method = request->method; return sendErrorResponse(400, "Error reading HTTP request line");
uri = request->uri;
httpVersion = request->version;
} }
else
{
// Read first line
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
if (!lineValid) // Validate request line (GET /foo HTTP/1.1\r\n)
{ auto requestLine = Http::parseRequestLine(line);
return sendErrorResponse(400, "Error reading HTTP request line"); auto method = std::get<0>(requestLine);
} auto uri = std::get<1>(requestLine);
auto httpVersion = std::get<2>(requestLine);
// Validate request line (GET /foo HTTP/1.1\r\n)
auto requestLine = Http::parseRequestLine(line);
method = std::get<0>(requestLine);
uri = std::get<1>(requestLine);
httpVersion = std::get<2>(requestLine);
}
if (method != "GET") if (method != "GET")
{ {
@ -295,22 +279,14 @@ namespace ix
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion); "Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
} }
WebSocketHttpHeaders headers; // Retrieve and validate HTTP headers
if (request) auto result = parseHttpHeaders(_socket, isCancellationRequested);
{ auto headersValid = result.first;
headers = request->headers; auto headers = result.second;
}
else
{
// Retrieve and validate HTTP headers
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
headers = result.second;
if (!headersValid) if (!headersValid)
{ {
return sendErrorResponse(400, "Error parsing HTTP headers"); return sendErrorResponse(400, "Error parsing HTTP headers");
}
} }
if (headers.find("sec-websocket-key") == headers.end()) if (headers.find("sec-websocket-key") == headers.end())

View File

@ -7,7 +7,6 @@
#pragma once #pragma once
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include "IXHttp.h"
#include "IXSocket.h" #include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketInitResult.h" #include "IXWebSocketInitResult.h"
@ -31,15 +30,12 @@ namespace ix
WebSocketInitResult clientHandshake(const std::string& url, WebSocketInitResult clientHandshake(const std::string& url,
const WebSocketHttpHeaders& extraHeaders, const WebSocketHttpHeaders& extraHeaders,
const std::string& protocol,
const std::string& host, const std::string& host,
const std::string& path, const std::string& path,
int port, int port,
int timeoutSecs); int timeoutSecs);
WebSocketInitResult serverHandshake(int timeoutSecs, WebSocketInitResult serverHandshake(int timeoutSecs, bool enablePerMessageDeflate);
bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
private: private:
std::string genRandomString(const int len); std::string genRandomString(const int len);

View File

@ -46,8 +46,6 @@
* *
*/ */
#include <cstdint>
#include "IXWebSocketPerMessageDeflate.h" #include "IXWebSocketPerMessageDeflate.h"
#include "IXUniquePtr.h" #include "IXUniquePtr.h"
@ -80,11 +78,6 @@ namespace ix
_decompressor->init(inflateBits, clientNoContextTakeover); _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) bool WebSocketPerMessageDeflate::compress(const std::string& in, std::string& out)
{ {
return _compressor->compress(in, out); return _compressor->compress(in, out);

View File

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

View File

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

View File

@ -10,10 +10,8 @@
#include "zlib.h" #include "zlib.h"
#endif #endif
#include <array> #include <array>
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
#include "IXWebSocketSendData.h"
namespace ix namespace ix
{ {
@ -24,7 +22,6 @@ namespace ix
~WebSocketPerMessageDeflateCompressor(); ~WebSocketPerMessageDeflateCompressor();
bool init(uint8_t deflateBits, bool clientNoContextTakeOver); 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::string& out);
bool compress(const std::string& in, std::vector<uint8_t>& out); bool compress(const std::string& in, std::vector<uint8_t>& out);
bool compress(const std::vector<uint8_t>& in, std::string& 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 (_clientNoContextTakeover) ss << "; client_no_context_takeover";
if (_serverNoContextTakeover) ss << "; server_no_context_takeover"; if (_serverNoContextTakeover) ss << "; server_no_context_takeover";
ss << "; server_max_window_bits=" << static_cast<int>(_serverMaxWindowBits); ss << "; server_max_window_bits=" << _serverMaxWindowBits;
ss << "; client_max_window_bits=" << static_cast<int>(_clientMaxWindowBits); ss << "; client_max_window_bits=" << _clientMaxWindowBits;
ss << "\r\n"; ss << "\r\n";

View File

@ -6,7 +6,6 @@
#pragma once #pragma once
#include <cstdint>
#include <string> #include <string>
namespace ix namespace ix

View File

@ -57,7 +57,7 @@ namespace ix
server.setOnConnectionCallback( server.setOnConnectionCallback(
[remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket, [remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState) { std::shared_ptr<ConnectionState> connectionState) {
auto state = std::static_pointer_cast<ProxyConnectionState>(connectionState); auto state = std::dynamic_pointer_cast<ProxyConnectionState>(connectionState);
auto remoteIp = connectionState->getRemoteIp(); auto remoteIp = connectionState->getRemoteIp();
// Server connection // Server connection

View File

@ -1,129 +0,0 @@
/*
* IXWebSocketSendData.h
*
* WebSocket (Binary/Text) send data buffer
*/
#pragma once
#include <cstdint>
#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

@ -19,20 +19,17 @@ namespace ix
{ {
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
const bool WebSocketServer::kDefaultEnablePong(true); const bool WebSocketServer::kDefaultEnablePong(true);
const int WebSocketServer::kPingIntervalSeconds(-1); // disable heartbeat
WebSocketServer::WebSocketServer(int port, WebSocketServer::WebSocketServer(int port,
const std::string& host, const std::string& host,
int backlog, int backlog,
size_t maxConnections, size_t maxConnections,
int handshakeTimeoutSecs, int handshakeTimeoutSecs,
int addressFamily, int addressFamily)
int pingIntervalSeconds)
: SocketServer(port, host, backlog, maxConnections, addressFamily) : SocketServer(port, host, backlog, maxConnections, addressFamily)
, _handshakeTimeoutSecs(handshakeTimeoutSecs) , _handshakeTimeoutSecs(handshakeTimeoutSecs)
, _enablePong(kDefaultEnablePong) , _enablePong(kDefaultEnablePong)
, _enablePerMessageDeflate(true) , _enablePerMessageDeflate(true)
, _pingIntervalSeconds(pingIntervalSeconds)
{ {
} }
@ -82,22 +79,9 @@ namespace ix
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket, void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState) std::shared_ptr<ConnectionState> connectionState)
{ {
handleUpgrade(std::move(socket), connectionState); setThreadName("WebSocketServer::" + connectionState->getId());
connectionState->setTerminated();
}
void WebSocketServer::handleUpgrade(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState,
HttpRequestPtr request)
{
setThreadName("Srv:ws:" + connectionState->getId());
auto webSocket = std::make_shared<WebSocket>(); auto webSocket = std::make_shared<WebSocket>();
webSocket->setAutoThreadName(false);
webSocket->setPingInterval(_pingIntervalSeconds);
if (_onConnectionCallback) if (_onConnectionCallback)
{ {
_onConnectionCallback(webSocket, connectionState); _onConnectionCallback(webSocket, connectionState);
@ -105,7 +89,7 @@ namespace ix
if (!webSocket->isOnMessageCallbackRegistered()) if (!webSocket->isOnMessageCallbackRegistered())
{ {
logError("WebSocketServer Application developer error: Server callback improperly " logError("WebSocketServer Application developer error: Server callback improperly "
"registered."); "registerered.");
logError("Missing call to setOnMessageCallback inside setOnConnectionCallback."); logError("Missing call to setOnMessageCallback inside setOnConnectionCallback.");
connectionState->setTerminated(); connectionState->setTerminated();
return; return;
@ -115,8 +99,9 @@ namespace ix
{ {
WebSocket* webSocketRawPtr = webSocket.get(); WebSocket* webSocketRawPtr = webSocket.get();
webSocket->setOnMessageCallback( webSocket->setOnMessageCallback(
[this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) [this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) {
{ _onClientMessageCallback(connectionState, *webSocketRawPtr, msg); }); _onClientMessageCallback(connectionState, *webSocketRawPtr, msg);
});
} }
else else
{ {
@ -145,7 +130,7 @@ namespace ix
} }
auto status = webSocket->connectToSocket( auto status = webSocket->connectToSocket(
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate, request); std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate);
if (status.success) if (status.success)
{ {
// Process incoming messages and execute callbacks // Process incoming messages and execute callbacks
@ -170,6 +155,8 @@ namespace ix
logError("Cannot delete client"); logError("Cannot delete client");
} }
} }
connectionState->setTerminated();
} }
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients() std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
@ -189,30 +176,28 @@ namespace ix
// //
void WebSocketServer::makeBroadcastServer() void WebSocketServer::makeBroadcastServer()
{ {
setOnClientMessageCallback( setOnClientMessageCallback([this](std::shared_ptr<ConnectionState> connectionState,
[this](std::shared_ptr<ConnectionState> connectionState, WebSocket& webSocket,
WebSocket& webSocket, const WebSocketMessagePtr& msg) {
const WebSocketMessagePtr& msg) auto remoteIp = connectionState->getRemoteIp();
if (msg->type == ix::WebSocketMessageType::Message)
{ {
auto remoteIp = connectionState->getRemoteIp(); for (auto&& client : getClients())
if (msg->type == ix::WebSocketMessageType::Message)
{ {
for (auto&& client : getClients()) if (client.get() != &webSocket)
{ {
if (client.get() != &webSocket) client->send(msg->str, msg->binary);
{
client->send(msg->str, msg->binary);
// Make sure the OS send buffer is flushed before moving on // Make sure the OS send buffer is flushed before moving on
do do
{ {
std::chrono::duration<double, std::milli> duration(500); std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration); std::this_thread::sleep_for(duration);
} while (client->bufferedAmount() != 0); } while (client->bufferedAmount() != 0);
}
} }
} }
}); }
});
} }
bool WebSocketServer::listenAndStart() bool WebSocketServer::listenAndStart()
@ -226,19 +211,4 @@ namespace ix
start(); start();
return true; return true;
} }
int WebSocketServer::getHandshakeTimeoutSecs()
{
return _handshakeTimeoutSecs;
}
bool WebSocketServer::isPongEnabled()
{
return _enablePong;
}
bool WebSocketServer::isPerMessageDeflateEnabled()
{
return _enablePerMessageDeflate;
}
} // namespace ix } // namespace ix

View File

@ -33,8 +33,7 @@ namespace ix
int backlog = SocketServer::kDefaultTcpBacklog, int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections, size_t maxConnections = SocketServer::kDefaultMaxConnections,
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs, int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
int addressFamily = SocketServer::kDefaultAddressFamily, int addressFamily = SocketServer::kDefaultAddressFamily);
int pingIntervalSeconds = WebSocketServer::kPingIntervalSeconds);
virtual ~WebSocketServer(); virtual ~WebSocketServer();
virtual void stop() final; virtual void stop() final;
@ -53,16 +52,11 @@ namespace ix
const static int kDefaultHandShakeTimeoutSecs; const static int kDefaultHandShakeTimeoutSecs;
int getHandshakeTimeoutSecs();
bool isPongEnabled();
bool isPerMessageDeflateEnabled();
private: private:
// Member variables // Member variables
int _handshakeTimeoutSecs; int _handshakeTimeoutSecs;
bool _enablePong; bool _enablePong;
bool _enablePerMessageDeflate; bool _enablePerMessageDeflate;
int _pingIntervalSeconds;
OnConnectionCallback _onConnectionCallback; OnConnectionCallback _onConnectionCallback;
OnClientMessageCallback _onClientMessageCallback; OnClientMessageCallback _onClientMessageCallback;
@ -71,16 +65,10 @@ namespace ix
std::set<std::shared_ptr<WebSocket>> _clients; std::set<std::shared_ptr<WebSocket>> _clients;
const static bool kDefaultEnablePong; const static bool kDefaultEnablePong;
const static int kPingIntervalSeconds;
// Methods // Methods
virtual void handleConnection(std::unique_ptr<Socket> socket, virtual void handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState); std::shared_ptr<ConnectionState> connectionState);
virtual size_t getConnectedClientsCount() final; virtual size_t getConnectedClientsCount() final;
protected:
void handleUpgrade(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState,
HttpRequestPtr request = nullptr);
}; };
} // namespace ix } // namespace ix

View File

@ -45,6 +45,7 @@
#include <cstdarg> #include <cstdarg>
#include <cstdlib> #include <cstdlib>
#include <sstream> #include <sstream>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <thread> #include <thread>
@ -53,6 +54,7 @@
namespace ix namespace ix
{ {
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat");
const int WebSocketTransport::kDefaultPingIntervalSecs(-1); const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
const bool WebSocketTransport::kDefaultEnablePong(true); const bool WebSocketTransport::kDefaultEnablePong(true);
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300); const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
@ -72,9 +74,6 @@ namespace ix
, _enablePong(kDefaultEnablePong) , _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs) , _pingIntervalSecs(kDefaultPingIntervalSecs)
, _pongReceived(false) , _pongReceived(false)
, _setCustomMessage(false)
, _kPingMessage("ixwebsocket::heartbeat")
, _pingType(SendMessageKind::Ping)
, _pingCount(0) , _pingCount(0)
, _lastSendPingTimePoint(std::chrono::steady_clock::now()) , _lastSendPingTimePoint(std::chrono::steady_clock::now())
{ {
@ -140,7 +139,7 @@ namespace ix
_enablePerMessageDeflate); _enablePerMessageDeflate);
result = webSocketHandshake.clientHandshake( result = webSocketHandshake.clientHandshake(
remoteUrl, headers, protocol, host, path, port, timeoutSecs); remoteUrl, headers, host, path, port, timeoutSecs);
if (result.http_status >= 300 && result.http_status < 400) if (result.http_status >= 300 && result.http_status < 400)
{ {
@ -171,8 +170,7 @@ namespace ix
// Server // Server
WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket, WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs, int timeoutSecs,
bool enablePerMessageDeflate, bool enablePerMessageDeflate)
HttpRequestPtr request)
{ {
std::lock_guard<std::mutex> lock(_socketMutex); std::lock_guard<std::mutex> lock(_socketMutex);
@ -189,8 +187,7 @@ namespace ix
_perMessageDeflateOptions, _perMessageDeflateOptions,
_enablePerMessageDeflate); _enablePerMessageDeflate);
auto result = auto result = webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate);
webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate, request);
if (result.success) if (result.success)
{ {
setReadyState(ReadyState::OPEN); setReadyState(ReadyState::OPEN);
@ -251,51 +248,13 @@ namespace ix
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs); return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs);
} }
void WebSocketTransport::setPingMessage(const std::string& message, SendMessageKind pingType) WebSocketSendInfo WebSocketTransport::sendHeartBeat()
{
_setCustomMessage = true;
_kPingMessage = message;
_pingType = pingType;
}
WebSocketSendInfo WebSocketTransport::sendHeartBeat(SendMessageKind pingMessage)
{ {
_pongReceived = false; _pongReceived = false;
std::stringstream ss; std::stringstream ss;
ss << kPingMessage << "::" << _pingIntervalSecs << "s"
ss << _kPingMessage; << "::" << _pingCount++;
if (!_setCustomMessage) return sendPing(ss.str());
{
ss << "::" << _pingIntervalSecs << "s"
<< "::" << _pingCount++;
}
if (pingMessage == SendMessageKind::Ping)
{
return sendPing(ss.str());
}
else if (pingMessage == SendMessageKind::Binary)
{
WebSocketSendInfo info = sendBinary(ss.str(), nullptr);
if (info.success)
{
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
return info;
}
else if (pingMessage == SendMessageKind::Text)
{
WebSocketSendInfo info = sendText(ss.str(), nullptr);
if (info.success)
{
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
return info;
}
// unknow type ping message
return {};
} }
bool WebSocketTransport::closingDelayExceeded() bool WebSocketTransport::closingDelayExceeded()
@ -311,9 +270,7 @@ namespace ix
{ {
if (pingIntervalExceeded()) if (pingIntervalExceeded())
{ {
// If it is not a 'ping' message of ping type, there is no need to judge whether if (!_pongReceived)
// pong will receive it
if (_pingType == SendMessageKind::Ping && !_pongReceived)
{ {
// ping response (PONG) exceeds the maximum delay, close the connection // ping response (PONG) exceeds the maximum delay, close the connection
close(WebSocketCloseConstants::kInternalErrorCode, close(WebSocketCloseConstants::kInternalErrorCode,
@ -321,7 +278,7 @@ namespace ix
} }
else else
{ {
sendHeartBeat(_pingType); sendHeartBeat();
} }
} }
} }
@ -340,11 +297,13 @@ namespace ix
lastingTimeoutDelayInMs = (1000 * _pingIntervalSecs) - timeSinceLastPingMs; lastingTimeoutDelayInMs = (1000 * _pingIntervalSecs) - timeSinceLastPingMs;
} }
// The platform may not have select interrupt capabilities, so wait with a small timeout #ifdef _WIN32
if (lastingTimeoutDelayInMs <= 0 && !_socket->isWakeUpFromPollSupported()) // Windows does not have select interrupt capabilities, so wait with a small timeout
if (lastingTimeoutDelayInMs <= 0)
{ {
lastingTimeoutDelayInMs = 20; lastingTimeoutDelayInMs = 20;
} }
#endif
// If we are requesting a cancellation, pass in a positive and small timeout // If we are requesting a cancellation, pass in a positive and small timeout
// to never poll forever without a timeout. // to never poll forever without a timeout.
@ -700,7 +659,6 @@ namespace ix
if (_readyState != ReadyState::CLOSING) if (_readyState != ReadyState::CLOSING)
{ {
// send back the CLOSE frame // send back the CLOSE frame
setReadyState(ReadyState::CLOSING);
sendCloseFrame(code, reason); sendCloseFrame(code, reason);
wakeUpFromPoll(SelectInterrupt::kCloseRequest); wakeUpFromPoll(SelectInterrupt::kCloseRequest);
@ -820,8 +778,9 @@ namespace ix
return static_cast<unsigned>(seconds); return static_cast<unsigned>(seconds);
} }
template<class T>
WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type, WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type,
const IXWebSocketSendData& message, const T& message,
bool compress, bool compress,
const OnProgressCallback& onProgressCallback) const OnProgressCallback& onProgressCallback)
{ {
@ -850,9 +809,8 @@ namespace ix
compressionError = false; compressionError = false;
wireSize = _compressedMessage.size(); wireSize = _compressedMessage.size();
IXWebSocketSendData compressedSendData(_compressedMessage); message_begin = _compressedMessage.cbegin();
message_begin = compressedSendData.cbegin(); message_end = _compressedMessage.cend();
message_end = compressedSendData.cend();
} }
{ {
@ -884,8 +842,8 @@ namespace ix
// //
auto steps = wireSize / kChunkSize; auto steps = wireSize / kChunkSize;
auto begin = message_begin; std::string::const_iterator begin = message_begin;
auto end = message_end; std::string::const_iterator end = message_end;
for (uint64_t i = 0; i < steps; ++i) for (uint64_t i = 0; i < steps; ++i)
{ {
@ -1024,7 +982,7 @@ namespace ix
return sendOnSocket(); return sendOnSocket();
} }
WebSocketSendInfo WebSocketTransport::sendPing(const IXWebSocketSendData& message) WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
{ {
bool compress = false; bool compress = false;
WebSocketSendInfo info = sendData(wsheader_type::PING, message, compress); WebSocketSendInfo info = sendData(wsheader_type::PING, message, compress);
@ -1038,7 +996,7 @@ namespace ix
return info; return info;
} }
WebSocketSendInfo WebSocketTransport::sendBinary(const IXWebSocketSendData& message, WebSocketSendInfo WebSocketTransport::sendBinary(const std::string& message,
const OnProgressCallback& onProgressCallback) const OnProgressCallback& onProgressCallback)
{ {
@ -1046,7 +1004,7 @@ namespace ix
wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate, onProgressCallback); wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate, onProgressCallback);
} }
WebSocketSendInfo WebSocketTransport::sendText(const IXWebSocketSendData& message, WebSocketSendInfo WebSocketTransport::sendText(const std::string& message,
const OnProgressCallback& onProgressCallback) const OnProgressCallback& onProgressCallback)
{ {
@ -1073,10 +1031,7 @@ namespace ix
else if (ret <= 0) else if (ret <= 0)
{ {
closeSocket(); closeSocket();
if (_readyState != ReadyState::CLOSING) setReadyState(ReadyState::CLOSED);
{
setReadyState(ReadyState::CLOSED);
}
return false; return false;
} }
else else
@ -1174,22 +1129,7 @@ namespace ix
{ {
_requestInitCancellation = true; _requestInitCancellation = true;
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return;
{
// Wake up the socket polling thread, as
// Socket::isReadyToRead() might be still waiting the
// interrupt event to happen.
bool wakeUpPoll = false;
{
std::lock_guard<std::mutex> lock(_socketMutex);
wakeUpPoll = (_socket && _socket->isWakeUpFromPollSupported());
}
if (wakeUpPoll)
{
wakeUpFromPoll(SelectInterrupt::kCloseRequest);
}
return;
}
if (closeWireSize == 0) if (closeWireSize == 0)
{ {

View File

@ -18,10 +18,8 @@
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketPerMessageDeflate.h" #include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateOptions.h" #include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendData.h"
#include "IXWebSocketSendInfo.h" #include "IXWebSocketSendInfo.h"
#include <atomic> #include <atomic>
#include <cstdint>
#include <functional> #include <functional>
#include <list> #include <list>
#include <memory> #include <memory>
@ -87,15 +85,14 @@ namespace ix
// Server // Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket, WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs, int timeoutSecs,
bool enablePerMessageDeflate, bool enablePerMessageDeflate);
HttpRequestPtr request = nullptr);
PollResult poll(); PollResult poll();
WebSocketSendInfo sendBinary(const IXWebSocketSendData& message, WebSocketSendInfo sendBinary(const std::string& message,
const OnProgressCallback& onProgressCallback); const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendText(const IXWebSocketSendData& message, WebSocketSendInfo sendText(const std::string& message,
const OnProgressCallback& onProgressCallback); const OnProgressCallback& onProgressCallback);
WebSocketSendInfo sendPing(const IXWebSocketSendData& message); WebSocketSendInfo sendPing(const std::string& message);
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode, void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage, const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage,
@ -110,12 +107,8 @@ namespace ix
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback); void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
size_t bufferedAmount() const; size_t bufferedAmount() const;
// set ping heartbeat message
void setPingMessage(const std::string& message, SendMessageKind pingType);
// internal // internal
// send any type of ping packet, not only 'ping' type WebSocketSendInfo sendHeartBeat();
WebSocketSendInfo sendHeartBeat(SendMessageKind pingType);
private: private:
std::string _url; std::string _url;
@ -220,10 +213,7 @@ namespace ix
std::atomic<bool> _pongReceived; std::atomic<bool> _pongReceived;
static const int kDefaultPingIntervalSecs; static const int kDefaultPingIntervalSecs;
static const std::string kPingMessage;
bool _setCustomMessage;
std::string _kPingMessage;
SendMessageKind _pingType;
std::atomic<uint64_t> _pingCount; std::atomic<uint64_t> _pingCount;
// We record when ping are being sent so that we can know when to send the next one // We record when ping are being sent so that we can know when to send the next one
@ -251,8 +241,9 @@ namespace ix
bool sendOnSocket(); bool sendOnSocket();
bool receiveFromSocket(); bool receiveFromSocket();
template<class T>
WebSocketSendInfo sendData(wsheader_type::opcode_type type, WebSocketSendInfo sendData(wsheader_type::opcode_type type,
const IXWebSocketSendData& message, const T& message,
bool compress, bool compress,
const OnProgressCallback& onProgressCallback = nullptr); const OnProgressCallback& onProgressCallback = nullptr);

View File

@ -6,4 +6,4 @@
#pragma once #pragma once
#define IX_WEBSOCKET_VERSION "11.4.6" #define IX_WEBSOCKET_VERSION "11.3.1"

View File

@ -13,24 +13,16 @@ all: brew
install: brew install: brew
-DCMAKE_INSTALL_PREFIX=/opt/homebrew
# Use -DCMAKE_INSTALL_PREFIX= to install into another location # Use -DCMAKE_INSTALL_PREFIX= to install into another location
# on osx it is good practice to make /usr/local user writable # on osx it is good practice to make /usr/local user writable
# sudo chown -R `whoami`/staff /usr/local # sudo chown -R `whoami`/staff /usr/local
# #
# Those days (since Apple Silicon mac shipped), on macOS homebrew installs in /opt/homebrew, and /usr/local is readonly
#
# Release, Debug, MinSizeRel, RelWithDebInfo are the build types # Release, Debug, MinSizeRel, RelWithDebInfo are the build types
# #
# Default rule does not use python as that requires first time users to have Python3 installed # Default rule does not use python as that requires first time users to have Python3 installed
# #
brew: brew:
ifeq ($(shell uname),Darwin) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install)
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_PREFIX=/opt/homebrew -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja)
else
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja)
endif
# Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the # Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the
# server side ?) and I can't work-around it easily, so we're using mbedtls on # server side ?) and I can't work-around it easily, so we're using mbedtls on
@ -39,10 +31,10 @@ ws_mbedtls_install:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install)
ws: ws:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
ws_unity: ws_unity:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
ws_install: ws_install:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
@ -54,13 +46,13 @@ ws_openssl_install:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install)
ws_mbedtls: ws_mbedtls:
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja) mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4)
ws_no_ssl: ws_no_ssl:
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; ninja) mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4)
ws_no_python: ws_no_python:
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; ninja install) mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j4 install)
uninstall: uninstall:
xargs rm -fv < build/install_manifest.txt xargs rm -fv < build/install_manifest.txt

View File

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDNDCCAhwCFCl+O/rR8flqYKKvD0iwkucFwMaLMA0GCSqGSIb3DQEBCwUAMEEx
FDASBgNVBAoMC21hY2hpbmV6b25lMRQwEgYDVQQKDAtJWFdlYlNvY2tldDETMBEG
A1UEAwwKdHJ1c3RlZC1jYTAgFw0yMjA4MjMyMDM2MjVaGA80MjgxMDYwMTIwMzYy
NVowajELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhCZXJrZWxl
eTEbMBkGA1UECgwSRHVtbXkgT3JnYW5pemF0aW9uMR4wHAYDVQQDDBVub3QuYS52
YWxpZC5ob3N0Lm5hbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2
9N806IjCvA82zfk9CPNwaEHOygNDJSXaZ38YDSI4C+Wf2imnMxrLQKaoccHUi+9L
4rQN/hSCg+uTULQUZ0iyssGDaIh4IcAeoEcNlXYHTrgP+aAaby3q5zeZ80K3+6e4
rqcuBPV2lLszJu3XXwEKbDSxW3De0gc2N8m9DN8Lx7i70DRf0F4m6RIMFF/kHXwa
zZLeG7rZb4xL684lmmQsWtk5Z600CvrBtUE7fQ7bhy0QhSt66kdTSL8IKQrbWcGj
q0tggFlOqeyZSi73gqUiAIuGO8/tRgmp4HygNyC24jpOB5nObOPPJTUEf5Mk1Bum
kD5a+yL6YbVdhiaK17wbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKsLXGLfO1IZ
LbofGc7/TCQwRayR3RdG4864PBy97KfJWyizg7Wm4X4yPFRG+6q3X5NKW32Ew9lI
jXulXCTjWOiSxiG4Pk20uczkOhWVHFdnS9gZvykPC/ElxBKPalT6MMstZWxpElsk
rCDKXj4LkD0po8bZrjlgSZQQQk6XMRkoRai2GWLJqIjaNCSF8nqb1wM/1OE1tAwi
polO1eFMA24yypvlXcNrNXjqcQ7LaoQFQltmi/RV+uTk7EK2F2jgYVzJ/pe2ET0i
RIMbGZTlAiemDGL9SpMsxftG6fSmP6QqDqYExmmPlZMLprb2da/2kelWFA+VkvdG
zFrnIcyfMY4=
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtvTfNOiIwrwPNs35PQjzcGhBzsoDQyUl2md/GA0iOAvln9op
pzMay0CmqHHB1IvvS+K0Df4UgoPrk1C0FGdIsrLBg2iIeCHAHqBHDZV2B064D/mg
Gm8t6uc3mfNCt/unuK6nLgT1dpS7Mybt118BCmw0sVtw3tIHNjfJvQzfC8e4u9A0
X9BeJukSDBRf5B18Gs2S3hu62W+MS+vOJZpkLFrZOWetNAr6wbVBO30O24ctEIUr
eupHU0i/CCkK21nBo6tLYIBZTqnsmUou94KlIgCLhjvP7UYJqeB8oDcgtuI6TgeZ
zmzjzyU1BH+TJNQbppA+Wvsi+mG1XYYmite8GwIDAQABAoIBAGRzAXG9EglI01mV
sPfvyCi5NRhiFXRyGtxU4pTD8TuwXHxtfV0NU/KwJlBpVLBrvBCAAbeE/qHB6D9T
metx4ZorRs/tPrAmZ6LpANnWa50LfUdYGK0qyZ0lIYPm6YS2KJnfWm6LznEyq60j
/IW45YthaXTO7aOI0OjVrG+dd4CxU1g1NtCQ9bdDMDjfXFVnoOifXIl8W22eRMoZ
LzCz+0sI0R0LenXCPT566de21F0NDkIK7NaQ1l5BMX4PA+RsN3cZlzyruA1woPKI
aBR2LQGNrBfDVGMATtUm89RpWAftb8FmXqYUsM7zAzTO6dViitiB7OFlw7Ax15YH
MTj5zGECgYEA35ocEEMfyahBN70bjyiqOHlzKwFjDl9DsUf8xqHsNhYAL+GrOK9A
8lN5ByzcnbV3TJtU4WYbPgQJld8gXFx4h3eS+SkA/ASkAdtgHfdMImZ1v7k3TIPf
DXOCsHzELsQY6OgiI572Nwzx/Tl+0dFwaOfLjU9iEmmqL667j1Y4NiMCgYEA0Xch
9K/vwZ1I9gM3ySvG40R2TRriC9Bf8jwrEWeRsWNvBcqtMMrgwAMsMCKDugSZR7n6
o3WZV6mpvYVLFx0b93v07i7EpFP27kMw3gLNBKX62snR9JbqwAMK7tktgLds5pKM
DvLHuAQ9XMMXMLX7WlSyhmtFeU7IDulTSHHqdakCgYEAywITCpy2xpKRK7bwx4gH
C6EQc/IdahYJ0nHmSL0IRY6x+sbrelp7H8ezcVVEs5bmylGYvc/DWgm2XjCnI9P8
xhlFAhw9PZJFCT6QRISaxfy6WSgi0cBEieTeubd9MmxtpT/khuyy5AZHyj0iLAL4
CPayMwjopIj0r7f3p8qC3HsCgYBmq2kmYVI4aZrIkv02CtIatYTy+DlSJxnQRvOp
PUWpWB6kDRrk7pxJIYT4NwKwG+7xvFQA6PR3hn7fmUUcGDWMEeMVGDFkho9ja+W4
/FB3dc/Gi+PwakS4RwWF20e1brLfNXeXICMKrHFTVYC5bIm+VgOHZW8RLa9bt7wN
p2CPuQKBgQCjW+rCODmMzEues/I143mYMDdZ1IschtWGiXBNrpkHm/DcZSutbacm
5C7Kwv44pfA90NHDTjuaIgRgfeUTawkrl8uPXEuj80mW72agf5oS8lJzD+2jibCj
Q4K52G+0LaTxHLZxufil28Rgja01c0mTcuLbhKtCgHl5EHP19wOKEg==
-----END RSA PRIVATE KEY-----

View File

@ -16,21 +16,21 @@ set (TEST_TARGET_NAMES
IXWebSocketServerTest IXWebSocketServerTest
IXWebSocketTestConnectionDisconnection IXWebSocketTestConnectionDisconnection
IXUrlParserTest IXUrlParserTest
# IXHttpClientTest ## FIXME httpbin.org does not seem normal IXHttpClientTest
IXUnityBuildsTest IXUnityBuildsTest
IXHttpTest IXHttpTest
IXDNSLookupTest IXDNSLookupTest
IXWebSocketSubProtocolTest IXWebSocketSubProtocolTest
# IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws # IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws
IXStrCaseCompareTest IXStrCaseCompareTest
IXExponentialBackoffTest
IXWebSocketCloseTest
) )
# Some unittest don't work on windows yet # Some unittest don't work on windows yet
# Windows without TLS does not have hmac yet # Windows without TLS does not have hmac yet
if (UNIX) if (UNIX)
list(APPEND TEST_TARGET_NAMES list(APPEND TEST_TARGET_NAMES
IXWebSocketCloseTest
# Fail on Windows in CI probably because the pathing is wrong and # Fail on Windows in CI probably because the pathing is wrong and
# some resource files cannot be found # some resource files cannot be found
IXHttpServerTest IXHttpServerTest

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,13 @@ TEST_CASE("dns", "[net]")
auto dnsLookup = std::make_shared<DNSLookup>("www.google.com", 80); auto dnsLookup = std::make_shared<DNSLookup>("www.google.com", 80);
std::string errMsg; std::string errMsg;
auto res = dnsLookup->resolve(errMsg, [] { return false; }); struct addrinfo* res;
res = dnsLookup->resolve(errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl; std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res != nullptr); REQUIRE(res != nullptr);
dnsLookup->release(res);
} }
SECTION("Test resolving a non-existing hostname") SECTION("Test resolving a non-existing hostname")
@ -29,7 +33,7 @@ TEST_CASE("dns", "[net]")
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80); auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
std::string errMsg; std::string errMsg;
auto res = dnsLookup->resolve(errMsg, [] { return false; }); struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl; std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr); REQUIRE(res == nullptr);
} }
@ -40,7 +44,7 @@ TEST_CASE("dns", "[net]")
std::string errMsg; std::string errMsg;
// The callback returning true means we are requesting cancellation // The callback returning true means we are requesting cancellation
auto res = dnsLookup->resolve(errMsg, [] { return true; }); struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return true; });
std::cerr << "Error message: " << errMsg << std::endl; std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr); REQUIRE(res == nullptr);
} }

View File

@ -1,39 +0,0 @@
/*
* 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,11 +5,8 @@
*/ */
#include "catch.hpp" #include "catch.hpp"
#include <cstdint>
#include <iostream> #include <iostream>
#include <ixwebsocket/IXGetFreePort.h>
#include <ixwebsocket/IXHttpClient.h> #include <ixwebsocket/IXHttpClient.h>
#include <ixwebsocket/IXHttpServer.h>
using namespace ix; using namespace ix;
@ -97,52 +94,6 @@ TEST_CASE("http_client", "[http]")
} }
#endif #endif
#if defined(IXWEBSOCKET_USE_TLS) && !defined(IXWEBSOCKET_USE_SECURE_TRANSPORT)
SECTION("Disable hostname validation")
{
static auto test_cert_with_wrong_name = [](bool validate_hostname)
{
int port = getFreePort();
ix::HttpServer server(port, "127.0.0.1");
SocketTLSOptions tlsOptionsServer;
tlsOptionsServer.tls = true;
tlsOptionsServer.caFile = "NONE";
tlsOptionsServer.certFile = "./.certs/wrong-name-server-crt.pem";
tlsOptionsServer.keyFile = "./.certs/wrong-name-server-key.pem";
server.setTLSOptions(tlsOptionsServer);
auto res = server.listen();
REQUIRE(res.first);
server.start();
HttpClient httpClient;
SocketTLSOptions tlsOptionsClient;
tlsOptionsClient.caFile = "./.certs/trusted-ca-crt.pem";
tlsOptionsClient.disable_hostname_validation = validate_hostname;
httpClient.setTLSOptions(tlsOptionsClient);
std::string url("https://localhost:" + std::to_string(port));
auto args = httpClient.createRequest(url);
args->connectTimeout = 10;
args->transferTimeout = 10;
auto response = httpClient.get(url, args);
std::cerr << "Status: " << response->statusCode << std::endl;
std::cerr << "Error code: " << (int) response->errorCode << std::endl;
std::cerr << "Error message: " << response->errorMsg << std::endl;
server.stop();
return std::make_tuple(response->errorCode, response->statusCode);
};
REQUIRE(test_cert_with_wrong_name(false) ==
std::make_tuple(HttpErrorCode::CannotConnect, 0));
REQUIRE(test_cert_with_wrong_name(true) == std::make_tuple(HttpErrorCode::Ok, 404));
}
#endif
SECTION("Async API, one call") SECTION("Async API, one call")
{ {
bool async = true; bool async = true;
@ -270,124 +221,4 @@ TEST_CASE("http_client", "[http]")
REQUIRE(statusCode1 == 200); REQUIRE(statusCode1 == 200);
REQUIRE(statusCode2 == 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);
}
} }

View File

@ -60,7 +60,6 @@ TEST_CASE("http server", "[httpd]")
REQUIRE(response->errorCode == HttpErrorCode::Ok); REQUIRE(response->errorCode == HttpErrorCode::Ok);
REQUIRE(response->statusCode == 200); REQUIRE(response->statusCode == 200);
REQUIRE(response->headers["Accept-Encoding"] == "gzip"); REQUIRE(response->headers["Accept-Encoding"] == "gzip");
REQUIRE(response->headers["Content-Encoding"] == "gzip");
server.stop(); server.stop();
} }

View File

@ -84,40 +84,6 @@ namespace ix
REQUIRE(port == 443); // default port for wss REQUIRE(port == 443); // default port for wss
} }
SECTION("wss://google.com/?arg=value")
{
std::string url = "wss://google.com/?arg=value&arg2=value2";
std::string protocol, host, path, query;
int port;
bool res;
res = UrlParser::parse(url, protocol, host, path, query, port);
REQUIRE(res);
REQUIRE(protocol == "wss");
REQUIRE(host == "google.com");
REQUIRE(path == "/?arg=value&arg2=value2");
REQUIRE(query == "arg=value&arg2=value2");
REQUIRE(port == 443); // default port for wss
}
SECTION("wss://google.com?arg=value")
{
std::string url = "wss://google.com?arg=value&arg2=value2";
std::string protocol, host, path, query;
int port;
bool res;
res = UrlParser::parse(url, protocol, host, path, query, port);
REQUIRE(res);
REQUIRE(protocol == "wss");
REQUIRE(host == "google.com");
REQUIRE(path == "/?arg=value&arg2=value2");
REQUIRE(query == "arg=value&arg2=value2");
REQUIRE(port == 443); // default port for wss
}
SECTION("real test") SECTION("real test")
{ {
std::string url = std::string url =

View File

@ -77,6 +77,24 @@ namespace
return std::make_pair(res.first, std::string(vec.begin(), vec.end())); return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
} }
// Assume the file exists
std::string readBytes(const std::string& path)
{
std::vector<uint8_t> memblock;
std::ifstream file(path);
file.seekg(0, file.end);
std::streamoff size = file.tellg();
file.seekg(0, file.beg);
memblock.reserve((size_t) size);
memblock.insert(
memblock.begin(), std::istream_iterator<char>(file), std::istream_iterator<char>());
std::string bytes(memblock.begin(), memblock.end());
return bytes;
}
std::string truncate(const std::string& str, size_t n) std::string truncate(const std::string& str, size_t n)
{ {
if (str.size() < n) if (str.size() < n)
@ -89,6 +107,12 @@ namespace
} }
} }
bool fileExists(const std::string& fileName)
{
std::ifstream infile(fileName);
return infile.good();
}
std::string extractFilename(const std::string& path) std::string extractFilename(const std::string& path)
{ {
std::string::size_type idx; std::string::size_type idx;
@ -892,8 +916,9 @@ namespace ix
auto dnsLookup = std::make_shared<DNSLookup>(hostname, 80); auto dnsLookup = std::make_shared<DNSLookup>(hostname, 80);
std::string errMsg; std::string errMsg;
struct addrinfo* res;
auto res = dnsLookup->resolve(errMsg, [] { return false; }); res = dnsLookup->resolve(errMsg, [] { return false; });
auto addr = res->ai_addr; auto addr = res->ai_addr;
@ -2461,8 +2486,10 @@ int main(int argc, char** argv)
bool verbose = false; bool verbose = false;
bool save = false; bool save = false;
bool quiet = false; bool quiet = false;
bool fluentd = false;
bool compress = false; bool compress = false;
bool compressRequest = false; bool compressRequest = false;
bool stress = false;
bool disableAutomaticReconnection = false; bool disableAutomaticReconnection = false;
bool disablePerMessageDeflate = false; bool disablePerMessageDeflate = false;
bool greetings = false; bool greetings = false;
@ -2478,6 +2505,7 @@ int main(int argc, char** argv)
int transferTimeout = 1800; int transferTimeout = 1800;
int maxRedirects = 5; int maxRedirects = 5;
int delayMs = -1; int delayMs = -1;
int count = 1;
int msgCount = 1000 * 1000; int msgCount = 1000 * 1000;
uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds
int pingIntervalSecs = 30; int pingIntervalSecs = 30;
@ -2501,7 +2529,6 @@ int main(int argc, char** argv)
"A (comma/space/colon) separated list of ciphers to use for TLS"); "A (comma/space/colon) separated list of ciphers to use for TLS");
app->add_flag("--tls", tlsOptions.tls, "Enable TLS (server only)"); app->add_flag("--tls", tlsOptions.tls, "Enable TLS (server only)");
app->add_flag("--verify_none", verifyNone, "Disable peer cert verification"); app->add_flag("--verify_none", verifyNone, "Disable peer cert verification");
app->add_flag("--disable-hostname-validation", tlsOptions.disable_hostname_validation, "Disable validation of certificates' hostnames");
}; };
app.add_flag("--version", version, "Print ws version"); app.add_flag("--version", version, "Print ws version");