Compare commits
105 Commits
Author | SHA1 | Date | |
---|---|---|---|
16eb269e1e | |||
2319dec278 | |||
f1be48aff1 | |||
93fd44813a | |||
54d4d81bf4 | |||
ea207d8199 | |||
e8287e91e4 | |||
c0505ac7fb | |||
1af39bf0eb | |||
2e904801a0 | |||
cc72494b63 | |||
fa9a4660c6 | |||
4773af8f2f | |||
c1403df74a | |||
3912e22b28 | |||
c9d5b4a581 | |||
9f8643032d | |||
0772ef7ef5 | |||
c030a62c8b | |||
931530b101 | |||
6c205b983e | |||
a65b334961 | |||
2de8aafcbc | |||
f075f586e1 | |||
93cb898989 | |||
e4da62547b | |||
2b4c06e6d2 | |||
7337ed34a6 | |||
15355188d5 | |||
8760c87635 | |||
2786631e3b | |||
1b30061a4d | |||
af003fc79b | |||
4f17cd5e74 | |||
b04764489c | |||
fc4a4bfb7c | |||
9e54fd5f1a | |||
1096f62196 | |||
b34d9f6a06 | |||
b21e2506bf | |||
303f99a432 | |||
a42ccea8dd | |||
beb26bc096 | |||
b45980f0f6 | |||
fbca513008 | |||
33ebd00932 | |||
fbe5e74109 | |||
a9f5d5353f | |||
22e0083832 | |||
5632360fbd | |||
20294841b3 | |||
74efdfebba | |||
0ab04f51fe | |||
4ed7968b05 | |||
287e48962f | |||
953c680eee | |||
2802cad8c4 | |||
9f770b10c0 | |||
677f79b0ea | |||
646b18bf28 | |||
0670954faf | |||
2469d7102e | |||
79acb915ce | |||
bad3adb6b4 | |||
e3dd4e60c0 | |||
c70f1d09a8 | |||
4b2b133c10 | |||
cd5fae6a5b | |||
5860c5c80b | |||
36257cbfe4 | |||
68ee57a6a7 | |||
9d79596629 | |||
0b6fd989f5 | |||
1c19a57fef | |||
a2abe861d3 | |||
0f5d15aa11 | |||
ccfd196863 | |||
9b8cfa0a37 | |||
85f6b1e0b7 | |||
64754df66c | |||
71a421eefc | |||
386ef3ab04 | |||
2c4bf8f4bd | |||
3a2c446225 | |||
35630fe7ed | |||
bea582c208 | |||
783d1d92dd | |||
415f6b4832 | |||
13d3300a40 | |||
432f0570f4 | |||
37a054723a | |||
c57cf413fb | |||
f1c106728b | |||
2eb5c9480e | |||
f9d75c9374 | |||
d1cd5e62ac | |||
f3b97097cd | |||
605be72579 | |||
49ff3789b5 | |||
96d61c6e5b | |||
9a23c5aaac | |||
d81e4d4fc0 | |||
bd44d32fdb | |||
b6abc12ecd | |||
2268b743ae |
43
.github/workflows/ccpp.yml
vendored
43
.github/workflows/ccpp.yml
vendored
@ -9,8 +9,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make test
|
||||
run: make test
|
||||
- name: make test_make
|
||||
run: make test_make
|
||||
|
||||
mac_tsan_sectransport:
|
||||
runs-on: macOS-latest
|
||||
@ -24,7 +24,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: install openssl
|
||||
run: brew install openssl
|
||||
run: brew install openssl@1.1
|
||||
- name: make test
|
||||
run: make test_tsan_openssl
|
||||
|
||||
@ -37,7 +37,7 @@ jobs:
|
||||
- name: make test
|
||||
run: make test_tsan_mbedtls
|
||||
|
||||
win:
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
@ -48,6 +48,39 @@ jobs:
|
||||
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 ..
|
||||
- run: cmake --build build
|
||||
|
||||
# Running the unittest does not work
|
||||
# Running the unittest does not work, the binary cannot be found
|
||||
#- run: ../build/test/ixwebsocket_unittest.exe
|
||||
# working-directory: test
|
||||
|
||||
uwp:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||
- run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_CXX_COMPILER=cl.exe -DUSE_TEST=1 ..
|
||||
- run: cmake --build build
|
||||
|
||||
#
|
||||
# Windows with OpenSSL is working but disabled as it takes 13 minutes (10 for openssl) to build with vcpkg
|
||||
#
|
||||
# windows_openssl:
|
||||
# runs-on: windows-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# - uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||
# - run: |
|
||||
# vcpkg install zlib:x64-windows
|
||||
# vcpkg install openssl:x64-windows
|
||||
# - run: |
|
||||
# mkdir build
|
||||
# cd build
|
||||
# cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_OPEN_SSL=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 ..
|
||||
# - run: cmake --build build
|
||||
#
|
||||
# # Running the unittest does not work, the binary cannot be found
|
||||
# #- run: ../build/test/ixwebsocket_unittest.exe
|
||||
# # working-directory: test
|
||||
|
||||
|
2
.github/workflows/mkdocs.yml
vendored
2
.github/workflows/mkdocs.yml
vendored
@ -17,6 +17,8 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install mkdocs
|
||||
pip install mkdocs-material
|
||||
pip install pygments
|
||||
- name: Build doc
|
||||
run: |
|
||||
git pull
|
||||
|
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,7 +1,12 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.3.0
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/pocc/pre-commit-hooks
|
||||
rev: v1.1.1
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [-i, -style=file]
|
||||
|
@ -5,7 +5,7 @@ include(FindPackageHandleStandardArgs)
|
||||
find_path(JSONCPP_INCLUDE_DIRS json/json.h)
|
||||
find_library(JSONCPP_LIBRARY jsoncpp)
|
||||
|
||||
find_package_handle_standard_args(JSONCPP
|
||||
find_package_handle_standard_args(JsonCpp
|
||||
FOUND_VAR
|
||||
JSONCPP_FOUND
|
||||
REQUIRED_VARS
|
||||
|
117
CMakeLists.txt
117
CMakeLists.txt
@ -12,6 +12,10 @@ set (CMAKE_CXX_STANDARD 14)
|
||||
set (CXX_STANDARD_REQUIRED ON)
|
||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if (UNIX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||
endif()
|
||||
@ -44,13 +48,11 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXWebSocketCloseConstants.cpp
|
||||
ixwebsocket/IXWebSocketHandshake.cpp
|
||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||
ixwebsocket/IXWebSocketMessageQueue.cpp
|
||||
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
||||
ixwebsocket/IXWebSocketServer.cpp
|
||||
ixwebsocket/IXWebSocketTransport.cpp
|
||||
ixwebsocket/LUrlParser.cpp
|
||||
)
|
||||
|
||||
set( IXWEBSOCKET_HEADERS
|
||||
@ -81,10 +83,10 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXWebSocketCloseInfo.h
|
||||
ixwebsocket/IXWebSocketErrorInfo.h
|
||||
ixwebsocket/IXWebSocketHandshake.h
|
||||
ixwebsocket/IXWebSocketHandshakeKeyGen.h
|
||||
ixwebsocket/IXWebSocketHttpHeaders.h
|
||||
ixwebsocket/IXWebSocketInitResult.h
|
||||
ixwebsocket/IXWebSocketMessage.h
|
||||
ixwebsocket/IXWebSocketMessageQueue.h
|
||||
ixwebsocket/IXWebSocketMessageType.h
|
||||
ixwebsocket/IXWebSocketOpenInfo.h
|
||||
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
||||
@ -94,8 +96,6 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXWebSocketServer.h
|
||||
ixwebsocket/IXWebSocketTransport.h
|
||||
ixwebsocket/IXWebSocketVersion.h
|
||||
ixwebsocket/LUrlParser.h
|
||||
ixwebsocket/libwshandshake.hpp
|
||||
)
|
||||
|
||||
if (UNIX)
|
||||
@ -113,30 +113,38 @@ elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp)
|
||||
else()
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
||||
endif()
|
||||
|
||||
option(USE_TLS "Enable TLS support" FALSE)
|
||||
|
||||
if (USE_TLS)
|
||||
if (WIN32)
|
||||
option(USE_MBED_TLS "Use Mbed TLS" ON)
|
||||
else()
|
||||
option(USE_MBED_TLS "Use Mbed TLS" OFF)
|
||||
# default to securetranport on Apple if nothing is configured
|
||||
if (APPLE)
|
||||
if (NOT USE_MBED_TLS AND NOT USE_OPEN_SSL) # unless we want something else
|
||||
set(USE_SECURE_TRANSPORT ON)
|
||||
endif()
|
||||
# default to mbedtls on windows if nothing is configured
|
||||
elseif (WIN32)
|
||||
if (NOT USE_OPEN_SSL) # unless we want something else
|
||||
set(USE_MBED_TLS ON)
|
||||
endif()
|
||||
else() # default to OpenSSL on all other platforms
|
||||
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
|
||||
set(USE_OPEN_SSL ON)
|
||||
endif()
|
||||
endif()
|
||||
option(USE_OPEN_SSL "Use OpenSSL" OFF)
|
||||
|
||||
if (USE_MBED_TLS)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
|
||||
elseif (APPLE AND NOT USE_OPEN_SSL)
|
||||
elseif (USE_SECURE_TRANSPORT)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
||||
else()
|
||||
set(USE_OPEN_SSL ON)
|
||||
elseif (USE_OPEN_SSL)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
||||
else()
|
||||
message(FATAL_ERROR "TLS Configuration error: unknown backend")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -151,55 +159,49 @@ if (USE_TLS)
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS)
|
||||
elseif (USE_OPEN_SSL)
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_OPEN_SSL)
|
||||
elseif (USE_SECURE_TRANSPORT)
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_SECURE_TRANSPORT)
|
||||
else()
|
||||
message(FATAL_ERROR "TLS Configuration error: unknown backend")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS AND NOT USE_OPEN_SSL)
|
||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||
endif()
|
||||
if (USE_TLS)
|
||||
if (USE_OPEN_SSL)
|
||||
message(STATUS "TLS configured to use openssl")
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
# Help finding Homebrew's OpenSSL on macOS
|
||||
if (APPLE)
|
||||
set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib)
|
||||
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
|
||||
|
||||
if (UNIX)
|
||||
find_package(Threads)
|
||||
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
# This is for MacPort OpenSSL 1.0
|
||||
# set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /opt/local/lib/openssl-1.0)
|
||||
# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0)
|
||||
endif()
|
||||
|
||||
if (USE_TLS AND USE_OPEN_SSL)
|
||||
# This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL
|
||||
if (NOT OPENSSL_FOUND)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||
|
||||
# Help finding Homebrew's OpenSSL on macOS
|
||||
if (APPLE)
|
||||
set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib)
|
||||
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
|
||||
endif()
|
||||
add_definitions(${OPENSSL_DEFINITIONS})
|
||||
target_include_directories(ixwebsocket PUBLIC ${OPENSSL_INCLUDE_DIR})
|
||||
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
||||
elseif (USE_MBED_TLS)
|
||||
message(STATUS "TLS configured to use mbedtls")
|
||||
|
||||
if(NOT OPENSSL_FOUND)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
add_definitions(${OPENSSL_DEFINITIONS})
|
||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if (USE_TLS AND USE_MBED_TLS)
|
||||
# FIXME I'm not too sure that this USE_VENDORED_THIRD_PARTY thing works
|
||||
if (USE_VENDORED_THIRD_PARTY)
|
||||
set (ENABLE_PROGRAMS OFF)
|
||||
add_subdirectory(third_party/mbedtls)
|
||||
include_directories(third_party/mbedtls/include)
|
||||
|
||||
target_link_libraries(ixwebsocket mbedtls)
|
||||
else()
|
||||
find_package(MbedTLS REQUIRED)
|
||||
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
|
||||
elseif (USE_SECURE_TRANSPORT)
|
||||
message(STATUS "TLS configured to use secure transport")
|
||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# This ZLIB_FOUND check is to help find a cmake manually configured zlib
|
||||
if (NOT ZLIB_FOUND)
|
||||
find_package(ZLIB)
|
||||
endif()
|
||||
@ -212,6 +214,21 @@ else()
|
||||
target_link_libraries(ixwebsocket zlibstatic)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
|
||||
if (USE_TLS)
|
||||
target_link_libraries(ixwebsocket Crypt32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (UNIX)
|
||||
find_package(Threads)
|
||||
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
|
||||
set( IXWEBSOCKET_INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
@ -45,3 +45,7 @@ IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version.
|
||||
If your company or project is using this library, feel free to open an issue or PR to amend this list.
|
||||
|
||||
- [Machine Zone](https://www.mz.com)
|
||||
- [Tokio](https://gitlab.com/HCInk/tokio), a discord library focused on audio playback with node bindings.
|
||||
- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), a work in progress discord library
|
||||
- [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
|
||||
|
@ -2,7 +2,7 @@ FROM alpine:3.11 as build
|
||||
|
||||
RUN apk add --no-cache \
|
||||
gcc g++ musl-dev linux-headers \
|
||||
cmake mbedtls-dev make zlib-dev
|
||||
cmake mbedtls-dev make zlib-dev ninja
|
||||
|
||||
RUN addgroup -S app && \
|
||||
adduser -S -G app app && \
|
||||
|
@ -1,6 +1,130 @@
|
||||
# Changelog
|
||||
All changes to this project will be documented in this file.
|
||||
|
||||
## [9.6.4] - 2020-05-20
|
||||
|
||||
(compiler fix) support clang 5 and earlier (contributed by @LunarWatcher)
|
||||
|
||||
## [9.6.3] - 2020-05-18
|
||||
|
||||
(cmake) revert CMake changes to fix #203 and be able to use an external OpenSSL
|
||||
|
||||
## [9.6.2] - 2020-05-17
|
||||
|
||||
(cmake) make install cmake files optional to not conflict with vcpkg
|
||||
|
||||
## [9.6.1] - 2020-05-17
|
||||
|
||||
(windows + tls) mbedtls is the default windows tls backend + add ability to load system certificates with mbdetls on windows
|
||||
|
||||
## [9.6.0] - 2020-05-12
|
||||
|
||||
(ixbots) add options to limit how many messages per minute should be processed
|
||||
|
||||
## [9.5.9] - 2020-05-12
|
||||
|
||||
(ixbots) add new class to configure a bot to simplify passing options around
|
||||
|
||||
## [9.5.8] - 2020-05-08
|
||||
|
||||
(openssl tls) (openssl < 1.1) logic inversion - crypto locking callback are not registered properly
|
||||
|
||||
## [9.5.7] - 2020-05-08
|
||||
|
||||
(cmake) default TLS back to mbedtls on Windows Universal Platform
|
||||
|
||||
## [9.5.6] - 2020-05-06
|
||||
|
||||
(cobra bots) add a --heartbeat_timeout option to specify when the bot should terminate because no events are received
|
||||
|
||||
## [9.5.5] - 2020-05-06
|
||||
|
||||
(openssl tls) when OpenSSL is older than 1.1, register the crypto locking callback to be thread safe. Should fix lots of CI failures
|
||||
|
||||
## [9.5.4] - 2020-05-04
|
||||
|
||||
(cobra bots) do not use a queue to store messages pending processing, let the bot handle queuing
|
||||
|
||||
## [9.5.3] - 2020-04-29
|
||||
|
||||
(http client) better current request cancellation support when the HttpClient destructor is invoked (see #189)
|
||||
|
||||
## [9.5.2] - 2020-04-27
|
||||
|
||||
(cmake) fix cmake broken tls option parsing
|
||||
|
||||
## [9.5.1] - 2020-04-27
|
||||
|
||||
(http client) Set default values for most HttpRequestArgs struct members (fix #185)
|
||||
|
||||
## [9.5.0] - 2020-04-25
|
||||
|
||||
(ssl) Default to OpenSSL on Windows, since it can load the system certificates by default
|
||||
|
||||
## [9.4.1] - 2020-04-25
|
||||
|
||||
(header) Add a space between header name and header value since most http parsers expects it, although it it not required. Cf #184 and #155
|
||||
|
||||
## [9.4.0] - 2020-04-24
|
||||
|
||||
(ssl) Add support for supplying SSL CA from memory, for OpenSSL and MbedTLS backends
|
||||
|
||||
## [9.3.3] - 2020-04-17
|
||||
|
||||
(ixbots) display sent/receive message, per seconds as accumulated
|
||||
|
||||
## [9.3.2] - 2020-04-17
|
||||
|
||||
(ws) add a --logfile option to configure all logs to go to a file
|
||||
|
||||
## [9.3.1] - 2020-04-16
|
||||
|
||||
(cobra bots) add a utility class to factor out the common bots features (heartbeat) and move all bots to used it + convert cobra_subscribe to be a bot and add a unittest for it
|
||||
|
||||
## [9.3.0] - 2020-04-15
|
||||
|
||||
(websocket) add a positive number to the heartbeat message sent, incremented each time the heartbeat is sent
|
||||
|
||||
## [9.2.9] - 2020-04-15
|
||||
|
||||
(ixcobra) change cobra event callback to use a struct instead of several objects, which is more flexible/extensible
|
||||
|
||||
## [9.2.8] - 2020-04-15
|
||||
|
||||
(ixcobra) make CobraConnection_EventType an enum class (CobraEventType)
|
||||
|
||||
## [9.2.7] - 2020-04-14
|
||||
|
||||
(ixsentry) add a library method to upload a payload directly to sentry
|
||||
|
||||
## [9.2.6] - 2020-04-14
|
||||
|
||||
(ixcobra) snake server / handle invalid incoming json messages + cobra subscriber in fluentd mode insert a created_at timestamp entry
|
||||
|
||||
## [9.2.5] - 2020-04-13
|
||||
|
||||
(websocket) WebSocketMessagePtr is a unique_ptr instead of a shared_ptr
|
||||
|
||||
## [9.2.4] - 2020-04-13
|
||||
|
||||
(websocket) use persistent member variable as temp variables to encode/decode zlib messages in order to reduce transient allocations
|
||||
|
||||
## [9.2.3] - 2020-04-13
|
||||
|
||||
(ws) add a --runtime option to ws cobra_subscribe to optionally limit how much time it will run
|
||||
|
||||
## [9.2.2] - 2020-04-04
|
||||
|
||||
(third_party deps) fix #177, update bundled spdlog to 1.6.0
|
||||
|
||||
## [9.2.1] - 2020-04-04
|
||||
|
||||
(windows) when using OpenSSL, the system store is used to populate the cacert. No need to ship a cacert.pem file with your app.
|
||||
|
||||
## [9.2.0] - 2020-04-04
|
||||
|
||||
(windows) ci: windows build with TLS (mbedtls) + verify that we can be build with OpenSSL
|
||||
|
||||
## [9.1.9] - 2020-03-30
|
||||
|
||||
(cobra to statsd bot) add ability to extract a numerical value and send a timer event to statsd, with the --timer option
|
||||
|
@ -18,10 +18,12 @@ There is a unittest which can be executed by typing `make test`.
|
||||
Options for building:
|
||||
|
||||
* `-DUSE_TLS=1` will enable TLS support
|
||||
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support (default on Windows)
|
||||
* `-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_WS=1` will build the ws interactive command line tool
|
||||
* `-DUSE_TEST=1` will build the unittest
|
||||
|
||||
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file that has instructions for building dependencies.
|
||||
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/ccpp.yml#L40) which have instructions for building dependencies.
|
||||
|
||||
It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:
|
||||
|
||||
@ -40,6 +42,19 @@ It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/m
|
||||
```
|
||||
vcpkg install ixwebsocket
|
||||
```
|
||||
To use the installed package within a cmake project, use the following:
|
||||
```cmake
|
||||
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") # this is super important in order for cmake to include the vcpkg search/lib paths!
|
||||
|
||||
# find library and its headers
|
||||
find_path(IXWEBSOCKET_INCLUDE_DIR ixwebsocket/IXWebSocket.h)
|
||||
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
|
||||
# include headers
|
||||
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 NOTFOUNS
|
||||
|
||||
```
|
||||
|
||||
### Conan
|
||||
|
||||
@ -59,7 +74,7 @@ Note that the version listed here might not be the latest one. See Bintray or th
|
||||
There is a Dockerfile for running the unittest on Linux, and to run the `ws` tool. It is also available on the docker registry.
|
||||
|
||||
```
|
||||
docker run bsergean/ws
|
||||
docker run docker.pkg.github.com/machinezone/ixwebsocket/ws:latest --help
|
||||
```
|
||||
|
||||
To use docker-compose you must make a docker container first.
|
||||
|
@ -8,6 +8,8 @@ The per message deflate compression option is supported. It can lead to very nic
|
||||
|
||||
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL and mbedTLS can be used on Android, Linux and Windows.
|
||||
|
||||
If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x.
|
||||
|
||||
### Polling and background thread work
|
||||
|
||||
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes. However concurrent writes need to be protected with mutex.
|
||||
@ -26,12 +28,17 @@ The library has an interactive tool which is handy for testing compatibility ith
|
||||
|
||||
The unittest tries to be comprehensive, and has been running on multiple platforms, with different sanitizers such as a thread sanitizer to catch data races or the undefined behavior sanitizer.
|
||||
|
||||
The regression test is running after each commit on travis.
|
||||
The regression test is running after each commit on github actions for multiple configurations.
|
||||
|
||||
* Linux
|
||||
* macOS with thread sanitizer
|
||||
* macOS, with OpenSSL, with thread sanitizer
|
||||
* macOS, with MbedTLS, with thread sanitizer
|
||||
* Windows, with MbedTLS (the unittest is not run yet)
|
||||
|
||||
## Limitations
|
||||
|
||||
* On Windows and 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. On Windows with mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
|
||||
* There is no convenient way to embed a ca cert.
|
||||
* 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`.
|
||||
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
|
||||
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
## Example code
|
||||
|
||||
```cpp
|
||||
```c++
|
||||
// Required on Windows
|
||||
ix::initNetSystem();
|
||||
|
||||
|
94
docs/packages.md
Normal file
94
docs/packages.md
Normal file
@ -0,0 +1,94 @@
|
||||
Notes on how we can update the different packages for ixwebsocket.
|
||||
|
||||
## VCPKG
|
||||
|
||||
Visit the [releases](https://github.com/machinezone/IXWebSocket/releases) page on Github. A tag must have been made first.
|
||||
|
||||
Download the latest entry.
|
||||
|
||||
```
|
||||
$ cd /tmp
|
||||
/tmp$ curl -s -O -L https://github.com/machinezone/IXWebSocket/archive/v9.1.9.tar.gz
|
||||
/tmp$
|
||||
/tmp$ openssl sha512 v9.1.9.tar.gz
|
||||
SHA512(v9.1.9.tar.gz)= f1fd731b5f6a9ce6d6d10bee22a5d9d9baaa8ea0564d6c4cd7eb91dcb88a45c49b2c7fdb75f8640a3589c1b30cee33ef5df8dcbb55920d013394d1e33ddd3c8e
|
||||
```
|
||||
|
||||
Now go punch those values in the vcpkg ixwebsocket port config files. Here is what the diff look like.
|
||||
|
||||
```
|
||||
vcpkg$ git diff
|
||||
diff --git a/ports/ixwebsocket/CONTROL b/ports/ixwebsocket/CONTROL
|
||||
index db9c2adc9..4acae5c3f 100644
|
||||
--- a/ports/ixwebsocket/CONTROL
|
||||
+++ b/ports/ixwebsocket/CONTROL
|
||||
@@ -1,5 +1,5 @@
|
||||
Source: ixwebsocket
|
||||
-Version: 8.0.5
|
||||
+Version: 9.1.9
|
||||
Build-Depends: zlib
|
||||
Homepage: https://github.com/machinezone/IXWebSocket
|
||||
Description: Lightweight WebSocket Client and Server + HTTP Client and Server
|
||||
diff --git a/ports/ixwebsocket/portfile.cmake b/ports/ixwebsocket/portfile.cmake
|
||||
index de082aece..68e523a05 100644
|
||||
--- a/ports/ixwebsocket/portfile.cmake
|
||||
+++ b/ports/ixwebsocket/portfile.cmake
|
||||
@@ -1,8 +1,8 @@
|
||||
vcpkg_from_github(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
REPO machinezone/IXWebSocket
|
||||
- REF v8.0.5
|
||||
- SHA512 9dcc20d9a0629b92c62a68a8bd7c8206f18dbd9e93289b0b687ec13c478ce9ad1f3563b38c399c8277b0d3812cc78ca725786ba1dedbc3445b9bdb9b689e8add
|
||||
+ REF v9.1.9
|
||||
+ SHA512 f1fd731b5f6a9ce6d6d10bee22a5d9d9baaa8ea0564d6c4cd7eb91dcb88a45c49b2c7fdb75f8640a3589c1b30cee33ef5df8dcbb55920d013394d1e33ddd3c8e
|
||||
)
|
||||
```
|
||||
|
||||
You will need a fork of the vcpkg repo to make a pull request.
|
||||
|
||||
```
|
||||
git fetch upstream
|
||||
git co master
|
||||
git reset --hard upstream/master
|
||||
git push origin master --force
|
||||
```
|
||||
|
||||
Make the pull request (I use a new branch to do that).
|
||||
|
||||
```
|
||||
vcpkg$ git co -b feature/ixwebsocket_9.1.9
|
||||
M ports/ixwebsocket/CONTROL
|
||||
M ports/ixwebsocket/portfile.cmake
|
||||
Switched to a new branch 'feature/ixwebsocket_9.1.9'
|
||||
vcpkg$
|
||||
vcpkg$
|
||||
vcpkg$ git commit -am 'ixwebsocket: update to 9.1.9'
|
||||
[feature/ixwebsocket_9.1.9 8587a4881] ixwebsocket: update to 9.1.9
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
vcpkg$
|
||||
vcpkg$ git push
|
||||
fatal: The current branch feature/ixwebsocket_9.1.9 has no upstream branch.
|
||||
To push the current branch and set the remote as upstream, use
|
||||
|
||||
git push --set-upstream origin feature/ixwebsocket_9.1.9
|
||||
|
||||
vcpkg$ git push --set-upstream origin feature/ixwebsocket_9.1.9
|
||||
|
||||
Enumerating objects: 11, done.
|
||||
Counting objects: 100% (11/11), done.
|
||||
Delta compression using up to 8 threads
|
||||
Compressing objects: 100% (6/6), done.
|
||||
Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done.
|
||||
Total 6 (delta 4), reused 0 (delta 0)
|
||||
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
|
||||
remote:
|
||||
remote: Create a pull request for 'feature/ixwebsocket_9.1.9' on GitHub by visiting:
|
||||
remote: https://github.com/bsergean/vcpkg/pull/new/feature/ixwebsocket_9.1.9
|
||||
remote:
|
||||
To https://github.com/bsergean/vcpkg.git
|
||||
* [new branch] feature/ixwebsocket_9.1.9 -> feature/ixwebsocket_9.1.9
|
||||
Branch 'feature/ixwebsocket_9.1.9' set up to track remote branch 'feature/ixwebsocket_9.1.9' from 'origin' by rebasing.
|
||||
vcpkg$
|
||||
```
|
||||
|
||||
Just visit this url, https://github.com/bsergean/vcpkg/pull/new/feature/ixwebsocket_9.1.9, printed on the console, to make the pull request.
|
@ -35,7 +35,7 @@ webSocket.setUrl(url);
|
||||
|
||||
// Optional heart beat, sent every 45 seconds when there is not any traffic
|
||||
// to make sure that load balancers do not kill an idle connection.
|
||||
webSocket.setHeartBeatPeriod(45);
|
||||
webSocket.setPingInterval(45);
|
||||
|
||||
// Per message deflate connection is enabled by default. You can tweak its parameters or disable it
|
||||
webSocket.disablePerMessageDeflate();
|
||||
@ -174,7 +174,7 @@ when there is no any traffic to make sure that load balancers do not kill an
|
||||
idle connection.
|
||||
|
||||
```cpp
|
||||
webSocket.setHeartBeatPeriod(45);
|
||||
webSocket.setPingInterval(45);
|
||||
```
|
||||
|
||||
### Supply extra HTTP headers.
|
||||
@ -436,6 +436,8 @@ setOnConnectionCallback(
|
||||
|
||||
To leverage TLS features, the library must be compiled with the option `USE_TLS=1`.
|
||||
|
||||
If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x.
|
||||
|
||||
Then, secure sockets are automatically used when connecting to a `wss://*` url.
|
||||
|
||||
Additional TLS options can be configured by passing a `ix::SocketTLSOptions` instance to the
|
||||
@ -445,7 +447,7 @@ Additional TLS options can be configured by passing a `ix::SocketTLSOptions` ins
|
||||
webSocket.setTLSOptions({
|
||||
.certFile = "path/to/cert/file.pem",
|
||||
.keyFile = "path/to/key/file.pem",
|
||||
.caFile = "path/to/trust/bundle/file.pem",
|
||||
.caFile = "path/to/trust/bundle/file.pem", // as a file, or in memory buffer in PEM format
|
||||
.tls = true // required in server mode
|
||||
});
|
||||
```
|
||||
@ -459,6 +461,7 @@ On a server, this is necessary for TLS support.
|
||||
Specifying `caFile` configures the trusted roots bundle file (in PEM format) that will be used to verify peer certificates.
|
||||
- The special value of `SYSTEM` (the default) indicates that the system-configured trust bundle should be used; this is generally what you want when connecting to any publicly exposed API/server.
|
||||
- The special value of `NONE` can be used to disable peer verification; this is only recommended to rule out certificate verification when testing connectivity.
|
||||
- If the value contain the special value `-----BEGIN CERTIFICATE-----`, the value will be read from memory, and not from a file. This is convenient on platforms like Android where reading / writing to the file system can be challenging without proper permissions, or without knowing the location of a temp directory.
|
||||
|
||||
For a client, specifying `caFile` can be used if connecting to a server that uses a self-signed cert, or when using a custom CA in an internal environment.
|
||||
|
||||
|
@ -4,16 +4,19 @@
|
||||
#
|
||||
|
||||
set (IXBOTS_SOURCES
|
||||
ixbots/IXCobraBot.cpp
|
||||
ixbots/IXCobraToSentryBot.cpp
|
||||
ixbots/IXCobraToStatsdBot.cpp
|
||||
ixbots/IXQueueManager.cpp
|
||||
ixbots/IXCobraToStdoutBot.cpp
|
||||
ixbots/IXStatsdClient.cpp
|
||||
)
|
||||
|
||||
set (IXBOTS_HEADERS
|
||||
ixbots/IXCobraBot.h
|
||||
ixbots/IXCobraBotConfig.h
|
||||
ixbots/IXCobraToSentryBot.h
|
||||
ixbots/IXCobraToStatsdBot.h
|
||||
ixbots/IXQueueManager.h
|
||||
ixbots/IXCobraToStdoutBot.h
|
||||
ixbots/IXStatsdClient.h
|
||||
)
|
||||
|
||||
@ -27,11 +30,6 @@ if (NOT JSONCPP_FOUND)
|
||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||
endif()
|
||||
|
||||
find_package(SpdLog)
|
||||
if (NOT SPDLOG_FOUND)
|
||||
set(SPDLOG_INCLUDE_DIRS ../third_party/spdlog/include)
|
||||
endif()
|
||||
|
||||
set(IXBOTS_INCLUDE_DIRS
|
||||
.
|
||||
..
|
||||
|
268
ixbots/ixbots/IXCobraBot.cpp
Normal file
268
ixbots/ixbots/IXCobraBot.cpp
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
* IXCobraBot.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t CobraBot::run(const CobraBotConfig& botConfig)
|
||||
{
|
||||
auto config = botConfig.cobraConfig;
|
||||
auto channel = botConfig.channel;
|
||||
auto filter = botConfig.filter;
|
||||
auto position = botConfig.position;
|
||||
auto enableHeartbeat = botConfig.enableHeartbeat;
|
||||
auto heartBeatTimeout = botConfig.heartBeatTimeout;
|
||||
auto runtime = botConfig.runtime;
|
||||
auto maxEventsPerMinute = botConfig.maxEventsPerMinute;
|
||||
auto limitReceivedEvents = botConfig.limitReceivedEvents;
|
||||
|
||||
ix::CobraConnection conn;
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
|
||||
std::atomic<uint64_t> sentCount(0);
|
||||
std::atomic<uint64_t> receivedCount(0);
|
||||
uint64_t sentCountTotal(0);
|
||||
uint64_t receivedCountTotal(0);
|
||||
uint64_t sentCountPerSecs(0);
|
||||
uint64_t receivedCountPerSecs(0);
|
||||
std::atomic<int> receivedCountPerMinutes(0);
|
||||
std::atomic<bool> stop(false);
|
||||
std::atomic<bool> throttled(false);
|
||||
std::atomic<bool> fatalCobraError(false);
|
||||
int minuteCounter = 0;
|
||||
|
||||
auto timer = [&sentCount,
|
||||
&receivedCount,
|
||||
&sentCountTotal,
|
||||
&receivedCountTotal,
|
||||
&sentCountPerSecs,
|
||||
&receivedCountPerSecs,
|
||||
&receivedCountPerMinutes,
|
||||
&minuteCounter,
|
||||
&stop] {
|
||||
while (!stop)
|
||||
{
|
||||
//
|
||||
// We cannot write to sentCount and receivedCount
|
||||
// as those are used externally, so we need to introduce
|
||||
// our own counters
|
||||
//
|
||||
std::stringstream ss;
|
||||
ss << "messages received "
|
||||
<< receivedCountPerSecs
|
||||
<< " "
|
||||
<< receivedCountTotal
|
||||
<< " sent "
|
||||
<< sentCountPerSecs
|
||||
<< " "
|
||||
<< sentCountTotal;
|
||||
CoreLogger::info(ss.str());
|
||||
|
||||
receivedCountPerSecs = receivedCount - receivedCountTotal;
|
||||
sentCountPerSecs = sentCount - sentCountTotal;
|
||||
|
||||
receivedCountTotal += receivedCountPerSecs;
|
||||
sentCountTotal += sentCountPerSecs;
|
||||
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (minuteCounter++ == 60)
|
||||
{
|
||||
receivedCountPerMinutes = 0;
|
||||
minuteCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CoreLogger::info("timer thread done");
|
||||
};
|
||||
|
||||
std::thread t1(timer);
|
||||
|
||||
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat, &heartBeatTimeout, &fatalCobraError] {
|
||||
std::string state("na");
|
||||
|
||||
if (!enableHeartbeat) return;
|
||||
|
||||
while (!stop)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "messages received " << receivedCount;
|
||||
ss << "messages sent " << sentCount;
|
||||
|
||||
std::string currentState = ss.str();
|
||||
|
||||
if (currentState == state)
|
||||
{
|
||||
CoreLogger::error("no messages received or sent for 1 minute, exiting");
|
||||
fatalCobraError = true;
|
||||
break;
|
||||
}
|
||||
state = currentState;
|
||||
|
||||
auto duration = std::chrono::seconds(heartBeatTimeout);
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
CoreLogger::info("heartbeat thread done");
|
||||
};
|
||||
|
||||
std::thread t2(heartbeat);
|
||||
|
||||
std::string subscriptionPosition(position);
|
||||
|
||||
conn.setEventCallback([this,
|
||||
&conn,
|
||||
&channel,
|
||||
&filter,
|
||||
&subscriptionPosition,
|
||||
&throttled,
|
||||
&receivedCount,
|
||||
&receivedCountPerMinutes,
|
||||
maxEventsPerMinute,
|
||||
limitReceivedEvents,
|
||||
&fatalCobraError,
|
||||
&sentCount](const CobraEventPtr& event) {
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
CoreLogger::info("Subscriber connected");
|
||||
|
||||
for (auto&& it : event->headers)
|
||||
{
|
||||
CoreLogger::info(it.first + "::" + it.second);
|
||||
}
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Closed)
|
||||
{
|
||||
CoreLogger::info("Subscriber closed: {}" + event->errMsg);
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Authenticated)
|
||||
{
|
||||
CoreLogger::info("Subscriber authenticated");
|
||||
CoreLogger::info("Subscribing to " + channel);
|
||||
CoreLogger::info("Subscribing at position " + subscriptionPosition);
|
||||
CoreLogger::info("Subscribing with filter " + filter);
|
||||
conn.subscribe(channel, filter, subscriptionPosition,
|
||||
[&sentCount, &receivedCountPerMinutes,
|
||||
maxEventsPerMinute, limitReceivedEvents,
|
||||
&throttled, &receivedCount,
|
||||
&subscriptionPosition, &fatalCobraError,
|
||||
this](const Json::Value& msg, const std::string& position) {
|
||||
subscriptionPosition = position;
|
||||
++receivedCount;
|
||||
|
||||
++receivedCountPerMinutes;
|
||||
if (limitReceivedEvents)
|
||||
{
|
||||
if (receivedCountPerMinutes > maxEventsPerMinute)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we cannot send to sentry fast enough, drop the message
|
||||
if (throttled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_onBotMessageCallback(
|
||||
msg, position, throttled,
|
||||
fatalCobraError, sentCount);
|
||||
});
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Subscribed)
|
||||
{
|
||||
CoreLogger::info("Subscriber: subscribed to channel " + event->subscriptionId);
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||
{
|
||||
CoreLogger::info("Subscriber: unsubscribed from channel " + event->subscriptionId);
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Error)
|
||||
{
|
||||
CoreLogger::error("Subscriber: error " + event->errMsg);
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Published)
|
||||
{
|
||||
CoreLogger::error("Published message hacked: " + std::to_string(event->msgId));
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Pong)
|
||||
{
|
||||
CoreLogger::info("Received websocket pong: " + event->errMsg);
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::HandshakeError)
|
||||
{
|
||||
CoreLogger::error("Subscriber: Handshake error: " + event->errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::AuthenticationError)
|
||||
{
|
||||
CoreLogger::error("Subscriber: Authentication error: " + event->errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::SubscriptionError)
|
||||
{
|
||||
CoreLogger::error("Subscriber: Subscription error: " + event->errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Run forever
|
||||
if (runtime == -1)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (fatalCobraError) break;
|
||||
}
|
||||
}
|
||||
// Run for a duration, used by unittesting now
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < runtime; ++i)
|
||||
{
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (fatalCobraError) break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Cleanup.
|
||||
// join all the bg threads and stop them.
|
||||
//
|
||||
conn.disconnect();
|
||||
stop = true;
|
||||
|
||||
// progress thread
|
||||
t1.join();
|
||||
|
||||
// heartbeat thread
|
||||
if (t2.joinable()) t2.join();
|
||||
|
||||
return fatalCobraError ? -1 : (int64_t) sentCount;
|
||||
}
|
||||
|
||||
void CobraBot::setOnBotMessageCallback(const OnBotMessageCallback& callback)
|
||||
{
|
||||
_onBotMessageCallback = callback;
|
||||
}
|
||||
} // namespace ix
|
34
ixbots/ixbots/IXCobraBot.h
Normal file
34
ixbots/ixbots/IXCobraBot.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* IXCobraBot.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <json/json.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
using OnBotMessageCallback = std::function<void(const Json::Value&,
|
||||
const std::string&,
|
||||
std::atomic<bool>&,
|
||||
std::atomic<bool>&,
|
||||
std::atomic<uint64_t>&)>;
|
||||
|
||||
class CobraBot
|
||||
{
|
||||
public:
|
||||
CobraBot() = default;
|
||||
|
||||
int64_t run(const CobraBotConfig& botConfig);
|
||||
void setOnBotMessageCallback(const OnBotMessageCallback& callback);
|
||||
|
||||
private:
|
||||
OnBotMessageCallback _onBotMessageCallback;
|
||||
};
|
||||
} // namespace ix
|
31
ixbots/ixbots/IXCobraBotConfig.h
Normal file
31
ixbots/ixbots/IXCobraBotConfig.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* IXCobraBotConfig.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <limits>
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct CobraBotConfig
|
||||
{
|
||||
CobraConfig cobraConfig;
|
||||
std::string channel;
|
||||
std::string filter;
|
||||
std::string position = std::string("$");
|
||||
bool enableHeartbeat = true;
|
||||
int heartBeatTimeout = 60;
|
||||
int runtime = -1;
|
||||
int maxEventsPerMinute = std::numeric_limits<int>::max();
|
||||
bool limitReceivedEvents = false;
|
||||
};
|
||||
} // namespace ix
|
@ -5,301 +5,72 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraToSentryBot.h"
|
||||
#include "IXQueueManager.h"
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int cobra_to_sentry_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose,
|
||||
bool strict,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
int64_t cobra_to_sentry_bot(const CobraBotConfig& config,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose)
|
||||
{
|
||||
ix::CobraConnection conn;
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
|
||||
Json::FastWriter jsonWriter;
|
||||
std::atomic<uint64_t> sentCount(0);
|
||||
std::atomic<uint64_t> receivedCount(0);
|
||||
std::atomic<bool> errorSending(false);
|
||||
std::atomic<bool> stop(false);
|
||||
std::atomic<bool> throttled(false);
|
||||
std::atomic<bool> fatalCobraError(false);
|
||||
|
||||
QueueManager queueManager(maxQueueSize);
|
||||
|
||||
auto timer = [&sentCount, &receivedCount, &stop] {
|
||||
while (!stop)
|
||||
{
|
||||
spdlog::info("messages received {} sent {}", receivedCount, sentCount);
|
||||
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
spdlog::info("timer thread done");
|
||||
};
|
||||
|
||||
std::thread t1(timer);
|
||||
|
||||
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat] {
|
||||
std::string state("na");
|
||||
|
||||
if (!enableHeartbeat) return;
|
||||
|
||||
while (!stop)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "messages received " << receivedCount;
|
||||
ss << "messages sent " << sentCount;
|
||||
|
||||
std::string currentState = ss.str();
|
||||
|
||||
if (currentState == state)
|
||||
CobraBot bot;
|
||||
bot.setOnBotMessageCallback([&sentryClient, &verbose](const Json::Value& msg,
|
||||
const std::string& /*position*/,
|
||||
std::atomic<bool>& throttled,
|
||||
std::atomic<bool>& /*fatalCobraError*/,
|
||||
std::atomic<uint64_t>& sentCount) -> void {
|
||||
sentryClient.send(msg, verbose,
|
||||
[&sentCount, &throttled](const HttpResponsePtr& response) {
|
||||
if (!response)
|
||||
{
|
||||
spdlog::error("no messages received or sent for 1 minute, exiting");
|
||||
exit(1);
|
||||
}
|
||||
state = currentState;
|
||||
|
||||
auto duration = std::chrono::minutes(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
spdlog::info("heartbeat thread done");
|
||||
};
|
||||
|
||||
std::thread t2(heartbeat);
|
||||
|
||||
auto sentrySender =
|
||||
[&queueManager, verbose, &errorSending, &sentCount, &stop, &throttled, &sentryClient] {
|
||||
|
||||
while (true)
|
||||
{
|
||||
Json::Value msg = queueManager.pop();
|
||||
|
||||
if (stop) break;
|
||||
if (msg.isNull()) continue;
|
||||
|
||||
auto ret = sentryClient.send(msg, verbose);
|
||||
HttpResponsePtr response = ret.first;
|
||||
|
||||
if (!response)
|
||||
{
|
||||
spdlog::warn("Null HTTP Response");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
for (auto it : response->headers)
|
||||
{
|
||||
spdlog::info("{}: {}", it.first, it.second);
|
||||
}
|
||||
|
||||
spdlog::info("Upload size: {}", response->uploadSize);
|
||||
spdlog::info("Download size: {}", response->downloadSize);
|
||||
|
||||
spdlog::info("Status: {}", response->statusCode);
|
||||
if (response->errorCode != HttpErrorCode::Ok)
|
||||
{
|
||||
spdlog::info("error message: {}", response->errorMsg);
|
||||
}
|
||||
|
||||
if (response->headers["Content-Type"] != "application/octet-stream")
|
||||
{
|
||||
spdlog::info("payload: {}", response->payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (response->statusCode != 200)
|
||||
{
|
||||
spdlog::error("Error sending data to sentry: {}", response->statusCode);
|
||||
spdlog::error("Body: {}", ret.second);
|
||||
spdlog::error("Response: {}", response->payload);
|
||||
errorSending = true;
|
||||
|
||||
// Error 429 Too Many Requests
|
||||
if (response->statusCode == 429)
|
||||
{
|
||||
auto retryAfter = response->headers["Retry-After"];
|
||||
std::stringstream ss;
|
||||
ss << retryAfter;
|
||||
int seconds;
|
||||
ss >> seconds;
|
||||
|
||||
if (!ss.eof() || ss.fail())
|
||||
{
|
||||
seconds = 30;
|
||||
spdlog::warn("Error parsing Retry-After header. "
|
||||
"Using {} for the sleep duration",
|
||||
seconds);
|
||||
}
|
||||
|
||||
spdlog::warn("Error 429 - Too Many Requests. ws will sleep "
|
||||
"and retry after {} seconds",
|
||||
retryAfter);
|
||||
|
||||
throttled = true;
|
||||
auto duration = std::chrono::seconds(seconds);
|
||||
std::this_thread::sleep_for(duration);
|
||||
throttled = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++sentCount;
|
||||
}
|
||||
|
||||
if (stop) break;
|
||||
CoreLogger::warn("Null HTTP Response");
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::info("sentrySender thread done");
|
||||
};
|
||||
|
||||
std::thread t3(sentrySender);
|
||||
|
||||
conn.setEventCallback([&conn,
|
||||
&channel,
|
||||
&filter,
|
||||
&position,
|
||||
&jsonWriter,
|
||||
verbose,
|
||||
&throttled,
|
||||
&receivedCount,
|
||||
&fatalCobraError,
|
||||
&queueManager](ix::CobraConnectionEventType eventType,
|
||||
const std::string& errMsg,
|
||||
const ix::WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId) {
|
||||
if (eventType == ix::CobraConnection_EventType_Open)
|
||||
{
|
||||
spdlog::info("Subscriber connected");
|
||||
|
||||
for (auto it : headers)
|
||||
if (response->statusCode == 200)
|
||||
{
|
||||
spdlog::info("{}: {}", it.first, it.second);
|
||||
sentCount++;
|
||||
}
|
||||
}
|
||||
if (eventType == ix::CobraConnection_EventType_Closed)
|
||||
{
|
||||
spdlog::info("Subscriber closed");
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||
{
|
||||
spdlog::info("Subscriber authenticated");
|
||||
conn.subscribe(channel,
|
||||
filter,
|
||||
position,
|
||||
[&jsonWriter, verbose, &throttled, &receivedCount, &queueManager](
|
||||
const Json::Value& msg, const std::string& position) {
|
||||
if (verbose)
|
||||
{
|
||||
spdlog::info("Subscriber received message {} -> {}", position, jsonWriter.write(msg));
|
||||
}
|
||||
else
|
||||
{
|
||||
CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode));
|
||||
CoreLogger::error("Response: " + response->payload);
|
||||
|
||||
// If we cannot send to sentry fast enough, drop the message
|
||||
if (throttled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Error 429 Too Many Requests
|
||||
if (response->statusCode == 429)
|
||||
{
|
||||
auto retryAfter = response->headers["Retry-After"];
|
||||
std::stringstream ss;
|
||||
ss << retryAfter;
|
||||
int seconds;
|
||||
ss >> seconds;
|
||||
|
||||
++receivedCount;
|
||||
queueManager.add(msg);
|
||||
});
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||
{
|
||||
spdlog::info("Subscriber: subscribed to channel {}", subscriptionId);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
||||
{
|
||||
spdlog::info("Subscriber: unsubscribed from channel {}", subscriptionId);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
||||
{
|
||||
spdlog::error("Subscriber: error {}", errMsg);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
||||
{
|
||||
spdlog::error("Published message hacked: {}", msgId);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Pong)
|
||||
{
|
||||
spdlog::info("Received websocket pong");
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Handshake_Error)
|
||||
{
|
||||
spdlog::error("Subscriber: Handshake error: {}", errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Authentication_Error)
|
||||
{
|
||||
spdlog::error("Subscriber: Authentication error: {}", errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscription_Error)
|
||||
{
|
||||
spdlog::error("Subscriber: Subscription error: {}", errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
if (!ss.eof() || ss.fail())
|
||||
{
|
||||
seconds = 30;
|
||||
CoreLogger::warn("Error parsing Retry-After header. "
|
||||
"Using " + retryAfter + " for the sleep duration");
|
||||
}
|
||||
|
||||
CoreLogger::warn("Error 429 - Too Many Requests. ws will sleep "
|
||||
"and retry after " + retryAfter + " seconds");
|
||||
|
||||
throttled = true;
|
||||
auto duration = std::chrono::seconds(seconds);
|
||||
std::this_thread::sleep_for(duration);
|
||||
throttled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Run forever
|
||||
if (runtime == -1)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (strict && errorSending) break;
|
||||
if (fatalCobraError) break;
|
||||
}
|
||||
}
|
||||
// Run for a duration, used by unittesting now
|
||||
else
|
||||
{
|
||||
for (int i = 0 ; i < runtime; ++i)
|
||||
{
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (strict && errorSending) break;
|
||||
if (fatalCobraError) break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Cleanup.
|
||||
// join all the bg threads and stop them.
|
||||
//
|
||||
conn.disconnect();
|
||||
stop = true;
|
||||
|
||||
// progress thread
|
||||
t1.join();
|
||||
|
||||
// heartbeat thread
|
||||
if (t2.joinable()) t2.join();
|
||||
|
||||
// sentry sender thread
|
||||
t3.join();
|
||||
|
||||
return ((strict && errorSending) || fatalCobraError) ? -1 : (int) sentCount;
|
||||
return bot.run(config);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,20 +5,14 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <cstdint>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <ixsentry/IXSentryClient.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int cobra_to_sentry_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose,
|
||||
bool strict,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
int64_t cobra_to_sentry_bot(const CobraBotConfig& config,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose);
|
||||
} // namespace ix
|
||||
|
@ -5,16 +5,13 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraToStatsdBot.h"
|
||||
#include "IXQueueManager.h"
|
||||
#include "IXStatsdClient.h"
|
||||
|
||||
#include <atomic>
|
||||
#include "IXCobraBot.h"
|
||||
#include "IXStatsdClient.h"
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
@ -56,84 +53,22 @@ namespace ix
|
||||
return val;
|
||||
}
|
||||
|
||||
int cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
int64_t cobra_to_statsd_bot(const ix::CobraBotConfig& config,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose)
|
||||
{
|
||||
ix::CobraConnection conn;
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
|
||||
auto tokens = parseFields(fields);
|
||||
|
||||
Json::FastWriter jsonWriter;
|
||||
std::atomic<uint64_t> sentCount(0);
|
||||
std::atomic<uint64_t> receivedCount(0);
|
||||
std::atomic<bool> stop(false);
|
||||
std::atomic<bool> fatalCobraError(false);
|
||||
|
||||
QueueManager queueManager(maxQueueSize);
|
||||
|
||||
auto progress = [&sentCount, &receivedCount, &stop] {
|
||||
while (!stop)
|
||||
{
|
||||
spdlog::info("messages received {} sent {}", receivedCount, sentCount);
|
||||
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
spdlog::info("timer thread done");
|
||||
};
|
||||
|
||||
std::thread t1(progress);
|
||||
|
||||
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat] {
|
||||
std::string state("na");
|
||||
|
||||
if (!enableHeartbeat) return;
|
||||
|
||||
while (!stop)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "messages received " << receivedCount;
|
||||
ss << "messages sent " << sentCount;
|
||||
|
||||
std::string currentState = ss.str();
|
||||
|
||||
if (currentState == state)
|
||||
{
|
||||
spdlog::error("no messages received or sent for 1 minute, exiting");
|
||||
exit(1);
|
||||
}
|
||||
state = currentState;
|
||||
|
||||
auto duration = std::chrono::minutes(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
spdlog::info("heartbeat thread done");
|
||||
};
|
||||
|
||||
std::thread t2(heartbeat);
|
||||
|
||||
auto statsdSender = [&statsdClient, &queueManager, &sentCount, &tokens, &stop, &gauge, &timer, &fatalCobraError, &verbose] {
|
||||
while (true)
|
||||
{
|
||||
Json::Value msg = queueManager.pop();
|
||||
|
||||
if (stop) return;
|
||||
if (msg.isNull()) continue;
|
||||
|
||||
CobraBot bot;
|
||||
bot.setOnBotMessageCallback(
|
||||
[&statsdClient, &tokens, &gauge, &timer, &verbose](const Json::Value& msg,
|
||||
const std::string& /*position*/,
|
||||
std::atomic<bool>& /*throttled*/,
|
||||
std::atomic<bool>& fatalCobraError,
|
||||
std::atomic<uint64_t>& sentCount) -> void {
|
||||
std::string id;
|
||||
for (auto&& attr : tokens)
|
||||
{
|
||||
@ -174,14 +109,14 @@ namespace ix
|
||||
}
|
||||
else
|
||||
{
|
||||
spdlog::error("Gauge {} is not a numberic type", gauge);
|
||||
CoreLogger::error("Gauge " + gauge + " is not a numeric type");
|
||||
fatalCobraError = true;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
spdlog::info("{} - {} -> {}", id, attrName, x);
|
||||
CoreLogger::info(id + " - " + attrName + " -> " + std::to_string(x));
|
||||
}
|
||||
|
||||
if (!gauge.empty())
|
||||
@ -194,127 +129,9 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
sentCount += 1;
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t3(statsdSender);
|
||||
|
||||
conn.setEventCallback(
|
||||
[&conn, &channel, &filter, &position, &jsonWriter, verbose, &queueManager, &receivedCount, &fatalCobraError](
|
||||
ix::CobraConnectionEventType eventType,
|
||||
const std::string& errMsg,
|
||||
const ix::WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId) {
|
||||
if (eventType == ix::CobraConnection_EventType_Open)
|
||||
{
|
||||
spdlog::info("Subscriber connected");
|
||||
|
||||
for (auto it : headers)
|
||||
{
|
||||
spdlog::info("{}: {}", it.first, it.second);
|
||||
}
|
||||
}
|
||||
if (eventType == ix::CobraConnection_EventType_Closed)
|
||||
{
|
||||
spdlog::info("Subscriber closed");
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||
{
|
||||
spdlog::info("Subscriber authenticated");
|
||||
conn.subscribe(channel,
|
||||
filter,
|
||||
position,
|
||||
[&jsonWriter, &queueManager, verbose, &receivedCount](
|
||||
const Json::Value& msg, const std::string& position) {
|
||||
if (verbose)
|
||||
{
|
||||
spdlog::info("Subscriber received message {} -> {}", position, jsonWriter.write(msg));
|
||||
}
|
||||
|
||||
receivedCount++;
|
||||
|
||||
++receivedCount;
|
||||
queueManager.add(msg);
|
||||
});
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||
{
|
||||
spdlog::info("Subscriber: subscribed to channel {}", subscriptionId);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
||||
{
|
||||
spdlog::info("Subscriber: unsubscribed from channel {}", subscriptionId);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
||||
{
|
||||
spdlog::error("Subscriber: error {}", errMsg);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
||||
{
|
||||
spdlog::error("Published message hacked: {}", msgId);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Pong)
|
||||
{
|
||||
spdlog::info("Received websocket pong");
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Handshake_Error)
|
||||
{
|
||||
spdlog::error("Subscriber: Handshake error: {}", errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Authentication_Error)
|
||||
{
|
||||
spdlog::error("Subscriber: Authentication error: {}", errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscription_Error)
|
||||
{
|
||||
spdlog::error("Subscriber: Subscription error: {}", errMsg);
|
||||
fatalCobraError = true;
|
||||
}
|
||||
sentCount++;
|
||||
});
|
||||
|
||||
// Run forever
|
||||
if (runtime == -1)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (fatalCobraError) break;
|
||||
}
|
||||
}
|
||||
// Run for a duration, used by unittesting now
|
||||
else
|
||||
{
|
||||
for (int i = 0 ; i < runtime; ++i)
|
||||
{
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (fatalCobraError) break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Cleanup.
|
||||
// join all the bg threads and stop them.
|
||||
//
|
||||
conn.disconnect();
|
||||
stop = true;
|
||||
|
||||
// progress thread
|
||||
t1.join();
|
||||
|
||||
// heartbeat thread
|
||||
if (t2.joinable()) t2.join();
|
||||
|
||||
// statsd sender thread
|
||||
t3.join();
|
||||
|
||||
return fatalCobraError ? -1 : (int) sentCount;
|
||||
return bot.run(config);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,23 +5,18 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <cstdint>
|
||||
#include <ixbots/IXStatsdClient.h>
|
||||
#include <string>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
int64_t cobra_to_statsd_bot(const ix::CobraBotConfig& config,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose);
|
||||
} // namespace ix
|
||||
|
88
ixbots/ixbots/IXCobraToStdoutBot.cpp
Normal file
88
ixbots/ixbots/IXCobraToStdoutBot.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* IXCobraToStdoutBot.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXCobraToStdoutBot.h"
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
using StreamWriterPtr = std::unique_ptr<Json::StreamWriter>;
|
||||
|
||||
StreamWriterPtr makeStreamWriter()
|
||||
{
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["commentStyle"] = "None";
|
||||
builder["indentation"] = ""; // will make the JSON object compact
|
||||
std::unique_ptr<Json::StreamWriter> jsonWriter(builder.newStreamWriter());
|
||||
return jsonWriter;
|
||||
}
|
||||
|
||||
std::string timeSinceEpoch()
|
||||
{
|
||||
std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();
|
||||
std::chrono::system_clock::duration dtn = tp.time_since_epoch();
|
||||
|
||||
std::stringstream ss;
|
||||
ss << dtn.count() * std::chrono::system_clock::period::num /
|
||||
std::chrono::system_clock::period::den;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void writeToStdout(bool fluentd,
|
||||
const StreamWriterPtr& jsonWriter,
|
||||
const Json::Value& msg,
|
||||
const std::string& position)
|
||||
{
|
||||
Json::Value enveloppe;
|
||||
if (fluentd)
|
||||
{
|
||||
enveloppe["producer"] = "cobra";
|
||||
enveloppe["consumer"] = "fluentd";
|
||||
|
||||
Json::Value nestedMessage(msg);
|
||||
nestedMessage["position"] = position;
|
||||
nestedMessage["created_at"] = timeSinceEpoch();
|
||||
enveloppe["message"] = nestedMessage;
|
||||
|
||||
jsonWriter->write(enveloppe, &std::cout);
|
||||
std::cout << std::endl; // add lf and flush
|
||||
}
|
||||
else
|
||||
{
|
||||
enveloppe = msg;
|
||||
std::cout << position << " ";
|
||||
jsonWriter->write(enveloppe, &std::cout);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t cobra_to_stdout_bot(const ix::CobraBotConfig& config,
|
||||
bool fluentd,
|
||||
bool quiet)
|
||||
{
|
||||
CobraBot bot;
|
||||
auto jsonWriter = makeStreamWriter();
|
||||
|
||||
bot.setOnBotMessageCallback(
|
||||
[&fluentd, &quiet, &jsonWriter](const Json::Value& msg,
|
||||
const std::string& position,
|
||||
std::atomic<bool>& /*throttled*/,
|
||||
std::atomic<bool>& /*fatalCobraError*/,
|
||||
std::atomic<uint64_t>& sentCount) -> void {
|
||||
if (!quiet)
|
||||
{
|
||||
writeToStdout(fluentd, jsonWriter, msg, position);
|
||||
}
|
||||
sentCount++;
|
||||
});
|
||||
|
||||
return bot.run(config);
|
||||
}
|
||||
} // namespace ix
|
18
ixbots/ixbots/IXCobraToStdoutBot.h
Normal file
18
ixbots/ixbots/IXCobraToStdoutBot.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* IXCobraToStdoutBot.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t cobra_to_stdout_bot(const ix::CobraBotConfig& config,
|
||||
bool fluentd,
|
||||
bool quiet);
|
||||
} // namespace ix
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* IXQueueManager.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXQueueManager.h"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
Json::Value QueueManager::pop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
if (_queues.empty())
|
||||
{
|
||||
Json::Value val;
|
||||
return val;
|
||||
}
|
||||
|
||||
std::vector<std::string> games;
|
||||
for (auto it : _queues)
|
||||
{
|
||||
games.push_back(it.first);
|
||||
}
|
||||
|
||||
std::random_shuffle(games.begin(), games.end());
|
||||
std::string game = games[0];
|
||||
|
||||
auto duration = std::chrono::seconds(1);
|
||||
_condition.wait_for(lock, duration);
|
||||
|
||||
if (_queues[game].empty())
|
||||
{
|
||||
Json::Value val;
|
||||
return val;
|
||||
}
|
||||
|
||||
auto msg = _queues[game].front();
|
||||
_queues[game].pop();
|
||||
return msg;
|
||||
}
|
||||
|
||||
void QueueManager::add(Json::Value msg)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
std::string game;
|
||||
if (msg.isMember("device") && msg["device"].isMember("game"))
|
||||
{
|
||||
game = msg["device"]["game"].asString();
|
||||
}
|
||||
|
||||
if (game.empty()) return;
|
||||
|
||||
// if the sending is not fast enough there is no point
|
||||
// in queuing too many events.
|
||||
if (_queues[game].size() < _maxQueueSize)
|
||||
{
|
||||
_queues[game].push(msg);
|
||||
_condition.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* IXQueueManager.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <json/json.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class QueueManager
|
||||
{
|
||||
public:
|
||||
QueueManager(size_t maxQueueSize)
|
||||
: _maxQueueSize(maxQueueSize)
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value pop();
|
||||
void add(Json::Value msg);
|
||||
|
||||
private:
|
||||
std::map<std::string, std::queue<Json::Value>> _queues;
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _condition;
|
||||
size_t _maxQueueSize;
|
||||
};
|
||||
}
|
@ -39,24 +39,21 @@
|
||||
|
||||
#include "IXStatsdClient.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
StatsdClient::StatsdClient(const std::string& host,
|
||||
int port,
|
||||
const std::string& prefix)
|
||||
: _host(host)
|
||||
, _port(port)
|
||||
, _prefix(prefix)
|
||||
, _stop(false)
|
||||
StatsdClient::StatsdClient(const std::string& host, int port, const std::string& prefix)
|
||||
: _host(host)
|
||||
, _port(port)
|
||||
, _prefix(prefix)
|
||||
, _stop(false)
|
||||
{
|
||||
_thread = std::thread([this]
|
||||
{
|
||||
_thread = std::thread([this] {
|
||||
while (!_stop)
|
||||
{
|
||||
flushQueue();
|
||||
@ -119,8 +116,8 @@ namespace ix
|
||||
cleanup(key);
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "%s%s:%zd|%s\n",
|
||||
_prefix.c_str(), key.c_str(), value, type.c_str());
|
||||
snprintf(
|
||||
buf, sizeof(buf), "%s%s:%zd|%s\n", _prefix.c_str(), key.c_str(), value, type.c_str());
|
||||
|
||||
enqueue(buf);
|
||||
return 0;
|
||||
@ -142,9 +139,7 @@ namespace ix
|
||||
auto ret = _socket.sendto(message);
|
||||
if (ret != 0)
|
||||
{
|
||||
std::cerr << "error: "
|
||||
<< strerror(UdpSocket::getErrno())
|
||||
<< std::endl;
|
||||
std::cerr << "error: " << strerror(UdpSocket::getErrno()) << std::endl;
|
||||
}
|
||||
_queue.pop_front();
|
||||
}
|
||||
|
@ -6,21 +6,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <ixwebsocket/IXUdpSocket.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class StatsdClient
|
||||
{
|
||||
public:
|
||||
StatsdClient(const std::string& host="127.0.0.1",
|
||||
int port=8125,
|
||||
StatsdClient(const std::string& host = "127.0.0.1",
|
||||
int port = 8125,
|
||||
const std::string& prefix = "");
|
||||
~StatsdClient();
|
||||
|
||||
|
@ -14,6 +14,7 @@ set (IXCOBRA_HEADERS
|
||||
ixcobra/IXCobraMetricsThreadedPublisher.h
|
||||
ixcobra/IXCobraMetricsPublisher.h
|
||||
ixcobra/IXCobraConfig.h
|
||||
ixcobra/IXCobraEventType.h
|
||||
)
|
||||
|
||||
add_library(ixcobra STATIC
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
|
@ -5,17 +5,17 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraConnection.h"
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -26,12 +26,12 @@ namespace ix
|
||||
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
|
||||
constexpr int CobraConnection::kPingIntervalSecs;
|
||||
|
||||
CobraConnection::CobraConnection() :
|
||||
_webSocket(new WebSocket()),
|
||||
_publishMode(CobraConnection_PublishMode_Immediate),
|
||||
_authenticated(false),
|
||||
_eventCallback(nullptr),
|
||||
_id(1)
|
||||
CobraConnection::CobraConnection()
|
||||
: _webSocket(new WebSocket())
|
||||
, _publishMode(CobraConnection_PublishMode_Immediate)
|
||||
, _authenticated(false)
|
||||
, _eventCallback(nullptr)
|
||||
, _id(1)
|
||||
{
|
||||
_pdu["action"] = "rtm/publish";
|
||||
|
||||
@ -87,7 +87,7 @@ namespace ix
|
||||
_eventCallback = eventCallback;
|
||||
}
|
||||
|
||||
void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType,
|
||||
void CobraConnection::invokeEventCallback(ix::CobraEventType eventType,
|
||||
const std::string& errorMsg,
|
||||
const WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
@ -96,7 +96,8 @@ namespace ix
|
||||
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
||||
if (_eventCallback)
|
||||
{
|
||||
_eventCallback(eventType, errorMsg, headers, subscriptionId, msgId);
|
||||
_eventCallback(
|
||||
std::make_unique<CobraEvent>(eventType, errorMsg, headers, subscriptionId, msgId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +106,7 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << errorMsg << " : received pdu => " << serializedPdu;
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Error, ss.str());
|
||||
invokeEventCallback(ix::CobraEventType::Error, ss.str());
|
||||
}
|
||||
|
||||
void CobraConnection::disconnect()
|
||||
@ -116,126 +117,119 @@ namespace ix
|
||||
|
||||
void CobraConnection::initWebSocketOnMessageCallback()
|
||||
{
|
||||
_webSocket->setOnMessageCallback(
|
||||
[this](const ix::WebSocketMessagePtr& msg)
|
||||
_webSocket->setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) {
|
||||
CobraConnection::invokeTrafficTrackerCallback(msg->wireSize, true);
|
||||
|
||||
std::stringstream ss;
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
CobraConnection::invokeTrafficTrackerCallback(msg->wireSize, true);
|
||||
invokeEventCallback(ix::CobraEventType::Open, std::string(), msg->openInfo.headers);
|
||||
sendHandshakeMessage();
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
_authenticated = false;
|
||||
|
||||
std::stringstream ss;
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
ss << "Close code " << msg->closeInfo.code;
|
||||
ss << " reason " << msg->closeInfo.reason;
|
||||
invokeEventCallback(ix::CobraEventType::Closed, ss.str());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
Json::Value data;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(msg->str, data))
|
||||
{
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Open,
|
||||
std::string(),
|
||||
msg->openInfo.headers);
|
||||
sendHandshakeMessage();
|
||||
invokeErrorCallback("Invalid json", msg->str);
|
||||
return;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
_authenticated = false;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Close code " << msg->closeInfo.code;
|
||||
ss << " reason " << msg->closeInfo.reason;
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Closed,
|
||||
ss.str());
|
||||
if (!data.isMember("action"))
|
||||
{
|
||||
invokeErrorCallback("Missing action", msg->str);
|
||||
return;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
|
||||
auto action = data["action"].asString();
|
||||
|
||||
if (action == "auth/handshake/ok")
|
||||
{
|
||||
Json::Value data;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(msg->str, data))
|
||||
if (!handleHandshakeResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Invalid json", msg->str);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.isMember("action"))
|
||||
{
|
||||
invokeErrorCallback("Missing action", msg->str);
|
||||
return;
|
||||
}
|
||||
|
||||
auto action = data["action"].asString();
|
||||
|
||||
if (action == "auth/handshake/ok")
|
||||
{
|
||||
if (!handleHandshakeResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Error extracting nonce from handshake response", msg->str);
|
||||
}
|
||||
}
|
||||
else if (action == "auth/handshake/error")
|
||||
{
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Handshake_Error,
|
||||
invokeErrorCallback("Error extracting nonce from handshake response",
|
||||
msg->str);
|
||||
}
|
||||
else if (action == "auth/authenticate/ok")
|
||||
{
|
||||
_authenticated = true;
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Authenticated);
|
||||
flushQueue();
|
||||
}
|
||||
else if (action == "auth/authenticate/error")
|
||||
{
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Authentication_Error,
|
||||
msg->str);
|
||||
}
|
||||
else if (action == "rtm/subscription/data")
|
||||
{
|
||||
handleSubscriptionData(data);
|
||||
}
|
||||
else if (action == "rtm/subscribe/ok")
|
||||
{
|
||||
if (!handleSubscriptionResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Error processing subscribe response", msg->str);
|
||||
}
|
||||
}
|
||||
else if (action == "rtm/subscribe/error")
|
||||
{
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Subscription_Error,
|
||||
msg->str);
|
||||
}
|
||||
else if (action == "rtm/unsubscribe/ok")
|
||||
{
|
||||
if (!handleUnsubscriptionResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Error processing unsubscribe response", msg->str);
|
||||
}
|
||||
}
|
||||
else if (action == "rtm/unsubscribe/error")
|
||||
{
|
||||
invokeErrorCallback("Unsubscription error", msg->str);
|
||||
}
|
||||
else if (action == "rtm/publish/ok")
|
||||
{
|
||||
if (!handlePublishResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Error processing publish response", msg->str);
|
||||
}
|
||||
}
|
||||
else if (action == "rtm/publish/error")
|
||||
{
|
||||
invokeErrorCallback("Publish error", msg->str);
|
||||
}
|
||||
else
|
||||
{
|
||||
invokeErrorCallback("Un-handled message type", msg->str);
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||
else if (action == "auth/handshake/error")
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||
invokeErrorCallback(ss.str(), std::string());
|
||||
invokeEventCallback(ix::CobraEventType::HandshakeError, msg->str);
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||
else if (action == "auth/authenticate/ok")
|
||||
{
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Pong);
|
||||
_authenticated = true;
|
||||
invokeEventCallback(ix::CobraEventType::Authenticated);
|
||||
flushQueue();
|
||||
}
|
||||
else if (action == "auth/authenticate/error")
|
||||
{
|
||||
invokeEventCallback(ix::CobraEventType::AuthenticationError, msg->str);
|
||||
}
|
||||
else if (action == "rtm/subscription/data")
|
||||
{
|
||||
handleSubscriptionData(data);
|
||||
}
|
||||
else if (action == "rtm/subscribe/ok")
|
||||
{
|
||||
if (!handleSubscriptionResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Error processing subscribe response", msg->str);
|
||||
}
|
||||
}
|
||||
else if (action == "rtm/subscribe/error")
|
||||
{
|
||||
invokeEventCallback(ix::CobraEventType::SubscriptionError, msg->str);
|
||||
}
|
||||
else if (action == "rtm/unsubscribe/ok")
|
||||
{
|
||||
if (!handleUnsubscriptionResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Error processing unsubscribe response", msg->str);
|
||||
}
|
||||
}
|
||||
else if (action == "rtm/unsubscribe/error")
|
||||
{
|
||||
invokeErrorCallback("Unsubscription error", msg->str);
|
||||
}
|
||||
else if (action == "rtm/publish/ok")
|
||||
{
|
||||
if (!handlePublishResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Error processing publish response", msg->str);
|
||||
}
|
||||
}
|
||||
else if (action == "rtm/publish/error")
|
||||
{
|
||||
invokeErrorCallback("Publish error", msg->str);
|
||||
}
|
||||
else
|
||||
{
|
||||
invokeErrorCallback("Un-handled message type", msg->str);
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||
invokeErrorCallback(ss.str(), std::string());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
invokeEventCallback(ix::CobraEventType::Pong, msg->str);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -249,12 +243,13 @@ namespace ix
|
||||
return _publishMode;
|
||||
}
|
||||
|
||||
void CobraConnection::configure(const std::string& appkey,
|
||||
const std::string& endpoint,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
void CobraConnection::configure(
|
||||
const std::string& appkey,
|
||||
const std::string& endpoint,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
_roleName = rolename;
|
||||
_roleSecret = rolesecret;
|
||||
@ -396,8 +391,9 @@ namespace ix
|
||||
|
||||
if (!subscriptionId.isString()) return false;
|
||||
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Subscribed,
|
||||
std::string(), WebSocketHttpHeaders(),
|
||||
invokeEventCallback(ix::CobraEventType::Subscribed,
|
||||
std::string(),
|
||||
WebSocketHttpHeaders(),
|
||||
subscriptionId.asString());
|
||||
return true;
|
||||
}
|
||||
@ -414,8 +410,9 @@ namespace ix
|
||||
|
||||
if (!subscriptionId.isString()) return false;
|
||||
|
||||
invokeEventCallback(ix::CobraConnection_EventType_UnSubscribed,
|
||||
std::string(), WebSocketHttpHeaders(),
|
||||
invokeEventCallback(ix::CobraEventType::UnSubscribed,
|
||||
std::string(),
|
||||
WebSocketHttpHeaders(),
|
||||
subscriptionId.asString());
|
||||
return true;
|
||||
}
|
||||
@ -462,9 +459,11 @@ namespace ix
|
||||
|
||||
uint64_t msgId = id.asUInt64();
|
||||
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Published,
|
||||
std::string(), WebSocketHttpHeaders(),
|
||||
std::string(), msgId);
|
||||
invokeEventCallback(ix::CobraEventType::Published,
|
||||
std::string(),
|
||||
WebSocketHttpHeaders(),
|
||||
std::string(),
|
||||
msgId);
|
||||
|
||||
invokePublishTrackerCallback(false, true);
|
||||
|
||||
@ -494,9 +493,7 @@ namespace ix
|
||||
}
|
||||
|
||||
std::pair<CobraConnection::MsgId, std::string> CobraConnection::prePublish(
|
||||
const Json::Value& channels,
|
||||
const Json::Value& msg,
|
||||
bool addToQueue)
|
||||
const Json::Value& channels, const Json::Value& msg, bool addToQueue)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_prePublishMutex);
|
||||
|
||||
@ -662,8 +659,7 @@ namespace ix
|
||||
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
||||
{
|
||||
auto webSocketSendInfo = _webSocket->send(serializedJson);
|
||||
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize,
|
||||
false);
|
||||
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
||||
return webSocketSendInfo.success;
|
||||
}
|
||||
|
||||
|
@ -6,18 +6,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXCobraConfig.h"
|
||||
#include "IXCobraEvent.h"
|
||||
#include "IXCobraEventType.h"
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
#include <json/json.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <limits>
|
||||
|
||||
#include "IXCobraConfig.h"
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
@ -28,21 +29,6 @@ namespace ix
|
||||
class WebSocket;
|
||||
struct SocketTLSOptions;
|
||||
|
||||
enum CobraConnectionEventType
|
||||
{
|
||||
CobraConnection_EventType_Authenticated = 0,
|
||||
CobraConnection_EventType_Error = 1,
|
||||
CobraConnection_EventType_Open = 2,
|
||||
CobraConnection_EventType_Closed = 3,
|
||||
CobraConnection_EventType_Subscribed = 4,
|
||||
CobraConnection_EventType_UnSubscribed = 5,
|
||||
CobraConnection_EventType_Published = 6,
|
||||
CobraConnection_EventType_Pong = 7,
|
||||
CobraConnection_EventType_Handshake_Error = 8,
|
||||
CobraConnection_EventType_Authentication_Error = 9,
|
||||
CobraConnection_EventType_Subscription_Error = 10
|
||||
};
|
||||
|
||||
enum CobraConnectionPublishMode
|
||||
{
|
||||
CobraConnection_PublishMode_Immediate = 0,
|
||||
@ -50,11 +36,7 @@ namespace ix
|
||||
};
|
||||
|
||||
using SubscriptionCallback = std::function<void(const Json::Value&, const std::string&)>;
|
||||
using EventCallback = std::function<void(CobraConnectionEventType,
|
||||
const std::string&,
|
||||
const WebSocketHttpHeaders&,
|
||||
const std::string&,
|
||||
uint64_t msgId)>;
|
||||
using EventCallback = std::function<void(const CobraEventPtr&)>;
|
||||
|
||||
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
||||
using PublishTrackerCallback = std::function<void(bool sent, bool acked)>;
|
||||
@ -138,10 +120,9 @@ namespace ix
|
||||
|
||||
/// Prepare a message for transmission
|
||||
/// (update the pdu, compute a msgId, serialize json to a string)
|
||||
std::pair<CobraConnection::MsgId, std::string> prePublish(
|
||||
const Json::Value& channels,
|
||||
const Json::Value& msg,
|
||||
bool addToQueue);
|
||||
std::pair<CobraConnection::MsgId, std::string> prePublish(const Json::Value& channels,
|
||||
const Json::Value& msg,
|
||||
bool addToQueue);
|
||||
|
||||
/// Attempt to send next message from the internal queue
|
||||
bool publishNext();
|
||||
@ -171,7 +152,7 @@ namespace ix
|
||||
static void invokePublishTrackerCallback(bool sent, bool acked);
|
||||
|
||||
/// Invoke event callbacks
|
||||
void invokeEventCallback(CobraConnectionEventType eventType,
|
||||
void invokeEventCallback(CobraEventType eventType,
|
||||
const std::string& errorMsg = std::string(),
|
||||
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
|
||||
const std::string& subscriptionId = std::string(),
|
||||
|
41
ixcobra/ixcobra/IXCobraEvent.h
Normal file
41
ixcobra/ixcobra/IXCobraEvent.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* IXCobraEvent.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXCobraEventType.h"
|
||||
#include <cstdint>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct CobraEvent
|
||||
{
|
||||
ix::CobraEventType type;
|
||||
const std::string& errMsg;
|
||||
const ix::WebSocketHttpHeaders& headers;
|
||||
const std::string& subscriptionId;
|
||||
uint64_t msgId; // CobraConnection::MsgId
|
||||
|
||||
CobraEvent(ix::CobraEventType t,
|
||||
const std::string& e,
|
||||
const ix::WebSocketHttpHeaders& h,
|
||||
const std::string& s,
|
||||
uint64_t m)
|
||||
: type(t)
|
||||
, errMsg(e)
|
||||
, headers(h)
|
||||
, subscriptionId(s)
|
||||
, msgId(m)
|
||||
{
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
using CobraEventPtr = std::unique_ptr<CobraEvent>;
|
||||
} // namespace ix
|
25
ixcobra/ixcobra/IXCobraEventType.h
Normal file
25
ixcobra/ixcobra/IXCobraEventType.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* IXCobraEventType.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ix
|
||||
{
|
||||
enum class CobraEventType
|
||||
{
|
||||
Authenticated = 0,
|
||||
Error = 1,
|
||||
Open = 2,
|
||||
Closed = 3,
|
||||
Subscribed = 4,
|
||||
UnSubscribed = 5,
|
||||
Published = 6,
|
||||
Pong = 7,
|
||||
HandshakeError = 8,
|
||||
AuthenticationError = 9,
|
||||
SubscriptionError = 10
|
||||
};
|
||||
}
|
@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraMetricsPublisher.h"
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
@ -17,8 +17,8 @@ namespace ix
|
||||
const std::string CobraMetricsPublisher::kSetRateControlId = "sms_set_rate_control_id";
|
||||
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
|
||||
|
||||
CobraMetricsPublisher::CobraMetricsPublisher() :
|
||||
_enabled(true)
|
||||
CobraMetricsPublisher::CobraMetricsPublisher()
|
||||
: _enabled(true)
|
||||
{
|
||||
}
|
||||
|
||||
@ -27,8 +27,7 @@ namespace ix
|
||||
;
|
||||
}
|
||||
|
||||
void CobraMetricsPublisher::configure(const CobraConfig& config,
|
||||
const std::string& channel)
|
||||
void CobraMetricsPublisher::configure(const CobraConfig& config, const std::string& channel)
|
||||
{
|
||||
// Configure the satori connection and start its publish background thread
|
||||
_cobra_metrics_theaded_publisher.configure(config, channel);
|
||||
@ -42,7 +41,7 @@ namespace ix
|
||||
}
|
||||
|
||||
void CobraMetricsPublisher::setGenericAttributes(const std::string& attrName,
|
||||
const Json::Value& value)
|
||||
const Json::Value& value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_device_mutex);
|
||||
_device[attrName] = value;
|
||||
@ -107,8 +106,7 @@ namespace ix
|
||||
auto last_update = _last_update.find(id);
|
||||
if (last_update == _last_update.end()) return false;
|
||||
|
||||
auto timeDeltaFromLastSend =
|
||||
std::chrono::steady_clock::now() - last_update->second;
|
||||
auto timeDeltaFromLastSend = std::chrono::steady_clock::now() - last_update->second;
|
||||
|
||||
return timeDeltaFromLastSend < std::chrono::seconds(rate_control_it->second);
|
||||
}
|
||||
@ -123,8 +121,7 @@ namespace ix
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto ms =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()).count();
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||
|
||||
return ms;
|
||||
}
|
||||
@ -165,10 +162,9 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
CobraConnection::MsgId CobraMetricsPublisher::push(
|
||||
const std::string& id,
|
||||
const Json::Value& data,
|
||||
bool shouldPushTest)
|
||||
CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id,
|
||||
const Json::Value& data,
|
||||
bool shouldPushTest)
|
||||
{
|
||||
if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId;
|
||||
|
||||
|
@ -40,8 +40,7 @@ namespace ix
|
||||
|
||||
/// Configuration / set keys, etc...
|
||||
/// All input data but the channel name is encrypted with rc4
|
||||
void configure(const CobraConfig& config,
|
||||
const std::string& channel);
|
||||
void configure(const CobraConfig& config, const std::string& channel);
|
||||
|
||||
/// Setter for the list of blacklisted metrics ids.
|
||||
/// That list is sorted internally for fast lookups
|
||||
@ -68,10 +67,14 @@ namespace ix
|
||||
/// shouldPush method for places where we want to be as lightweight as possible when
|
||||
/// collecting metrics. When set to false, it is used so that we don't do double work when
|
||||
/// computing whether a metrics should be sent or not.
|
||||
CobraConnection::MsgId push(const std::string& id, const Json::Value& data, bool shouldPushTest = true);
|
||||
CobraConnection::MsgId push(const std::string& id,
|
||||
const Json::Value& data,
|
||||
bool shouldPushTest = true);
|
||||
|
||||
/// Interface used by lua. msg is a json encoded string.
|
||||
CobraConnection::MsgId push(const std::string& id, const std::string& data, bool shouldPushTest = true);
|
||||
CobraConnection::MsgId push(const std::string& id,
|
||||
const std::string& data,
|
||||
bool shouldPushTest = true);
|
||||
|
||||
/// Tells whether a metric can be pushed.
|
||||
/// A metric can be pushed if it satisfies those conditions:
|
||||
@ -89,10 +92,16 @@ namespace ix
|
||||
void setGenericAttributes(const std::string& attrName, const Json::Value& value);
|
||||
|
||||
/// Set a unique id for the session. A uuid can be used.
|
||||
void setSession(const std::string& session) { _session = session; }
|
||||
void setSession(const std::string& session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
/// Get the unique id used to identify the current session
|
||||
const std::string& getSession() const { return _session; }
|
||||
const std::string& getSession() const
|
||||
{
|
||||
return _session;
|
||||
}
|
||||
|
||||
/// Return the number of milliseconds since the epoch (~1970)
|
||||
uint64_t getMillisecondsSinceEpoch() const;
|
||||
|
@ -5,72 +5,77 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraMetricsThreadedPublisher.h"
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher() :
|
||||
_stop(false)
|
||||
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher()
|
||||
: _stop(false)
|
||||
{
|
||||
_cobra_connection.setEventCallback(
|
||||
[]
|
||||
(ix::CobraConnectionEventType eventType,
|
||||
const std::string& errMsg,
|
||||
const ix::WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId)
|
||||
_cobra_connection.setEventCallback([](const CobraEventPtr& event) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Handshake headers" << std::endl;
|
||||
|
||||
if (eventType == ix::CobraConnection_EventType_Open)
|
||||
for (auto&& it : event->headers)
|
||||
{
|
||||
ss << "Handshake headers" << std::endl;
|
||||
ss << it.first << ": " << it.second << std::endl;
|
||||
}
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Authenticated)
|
||||
{
|
||||
ss << "Authenticated";
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Error)
|
||||
{
|
||||
ss << "Error: " << event->errMsg;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Closed)
|
||||
{
|
||||
ss << "Connection closed: " << event->errMsg;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Subscribed)
|
||||
{
|
||||
ss << "Subscribed through subscription id: " << event->subscriptionId;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||
{
|
||||
ss << "Unsubscribed through subscription id: " << event->subscriptionId;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Published)
|
||||
{
|
||||
ss << "Published message " << event->msgId << " acked";
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Pong)
|
||||
{
|
||||
ss << "Received websocket pong";
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::HandshakeError)
|
||||
{
|
||||
ss << "Handshake error: " << event->errMsg;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::AuthenticationError)
|
||||
{
|
||||
ss << "Authentication error: " << event->errMsg;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::SubscriptionError)
|
||||
{
|
||||
ss << "Subscription error: " << event->errMsg;
|
||||
}
|
||||
|
||||
for (auto it : headers)
|
||||
{
|
||||
ss << it.first << ": " << it.second << std::endl;
|
||||
}
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||
{
|
||||
ss << "Authenticated";
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
||||
{
|
||||
ss << "Error: " << errMsg;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Closed)
|
||||
{
|
||||
ss << "Connection closed: " << errMsg;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||
{
|
||||
ss << "Subscribed through subscription id: " << subscriptionId;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
||||
{
|
||||
ss << "Unsubscribed through subscription id: " << subscriptionId;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
||||
{
|
||||
ss << "Published message " << msgId << " acked";
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Pong)
|
||||
{
|
||||
ss << "Received websocket pong";
|
||||
}
|
||||
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
CoreLogger::log(ss.str().c_str());
|
||||
});
|
||||
}
|
||||
|
||||
@ -95,11 +100,10 @@ namespace ix
|
||||
void CobraMetricsThreadedPublisher::configure(const CobraConfig& config,
|
||||
const std::string& channel)
|
||||
{
|
||||
ix::IXCoreLogger::Log(config.socketTLSOptions.getDescription().c_str());
|
||||
CoreLogger::log(config.socketTLSOptions.getDescription().c_str());
|
||||
|
||||
_channel = channel;
|
||||
_cobra_connection.configure(config);
|
||||
|
||||
}
|
||||
|
||||
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
|
||||
@ -157,13 +161,15 @@ namespace ix
|
||||
{
|
||||
_cobra_connection.suspend();
|
||||
continue;
|
||||
}; break;
|
||||
};
|
||||
break;
|
||||
|
||||
case MessageKind::Resume:
|
||||
{
|
||||
_cobra_connection.resume();
|
||||
continue;
|
||||
}; break;
|
||||
};
|
||||
break;
|
||||
|
||||
case MessageKind::Message:
|
||||
{
|
||||
@ -171,7 +177,8 @@ namespace ix
|
||||
{
|
||||
_cobra_connection.publishNext();
|
||||
}
|
||||
}; break;
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,7 @@ namespace ix
|
||||
~CobraMetricsThreadedPublisher();
|
||||
|
||||
/// Configuration / set keys, etc...
|
||||
void configure(const CobraConfig& config,
|
||||
const std::string& channel);
|
||||
void configure(const CobraConfig& config, const std::string& channel);
|
||||
|
||||
/// Start the worker thread, used for background publishing
|
||||
void start();
|
||||
|
@ -1,14 +1,44 @@
|
||||
#include "ixcore/utils/IXCoreLogger.h"
|
||||
/*
|
||||
* IXCoreLogger.cpp
|
||||
* Author: Thomas Wells, Benjamin Sergeant
|
||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "ixcore/utils/IXCoreLogger.h"
|
||||
|
||||
namespace ix
|
||||
{
|
||||
// Default do nothing logger
|
||||
IXCoreLogger::LogFunc IXCoreLogger::_currentLogger = [](const char* /*msg*/){};
|
||||
// Default do a no-op logger
|
||||
CoreLogger::LogFunc CoreLogger::_currentLogger = [](const char*, LogLevel) {};
|
||||
|
||||
void IXCoreLogger::Log(const char* msg)
|
||||
{
|
||||
_currentLogger(msg);
|
||||
}
|
||||
void CoreLogger::log(const char* msg, LogLevel level)
|
||||
{
|
||||
_currentLogger(msg, level);
|
||||
}
|
||||
|
||||
} // ix
|
||||
void CoreLogger::debug(const std::string& msg)
|
||||
{
|
||||
_currentLogger(msg.c_str(), LogLevel::Debug);
|
||||
}
|
||||
|
||||
void CoreLogger::info(const std::string& msg)
|
||||
{
|
||||
_currentLogger(msg.c_str(), LogLevel::Info);
|
||||
}
|
||||
|
||||
void CoreLogger::warn(const std::string& msg)
|
||||
{
|
||||
_currentLogger(msg.c_str(), LogLevel::Warning);
|
||||
}
|
||||
|
||||
void CoreLogger::error(const std::string& msg)
|
||||
{
|
||||
_currentLogger(msg.c_str(), LogLevel::Error);
|
||||
}
|
||||
|
||||
void CoreLogger::critical(const std::string& msg)
|
||||
{
|
||||
_currentLogger(msg.c_str(), LogLevel::Critical);
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
@ -1,15 +1,41 @@
|
||||
/*
|
||||
* IXCoreLogger.h
|
||||
* Author: Thomas Wells, Benjamin Sergeant
|
||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class IXCoreLogger
|
||||
enum class LogLevel
|
||||
{
|
||||
Debug = 0,
|
||||
Info = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
Critical = 4
|
||||
};
|
||||
|
||||
class CoreLogger
|
||||
{
|
||||
public:
|
||||
using LogFunc = std::function<void(const char*)>;
|
||||
static void Log(const char* msg);
|
||||
using LogFunc = std::function<void(const char*, LogLevel level)>;
|
||||
|
||||
static void setLogFunction(LogFunc& func) { _currentLogger = func; }
|
||||
static void log(const char* msg, LogLevel level = LogLevel::Debug);
|
||||
|
||||
static void debug(const std::string& msg);
|
||||
static void info(const std::string& msg);
|
||||
static void warn(const std::string& msg);
|
||||
static void error(const std::string& msg);
|
||||
static void critical(const std::string& msg);
|
||||
|
||||
static void setLogFunction(LogFunc& func)
|
||||
{
|
||||
_currentLogger = func;
|
||||
}
|
||||
|
||||
private:
|
||||
static LogFunc _currentLogger;
|
||||
|
@ -29,10 +29,9 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
static const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
std::string base64_encode(const std::string& data, size_t len)
|
||||
{
|
||||
@ -50,26 +49,26 @@ namespace ix
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
while(len--)
|
||||
while (len--)
|
||||
{
|
||||
char_array_3[i++] = *(bytes_to_encode++);
|
||||
if(i == 3)
|
||||
if (i == 3)
|
||||
{
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for(i = 0; (i <4) ; i++)
|
||||
for (i = 0; (i < 4); i++)
|
||||
ret += base64_chars[char_array_4[i]];
|
||||
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(i)
|
||||
if (i)
|
||||
{
|
||||
for(j = i; j < 3; j++)
|
||||
for (j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
@ -77,12 +76,11 @@ namespace ix
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for(j = 0; (j < i + 1); j++)
|
||||
for (j = 0; (j < i + 1); j++)
|
||||
ret += base64_chars[char_array_4[j]];
|
||||
|
||||
while((i++ < 3))
|
||||
while ((i++ < 3))
|
||||
ret += '=';
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -95,7 +93,7 @@ namespace ix
|
||||
|
||||
std::string base64_decode(const std::string& encoded_string)
|
||||
{
|
||||
int in_len = (int)encoded_string.size();
|
||||
int in_len = (int) encoded_string.size();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int in_ = 0;
|
||||
@ -103,40 +101,42 @@ namespace ix
|
||||
std::string ret;
|
||||
ret.reserve(((in_len + 3) / 4) * 3);
|
||||
|
||||
while(in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
|
||||
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
|
||||
{
|
||||
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||
if(i ==4)
|
||||
char_array_4[i++] = encoded_string[in_];
|
||||
in_++;
|
||||
if (i == 4)
|
||||
{
|
||||
for(i = 0; i <4; i++)
|
||||
for (i = 0; i < 4; i++)
|
||||
char_array_4[i] = base64_chars.find(char_array_4[i]);
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for(i = 0; (i < 3); i++)
|
||||
for (i = 0; (i < 3); i++)
|
||||
ret += char_array_3[i];
|
||||
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(i)
|
||||
if (i)
|
||||
{
|
||||
for(j = i; j <4; j++)
|
||||
for (j = i; j < 4; j++)
|
||||
char_array_4[j] = 0;
|
||||
|
||||
for(j = 0; j <4; j++)
|
||||
for (j = 0; j < 4; j++)
|
||||
char_array_4[j] = base64_chars.find(char_array_4[j]);
|
||||
|
||||
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||
|
||||
for(j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||
for (j = 0; (j < i - 1); j++)
|
||||
ret += char_array_3[j];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,16 +5,17 @@
|
||||
*/
|
||||
|
||||
#include "IXHMac.h"
|
||||
|
||||
#include "IXBase64.h"
|
||||
|
||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||
# include <mbedtls/md.h>
|
||||
#include <mbedtls/md.h>
|
||||
#elif defined(__APPLE__)
|
||||
# include <CommonCrypto/CommonHMAC.h>
|
||||
#include <CommonCrypto/CommonHMAC.h>
|
||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||
# include <openssl/hmac.h>
|
||||
#include <openssl/hmac.h>
|
||||
#else
|
||||
# include <assert.h>
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
@ -26,19 +27,21 @@ namespace ix
|
||||
|
||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5),
|
||||
(unsigned char *) key.c_str(), key.size(),
|
||||
(unsigned char *) data.c_str(), data.size(),
|
||||
(unsigned char *) &hash);
|
||||
(unsigned char*) key.c_str(),
|
||||
key.size(),
|
||||
(unsigned char*) data.c_str(),
|
||||
data.size(),
|
||||
(unsigned char*) &hash);
|
||||
#elif defined(__APPLE__)
|
||||
CCHmac(kCCHmacAlgMD5,
|
||||
key.c_str(), key.size(),
|
||||
data.c_str(), data.size(),
|
||||
&hash);
|
||||
CCHmac(kCCHmacAlgMD5, key.c_str(), key.size(), data.c_str(), data.size(), &hash);
|
||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||
HMAC(EVP_md5(),
|
||||
key.c_str(), (int) key.size(),
|
||||
(unsigned char *) data.c_str(), (int) data.size(),
|
||||
(unsigned char *) hash, nullptr);
|
||||
key.c_str(),
|
||||
(int) key.size(),
|
||||
(unsigned char*) data.c_str(),
|
||||
(int) data.size(),
|
||||
(unsigned char*) hash,
|
||||
nullptr);
|
||||
#else
|
||||
assert(false && "hmac not implemented on this platform");
|
||||
#endif
|
||||
@ -47,4 +50,4 @@ namespace ix
|
||||
|
||||
return base64_encode(hashString, (uint32_t) hashString.size());
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -19,4 +19,4 @@ namespace ix
|
||||
|
||||
return hashAddress;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -16,23 +16,23 @@
|
||||
|
||||
#include "IXUuid.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class Uuid
|
||||
{
|
||||
public:
|
||||
Uuid();
|
||||
std::string toString() const;
|
||||
public:
|
||||
Uuid();
|
||||
std::string toString() const;
|
||||
|
||||
private:
|
||||
uint64_t _ab;
|
||||
uint64_t _cd;
|
||||
private:
|
||||
uint64_t _ab;
|
||||
uint64_t _cd;
|
||||
};
|
||||
|
||||
Uuid::Uuid()
|
||||
@ -60,7 +60,7 @@ namespace ix
|
||||
ss << std::setw(8) << (a);
|
||||
ss << std::setw(4) << (b >> 16);
|
||||
ss << std::setw(4) << (b & 0xFFFF);
|
||||
ss << std::setw(4) << (c >> 16 );
|
||||
ss << std::setw(4) << (c >> 16);
|
||||
ss << std::setw(4) << (c & 0xFFFF);
|
||||
ss << std::setw(8) << d;
|
||||
|
||||
@ -72,4 +72,4 @@ namespace ix
|
||||
Uuid id;
|
||||
return id.toString();
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -7,12 +7,12 @@
|
||||
#include "IXSentryClient.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketVersion.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -64,7 +64,8 @@ namespace ix
|
||||
std::string SentryClient::computeAuthHeader()
|
||||
{
|
||||
std::string securityHeader("Sentry sentry_version=5");
|
||||
securityHeader += ",sentry_client=ws/1.0.0";
|
||||
securityHeader += ",sentry_client=ws/";
|
||||
securityHeader += std::string(IX_WEBSOCKET_VERSION);
|
||||
securityHeader += ",sentry_timestamp=" + std::to_string(SentryClient::getTimestamp());
|
||||
securityHeader += ",sentry_key=" + _publicKey;
|
||||
securityHeader += ",sentry_secret=" + _secretKey;
|
||||
@ -225,44 +226,44 @@ namespace ix
|
||||
return _jsonWriter.write(payload);
|
||||
}
|
||||
|
||||
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg, bool verbose)
|
||||
void SentryClient::send(
|
||||
const Json::Value& msg,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
{
|
||||
auto args = _httpClient->createRequest();
|
||||
args->url = _url;
|
||||
args->verb = HttpClient::kPost;
|
||||
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
||||
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
||||
args->body = computePayload(msg);
|
||||
|
||||
std::string body = computePayload(msg);
|
||||
HttpResponsePtr response = _httpClient->post(_url, body, args);
|
||||
|
||||
return std::make_pair(response, body);
|
||||
_httpClient->performRequest(args, onResponseCallback);
|
||||
}
|
||||
|
||||
// https://sentry.io/api/12345/minidump?sentry_key=abcdefgh");
|
||||
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "https://sentry.io/api/"
|
||||
<< project
|
||||
<< "/minidump?sentry_key="
|
||||
<< key;
|
||||
ss << "https://sentry.io/api/" << project << "/minidump?sentry_key=" << key;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
//
|
||||
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
|
||||
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp
|
||||
// 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
|
||||
//
|
||||
void SentryClient::uploadMinidump(
|
||||
const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
void SentryClient::uploadMinidump(const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
{
|
||||
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
|
||||
|
||||
@ -273,7 +274,7 @@ namespace ix
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->multipartBoundary = multipartBoundary;
|
||||
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
||||
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
||||
|
||||
HttpFormDataParameters httpFormDataParameters;
|
||||
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
|
||||
@ -282,7 +283,27 @@ namespace ix
|
||||
httpParameters["sentry"] = sentryMetadata;
|
||||
|
||||
args->url = computeUrl(project, key);
|
||||
args->body = _httpClient->serializeHttpFormDataParameters(multipartBoundary, httpFormDataParameters, httpParameters);
|
||||
args->body = _httpClient->serializeHttpFormDataParameters(
|
||||
multipartBoundary, httpFormDataParameters, httpParameters);
|
||||
|
||||
_httpClient->performRequest(args, onResponseCallback);
|
||||
}
|
||||
|
||||
void SentryClient::uploadPayload(const Json::Value& payload,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
{
|
||||
auto args = _httpClient->createRequest();
|
||||
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||
args->verb = HttpClient::kPost;
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
||||
|
||||
args->url = _url;
|
||||
args->body = _jsonWriter.write(payload);
|
||||
|
||||
_httpClient->performRequest(args, onResponseCallback);
|
||||
}
|
||||
|
@ -10,8 +10,8 @@
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <json/json.h>
|
||||
#include <regex>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -21,20 +21,26 @@ namespace ix
|
||||
SentryClient(const std::string& dsn);
|
||||
~SentryClient() = default;
|
||||
|
||||
std::pair<HttpResponsePtr, std::string> send(const Json::Value& msg, bool verbose);
|
||||
void send(const Json::Value& msg,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
void uploadMinidump(const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
void uploadPayload(const Json::Value& payload,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||
|
||||
// Mostly for testing
|
||||
void setTLSOptions(const SocketTLSOptions& tlsOptions);
|
||||
|
||||
void uploadMinidump(
|
||||
const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
private:
|
||||
int64_t getTimestamp();
|
||||
|
@ -6,10 +6,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
namespace snake
|
||||
{
|
||||
|
@ -28,10 +28,7 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
CancellationRequest cancellationRequest = []() -> bool
|
||||
{
|
||||
return false;
|
||||
};
|
||||
CancellationRequest cancellationRequest = []() -> bool { return false; };
|
||||
|
||||
std::string errMsg;
|
||||
return _socket->connect(hostname, port, errMsg, cancellationRequest);
|
||||
@ -252,9 +249,8 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string RedisClient::prepareXaddCommand(
|
||||
const std::string& stream,
|
||||
const std::string& message)
|
||||
std::string RedisClient::prepareXaddCommand(const std::string& stream,
|
||||
const std::string& message)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "*5\r\n";
|
||||
@ -328,7 +324,9 @@ namespace ix
|
||||
return streamId;
|
||||
}
|
||||
|
||||
bool RedisClient::sendCommand(const std::string& commands, int commandsCount, std::string& errMsg)
|
||||
bool RedisClient::sendCommand(const std::string& commands,
|
||||
int commandsCount,
|
||||
std::string& errMsg)
|
||||
{
|
||||
bool sent = _socket->writeBytes(commands, nullptr);
|
||||
if (!sent)
|
||||
|
@ -8,11 +8,10 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class RedisClient
|
||||
@ -39,14 +38,11 @@ namespace ix
|
||||
const OnRedisSubscribeCallback& callback);
|
||||
|
||||
// XADD
|
||||
std::string xadd(
|
||||
const std::string& channel,
|
||||
const std::string& message,
|
||||
std::string& errMsg);
|
||||
std::string xadd(const std::string& channel,
|
||||
const std::string& message,
|
||||
std::string& errMsg);
|
||||
|
||||
std::string prepareXaddCommand(
|
||||
const std::string& stream,
|
||||
const std::string& message);
|
||||
std::string prepareXaddCommand(const std::string& stream, const std::string& message);
|
||||
|
||||
std::string readXaddReply(std::string& errMsg);
|
||||
|
||||
|
@ -6,17 +6,18 @@
|
||||
|
||||
#include "IXRedisServer.h"
|
||||
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXSocketConnect.h>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXCancellationRequest.h>
|
||||
#include <fstream>
|
||||
#include <ixwebsocket/IXCancellationRequest.h>
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketConnect.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
||||
RedisServer::RedisServer(
|
||||
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||
, _connectedClientsCount(0)
|
||||
, _stopHandlingConnections(false)
|
||||
@ -114,8 +115,7 @@ namespace ix
|
||||
for (auto it : _subscribers)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Subscription id: " << it.first
|
||||
<< " #subscribers: " << it.second.size();
|
||||
ss << "Subscription id: " << it.first << " #subscribers: " << it.second.size();
|
||||
|
||||
logInfo(ss.str());
|
||||
}
|
||||
@ -126,8 +126,7 @@ namespace ix
|
||||
return _connectedClientsCount;
|
||||
}
|
||||
|
||||
bool RedisServer::startsWith(const std::string& str,
|
||||
const std::string& start)
|
||||
bool RedisServer::startsWith(const std::string& str, const std::string& start)
|
||||
{
|
||||
return str.compare(0, start.length(), start) == 0;
|
||||
}
|
||||
@ -144,9 +143,8 @@ namespace ix
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool RedisServer::parseRequest(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
std::vector<std::string>& tokens)
|
||||
bool RedisServer::parseRequest(std::unique_ptr<Socket>& socket,
|
||||
std::vector<std::string>& tokens)
|
||||
{
|
||||
// Parse first line
|
||||
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
||||
@ -190,9 +188,8 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handleCommand(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
bool RedisServer::handleCommand(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 1) return false;
|
||||
|
||||
@ -207,31 +204,30 @@ namespace ix
|
||||
//
|
||||
ss << "*6\r\n";
|
||||
ss << writeString("publish"); // 1
|
||||
ss << ":3\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":2\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
ss << ":3\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":2\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
|
||||
//
|
||||
// subscribe
|
||||
//
|
||||
ss << "*6\r\n";
|
||||
ss << writeString("subscribe"); // 1
|
||||
ss << ":2\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":1\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
ss << ":2\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":1\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
|
||||
socket->writeBytes(ss.str(), cb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handleSubscribe(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
bool RedisServer::handleSubscribe(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 2) return false;
|
||||
|
||||
@ -250,9 +246,8 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handlePublish(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
bool RedisServer::handlePublish(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 3) return false;
|
||||
|
||||
@ -281,9 +276,7 @@ namespace ix
|
||||
|
||||
// return the number of clients that received the message.
|
||||
std::stringstream ss;
|
||||
ss << ":"
|
||||
<< std::to_string(subscribers.size())
|
||||
<< "\r\n";
|
||||
ss << ":" << std::to_string(subscribers.size()) << "\r\n";
|
||||
socket->writeBytes(ss.str(), cb);
|
||||
|
||||
return true;
|
||||
|
@ -6,13 +6,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXSocketServer.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketServer.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility> // pair
|
||||
@ -50,18 +50,14 @@ namespace ix
|
||||
bool startsWith(const std::string& str, const std::string& start);
|
||||
std::string writeString(const std::string& str);
|
||||
|
||||
bool parseRequest(
|
||||
std::unique_ptr<Socket>& socket,
|
||||
std::vector<std::string>& tokens);
|
||||
bool parseRequest(std::unique_ptr<Socket>& socket, std::vector<std::string>& tokens);
|
||||
|
||||
bool handlePublish(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
bool handlePublish(std::unique_ptr<Socket>& socket, const std::vector<std::string>& tokens);
|
||||
|
||||
bool handleSubscribe(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
|
||||
bool handleCommand(std::unique_ptr<Socket>& socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
bool handleCommand(std::unique_ptr<Socket>& socket, const std::vector<std::string>& tokens);
|
||||
|
||||
void cleanupSubscribers(std::unique_ptr<Socket>& socket);
|
||||
};
|
||||
|
@ -10,9 +10,9 @@
|
||||
#include "IXSnakeConnectionState.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <iostream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace snake
|
||||
@ -189,7 +189,8 @@ namespace snake
|
||||
nlohmann::json response = {
|
||||
{"action", "rtm/subscription/data"},
|
||||
{"id", id++},
|
||||
{"body", {{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||
{"body",
|
||||
{{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||
|
||||
ws->sendText(response.dump());
|
||||
};
|
||||
@ -197,7 +198,7 @@ namespace snake
|
||||
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
|
||||
std::stringstream ss;
|
||||
ss << "Redis Response: " << redisResponse << "...";
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
ix::CoreLogger::log(ss.str().c_str());
|
||||
|
||||
// Success
|
||||
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
||||
@ -209,7 +210,7 @@ namespace snake
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Subscribing to " << appChannel << "...";
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
ix::CoreLogger::log(ss.str().c_str());
|
||||
}
|
||||
|
||||
if (!redisClient.subscribe(appChannel, responseCallback, callback))
|
||||
@ -251,7 +252,21 @@ namespace snake
|
||||
const AppConfig& appConfig,
|
||||
const std::string& str)
|
||||
{
|
||||
auto pdu = nlohmann::json::parse(str);
|
||||
nlohmann::json pdu;
|
||||
try
|
||||
{
|
||||
pdu = nlohmann::json::parse(str);
|
||||
}
|
||||
catch (const nlohmann::json::parse_error& e)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "malformed json pdu: " << e.what() << " -> " << str << "";
|
||||
|
||||
nlohmann::json response = {{"body", {{"error", "invalid_json"}, {"reason", ss.str()}}}};
|
||||
ws->sendText(response.dump());
|
||||
return;
|
||||
}
|
||||
|
||||
auto action = pdu["action"];
|
||||
|
||||
if (action == "auth/handshake")
|
||||
|
@ -10,8 +10,8 @@
|
||||
#include "IXSnakeConnectionState.h"
|
||||
#include "IXSnakeProtocol.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace snake
|
||||
@ -29,7 +29,7 @@ namespace snake
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Listening on " << appConfig.hostname << ":" << appConfig.port;
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
ix::CoreLogger::log(ss.str().c_str());
|
||||
}
|
||||
|
||||
//
|
||||
@ -67,6 +67,7 @@ namespace snake
|
||||
webSocket->setOnMessageCallback(
|
||||
[this, webSocket, state](const ix::WebSocketMessagePtr& msg) {
|
||||
std::stringstream ss;
|
||||
ix::LogLevel logLevel = ix::LogLevel::Debug;
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
ss << "New connection" << std::endl;
|
||||
@ -86,6 +87,7 @@ namespace snake
|
||||
_appConfig.redisPort))
|
||||
{
|
||||
ss << "Cannot connect to redis host" << std::endl;
|
||||
logLevel = ix::LogLevel::Error;
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
@ -101,6 +103,7 @@ namespace snake
|
||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||
logLevel = ix::LogLevel::Error;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
||||
{
|
||||
@ -112,7 +115,7 @@ namespace snake
|
||||
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
||||
}
|
||||
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
ix::CoreLogger::log(ss.str().c_str(), logLevel);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
|
||||
namespace ix
|
||||
@ -13,6 +14,8 @@ namespace ix
|
||||
CancellationRequest makeCancellationRequestWithTimeout(
|
||||
int secs, std::atomic<bool>& requestInitCancellation)
|
||||
{
|
||||
assert(secs > 0);
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto timeout = std::chrono::seconds(secs);
|
||||
|
||||
|
@ -4,6 +4,19 @@
|
||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
//
|
||||
// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to returns wchar_t
|
||||
// which is different from all other platforms. We want the non unicode version.
|
||||
// See https://github.com/microsoft/vcpkg/pull/11030
|
||||
// We could do this in IXNetSystem.cpp but so far we are only using gai_strerror in here.
|
||||
//
|
||||
#ifdef _UNICODE
|
||||
#undef _UNICODE
|
||||
#endif
|
||||
#ifdef UNICODE
|
||||
#undef UNICODE
|
||||
#endif
|
||||
|
||||
#include "IXDNSLookup.h"
|
||||
|
||||
#include "IXNetSystem.h"
|
||||
|
@ -78,12 +78,12 @@ namespace ix
|
||||
WebSocketHttpHeaders extraHeaders;
|
||||
std::string body;
|
||||
std::string multipartBoundary;
|
||||
int connectTimeout;
|
||||
int transferTimeout;
|
||||
bool followRedirects;
|
||||
int maxRedirects;
|
||||
bool verbose;
|
||||
bool compress;
|
||||
int connectTimeout = 60;
|
||||
int transferTimeout = 1800;
|
||||
bool followRedirects = true;
|
||||
int maxRedirects = 5;
|
||||
bool verbose = false;
|
||||
bool compress = true;
|
||||
Logger logger;
|
||||
OnProgressCallback onProgressCallback;
|
||||
};
|
||||
|
@ -25,10 +25,12 @@ namespace ix
|
||||
const std::string HttpClient::kHead = "HEAD";
|
||||
const std::string HttpClient::kDel = "DEL";
|
||||
const std::string HttpClient::kPut = "PUT";
|
||||
const std::string HttpClient::kPatch = "PATCH";
|
||||
|
||||
HttpClient::HttpClient(bool async)
|
||||
: _async(async)
|
||||
, _stop(false)
|
||||
, _forceBody(false)
|
||||
{
|
||||
if (!_async) return;
|
||||
|
||||
@ -49,6 +51,11 @@ namespace ix
|
||||
_tlsOptions = tlsOptions;
|
||||
}
|
||||
|
||||
void HttpClient::setForceBody(bool value)
|
||||
{
|
||||
_forceBody = value;
|
||||
}
|
||||
|
||||
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
|
||||
{
|
||||
auto request = std::make_shared<HttpRequestArgs>();
|
||||
@ -192,7 +199,7 @@ namespace ix
|
||||
ss << "User-Agent: " << userAgent() << "\r\n";
|
||||
}
|
||||
|
||||
if (verb == kPost || verb == kPut)
|
||||
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
|
||||
{
|
||||
ss << "Content-Length: " << body.size() << "\r\n";
|
||||
|
||||
@ -220,11 +227,10 @@ namespace ix
|
||||
|
||||
std::string req(ss.str());
|
||||
std::string errMsg;
|
||||
std::atomic<bool> requestInitCancellation(false);
|
||||
|
||||
// Make a cancellation object dealing with connection timeout
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(args->connectTimeout, requestInitCancellation);
|
||||
makeCancellationRequestWithTimeout(args->connectTimeout, _stop);
|
||||
|
||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
||||
if (!success)
|
||||
@ -242,8 +248,7 @@ namespace ix
|
||||
}
|
||||
|
||||
// Make a new cancellation object dealing with transfer timeout
|
||||
isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(args->transferTimeout, requestInitCancellation);
|
||||
isCancellationRequested = makeCancellationRequestWithTimeout(args->transferTimeout, _stop);
|
||||
|
||||
if (args->verbose)
|
||||
{
|
||||
@ -562,6 +567,20 @@ namespace ix
|
||||
return request(url, kPut, body, args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::patch(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kPatch, serializeHttpParameters(httpParameters), args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::patch(const std::string& url,
|
||||
const std::string& body,
|
||||
const HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kPatch, body, args);
|
||||
}
|
||||
|
||||
std::string HttpClient::urlEncode(const std::string& value)
|
||||
{
|
||||
std::ostringstream escaped;
|
||||
|
@ -46,12 +46,19 @@ namespace ix
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args);
|
||||
|
||||
HttpResponsePtr patch(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
HttpRequestArgsPtr args);
|
||||
HttpResponsePtr patch(const std::string& url,
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args);
|
||||
|
||||
HttpResponsePtr request(const std::string& url,
|
||||
const std::string& verb,
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args,
|
||||
int redirects = 0);
|
||||
|
||||
void setForceBody(bool value);
|
||||
// Async API
|
||||
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
|
||||
const std::string& verb = HttpClient::kGet);
|
||||
@ -78,6 +85,7 @@ namespace ix
|
||||
const static std::string kHead;
|
||||
const static std::string kDel;
|
||||
const static std::string kPut;
|
||||
const static std::string kPatch;
|
||||
|
||||
private:
|
||||
void log(const std::string& msg, HttpRequestArgsPtr args);
|
||||
@ -86,7 +94,6 @@ namespace ix
|
||||
|
||||
// Async API background thread runner
|
||||
void run();
|
||||
|
||||
// Async API
|
||||
bool _async;
|
||||
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
|
||||
@ -99,5 +106,7 @@ namespace ix
|
||||
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
|
||||
|
||||
SocketTLSOptions _tlsOptions;
|
||||
|
||||
bool _forceBody;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -19,6 +19,7 @@ typedef unsigned long int nfds_t;
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
|
@ -1,115 +0,0 @@
|
||||
/*
|
||||
* IXSelectInterruptEventFd.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
//
|
||||
// On Linux we use eventd to wake up select.
|
||||
//
|
||||
|
||||
//
|
||||
// Linux/Android has a special type of virtual files. select(2) will react
|
||||
// when reading/writing to those files, unlike closing sockets.
|
||||
//
|
||||
// https://linux.die.net/man/2/eventfd
|
||||
// http://www.sourcexr.com/articles/2013/10/26/lightweight-inter-process-signaling-with-eventfd
|
||||
//
|
||||
// eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4)
|
||||
// is on Kernel 3.x
|
||||
//
|
||||
// cf Android/Kernel table here
|
||||
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
|
||||
//
|
||||
// On macOS we use UNIX pipes to wake up select.
|
||||
//
|
||||
|
||||
#include "IXSelectInterruptEventFd.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <string.h> // for strerror
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h> // for write
|
||||
|
||||
namespace ix
|
||||
{
|
||||
SelectInterruptEventFd::SelectInterruptEventFd()
|
||||
{
|
||||
_eventfd = -1;
|
||||
}
|
||||
|
||||
SelectInterruptEventFd::~SelectInterruptEventFd()
|
||||
{
|
||||
::close(_eventfd);
|
||||
}
|
||||
|
||||
bool SelectInterruptEventFd::init(std::string& errorMsg)
|
||||
{
|
||||
// calling init twice is a programming error
|
||||
assert(_eventfd == -1);
|
||||
|
||||
_eventfd = eventfd(0, 0);
|
||||
if (_eventfd < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptEventFd::init() failed in eventfd()"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
|
||||
_eventfd = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fcntl(_eventfd, F_SETFL, O_NONBLOCK) == -1)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SelectInterruptEventFd::init() failed in fcntl() call"
|
||||
<< " : " << strerror(errno);
|
||||
errorMsg = ss.str();
|
||||
|
||||
_eventfd = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectInterruptEventFd::notify(uint64_t value)
|
||||
{
|
||||
int fd = _eventfd;
|
||||
|
||||
if (fd == -1) return false;
|
||||
|
||||
// we should write 8 bytes for an uint64_t
|
||||
return write(fd, &value, sizeof(value)) == 8;
|
||||
}
|
||||
|
||||
// TODO: return max uint64_t for errors ?
|
||||
uint64_t SelectInterruptEventFd::read()
|
||||
{
|
||||
int fd = _eventfd;
|
||||
|
||||
uint64_t value = 0;
|
||||
::read(fd, &value, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
bool SelectInterruptEventFd::clear()
|
||||
{
|
||||
if (_eventfd == -1) return false;
|
||||
|
||||
// 0 is a special value ; select will not wake up
|
||||
uint64_t value = 0;
|
||||
|
||||
// we should write 8 bytes for an uint64_t
|
||||
return write(_eventfd, &value, sizeof(value)) == 8;
|
||||
}
|
||||
|
||||
int SelectInterruptEventFd::getFd() const
|
||||
{
|
||||
return _eventfd;
|
||||
}
|
||||
} // namespace ix
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* IXSelectInterruptEventFd.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXSelectInterrupt.h"
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SelectInterruptEventFd final : public SelectInterrupt
|
||||
{
|
||||
public:
|
||||
SelectInterruptEventFd();
|
||||
virtual ~SelectInterruptEventFd();
|
||||
|
||||
bool init(std::string& errorMsg) final;
|
||||
|
||||
bool notify(uint64_t value) final;
|
||||
bool clear() final;
|
||||
uint64_t read() final;
|
||||
int getFd() const final;
|
||||
|
||||
private:
|
||||
int _eventfd;
|
||||
};
|
||||
} // namespace ix
|
@ -376,7 +376,8 @@ namespace ix
|
||||
{
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
{
|
||||
return std::make_pair(false, std::string());
|
||||
const std::string errorMsg("Cancellation Requested");
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
|
||||
size_t size = std::min(kChunkSize, length - output.size());
|
||||
@ -388,7 +389,8 @@ namespace ix
|
||||
}
|
||||
else if (ret <= 0 && !Socket::isWaitNeeded())
|
||||
{
|
||||
return std::make_pair(false, std::string());
|
||||
const std::string errorMsg("Recv Error");
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
|
||||
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
|
||||
@ -397,7 +399,8 @@ namespace ix
|
||||
// This way we are not busy looping
|
||||
if (isReadyToRead(1) == PollResultType::Error)
|
||||
{
|
||||
return std::make_pair(false, std::string());
|
||||
const std::string errorMsg("Poll Error");
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
/*
|
||||
* IXSocketAppleSSL.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Adapted from Satori SDK Apple SSL code.
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||
|
||||
#include "IXSocketAppleSSL.h"
|
||||
|
||||
#include "IXSocketConnect.h"
|
||||
@ -307,3 +309,5 @@ namespace ix
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||
|
@ -1,8 +1,9 @@
|
||||
/*
|
||||
* IXSocketAppleSSL.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -47,3 +48,5 @@ namespace ix
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT
|
||||
|
@ -1,12 +1,13 @@
|
||||
/*
|
||||
* IXSocketMbedTLS.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
* Author: Benjamin Sergeant, Max Weisel
|
||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Some code taken from
|
||||
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
|
||||
* and mini_client.c example from mbedtls
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
|
||||
#include "IXSocketMbedTLS.h"
|
||||
|
||||
@ -42,6 +43,55 @@ namespace ix
|
||||
mbedtls_pk_init(&_pkey);
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
|
||||
CERT_SYSTEM_STORE_CURRENT_USER;
|
||||
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
|
||||
|
||||
if (!systemStore)
|
||||
{
|
||||
errorMsg = "CertOpenStore failed with ";
|
||||
errorMsg += std::to_string(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
PCCERT_CONTEXT certificateIterator = NULL;
|
||||
|
||||
int certificateCount = 0;
|
||||
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
|
||||
{
|
||||
if (certificateIterator->dwCertEncodingType & X509_ASN_ENCODING)
|
||||
{
|
||||
int ret = mbedtls_x509_crt_parse(&_cacert,
|
||||
certificateIterator->pbCertEncoded,
|
||||
certificateIterator->cbCertEncoded);
|
||||
if (ret == 0)
|
||||
{
|
||||
++certificateCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CertFreeCertificateContext(certificateIterator);
|
||||
CertCloseStore(systemStore, 0);
|
||||
|
||||
if (certificateCount == 0)
|
||||
{
|
||||
errorMsg = "No certificates found";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
// On macOS we can query the system cert location from the keychain
|
||||
// On Linux we could try to fetch some local files based on the distribution
|
||||
// On Android we could use JNI to get to the system certs
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
|
||||
{
|
||||
initMBedTLS();
|
||||
@ -95,18 +145,36 @@ namespace ix
|
||||
}
|
||||
else
|
||||
{
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
|
||||
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
|
||||
if (_tlsOptions.isUsingSystemDefaults())
|
||||
{
|
||||
; // FIXME
|
||||
if (!loadSystemCertificates(errMsg))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
|
||||
else
|
||||
{
|
||||
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||
return false;
|
||||
if (_tlsOptions.isUsingInMemoryCAs())
|
||||
{
|
||||
const char* buffer = _tlsOptions.caFile.c_str();
|
||||
size_t bufferSize =
|
||||
_tlsOptions.caFile.size() + 1; // Needs to include null terminating
|
||||
// character otherwise mbedtls will fail.
|
||||
if (mbedtls_x509_crt_parse(
|
||||
&_cacert, (const unsigned char*) buffer, bufferSize) < 0)
|
||||
{
|
||||
errMsg = "Cannot parse CA from memory.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
|
||||
{
|
||||
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
||||
@ -280,3 +348,5 @@ namespace ix
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_MBED_TLS
|
||||
|
@ -1,8 +1,9 @@
|
||||
/*
|
||||
* IXSocketMbedTLS.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -51,6 +52,9 @@ namespace ix
|
||||
|
||||
bool init(const std::string& host, bool isClient, std::string& errMsg);
|
||||
void initMBedTLS();
|
||||
bool loadSystemCertificates(std::string& errMsg);
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_MBED_TLS
|
||||
|
@ -1,10 +1,11 @@
|
||||
/*
|
||||
* IXSocketOpenSSL.cpp
|
||||
* Author: Benjamin Sergeant, Matt DeBoer
|
||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||
* Author: Benjamin Sergeant, Matt DeBoer, Max Weisel
|
||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Adapted from Satori SDK OpenSSL code.
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_OPEN_SSL
|
||||
|
||||
#include "IXSocketOpenSSL.h"
|
||||
|
||||
@ -21,6 +22,57 @@
|
||||
#endif
|
||||
#define socketerrno errno
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace
|
||||
{
|
||||
bool loadWindowsSystemCertificates(SSL_CTX* ssl, std::string& errorMsg)
|
||||
{
|
||||
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
|
||||
CERT_SYSTEM_STORE_CURRENT_USER;
|
||||
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
|
||||
|
||||
if (!systemStore)
|
||||
{
|
||||
errorMsg = "CertOpenStore failed with ";
|
||||
errorMsg += std::to_string(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
PCCERT_CONTEXT certificateIterator = NULL;
|
||||
X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl);
|
||||
|
||||
int certificateCount = 0;
|
||||
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
|
||||
{
|
||||
X509* x509 = d2i_X509(NULL,
|
||||
(const unsigned char**) &certificateIterator->pbCertEncoded,
|
||||
certificateIterator->cbCertEncoded);
|
||||
|
||||
if (x509)
|
||||
{
|
||||
if (X509_STORE_add_cert(opensslStore, x509) == 1)
|
||||
{
|
||||
++certificateCount;
|
||||
}
|
||||
|
||||
X509_free(x509);
|
||||
}
|
||||
}
|
||||
|
||||
CertFreeCertificateContext(certificateIterator);
|
||||
CertCloseStore(systemStore, 0);
|
||||
|
||||
if (certificateCount == 0)
|
||||
{
|
||||
errorMsg = "No certificates found";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
const std::string kDefaultCiphers =
|
||||
@ -33,6 +85,8 @@ namespace ix
|
||||
|
||||
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
||||
std::once_flag SocketOpenSSL::_openSSLInitFlag;
|
||||
std::unique_ptr<std::mutex[]> SocketOpenSSL::_openSSLMutexes =
|
||||
std::make_unique<std::mutex[]>(CRYPTO_num_locks());
|
||||
|
||||
SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
@ -54,6 +108,11 @@ namespace ix
|
||||
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
|
||||
#else
|
||||
(void) OPENSSL_config(nullptr);
|
||||
|
||||
if (CRYPTO_get_locking_callback() == nullptr)
|
||||
{
|
||||
CRYPTO_set_locking_callback(SocketOpenSSL::openSSLLockingCallback);
|
||||
}
|
||||
#endif
|
||||
|
||||
(void) OpenSSL_add_ssl_algorithms();
|
||||
@ -62,6 +121,21 @@ namespace ix
|
||||
_openSSLInitializationSuccessful = true;
|
||||
}
|
||||
|
||||
void SocketOpenSSL::openSSLLockingCallback(int mode,
|
||||
int type,
|
||||
const char* /*file*/,
|
||||
int /*line*/)
|
||||
{
|
||||
if (mode & CRYPTO_LOCK)
|
||||
{
|
||||
_openSSLMutexes[type].lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
_openSSLMutexes[type].unlock();
|
||||
}
|
||||
}
|
||||
|
||||
std::string SocketOpenSSL::getSSLError(int ret)
|
||||
{
|
||||
unsigned long e;
|
||||
@ -143,6 +217,66 @@ namespace ix
|
||||
return ctx;
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::openSSLAddCARootsFromString(const std::string roots)
|
||||
{
|
||||
// Create certificate store
|
||||
X509_STORE* certificate_store = SSL_CTX_get_cert_store(_ssl_context);
|
||||
if (certificate_store == nullptr) return false;
|
||||
|
||||
// Configure to allow intermediate certs
|
||||
X509_STORE_set_flags(certificate_store,
|
||||
X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_PARTIAL_CHAIN);
|
||||
|
||||
// Create a new buffer and populate it with the roots
|
||||
BIO* buffer = BIO_new_mem_buf((void*) roots.c_str(), static_cast<int>(roots.length()));
|
||||
if (buffer == nullptr) return false;
|
||||
|
||||
// Read each root in the buffer and add to the certificate store
|
||||
bool success = true;
|
||||
size_t number_of_roots = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Read the next root in the buffer
|
||||
X509* root = PEM_read_bio_X509_AUX(buffer, nullptr, nullptr, (void*) "");
|
||||
if (root == nullptr)
|
||||
{
|
||||
// No more certs left in the buffer, we're done.
|
||||
ERR_clear_error();
|
||||
break;
|
||||
}
|
||||
|
||||
// Try adding the root to the certificate store
|
||||
ERR_clear_error();
|
||||
if (!X509_STORE_add_cert(certificate_store, root))
|
||||
{
|
||||
// Failed to add. If the error is unrelated to the x509 lib or the cert already
|
||||
// exists, we're safe to continue.
|
||||
unsigned long error = ERR_get_error();
|
||||
if (ERR_GET_LIB(error) != ERR_LIB_X509 ||
|
||||
ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE)
|
||||
{
|
||||
// Failed. Clean up and bail.
|
||||
success = false;
|
||||
X509_free(root);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up and loop
|
||||
X509_free(root);
|
||||
number_of_roots++;
|
||||
}
|
||||
|
||||
// Clean up buffer
|
||||
BIO_free(buffer);
|
||||
|
||||
// Make sure we loaded at least one certificate.
|
||||
if (number_of_roots == 0) success = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a hostname matches a pattern
|
||||
*/
|
||||
@ -336,6 +470,12 @@ namespace ix
|
||||
{
|
||||
if (_tlsOptions.isUsingSystemDefaults())
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!loadWindowsSystemCertificates(_ssl_context, errMsg))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
@ -343,21 +483,34 @@ namespace ix
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (SSL_CTX_load_verify_locations(
|
||||
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
|
||||
else
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + _tlsOptions.caFile +
|
||||
"\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
if (_tlsOptions.isUsingInMemoryCAs())
|
||||
{
|
||||
// Load from memory
|
||||
openSSLAddCARootsFromString(_tlsOptions.caFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SSL_CTX_load_verify_locations(
|
||||
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
||||
_tlsOptions.caFile + "\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(_ssl_context,
|
||||
SSL_VERIFY_PEER,
|
||||
[](int preverify, X509_STORE_CTX*) -> int { return preverify; });
|
||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||
SSL_CTX_set_verify(
|
||||
_ssl_context, SSL_VERIFY_PEER, [](int preverify, X509_STORE_CTX*) -> int {
|
||||
return preverify;
|
||||
});
|
||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -467,26 +620,35 @@ namespace ix
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* root_ca_file = _tlsOptions.caFile.c_str();
|
||||
STACK_OF(X509_NAME) * rootCAs;
|
||||
rootCAs = SSL_load_client_CA_file(root_ca_file);
|
||||
if (rootCAs == NULL)
|
||||
if (_tlsOptions.isUsingInMemoryCAs())
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + _tlsOptions.caFile +
|
||||
"') failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
// Load from memory
|
||||
openSSLAddCARootsFromString(_tlsOptions.caFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
|
||||
if (SSL_CTX_load_verify_locations(_ssl_context, root_ca_file, nullptr) != 1)
|
||||
const char* root_ca_file = _tlsOptions.caFile.c_str();
|
||||
STACK_OF(X509_NAME) * rootCAs;
|
||||
rootCAs = SSL_load_client_CA_file(root_ca_file);
|
||||
if (rootCAs == NULL)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
||||
_tlsOptions.caFile + "\") failed: ";
|
||||
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" +
|
||||
_tlsOptions.caFile + "') failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
|
||||
if (SSL_CTX_load_verify_locations(
|
||||
_ssl_context, root_ca_file, nullptr) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
||||
_tlsOptions.caFile + "\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -673,3 +835,5 @@ namespace ix
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_OPEN_SSL
|
||||
|
@ -1,8 +1,9 @@
|
||||
/*
|
||||
* IXSocketOpenSSL.h
|
||||
* Author: Benjamin Sergeant, Matt DeBoer
|
||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#ifdef IXWEBSOCKET_USE_OPEN_SSL
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -39,6 +40,7 @@ namespace ix
|
||||
void openSSLInitialize();
|
||||
std::string getSSLError(int ret);
|
||||
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
||||
bool openSSLAddCARootsFromString(const std::string roots);
|
||||
bool openSSLClientHandshake(const std::string& hostname,
|
||||
std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested);
|
||||
@ -47,6 +49,9 @@ namespace ix
|
||||
bool handleTLSOptions(std::string& errMsg);
|
||||
bool openSSLServerHandshake(std::string& errMsg);
|
||||
|
||||
// Required for OpenSSL < 1.1
|
||||
static void openSSLLockingCallback(int mode, int type, const char* /*file*/, int /*line*/);
|
||||
|
||||
SSL* _ssl_connection;
|
||||
SSL_CTX* _ssl_context;
|
||||
const SSL_METHOD* _ssl_method;
|
||||
@ -56,6 +61,9 @@ namespace ix
|
||||
|
||||
static std::once_flag _openSSLInitFlag;
|
||||
static std::atomic<bool> _openSSLInitializationSuccessful;
|
||||
static std::unique_ptr<std::mutex[]> _openSSLMutexes;
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
||||
#endif // IXWEBSOCKET_USE_OPEN_SSL
|
||||
|
@ -15,6 +15,7 @@ namespace ix
|
||||
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
|
||||
const char* kTLSCAFileDisableVerify = "NONE";
|
||||
const char* kTLSCiphersUseDefault = "DEFAULT";
|
||||
const char* kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
|
||||
|
||||
bool SocketTLSOptions::isValid() const
|
||||
{
|
||||
@ -58,6 +59,11 @@ namespace ix
|
||||
return caFile == kTLSCAFileUseSystemDefaults;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isUsingInMemoryCAs() const
|
||||
{
|
||||
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
||||
{
|
||||
return caFile == kTLSCAFileDisableVerify;
|
||||
|
@ -37,6 +37,8 @@ namespace ix
|
||||
|
||||
bool isUsingSystemDefaults() const;
|
||||
|
||||
bool isUsingInMemoryCAs() const;
|
||||
|
||||
bool isPeerVerifyDisabled() const;
|
||||
|
||||
bool isUsingDefaultCiphers() const;
|
||||
|
@ -44,6 +44,18 @@ namespace ix
|
||||
return err;
|
||||
}
|
||||
|
||||
bool UdpSocket::isWaitNeeded()
|
||||
{
|
||||
int err = getErrno();
|
||||
|
||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UdpSocket::closeSocket(int fd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@ -62,6 +74,13 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
unsigned long nonblocking = 1;
|
||||
ioctlsocket(_sockfd, FIONBIO, &nonblocking);
|
||||
#else
|
||||
fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
|
||||
#endif
|
||||
|
||||
memset(&_server, 0, sizeof(_server));
|
||||
_server.sin_family = AF_INET;
|
||||
_server.sin_port = htons(port);
|
||||
@ -93,4 +112,15 @@ namespace ix
|
||||
return (ssize_t)::sendto(
|
||||
_sockfd, buffer.data(), buffer.size(), 0, (struct sockaddr*) &_server, sizeof(_server));
|
||||
}
|
||||
|
||||
ssize_t UdpSocket::recvfrom(char* buffer, size_t length)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int addressLen = (int) sizeof(_server);
|
||||
#else
|
||||
socklen_t addressLen = (socklen_t) sizeof(_server);
|
||||
#endif
|
||||
return (ssize_t)::recvfrom(
|
||||
_sockfd, buffer, length, 0, (struct sockaddr*) &_server, &addressLen);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -28,9 +28,12 @@ namespace ix
|
||||
// Virtual methods
|
||||
bool init(const std::string& host, int port, std::string& errMsg);
|
||||
ssize_t sendto(const std::string& buffer);
|
||||
ssize_t recvfrom(char* buffer, size_t length);
|
||||
|
||||
void close();
|
||||
|
||||
static int getErrno();
|
||||
static bool isWaitNeeded();
|
||||
static void closeSocket(int fd);
|
||||
|
||||
private:
|
||||
|
@ -1,4 +1,29 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* IXUrlParser.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
@ -6,7 +31,309 @@
|
||||
|
||||
#include "IXUrlParser.h"
|
||||
|
||||
#include "LUrlParser.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace
|
||||
{
|
||||
enum LUrlParserError
|
||||
{
|
||||
LUrlParserError_Ok = 0,
|
||||
LUrlParserError_Uninitialized = 1,
|
||||
LUrlParserError_NoUrlCharacter = 2,
|
||||
LUrlParserError_InvalidSchemeName = 3,
|
||||
LUrlParserError_NoDoubleSlash = 4,
|
||||
LUrlParserError_NoAtSign = 5,
|
||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||
LUrlParserError_NoSlash = 7,
|
||||
};
|
||||
|
||||
class clParseURL
|
||||
{
|
||||
public:
|
||||
LUrlParserError m_ErrorCode;
|
||||
std::string m_Scheme;
|
||||
std::string m_Host;
|
||||
std::string m_Port;
|
||||
std::string m_Path;
|
||||
std::string m_Query;
|
||||
std::string m_Fragment;
|
||||
std::string m_UserName;
|
||||
std::string m_Password;
|
||||
|
||||
clParseURL()
|
||||
: m_ErrorCode(LUrlParserError_Uninitialized)
|
||||
{
|
||||
}
|
||||
|
||||
/// return 'true' if the parsing was successful
|
||||
bool IsValid() const
|
||||
{
|
||||
return m_ErrorCode == LUrlParserError_Ok;
|
||||
}
|
||||
|
||||
/// helper to convert the port number to int, return 'true' if the port is valid (within the
|
||||
/// 0..65535 range)
|
||||
bool GetPort(int* OutPort) const;
|
||||
|
||||
/// parse the URL
|
||||
static clParseURL ParseURL(const std::string& URL);
|
||||
|
||||
private:
|
||||
explicit clParseURL(LUrlParserError ErrorCode)
|
||||
: m_ErrorCode(ErrorCode)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static bool IsSchemeValid(const std::string& SchemeName)
|
||||
{
|
||||
for (auto c : SchemeName)
|
||||
{
|
||||
if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool clParseURL::GetPort(int* OutPort) const
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int Port = atoi(m_Port.c_str());
|
||||
|
||||
if (Port <= 0 || Port > 65535)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutPort)
|
||||
{
|
||||
*OutPort = Port;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// based on RFC 1738 and RFC 3986
|
||||
clParseURL clParseURL::ParseURL(const std::string& URL)
|
||||
{
|
||||
clParseURL Result;
|
||||
|
||||
const char* CurrentString = URL.c_str();
|
||||
|
||||
/*
|
||||
* <scheme>:<scheme-specific-part>
|
||||
* <scheme> := [a-z\+\-\.]+
|
||||
* For resiliency, programs interpreting URLs should treat upper case letters as
|
||||
*equivalent to lower case in scheme names
|
||||
*/
|
||||
|
||||
// try to read scheme
|
||||
{
|
||||
const char* LocalString = strchr(CurrentString, ':');
|
||||
|
||||
if (!LocalString)
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoUrlCharacter);
|
||||
}
|
||||
|
||||
// save the scheme name
|
||||
Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
if (!IsSchemeValid(Result.m_Scheme))
|
||||
{
|
||||
return clParseURL(LUrlParserError_InvalidSchemeName);
|
||||
}
|
||||
|
||||
// scheme should be lowercase
|
||||
std::transform(
|
||||
Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower);
|
||||
|
||||
// skip ':'
|
||||
CurrentString = LocalString + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* //<user>:<password>@<host>:<port>/<url-path>
|
||||
* any ":", "@" and "/" must be normalized
|
||||
*/
|
||||
|
||||
// skip "//"
|
||||
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||
|
||||
// check if the user name and password are specified
|
||||
bool bHasUserName = false;
|
||||
|
||||
const char* LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
{
|
||||
if (*LocalString == '@')
|
||||
{
|
||||
// user name and password are specified
|
||||
bHasUserName = true;
|
||||
break;
|
||||
}
|
||||
else if (*LocalString == '/')
|
||||
{
|
||||
// end of <host>:<port> specification
|
||||
bHasUserName = false;
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
// user name and password
|
||||
LocalString = CurrentString;
|
||||
|
||||
if (bHasUserName)
|
||||
{
|
||||
// read user name
|
||||
while (*LocalString && *LocalString != ':' && *LocalString != '@')
|
||||
LocalString++;
|
||||
|
||||
Result.m_UserName = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
// proceed with the current pointer
|
||||
CurrentString = LocalString;
|
||||
|
||||
if (*CurrentString == ':')
|
||||
{
|
||||
// skip ':'
|
||||
CurrentString++;
|
||||
|
||||
// read password
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '@')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Password = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// skip '@'
|
||||
if (*CurrentString != '@')
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoAtSign);
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
}
|
||||
|
||||
bool bHasBracket = (*CurrentString == '[');
|
||||
|
||||
// go ahead, read the host name
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
{
|
||||
if (bHasBracket && *LocalString == ']')
|
||||
{
|
||||
// end of IPv6 address
|
||||
LocalString++;
|
||||
break;
|
||||
}
|
||||
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
|
||||
{
|
||||
// port number is specified
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
Result.m_Host = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// is port number specified?
|
||||
if (*CurrentString == ':')
|
||||
{
|
||||
CurrentString++;
|
||||
|
||||
// read port number
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '/')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Port = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// end of string
|
||||
if (!*CurrentString)
|
||||
{
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// skip '/'
|
||||
if (*CurrentString != '/')
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoSlash);
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
|
||||
// parse the path
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '#' && *LocalString != '?')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Path = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// check for query
|
||||
if (*CurrentString == '?')
|
||||
{
|
||||
// skip '?'
|
||||
CurrentString++;
|
||||
|
||||
// read query
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '#')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Query = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// check for fragment
|
||||
if (*CurrentString == '#')
|
||||
{
|
||||
// skip '#'
|
||||
CurrentString++;
|
||||
|
||||
// read fragment
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
LocalString++;
|
||||
|
||||
Result.m_Fragment = std::string(CurrentString, LocalString - CurrentString);
|
||||
}
|
||||
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -17,7 +344,7 @@ namespace ix
|
||||
std::string& query,
|
||||
int& port)
|
||||
{
|
||||
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
|
||||
clParseURL res = clParseURL::ParseURL(url);
|
||||
|
||||
if (!res.IsValid())
|
||||
{
|
||||
|
@ -34,7 +34,7 @@ namespace ix
|
||||
_ws.setOnCloseCallback(
|
||||
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
|
||||
_onMessageCallback(
|
||||
std::make_shared<WebSocketMessage>(WebSocketMessageType::Close,
|
||||
std::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
|
||||
"",
|
||||
wireSize,
|
||||
WebSocketErrorInfo(),
|
||||
@ -193,7 +193,7 @@ namespace ix
|
||||
return status;
|
||||
}
|
||||
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(
|
||||
WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
@ -225,7 +225,7 @@ namespace ix
|
||||
}
|
||||
|
||||
_onMessageCallback(
|
||||
std::make_shared<WebSocketMessage>(WebSocketMessageType::Open,
|
||||
std::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
WebSocketErrorInfo(),
|
||||
@ -310,7 +310,7 @@ namespace ix
|
||||
connectErr.reason = status.errorStr;
|
||||
connectErr.http_status = status.http_status;
|
||||
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(WebSocketMessageType::Error,
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
|
||||
"",
|
||||
0,
|
||||
connectErr,
|
||||
@ -386,7 +386,7 @@ namespace ix
|
||||
|
||||
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
|
||||
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(webSocketMessageType,
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(webSocketMessageType,
|
||||
msg,
|
||||
wireSize,
|
||||
webSocketErrorInfo,
|
||||
|
@ -6,6 +6,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct WebSocketCloseInfo
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUrlParser.h"
|
||||
#include "IXUserAgent.h"
|
||||
#include "libwshandshake.hpp"
|
||||
#include "IXWebSocketHandshakeKeyGen.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
@ -133,7 +133,7 @@ namespace ix
|
||||
|
||||
for (auto& it : extraHeaders)
|
||||
{
|
||||
ss << it.first << ":" << it.second << "\r\n";
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
}
|
||||
|
||||
if (_enablePerMessageDeflate)
|
||||
|
171
ixwebsocket/IXWebSocketHandshakeKeyGen.h
Normal file
171
ixwebsocket/IXWebSocketHandshakeKeyGen.h
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright (c) 2016 Alex Hultman and contributors
|
||||
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
class WebSocketHandshakeKeyGen
|
||||
{
|
||||
template<int N, typename T>
|
||||
struct static_for
|
||||
{
|
||||
void operator()(uint32_t* a, uint32_t* b)
|
||||
{
|
||||
static_for<N - 1, T>()(a, b);
|
||||
T::template f<N - 1>(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct static_for<0, T>
|
||||
{
|
||||
void operator()(uint32_t* /*a*/, uint32_t* /*hash*/)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template<int state>
|
||||
struct Sha1Loop
|
||||
{
|
||||
static inline uint32_t rol(uint32_t value, size_t bits)
|
||||
{
|
||||
return (value << bits) | (value >> (32 - bits));
|
||||
}
|
||||
static inline uint32_t blk(uint32_t b[16], size_t i)
|
||||
{
|
||||
return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1);
|
||||
}
|
||||
|
||||
template<int i>
|
||||
static inline void f(uint32_t* a, uint32_t* b)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 1:
|
||||
a[i % 5] +=
|
||||
((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) +
|
||||
b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5);
|
||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||
break;
|
||||
case 2:
|
||||
b[i] = blk(b, i);
|
||||
a[(1 + i) % 5] +=
|
||||
((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) +
|
||||
b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5);
|
||||
a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30);
|
||||
break;
|
||||
case 3:
|
||||
b[(i + 4) % 16] = blk(b, (i + 4) % 16);
|
||||
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) +
|
||||
b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5);
|
||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||
break;
|
||||
case 4:
|
||||
b[(i + 8) % 16] = blk(b, (i + 8) % 16);
|
||||
a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) |
|
||||
(a[(3 + i) % 5] & a[(2 + i) % 5])) +
|
||||
b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5);
|
||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||
break;
|
||||
case 5:
|
||||
b[(i + 12) % 16] = blk(b, (i + 12) % 16);
|
||||
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) +
|
||||
b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5);
|
||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||
break;
|
||||
case 6: b[i] += a[4 - i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static inline void sha1(uint32_t hash[5], uint32_t b[16])
|
||||
{
|
||||
uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]};
|
||||
static_for<16, Sha1Loop<1>>()(a, b);
|
||||
static_for<4, Sha1Loop<2>>()(a, b);
|
||||
static_for<20, Sha1Loop<3>>()(a, b);
|
||||
static_for<20, Sha1Loop<4>>()(a, b);
|
||||
static_for<20, Sha1Loop<5>>()(a, b);
|
||||
static_for<5, Sha1Loop<6>>()(a, hash);
|
||||
}
|
||||
|
||||
static inline void base64(unsigned char* src, char* dst)
|
||||
{
|
||||
const char* b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
for (int i = 0; i < 18; i += 3)
|
||||
{
|
||||
*dst++ = b64[(src[i] >> 2) & 63];
|
||||
*dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)];
|
||||
*dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)];
|
||||
*dst++ = b64[src[i + 2] & 63];
|
||||
}
|
||||
*dst++ = b64[(src[18] >> 2) & 63];
|
||||
*dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)];
|
||||
*dst++ = b64[((src[19] & 15) << 2)];
|
||||
*dst++ = '=';
|
||||
}
|
||||
|
||||
public:
|
||||
static inline void generate(const std::string& inputStr, char output[28])
|
||||
{
|
||||
char input[25] = {};
|
||||
strncpy(input, inputStr.c_str(), 25 - 1);
|
||||
input[25 - 1] = '\0';
|
||||
|
||||
uint32_t b_output[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
|
||||
uint32_t b_input[16] = {0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0x32353845,
|
||||
0x41464135,
|
||||
0x2d453931,
|
||||
0x342d3437,
|
||||
0x44412d39,
|
||||
0x3543412d,
|
||||
0x43354142,
|
||||
0x30444338,
|
||||
0x35423131,
|
||||
0x80000000};
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 |
|
||||
(input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24;
|
||||
}
|
||||
sha1(b_output, b_input);
|
||||
uint32_t last_b[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480};
|
||||
sha1(b_output, last_b);
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
uint32_t tmp = b_output[i];
|
||||
char* bytes = (char*) &b_output[i];
|
||||
bytes[3] = tmp & 0xff;
|
||||
bytes[2] = (tmp >> 8) & 0xff;
|
||||
bytes[1] = (tmp >> 16) & 0xff;
|
||||
bytes[0] = (tmp >> 24) & 0xff;
|
||||
}
|
||||
base64((unsigned char*) b_output, output);
|
||||
}
|
||||
};
|
@ -19,7 +19,7 @@ namespace ix
|
||||
struct WebSocketMessage
|
||||
{
|
||||
WebSocketMessageType type;
|
||||
std::string str;
|
||||
const std::string& str;
|
||||
size_t wireSize;
|
||||
WebSocketErrorInfo errorInfo;
|
||||
WebSocketOpenInfo openInfo;
|
||||
@ -34,7 +34,7 @@ namespace ix
|
||||
WebSocketCloseInfo c,
|
||||
bool b = false)
|
||||
: type(t)
|
||||
, str(std::move(s))
|
||||
, str(s)
|
||||
, wireSize(w)
|
||||
, errorInfo(e)
|
||||
, openInfo(o)
|
||||
@ -45,5 +45,5 @@ namespace ix
|
||||
}
|
||||
};
|
||||
|
||||
using WebSocketMessagePtr = std::shared_ptr<WebSocketMessage>;
|
||||
using WebSocketMessagePtr = std::unique_ptr<WebSocketMessage>;
|
||||
} // namespace ix
|
||||
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* IXWebSocketMessageQueue.cpp
|
||||
* Author: Korchynskyi Dmytro
|
||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXWebSocketMessageQueue.h"
|
||||
|
||||
namespace ix
|
||||
{
|
||||
WebSocketMessageQueue::WebSocketMessageQueue(WebSocket* websocket)
|
||||
{
|
||||
bindWebsocket(websocket);
|
||||
}
|
||||
|
||||
WebSocketMessageQueue::~WebSocketMessageQueue()
|
||||
{
|
||||
if (!_messages.empty())
|
||||
{
|
||||
// not handled all messages
|
||||
}
|
||||
|
||||
bindWebsocket(nullptr);
|
||||
}
|
||||
|
||||
void WebSocketMessageQueue::bindWebsocket(WebSocket* websocket)
|
||||
{
|
||||
if (_websocket == websocket) return;
|
||||
|
||||
// unbind old
|
||||
if (_websocket)
|
||||
{
|
||||
// set dummy callback just to avoid crash
|
||||
_websocket->setOnMessageCallback([](const WebSocketMessagePtr&) {});
|
||||
}
|
||||
|
||||
_websocket = websocket;
|
||||
|
||||
// bind new
|
||||
if (_websocket)
|
||||
{
|
||||
_websocket->setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
|
||||
std::lock_guard<std::mutex> lock(_messagesMutex);
|
||||
_messages.emplace_back(std::move(msg));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketMessageQueue::setOnMessageCallback(const OnMessageCallback& callback)
|
||||
{
|
||||
_onMessageUserCallback = callback;
|
||||
}
|
||||
|
||||
void WebSocketMessageQueue::setOnMessageCallback(OnMessageCallback&& callback)
|
||||
{
|
||||
_onMessageUserCallback = std::move(callback);
|
||||
}
|
||||
|
||||
WebSocketMessagePtr WebSocketMessageQueue::popMessage()
|
||||
{
|
||||
WebSocketMessagePtr message;
|
||||
std::lock_guard<std::mutex> lock(_messagesMutex);
|
||||
|
||||
if (!_messages.empty())
|
||||
{
|
||||
message = std::move(_messages.front());
|
||||
_messages.pop_front();
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
void WebSocketMessageQueue::poll(int count)
|
||||
{
|
||||
if (!_onMessageUserCallback) return;
|
||||
|
||||
WebSocketMessagePtr message;
|
||||
|
||||
while (count > 0 && (message = popMessage()))
|
||||
{
|
||||
_onMessageUserCallback(message);
|
||||
--count;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ix
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* IXWebSocketMessageQueue.h
|
||||
* Author: Korchynskyi Dmytro
|
||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXWebSocket.h"
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
//
|
||||
// A helper class to dispatch websocket message callbacks in your thread.
|
||||
//
|
||||
class WebSocketMessageQueue
|
||||
{
|
||||
public:
|
||||
WebSocketMessageQueue(WebSocket* websocket = nullptr);
|
||||
~WebSocketMessageQueue();
|
||||
|
||||
void bindWebsocket(WebSocket* websocket);
|
||||
|
||||
void setOnMessageCallback(const OnMessageCallback& callback);
|
||||
void setOnMessageCallback(OnMessageCallback&& callback);
|
||||
|
||||
void poll(int count = 512);
|
||||
|
||||
protected:
|
||||
WebSocketMessagePtr popMessage();
|
||||
|
||||
private:
|
||||
WebSocket* _websocket = nullptr;
|
||||
OnMessageCallback _onMessageUserCallback;
|
||||
std::mutex _messagesMutex;
|
||||
std::list<WebSocketMessagePtr> _messages;
|
||||
};
|
||||
} // namespace ix
|
@ -6,6 +6,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct WebSocketOpenInfo
|
||||
|
@ -87,6 +87,9 @@ namespace ix
|
||||
//
|
||||
size_t output;
|
||||
|
||||
// Clear output
|
||||
out.clear();
|
||||
|
||||
if (in.empty())
|
||||
{
|
||||
// See issue #167
|
||||
@ -174,6 +177,9 @@ namespace ix
|
||||
_inflateState.avail_in = (uInt) inFixed.size();
|
||||
_inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));
|
||||
|
||||
// Clear output
|
||||
out.clear();
|
||||
|
||||
do
|
||||
{
|
||||
_inflateState.avail_out = (uInt) _compressBufferSize;
|
||||
|
@ -62,7 +62,7 @@ namespace ix
|
||||
WebSocketTransport::WebSocketTransport()
|
||||
: _useMask(true)
|
||||
, _blockingSend(false)
|
||||
, _compressedMessage(false)
|
||||
, _receivedMessageCompressed(false)
|
||||
, _readyState(ReadyState::CLOSED)
|
||||
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
|
||||
, _closeReason(WebSocketCloseConstants::kInternalErrorMessage)
|
||||
@ -74,6 +74,7 @@ namespace ix
|
||||
, _enablePong(kDefaultEnablePong)
|
||||
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
||||
, _pongReceived(false)
|
||||
, _pingCount(0)
|
||||
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
||||
{
|
||||
_readbuf.resize(kChunkSize);
|
||||
@ -221,7 +222,8 @@ namespace ix
|
||||
{
|
||||
_pongReceived = false;
|
||||
std::stringstream ss;
|
||||
ss << kPingMessage << "::" << _pingIntervalSecs << "s";
|
||||
ss << kPingMessage << "::" << _pingIntervalSecs << "s"
|
||||
<< "::" << _pingCount++;
|
||||
return sendPing(ss.str());
|
||||
}
|
||||
|
||||
@ -493,7 +495,7 @@ namespace ix
|
||||
? MessageKind::MSG_TEXT
|
||||
: MessageKind::MSG_BINARY;
|
||||
|
||||
_compressedMessage = _enablePerMessageDeflate && ws.rsv1;
|
||||
_receivedMessageCompressed = _enablePerMessageDeflate && ws.rsv1;
|
||||
|
||||
// Continuation message needs to follow a non-fin TEXT or BINARY message
|
||||
if (!_chunks.empty())
|
||||
@ -515,10 +517,12 @@ namespace ix
|
||||
//
|
||||
if (ws.fin && _chunks.empty())
|
||||
{
|
||||
emitMessage(
|
||||
_fragmentedMessageKind, frameData, _compressedMessage, onMessageCallback);
|
||||
emitMessage(_fragmentedMessageKind,
|
||||
frameData,
|
||||
_receivedMessageCompressed,
|
||||
onMessageCallback);
|
||||
|
||||
_compressedMessage = false;
|
||||
_receivedMessageCompressed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -535,11 +539,11 @@ namespace ix
|
||||
{
|
||||
emitMessage(_fragmentedMessageKind,
|
||||
getMergedChunks(),
|
||||
_compressedMessage,
|
||||
_receivedMessageCompressed,
|
||||
onMessageCallback);
|
||||
|
||||
_chunks.clear();
|
||||
_compressedMessage = false;
|
||||
_receivedMessageCompressed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -712,17 +716,16 @@ namespace ix
|
||||
// When the RSV1 bit is 1 it means the message is compressed
|
||||
if (compressedMessage && messageKind != MessageKind::FRAGMENT)
|
||||
{
|
||||
std::string decompressedMessage;
|
||||
bool success = _perMessageDeflate->decompress(message, decompressedMessage);
|
||||
bool success = _perMessageDeflate->decompress(message, _decompressedMessage);
|
||||
|
||||
if (messageKind == MessageKind::MSG_TEXT && !validateUtf8(decompressedMessage))
|
||||
if (messageKind == MessageKind::MSG_TEXT && !validateUtf8(_decompressedMessage))
|
||||
{
|
||||
close(WebSocketCloseConstants::kInvalidFramePayloadData,
|
||||
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
|
||||
onMessageCallback(_decompressedMessage, wireSize, !success, messageKind);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -759,7 +762,6 @@ namespace ix
|
||||
|
||||
size_t payloadSize = message.size();
|
||||
size_t wireSize = message.size();
|
||||
std::string compressedMessage;
|
||||
bool compressionError = false;
|
||||
|
||||
std::string::const_iterator message_begin = message.begin();
|
||||
@ -767,7 +769,7 @@ namespace ix
|
||||
|
||||
if (compress)
|
||||
{
|
||||
if (!_perMessageDeflate->compress(message, compressedMessage))
|
||||
if (!_perMessageDeflate->compress(message, _compressedMessage))
|
||||
{
|
||||
bool success = false;
|
||||
compressionError = true;
|
||||
@ -776,10 +778,10 @@ namespace ix
|
||||
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
|
||||
}
|
||||
compressionError = false;
|
||||
wireSize = compressedMessage.size();
|
||||
wireSize = _compressedMessage.size();
|
||||
|
||||
message_begin = compressedMessage.begin();
|
||||
message_end = compressedMessage.end();
|
||||
message_begin = _compressedMessage.begin();
|
||||
message_end = _compressedMessage.end();
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -165,7 +165,7 @@ namespace ix
|
||||
MessageKind _fragmentedMessageKind;
|
||||
|
||||
// Ditto for whether a message is compressed
|
||||
bool _compressedMessage;
|
||||
bool _receivedMessageCompressed;
|
||||
|
||||
// Fragments are 32K long
|
||||
static constexpr size_t kChunkSize = 1 << 15;
|
||||
@ -189,6 +189,9 @@ namespace ix
|
||||
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
||||
std::atomic<bool> _enablePerMessageDeflate;
|
||||
|
||||
std::string _decompressedMessage;
|
||||
std::string _compressedMessage;
|
||||
|
||||
// Used to control TLS connection behavior
|
||||
SocketTLSOptions _socketTLSOptions;
|
||||
|
||||
@ -209,6 +212,7 @@ namespace ix
|
||||
|
||||
static const int kDefaultPingIntervalSecs;
|
||||
static const std::string kPingMessage;
|
||||
std::atomic<uint64_t> _pingCount;
|
||||
|
||||
// We record when ping are being sent so that we can know when to send the next one
|
||||
mutable std::mutex _lastSendPingTimePointMutex;
|
||||
|
@ -6,4 +6,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define IX_WEBSOCKET_VERSION "9.1.9"
|
||||
#define IX_WEBSOCKET_VERSION "9.6.4"
|
||||
|
@ -1,280 +0,0 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* 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 "LUrlParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
|
||||
// check if the scheme name is valid
|
||||
static bool IsSchemeValid(const std::string& SchemeName)
|
||||
{
|
||||
for (auto c : SchemeName)
|
||||
{
|
||||
if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LUrlParser::clParseURL::GetPort(int* OutPort) const
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int Port = atoi(m_Port.c_str());
|
||||
|
||||
if (Port <= 0 || Port > 65535)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutPort)
|
||||
{
|
||||
*OutPort = Port;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// based on RFC 1738 and RFC 3986
|
||||
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL(const std::string& URL)
|
||||
{
|
||||
LUrlParser::clParseURL Result;
|
||||
|
||||
const char* CurrentString = URL.c_str();
|
||||
|
||||
/*
|
||||
* <scheme>:<scheme-specific-part>
|
||||
* <scheme> := [a-z\+\-\.]+
|
||||
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to
|
||||
*lower case in scheme names
|
||||
*/
|
||||
|
||||
// try to read scheme
|
||||
{
|
||||
const char* LocalString = strchr(CurrentString, ':');
|
||||
|
||||
if (!LocalString)
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoUrlCharacter);
|
||||
}
|
||||
|
||||
// save the scheme name
|
||||
Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
if (!IsSchemeValid(Result.m_Scheme))
|
||||
{
|
||||
return clParseURL(LUrlParserError_InvalidSchemeName);
|
||||
}
|
||||
|
||||
// scheme should be lowercase
|
||||
std::transform(
|
||||
Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower);
|
||||
|
||||
// skip ':'
|
||||
CurrentString = LocalString + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* //<user>:<password>@<host>:<port>/<url-path>
|
||||
* any ":", "@" and "/" must be normalized
|
||||
*/
|
||||
|
||||
// skip "//"
|
||||
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
|
||||
|
||||
// check if the user name and password are specified
|
||||
bool bHasUserName = false;
|
||||
|
||||
const char* LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
{
|
||||
if (*LocalString == '@')
|
||||
{
|
||||
// user name and password are specified
|
||||
bHasUserName = true;
|
||||
break;
|
||||
}
|
||||
else if (*LocalString == '/')
|
||||
{
|
||||
// end of <host>:<port> specification
|
||||
bHasUserName = false;
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
// user name and password
|
||||
LocalString = CurrentString;
|
||||
|
||||
if (bHasUserName)
|
||||
{
|
||||
// read user name
|
||||
while (*LocalString && *LocalString != ':' && *LocalString != '@')
|
||||
LocalString++;
|
||||
|
||||
Result.m_UserName = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
// proceed with the current pointer
|
||||
CurrentString = LocalString;
|
||||
|
||||
if (*CurrentString == ':')
|
||||
{
|
||||
// skip ':'
|
||||
CurrentString++;
|
||||
|
||||
// read password
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '@')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Password = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// skip '@'
|
||||
if (*CurrentString != '@')
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoAtSign);
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
}
|
||||
|
||||
bool bHasBracket = (*CurrentString == '[');
|
||||
|
||||
// go ahead, read the host name
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
{
|
||||
if (bHasBracket && *LocalString == ']')
|
||||
{
|
||||
// end of IPv6 address
|
||||
LocalString++;
|
||||
break;
|
||||
}
|
||||
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
|
||||
{
|
||||
// port number is specified
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
Result.m_Host = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// is port number specified?
|
||||
if (*CurrentString == ':')
|
||||
{
|
||||
CurrentString++;
|
||||
|
||||
// read port number
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '/')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Port = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// end of string
|
||||
if (!*CurrentString)
|
||||
{
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// skip '/'
|
||||
if (*CurrentString != '/')
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoSlash);
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
|
||||
// parse the path
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '#' && *LocalString != '?')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Path = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// check for query
|
||||
if (*CurrentString == '?')
|
||||
{
|
||||
// skip '?'
|
||||
CurrentString++;
|
||||
|
||||
// read query
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString && *LocalString != '#')
|
||||
LocalString++;
|
||||
|
||||
Result.m_Query = std::string(CurrentString, LocalString - CurrentString);
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// check for fragment
|
||||
if (*CurrentString == '#')
|
||||
{
|
||||
// skip '#'
|
||||
CurrentString++;
|
||||
|
||||
// read fragment
|
||||
LocalString = CurrentString;
|
||||
|
||||
while (*LocalString)
|
||||
LocalString++;
|
||||
|
||||
Result.m_Fragment = std::string(CurrentString, LocalString - CurrentString);
|
||||
}
|
||||
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace LUrlParser
|
||||
{
|
||||
enum LUrlParserError
|
||||
{
|
||||
LUrlParserError_Ok = 0,
|
||||
LUrlParserError_Uninitialized = 1,
|
||||
LUrlParserError_NoUrlCharacter = 2,
|
||||
LUrlParserError_InvalidSchemeName = 3,
|
||||
LUrlParserError_NoDoubleSlash = 4,
|
||||
LUrlParserError_NoAtSign = 5,
|
||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||
LUrlParserError_NoSlash = 7,
|
||||
};
|
||||
|
||||
class clParseURL
|
||||
{
|
||||
public:
|
||||
LUrlParserError m_ErrorCode;
|
||||
std::string m_Scheme;
|
||||
std::string m_Host;
|
||||
std::string m_Port;
|
||||
std::string m_Path;
|
||||
std::string m_Query;
|
||||
std::string m_Fragment;
|
||||
std::string m_UserName;
|
||||
std::string m_Password;
|
||||
|
||||
clParseURL()
|
||||
: m_ErrorCode(LUrlParserError_Uninitialized)
|
||||
{
|
||||
}
|
||||
|
||||
/// return 'true' if the parsing was successful
|
||||
bool IsValid() const
|
||||
{
|
||||
return m_ErrorCode == LUrlParserError_Ok;
|
||||
}
|
||||
|
||||
/// helper to convert the port number to int, return 'true' if the port is valid (within the
|
||||
/// 0..65535 range)
|
||||
bool GetPort(int* OutPort) const;
|
||||
|
||||
/// parse the URL
|
||||
static clParseURL ParseURL(const std::string& URL);
|
||||
|
||||
private:
|
||||
explicit clParseURL(LUrlParserError ErrorCode)
|
||||
: m_ErrorCode(ErrorCode)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace LUrlParser
|
@ -1,135 +0,0 @@
|
||||
// Copyright (c) 2016 Alex Hultman and contributors
|
||||
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgement in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
|
||||
class WebSocketHandshakeKeyGen {
|
||||
template <int N, typename T>
|
||||
struct static_for {
|
||||
void operator()(uint32_t *a, uint32_t *b) {
|
||||
static_for<N - 1, T>()(a, b);
|
||||
T::template f<N - 1>(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct static_for<0, T> {
|
||||
void operator()(uint32_t * /*a*/, uint32_t * /*hash*/) {}
|
||||
};
|
||||
|
||||
template <int state>
|
||||
struct Sha1Loop {
|
||||
static inline uint32_t rol(uint32_t value, size_t bits) {return (value << bits) | (value >> (32 - bits));}
|
||||
static inline uint32_t blk(uint32_t b[16], size_t i) {
|
||||
return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1);
|
||||
}
|
||||
|
||||
template <int i>
|
||||
static inline void f(uint32_t *a, uint32_t *b) {
|
||||
switch (state) {
|
||||
case 1:
|
||||
a[i % 5] += ((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5);
|
||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||
break;
|
||||
case 2:
|
||||
b[i] = blk(b, i);
|
||||
a[(1 + i) % 5] += ((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5);
|
||||
a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30);
|
||||
break;
|
||||
case 3:
|
||||
b[(i + 4) % 16] = blk(b, (i + 4) % 16);
|
||||
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5);
|
||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||
break;
|
||||
case 4:
|
||||
b[(i + 8) % 16] = blk(b, (i + 8) % 16);
|
||||
a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) | (a[(3 + i) % 5] & a[(2 + i) % 5])) + b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5);
|
||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||
break;
|
||||
case 5:
|
||||
b[(i + 12) % 16] = blk(b, (i + 12) % 16);
|
||||
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5);
|
||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
||||
break;
|
||||
case 6:
|
||||
b[i] += a[4 - i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static inline void sha1(uint32_t hash[5], uint32_t b[16]) {
|
||||
uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]};
|
||||
static_for<16, Sha1Loop<1>>()(a, b);
|
||||
static_for<4, Sha1Loop<2>>()(a, b);
|
||||
static_for<20, Sha1Loop<3>>()(a, b);
|
||||
static_for<20, Sha1Loop<4>>()(a, b);
|
||||
static_for<20, Sha1Loop<5>>()(a, b);
|
||||
static_for<5, Sha1Loop<6>>()(a, hash);
|
||||
}
|
||||
|
||||
static inline void base64(unsigned char *src, char *dst) {
|
||||
const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
for (int i = 0; i < 18; i += 3) {
|
||||
*dst++ = b64[(src[i] >> 2) & 63];
|
||||
*dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)];
|
||||
*dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)];
|
||||
*dst++ = b64[src[i + 2] & 63];
|
||||
}
|
||||
*dst++ = b64[(src[18] >> 2) & 63];
|
||||
*dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)];
|
||||
*dst++ = b64[((src[19] & 15) << 2)];
|
||||
*dst++ = '=';
|
||||
}
|
||||
|
||||
public:
|
||||
static inline void generate(const std::string& inputStr, char output[28]) {
|
||||
|
||||
char input[25] = {};
|
||||
strncpy(input, inputStr.c_str(), 25 - 1);
|
||||
input[25 - 1] = '\0';
|
||||
|
||||
uint32_t b_output[5] = {
|
||||
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
|
||||
};
|
||||
uint32_t b_input[16] = {
|
||||
0, 0, 0, 0, 0, 0, 0x32353845, 0x41464135, 0x2d453931, 0x342d3437, 0x44412d39,
|
||||
0x3543412d, 0x43354142, 0x30444338, 0x35423131, 0x80000000
|
||||
};
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 | (input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24;
|
||||
}
|
||||
sha1(b_output, b_input);
|
||||
uint32_t last_b[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480};
|
||||
sha1(b_output, last_b);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
uint32_t tmp = b_output[i];
|
||||
char *bytes = (char *) &b_output[i];
|
||||
bytes[3] = tmp & 0xff;
|
||||
bytes[2] = (tmp >> 8) & 0xff;
|
||||
bytes[1] = (tmp >> 16) & 0xff;
|
||||
bytes[0] = (tmp >> 24) & 0xff;
|
||||
}
|
||||
base64((unsigned char *) b_output, output);
|
||||
}
|
||||
};
|
34
makefile
34
makefile
@ -20,13 +20,13 @@ install: brew
|
||||
# Release, Debug, MinSizeRel, RelWithDebInfo are the build types
|
||||
#
|
||||
brew:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4 install)
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install)
|
||||
|
||||
# Docker default target. We've add problem with OpenSSL and TLS 1.3 (on the
|
||||
# server side ?) and I can't work-around it easily, so we're using mbedtls on
|
||||
# Linux for the SSL backend, which works great.
|
||||
ws_mbedtls_install:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4 install)
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install)
|
||||
|
||||
ws:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j 4)
|
||||
@ -103,6 +103,10 @@ test_server:
|
||||
# env TEST=Websocket_chat make test
|
||||
# env TEST=heartbeat make test
|
||||
test:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_make:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
@ -116,27 +120,43 @@ test_ubsan:
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_asan:
|
||||
test_asan: build_test_asan
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
build_test_asan:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableAddressSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_tsan_openssl:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_ubsan_openssl:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableUndefinedBehaviorSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_tsan_openssl_release:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Release -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -configuration Release -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Release/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_tsan_mbedtls:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_MBED_TLS=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_openssl:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPEN_SSL=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
build_test_openssl:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPEN_SSL=1 -DUSE_TEST=1 .. ; ninja install)
|
||||
|
||||
test_openssl: build_test_openssl
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_mbedtls:
|
||||
build_test_mbedtls:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
|
||||
test_mbedtls: build_test_mbedtls
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_no_ssl:
|
||||
|
@ -54,6 +54,7 @@ set (SOURCES
|
||||
IXWebSocketSubProtocolTest.cpp
|
||||
IXSentryClientTest.cpp
|
||||
IXWebSocketChatTest.cpp
|
||||
IXWebSocketBroadcastTest.cpp
|
||||
)
|
||||
|
||||
# Some unittest don't work on windows yet
|
||||
@ -65,13 +66,7 @@ if (UNIX)
|
||||
IXCobraMetricsPublisherTest.cpp
|
||||
IXCobraToSentryBotTest.cpp
|
||||
IXCobraToStatsdBotTest.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
# Some unittest fail for dubious reason on Ubuntu Xenial with TSAN
|
||||
if (MAC OR WIN32)
|
||||
list(APPEND SOURCES
|
||||
IXWebSocketMessageQTest.cpp
|
||||
IXCobraToStdoutBotTest.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
|
@ -180,44 +180,40 @@ namespace
|
||||
_conn.configure(_cobraConfig);
|
||||
_conn.connect();
|
||||
|
||||
_conn.setEventCallback([this, channel](ix::CobraConnectionEventType eventType,
|
||||
const std::string& errMsg,
|
||||
const ix::WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId) {
|
||||
if (eventType == ix::CobraConnection_EventType_Open)
|
||||
_conn.setEventCallback([this, channel](const CobraEventPtr& event) {
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
log("Subscriber connected: " + _user);
|
||||
for (auto&& it : headers)
|
||||
for (auto&& it : event->headers)
|
||||
{
|
||||
log("Headers " + it.first + " " + it.second);
|
||||
}
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||
else if (event->type == ix::CobraEventType::Authenticated)
|
||||
{
|
||||
log("Subscriber authenticated: " + _user);
|
||||
subscribe(channel);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Error)
|
||||
else if (event->type == ix::CobraEventType::Error)
|
||||
{
|
||||
log(errMsg + _user);
|
||||
log(event->errMsg + _user);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Closed)
|
||||
else if (event->type == ix::CobraEventType::Closed)
|
||||
{
|
||||
log("Connection closed: " + _user);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||
else if (event->type == ix::CobraEventType::Subscribed)
|
||||
{
|
||||
log("Subscription ok: " + _user + " subscription_id " + subscriptionId);
|
||||
log("Subscription ok: " + _user + " subscription_id " + event->subscriptionId);
|
||||
_connectedAndSubscribed = true;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
||||
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||
{
|
||||
log("Unsubscription ok: " + _user + " subscription_id " + subscriptionId);
|
||||
log("Unsubscription ok: " + _user + " subscription_id " + event->subscriptionId);
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
||||
else if (event->type == ix::CobraEventType::Published)
|
||||
{
|
||||
TLogger() << "Subscriber: published message acked: " << msgId;
|
||||
TLogger() << "Subscriber: published message acked: " << event->msgId;
|
||||
}
|
||||
});
|
||||
|
||||
@ -248,11 +244,7 @@ namespace
|
||||
ix::msleep(50);
|
||||
_conn.disconnect();
|
||||
|
||||
_conn.setEventCallback([](ix::CobraConnectionEventType /*eventType*/,
|
||||
const std::string& /*errMsg*/,
|
||||
const ix::WebSocketHttpHeaders& /*headers*/,
|
||||
const std::string& /*subscriptionId*/,
|
||||
CobraConnection::MsgId /*msgId*/) { ; });
|
||||
_conn.setEventCallback([](const CobraEventPtr& /*event*/) {});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -54,24 +54,24 @@ namespace
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
|
||||
conn.setEventCallback([&conn, &channel](ix::CobraConnectionEventType eventType,
|
||||
const std::string& errMsg,
|
||||
const ix::WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId) {
|
||||
if (eventType == ix::CobraConnection_EventType_Open)
|
||||
conn.setEventCallback([&conn, &channel](const CobraEventPtr& event) {
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
TLogger() << "Subscriber connected:";
|
||||
for (auto&& it : headers)
|
||||
for (auto&& it : event->headers)
|
||||
{
|
||||
log("Headers " + it.first + " " + it.second);
|
||||
}
|
||||
}
|
||||
if (eventType == ix::CobraConnection_EventType_Error)
|
||||
else if (event->type == ix::CobraEventType::Closed)
|
||||
{
|
||||
TLogger() << "Subscriber error:" << errMsg;
|
||||
TLogger() << "Subscriber closed:" << event->errMsg;
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Authenticated)
|
||||
else if (event->type == ix::CobraEventType::Error)
|
||||
{
|
||||
TLogger() << "Subscriber error:" << event->errMsg;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Authenticated)
|
||||
{
|
||||
log("Subscriber authenticated");
|
||||
std::string filter;
|
||||
@ -92,29 +92,29 @@ namespace
|
||||
gMessageCount++;
|
||||
});
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Subscribed)
|
||||
else if (event->type == ix::CobraEventType::Subscribed)
|
||||
{
|
||||
TLogger() << "Subscriber: subscribed to channel " << subscriptionId;
|
||||
if (subscriptionId == channel)
|
||||
TLogger() << "Subscriber: subscribed to channel " << event->subscriptionId;
|
||||
if (event->subscriptionId == channel)
|
||||
{
|
||||
gSubscriberConnectedAndSubscribed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
TLogger() << "Subscriber: unexpected channel " << subscriptionId;
|
||||
TLogger() << "Subscriber: unexpected channel " << event->subscriptionId;
|
||||
}
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
|
||||
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||
{
|
||||
TLogger() << "Subscriber: ununexpected from channel " << subscriptionId;
|
||||
if (subscriptionId != channel)
|
||||
TLogger() << "Subscriber: ununexpected from channel " << event->subscriptionId;
|
||||
if (event->subscriptionId != channel)
|
||||
{
|
||||
TLogger() << "Subscriber: unexpected channel " << subscriptionId;
|
||||
TLogger() << "Subscriber: unexpected channel " << event->subscriptionId;
|
||||
}
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Published)
|
||||
else if (event->type == ix::CobraEventType::Published)
|
||||
{
|
||||
TLogger() << "Subscriber: published message acked: " << msgId;
|
||||
TLogger() << "Subscriber: published message acked: " << event->msgId;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -138,12 +138,12 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
||||
|
||||
std::thread publisherThread(runPublisher, config, channel);
|
||||
|
||||
std::string filter;
|
||||
std::string position("$");
|
||||
ix::CobraBotConfig cobraBotConfig;
|
||||
cobraBotConfig.cobraConfig = config;
|
||||
cobraBotConfig.channel = channel;
|
||||
cobraBotConfig.runtime = 3; // Only run the bot for 3 seconds
|
||||
cobraBotConfig.enableHeartbeat = false;
|
||||
bool verbose = true;
|
||||
bool strict = true;
|
||||
size_t maxQueueSize = 10;
|
||||
bool enableHeartbeat = false;
|
||||
|
||||
// FIXME: try to get this working with https instead of http
|
||||
// to regress the TLS 1.3 OpenSSL bug
|
||||
@ -158,19 +158,7 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
||||
SentryClient sentryClient(dsn);
|
||||
sentryClient.setTLSOptions(tlsOptionsClient);
|
||||
|
||||
// Only run the bot for 3 seconds
|
||||
int runtime = 3;
|
||||
|
||||
int sentCount = cobra_to_sentry_bot(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
sentryClient,
|
||||
verbose,
|
||||
strict,
|
||||
maxQueueSize,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
int64_t sentCount = cobra_to_sentry_bot(cobraBotConfig, sentryClient, verbose);
|
||||
//
|
||||
// We want at least 2 messages to be sent
|
||||
//
|
||||
|
@ -87,14 +87,11 @@ TEST_CASE("Cobra_to_statsd_bot", "[cobra_bots]")
|
||||
|
||||
std::thread publisherThread(runPublisher, config, channel);
|
||||
|
||||
std::string filter;
|
||||
std::string position("$");
|
||||
bool verbose = true;
|
||||
size_t maxQueueSize = 10;
|
||||
bool enableHeartbeat = false;
|
||||
|
||||
// Only run the bot for 3 seconds
|
||||
int runtime = 3;
|
||||
ix::CobraBotConfig cobraBotConfig;
|
||||
cobraBotConfig.cobraConfig = config;
|
||||
cobraBotConfig.channel = channel;
|
||||
cobraBotConfig.runtime = 3; // Only run the bot for 3 seconds
|
||||
cobraBotConfig.enableHeartbeat = false;
|
||||
|
||||
std::string hostname("127.0.0.1");
|
||||
// std::string hostname("www.google.com");
|
||||
@ -113,19 +110,10 @@ TEST_CASE("Cobra_to_statsd_bot", "[cobra_bots]")
|
||||
std::string fields("device.game\ndevice.os_name");
|
||||
std::string gauge;
|
||||
std::string timer;
|
||||
bool verbose = true;
|
||||
|
||||
int sentCount = ix::cobra_to_statsd_bot(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
statsdClient,
|
||||
fields,
|
||||
gauge,
|
||||
timer,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
int64_t sentCount =
|
||||
ix::cobra_to_statsd_bot(cobraBotConfig, statsdClient, fields, gauge, timer, verbose);
|
||||
//
|
||||
// We want at least 2 messages to be sent
|
||||
//
|
||||
|
115
test/IXCobraToStdoutBotTest.cpp
Normal file
115
test/IXCobraToStdoutBotTest.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* IXCobraToStdoutTest.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXTest.h"
|
||||
#include "catch.hpp"
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <ixbots/IXCobraToStdoutBot.h>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcobra/IXCobraMetricsPublisher.h>
|
||||
#include <ixcrypto/IXUuid.h>
|
||||
#include <ixsentry/IXSentryClient.h>
|
||||
#include <ixsnake/IXRedisServer.h>
|
||||
#include <ixsnake/IXSnakeServer.h>
|
||||
#include <ixwebsocket/IXHttpServer.h>
|
||||
#include <ixwebsocket/IXUserAgent.h>
|
||||
|
||||
using namespace ix;
|
||||
|
||||
namespace
|
||||
{
|
||||
void runPublisher(const ix::CobraConfig& config, const std::string& channel)
|
||||
{
|
||||
ix::CobraMetricsPublisher cobraMetricsPublisher;
|
||||
cobraMetricsPublisher.configure(config, channel);
|
||||
cobraMetricsPublisher.setSession(uuid4());
|
||||
cobraMetricsPublisher.enable(true);
|
||||
|
||||
Json::Value msg;
|
||||
msg["fps"] = 60;
|
||||
|
||||
cobraMetricsPublisher.setGenericAttributes("game", "ody");
|
||||
|
||||
// Wait a bit
|
||||
ix::msleep(500);
|
||||
|
||||
// publish some messages
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #1)
|
||||
cobraMetricsPublisher.push("sms_metric_B_id", msg); // (msg #2)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #3)
|
||||
cobraMetricsPublisher.push("sms_metric_D_id", msg); // (msg #4)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #4)
|
||||
cobraMetricsPublisher.push("sms_metric_F_id", msg); // (msg #5)
|
||||
ix::msleep(500);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Cobra_to_stdout_bot", "[cobra_bots]")
|
||||
{
|
||||
SECTION("Exchange and count sent/received messages.")
|
||||
{
|
||||
int port = getFreePort();
|
||||
snake::AppConfig appConfig = makeSnakeServerConfig(port, true);
|
||||
|
||||
// Start a redis server
|
||||
ix::RedisServer redisServer(appConfig.redisPort);
|
||||
auto res = redisServer.listen();
|
||||
REQUIRE(res.first);
|
||||
redisServer.start();
|
||||
|
||||
// Start a snake server
|
||||
snake::SnakeServer snakeServer(appConfig);
|
||||
snakeServer.run();
|
||||
|
||||
// Run the bot for a small amount of time
|
||||
std::string channel = ix::generateSessionId();
|
||||
std::string appkey("FC2F10139A2BAc53BB72D9db967b024f");
|
||||
std::string role = "_sub";
|
||||
std::string secret = "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba";
|
||||
std::string endpoint = makeCobraEndpoint(port, true);
|
||||
|
||||
ix::CobraConfig config;
|
||||
config.endpoint = endpoint;
|
||||
config.appkey = appkey;
|
||||
config.rolename = role;
|
||||
config.rolesecret = secret;
|
||||
config.socketTLSOptions = makeClientTLSOptions();
|
||||
|
||||
std::thread publisherThread(runPublisher, config, channel);
|
||||
|
||||
ix::CobraBotConfig cobraBotConfig;
|
||||
cobraBotConfig.cobraConfig = config;
|
||||
cobraBotConfig.channel = channel;
|
||||
cobraBotConfig.runtime = 3; // Only run the bot for 3 seconds
|
||||
cobraBotConfig.enableHeartbeat = false;
|
||||
bool quiet = false;
|
||||
|
||||
// We could try to capture the output ... not sure how.
|
||||
bool fluentd = true;
|
||||
|
||||
int64_t sentCount = ix::cobra_to_stdout_bot(cobraBotConfig, fluentd, quiet);
|
||||
//
|
||||
// We want at least 2 messages to be sent
|
||||
//
|
||||
REQUIRE(sentCount >= 2);
|
||||
|
||||
// Give us 1s for all messages to be received
|
||||
ix::msleep(1000);
|
||||
|
||||
spdlog::info("Stopping snake server...");
|
||||
snakeServer.stop();
|
||||
|
||||
spdlog::info("Stopping redis server...");
|
||||
redisServer.stop();
|
||||
|
||||
publisherThread.join();
|
||||
}
|
||||
}
|
@ -4,6 +4,14 @@
|
||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
// Using inet_addr will trigger an error on uwp without this
|
||||
// FIXME: use a different api
|
||||
#ifdef _WIN32
|
||||
#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "IXGetFreePort.h"
|
||||
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
|
@ -17,8 +17,11 @@
|
||||
#include <ixwebsocket/IXSelectInterruptFactory.h>
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketAppleSSL.h>
|
||||
#include <ixwebsocket/IXSocketConnect.h>
|
||||
#include <ixwebsocket/IXSocketFactory.h>
|
||||
#include <ixwebsocket/IXSocketMbedTLS.h>
|
||||
#include <ixwebsocket/IXSocketOpenSSL.h>
|
||||
#include <ixwebsocket/IXSocketServer.h>
|
||||
#include <ixwebsocket/IXUrlParser.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
@ -26,9 +29,9 @@
|
||||
#include <ixwebsocket/IXWebSocketCloseInfo.h>
|
||||
#include <ixwebsocket/IXWebSocketErrorInfo.h>
|
||||
#include <ixwebsocket/IXWebSocketHandshake.h>
|
||||
#include <ixwebsocket/IXWebSocketHandshakeKeyGen.h>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketMessage.h>
|
||||
#include <ixwebsocket/IXWebSocketMessageQueue.h>
|
||||
#include <ixwebsocket/IXWebSocketMessageType.h>
|
||||
#include <ixwebsocket/IXWebSocketOpenInfo.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflate.h>
|
||||
@ -37,8 +40,6 @@
|
||||
#include <ixwebsocket/IXWebSocketSendInfo.h>
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
#include <ixwebsocket/IXWebSocketTransport.h>
|
||||
#include <ixwebsocket/LUrlParser.h>
|
||||
#include <ixwebsocket/libwshandshake.hpp>
|
||||
|
||||
using namespace ix;
|
||||
|
||||
|
306
test/IXWebSocketBroadcastTest.cpp
Normal file
306
test/IXWebSocketBroadcastTest.cpp
Normal file
@ -0,0 +1,306 @@
|
||||
/*
|
||||
* IXWebSocketServerTest.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXTest.h"
|
||||
#include "catch.hpp"
|
||||
#include "msgpack11.hpp"
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketFactory.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
|
||||
using msgpack11::MsgPack;
|
||||
using namespace ix;
|
||||
|
||||
namespace
|
||||
{
|
||||
class WebSocketChat
|
||||
{
|
||||
public:
|
||||
WebSocketChat(const std::string& user, const std::string& session, int port);
|
||||
|
||||
void subscribe(const std::string& channel);
|
||||
void start();
|
||||
void stop();
|
||||
bool isReady() const;
|
||||
|
||||
void sendMessage(const std::string& text);
|
||||
size_t getReceivedMessagesCount() const;
|
||||
const std::vector<std::string>& getReceivedMessages() const;
|
||||
|
||||
std::string encodeMessage(const std::string& text);
|
||||
std::pair<std::string, std::string> decodeMessage(const std::string& str);
|
||||
void appendMessage(const std::string& message);
|
||||
|
||||
private:
|
||||
std::string _user;
|
||||
std::string _session;
|
||||
int _port;
|
||||
|
||||
ix::WebSocket _webSocket;
|
||||
|
||||
std::vector<std::string> _receivedMessages;
|
||||
mutable std::mutex _mutex;
|
||||
};
|
||||
|
||||
WebSocketChat::WebSocketChat(const std::string& user, const std::string& session, int port)
|
||||
: _user(user)
|
||||
, _session(session)
|
||||
, _port(port)
|
||||
{
|
||||
_webSocket.setTLSOptions(makeClientTLSOptions());
|
||||
}
|
||||
|
||||
size_t WebSocketChat::getReceivedMessagesCount() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _receivedMessages.size();
|
||||
}
|
||||
|
||||
const std::vector<std::string>& WebSocketChat::getReceivedMessages() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _receivedMessages;
|
||||
}
|
||||
|
||||
void WebSocketChat::appendMessage(const std::string& message)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_receivedMessages.push_back(message);
|
||||
}
|
||||
|
||||
bool WebSocketChat::isReady() const
|
||||
{
|
||||
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||
}
|
||||
|
||||
void WebSocketChat::stop()
|
||||
{
|
||||
_webSocket.stop();
|
||||
}
|
||||
|
||||
void WebSocketChat::start()
|
||||
{
|
||||
std::string url;
|
||||
{
|
||||
bool preferTLS = true;
|
||||
url = makeCobraEndpoint(_port, preferTLS);
|
||||
}
|
||||
|
||||
_webSocket.setUrl(url);
|
||||
|
||||
std::stringstream ss;
|
||||
log(std::string("Connecting to url: ") + url);
|
||||
|
||||
_webSocket.setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) {
|
||||
std::stringstream ss;
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
ss << "websocket_broadcast_client: " << _user << " Connected !";
|
||||
log(ss.str());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
ss << "websocket_broadcast_client: " << _user << " disconnected !";
|
||||
log(ss.str());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
auto result = decodeMessage(msg->str);
|
||||
|
||||
// Our "chat" / "broacast" node.js server does not send us
|
||||
// the messages we send, so we don't need to have a msg_user != user
|
||||
// as we do for the satori chat example.
|
||||
|
||||
// store text
|
||||
appendMessage(result.second);
|
||||
|
||||
std::string payload = result.second;
|
||||
if (payload.size() > 2000)
|
||||
{
|
||||
payload = "<message too large>";
|
||||
}
|
||||
|
||||
ss << std::endl << result.first << " > " << payload << std::endl << _user << " > ";
|
||||
log(ss.str());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
ss << "websocket_broadcast_client: " << _user << " Error ! "
|
||||
<< msg->errorInfo.reason;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Ping)
|
||||
{
|
||||
log("websocket_broadcast_client: received ping message");
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
log("websocket_broadcast_client: received pong message");
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
||||
{
|
||||
log("websocket_broadcast_client: received message fragment");
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Unexpected ix::WebSocketMessageType";
|
||||
log(ss.str());
|
||||
}
|
||||
});
|
||||
|
||||
_webSocket.start();
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> WebSocketChat::decodeMessage(const std::string& str)
|
||||
{
|
||||
std::string errMsg;
|
||||
MsgPack msg = MsgPack::parse(str, errMsg);
|
||||
|
||||
std::string msg_user = msg["user"].string_value();
|
||||
std::string msg_text = msg["text"].string_value();
|
||||
|
||||
return std::pair<std::string, std::string>(msg_user, msg_text);
|
||||
}
|
||||
|
||||
std::string WebSocketChat::encodeMessage(const std::string& text)
|
||||
{
|
||||
std::map<MsgPack, MsgPack> obj;
|
||||
obj["user"] = _user;
|
||||
obj["text"] = text;
|
||||
|
||||
MsgPack msg(obj);
|
||||
|
||||
std::string output = msg.dump();
|
||||
return output;
|
||||
}
|
||||
|
||||
void WebSocketChat::sendMessage(const std::string& text)
|
||||
{
|
||||
_webSocket.sendBinary(encodeMessage(text));
|
||||
}
|
||||
|
||||
bool startServer(ix::WebSocketServer& server, std::string& connectionId)
|
||||
{
|
||||
bool preferTLS = true;
|
||||
server.setTLSOptions(makeServerTLSOptions(preferTLS));
|
||||
|
||||
server.setOnConnectionCallback([&server, &connectionId](
|
||||
std::shared_ptr<ix::WebSocket> webSocket,
|
||||
std::shared_ptr<ConnectionState> connectionState) {
|
||||
webSocket->setOnMessageCallback([webSocket, connectionState, &connectionId, &server](
|
||||
const ix::WebSocketMessagePtr& msg) {
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
TLogger() << "New connection";
|
||||
connectionState->computeId();
|
||||
TLogger() << "id: " << connectionState->getId();
|
||||
TLogger() << "Uri: " << msg->openInfo.uri;
|
||||
TLogger() << "Headers:";
|
||||
for (auto it : msg->openInfo.headers)
|
||||
{
|
||||
TLogger() << it.first << ": " << it.second;
|
||||
}
|
||||
|
||||
connectionId = connectionState->getId();
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
TLogger() << "Closed connection";
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
for (auto&& client : server.getClients())
|
||||
{
|
||||
if (client != webSocket)
|
||||
{
|
||||
client->send(msg->str, msg->binary);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
auto res = server.listen();
|
||||
if (!res.first)
|
||||
{
|
||||
TLogger() << res.second;
|
||||
return false;
|
||||
}
|
||||
|
||||
server.start();
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
||||
{
|
||||
SECTION("Connect to the server, do not send anything. Should timeout and return 400")
|
||||
{
|
||||
int port = getFreePort();
|
||||
ix::WebSocketServer server(port);
|
||||
std::string connectionId;
|
||||
REQUIRE(startServer(server, connectionId));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
std::vector<std::shared_ptr<WebSocketChat>> chatClients;
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
std::string user("user_" + std::to_string(i));
|
||||
chatClients.push_back(std::make_shared<WebSocketChat>(user, session, port));
|
||||
chatClients[i]->start();
|
||||
ix::msleep(50);
|
||||
}
|
||||
|
||||
// Wait for all chat instance to be ready
|
||||
while (true)
|
||||
{
|
||||
bool allReady = true;
|
||||
for (size_t i = 0; i < chatClients.size(); ++i)
|
||||
{
|
||||
allReady &= chatClients[i]->isReady();
|
||||
}
|
||||
if (allReady) break;
|
||||
ix::msleep(10);
|
||||
}
|
||||
|
||||
for (int j = 0; j < 1000; j++)
|
||||
{
|
||||
for (size_t i = 0; i < chatClients.size(); ++i)
|
||||
{
|
||||
chatClients[i]->sendMessage("hello world");
|
||||
}
|
||||
|
||||
if (j == 250)
|
||||
{
|
||||
server.stop();
|
||||
ix::msleep(100);
|
||||
}
|
||||
if (j == 500)
|
||||
{
|
||||
server.start();
|
||||
ix::msleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
// wait 1 second
|
||||
ix::msleep(2000);
|
||||
|
||||
// Stop all clients
|
||||
size_t messageCount = chatClients.size() * 50;
|
||||
for (size_t i = 0; i < chatClients.size(); ++i)
|
||||
{
|
||||
REQUIRE(chatClients[i]->getReceivedMessagesCount() >= messageCount);
|
||||
chatClients[i]->stop();
|
||||
}
|
||||
|
||||
// Give us 500ms for the server to notice that clients went away
|
||||
ix::msleep(500);
|
||||
server.stop();
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
}
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
/*
|
||||
* IXWebSocketMessageQTest.cpp
|
||||
* Author: Korchynskyi Dmytro
|
||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXTest.h"
|
||||
#include "catch.hpp"
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXWebSocketMessageQueue.h>
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
#include <thread>
|
||||
|
||||
using namespace ix;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool startServer(ix::WebSocketServer& server)
|
||||
{
|
||||
server.setOnConnectionCallback([&server](std::shared_ptr<ix::WebSocket> webSocket,
|
||||
std::shared_ptr<ConnectionState> connectionState) {
|
||||
webSocket->setOnMessageCallback(
|
||||
[connectionState, &server](const WebSocketMessagePtr& msg) {
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
TLogger() << "New connection";
|
||||
connectionState->computeId();
|
||||
TLogger() << "id: " << connectionState->getId();
|
||||
TLogger() << "Uri: " << msg->openInfo.uri;
|
||||
TLogger() << "Headers:";
|
||||
for (auto&& it : msg->openInfo.headers)
|
||||
{
|
||||
TLogger() << it.first << ": " << it.second;
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
TLogger() << "Closed connection";
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
TLogger() << "Message received: " << msg->str;
|
||||
|
||||
for (auto&& client : server.getClients())
|
||||
{
|
||||
client->send(msg->str);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
auto res = server.listen();
|
||||
if (!res.first)
|
||||
{
|
||||
TLogger() << res.second;
|
||||
return false;
|
||||
}
|
||||
|
||||
server.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
class MsgQTestClient
|
||||
{
|
||||
public:
|
||||
MsgQTestClient()
|
||||
{
|
||||
msgQ.bindWebsocket(&ws);
|
||||
|
||||
msgQ.setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
|
||||
REQUIRE(mainThreadId == std::this_thread::get_id());
|
||||
|
||||
std::stringstream ss;
|
||||
if (msg->type == WebSocketMessageType::Open)
|
||||
{
|
||||
log("client connected");
|
||||
sendNextMessage();
|
||||
}
|
||||
else if (msg->type == WebSocketMessageType::Close)
|
||||
{
|
||||
log("client disconnected");
|
||||
}
|
||||
else if (msg->type == WebSocketMessageType::Error)
|
||||
{
|
||||
ss << "Error ! " << msg->errorInfo.reason;
|
||||
log(ss.str());
|
||||
testDone = true;
|
||||
}
|
||||
else if (msg->type == WebSocketMessageType::Pong)
|
||||
{
|
||||
ss << "Received pong message " << msg->str;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (msg->type == WebSocketMessageType::Ping)
|
||||
{
|
||||
ss << "Received ping message " << msg->str;
|
||||
log(ss.str());
|
||||
}
|
||||
else if (msg->type == WebSocketMessageType::Message)
|
||||
{
|
||||
REQUIRE(msg->str.compare("Hey dude!") == 0);
|
||||
++receivedCount;
|
||||
ss << "Received message " << msg->str;
|
||||
log(ss.str());
|
||||
sendNextMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Invalid WebSocketMessageType";
|
||||
log(ss.str());
|
||||
testDone = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void sendNextMessage()
|
||||
{
|
||||
if (receivedCount >= 3)
|
||||
{
|
||||
testDone = true;
|
||||
succeeded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto info = ws.sendText("Hey dude!");
|
||||
if (info.success)
|
||||
log("sent message");
|
||||
else
|
||||
log("send failed");
|
||||
}
|
||||
}
|
||||
|
||||
void run(const std::string& url)
|
||||
{
|
||||
mainThreadId = std::this_thread::get_id();
|
||||
testDone = false;
|
||||
receivedCount = 0;
|
||||
|
||||
ws.setUrl(url);
|
||||
ws.start();
|
||||
|
||||
while (!testDone)
|
||||
{
|
||||
msgQ.poll();
|
||||
msleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
bool isSucceeded() const
|
||||
{
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
private:
|
||||
WebSocket ws;
|
||||
WebSocketMessageQueue msgQ;
|
||||
bool testDone = false;
|
||||
uint32_t receivedCount = 0;
|
||||
std::thread::id mainThreadId;
|
||||
bool succeeded = false;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Websocket_message_queue", "[websocket_message_q]")
|
||||
{
|
||||
SECTION("Send several messages")
|
||||
{
|
||||
int port = getFreePort();
|
||||
WebSocketServer server(port);
|
||||
REQUIRE(startServer(server));
|
||||
|
||||
MsgQTestClient testClient;
|
||||
testClient.run("ws://127.0.0.1:" + std::to_string(port));
|
||||
REQUIRE(testClient.isSucceeded());
|
||||
|
||||
server.stop();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user