Compare commits
No commits in common. "master" and "v11.2.4" have entirely different histories.
5
.github/workflows/mkdocs.yml
vendored
5
.github/workflows/mkdocs.yml
vendored
@ -1,8 +1,6 @@
|
|||||||
name: mkdocs
|
name: mkdocs
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths:
|
paths:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
|
||||||
@ -23,8 +21,5 @@ jobs:
|
|||||||
pip install pygments
|
pip install pygments
|
||||||
- name: Build doc
|
- name: Build doc
|
||||||
run: |
|
run: |
|
||||||
git checkout master
|
|
||||||
git clean -dfx .
|
|
||||||
git fetch
|
|
||||||
git pull
|
git pull
|
||||||
mkdocs gh-deploy
|
mkdocs gh-deploy
|
||||||
|
19
.github/workflows/stale.yml
vendored
Normal file
19
.github/workflows/stale.yml
vendored
Normal 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'
|
1
.github/workflows/unittest_linux.yml
vendored
1
.github/workflows/unittest_linux.yml
vendored
@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
linux:
|
||||||
|
1
.github/workflows/unittest_linux_asan.yml
vendored
1
.github/workflows/unittest_linux_asan.yml
vendored
@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
linux:
|
||||||
|
@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
mac_tsan_mbedtls:
|
mac_tsan_mbedtls:
|
||||||
|
@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
mac_tsan_openssl:
|
mac_tsan_openssl:
|
||||||
|
@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
mac_tsan_sectransport:
|
mac_tsan_sectransport:
|
||||||
|
1
.github/workflows/unittest_uwp.yml
vendored
1
.github/workflows/unittest_uwp.yml
vendored
@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
uwp:
|
uwp:
|
||||||
|
26
.github/workflows/unittest_windows.yml
vendored
Normal file
26
.github/workflows/unittest_windows.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: windows
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
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
|
3
.github/workflows/unittest_windows_gcc.yml
vendored
3
.github/workflows/unittest_windows_gcc.yml
vendored
@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows:
|
windows:
|
||||||
@ -11,7 +10,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
2
.gitignore
vendored
@ -8,5 +8,3 @@ ws/.srl
|
|||||||
ixhttpd
|
ixhttpd
|
||||||
makefile
|
makefile
|
||||||
a.out
|
a.out
|
||||||
.idea/
|
|
||||||
cmake-build-debug/
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
|
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
|
||||||
|
|
||||||
# mbedtls-3.0 changed headers files, and we need to ifdef'out a few things
|
|
||||||
find_path(MBEDTLS_VERSION_GREATER_THAN_3 mbedtls/build_info.h)
|
|
||||||
|
|
||||||
find_library(MBEDTLS_LIBRARY mbedtls)
|
find_library(MBEDTLS_LIBRARY mbedtls)
|
||||||
find_library(MBEDX509_LIBRARY mbedx509)
|
find_library(MBEDX509_LIBRARY mbedx509)
|
||||||
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
|
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
|
||||||
|
@ -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)
|
|
||||||
# Building shared library
|
|
||||||
|
|
||||||
if(MSVC)
|
|
||||||
# Workaround for some projects
|
|
||||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library( ixwebsocket SHARED
|
|
||||||
${IXWEBSOCKET_SOURCES}
|
|
||||||
${IXWEBSOCKET_HEADERS}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set library version
|
|
||||||
set_target_properties(ixwebsocket PROPERTIES VERSION ${PROJECT_VERSION})
|
|
||||||
else()
|
|
||||||
# Static library
|
|
||||||
add_library( ixwebsocket
|
add_library( ixwebsocket
|
||||||
${IXWEBSOCKET_SOURCES}
|
${IXWEBSOCKET_SOURCES}
|
||||||
${IXWEBSOCKET_HEADERS}
|
${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)
|
||||||
@ -204,57 +180,56 @@ if (USE_TLS)
|
|||||||
# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0)
|
# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL
|
# Use OPENSSL_ROOT_DIR CMake variable if you need to use your own openssl
|
||||||
if (NOT OPENSSL_FOUND)
|
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
endif()
|
|
||||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||||
|
|
||||||
add_definitions(${OPENSSL_DEFINITIONS})
|
add_definitions(${OPENSSL_DEFINITIONS})
|
||||||
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
|
target_include_directories(ixwebsocket PUBLIC ${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")
|
||||||
|
|
||||||
# This MBEDTLS_FOUND check is to help find a cmake manually configured MbedTLS
|
|
||||||
if (NOT MBEDTLS_FOUND)
|
|
||||||
find_package(MbedTLS REQUIRED)
|
find_package(MbedTLS REQUIRED)
|
||||||
|
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||||
if (MBEDTLS_VERSION_GREATER_THAN_3)
|
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
|
||||||
target_compile_definitions(ixwebsocket PRIVATE IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
endif()
|
|
||||||
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${MBEDTLS_INCLUDE_DIRS}>)
|
|
||||||
target_link_libraries(ixwebsocket PRIVATE ${MBEDTLS_LIBRARIES})
|
|
||||||
elseif (USE_SECURE_TRANSPORT)
|
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)
|
||||||
|
# Use ZLIB_ROOT CMake variable if you need to use your own zlib
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
target_link_libraries(ixwebsocket PRIVATE ZLIB::ZLIB)
|
include_directories(${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()
|
||||||
|
|
||||||
|
|
||||||
@ -267,39 +242,23 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
|||||||
target_compile_options(ixwebsocket PRIVATE /MP)
|
target_compile_options(ixwebsocket PRIVATE /MP)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
|
|
||||||
target_include_directories(ixwebsocket PUBLIC
|
target_include_directories(ixwebsocket PUBLIC
|
||||||
$<BUILD_INTERFACE:${IXWEBSOCKET_INCLUDE_DIRS}/>
|
$<BUILD_INTERFACE:${IXWEBSOCKET_INCLUDE_DIRS}/>
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket>
|
$<INSTALL_INTERFACE:include/ixwebsocket>
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (IXWEBSOCKET_INSTALL)
|
|
||||||
install(TARGETS ixwebsocket
|
install(TARGETS ixwebsocket
|
||||||
EXPORT ixwebsocket
|
EXPORT ixwebsocket
|
||||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
ARCHIVE DESTINATION lib
|
||||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
|
PUBLIC_HEADER DESTINATION include/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 lib/cmake/ixwebsocket)
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (USE_WS OR USE_TEST)
|
if (USE_WS OR USE_TEST)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
27
README.md
27
README.md
@ -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
|
||||||
/*
|
/*
|
||||||
@ -24,7 +24,6 @@ It is been used on big mobile video game titles sending and receiving tons of me
|
|||||||
|
|
||||||
#include <ixwebsocket/IXNetSystem.h>
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
#include <ixwebsocket/IXUserAgent.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
@ -35,9 +34,6 @@ int main()
|
|||||||
// Our websocket object
|
// Our websocket object
|
||||||
ix::WebSocket webSocket;
|
ix::WebSocket webSocket;
|
||||||
|
|
||||||
// Connect to a server with encryption
|
|
||||||
// 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);
|
||||||
|
|
||||||
@ -57,12 +53,6 @@ int main()
|
|||||||
std::cout << "Connection established" << std::endl;
|
std::cout << "Connection established" << std::endl;
|
||||||
std::cout << "> " << std::flush;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -101,16 +91,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 +104,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.
|
||||||
@ -138,6 +124,7 @@ To check the performance of a websocket library, you can look at the [autoroute]
|
|||||||
| 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] |
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
22
appveyor.yml
Normal file
22
appveyor.yml
Normal 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
|
@ -2,69 +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
|
|
||||||
|
|
||||||
(library/cmake) Compatible with MbedTLS 3 + fix a bug on Windows where the incorrect remote port is computed (#320)
|
|
||||||
|
|
||||||
## [11.3.0] - 2021-09-20
|
|
||||||
|
|
||||||
(library/cmake) Only find OpenSSL, MbedTLS, zlib if they have not already been found, make CMake install optional (#317) + Use GNUInstallDirs in cmake (#318)
|
|
||||||
|
|
||||||
## [11.2.10] - 2021-07-27
|
|
||||||
|
|
||||||
(ws) bump CLI command line parsing library from 1.8 to 2.0
|
|
||||||
|
|
||||||
## [11.2.9] - 2021-06-08
|
|
||||||
|
|
||||||
(ws) ws connect has a -g option to gzip decompress messages for API such as the websocket Huobi Global.
|
|
||||||
|
|
||||||
## [11.2.8] - 2021-06-03
|
|
||||||
|
|
||||||
(websocket client + server) WebSocketMessage class tweak to fix unsafe patterns
|
|
||||||
|
|
||||||
## [11.2.7] - 2021-05-27
|
|
||||||
|
|
||||||
(websocket server) Handle and accept firefox browser special upgrade value (keep-alive, Upgrade)
|
|
||||||
|
|
||||||
## [11.2.6] - 2021-05-18
|
|
||||||
|
|
||||||
(Windows) move EINVAL (re)definition from IXSocket.h to IXNetSystem.h (fix #289)
|
|
||||||
|
|
||||||
## [11.2.5] - 2021-04-04
|
|
||||||
|
|
||||||
(http client) DEL is not an HTTP method name, but DELETE is
|
|
||||||
|
|
||||||
## [11.2.4] - 2021-03-25
|
## [11.2.4] - 2021-03-25
|
||||||
|
|
||||||
(cmake) install IXUniquePtr.h
|
(cmake) install IXUniquePtr.h
|
||||||
|
@ -20,7 +20,7 @@ Options for building:
|
|||||||
* `-DBUILD_SHARED_LIBS=ON` will build the unittest as a shared libary instead of a static library, which is the default
|
* `-DBUILD_SHARED_LIBS=ON` will build the unittest as a shared libary instead of a static library, which is the default
|
||||||
* `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension
|
* `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension
|
||||||
* `-DUSE_TLS=1` will enable TLS support
|
* `-DUSE_TLS=1` will enable TLS support
|
||||||
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows). When using a custom version of openssl (say a prebuilt version, odd runtime problems can happens, as in #319, and special cmake trickery will be required (see this [comment](https://github.com/machinezone/IXWebSocket/issues/175#issuecomment-620231032))
|
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows)
|
||||||
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support
|
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support
|
||||||
* `-DUSE_WS=1` will build the ws interactive command line tool
|
* `-DUSE_WS=1` will build the ws interactive command line tool
|
||||||
* `-DUSE_TEST=1` will build the unittest
|
* `-DUSE_TEST=1` will build the unittest
|
||||||
@ -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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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`.
|
||||||
|
131
docs/index.md
131
docs/index.md
@ -1,66 +1,32 @@
|
|||||||
## 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++
|
||||||
/*
|
|
||||||
* main.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
|
|
||||||
*
|
|
||||||
* On macOS
|
|
||||||
* $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install)
|
|
||||||
* $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
|
|
||||||
* $ ./a.out
|
|
||||||
*
|
|
||||||
* Or use cmake -DBUILD_DEMO=ON option for other platforms
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <ixwebsocket/IXNetSystem.h>
|
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
|
||||||
#include <ixwebsocket/IXUserAgent.h>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
// Required on Windows
|
// Required on Windows
|
||||||
ix::initNetSystem();
|
ix::initNetSystem();
|
||||||
|
|
||||||
// Our websocket object
|
// Our websocket object
|
||||||
ix::WebSocket webSocket;
|
ix::WebSocket webSocket;
|
||||||
|
|
||||||
// Connect to a server with encryption
|
std::string url("ws://localhost:8080/");
|
||||||
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
|
|
||||||
std::string url("wss://echo.websocket.org");
|
|
||||||
webSocket.setUrl(url);
|
webSocket.setUrl(url);
|
||||||
|
|
||||||
std::cout << "Connecting to " << url << "..." << std::endl;
|
// Setup a callback to be fired when a message or an event (open, close, error) is received
|
||||||
|
|
||||||
// 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)
|
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||||
{
|
{
|
||||||
if (msg->type == ix::WebSocketMessageType::Message)
|
if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
std::cout << "received message: " << msg->str << std::endl;
|
std::cout << 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -70,42 +36,19 @@ int main()
|
|||||||
|
|
||||||
// Send a message to the server (default to TEXT mode)
|
// Send a message to the server (default to TEXT mode)
|
||||||
webSocket.send("hello world");
|
webSocket.send("hello world");
|
||||||
|
|
||||||
// Display a prompt
|
|
||||||
std::cout << "> " << std::flush;
|
|
||||||
|
|
||||||
std::string text;
|
|
||||||
// Read text from the console and send messages in text mode.
|
|
||||||
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
|
|
||||||
while (std::getline(std::cin, text))
|
|
||||||
{
|
|
||||||
webSocket.send(text);
|
|
||||||
std::cout << "> " << std::flush;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
|
||||||
|
@ -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,14 +372,15 @@ 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<ConnectionState> connectionState,
|
||||||
|
WebSocket& webSocket,
|
||||||
|
const WebSocketMessagePtr& msg)
|
||||||
|
{
|
||||||
// The ConnectionState object contains information about the connection,
|
// The ConnectionState object contains information about the connection,
|
||||||
// at this point only the client ip address and the port.
|
// at this point only the client ip address and the port.
|
||||||
std::cout << "Remote ip: " << connectionState->getRemoteIp() << std::endl;
|
std::cout << "Remote ip: " << connectionState->getRemoteIp();
|
||||||
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
@ -411,7 +398,7 @@ server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connec
|
|||||||
std::cout << "Headers:" << std::endl;
|
std::cout << "Headers:" << std::endl;
|
||||||
for (auto it : msg->openInfo.headers)
|
for (auto it : msg->openInfo.headers)
|
||||||
{
|
{
|
||||||
std::cout << "\t" << it.first << ": " << it.second << std::endl;
|
std::cout << it.first << ": " << it.second << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
@ -420,11 +407,9 @@ server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connec
|
|||||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
// All connected clients are available in an std::set. See the broadcast cpp example.
|
||||||
// Second parameter tells whether we are sending the message in binary or text mode.
|
// Second parameter tells whether we are sending the message in binary or text mode.
|
||||||
// Here we send it in the same mode as it was received.
|
// Here we send it in the same mode as it was received.
|
||||||
std::cout << "Received: " << msg->str << std::endl;
|
|
||||||
|
|
||||||
webSocket.send(msg->str, msg->binary);
|
webSocket.send(msg->str, msg->binary);
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
@ -445,17 +430,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 +509,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 +600,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`.
|
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
@PACKAGE_INIT@
|
|
||||||
|
|
||||||
include(CMakeFindDependencyMacro)
|
|
||||||
|
|
||||||
if (@USE_ZLIB@)
|
|
||||||
find_dependency(ZLIB)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-targets.cmake")
|
|
@ -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@
|
|
@ -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_ */
|
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
{
|
||||||
|
contentLength = std::stoi(headers["Content-Length"]);
|
||||||
|
}
|
||||||
|
catch (std::exception)
|
||||||
{
|
{
|
||||||
const char* p = headers["Content-Length"].c_str();
|
|
||||||
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(
|
return std::make_tuple(
|
||||||
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
|
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
|
||||||
}
|
}
|
||||||
contentLength = val;
|
|
||||||
}
|
|
||||||
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(
|
||||||
|
@ -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>;
|
||||||
|
@ -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>
|
||||||
@ -21,11 +20,10 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
|
|
||||||
const std::string HttpClient::kPost = "POST";
|
const std::string HttpClient::kPost = "POST";
|
||||||
const std::string HttpClient::kGet = "GET";
|
const std::string HttpClient::kGet = "GET";
|
||||||
const std::string HttpClient::kHead = "HEAD";
|
const std::string HttpClient::kHead = "HEAD";
|
||||||
const std::string HttpClient::kDelete = "DELETE";
|
const std::string HttpClient::kDel = "DEL";
|
||||||
const std::string HttpClient::kPut = "PUT";
|
const std::string HttpClient::kPut = "PUT";
|
||||||
const std::string HttpClient::kPatch = "PATCH";
|
const std::string HttpClient::kPatch = "PATCH";
|
||||||
|
|
||||||
@ -140,9 +138,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 +172,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";
|
||||||
@ -197,24 +189,18 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set a default Accept header if none is present
|
// Set a default Accept header if none is present
|
||||||
if (args->extraHeaders.find("Accept") == args->extraHeaders.end())
|
if (headers.find("Accept") == headers.end())
|
||||||
{
|
{
|
||||||
ss << "Accept: */*"
|
ss << "Accept: */*"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a default User agent if none is present
|
// Set a default User agent if none is present
|
||||||
if (args->extraHeaders.find("User-Agent") == args->extraHeaders.end())
|
if (headers.find("User-Agent") == headers.end())
|
||||||
{
|
{
|
||||||
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 +240,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 +259,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 +276,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 +295,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 +332,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,30 +398,24 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args->onChunkCallback)
|
|
||||||
{
|
|
||||||
payload.reserve(contentLength);
|
|
||||||
payload += chunkResult.second;
|
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 +423,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 +430,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 +450,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args->onChunkCallback)
|
|
||||||
{
|
|
||||||
payload.reserve(payload.size() + (size_t) chunkSize);
|
|
||||||
payload += chunkResult.second;
|
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,
|
||||||
@ -592,9 +557,9 @@ namespace ix
|
|||||||
return request(url, kHead, std::string(), args);
|
return request(url, kHead, std::string(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::Delete(const std::string& url, HttpRequestArgsPtr args)
|
HttpResponsePtr HttpClient::del(const std::string& url, HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
return request(url, kDelete, std::string(), args);
|
return request(url, kDel, std::string(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::request(const std::string& url,
|
HttpResponsePtr HttpClient::request(const std::string& url,
|
||||||
|
@ -30,7 +30,7 @@ namespace ix
|
|||||||
|
|
||||||
HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args);
|
HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args);
|
||||||
HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args);
|
HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args);
|
||||||
HttpResponsePtr Delete(const std::string& url, HttpRequestArgsPtr args);
|
HttpResponsePtr del(const std::string& url, HttpRequestArgsPtr args);
|
||||||
|
|
||||||
HttpResponsePtr post(const std::string& url,
|
HttpResponsePtr post(const std::string& url,
|
||||||
const HttpParameters& httpParameters,
|
const HttpParameters& httpParameters,
|
||||||
@ -94,7 +94,7 @@ namespace ix
|
|||||||
const static std::string kPost;
|
const static std::string kPost;
|
||||||
const static std::string kGet;
|
const static std::string kGet;
|
||||||
const static std::string kHead;
|
const static std::string kHead;
|
||||||
const static std::string kDelete;
|
const static std::string kDel;
|
||||||
const static std::string kPut;
|
const static std::string kPut;
|
||||||
const static std::string kPatch;
|
const static std::string kPatch;
|
||||||
|
|
||||||
|
@ -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 (request->headers["Upgrade"] == "websocket")
|
|
||||||
{
|
|
||||||
WebSocketServer::handleUpgrade(std::move(socket), connectionState, request);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto response = _onConnectionCallback(request, connectionState);
|
|
||||||
if (!Http::sendResponse(response, socket))
|
if (!Http::sendResponse(response, socket))
|
||||||
{
|
{
|
||||||
logError("Cannot send response");
|
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
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
|
@ -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,118 +42,9 @@ 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
|
||||||
|
|
||||||
if (event && *event)
|
|
||||||
{
|
|
||||||
HANDLE interruptEvent = reinterpret_cast<HANDLE>(*event);
|
|
||||||
*event = nullptr; // the event wasn't signaled yet
|
|
||||||
|
|
||||||
if (nfds < 0 || nfds >= MAXIMUM_WAIT_OBJECTS - 1)
|
|
||||||
{
|
|
||||||
WSASetLastError(WSAEINVAL);
|
|
||||||
return SOCKET_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
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->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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
*event = reinterpret_cast<void*>(interruptEvent);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
if (event && *event) *event = nullptr;
|
|
||||||
|
|
||||||
socket_t maxfd = 0;
|
socket_t maxfd = 0;
|
||||||
fd_set readfds, writefds, errorfds;
|
fd_set readfds, writefds, errorfds;
|
||||||
FD_ZERO(&readfds);
|
FD_ZERO(&readfds);
|
||||||
@ -267,11 +102,9 @@ namespace ix
|
|||||||
fd->revents |= POLLERR;
|
fd->revents |= POLLERR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (event && *event) *event = nullptr;
|
|
||||||
|
|
||||||
|
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;
|
||||||
@ -360,7 +193,6 @@ namespace ix
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_WIN32) && defined(__GNUC__)
|
|
||||||
static int hexval(unsigned c)
|
static int hexval(unsigned c)
|
||||||
{
|
{
|
||||||
if (c - '0' < 10) return c - '0';
|
if (c - '0' < 10) return c - '0';
|
||||||
@ -368,7 +200,6 @@ namespace ix
|
|||||||
if (c - 'a' < 6) return c - 'a' + 10;
|
if (c - 'a' < 6) return c - 'a' + 10;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// mingw does not have inet_pton, which were taken as is from the musl C library.
|
// mingw does not have inet_pton, which were taken as is from the musl C library.
|
||||||
@ -445,17 +276,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
|
||||||
|
@ -6,43 +6,23 @@
|
|||||||
|
|
||||||
#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 EAGAIN
|
|
||||||
#undef EINPROGRESS
|
|
||||||
#undef EBADF
|
|
||||||
#undef EINVAL
|
|
||||||
|
|
||||||
// map to WSA error codes
|
|
||||||
#define EWOULDBLOCK WSAEWOULDBLOCK
|
|
||||||
#define EAGAIN WSATRY_AGAIN
|
|
||||||
#define EINPROGRESS WSAEINPROGRESS
|
|
||||||
#define EBADF WSAEBADF
|
|
||||||
#define EINVAL WSAEINVAL
|
|
||||||
|
|
||||||
// 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 +64,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
|
||||||
|
@ -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&)>;
|
|
||||||
}
|
}
|
||||||
|
@ -45,9 +45,4 @@ namespace ix
|
|||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* SelectInterrupt::getEvent() const
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
@ -34,12 +34,8 @@ namespace ix
|
|||||||
|
|
||||||
SelectInterruptPipe::~SelectInterruptPipe()
|
SelectInterruptPipe::~SelectInterruptPipe()
|
||||||
{
|
{
|
||||||
if (-1 != _fildes[kPipeReadIndex]) {
|
|
||||||
::close(_fildes[kPipeReadIndex]);
|
::close(_fildes[kPipeReadIndex]);
|
||||||
}
|
|
||||||
if (-1 != _fildes[kPipeWriteIndex]) {
|
|
||||||
::close(_fildes[kPipeWriteIndex]);
|
::close(_fildes[kPipeWriteIndex]);
|
||||||
}
|
|
||||||
_fildes[kPipeReadIndex] = -1;
|
_fildes[kPipeReadIndex] = -1;
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
_fildes[kPipeWriteIndex] = -1;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
}
|
||||||
|
else if (interruptFd != -1 && fds[1].revents & POLLIN)
|
||||||
{
|
{
|
||||||
// Emulation mode: SelectInterrupt neither supports fd nor events
|
uint64_t value = selectInterrupt->read();
|
||||||
|
|
||||||
// Check the selectInterrupt for requests
|
if (value == SelectInterrupt::kSendRequest)
|
||||||
readSelectInterruptRequest(selectInterrupt, &pollResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ((interruptFd != -1 && fds[1].revents & POLLIN) || (interruptEvent != nullptr && event != nullptr))
|
|
||||||
{
|
{
|
||||||
// The InterruptEvent was signaled
|
pollResult = PollResultType::SendRequest;
|
||||||
readSelectInterruptRequest(selectInterrupt, &pollResult);
|
}
|
||||||
|
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,29 +376,20 @@ 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)
|
|
||||||
{
|
|
||||||
std::string chunk(readBuffer.begin(), readBuffer.begin() + ret);
|
|
||||||
onChunkCallback(chunk);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
|
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
|
||||||
}
|
}
|
||||||
bytesRead += ret;
|
|
||||||
}
|
|
||||||
else if (ret <= 0 && !Socket::isWaitNeeded())
|
else if (ret <= 0 && !Socket::isWaitNeeded())
|
||||||
{
|
{
|
||||||
const std::string errorMsg("Recv Error");
|
const std::string errorMsg("Recv Error");
|
||||||
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
|
||||||
|
@ -7,21 +7,28 @@
|
|||||||
#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
|
|
||||||
|
#undef EWOULDBLOCK
|
||||||
|
#undef EAGAIN
|
||||||
|
#undef EINPROGRESS
|
||||||
|
#undef EBADF
|
||||||
|
#undef EINVAL
|
||||||
|
|
||||||
|
// map to WSA error codes
|
||||||
|
#define EWOULDBLOCK WSAEWOULDBLOCK
|
||||||
|
#define EAGAIN WSATRY_AGAIN
|
||||||
|
#define EINPROGRESS WSAEINPROGRESS
|
||||||
|
#define EBADF WSAEBADF
|
||||||
|
#define EINVAL WSAEINVAL
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
@ -50,7 +57,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 +82,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 +97,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;
|
||||||
|
@ -205,8 +205,6 @@ 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);
|
||||||
|
|
||||||
if (!_tlsOptions.disable_hostname_validation)
|
|
||||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
||||||
|
|
||||||
if (_tlsOptions.isPeerVerifyDisabled())
|
if (_tlsOptions.isPeerVerifyDisabled())
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
@ -140,11 +132,7 @@ namespace ix
|
|||||||
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
|
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#ifdef IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3
|
|
||||||
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "", mbedtls_ctr_drbg_random, &_ctr_drbg) < 0)
|
|
||||||
#else
|
|
||||||
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
|
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
|
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
|
||||||
return false;
|
return false;
|
||||||
@ -203,14 +191,11 @@ 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";
|
errMsg = "SNI setup failed";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -359,11 +344,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;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#include <mbedtls/debug.h>
|
#include <mbedtls/debug.h>
|
||||||
#include <mbedtls/entropy.h>
|
#include <mbedtls/entropy.h>
|
||||||
#include <mbedtls/error.h>
|
#include <mbedtls/error.h>
|
||||||
#include <mbedtls/net_sockets.h>
|
#include <mbedtls/net.h>
|
||||||
#include <mbedtls/platform.h>
|
#include <mbedtls/platform.h>
|
||||||
#include <mbedtls/x509.h>
|
#include <mbedtls/x509.h>
|
||||||
#include <mbedtls/x509_crt.h>
|
#include <mbedtls/x509_crt.h>
|
||||||
|
@ -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* param = SSL_get0_param(_ssl_connection);
|
||||||
X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size());
|
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
|
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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())
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
#include "IXUuid.h"
|
#include "IXUuid.h"
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
@ -13,13 +13,6 @@
|
|||||||
#include "IXWebSocketHandshake.h"
|
#include "IXWebSocketHandshake.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
const std::string emptyMsg;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -40,15 +33,12 @@ 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,
|
"",
|
||||||
wireSize,
|
wireSize,
|
||||||
WebSocketErrorInfo(),
|
WebSocketErrorInfo(),
|
||||||
WebSocketOpenInfo(),
|
WebSocketOpenInfo(),
|
||||||
@ -79,7 +69,7 @@ namespace ix
|
|||||||
_extraHeaders = headers;
|
_extraHeaders = headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string WebSocket::getUrl() const
|
const std::string& WebSocket::getUrl() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
return _url;
|
return _url;
|
||||||
@ -98,23 +88,12 @@ namespace ix
|
|||||||
_socketTLSOptions = socketTLSOptions;
|
_socketTLSOptions = socketTLSOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WebSocketPerMessageDeflateOptions WebSocket::getPerMessageDeflateOptions() const
|
const WebSocketPerMessageDeflateOptions& WebSocket::getPerMessageDeflateOptions() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
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 +188,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 +198,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)
|
||||||
{
|
{
|
||||||
@ -238,7 +217,7 @@ namespace ix
|
|||||||
|
|
||||||
_onMessageCallback(ix::make_unique<WebSocketMessage>(
|
_onMessageCallback(ix::make_unique<WebSocketMessage>(
|
||||||
WebSocketMessageType::Open,
|
WebSocketMessageType::Open,
|
||||||
emptyMsg,
|
"",
|
||||||
0,
|
0,
|
||||||
WebSocketErrorInfo(),
|
WebSocketErrorInfo(),
|
||||||
WebSocketOpenInfo(status.uri, status.headers, status.protocol),
|
WebSocketOpenInfo(status.uri, status.headers, status.protocol),
|
||||||
@ -247,7 +226,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 +234,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 +243,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;
|
||||||
@ -273,7 +251,7 @@ namespace ix
|
|||||||
|
|
||||||
_onMessageCallback(
|
_onMessageCallback(
|
||||||
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
|
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
|
||||||
emptyMsg,
|
"",
|
||||||
0,
|
0,
|
||||||
WebSocketErrorInfo(),
|
WebSocketErrorInfo(),
|
||||||
WebSocketOpenInfo(status.uri, status.headers),
|
WebSocketOpenInfo(status.uri, status.headers),
|
||||||
@ -282,7 +260,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;
|
||||||
@ -360,7 +338,7 @@ namespace ix
|
|||||||
connectErr.http_status = status.http_status;
|
connectErr.http_status = status.http_status;
|
||||||
|
|
||||||
_onMessageCallback(ix::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
|
_onMessageCallback(ix::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
|
||||||
emptyMsg,
|
"",
|
||||||
0,
|
0,
|
||||||
connectErr,
|
connectErr,
|
||||||
WebSocketOpenInfo(),
|
WebSocketOpenInfo(),
|
||||||
@ -370,11 +348,8 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::run()
|
void WebSocket::run()
|
||||||
{
|
|
||||||
if (_autoThreadName)
|
|
||||||
{
|
{
|
||||||
setThreadName(getUrl());
|
setThreadName(getUrl());
|
||||||
}
|
|
||||||
|
|
||||||
bool firstConnectionAttempt = true;
|
bool firstConnectionAttempt = true;
|
||||||
|
|
||||||
@ -403,9 +378,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 +461,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 +479,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 +510,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 +587,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
|
||||||
|
@ -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);
|
|
||||||
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);
|
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);
|
||||||
@ -104,9 +92,8 @@ namespace ix
|
|||||||
ReadyState getReadyState() const;
|
ReadyState getReadyState() const;
|
||||||
static std::string readyStateToString(ReadyState readyState);
|
static std::string readyStateToString(ReadyState readyState);
|
||||||
|
|
||||||
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
|
||||||
|
@ -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";
|
||||||
@ -206,9 +204,6 @@ namespace ix
|
|||||||
// Check the value of the connection field
|
// Check the value of the connection field
|
||||||
// Some websocket servers (Go/Gorilla?) send lowercase values for the
|
// Some websocket servers (Go/Gorilla?) send lowercase values for the
|
||||||
// connection header, so do a case insensitive comparison
|
// connection header, so do a case insensitive comparison
|
||||||
//
|
|
||||||
// See https://github.com/apache/thrift/commit/7c4bdf9914fcba6c89e0f69ae48b9675578f084a
|
|
||||||
//
|
|
||||||
if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
|
if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@ -247,26 +242,13 @@ 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;
|
|
||||||
std::string uri;
|
|
||||||
std::string httpVersion;
|
|
||||||
|
|
||||||
if (request)
|
|
||||||
{
|
|
||||||
method = request->method;
|
|
||||||
uri = request->uri;
|
|
||||||
httpVersion = request->version;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Read first line
|
// Read first line
|
||||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||||
auto lineValid = lineResult.first;
|
auto lineValid = lineResult.first;
|
||||||
@ -279,10 +261,9 @@ namespace ix
|
|||||||
|
|
||||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||||
auto requestLine = Http::parseRequestLine(line);
|
auto requestLine = Http::parseRequestLine(line);
|
||||||
method = std::get<0>(requestLine);
|
auto method = std::get<0>(requestLine);
|
||||||
uri = std::get<1>(requestLine);
|
auto uri = std::get<1>(requestLine);
|
||||||
httpVersion = std::get<2>(requestLine);
|
auto httpVersion = std::get<2>(requestLine);
|
||||||
}
|
|
||||||
|
|
||||||
if (method != "GET")
|
if (method != "GET")
|
||||||
{
|
{
|
||||||
@ -295,23 +276,15 @@ namespace ix
|
|||||||
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
if (request)
|
|
||||||
{
|
|
||||||
headers = request->headers;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Retrieve and validate HTTP headers
|
// Retrieve and validate HTTP headers
|
||||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||||
auto headersValid = result.first;
|
auto headersValid = result.first;
|
||||||
headers = result.second;
|
auto 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())
|
||||||
{
|
{
|
||||||
@ -323,8 +296,7 @@ namespace ix
|
|||||||
return sendErrorResponse(400, "Missing Upgrade header");
|
return sendErrorResponse(400, "Missing Upgrade header");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket") &&
|
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket"))
|
||||||
headers["Upgrade"] != "keep-alive, Upgrade") // special case for firefox
|
|
||||||
{
|
{
|
||||||
return sendErrorResponse(400,
|
return sendErrorResponse(400,
|
||||||
"Invalid Upgrade header, "
|
"Invalid Upgrade header, "
|
||||||
|
@ -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);
|
||||||
|
@ -42,18 +42,6 @@ namespace ix
|
|||||||
{
|
{
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Deleted overload to prevent binding `str` to a temporary, which would cause
|
|
||||||
* undefined behavior since class members don't extend lifetime beyond the constructor call.
|
|
||||||
*/
|
|
||||||
WebSocketMessage(WebSocketMessageType t,
|
|
||||||
std::string&& s,
|
|
||||||
size_t w,
|
|
||||||
WebSocketErrorInfo e,
|
|
||||||
WebSocketOpenInfo o,
|
|
||||||
WebSocketCloseInfo c,
|
|
||||||
bool b = false) = delete;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using WebSocketMessagePtr = std::unique_ptr<WebSocketMessage>;
|
using WebSocketMessagePtr = std::unique_ptr<WebSocketMessage>;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -14,12 +14,12 @@ namespace ix
|
|||||||
{
|
{
|
||||||
/// Default values as defined in the RFC
|
/// Default values as defined in the RFC
|
||||||
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultServerMaxWindowBits = 15;
|
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultServerMaxWindowBits = 15;
|
||||||
static const uint8_t minServerMaxWindowBits = 8;
|
static const int minServerMaxWindowBits = 8;
|
||||||
static const uint8_t maxServerMaxWindowBits = 15;
|
static const int maxServerMaxWindowBits = 15;
|
||||||
|
|
||||||
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15;
|
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15;
|
||||||
static const uint8_t minClientMaxWindowBits = 8;
|
static const int minClientMaxWindowBits = 8;
|
||||||
static const uint8_t maxClientMaxWindowBits = 15;
|
static const int maxClientMaxWindowBits = 15;
|
||||||
|
|
||||||
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
|
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
|
||||||
bool enabled,
|
bool enabled,
|
||||||
@ -85,7 +85,11 @@ namespace ix
|
|||||||
|
|
||||||
if (startsWith(token, "server_max_window_bits="))
|
if (startsWith(token, "server_max_window_bits="))
|
||||||
{
|
{
|
||||||
uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10);
|
std::string val = token.substr(token.find_last_of("=") + 1);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << val;
|
||||||
|
int x;
|
||||||
|
ss >> x;
|
||||||
|
|
||||||
// Sanitize values to be in the proper range [8, 15] in
|
// Sanitize values to be in the proper range [8, 15] in
|
||||||
// case a server would give us bogus values
|
// case a server would give us bogus values
|
||||||
@ -95,7 +99,11 @@ namespace ix
|
|||||||
|
|
||||||
if (startsWith(token, "client_max_window_bits="))
|
if (startsWith(token, "client_max_window_bits="))
|
||||||
{
|
{
|
||||||
uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10);
|
std::string val = token.substr(token.find_last_of("=") + 1);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << val;
|
||||||
|
int x;
|
||||||
|
ss >> x;
|
||||||
|
|
||||||
// Sanitize values to be in the proper range [8, 15] in
|
// Sanitize values to be in the proper range [8, 15] in
|
||||||
// case a server would give us bogus values
|
// case a server would give us bogus values
|
||||||
@ -127,8 +135,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";
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -40,8 +39,8 @@ namespace ix
|
|||||||
bool _enabled;
|
bool _enabled;
|
||||||
bool _clientNoContextTakeover;
|
bool _clientNoContextTakeover;
|
||||||
bool _serverNoContextTakeover;
|
bool _serverNoContextTakeover;
|
||||||
uint8_t _clientMaxWindowBits;
|
int _clientMaxWindowBits;
|
||||||
uint8_t _serverMaxWindowBits;
|
int _serverMaxWindowBits;
|
||||||
|
|
||||||
void sanitizeClientMaxWindowBits();
|
void sanitizeClientMaxWindowBits();
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -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,11 +176,9 @@ 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();
|
auto remoteIp = connectionState->getRemoteIp();
|
||||||
if (msg->type == ix::WebSocketMessageType::Message)
|
if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
@ -206,6 +191,7 @@ namespace ix
|
|||||||
// Make sure the OS send buffer is flushed before moving on
|
// Make sure the OS send buffer is flushed before moving on
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
size_t bufferedAmount = client->bufferedAmount();
|
||||||
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);
|
||||||
@ -226,19 +212,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
|
||||||
|
@ -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
|
||||||
|
@ -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,52 +248,14 @@ 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;
|
|
||||||
if (!_setCustomMessage)
|
|
||||||
{
|
|
||||||
ss << "::" << _pingIntervalSecs << "s"
|
|
||||||
<< "::" << _pingCount++;
|
<< "::" << _pingCount++;
|
||||||
}
|
|
||||||
if (pingMessage == SendMessageKind::Ping)
|
|
||||||
{
|
|
||||||
return sendPing(ss.str());
|
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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -6,4 +6,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define IX_WEBSOCKET_VERSION "11.4.6"
|
#define IX_WEBSOCKET_VERSION "11.2.4"
|
||||||
|
10
main.cpp
10
main.cpp
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
#include <ixwebsocket/IXNetSystem.h>
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
#include <ixwebsocket/IXUserAgent.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
@ -26,12 +25,9 @@ int main()
|
|||||||
// Our websocket object
|
// Our websocket object
|
||||||
ix::WebSocket webSocket;
|
ix::WebSocket webSocket;
|
||||||
|
|
||||||
// Connect to a server with encryption
|
|
||||||
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
|
|
||||||
std::string url("wss://echo.websocket.org");
|
std::string url("wss://echo.websocket.org");
|
||||||
webSocket.setUrl(url);
|
webSocket.setUrl(url);
|
||||||
|
|
||||||
std::cout << ix::userAgent() << std::endl;
|
|
||||||
std::cout << "Connecting to " << url << "..." << std::endl;
|
std::cout << "Connecting to " << url << "..." << std::endl;
|
||||||
|
|
||||||
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
||||||
@ -48,12 +44,6 @@ int main()
|
|||||||
std::cout << "Connection established" << std::endl;
|
std::cout << "Connection established" << std::endl;
|
||||||
std::cout << "> " << std::flush;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
38
makefile.dev
38
makefile.dev
@ -13,54 +13,46 @@ 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=ON -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
|
||||||
# Linux for the SSL backend, which works great.
|
# Linux for the SSL backend, which works great.
|
||||||
ws_mbedtls_install:
|
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_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_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_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_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
|
||||||
|
|
||||||
ws_install_release:
|
ws_install_release:
|
||||||
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_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
|
||||||
|
|
||||||
ws_openssl_install:
|
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_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_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_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_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
|
||||||
@ -119,27 +111,27 @@ test_server:
|
|||||||
(cd test && npm i ws && node broadcast-server.js)
|
(cd test && npm i ws && node broadcast-server.js)
|
||||||
|
|
||||||
test:
|
test:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 ..)
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 ..)
|
||||||
(cd build ; ninja)
|
(cd build ; ninja)
|
||||||
(cd build ; ninja -v test)
|
(cd build ; ninja -v test)
|
||||||
|
|
||||||
test_asan:
|
test_asan:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer")
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer")
|
||||||
(cd build ; ninja)
|
(cd build ; ninja)
|
||||||
(cd build ; ctest -V .)
|
(cd build ; ctest -V .)
|
||||||
|
|
||||||
test_tsan_mbedtls:
|
test_tsan_mbedtls:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
||||||
(cd build ; ninja)
|
(cd build ; ninja)
|
||||||
(cd build ; ninja test)
|
(cd build ; ninja test)
|
||||||
|
|
||||||
test_tsan_openssl:
|
test_tsan_openssl:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
mkdir -p build && (cd build ; cmake -GNinja --DCMAKE_UNITY_BUILD=ON DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
||||||
(cd build ; ninja)
|
(cd build ; ninja)
|
||||||
(cd build ; ninja test)
|
(cd build ; ninja test)
|
||||||
|
|
||||||
test_tsan_sectransport:
|
test_tsan_sectransport:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
mkdir -p build && (cd build ; cmake -GNinja --DCMAKE_UNITY_BUILD=ON DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
||||||
(cd build ; ninja)
|
(cd build ; ninja)
|
||||||
(cd build ; ninja test)
|
(cd build ; ninja test)
|
||||||
|
|
||||||
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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 =
|
||||||
|
5132
third_party/cli11/CLI11.hpp
vendored
5132
third_party/cli11/CLI11.hpp
vendored
File diff suppressed because it is too large
Load Diff
2
ws/package-lock.json
generated
2
ws/package-lock.json
generated
@ -8,7 +8,7 @@
|
|||||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": ">=6.2.2",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz",
|
||||||
"integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==",
|
"integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
66
ws/ws.cpp
66
ws/ws.cpp
@ -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;
|
||||||
@ -608,8 +632,7 @@ namespace ix
|
|||||||
uint32_t maxWaitBetweenReconnectionRetries,
|
uint32_t maxWaitBetweenReconnectionRetries,
|
||||||
const ix::SocketTLSOptions& tlsOptions,
|
const ix::SocketTLSOptions& tlsOptions,
|
||||||
const std::string& subprotocol,
|
const std::string& subprotocol,
|
||||||
int pingIntervalSecs,
|
int pingIntervalSecs);
|
||||||
bool decompressGzipMessages);
|
|
||||||
|
|
||||||
void subscribe(const std::string& channel);
|
void subscribe(const std::string& channel);
|
||||||
void start();
|
void start();
|
||||||
@ -634,7 +657,6 @@ namespace ix
|
|||||||
bool _binaryMode;
|
bool _binaryMode;
|
||||||
std::atomic<int> _receivedBytes;
|
std::atomic<int> _receivedBytes;
|
||||||
std::atomic<int> _sentBytes;
|
std::atomic<int> _sentBytes;
|
||||||
bool _decompressGzipMessages;
|
|
||||||
|
|
||||||
void log(const std::string& msg);
|
void log(const std::string& msg);
|
||||||
WebSocketHttpHeaders parseHeaders(const std::string& data);
|
WebSocketHttpHeaders parseHeaders(const std::string& data);
|
||||||
@ -648,14 +670,12 @@ namespace ix
|
|||||||
uint32_t maxWaitBetweenReconnectionRetries,
|
uint32_t maxWaitBetweenReconnectionRetries,
|
||||||
const ix::SocketTLSOptions& tlsOptions,
|
const ix::SocketTLSOptions& tlsOptions,
|
||||||
const std::string& subprotocol,
|
const std::string& subprotocol,
|
||||||
int pingIntervalSecs,
|
int pingIntervalSecs)
|
||||||
bool decompressGzipMessages)
|
|
||||||
: _url(url)
|
: _url(url)
|
||||||
, _disablePerMessageDeflate(disablePerMessageDeflate)
|
, _disablePerMessageDeflate(disablePerMessageDeflate)
|
||||||
, _binaryMode(binaryMode)
|
, _binaryMode(binaryMode)
|
||||||
, _receivedBytes(0)
|
, _receivedBytes(0)
|
||||||
, _sentBytes(0)
|
, _sentBytes(0)
|
||||||
, _decompressGzipMessages(decompressGzipMessages)
|
|
||||||
{
|
{
|
||||||
if (disableAutomaticReconnection)
|
if (disableAutomaticReconnection)
|
||||||
{
|
{
|
||||||
@ -764,21 +784,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
spdlog::info("Received {} bytes", msg->wireSize);
|
spdlog::info("Received {} bytes", msg->wireSize);
|
||||||
|
|
||||||
std::string payload = msg->str;
|
ss << "ws_connect: received message: " << msg->str;
|
||||||
if (_decompressGzipMessages)
|
|
||||||
{
|
|
||||||
std::string decompressedBytes;
|
|
||||||
if (gzipDecompress(payload, decompressedBytes))
|
|
||||||
{
|
|
||||||
payload = decompressedBytes;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
spdlog::error("Error decompressing: {}", payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ss << "ws_connect: received message: " << payload;
|
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
@ -831,8 +837,7 @@ namespace ix
|
|||||||
uint32_t maxWaitBetweenReconnectionRetries,
|
uint32_t maxWaitBetweenReconnectionRetries,
|
||||||
const ix::SocketTLSOptions& tlsOptions,
|
const ix::SocketTLSOptions& tlsOptions,
|
||||||
const std::string& subprotocol,
|
const std::string& subprotocol,
|
||||||
int pingIntervalSecs,
|
int pingIntervalSecs)
|
||||||
bool decompressGzipMessages)
|
|
||||||
{
|
{
|
||||||
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
|
std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
|
||||||
WebSocketConnect webSocketChat(url,
|
WebSocketConnect webSocketChat(url,
|
||||||
@ -843,8 +848,7 @@ namespace ix
|
|||||||
maxWaitBetweenReconnectionRetries,
|
maxWaitBetweenReconnectionRetries,
|
||||||
tlsOptions,
|
tlsOptions,
|
||||||
subprotocol,
|
subprotocol,
|
||||||
pingIntervalSecs,
|
pingIntervalSecs);
|
||||||
decompressGzipMessages);
|
|
||||||
webSocketChat.start();
|
webSocketChat.start();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
@ -892,8 +896,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 +2466,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,11 +2485,11 @@ 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;
|
||||||
int runCount = 1;
|
int runCount = 1;
|
||||||
bool decompressGzipMessages = false;
|
|
||||||
|
|
||||||
auto addGenericOptions = [&pidfile](CLI::App* app) {
|
auto addGenericOptions = [&pidfile](CLI::App* app) {
|
||||||
app->add_option("--pidfile", pidfile, "Pid file");
|
app->add_option("--pidfile", pidfile, "Pid file");
|
||||||
@ -2501,7 +2508,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");
|
||||||
@ -2546,7 +2552,6 @@ int main(int argc, char** argv)
|
|||||||
"Max Wait Time between reconnection retries");
|
"Max Wait Time between reconnection retries");
|
||||||
connectApp->add_option("--ping_interval", pingIntervalSecs, "Interval between sending pings");
|
connectApp->add_option("--ping_interval", pingIntervalSecs, "Interval between sending pings");
|
||||||
connectApp->add_option("--subprotocol", subprotocol, "Subprotocol");
|
connectApp->add_option("--subprotocol", subprotocol, "Subprotocol");
|
||||||
connectApp->add_flag("-g", decompressGzipMessages, "Decompress gziped messages");
|
|
||||||
addGenericOptions(connectApp);
|
addGenericOptions(connectApp);
|
||||||
addTLSOptions(connectApp);
|
addTLSOptions(connectApp);
|
||||||
|
|
||||||
@ -2735,8 +2740,7 @@ int main(int argc, char** argv)
|
|||||||
maxWaitBetweenReconnectionRetries,
|
maxWaitBetweenReconnectionRetries,
|
||||||
tlsOptions,
|
tlsOptions,
|
||||||
subprotocol,
|
subprotocol,
|
||||||
pingIntervalSecs,
|
pingIntervalSecs);
|
||||||
decompressGzipMessages);
|
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("autoroute"))
|
else if (app.got_subcommand("autoroute"))
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user