Compare commits
1 Commits
v10.1.7
...
feature/wi
Author | SHA1 | Date | |
---|---|---|---|
2cbe198497 |
92
.github/workflows/ccpp.yml
vendored
Normal file
92
.github/workflows/ccpp.yml
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
name: unittest
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: make test
|
||||||
|
run: make test
|
||||||
|
|
||||||
|
mac_tsan_sectransport:
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: make test_tsan
|
||||||
|
run: make test_tsan
|
||||||
|
|
||||||
|
mac_tsan_openssl:
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: install openssl
|
||||||
|
run: brew install openssl
|
||||||
|
- name: make test
|
||||||
|
run: make test_tsan_openssl
|
||||||
|
|
||||||
|
mac_tsan_mbedtls:
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: install mbedtls
|
||||||
|
run: brew install mbedtls
|
||||||
|
- name: make test
|
||||||
|
run: make test_tsan_mbedtls
|
||||||
|
|
||||||
|
windows_no_tls:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||||
|
- run: |
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_TEST=1 ..
|
||||||
|
- run: cmake --build build
|
||||||
|
- run: |
|
||||||
|
cd test
|
||||||
|
..\build\test\ixwebsocket_unittest.exe
|
||||||
|
|
||||||
|
# windows_mbedtls:
|
||||||
|
# runs-on: windows-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@v1
|
||||||
|
# - uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||||
|
# - run: |
|
||||||
|
# vcpkg install zlib:x64-windows
|
||||||
|
# vcpkg install mbedtls:x64-windows
|
||||||
|
# - run: |
|
||||||
|
# mkdir build
|
||||||
|
# cd build
|
||||||
|
# cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_MBED_TLS=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
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
19
.github/workflows/stale.yml
vendored
19
.github/workflows/stale.yml
vendored
@ -1,19 +0,0 @@
|
|||||||
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'
|
|
13
.github/workflows/unittest_linux.yml
vendored
13
.github/workflows/unittest_linux.yml
vendored
@ -1,13 +0,0 @@
|
|||||||
name: linux
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
linux:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: make test_make
|
|
||||||
run: make test_make
|
|
15
.github/workflows/unittest_mac_tsan_mbedtls.yml
vendored
15
.github/workflows/unittest_mac_tsan_mbedtls.yml
vendored
@ -1,15 +0,0 @@
|
|||||||
name: mac_tsan_mbedtls
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
mac_tsan_mbedtls:
|
|
||||||
runs-on: macOS-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: install mbedtls
|
|
||||||
run: brew install mbedtls
|
|
||||||
- name: make test
|
|
||||||
run: make test_tsan_mbedtls
|
|
15
.github/workflows/unittest_mac_tsan_openssl.yml
vendored
15
.github/workflows/unittest_mac_tsan_openssl.yml
vendored
@ -1,15 +0,0 @@
|
|||||||
name: mac_tsan_openssl
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
mac_tsan_openssl:
|
|
||||||
runs-on: macOS-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: install openssl
|
|
||||||
run: brew install openssl@1.1
|
|
||||||
- name: make test
|
|
||||||
run: make test_tsan_openssl
|
|
@ -1,13 +0,0 @@
|
|||||||
name: mac_tsan_sectransport
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
mac_tsan_sectransport:
|
|
||||||
runs-on: macOS-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: make test_tsan
|
|
||||||
run: make test_tsan
|
|
38
.github/workflows/unittest_uwp.yml
vendored
38
.github/workflows/unittest_uwp.yml
vendored
@ -1,38 +0,0 @@
|
|||||||
name: uwp
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
uwp:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
|
||||||
- run: |
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_CXX_COMPILER=cl.exe -DUSE_TEST=1 -DUSE_ZLIB=0 ..
|
|
||||||
- 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
|
|
20
.github/workflows/unittest_windows.yml
vendored
20
.github/workflows/unittest_windows.yml
vendored
@ -1,20 +0,0 @@
|
|||||||
name: windows
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
windows:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
|
||||||
- run: |
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 ..
|
|
||||||
- run: cmake --build build
|
|
||||||
|
|
||||||
#- run: ../build/test/ixwebsocket_unittest.exe
|
|
||||||
# working-directory: test
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,4 +5,3 @@ ixsnake/ixsnake/.certs/
|
|||||||
site/
|
site/
|
||||||
ws/.certs/
|
ws/.certs/
|
||||||
ws/.srl
|
ws/.srl
|
||||||
ixhttpd
|
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v2.5.0
|
rev: v2.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- 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_path(JSONCPP_INCLUDE_DIRS json/json.h)
|
||||||
find_library(JSONCPP_LIBRARY jsoncpp)
|
find_library(JSONCPP_LIBRARY jsoncpp)
|
||||||
|
|
||||||
find_package_handle_standard_args(JsonCpp
|
find_package_handle_standard_args(JSONCPP
|
||||||
FOUND_VAR
|
FOUND_VAR
|
||||||
JSONCPP_FOUND
|
JSONCPP_FOUND
|
||||||
REQUIRED_VARS
|
REQUIRED_VARS
|
||||||
|
@ -7,7 +7,7 @@ find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
|
|||||||
set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}")
|
set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}")
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
include(FindPackageHandleStandardArgs)
|
||||||
find_package_handle_standard_args(MbedTLS DEFAULT_MSG
|
find_package_handle_standard_args(MBEDTLS DEFAULT_MSG
|
||||||
MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
|
MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
|
||||||
|
|
||||||
mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
|
mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
|
||||||
|
175
CMakeLists.txt
175
CMakeLists.txt
@ -3,7 +3,7 @@
|
|||||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||||
#
|
#
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.4.1...3.17.2)
|
cmake_minimum_required(VERSION 3.4.1)
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
|
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
|
||||||
|
|
||||||
project(ixwebsocket C CXX)
|
project(ixwebsocket C CXX)
|
||||||
@ -12,10 +12,6 @@ set (CMAKE_CXX_STANDARD 14)
|
|||||||
set (CXX_STANDARD_REQUIRED ON)
|
set (CXX_STANDARD_REQUIRED ON)
|
||||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||||
endif()
|
endif()
|
||||||
@ -30,15 +26,12 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXConnectionState.cpp
|
ixwebsocket/IXConnectionState.cpp
|
||||||
ixwebsocket/IXDNSLookup.cpp
|
ixwebsocket/IXDNSLookup.cpp
|
||||||
ixwebsocket/IXExponentialBackoff.cpp
|
ixwebsocket/IXExponentialBackoff.cpp
|
||||||
ixwebsocket/IXGetFreePort.cpp
|
|
||||||
ixwebsocket/IXHttp.cpp
|
ixwebsocket/IXHttp.cpp
|
||||||
ixwebsocket/IXHttpClient.cpp
|
ixwebsocket/IXHttpClient.cpp
|
||||||
ixwebsocket/IXHttpServer.cpp
|
ixwebsocket/IXHttpServer.cpp
|
||||||
ixwebsocket/IXNetSystem.cpp
|
ixwebsocket/IXNetSystem.cpp
|
||||||
ixwebsocket/IXSelectInterrupt.cpp
|
ixwebsocket/IXSelectInterrupt.cpp
|
||||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||||
ixwebsocket/IXSelectInterruptPipe.cpp
|
|
||||||
ixwebsocket/IXSetThreadName.cpp
|
|
||||||
ixwebsocket/IXSocket.cpp
|
ixwebsocket/IXSocket.cpp
|
||||||
ixwebsocket/IXSocketConnect.cpp
|
ixwebsocket/IXSocketConnect.cpp
|
||||||
ixwebsocket/IXSocketFactory.cpp
|
ixwebsocket/IXSocketFactory.cpp
|
||||||
@ -51,22 +44,21 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXWebSocketCloseConstants.cpp
|
ixwebsocket/IXWebSocketCloseConstants.cpp
|
||||||
ixwebsocket/IXWebSocketHandshake.cpp
|
ixwebsocket/IXWebSocketHandshake.cpp
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||||
|
ixwebsocket/IXWebSocketMessageQueue.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
||||||
ixwebsocket/IXWebSocketProxyServer.cpp
|
|
||||||
ixwebsocket/IXWebSocketServer.cpp
|
ixwebsocket/IXWebSocketServer.cpp
|
||||||
ixwebsocket/IXWebSocketTransport.cpp
|
ixwebsocket/IXWebSocketTransport.cpp
|
||||||
|
ixwebsocket/LUrlParser.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set( IXWEBSOCKET_HEADERS
|
set( IXWEBSOCKET_HEADERS
|
||||||
ixwebsocket/IXBench.h
|
ixwebsocket/IXBench.h
|
||||||
ixwebsocket/IXCancellationRequest.h
|
ixwebsocket/IXCancellationRequest.h
|
||||||
ixwebsocket/IXConnectionInfo.h
|
|
||||||
ixwebsocket/IXConnectionState.h
|
ixwebsocket/IXConnectionState.h
|
||||||
ixwebsocket/IXDNSLookup.h
|
ixwebsocket/IXDNSLookup.h
|
||||||
ixwebsocket/IXExponentialBackoff.h
|
ixwebsocket/IXExponentialBackoff.h
|
||||||
ixwebsocket/IXGetFreePort.h
|
|
||||||
ixwebsocket/IXHttp.h
|
ixwebsocket/IXHttp.h
|
||||||
ixwebsocket/IXHttpClient.h
|
ixwebsocket/IXHttpClient.h
|
||||||
ixwebsocket/IXHttpServer.h
|
ixwebsocket/IXHttpServer.h
|
||||||
@ -74,7 +66,6 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXProgressCallback.h
|
ixwebsocket/IXProgressCallback.h
|
||||||
ixwebsocket/IXSelectInterrupt.h
|
ixwebsocket/IXSelectInterrupt.h
|
||||||
ixwebsocket/IXSelectInterruptFactory.h
|
ixwebsocket/IXSelectInterruptFactory.h
|
||||||
ixwebsocket/IXSelectInterruptPipe.h
|
|
||||||
ixwebsocket/IXSetThreadName.h
|
ixwebsocket/IXSetThreadName.h
|
||||||
ixwebsocket/IXSocket.h
|
ixwebsocket/IXSocket.h
|
||||||
ixwebsocket/IXSocketConnect.h
|
ixwebsocket/IXSocketConnect.h
|
||||||
@ -90,52 +81,63 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXWebSocketCloseInfo.h
|
ixwebsocket/IXWebSocketCloseInfo.h
|
||||||
ixwebsocket/IXWebSocketErrorInfo.h
|
ixwebsocket/IXWebSocketErrorInfo.h
|
||||||
ixwebsocket/IXWebSocketHandshake.h
|
ixwebsocket/IXWebSocketHandshake.h
|
||||||
ixwebsocket/IXWebSocketHandshakeKeyGen.h
|
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.h
|
ixwebsocket/IXWebSocketHttpHeaders.h
|
||||||
ixwebsocket/IXWebSocketInitResult.h
|
ixwebsocket/IXWebSocketInitResult.h
|
||||||
ixwebsocket/IXWebSocketMessage.h
|
ixwebsocket/IXWebSocketMessage.h
|
||||||
|
ixwebsocket/IXWebSocketMessageQueue.h
|
||||||
ixwebsocket/IXWebSocketMessageType.h
|
ixwebsocket/IXWebSocketMessageType.h
|
||||||
ixwebsocket/IXWebSocketOpenInfo.h
|
ixwebsocket/IXWebSocketOpenInfo.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
|
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
|
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
|
||||||
ixwebsocket/IXWebSocketProxyServer.h
|
|
||||||
ixwebsocket/IXWebSocketSendInfo.h
|
ixwebsocket/IXWebSocketSendInfo.h
|
||||||
ixwebsocket/IXWebSocketServer.h
|
ixwebsocket/IXWebSocketServer.h
|
||||||
ixwebsocket/IXWebSocketTransport.h
|
ixwebsocket/IXWebSocketTransport.h
|
||||||
ixwebsocket/IXWebSocketVersion.h
|
ixwebsocket/IXWebSocketVersion.h
|
||||||
|
ixwebsocket/LUrlParser.h
|
||||||
|
ixwebsocket/libwshandshake.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (UNIX)
|
||||||
|
# Linux, Mac, iOS, Android
|
||||||
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.cpp )
|
||||||
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.h )
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Platform specific code
|
||||||
|
if (APPLE)
|
||||||
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
|
||||||
|
elseif (WIN32)
|
||||||
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
|
||||||
|
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)
|
option(USE_TLS "Enable TLS support" FALSE)
|
||||||
|
|
||||||
if (USE_TLS)
|
if (USE_TLS)
|
||||||
# default to securetranport on Apple if nothing is configured
|
option(USE_MBED_TLS "Use Mbed TLS" OFF)
|
||||||
if (APPLE)
|
option(USE_OPEN_SSL "Use OpenSSL" OFF)
|
||||||
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
|
# default to mbedtls on windows if nothing is configured
|
||||||
elseif (WIN32)
|
if (WIN32 AND NOT USE_OPEN_SSL AND NOT USE_MBED_TLS)
|
||||||
if (NOT USE_OPEN_SSL) # unless we want something else
|
option(USE_MBED_TLS "Use Mbed TLS" ON)
|
||||||
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()
|
endif()
|
||||||
|
|
||||||
if (USE_MBED_TLS)
|
if (USE_MBED_TLS)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
|
||||||
elseif (USE_SECURE_TRANSPORT)
|
elseif (APPLE AND NOT USE_OPEN_SSL)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
||||||
elseif (USE_OPEN_SSL)
|
else()
|
||||||
|
set(USE_OPEN_SSL ON)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "TLS Configuration error: unknown backend")
|
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -150,64 +152,16 @@ if (USE_TLS)
|
|||||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS)
|
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS)
|
||||||
elseif (USE_OPEN_SSL)
|
elseif (USE_OPEN_SSL)
|
||||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_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()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_TLS)
|
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS AND NOT USE_OPEN_SSL)
|
||||||
if (USE_OPEN_SSL)
|
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||||
message(STATUS "TLS configured to use openssl")
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
|
||||||
# Use OPENSSL_ROOT_DIR CMake variable if you need to use your own openssl
|
|
||||||
find_package(OpenSSL REQUIRED)
|
|
||||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
option(USE_ZLIB "Enable zlib support" TRUE)
|
|
||||||
|
|
||||||
if (USE_ZLIB)
|
|
||||||
# Use ZLIB_ROOT CMake variable if you need to use your own zlib
|
|
||||||
find_package(ZLIB REQUIRED)
|
|
||||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
|
|
||||||
|
|
||||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||||
|
|
||||||
if (USE_TLS)
|
|
||||||
target_link_libraries(ixwebsocket Crypt32)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
@ -215,6 +169,49 @@ if (UNIX)
|
|||||||
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
|
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (USE_TLS AND USE_OPEN_SSL)
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
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})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT ZLIB_FOUND)
|
||||||
|
find_package(ZLIB)
|
||||||
|
endif()
|
||||||
|
if (ZLIB_FOUND)
|
||||||
|
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
|
||||||
|
else()
|
||||||
|
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
|
||||||
|
add_subdirectory(third_party/zlib)
|
||||||
|
target_link_libraries(ixwebsocket zlibstatic)
|
||||||
|
endif()
|
||||||
|
|
||||||
set( IXWEBSOCKET_INCLUDE_DIRS
|
set( IXWEBSOCKET_INCLUDE_DIRS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
@ -225,29 +222,19 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
|||||||
target_compile_options(ixwebsocket PRIVATE /MP)
|
target_compile_options(ixwebsocket PRIVATE /MP)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(ixwebsocket PUBLIC
|
target_include_directories(ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS})
|
||||||
$<BUILD_INTERFACE:${IXWEBSOCKET_INCLUDE_DIRS}/>
|
|
||||||
$<INSTALL_INTERFACE:include/ixwebsocket>
|
|
||||||
)
|
|
||||||
|
|
||||||
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
|
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
|
||||||
|
|
||||||
install(TARGETS ixwebsocket
|
install(TARGETS ixwebsocket
|
||||||
EXPORT ixwebsocket
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
|
||||||
ARCHIVE DESTINATION lib
|
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
|
||||||
PUBLIC_HEADER DESTINATION include/ixwebsocket/
|
|
||||||
)
|
)
|
||||||
|
|
||||||
install(EXPORT ixwebsocket
|
|
||||||
FILE ixwebsocket-config.cmake
|
|
||||||
NAMESPACE ixwebsocket::
|
|
||||||
DESTINATION lib/cmake/ixwebsocket)
|
|
||||||
|
|
||||||
if (USE_WS OR USE_TEST)
|
if (USE_WS OR USE_TEST)
|
||||||
add_subdirectory(ixcore)
|
add_subdirectory(ixcore)
|
||||||
add_subdirectory(ixcrypto)
|
add_subdirectory(ixcrypto)
|
||||||
add_subdirectory(ixcobra)
|
add_subdirectory(ixcobra)
|
||||||
add_subdirectory(ixredis)
|
|
||||||
add_subdirectory(ixsnake)
|
add_subdirectory(ixsnake)
|
||||||
add_subdirectory(ixsentry)
|
add_subdirectory(ixsentry)
|
||||||
add_subdirectory(ixbots)
|
add_subdirectory(ixbots)
|
||||||
|
101
README.md
101
README.md
@ -1,72 +1,37 @@
|
|||||||
## Hello world
|
## Hello world
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
|
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
|
||||||
|
|
||||||
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
|
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
/*
|
// Required on Windows
|
||||||
* main.cpp
|
ix::initNetSystem();
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
|
|
||||||
*
|
|
||||||
* On macOS
|
|
||||||
* $ mkdir -p build ; cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install
|
|
||||||
* $ clang++ --std=c++14 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
|
|
||||||
* $ ./a.out
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <ixwebsocket/IXNetSystem.h>
|
// Our websocket object
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
ix::WebSocket webSocket;
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
int main()
|
std::string url("ws://localhost:8080/");
|
||||||
{
|
webSocket.setUrl(url);
|
||||||
// Required on Windows
|
|
||||||
ix::initNetSystem();
|
|
||||||
|
|
||||||
// Our websocket object
|
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
||||||
ix::WebSocket webSocket;
|
// when a message or an event (open, close, error) is received
|
||||||
|
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||||
std::string url("wss://echo.websocket.org");
|
|
||||||
webSocket.setUrl(url);
|
|
||||||
|
|
||||||
std::cout << "Connecting to " << url << "..." << std::endl;
|
|
||||||
|
|
||||||
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
|
||||||
// when a message or an event (open, close, error) is received
|
|
||||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
|
||||||
{
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
std::cout << "received message: " << msg->str << std::endl;
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
std::cout << "Connection established" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Now that our callback is setup, we can start our background thread and receive messages
|
|
||||||
webSocket.start();
|
|
||||||
|
|
||||||
// Send a message to the server (default to TEXT mode)
|
|
||||||
webSocket.send("hello world");
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
std::string text;
|
if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
std::cout << "> " << std::flush;
|
{
|
||||||
std::getline(std::cin, text);
|
std::cout << msg->str << std::endl;
|
||||||
|
}
|
||||||
webSocket.send(text);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return 0;
|
// Now that our callback is setup, we can start our background thread and receive messages
|
||||||
}
|
webSocket.start();
|
||||||
|
|
||||||
|
// Send a message to the server (default to TEXT mode)
|
||||||
|
webSocket.send("hello world");
|
||||||
```
|
```
|
||||||
|
|
||||||
Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
|
Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
|
||||||
@ -80,27 +45,3 @@ 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.
|
If your company or project is using this library, feel free to open an issue or PR to amend this list.
|
||||||
|
|
||||||
- [Machine Zone](https://www.mz.com)
|
- [Machine Zone](https://www.mz.com)
|
||||||
- [Tokio](https://gitlab.com/HCInk/tokio), a discord library focused on audio playback with node bindings.
|
|
||||||
- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework.
|
|
||||||
- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod
|
|
||||||
- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper
|
|
||||||
|
|
||||||
## Continuous Integration
|
|
||||||
|
|
||||||
| OS | TLS | Sanitizer | Status |
|
|
||||||
|-------------------|-------------------|-------------------|-------------------|
|
|
||||||
| Linux | OpenSSL | None | [![Build2][1]][7] |
|
|
||||||
| macOS | Secure Transport | Thread Sanitizer | [![Build2][2]][7] |
|
|
||||||
| macOS | OpenSSL | Thread Sanitizer | [![Build2][3]][7] |
|
|
||||||
| macOS | MbedTLS | Thread Sanitizer | [![Build2][4]][7] |
|
|
||||||
| Windows | Disabled | None | [![Build2][5]][7] |
|
|
||||||
| UWP | Disabled | None | [![Build2][6]][7] |
|
|
||||||
|
|
||||||
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
|
|
||||||
[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg
|
|
||||||
[3]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg
|
|
||||||
[4]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg
|
|
||||||
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
|
|
||||||
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
|
|
||||||
[7]: https://github.com/machinezone/IXWebSocket
|
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
FROM alpine:3.12 as build
|
FROM alpine:3.11 as build
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
gcc g++ musl-dev linux-headers \
|
gcc g++ musl-dev linux-headers \
|
||||||
cmake mbedtls-dev make zlib-dev python3-dev ninja
|
cmake mbedtls-dev make zlib-dev
|
||||||
|
|
||||||
RUN addgroup -S app && \
|
RUN addgroup -S app && \
|
||||||
adduser -S -G app app && \
|
adduser -S -G app app && \
|
||||||
@ -18,9 +18,9 @@ USER app
|
|||||||
RUN make ws_mbedtls_install && \
|
RUN make ws_mbedtls_install && \
|
||||||
sh tools/trim_repo_for_docker.sh
|
sh tools/trim_repo_for_docker.sh
|
||||||
|
|
||||||
FROM alpine:3.12 as runtime
|
FROM alpine:3.11 as runtime
|
||||||
|
|
||||||
RUN apk add --no-cache libstdc++ mbedtls ca-certificates python3 && \
|
RUN apk add --no-cache libstdc++ mbedtls ca-certificates && \
|
||||||
addgroup -S app && \
|
addgroup -S app && \
|
||||||
adduser -S -G app app
|
adduser -S -G app app
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
# Build time
|
|
||||||
FROM ubuntu:groovy as build
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
|
|
||||||
RUN apt-get -y install g++ libssl-dev libz-dev make python ninja-build
|
|
||||||
RUN apt-get -y install cmake
|
|
||||||
RUN apt-get -y install gdb
|
|
||||||
|
|
||||||
COPY . /opt
|
|
||||||
WORKDIR /opt
|
|
||||||
|
|
||||||
#
|
|
||||||
# To use the container interactively for debugging/building
|
|
||||||
# 1. Build with
|
|
||||||
# CMD ["ls"]
|
|
||||||
# 2. Run with
|
|
||||||
# docker run --entrypoint sh -it docker-game-eng-dev.addsrv.com/ws:9.10.6
|
|
||||||
#
|
|
||||||
|
|
||||||
RUN ["make", "test"]
|
|
||||||
# CMD ["ls"]
|
|
@ -1,295 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All changes to this project will be documented in this file.
|
All changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [10.1.7] - 2020-08-11
|
|
||||||
|
|
||||||
(ws) -q option imply info log level, not warning log level
|
|
||||||
|
|
||||||
## [10.1.6] - 2020-08-06
|
|
||||||
|
|
||||||
(websocket server) Handle programmer error when the server callback is not registered properly (fix #227)
|
|
||||||
|
|
||||||
## [10.1.5] - 2020-08-02
|
|
||||||
|
|
||||||
(ws) Add a new ws sub-command, push_server. This command runs a server which sends many messages in a loop to a websocket client. We can receive above 200,000 messages per second (cf #235).
|
|
||||||
|
|
||||||
## [10.1.4] - 2020-08-02
|
|
||||||
|
|
||||||
(ws) Add a new ws sub-command, echo_client. This command sends a message to an echo server, and send back to a server whatever message it does receive. When connecting to a local ws echo_server, on my MacBook Pro 2015 I can send/receive around 30,000 messages per second. (cf #235)
|
|
||||||
|
|
||||||
## [10.1.3] - 2020-08-02
|
|
||||||
|
|
||||||
(ws) ws echo_server. Add a -q option to only enable warning and error log levels. This is useful for bench-marking so that we do not print a lot of things on the console. (cf #235)
|
|
||||||
|
|
||||||
## [10.1.2] - 2020-07-31
|
|
||||||
|
|
||||||
(build) make using zlib optional, with the caveat that some http and websocket features are not available when zlib is absent
|
|
||||||
|
|
||||||
## [10.1.1] - 2020-07-29
|
|
||||||
|
|
||||||
(websocket client) onProgressCallback not called for short messages on a websocket (fix #233)
|
|
||||||
|
|
||||||
## [10.1.0] - 2020-07-29
|
|
||||||
|
|
||||||
(websocket client) heartbeat is not sent at the requested frequency (fix #232)
|
|
||||||
|
|
||||||
## [10.0.3] - 2020-07-28
|
|
||||||
|
|
||||||
compiler warning fixes
|
|
||||||
|
|
||||||
## [10.0.2] - 2020-07-28
|
|
||||||
|
|
||||||
(ixcobra) CobraConnection: unsubscribe from all subscriptions when disconnecting
|
|
||||||
|
|
||||||
## [10.0.1] - 2020-07-27
|
|
||||||
|
|
||||||
(socket utility) move ix::getFreePort to ixwebsocket library
|
|
||||||
|
|
||||||
## [10.0.0] - 2020-07-25
|
|
||||||
|
|
||||||
(ixwebsocket server) change legacy api with 2 nested callbacks, so that the first api takes a weak_ptr<WebSocket> as its first argument
|
|
||||||
|
|
||||||
## [9.10.7] - 2020-07-25
|
|
||||||
|
|
||||||
(ixwebsocket) add WebSocketProxyServer, from ws. Still need to make the interface better.
|
|
||||||
|
|
||||||
## [9.10.6] - 2020-07-24
|
|
||||||
|
|
||||||
(ws) port broadcast_server sub-command to the new server API
|
|
||||||
|
|
||||||
## [9.10.5] - 2020-07-24
|
|
||||||
|
|
||||||
(unittest) port most unittests to the new server API
|
|
||||||
|
|
||||||
## [9.10.3] - 2020-07-24
|
|
||||||
|
|
||||||
(ws) port ws transfer to the new server API
|
|
||||||
|
|
||||||
## [9.10.2] - 2020-07-24
|
|
||||||
|
|
||||||
(websocket client) reset WebSocketTransport onClose callback in the WebSocket destructor
|
|
||||||
|
|
||||||
## [9.10.1] - 2020-07-24
|
|
||||||
|
|
||||||
(websocket server) reset client websocket callback when the connection is closed
|
|
||||||
|
|
||||||
## [9.10.0] - 2020-07-23
|
|
||||||
|
|
||||||
(websocket server) add a new simpler API to handle client connections / that API does not trigger a memory leak while the previous one did
|
|
||||||
|
|
||||||
## [9.9.3] - 2020-07-17
|
|
||||||
|
|
||||||
(build) merge platform specific files which were used to have different implementations for setting a thread name into a single file, to make it easier to include every source files and build the ixwebsocket library (fix #226)
|
|
||||||
|
|
||||||
## [9.9.2] - 2020-07-10
|
|
||||||
|
|
||||||
(socket server) bump default max connection count from 32 to 128
|
|
||||||
|
|
||||||
## [9.9.1] - 2020-07-10
|
|
||||||
|
|
||||||
(snake) implement super simple stream sql expression support in snake server
|
|
||||||
|
|
||||||
## [9.9.0] - 2020-07-08
|
|
||||||
|
|
||||||
(socket+websocket+http+redis+snake servers) expose the remote ip and remote port when a new connection is made
|
|
||||||
|
|
||||||
## [9.8.6] - 2020-07-06
|
|
||||||
|
|
||||||
(cmake) change the way zlib and openssl are searched
|
|
||||||
|
|
||||||
## [9.8.5] - 2020-07-06
|
|
||||||
|
|
||||||
(cobra python bots) remove the test which stop the bot when events do not follow cobra metrics system schema with an id and a device entry
|
|
||||||
|
|
||||||
## [9.8.4] - 2020-06-26
|
|
||||||
|
|
||||||
(cobra bots) remove bots which is not required now that we can use Python extensions
|
|
||||||
|
|
||||||
## [9.8.3] - 2020-06-25
|
|
||||||
|
|
||||||
(cmake) new python code is optional and enabled at cmake time with -DUSE_PYTHON=1
|
|
||||||
|
|
||||||
## [9.8.2] - 2020-06-24
|
|
||||||
|
|
||||||
(cobra bots) new cobra metrics bot to send data to statsd using Python for processing the message
|
|
||||||
|
|
||||||
## [9.8.1] - 2020-06-19
|
|
||||||
|
|
||||||
(cobra metrics to statsd bot) fps slow frame info : do not include os name
|
|
||||||
|
|
||||||
## [9.8.0] - 2020-06-19
|
|
||||||
|
|
||||||
(cobra metrics to statsd bot) send info about memory warnings
|
|
||||||
|
|
||||||
## [9.7.9] - 2020-06-18
|
|
||||||
|
|
||||||
(http client) fix deadlock when following redirects
|
|
||||||
|
|
||||||
## [9.7.8] - 2020-06-18
|
|
||||||
|
|
||||||
(cobra metrics to statsd bot) send info about net requests
|
|
||||||
|
|
||||||
## [9.7.7] - 2020-06-17
|
|
||||||
|
|
||||||
(cobra client and bots) add batch_size subscription option for retrieving multiple messages at once
|
|
||||||
|
|
||||||
## [9.7.6] - 2020-06-15
|
|
||||||
|
|
||||||
(websocket) WebSocketServer is not a final class, so that users can extend it (fix #215)
|
|
||||||
|
|
||||||
## [9.7.5] - 2020-06-15
|
|
||||||
|
|
||||||
(cobra bots) minor aesthetic change, in how we display http headers with a : then space as key value separator instead of :: with no space
|
|
||||||
|
|
||||||
## [9.7.4] - 2020-06-11
|
|
||||||
|
|
||||||
(cobra metrics to statsd bot) change from a statsd type of gauge to a timing one
|
|
||||||
|
|
||||||
## [9.7.3] - 2020-06-11
|
|
||||||
|
|
||||||
(redis cobra bots) capture most used devices in a zset
|
|
||||||
|
|
||||||
## [9.7.2] - 2020-06-11
|
|
||||||
|
|
||||||
(ws) add bare bone redis-cli like sub-command, with command line editing powered by libnoise
|
|
||||||
|
|
||||||
## [9.7.1] - 2020-06-11
|
|
||||||
|
|
||||||
(redis cobra bots) ws cobra metrics to redis / hostname invalid parsing
|
|
||||||
|
|
||||||
## [9.7.0] - 2020-06-11
|
|
||||||
|
|
||||||
(redis cobra bots) xadd with maxlen + fix bug in xadd client implementation and ws cobra metrics to redis command argument parsing
|
|
||||||
|
|
||||||
## [9.6.9] - 2020-06-10
|
|
||||||
|
|
||||||
(redis cobra bots) update the cobra to redis bot to use the bot framework, and change it to report fps metrics into redis streams.
|
|
||||||
|
|
||||||
## [9.6.6] - 2020-06-04
|
|
||||||
|
|
||||||
(statsd cobra bots) statsd improvement: prefix does not need a dot as a suffix, message size can be larger than 256 bytes, error handling was invalid, use core logger for logging instead of std::cerr
|
|
||||||
|
|
||||||
## [9.6.5] - 2020-05-29
|
|
||||||
|
|
||||||
(http server) support gzip compression
|
|
||||||
|
|
||||||
## [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
|
## [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.
|
(windows) when using OpenSSL, the system store is used to populate the cacert. No need to ship a cacert.pem file with your app.
|
||||||
|
@ -17,15 +17,11 @@ There is a unittest which can be executed by typing `make test`.
|
|||||||
|
|
||||||
Options for building:
|
Options for building:
|
||||||
|
|
||||||
* `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension
|
|
||||||
* `-DUSE_TLS=1` will enable TLS support
|
* `-DUSE_TLS=1` will enable TLS support
|
||||||
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows)
|
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support (default on 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_WS=1` will build the ws interactive command line tool
|
||||||
* `-DUSE_TEST=1` will build the unittest
|
|
||||||
* `-DUSE_PYTHON=1` will use Python3 for cobra bots, require Python3 to be installed.
|
|
||||||
|
|
||||||
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/unittest_windows.yml) which have instructions for building dependencies.
|
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.
|
||||||
|
|
||||||
It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:
|
It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:
|
||||||
|
|
||||||
@ -44,19 +40,6 @@ It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/m
|
|||||||
```
|
```
|
||||||
vcpkg install ixwebsocket
|
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
|
### Conan
|
||||||
|
|
||||||
@ -76,7 +59,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.
|
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 docker.pkg.github.com/machinezone/ixwebsocket/ws:latest --help
|
docker run bsergean/ws
|
||||||
```
|
```
|
||||||
|
|
||||||
To use docker-compose you must make a docker container first.
|
To use docker-compose you must make a docker container first.
|
||||||
|
@ -38,7 +38,8 @@ The regression test is running after each commit on github actions for multiple
|
|||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
* On some configuration (mostly Android) certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. With mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
|
* On 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.
|
||||||
* 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.
|
* 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.
|
* 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.
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|

|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client and server HTTP communication. *TLS* aka *SSL* is supported. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
|
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client and server HTTP communication. *TLS* aka *SSL* is supported. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
|
||||||
|
@ -4,7 +4,7 @@ Notes on how we can update the different packages for ixwebsocket.
|
|||||||
|
|
||||||
Visit the [releases](https://github.com/machinezone/IXWebSocket/releases) page on Github. A tag must have been made first.
|
Visit the [releases](https://github.com/machinezone/IXWebSocket/releases) page on Github. A tag must have been made first.
|
||||||
|
|
||||||
Download the latest entry.
|
Download the latest entry.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd /tmp
|
$ cd /tmp
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
## WebSocket Client performance
|
|
||||||
|
|
||||||
We will run a client and a server on the same machine, connecting to localhost. This bench is run on a MacBook Pro from 2015. We can receive over 200,000 (small) messages per second, another way to put it is that it takes 5 micro-second to receive and process one message. This is an indication about the minimal latency to receive messages.
|
|
||||||
|
|
||||||
### Receiving messages
|
|
||||||
|
|
||||||
By using the push_server ws sub-command, the server will send the same message in a loop to any connected client.
|
|
||||||
|
|
||||||
```
|
|
||||||
ws push_server -q --send_msg 'yo'
|
|
||||||
```
|
|
||||||
|
|
||||||
By using the echo_client ws sub-command, with the -m (mute or no_send), we will display statistics on how many messages we can receive per second.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ws echo_client -m ws://localhost:8008
|
|
||||||
[2020-08-02 12:31:17.284] [info] ws_echo_client: connected
|
|
||||||
[2020-08-02 12:31:17.284] [info] Uri: /
|
|
||||||
[2020-08-02 12:31:17.284] [info] Headers:
|
|
||||||
[2020-08-02 12:31:17.284] [info] Connection: Upgrade
|
|
||||||
[2020-08-02 12:31:17.284] [info] Sec-WebSocket-Accept: byy/pMK2d0PtRwExaaiOnXJTQHo=
|
|
||||||
[2020-08-02 12:31:17.284] [info] Server: ixwebsocket/10.1.4 macos ssl/SecureTransport zlib 1.2.11
|
|
||||||
[2020-08-02 12:31:17.284] [info] Upgrade: websocket
|
|
||||||
[2020-08-02 12:31:17.663] [info] messages received: 0 per second 2595307 total
|
|
||||||
[2020-08-02 12:31:18.668] [info] messages received: 79679 per second 2674986 total
|
|
||||||
[2020-08-02 12:31:19.668] [info] messages received: 207438 per second 2882424 total
|
|
||||||
[2020-08-02 12:31:20.673] [info] messages received: 209207 per second 3091631 total
|
|
||||||
[2020-08-02 12:31:21.676] [info] messages received: 216056 per second 3307687 total
|
|
||||||
[2020-08-02 12:31:22.680] [info] messages received: 214927 per second 3522614 total
|
|
||||||
[2020-08-02 12:31:23.684] [info] messages received: 216960 per second 3739574 total
|
|
||||||
[2020-08-02 12:31:24.688] [info] messages received: 215232 per second 3954806 total
|
|
||||||
[2020-08-02 12:31:25.691] [info] messages received: 212300 per second 4167106 total
|
|
||||||
[2020-08-02 12:31:26.694] [info] messages received: 212501 per second 4379607 total
|
|
||||||
[2020-08-02 12:31:27.699] [info] messages received: 212330 per second 4591937 total
|
|
||||||
[2020-08-02 12:31:28.702] [info] messages received: 216511 per second 4808448 total
|
|
||||||
```
|
|
174
docs/usage.md
174
docs/usage.md
@ -67,28 +67,9 @@ webSocket.stop()
|
|||||||
|
|
||||||
### Sending messages
|
### Sending messages
|
||||||
|
|
||||||
`WebSocketSendInfo result = websocket.send("foo")` will send a message.
|
`websocket.send("foo")` will send a message.
|
||||||
|
|
||||||
If the connection was closed, sending will fail, and the success field of the result object will be set to false. There could also be a compression error in which case the compressError field will be set to true. The payloadSize field and wireSize fields will tell you respectively how much bytes the message weight, and how many bytes were sent on the wire (potentially compressed + counting the message header (a few bytes).
|
If the connection was closed and sending failed, the return value will be set to false.
|
||||||
|
|
||||||
There is an optional progress callback that can be passed in as the second argument. If a message is large it will be fragmented into chunks which will be sent independantly. Everytime the we can write a fragment into the OS network cache, the callback will be invoked. If a user wants to cancel a slow send, false should be returned from within the callback.
|
|
||||||
|
|
||||||
Here is an example code snippet copied from the ws send sub-command. Each fragment weights 32K, so the total integer is the wireSize divided by 32K. As an example if you are sending 32M of data, uncompressed, total will be 1000. current will be set to 0 for the first fragment, then 1, 2 etc...
|
|
||||||
|
|
||||||
```
|
|
||||||
auto result =
|
|
||||||
_webSocket.sendBinary(serializedMsg, [this, throttle](int current, int total) -> bool {
|
|
||||||
spdlog::info("ws_send: Step {} out of {}", current + 1, total);
|
|
||||||
|
|
||||||
if (throttle)
|
|
||||||
{
|
|
||||||
std::chrono::duration<double, std::milli> duration(10);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _connected;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### ReadyState
|
### ReadyState
|
||||||
|
|
||||||
@ -265,10 +246,6 @@ uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
|
|||||||
|
|
||||||
## WebSocket server API
|
## WebSocket server API
|
||||||
|
|
||||||
### Legacy api
|
|
||||||
|
|
||||||
This api was actually changed to take a weak_ptr<WebSocket> as the first argument to setOnConnectionCallback ; previously it would take a shared_ptr<WebSocket> which was creating cycles and then memory leaks problems.
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
|
||||||
@ -279,50 +256,39 @@ This api was actually changed to take a weak_ptr<WebSocket> as the first argumen
|
|||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::weak_ptr<WebSocket> webSocket,
|
[&server](std::shared_ptr<WebSocket> webSocket,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
|
||||||
{
|
{
|
||||||
std::cout << "Remote ip: " << connectionInfo->remoteIp << std::endl;
|
webSocket->setOnMessageCallback(
|
||||||
|
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
|
||||||
auto ws = webSocket.lock();
|
{
|
||||||
if (ws)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
|
||||||
ws->setOnMessageCallback(
|
|
||||||
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
|
|
||||||
{
|
{
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
std::cerr << "New connection" << std::endl;
|
||||||
|
|
||||||
|
// A connection state object is available, and has a default id
|
||||||
|
// You can subclass ConnectionState and pass an alternate factory
|
||||||
|
// to override it. It is useful if you want to store custom
|
||||||
|
// attributes per connection (authenticated bool flag, attributes, etc...)
|
||||||
|
std::cerr << "id: " << connectionState->getId() << std::endl;
|
||||||
|
|
||||||
|
// The uri the client did connect to.
|
||||||
|
std::cerr << "Uri: " << msg->openInfo.uri << std::endl;
|
||||||
|
|
||||||
|
std::cerr << "Headers:" << std::endl;
|
||||||
|
for (auto it : msg->openInfo.headers)
|
||||||
{
|
{
|
||||||
std::cout << "New connection" << std::endl;
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
|
|
||||||
// A connection state object is available, and has a default id
|
|
||||||
// You can subclass ConnectionState and pass an alternate factory
|
|
||||||
// to override it. It is useful if you want to store custom
|
|
||||||
// attributes per connection (authenticated bool flag, attributes, etc...)
|
|
||||||
std::cout << "id: " << connectionState->getId() << std::endl;
|
|
||||||
|
|
||||||
// The uri the client did connect to.
|
|
||||||
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
|
|
||||||
|
|
||||||
std::cout << "Headers:" << std::endl;
|
|
||||||
for (auto it : msg->openInfo.headers)
|
|
||||||
{
|
|
||||||
std::cout << it.first << ": " << it.second << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
// For an echo server, we just send back to the client whatever was received by the server
|
|
||||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
|
||||||
// Second parameter tells whether we are sending the message in binary or text mode.
|
|
||||||
// Here we send it in the same mode as it was received.
|
|
||||||
auto ws = webSocket.lock();
|
|
||||||
if (ws)
|
|
||||||
{
|
|
||||||
ws->send(msg->str, msg->binary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
// For an echo server, we just send back to the client whatever was received by the server
|
||||||
|
// All connected clients are available in an std::set. See the broadcast cpp example.
|
||||||
|
// Second parameter tells whether we are sending the message in binary or text mode.
|
||||||
|
// Here we send it in the same mode as it was received.
|
||||||
|
webSocket->send(msg->str, msg->binary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -343,74 +309,6 @@ server.wait();
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### New api
|
|
||||||
|
|
||||||
The new API does not require to use 2 nested callbacks, which is a bit annoying. The real fix is that there was a memory leak due to a shared_ptr cycle, due to passing down a shared_ptr<WebSocket> down to the callbacks.
|
|
||||||
|
|
||||||
The webSocket reference is guaranteed to be always valid ; by design the callback will never be invoked with a null webSocket object.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
// Run a server on localhost at a given port.
|
|
||||||
// Bound host name, max connections and listen backlog can also be passed in as parameters.
|
|
||||||
ix::WebSocketServer server(port);
|
|
||||||
|
|
||||||
server.setOnClientMessageCallback(std::shared_ptr<ConnectionState> connectionState,
|
|
||||||
ConnectionInfo& connectionInfo,
|
|
||||||
WebSocket& webSocket,
|
|
||||||
const WebSocketMessagePtr& msg)
|
|
||||||
{
|
|
||||||
// The ConnectionInfo object contains information about the connection,
|
|
||||||
// at this point only the client ip address and the port.
|
|
||||||
std::cout << "Remote ip: " << connectionInfo.remoteIp << std::endl;
|
|
||||||
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
std::cout << "New connection" << std::endl;
|
|
||||||
|
|
||||||
// A connection state object is available, and has a default id
|
|
||||||
// You can subclass ConnectionState and pass an alternate factory
|
|
||||||
// to override it. It is useful if you want to store custom
|
|
||||||
// attributes per connection (authenticated bool flag, attributes, etc...)
|
|
||||||
std::cout << "id: " << connectionState->getId() << std::endl;
|
|
||||||
|
|
||||||
// The uri the client did connect to.
|
|
||||||
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
|
|
||||||
|
|
||||||
std::cout << "Headers:" << std::endl;
|
|
||||||
for (auto it : msg->openInfo.headers)
|
|
||||||
{
|
|
||||||
std::cout << it.first << ": " << it.second << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
// For an echo server, we just send back to the client whatever was received by the server
|
|
||||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
|
||||||
// Second parameter tells whether we are sending the message in binary or text mode.
|
|
||||||
// Here we send it in the same mode as it was received.
|
|
||||||
webSocket.send(msg->str, msg->binary);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
auto res = server.listen();
|
|
||||||
if (!res.first)
|
|
||||||
{
|
|
||||||
// Error handling
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the server in the background. Server can be stoped by calling server.stop()
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
// Block until server.stop() is called.
|
|
||||||
server.wait();
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## HTTP client API
|
## HTTP client API
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@ -494,8 +392,6 @@ bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
|
|||||||
// ok will be false if your httpClient is not async
|
// ok will be false if your httpClient is not async
|
||||||
```
|
```
|
||||||
|
|
||||||
See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart.
|
|
||||||
|
|
||||||
## HTTP server API
|
## HTTP server API
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@ -519,14 +415,11 @@ If you want to handle how requests are processed, implement the setOnConnectionC
|
|||||||
```cpp
|
```cpp
|
||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this](HttpRequestPtr request,
|
[this](HttpRequestPtr request,
|
||||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr
|
|
||||||
{
|
{
|
||||||
// Build a string for the response
|
// Build a string for the response
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << connectionInfo->remoteIp
|
ss << request->method
|
||||||
<< " "
|
|
||||||
<< request->method
|
|
||||||
<< " "
|
<< " "
|
||||||
<< request->uri;
|
<< request->uri;
|
||||||
|
|
||||||
@ -554,7 +447,7 @@ Additional TLS options can be configured by passing a `ix::SocketTLSOptions` ins
|
|||||||
webSocket.setTLSOptions({
|
webSocket.setTLSOptions({
|
||||||
.certFile = "path/to/cert/file.pem",
|
.certFile = "path/to/cert/file.pem",
|
||||||
.keyFile = "path/to/key/file.pem",
|
.keyFile = "path/to/key/file.pem",
|
||||||
.caFile = "path/to/trust/bundle/file.pem", // as a file, or in memory buffer in PEM format
|
.caFile = "path/to/trust/bundle/file.pem",
|
||||||
.tls = true // required in server mode
|
.tls = true // required in server mode
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@ -568,7 +461,6 @@ 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.
|
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 `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.
|
- 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.
|
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.
|
||||||
|
|
||||||
|
46
httpd.cpp
46
httpd.cpp
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* httpd.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Buid with make httpd
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <ixwebsocket/IXHttpServer.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
|
||||||
{
|
|
||||||
if (argc != 3)
|
|
||||||
{
|
|
||||||
std::cerr << "Usage: " << argv[0]
|
|
||||||
<< " <port> <host>" << std::endl;
|
|
||||||
std::cerr << " " << argv[0] << " 9090 127.0.0.1" << std::endl;
|
|
||||||
std::cerr << " " << argv[0] << " 9090 0.0.0.0" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int port;
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << argv[1];
|
|
||||||
ss >> port;
|
|
||||||
std::string hostname(argv[2]);
|
|
||||||
|
|
||||||
std::cout << "Listening on " << hostname
|
|
||||||
<< ":" << port << std::endl;
|
|
||||||
|
|
||||||
ix::HttpServer server(port, hostname);
|
|
||||||
|
|
||||||
auto res = server.listen();
|
|
||||||
if (!res.first)
|
|
||||||
{
|
|
||||||
std::cout << res.second << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.start();
|
|
||||||
server.wait();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -4,23 +4,16 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
set (IXBOTS_SOURCES
|
set (IXBOTS_SOURCES
|
||||||
ixbots/IXCobraBot.cpp
|
|
||||||
ixbots/IXCobraToSentryBot.cpp
|
ixbots/IXCobraToSentryBot.cpp
|
||||||
ixbots/IXCobraToStatsdBot.cpp
|
ixbots/IXCobraToStatsdBot.cpp
|
||||||
ixbots/IXCobraToStdoutBot.cpp
|
ixbots/IXQueueManager.cpp
|
||||||
ixbots/IXCobraMetricsToRedisBot.cpp
|
|
||||||
ixbots/IXCobraToPythonBot.cpp
|
|
||||||
ixbots/IXStatsdClient.cpp
|
ixbots/IXStatsdClient.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set (IXBOTS_HEADERS
|
set (IXBOTS_HEADERS
|
||||||
ixbots/IXCobraBot.h
|
|
||||||
ixbots/IXCobraBotConfig.h
|
|
||||||
ixbots/IXCobraToSentryBot.h
|
ixbots/IXCobraToSentryBot.h
|
||||||
ixbots/IXCobraToStatsdBot.h
|
ixbots/IXCobraToStatsdBot.h
|
||||||
ixbots/IXCobraToStdoutBot.h
|
ixbots/IXQueueManager.h
|
||||||
ixbots/IXCobraMetricsToRedisBot.h
|
|
||||||
ixbots/IXCobraToPythonBot.h
|
|
||||||
ixbots/IXStatsdClient.h
|
ixbots/IXStatsdClient.h
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,9 +27,9 @@ if (NOT JSONCPP_FOUND)
|
|||||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_PYTHON)
|
find_package(SpdLog)
|
||||||
target_compile_definitions(ixbots PUBLIC IXBOTS_USE_PYTHON)
|
if (NOT SPDLOG_FOUND)
|
||||||
find_package(Python COMPONENTS Development)
|
set(SPDLOG_INCLUDE_DIRS ../third_party/spdlog/include)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(IXBOTS_INCLUDE_DIRS
|
set(IXBOTS_INCLUDE_DIRS
|
||||||
@ -45,13 +38,8 @@ set(IXBOTS_INCLUDE_DIRS
|
|||||||
../ixcore
|
../ixcore
|
||||||
../ixwebsocket
|
../ixwebsocket
|
||||||
../ixcobra
|
../ixcobra
|
||||||
../ixredis
|
|
||||||
../ixsentry
|
../ixsentry
|
||||||
${JSONCPP_INCLUDE_DIRS}
|
${JSONCPP_INCLUDE_DIRS}
|
||||||
${SPDLOG_INCLUDE_DIRS})
|
${SPDLOG_INCLUDE_DIRS})
|
||||||
|
|
||||||
if (USE_PYTHON)
|
|
||||||
set(IXBOTS_INCLUDE_DIRS ${IXBOTS_INCLUDE_DIRS} ${Python_INCLUDE_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_include_directories( ixbots PUBLIC ${IXBOTS_INCLUDE_DIRS} )
|
target_include_directories( ixbots PUBLIC ${IXBOTS_INCLUDE_DIRS} )
|
||||||
|
@ -1,320 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 <ixwebsocket/IXSetThreadName.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;
|
|
||||||
auto batchSize = botConfig.batchSize;
|
|
||||||
|
|
||||||
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);
|
|
||||||
std::atomic<bool> stalledConnection(false);
|
|
||||||
int minuteCounter = 0;
|
|
||||||
|
|
||||||
auto timer = [&sentCount,
|
|
||||||
&receivedCount,
|
|
||||||
&sentCountTotal,
|
|
||||||
&receivedCountTotal,
|
|
||||||
&sentCountPerSecs,
|
|
||||||
&receivedCountPerSecs,
|
|
||||||
&receivedCountPerMinutes,
|
|
||||||
&minuteCounter,
|
|
||||||
&conn,
|
|
||||||
&stop] {
|
|
||||||
setThreadName("Bot progress");
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (conn.isAuthenticated())
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
&stalledConnection]
|
|
||||||
{
|
|
||||||
setThreadName("Bot heartbeat");
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
ss.str("");
|
|
||||||
ss << "no messages received or sent for "
|
|
||||||
<< heartBeatTimeout << " seconds, reconnecting";
|
|
||||||
|
|
||||||
CoreLogger::error(ss.str());
|
|
||||||
stalledConnection = true;
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
batchSize,
|
|
||||||
&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, batchSize,
|
|
||||||
[&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;
|
|
||||||
|
|
||||||
if (stalledConnection)
|
|
||||||
{
|
|
||||||
conn.disconnect();
|
|
||||||
conn.connect();
|
|
||||||
stalledConnection = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
if (stalledConnection)
|
|
||||||
{
|
|
||||||
conn.disconnect();
|
|
||||||
conn.connect();
|
|
||||||
stalledConnection = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CobraBot::getDeviceIdentifier(const Json::Value& msg)
|
|
||||||
{
|
|
||||||
std::string deviceId("na");
|
|
||||||
|
|
||||||
auto osName = msg["device"]["os_name"];
|
|
||||||
if (osName == "Android")
|
|
||||||
{
|
|
||||||
deviceId = msg["device"]["model"].asString();
|
|
||||||
}
|
|
||||||
else if (osName == "iOS")
|
|
||||||
{
|
|
||||||
deviceId = msg["device"]["hardware_model"].asString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ix
|
|
@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
std::string getDeviceIdentifier(const Json::Value& msg);
|
|
||||||
|
|
||||||
private:
|
|
||||||
OnBotMessageCallback _onBotMessageCallback;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
int batchSize = 1;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXCobraMetricsToRedisBot.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXCobraMetricsToRedisBot.h"
|
|
||||||
|
|
||||||
#include "IXCobraBot.h"
|
|
||||||
#include "IXStatsdClient.h"
|
|
||||||
#include <chrono>
|
|
||||||
#include <ixcobra/IXCobraConnection.h>
|
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <map>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
std::string removeSpaces(const std::string& str)
|
|
||||||
{
|
|
||||||
std::string out(str);
|
|
||||||
out.erase(
|
|
||||||
std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }),
|
|
||||||
out.end());
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
bool processPerfMetricsEventSlowFrames(const Json::Value& msg,
|
|
||||||
RedisClient& redisClient,
|
|
||||||
const std::string& deviceId)
|
|
||||||
{
|
|
||||||
auto frameRateHistogramCounts = msg["data"]["FrameRateHistogramCounts"];
|
|
||||||
|
|
||||||
int slowFrames = 0;
|
|
||||||
slowFrames += frameRateHistogramCounts[4].asInt();
|
|
||||||
slowFrames += frameRateHistogramCounts[5].asInt();
|
|
||||||
slowFrames += frameRateHistogramCounts[6].asInt();
|
|
||||||
slowFrames += frameRateHistogramCounts[7].asInt();
|
|
||||||
|
|
||||||
//
|
|
||||||
// XADD without a device id
|
|
||||||
//
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << msg["id"].asString() << "_slow_frames" << "."
|
|
||||||
<< msg["device"]["game"].asString() << "."
|
|
||||||
<< msg["device"]["os_name"].asString() << "."
|
|
||||||
<< removeSpaces(msg["data"]["Tag"].asString());
|
|
||||||
|
|
||||||
int maxLen;
|
|
||||||
maxLen = 100000;
|
|
||||||
std::string id = ss.str();
|
|
||||||
std::string errMsg;
|
|
||||||
if (redisClient.xadd(id, std::to_string(slowFrames), maxLen, errMsg).empty())
|
|
||||||
{
|
|
||||||
CoreLogger::info(std::string("redis XADD error: ") + errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// XADD with a device id
|
|
||||||
//
|
|
||||||
ss.str(""); // reset the stringstream
|
|
||||||
ss << msg["id"].asString() << "_slow_frames_by_device" << "."
|
|
||||||
<< deviceId << "."
|
|
||||||
<< msg["device"]["game"].asString() << "."
|
|
||||||
<< msg["device"]["os_name"].asString() << "."
|
|
||||||
<< removeSpaces(msg["data"]["Tag"].asString());
|
|
||||||
|
|
||||||
id = ss.str();
|
|
||||||
maxLen = 1000;
|
|
||||||
if (redisClient.xadd(id, std::to_string(slowFrames), maxLen, errMsg).empty())
|
|
||||||
{
|
|
||||||
CoreLogger::info(std::string("redis XADD error: ") + errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Add device to the device zset, and increment the score
|
|
||||||
// so that we know which devices are used more than others
|
|
||||||
// ZINCRBY myzset 1 one
|
|
||||||
//
|
|
||||||
ss.str(""); // reset the stringstream
|
|
||||||
ss << msg["id"].asString() << "_slow_frames_devices" << "."
|
|
||||||
<< msg["device"]["game"].asString();
|
|
||||||
|
|
||||||
id = ss.str();
|
|
||||||
std::vector<std::string> args = {
|
|
||||||
"ZINCRBY", id, "1", deviceId
|
|
||||||
};
|
|
||||||
auto response = redisClient.send(args, errMsg);
|
|
||||||
if (response.first == RespType::Error)
|
|
||||||
{
|
|
||||||
CoreLogger::info(std::string("redis ZINCRBY error: ") + errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t cobra_metrics_to_redis_bot(const ix::CobraBotConfig& config,
|
|
||||||
RedisClient& redisClient,
|
|
||||||
bool verbose)
|
|
||||||
{
|
|
||||||
CobraBot bot;
|
|
||||||
|
|
||||||
bot.setOnBotMessageCallback(
|
|
||||||
[&redisClient, &verbose, &bot]
|
|
||||||
(const Json::Value& msg,
|
|
||||||
const std::string& /*position*/,
|
|
||||||
std::atomic<bool>& /*throttled*/,
|
|
||||||
std::atomic<bool>& /*fatalCobraError*/,
|
|
||||||
std::atomic<uint64_t>& sentCount) -> void {
|
|
||||||
if (msg["device"].isNull())
|
|
||||||
{
|
|
||||||
CoreLogger::info("no device entry, skipping event");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg["id"].isNull())
|
|
||||||
{
|
|
||||||
CoreLogger::info("no id entry, skipping event");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Display full message with
|
|
||||||
if (verbose)
|
|
||||||
{
|
|
||||||
CoreLogger::info(msg.toStyledString());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool success = false;
|
|
||||||
if (msg["id"].asString() == "engine_performance_metrics_id")
|
|
||||||
{
|
|
||||||
auto deviceId = bot.getDeviceIdentifier(msg);
|
|
||||||
success = processPerfMetricsEventSlowFrames(msg, redisClient, deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) sentCount++;
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot.run(config);
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXCobraMetricsToRedisBot.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <ixredis/IXRedisClient.h>
|
|
||||||
#include "IXCobraBotConfig.h"
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
int64_t cobra_metrics_to_redis_bot(const ix::CobraBotConfig& config,
|
|
||||||
RedisClient& redisClient,
|
|
||||||
bool verbose);
|
|
||||||
} // namespace ix
|
|
||||||
|
|
@ -1,332 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXCobraToPythonBot.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXCobraToPythonBot.h"
|
|
||||||
|
|
||||||
#include "IXCobraBot.h"
|
|
||||||
#include "IXStatsdClient.h"
|
|
||||||
#include <chrono>
|
|
||||||
#include <ixcobra/IXCobraConnection.h>
|
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <map>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
//
|
|
||||||
// I cannot get Windows to easily build on CI (github action) so support
|
|
||||||
// is disabled for now. It should be a simple fix
|
|
||||||
// (linking error about missing debug build)
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifdef IXBOTS_USE_PYTHON
|
|
||||||
#define PY_SSIZE_T_CLEAN
|
|
||||||
#include <Python.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef IXBOTS_USE_PYTHON
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// This function is unused at this point. It produce a correct output,
|
|
||||||
// but triggers memory leaks when called repeateadly, as I cannot figure out how to
|
|
||||||
// make the reference counting Python functions to work properly (Py_DECREF and friends)
|
|
||||||
//
|
|
||||||
PyObject* jsonToPythonObject(const Json::Value& val)
|
|
||||||
{
|
|
||||||
switch(val.type())
|
|
||||||
{
|
|
||||||
case Json::nullValue:
|
|
||||||
{
|
|
||||||
return Py_None;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Json::intValue:
|
|
||||||
{
|
|
||||||
return PyLong_FromLong(val.asInt64());
|
|
||||||
}
|
|
||||||
|
|
||||||
case Json::uintValue:
|
|
||||||
{
|
|
||||||
return PyLong_FromLong(val.asUInt64());
|
|
||||||
}
|
|
||||||
|
|
||||||
case Json::realValue:
|
|
||||||
{
|
|
||||||
return PyFloat_FromDouble(val.asDouble());
|
|
||||||
}
|
|
||||||
|
|
||||||
case Json::stringValue:
|
|
||||||
{
|
|
||||||
return PyUnicode_FromString(val.asCString());
|
|
||||||
}
|
|
||||||
|
|
||||||
case Json::booleanValue:
|
|
||||||
{
|
|
||||||
return val.asBool() ? Py_True : Py_False;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Json::arrayValue:
|
|
||||||
{
|
|
||||||
PyObject* list = PyList_New(val.size());
|
|
||||||
Py_ssize_t i = 0;
|
|
||||||
for (auto&& it = val.begin(); it != val.end(); ++it)
|
|
||||||
{
|
|
||||||
PyList_SetItem(list, i++, jsonToPythonObject(*it));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Json::objectValue:
|
|
||||||
{
|
|
||||||
PyObject* dict = PyDict_New();
|
|
||||||
for (auto&& it = val.begin(); it != val.end(); ++it)
|
|
||||||
{
|
|
||||||
PyObject* key = jsonToPythonObject(it.key());
|
|
||||||
PyObject* value = jsonToPythonObject(*it);
|
|
||||||
|
|
||||||
PyDict_SetItem(dict, key, value);
|
|
||||||
}
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
int64_t cobra_to_python_bot(const ix::CobraBotConfig& config,
|
|
||||||
StatsdClient& statsdClient,
|
|
||||||
const std::string& scriptPath)
|
|
||||||
{
|
|
||||||
#ifndef IXBOTS_USE_PYTHON
|
|
||||||
CoreLogger::error("Command is disabled. "
|
|
||||||
"Needs to be configured with USE_PYTHON=1");
|
|
||||||
return -1;
|
|
||||||
#else
|
|
||||||
CobraBot bot;
|
|
||||||
Py_InitializeEx(0); // 0 arg so that we do not install signal handlers
|
|
||||||
// which prevent us from using Ctrl-C
|
|
||||||
|
|
||||||
size_t lastIndex = scriptPath.find_last_of(".");
|
|
||||||
std::string modulePath = scriptPath.substr(0, lastIndex);
|
|
||||||
|
|
||||||
PyObject* pyModuleName = PyUnicode_DecodeFSDefault(modulePath.c_str());
|
|
||||||
|
|
||||||
if (pyModuleName == nullptr)
|
|
||||||
{
|
|
||||||
CoreLogger::error("Python error: Cannot decode file system path");
|
|
||||||
PyErr_Print();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import module
|
|
||||||
PyObject* pyModule = PyImport_Import(pyModuleName);
|
|
||||||
Py_DECREF(pyModuleName);
|
|
||||||
if (pyModule == nullptr)
|
|
||||||
{
|
|
||||||
CoreLogger::error("Python error: Cannot import module.");
|
|
||||||
CoreLogger::error("Module name cannot countain dash characters.");
|
|
||||||
CoreLogger::error("Is PYTHONPATH set correctly ?");
|
|
||||||
PyErr_Print();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// module main funtion name is named 'run'
|
|
||||||
const std::string entryPoint("run");
|
|
||||||
PyObject* pyFunc = PyObject_GetAttrString(pyModule, entryPoint.c_str());
|
|
||||||
|
|
||||||
if (!pyFunc)
|
|
||||||
{
|
|
||||||
CoreLogger::error("run symbol is missing from module.");
|
|
||||||
PyErr_Print();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PyCallable_Check(pyFunc))
|
|
||||||
{
|
|
||||||
CoreLogger::error("run symbol is not a function.");
|
|
||||||
PyErr_Print();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.setOnBotMessageCallback(
|
|
||||||
[&statsdClient, pyFunc]
|
|
||||||
(const Json::Value& msg,
|
|
||||||
const std::string& /*position*/,
|
|
||||||
std::atomic<bool>& /*throttled*/,
|
|
||||||
std::atomic<bool>& fatalCobraError,
|
|
||||||
std::atomic<uint64_t>& sentCount) -> void {
|
|
||||||
//
|
|
||||||
// Invoke python script here. First build function parameters, a tuple
|
|
||||||
//
|
|
||||||
const int kVersion = 1; // We can bump this and let the interface evolve
|
|
||||||
|
|
||||||
PyObject *pyArgs = PyTuple_New(2);
|
|
||||||
PyTuple_SetItem(pyArgs, 0, PyLong_FromLong(kVersion)); // First argument
|
|
||||||
|
|
||||||
//
|
|
||||||
// It would be better to create a Python object (a dictionary)
|
|
||||||
// from the json msg, but it is simpler to serialize it to a string
|
|
||||||
// and decode it on the Python side of the fence
|
|
||||||
//
|
|
||||||
PyObject* pySerializedJson = PyUnicode_FromString(msg.toStyledString().c_str());
|
|
||||||
PyTuple_SetItem(pyArgs, 1, pySerializedJson); // Second argument
|
|
||||||
|
|
||||||
// Invoke the python routine
|
|
||||||
PyObject* pyList = PyObject_CallObject(pyFunc, pyArgs);
|
|
||||||
|
|
||||||
// Error calling the function
|
|
||||||
if (pyList == nullptr)
|
|
||||||
{
|
|
||||||
fatalCobraError = true;
|
|
||||||
CoreLogger::error("run() function call failed. Input msg: ");
|
|
||||||
auto serializedMsg = msg.toStyledString();
|
|
||||||
CoreLogger::error(serializedMsg);
|
|
||||||
PyErr_Print();
|
|
||||||
CoreLogger::error("================");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid return type
|
|
||||||
if (!PyList_Check(pyList))
|
|
||||||
{
|
|
||||||
fatalCobraError = true;
|
|
||||||
CoreLogger::error("run() return type should be a list");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The result is a list of dict containing sufficient info
|
|
||||||
// to send messages to statsd
|
|
||||||
auto listSize = PyList_Size(pyList);
|
|
||||||
|
|
||||||
for (Py_ssize_t i = 0 ; i < listSize; ++i)
|
|
||||||
{
|
|
||||||
PyObject* dict = PyList_GetItem(pyList, i);
|
|
||||||
|
|
||||||
// Make sure this is a dict
|
|
||||||
if (!PyDict_Check(dict))
|
|
||||||
{
|
|
||||||
fatalCobraError = true;
|
|
||||||
CoreLogger::error("list element is not a dict");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Retrieve object kind
|
|
||||||
//
|
|
||||||
PyObject* pyKind = PyDict_GetItemString(dict, "kind");
|
|
||||||
if (!PyUnicode_Check(pyKind))
|
|
||||||
{
|
|
||||||
fatalCobraError = true;
|
|
||||||
CoreLogger::error("kind entry is not a string");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::string kind(PyUnicode_AsUTF8(pyKind));
|
|
||||||
|
|
||||||
bool counter = false;
|
|
||||||
bool gauge = false;
|
|
||||||
bool timing = false;
|
|
||||||
|
|
||||||
if (kind == "counter")
|
|
||||||
{
|
|
||||||
counter = true;
|
|
||||||
}
|
|
||||||
else if (kind == "gauge")
|
|
||||||
{
|
|
||||||
gauge = true;
|
|
||||||
}
|
|
||||||
else if (kind == "timing")
|
|
||||||
{
|
|
||||||
timing = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fatalCobraError = true;
|
|
||||||
CoreLogger::error(std::string("invalid kind entry: ") + kind +
|
|
||||||
". Supported ones are counter, gauge, timing");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Retrieve object key
|
|
||||||
//
|
|
||||||
PyObject* pyKey = PyDict_GetItemString(dict, "key");
|
|
||||||
if (!PyUnicode_Check(pyKey))
|
|
||||||
{
|
|
||||||
fatalCobraError = true;
|
|
||||||
CoreLogger::error("key entry is not a string");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::string key(PyUnicode_AsUTF8(pyKey));
|
|
||||||
|
|
||||||
//
|
|
||||||
// Retrieve object value and send data to statsd
|
|
||||||
//
|
|
||||||
PyObject* pyValue = PyDict_GetItemString(dict, "value");
|
|
||||||
|
|
||||||
// Send data to statsd
|
|
||||||
if (PyFloat_Check(pyValue))
|
|
||||||
{
|
|
||||||
double value = PyFloat_AsDouble(pyValue);
|
|
||||||
|
|
||||||
if (counter)
|
|
||||||
{
|
|
||||||
statsdClient.count(key, value);
|
|
||||||
}
|
|
||||||
else if (gauge)
|
|
||||||
{
|
|
||||||
statsdClient.gauge(key, value);
|
|
||||||
}
|
|
||||||
else if (timing)
|
|
||||||
{
|
|
||||||
statsdClient.timing(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (PyLong_Check(pyValue))
|
|
||||||
{
|
|
||||||
long value = PyLong_AsLong(pyValue);
|
|
||||||
|
|
||||||
if (counter)
|
|
||||||
{
|
|
||||||
statsdClient.count(key, value);
|
|
||||||
}
|
|
||||||
else if (gauge)
|
|
||||||
{
|
|
||||||
statsdClient.gauge(key, value);
|
|
||||||
}
|
|
||||||
else if (timing)
|
|
||||||
{
|
|
||||||
statsdClient.timing(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fatalCobraError = true;
|
|
||||||
CoreLogger::error("value entry is neither an int or a float");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
sentCount++; // should we update this for each statsd object sent ?
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_DECREF(pyArgs);
|
|
||||||
Py_DECREF(pyList);
|
|
||||||
});
|
|
||||||
|
|
||||||
bool status = bot.run(config);
|
|
||||||
|
|
||||||
// Cleanup - we should do something similar in all exit case ...
|
|
||||||
Py_DECREF(pyFunc);
|
|
||||||
Py_DECREF(pyModule);
|
|
||||||
Py_FinalizeEx();
|
|
||||||
|
|
||||||
return status;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXCobraMetricsToStatsdBot.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <ixbots/IXStatsdClient.h>
|
|
||||||
#include "IXCobraBotConfig.h"
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
int64_t cobra_to_python_bot(const ix::CobraBotConfig& config,
|
|
||||||
StatsdClient& statsdClient,
|
|
||||||
const std::string& scriptPath);
|
|
||||||
} // namespace ix
|
|
@ -5,72 +5,301 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraToSentryBot.h"
|
#include "IXCobraToSentryBot.h"
|
||||||
|
#include "IXQueueManager.h"
|
||||||
#include "IXCobraBot.h"
|
|
||||||
#include <ixcobra/IXCobraConnection.h>
|
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <ixcobra/IXCobraConnection.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
int64_t cobra_to_sentry_bot(const CobraBotConfig& config,
|
int cobra_to_sentry_bot(const CobraConfig& config,
|
||||||
SentryClient& sentryClient,
|
const std::string& channel,
|
||||||
bool verbose)
|
const std::string& filter,
|
||||||
|
const std::string& position,
|
||||||
|
SentryClient& sentryClient,
|
||||||
|
bool verbose,
|
||||||
|
bool strict,
|
||||||
|
size_t maxQueueSize,
|
||||||
|
bool enableHeartbeat,
|
||||||
|
int runtime)
|
||||||
{
|
{
|
||||||
CobraBot bot;
|
ix::CobraConnection conn;
|
||||||
bot.setOnBotMessageCallback([&sentryClient, &verbose](const Json::Value& msg,
|
conn.configure(config);
|
||||||
const std::string& /*position*/,
|
conn.connect();
|
||||||
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)
|
|
||||||
{
|
|
||||||
CoreLogger::warn("Null HTTP Response");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response->statusCode == 200)
|
Json::FastWriter jsonWriter;
|
||||||
{
|
std::atomic<uint64_t> sentCount(0);
|
||||||
sentCount++;
|
std::atomic<uint64_t> receivedCount(0);
|
||||||
}
|
std::atomic<bool> errorSending(false);
|
||||||
else
|
std::atomic<bool> stop(false);
|
||||||
{
|
std::atomic<bool> throttled(false);
|
||||||
CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode));
|
std::atomic<bool> fatalCobraError(false);
|
||||||
CoreLogger::error("Response: " + response->payload);
|
|
||||||
|
|
||||||
// Error 429 Too Many Requests
|
QueueManager queueManager(maxQueueSize);
|
||||||
if (response->statusCode == 429)
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
auto retryAfter = response->headers["Retry-After"];
|
spdlog::warn("Null HTTP Response");
|
||||||
std::stringstream ss;
|
continue;
|
||||||
ss << retryAfter;
|
}
|
||||||
int seconds;
|
|
||||||
ss >> seconds;
|
|
||||||
|
|
||||||
if (!ss.eof() || ss.fail())
|
if (verbose)
|
||||||
|
{
|
||||||
|
for (auto it : response->headers)
|
||||||
{
|
{
|
||||||
seconds = 30;
|
spdlog::info("{}: {}", it.first, it.second);
|
||||||
CoreLogger::warn("Error parsing Retry-After header. "
|
|
||||||
"Using " + retryAfter + " for the sleep duration");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreLogger::warn("Error 429 - Too Many Requests. ws will sleep "
|
spdlog::info("Upload size: {}", response->uploadSize);
|
||||||
"and retry after " + retryAfter + " seconds");
|
spdlog::info("Download size: {}", response->downloadSize);
|
||||||
|
|
||||||
throttled = true;
|
spdlog::info("Status: {}", response->statusCode);
|
||||||
auto duration = std::chrono::seconds(seconds);
|
if (response->errorCode != HttpErrorCode::Ok)
|
||||||
std::this_thread::sleep_for(duration);
|
{
|
||||||
throttled = false;
|
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;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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, verbose, &throttled, &receivedCount, &queueManager](
|
||||||
|
const Json::Value& msg, const std::string& position) {
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
spdlog::info("Subscriber received message {} -> {}", position, jsonWriter.write(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we cannot send to sentry fast enough, drop the message
|
||||||
|
if (throttled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return bot.run(config);
|
// 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;
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -5,14 +5,20 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <ixcobra/IXCobraConfig.h>
|
||||||
#include "IXCobraBotConfig.h"
|
|
||||||
#include <ixsentry/IXSentryClient.h>
|
#include <ixsentry/IXSentryClient.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
int64_t cobra_to_sentry_bot(const CobraBotConfig& config,
|
int cobra_to_sentry_bot(const CobraConfig& config,
|
||||||
SentryClient& sentryClient,
|
const std::string& channel,
|
||||||
bool verbose);
|
const std::string& filter,
|
||||||
|
const std::string& position,
|
||||||
|
SentryClient& sentryClient,
|
||||||
|
bool verbose,
|
||||||
|
bool strict,
|
||||||
|
size_t maxQueueSize,
|
||||||
|
bool enableHeartbeat,
|
||||||
|
int runtime);
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -5,13 +5,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraToStatsdBot.h"
|
#include "IXCobraToStatsdBot.h"
|
||||||
|
#include "IXQueueManager.h"
|
||||||
#include "IXCobraBot.h"
|
|
||||||
#include "IXStatsdClient.h"
|
#include "IXStatsdClient.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
#include <ixcobra/IXCobraConnection.h>
|
#include <ixcobra/IXCobraConnection.h>
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -53,34 +56,90 @@ namespace ix
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t cobra_to_statsd_bot(const ix::CobraBotConfig& config,
|
int cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||||
StatsdClient& statsdClient,
|
const std::string& channel,
|
||||||
const std::string& fields,
|
const std::string& filter,
|
||||||
const std::string& gauge,
|
const std::string& position,
|
||||||
const std::string& timer,
|
StatsdClient& statsdClient,
|
||||||
bool verbose)
|
const std::string& fields,
|
||||||
|
const std::string& gauge,
|
||||||
|
const std::string& timer,
|
||||||
|
bool verbose,
|
||||||
|
size_t maxQueueSize,
|
||||||
|
bool enableHeartbeat,
|
||||||
|
int runtime)
|
||||||
{
|
{
|
||||||
|
ix::CobraConnection conn;
|
||||||
|
conn.configure(config);
|
||||||
|
conn.connect();
|
||||||
|
|
||||||
auto tokens = parseFields(fields);
|
auto tokens = parseFields(fields);
|
||||||
|
|
||||||
CobraBot bot;
|
Json::FastWriter jsonWriter;
|
||||||
bot.setOnBotMessageCallback(
|
std::atomic<uint64_t> sentCount(0);
|
||||||
[&statsdClient, &tokens, &gauge, &timer, &verbose](const Json::Value& msg,
|
std::atomic<uint64_t> receivedCount(0);
|
||||||
const std::string& /*position*/,
|
std::atomic<bool> stop(false);
|
||||||
std::atomic<bool>& /*throttled*/,
|
std::atomic<bool> fatalCobraError(false);
|
||||||
std::atomic<bool>& fatalCobraError,
|
|
||||||
std::atomic<uint64_t>& sentCount) -> void {
|
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;
|
||||||
|
|
||||||
std::string id;
|
std::string id;
|
||||||
size_t idx = 0;
|
|
||||||
for (auto&& attr : tokens)
|
for (auto&& attr : tokens)
|
||||||
{
|
{
|
||||||
|
id += ".";
|
||||||
auto val = extractAttr(attr, msg);
|
auto val = extractAttr(attr, msg);
|
||||||
id += val.asString();
|
id += val.asString();
|
||||||
|
|
||||||
// We add a dot separator unless we are processing the last token
|
|
||||||
if (idx++ != tokens.size() - 1)
|
|
||||||
{
|
|
||||||
id += ".";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gauge.empty() && timer.empty())
|
if (gauge.empty() && timer.empty())
|
||||||
@ -115,14 +174,14 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CoreLogger::error("Gauge " + gauge + " is not a numeric type");
|
spdlog::error("Gauge {} is not a numberic type", gauge);
|
||||||
fatalCobraError = true;
|
fatalCobraError = true;
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
{
|
{
|
||||||
CoreLogger::info(id + " - " + attrName + " -> " + std::to_string(x));
|
spdlog::info("{} - {} -> {}", id, attrName, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gauge.empty())
|
if (!gauge.empty())
|
||||||
@ -135,9 +194,127 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sentCount++;
|
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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return bot.run(config);
|
// 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;
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -5,18 +5,23 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <ixcobra/IXCobraConfig.h>
|
||||||
#include <ixbots/IXStatsdClient.h>
|
#include <ixbots/IXStatsdClient.h>
|
||||||
#include "IXCobraBotConfig.h"
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
int64_t cobra_to_statsd_bot(const ix::CobraBotConfig& config,
|
int cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||||
StatsdClient& statsdClient,
|
const std::string& channel,
|
||||||
const std::string& fields,
|
const std::string& filter,
|
||||||
const std::string& gauge,
|
const std::string& position,
|
||||||
const std::string& timer,
|
StatsdClient& statsdClient,
|
||||||
bool verbose);
|
const std::string& fields,
|
||||||
|
const std::string& gauge,
|
||||||
|
const std::string& timer,
|
||||||
|
bool verbose,
|
||||||
|
size_t maxQueueSize,
|
||||||
|
bool enableHeartbeat,
|
||||||
|
int runtime);
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
66
ixbots/ixbots/IXQueueManager.cpp
Normal file
66
ixbots/ixbots/IXQueueManager.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
ixbots/ixbots/IXQueueManager.h
Normal file
35
ixbots/ixbots/IXQueueManager.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
}
|
@ -40,27 +40,23 @@
|
|||||||
#include "IXStatsdClient.h"
|
#include "IXStatsdClient.h"
|
||||||
|
|
||||||
#include <ixwebsocket/IXNetSystem.h>
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
#include <ixwebsocket/IXSetThreadName.h>
|
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
StatsdClient::StatsdClient(const std::string& host,
|
StatsdClient::StatsdClient(const std::string& host,
|
||||||
int port,
|
int port,
|
||||||
const std::string& prefix,
|
const std::string& prefix)
|
||||||
bool verbose)
|
: _host(host)
|
||||||
: _host(host)
|
, _port(port)
|
||||||
, _port(port)
|
, _prefix(prefix)
|
||||||
, _prefix(prefix)
|
, _stop(false)
|
||||||
, _stop(false)
|
|
||||||
, _verbose(verbose)
|
|
||||||
{
|
{
|
||||||
_thread = std::thread([this] {
|
_thread = std::thread([this]
|
||||||
setThreadName("Statsd");
|
{
|
||||||
|
|
||||||
while (!_stop)
|
while (!_stop)
|
||||||
{
|
{
|
||||||
flushQueue();
|
flushQueue();
|
||||||
@ -122,15 +118,11 @@ namespace ix
|
|||||||
{
|
{
|
||||||
cleanup(key);
|
cleanup(key);
|
||||||
|
|
||||||
std::stringstream ss;
|
char buf[256];
|
||||||
ss << _prefix << "." << key << ":" << value << "|" << type;
|
snprintf(buf, sizeof(buf), "%s%s:%zd|%s\n",
|
||||||
|
_prefix.c_str(), key.c_str(), value, type.c_str());
|
||||||
|
|
||||||
if (_verbose)
|
enqueue(buf);
|
||||||
{
|
|
||||||
CoreLogger::info(ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
enqueue(ss.str() + "\n");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,13 +140,12 @@ namespace ix
|
|||||||
{
|
{
|
||||||
auto message = _queue.front();
|
auto message = _queue.front();
|
||||||
auto ret = _socket.sendto(message);
|
auto ret = _socket.sendto(message);
|
||||||
if (ret == -1)
|
if (ret != 0)
|
||||||
{
|
{
|
||||||
CoreLogger::error(std::string("statsd error: ") + strerror(UdpSocket::getErrno()));
|
std::cerr << "error: "
|
||||||
|
<< strerror(UdpSocket::getErrno())
|
||||||
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we always dequeue regardless of the ability to send the message
|
|
||||||
// so that we keep our queue size under control
|
|
||||||
_queue.pop_front();
|
_queue.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,22 +6,22 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <deque>
|
|
||||||
#include <ixwebsocket/IXUdpSocket.h>
|
#include <ixwebsocket/IXUdpSocket.h>
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class StatsdClient
|
class StatsdClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
StatsdClient(const std::string& host = "127.0.0.1",
|
StatsdClient(const std::string& host="127.0.0.1",
|
||||||
int port = 8125,
|
int port=8125,
|
||||||
const std::string& prefix = "",
|
const std::string& prefix = "");
|
||||||
bool verbose = false);
|
|
||||||
~StatsdClient();
|
~StatsdClient();
|
||||||
|
|
||||||
bool init(std::string& errMsg);
|
bool init(std::string& errMsg);
|
||||||
@ -53,7 +53,6 @@ namespace ix
|
|||||||
std::mutex _mutex; // for the queue
|
std::mutex _mutex; // for the queue
|
||||||
|
|
||||||
std::deque<std::string> _queue;
|
std::deque<std::string> _queue;
|
||||||
bool _verbose;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end namespace ix
|
} // end namespace ix
|
||||||
|
@ -14,7 +14,6 @@ set (IXCOBRA_HEADERS
|
|||||||
ixcobra/IXCobraMetricsThreadedPublisher.h
|
ixcobra/IXCobraMetricsThreadedPublisher.h
|
||||||
ixcobra/IXCobraMetricsPublisher.h
|
ixcobra/IXCobraMetricsPublisher.h
|
||||||
ixcobra/IXCobraConfig.h
|
ixcobra/IXCobraConfig.h
|
||||||
ixcobra/IXCobraEventType.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(ixcobra STATIC
|
add_library(ixcobra STATIC
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||||
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraConnection.h"
|
#include "IXCobraConnection.h"
|
||||||
|
#include <ixcrypto/IXHMac.h>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <stdexcept>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ixcrypto/IXHMac.h>
|
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -26,12 +26,12 @@ namespace ix
|
|||||||
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
|
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
|
||||||
constexpr int CobraConnection::kPingIntervalSecs;
|
constexpr int CobraConnection::kPingIntervalSecs;
|
||||||
|
|
||||||
CobraConnection::CobraConnection()
|
CobraConnection::CobraConnection() :
|
||||||
: _webSocket(new WebSocket())
|
_webSocket(new WebSocket()),
|
||||||
, _publishMode(CobraConnection_PublishMode_Immediate)
|
_publishMode(CobraConnection_PublishMode_Immediate),
|
||||||
, _authenticated(false)
|
_authenticated(false),
|
||||||
, _eventCallback(nullptr)
|
_eventCallback(nullptr),
|
||||||
, _id(1)
|
_id(1)
|
||||||
{
|
{
|
||||||
_pdu["action"] = "rtm/publish";
|
_pdu["action"] = "rtm/publish";
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ namespace ix
|
|||||||
_eventCallback = eventCallback;
|
_eventCallback = eventCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::invokeEventCallback(ix::CobraEventType eventType,
|
void CobraConnection::invokeEventCallback(ix::CobraConnectionEventType eventType,
|
||||||
const std::string& errorMsg,
|
const std::string& errorMsg,
|
||||||
const WebSocketHttpHeaders& headers,
|
const WebSocketHttpHeaders& headers,
|
||||||
const std::string& subscriptionId,
|
const std::string& subscriptionId,
|
||||||
@ -96,8 +96,7 @@ namespace ix
|
|||||||
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
||||||
if (_eventCallback)
|
if (_eventCallback)
|
||||||
{
|
{
|
||||||
_eventCallback(
|
_eventCallback(eventType, errorMsg, headers, subscriptionId, msgId);
|
||||||
std::make_unique<CobraEvent>(eventType, errorMsg, headers, subscriptionId, msgId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,136 +105,137 @@ namespace ix
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << errorMsg << " : received pdu => " << serializedPdu;
|
ss << errorMsg << " : received pdu => " << serializedPdu;
|
||||||
invokeEventCallback(ix::CobraEventType::Error, ss.str());
|
invokeEventCallback(ix::CobraConnection_EventType_Error, ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::disconnect()
|
void CobraConnection::disconnect()
|
||||||
{
|
{
|
||||||
auto subscriptionIds = getSubscriptionsIds();
|
|
||||||
for (auto&& subscriptionId : subscriptionIds)
|
|
||||||
{
|
|
||||||
unsubscribe(subscriptionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
_authenticated = false;
|
_authenticated = false;
|
||||||
_webSocket->stop();
|
_webSocket->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::initWebSocketOnMessageCallback()
|
void CobraConnection::initWebSocketOnMessageCallback()
|
||||||
{
|
{
|
||||||
_webSocket->setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) {
|
_webSocket->setOnMessageCallback(
|
||||||
CobraConnection::invokeTrafficTrackerCallback(msg->wireSize, true);
|
[this](const ix::WebSocketMessagePtr& msg)
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
{
|
||||||
invokeEventCallback(ix::CobraEventType::Open, std::string(), msg->openInfo.headers);
|
CobraConnection::invokeTrafficTrackerCallback(msg->wireSize, true);
|
||||||
sendHandshakeMessage();
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
|
||||||
{
|
|
||||||
_authenticated = false;
|
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Close code " << msg->closeInfo.code;
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
invokeErrorCallback("Invalid json", msg->str);
|
invokeEventCallback(ix::CobraConnection_EventType_Open,
|
||||||
return;
|
std::string(),
|
||||||
|
msg->openInfo.headers);
|
||||||
|
sendHandshakeMessage();
|
||||||
}
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
if (!data.isMember("action"))
|
|
||||||
{
|
{
|
||||||
invokeErrorCallback("Missing action", msg->str);
|
_authenticated = false;
|
||||||
return;
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Close code " << msg->closeInfo.code;
|
||||||
|
ss << " reason " << msg->closeInfo.reason;
|
||||||
|
invokeEventCallback(ix::CobraConnection_EventType_Closed,
|
||||||
|
ss.str());
|
||||||
}
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
auto action = data["action"].asString();
|
|
||||||
|
|
||||||
if (action == "auth/handshake/ok")
|
|
||||||
{
|
{
|
||||||
if (!handleHandshakeResponse(data))
|
Json::Value data;
|
||||||
|
Json::Reader reader;
|
||||||
|
if (!reader.parse(msg->str, data))
|
||||||
{
|
{
|
||||||
invokeErrorCallback("Error extracting nonce from handshake response",
|
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,
|
||||||
msg->str);
|
msg->str);
|
||||||
}
|
}
|
||||||
}
|
else if (action == "auth/authenticate/ok")
|
||||||
else if (action == "auth/handshake/error")
|
|
||||||
{
|
|
||||||
invokeEventCallback(ix::CobraEventType::HandshakeError, msg->str);
|
|
||||||
}
|
|
||||||
else if (action == "auth/authenticate/ok")
|
|
||||||
{
|
|
||||||
_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);
|
_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 (action == "rtm/subscribe/error")
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
invokeEventCallback(ix::CobraEventType::SubscriptionError, msg->str);
|
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 (action == "rtm/unsubscribe/ok")
|
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
if (!handleUnsubscriptionResponse(data))
|
invokeEventCallback(ix::CobraConnection_EventType_Pong);
|
||||||
{
|
|
||||||
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,13 +249,12 @@ namespace ix
|
|||||||
return _publishMode;
|
return _publishMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraConnection::configure(
|
void CobraConnection::configure(const std::string& appkey,
|
||||||
const std::string& appkey,
|
const std::string& endpoint,
|
||||||
const std::string& endpoint,
|
const std::string& rolename,
|
||||||
const std::string& rolename,
|
const std::string& rolesecret,
|
||||||
const std::string& rolesecret,
|
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
const SocketTLSOptions& socketTLSOptions)
|
||||||
const SocketTLSOptions& socketTLSOptions)
|
|
||||||
{
|
{
|
||||||
_roleName = rolename;
|
_roleName = rolename;
|
||||||
_roleSecret = rolesecret;
|
_roleSecret = rolesecret;
|
||||||
@ -397,9 +396,8 @@ namespace ix
|
|||||||
|
|
||||||
if (!subscriptionId.isString()) return false;
|
if (!subscriptionId.isString()) return false;
|
||||||
|
|
||||||
invokeEventCallback(ix::CobraEventType::Subscribed,
|
invokeEventCallback(ix::CobraConnection_EventType_Subscribed,
|
||||||
std::string(),
|
std::string(), WebSocketHttpHeaders(),
|
||||||
WebSocketHttpHeaders(),
|
|
||||||
subscriptionId.asString());
|
subscriptionId.asString());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -416,9 +414,8 @@ namespace ix
|
|||||||
|
|
||||||
if (!subscriptionId.isString()) return false;
|
if (!subscriptionId.isString()) return false;
|
||||||
|
|
||||||
invokeEventCallback(ix::CobraEventType::UnSubscribed,
|
invokeEventCallback(ix::CobraConnection_EventType_UnSubscribed,
|
||||||
std::string(),
|
std::string(), WebSocketHttpHeaders(),
|
||||||
WebSocketHttpHeaders(),
|
|
||||||
subscriptionId.asString());
|
subscriptionId.asString());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -465,11 +462,9 @@ namespace ix
|
|||||||
|
|
||||||
uint64_t msgId = id.asUInt64();
|
uint64_t msgId = id.asUInt64();
|
||||||
|
|
||||||
invokeEventCallback(ix::CobraEventType::Published,
|
invokeEventCallback(ix::CobraConnection_EventType_Published,
|
||||||
std::string(),
|
std::string(), WebSocketHttpHeaders(),
|
||||||
WebSocketHttpHeaders(),
|
std::string(), msgId);
|
||||||
std::string(),
|
|
||||||
msgId);
|
|
||||||
|
|
||||||
invokePublishTrackerCallback(false, true);
|
invokePublishTrackerCallback(false, true);
|
||||||
|
|
||||||
@ -499,7 +494,9 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<CobraConnection::MsgId, std::string> CobraConnection::prePublish(
|
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);
|
std::lock_guard<std::mutex> lock(_prePublishMutex);
|
||||||
|
|
||||||
@ -568,13 +565,11 @@ namespace ix
|
|||||||
void CobraConnection::subscribe(const std::string& channel,
|
void CobraConnection::subscribe(const std::string& channel,
|
||||||
const std::string& filter,
|
const std::string& filter,
|
||||||
const std::string& position,
|
const std::string& position,
|
||||||
int batchSize,
|
|
||||||
SubscriptionCallback cb)
|
SubscriptionCallback cb)
|
||||||
{
|
{
|
||||||
// Create and send a subscribe pdu
|
// Create and send a subscribe pdu
|
||||||
Json::Value body;
|
Json::Value body;
|
||||||
body["channel"] = channel;
|
body["channel"] = channel;
|
||||||
body["batch_size"] = batchSize;
|
|
||||||
|
|
||||||
if (!filter.empty())
|
if (!filter.empty())
|
||||||
{
|
{
|
||||||
@ -620,18 +615,6 @@ namespace ix
|
|||||||
_webSocket->send(pdu.toStyledString());
|
_webSocket->send(pdu.toStyledString());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> CobraConnection::getSubscriptionsIds()
|
|
||||||
{
|
|
||||||
std::vector<std::string> subscriptionIds;
|
|
||||||
std::lock_guard<std::mutex> lock(_cbsMutex);
|
|
||||||
|
|
||||||
for (auto&& it : _cbs)
|
|
||||||
{
|
|
||||||
subscriptionIds.push_back(it.first);
|
|
||||||
}
|
|
||||||
return subscriptionIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Enqueue strategy drops old messages when we are at full capacity
|
// Enqueue strategy drops old messages when we are at full capacity
|
||||||
//
|
//
|
||||||
@ -679,7 +662,8 @@ namespace ix
|
|||||||
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
||||||
{
|
{
|
||||||
auto webSocketSendInfo = _webSocket->send(serializedJson);
|
auto webSocketSendInfo = _webSocket->send(serializedJson);
|
||||||
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
CobraConnection::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize,
|
||||||
|
false);
|
||||||
return webSocketSendInfo.success;
|
return webSocketSendInfo.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,19 +6,18 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXCobraConfig.h"
|
|
||||||
#include "IXCobraEvent.h"
|
|
||||||
#include "IXCobraEventType.h"
|
|
||||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
#include <limits>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "IXCobraConfig.h"
|
||||||
|
|
||||||
#ifdef max
|
#ifdef max
|
||||||
#undef max
|
#undef max
|
||||||
@ -29,6 +28,21 @@ namespace ix
|
|||||||
class WebSocket;
|
class WebSocket;
|
||||||
struct SocketTLSOptions;
|
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
|
enum CobraConnectionPublishMode
|
||||||
{
|
{
|
||||||
CobraConnection_PublishMode_Immediate = 0,
|
CobraConnection_PublishMode_Immediate = 0,
|
||||||
@ -36,7 +50,11 @@ namespace ix
|
|||||||
};
|
};
|
||||||
|
|
||||||
using SubscriptionCallback = std::function<void(const Json::Value&, const std::string&)>;
|
using SubscriptionCallback = std::function<void(const Json::Value&, const std::string&)>;
|
||||||
using EventCallback = std::function<void(const CobraEventPtr&)>;
|
using EventCallback = std::function<void(CobraConnectionEventType,
|
||||||
|
const std::string&,
|
||||||
|
const WebSocketHttpHeaders&,
|
||||||
|
const std::string&,
|
||||||
|
uint64_t msgId)>;
|
||||||
|
|
||||||
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
using TrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
||||||
using PublishTrackerCallback = std::function<void(bool sent, bool acked)>;
|
using PublishTrackerCallback = std::function<void(bool sent, bool acked)>;
|
||||||
@ -88,7 +106,6 @@ namespace ix
|
|||||||
void subscribe(const std::string& channel,
|
void subscribe(const std::string& channel,
|
||||||
const std::string& filter = std::string(),
|
const std::string& filter = std::string(),
|
||||||
const std::string& position = std::string(),
|
const std::string& position = std::string(),
|
||||||
int batchSize = 1,
|
|
||||||
SubscriptionCallback cb = nullptr);
|
SubscriptionCallback cb = nullptr);
|
||||||
|
|
||||||
/// Unsubscribe from a channel
|
/// Unsubscribe from a channel
|
||||||
@ -121,9 +138,10 @@ namespace ix
|
|||||||
|
|
||||||
/// Prepare a message for transmission
|
/// Prepare a message for transmission
|
||||||
/// (update the pdu, compute a msgId, serialize json to a string)
|
/// (update the pdu, compute a msgId, serialize json to a string)
|
||||||
std::pair<CobraConnection::MsgId, std::string> prePublish(const Json::Value& channels,
|
std::pair<CobraConnection::MsgId, std::string> prePublish(
|
||||||
const Json::Value& msg,
|
const Json::Value& channels,
|
||||||
bool addToQueue);
|
const Json::Value& msg,
|
||||||
|
bool addToQueue);
|
||||||
|
|
||||||
/// Attempt to send next message from the internal queue
|
/// Attempt to send next message from the internal queue
|
||||||
bool publishNext();
|
bool publishNext();
|
||||||
@ -153,7 +171,7 @@ namespace ix
|
|||||||
static void invokePublishTrackerCallback(bool sent, bool acked);
|
static void invokePublishTrackerCallback(bool sent, bool acked);
|
||||||
|
|
||||||
/// Invoke event callbacks
|
/// Invoke event callbacks
|
||||||
void invokeEventCallback(CobraEventType eventType,
|
void invokeEventCallback(CobraConnectionEventType eventType,
|
||||||
const std::string& errorMsg = std::string(),
|
const std::string& errorMsg = std::string(),
|
||||||
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
|
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
|
||||||
const std::string& subscriptionId = std::string(),
|
const std::string& subscriptionId = std::string(),
|
||||||
@ -163,9 +181,6 @@ namespace ix
|
|||||||
/// Tells whether the internal queue is empty or not
|
/// Tells whether the internal queue is empty or not
|
||||||
bool isQueueEmpty();
|
bool isQueueEmpty();
|
||||||
|
|
||||||
/// Retrieve all subscriptions ids
|
|
||||||
std::vector<std::string> getSubscriptionsIds();
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Member variables
|
/// Member variables
|
||||||
///
|
///
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 "IXCobraMetricsPublisher.h"
|
||||||
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
@ -17,8 +17,8 @@ namespace ix
|
|||||||
const std::string CobraMetricsPublisher::kSetRateControlId = "sms_set_rate_control_id";
|
const std::string CobraMetricsPublisher::kSetRateControlId = "sms_set_rate_control_id";
|
||||||
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
|
const std::string CobraMetricsPublisher::kSetBlacklistId = "sms_set_blacklist_id";
|
||||||
|
|
||||||
CobraMetricsPublisher::CobraMetricsPublisher()
|
CobraMetricsPublisher::CobraMetricsPublisher() :
|
||||||
: _enabled(true)
|
_enabled(true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +27,8 @@ 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
|
// Configure the satori connection and start its publish background thread
|
||||||
_cobra_metrics_theaded_publisher.configure(config, channel);
|
_cobra_metrics_theaded_publisher.configure(config, channel);
|
||||||
@ -41,7 +42,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CobraMetricsPublisher::setGenericAttributes(const std::string& attrName,
|
void CobraMetricsPublisher::setGenericAttributes(const std::string& attrName,
|
||||||
const Json::Value& value)
|
const Json::Value& value)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_device_mutex);
|
std::lock_guard<std::mutex> lock(_device_mutex);
|
||||||
_device[attrName] = value;
|
_device[attrName] = value;
|
||||||
@ -106,7 +107,8 @@ namespace ix
|
|||||||
auto last_update = _last_update.find(id);
|
auto last_update = _last_update.find(id);
|
||||||
if (last_update == _last_update.end()) return false;
|
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);
|
return timeDeltaFromLastSend < std::chrono::seconds(rate_control_it->second);
|
||||||
}
|
}
|
||||||
@ -121,7 +123,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
auto now = std::chrono::system_clock::now();
|
auto now = std::chrono::system_clock::now();
|
||||||
auto ms =
|
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;
|
return ms;
|
||||||
}
|
}
|
||||||
@ -162,9 +165,10 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id,
|
CobraConnection::MsgId CobraMetricsPublisher::push(
|
||||||
const Json::Value& data,
|
const std::string& id,
|
||||||
bool shouldPushTest)
|
const Json::Value& data,
|
||||||
|
bool shouldPushTest)
|
||||||
{
|
{
|
||||||
if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId;
|
if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId;
|
||||||
|
|
||||||
|
@ -40,7 +40,8 @@ namespace ix
|
|||||||
|
|
||||||
/// Configuration / set keys, etc...
|
/// Configuration / set keys, etc...
|
||||||
/// All input data but the channel name is encrypted with rc4
|
/// 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.
|
/// Setter for the list of blacklisted metrics ids.
|
||||||
/// That list is sorted internally for fast lookups
|
/// That list is sorted internally for fast lookups
|
||||||
@ -67,14 +68,10 @@ namespace ix
|
|||||||
/// shouldPush method for places where we want to be as lightweight as possible when
|
/// 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
|
/// 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.
|
/// computing whether a metrics should be sent or not.
|
||||||
CobraConnection::MsgId push(const std::string& id,
|
CobraConnection::MsgId push(const std::string& id, const Json::Value& data, bool shouldPushTest = true);
|
||||||
const Json::Value& data,
|
|
||||||
bool shouldPushTest = true);
|
|
||||||
|
|
||||||
/// Interface used by lua. msg is a json encoded string.
|
/// Interface used by lua. msg is a json encoded string.
|
||||||
CobraConnection::MsgId push(const std::string& id,
|
CobraConnection::MsgId push(const std::string& id, const std::string& data, bool shouldPushTest = true);
|
||||||
const std::string& data,
|
|
||||||
bool shouldPushTest = true);
|
|
||||||
|
|
||||||
/// Tells whether a metric can be pushed.
|
/// Tells whether a metric can be pushed.
|
||||||
/// A metric can be pushed if it satisfies those conditions:
|
/// A metric can be pushed if it satisfies those conditions:
|
||||||
@ -92,16 +89,10 @@ namespace ix
|
|||||||
void setGenericAttributes(const std::string& attrName, const Json::Value& value);
|
void setGenericAttributes(const std::string& attrName, const Json::Value& value);
|
||||||
|
|
||||||
/// Set a unique id for the session. A uuid can be used.
|
/// Set a unique id for the session. A uuid can be used.
|
||||||
void setSession(const std::string& session)
|
void setSession(const std::string& session) { _session = session; }
|
||||||
{
|
|
||||||
_session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the unique id used to identify the current session
|
/// Get the unique id used to identify the current session
|
||||||
const std::string& getSession() const
|
const std::string& getSession() const { return _session; }
|
||||||
{
|
|
||||||
return _session;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the number of milliseconds since the epoch (~1970)
|
/// Return the number of milliseconds since the epoch (~1970)
|
||||||
uint64_t getMillisecondsSinceEpoch() const;
|
uint64_t getMillisecondsSinceEpoch() const;
|
||||||
|
@ -5,77 +5,72 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXCobraMetricsThreadedPublisher.h"
|
#include "IXCobraMetricsThreadedPublisher.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cmath>
|
|
||||||
#include <iostream>
|
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
#include <ixwebsocket/IXSetThreadName.h>
|
#include <ixwebsocket/IXSetThreadName.h>
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
#include <sstream>
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher()
|
CobraMetricsThreadedPublisher::CobraMetricsThreadedPublisher() :
|
||||||
: _stop(false)
|
_stop(false)
|
||||||
{
|
{
|
||||||
_cobra_connection.setEventCallback([](const CobraEventPtr& event) {
|
_cobra_connection.setEventCallback(
|
||||||
std::stringstream ss;
|
[]
|
||||||
|
(ix::CobraConnectionEventType eventType,
|
||||||
if (event->type == ix::CobraEventType::Open)
|
const std::string& errMsg,
|
||||||
|
const ix::WebSocketHttpHeaders& headers,
|
||||||
|
const std::string& subscriptionId,
|
||||||
|
CobraConnection::MsgId msgId)
|
||||||
{
|
{
|
||||||
ss << "Handshake headers" << std::endl;
|
std::stringstream ss;
|
||||||
|
|
||||||
for (auto&& it : event->headers)
|
if (eventType == ix::CobraConnection_EventType_Open)
|
||||||
{
|
{
|
||||||
ss << it.first << ": " << it.second << std::endl;
|
ss << "Handshake headers" << 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreLogger::log(ss.str().c_str());
|
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());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,10 +95,11 @@ namespace ix
|
|||||||
void CobraMetricsThreadedPublisher::configure(const CobraConfig& config,
|
void CobraMetricsThreadedPublisher::configure(const CobraConfig& config,
|
||||||
const std::string& channel)
|
const std::string& channel)
|
||||||
{
|
{
|
||||||
CoreLogger::log(config.socketTLSOptions.getDescription().c_str());
|
ix::IXCoreLogger::Log(config.socketTLSOptions.getDescription().c_str());
|
||||||
|
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
_cobra_connection.configure(config);
|
_cobra_connection.configure(config);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
|
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
|
||||||
@ -161,15 +157,13 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_cobra_connection.suspend();
|
_cobra_connection.suspend();
|
||||||
continue;
|
continue;
|
||||||
};
|
}; break;
|
||||||
break;
|
|
||||||
|
|
||||||
case MessageKind::Resume:
|
case MessageKind::Resume:
|
||||||
{
|
{
|
||||||
_cobra_connection.resume();
|
_cobra_connection.resume();
|
||||||
continue;
|
continue;
|
||||||
};
|
}; break;
|
||||||
break;
|
|
||||||
|
|
||||||
case MessageKind::Message:
|
case MessageKind::Message:
|
||||||
{
|
{
|
||||||
@ -177,8 +171,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_cobra_connection.publishNext();
|
_cobra_connection.publishNext();
|
||||||
}
|
}
|
||||||
};
|
}; break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ namespace ix
|
|||||||
~CobraMetricsThreadedPublisher();
|
~CobraMetricsThreadedPublisher();
|
||||||
|
|
||||||
/// Configuration / set keys, etc...
|
/// 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
|
/// Start the worker thread, used for background publishing
|
||||||
void start();
|
void start();
|
||||||
|
@ -1,44 +1,14 @@
|
|||||||
/*
|
|
||||||
* IXCoreLogger.cpp
|
|
||||||
* Author: Thomas Wells, Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ixcore/utils/IXCoreLogger.h"
|
#include "ixcore/utils/IXCoreLogger.h"
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
// Default do a no-op logger
|
// Default do nothing logger
|
||||||
CoreLogger::LogFunc CoreLogger::_currentLogger = [](const char*, LogLevel) {};
|
IXCoreLogger::LogFunc IXCoreLogger::_currentLogger = [](const char* /*msg*/){};
|
||||||
|
|
||||||
void CoreLogger::log(const char* msg, LogLevel level)
|
void IXCoreLogger::Log(const char* msg)
|
||||||
{
|
{
|
||||||
_currentLogger(msg, level);
|
_currentLogger(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreLogger::debug(const std::string& msg)
|
} // ix
|
||||||
{
|
|
||||||
_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,41 +1,15 @@
|
|||||||
/*
|
|
||||||
* IXCoreLogger.h
|
|
||||||
* Author: Thomas Wells, Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
enum class LogLevel
|
class IXCoreLogger
|
||||||
{
|
|
||||||
Debug = 0,
|
|
||||||
Info = 1,
|
|
||||||
Warning = 2,
|
|
||||||
Error = 3,
|
|
||||||
Critical = 4
|
|
||||||
};
|
|
||||||
|
|
||||||
class CoreLogger
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using LogFunc = std::function<void(const char*, LogLevel level)>;
|
using LogFunc = std::function<void(const char*)>;
|
||||||
|
static void Log(const char* msg);
|
||||||
|
|
||||||
static void log(const char* msg, LogLevel level = LogLevel::Debug);
|
static void setLogFunction(LogFunc& func) { _currentLogger = func; }
|
||||||
|
|
||||||
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:
|
private:
|
||||||
static LogFunc _currentLogger;
|
static LogFunc _currentLogger;
|
||||||
|
@ -37,7 +37,9 @@ if (USE_MBED_TLS)
|
|||||||
target_include_directories(ixcrypto PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
target_include_directories(ixcrypto PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||||
target_link_libraries(ixcrypto ${MBEDTLS_LIBRARIES})
|
target_link_libraries(ixcrypto ${MBEDTLS_LIBRARIES})
|
||||||
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_MBED_TLS)
|
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_MBED_TLS)
|
||||||
elseif (USE_OPEN_SSL)
|
elseif (APPLE)
|
||||||
|
elseif (WIN32)
|
||||||
|
else()
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
add_definitions(${OPENSSL_DEFINITIONS})
|
add_definitions(${OPENSSL_DEFINITIONS})
|
||||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||||
|
@ -29,9 +29,10 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
static const std::string base64_chars =
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
"0123456789+/";
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"0123456789+/";
|
||||||
|
|
||||||
std::string base64_encode(const std::string& data, size_t len)
|
std::string base64_encode(const std::string& data, size_t len)
|
||||||
{
|
{
|
||||||
@ -49,26 +50,26 @@ namespace ix
|
|||||||
unsigned char char_array_3[3];
|
unsigned char char_array_3[3];
|
||||||
unsigned char char_array_4[4];
|
unsigned char char_array_4[4];
|
||||||
|
|
||||||
while (len--)
|
while(len--)
|
||||||
{
|
{
|
||||||
char_array_3[i++] = *(bytes_to_encode++);
|
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[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[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[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
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]];
|
ret += base64_chars[char_array_4[i]];
|
||||||
|
|
||||||
i = 0;
|
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_3[j] = '\0';
|
||||||
|
|
||||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||||
@ -76,11 +77,12 @@ namespace ix
|
|||||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
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;
|
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]];
|
ret += base64_chars[char_array_4[j]];
|
||||||
|
|
||||||
while ((i++ < 3))
|
while((i++ < 3))
|
||||||
ret += '=';
|
ret += '=';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@ -93,7 +95,7 @@ namespace ix
|
|||||||
|
|
||||||
std::string base64_decode(const std::string& encoded_string)
|
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 i = 0;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
int in_ = 0;
|
int in_ = 0;
|
||||||
@ -101,42 +103,40 @@ namespace ix
|
|||||||
std::string ret;
|
std::string ret;
|
||||||
ret.reserve(((in_len + 3) / 4) * 3);
|
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_];
|
char_array_4[i++] = encoded_string[in_]; in_++;
|
||||||
in_++;
|
if(i ==4)
|
||||||
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_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[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[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];
|
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];
|
ret += char_array_3[i];
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i)
|
if(i)
|
||||||
{
|
{
|
||||||
for (j = i; j < 4; j++)
|
for(j = i; j <4; j++)
|
||||||
char_array_4[j] = 0;
|
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_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[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[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];
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
for (j = 0; (j < i - 1); j++)
|
for(j = 0; (j < i - 1); j++) ret += char_array_3[j];
|
||||||
ret += char_array_3[j];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
} // namespace ix
|
}
|
||||||
|
@ -5,17 +5,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXHMac.h"
|
#include "IXHMac.h"
|
||||||
|
|
||||||
#include "IXBase64.h"
|
#include "IXBase64.h"
|
||||||
|
|
||||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||||
#include <mbedtls/md.h>
|
# include <mbedtls/md.h>
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#include <CommonCrypto/CommonHMAC.h>
|
# include <CommonCrypto/CommonHMAC.h>
|
||||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||||
#include <openssl/hmac.h>
|
# include <openssl/hmac.h>
|
||||||
#else
|
#else
|
||||||
#include <assert.h>
|
# include <assert.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -27,21 +26,19 @@ namespace ix
|
|||||||
|
|
||||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5),
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5),
|
||||||
(unsigned char*) key.c_str(),
|
(unsigned char *) key.c_str(), key.size(),
|
||||||
key.size(),
|
(unsigned char *) data.c_str(), data.size(),
|
||||||
(unsigned char*) data.c_str(),
|
(unsigned char *) &hash);
|
||||||
data.size(),
|
|
||||||
(unsigned char*) &hash);
|
|
||||||
#elif defined(__APPLE__)
|
#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)
|
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||||
HMAC(EVP_md5(),
|
HMAC(EVP_md5(),
|
||||||
key.c_str(),
|
key.c_str(), (int) key.size(),
|
||||||
(int) key.size(),
|
(unsigned char *) data.c_str(), (int) data.size(),
|
||||||
(unsigned char*) data.c_str(),
|
(unsigned char *) hash, nullptr);
|
||||||
(int) data.size(),
|
|
||||||
(unsigned char*) hash,
|
|
||||||
nullptr);
|
|
||||||
#else
|
#else
|
||||||
assert(false && "hmac not implemented on this platform");
|
assert(false && "hmac not implemented on this platform");
|
||||||
#endif
|
#endif
|
||||||
@ -50,4 +47,4 @@ namespace ix
|
|||||||
|
|
||||||
return base64_encode(hashString, (uint32_t) hashString.size());
|
return base64_encode(hashString, (uint32_t) hashString.size());
|
||||||
}
|
}
|
||||||
} // namespace ix
|
}
|
||||||
|
@ -19,4 +19,4 @@ namespace ix
|
|||||||
|
|
||||||
return hashAddress;
|
return hashAddress;
|
||||||
}
|
}
|
||||||
} // namespace ix
|
}
|
||||||
|
@ -16,23 +16,23 @@
|
|||||||
|
|
||||||
#include "IXUuid.h"
|
#include "IXUuid.h"
|
||||||
|
|
||||||
#include <iomanip>
|
|
||||||
#include <random>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class Uuid
|
class Uuid
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Uuid();
|
Uuid();
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint64_t _ab;
|
uint64_t _ab;
|
||||||
uint64_t _cd;
|
uint64_t _cd;
|
||||||
};
|
};
|
||||||
|
|
||||||
Uuid::Uuid()
|
Uuid::Uuid()
|
||||||
@ -60,7 +60,7 @@ namespace ix
|
|||||||
ss << std::setw(8) << (a);
|
ss << std::setw(8) << (a);
|
||||||
ss << std::setw(4) << (b >> 16);
|
ss << std::setw(4) << (b >> 16);
|
||||||
ss << std::setw(4) << (b & 0xFFFF);
|
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(4) << (c & 0xFFFF);
|
||||||
ss << std::setw(8) << d;
|
ss << std::setw(8) << d;
|
||||||
|
|
||||||
@ -72,4 +72,4 @@ namespace ix
|
|||||||
Uuid id;
|
Uuid id;
|
||||||
return id.toString();
|
return id.toString();
|
||||||
}
|
}
|
||||||
} // namespace ix
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
#
|
|
||||||
# Author: Benjamin Sergeant
|
|
||||||
# Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
#
|
|
||||||
|
|
||||||
set (IXREDIS_SOURCES
|
|
||||||
ixredis/IXRedisClient.cpp
|
|
||||||
ixredis/IXRedisServer.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set (IXREDIS_HEADERS
|
|
||||||
ixredis/IXRedisClient.h
|
|
||||||
ixredis/IXRedisServer.h
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(ixredis STATIC
|
|
||||||
${IXREDIS_SOURCES}
|
|
||||||
${IXREDIS_HEADERS}
|
|
||||||
)
|
|
||||||
|
|
||||||
set(IXREDIS_INCLUDE_DIRS
|
|
||||||
.
|
|
||||||
..
|
|
||||||
../ixcore
|
|
||||||
../ixwebsocket)
|
|
||||||
|
|
||||||
target_include_directories( ixredis PUBLIC ${IXREDIS_INCLUDE_DIRS} )
|
|
@ -7,12 +7,12 @@
|
|||||||
#include "IXSentryClient.h"
|
#include "IXSentryClient.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
#include <ixwebsocket/IXWebSocketVersion.h>
|
#include <ixwebsocket/IXWebSocketVersion.h>
|
||||||
#include <sstream>
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -64,8 +64,7 @@ namespace ix
|
|||||||
std::string SentryClient::computeAuthHeader()
|
std::string SentryClient::computeAuthHeader()
|
||||||
{
|
{
|
||||||
std::string securityHeader("Sentry sentry_version=5");
|
std::string securityHeader("Sentry sentry_version=5");
|
||||||
securityHeader += ",sentry_client=ws/";
|
securityHeader += ",sentry_client=ws/1.0.0";
|
||||||
securityHeader += std::string(IX_WEBSOCKET_VERSION);
|
|
||||||
securityHeader += ",sentry_timestamp=" + std::to_string(SentryClient::getTimestamp());
|
securityHeader += ",sentry_timestamp=" + std::to_string(SentryClient::getTimestamp());
|
||||||
securityHeader += ",sentry_key=" + _publicKey;
|
securityHeader += ",sentry_key=" + _publicKey;
|
||||||
securityHeader += ",sentry_secret=" + _secretKey;
|
securityHeader += ",sentry_secret=" + _secretKey;
|
||||||
@ -226,44 +225,44 @@ namespace ix
|
|||||||
return _jsonWriter.write(payload);
|
return _jsonWriter.write(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SentryClient::send(
|
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg, bool verbose)
|
||||||
const Json::Value& msg,
|
|
||||||
bool verbose,
|
|
||||||
const OnResponseCallback& onResponseCallback)
|
|
||||||
{
|
{
|
||||||
auto args = _httpClient->createRequest();
|
auto args = _httpClient->createRequest();
|
||||||
args->url = _url;
|
|
||||||
args->verb = HttpClient::kPost;
|
|
||||||
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||||
args->connectTimeout = 60;
|
args->connectTimeout = 60;
|
||||||
args->transferTimeout = 5 * 60;
|
args->transferTimeout = 5 * 60;
|
||||||
args->followRedirects = true;
|
args->followRedirects = true;
|
||||||
args->verbose = verbose;
|
args->verbose = verbose;
|
||||||
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
||||||
args->body = computePayload(msg);
|
|
||||||
|
|
||||||
_httpClient->performRequest(args, onResponseCallback);
|
std::string body = computePayload(msg);
|
||||||
|
HttpResponsePtr response = _httpClient->post(_url, body, args);
|
||||||
|
|
||||||
|
return std::make_pair(response, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://sentry.io/api/12345/minidump?sentry_key=abcdefgh");
|
// https://sentry.io/api/12345/minidump?sentry_key=abcdefgh");
|
||||||
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
|
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
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();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp
|
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
|
||||||
// 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
|
|
||||||
//
|
//
|
||||||
void SentryClient::uploadMinidump(const std::string& sentryMetadata,
|
void SentryClient::uploadMinidump(
|
||||||
const std::string& minidumpBytes,
|
const std::string& sentryMetadata,
|
||||||
const std::string& project,
|
const std::string& minidumpBytes,
|
||||||
const std::string& key,
|
const std::string& project,
|
||||||
bool verbose,
|
const std::string& key,
|
||||||
const OnResponseCallback& onResponseCallback)
|
bool verbose,
|
||||||
|
const OnResponseCallback& onResponseCallback)
|
||||||
{
|
{
|
||||||
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
|
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
|
||||||
|
|
||||||
@ -274,7 +273,7 @@ namespace ix
|
|||||||
args->followRedirects = true;
|
args->followRedirects = true;
|
||||||
args->verbose = verbose;
|
args->verbose = verbose;
|
||||||
args->multipartBoundary = multipartBoundary;
|
args->multipartBoundary = multipartBoundary;
|
||||||
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
||||||
|
|
||||||
HttpFormDataParameters httpFormDataParameters;
|
HttpFormDataParameters httpFormDataParameters;
|
||||||
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
|
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
|
||||||
@ -283,27 +282,7 @@ namespace ix
|
|||||||
httpParameters["sentry"] = sentryMetadata;
|
httpParameters["sentry"] = sentryMetadata;
|
||||||
|
|
||||||
args->url = computeUrl(project, key);
|
args->url = computeUrl(project, key);
|
||||||
args->body = _httpClient->serializeHttpFormDataParameters(
|
args->body = _httpClient->serializeHttpFormDataParameters(multipartBoundary, httpFormDataParameters, httpParameters);
|
||||||
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);
|
_httpClient->performRequest(args, onResponseCallback);
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
#include <ixwebsocket/IXHttpClient.h>
|
#include <ixwebsocket/IXHttpClient.h>
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
#include <memory>
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -21,26 +21,20 @@ namespace ix
|
|||||||
SentryClient(const std::string& dsn);
|
SentryClient(const std::string& dsn);
|
||||||
~SentryClient() = default;
|
~SentryClient() = default;
|
||||||
|
|
||||||
void send(const Json::Value& msg,
|
std::pair<HttpResponsePtr, std::string> send(const Json::Value& msg, bool verbose);
|
||||||
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);
|
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||||
|
|
||||||
// Mostly for testing
|
// Mostly for testing
|
||||||
void setTLSOptions(const SocketTLSOptions& tlsOptions);
|
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:
|
private:
|
||||||
int64_t getTimestamp();
|
int64_t getTimestamp();
|
||||||
|
@ -7,14 +7,16 @@ set (IXSNAKE_SOURCES
|
|||||||
ixsnake/IXSnakeServer.cpp
|
ixsnake/IXSnakeServer.cpp
|
||||||
ixsnake/IXSnakeProtocol.cpp
|
ixsnake/IXSnakeProtocol.cpp
|
||||||
ixsnake/IXAppConfig.cpp
|
ixsnake/IXAppConfig.cpp
|
||||||
ixsnake/IXStreamSql.cpp
|
ixsnake/IXRedisClient.cpp
|
||||||
|
ixsnake/IXRedisServer.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set (IXSNAKE_HEADERS
|
set (IXSNAKE_HEADERS
|
||||||
ixsnake/IXSnakeServer.h
|
ixsnake/IXSnakeServer.h
|
||||||
ixsnake/IXSnakeProtocol.h
|
ixsnake/IXSnakeProtocol.h
|
||||||
ixsnake/IXAppConfig.h
|
ixsnake/IXAppConfig.h
|
||||||
ixsnake/IXStreamSql.h
|
ixsnake/IXRedisClient.h
|
||||||
|
ixsnake/IXRedisServer.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(ixsnake STATIC
|
add_library(ixsnake STATIC
|
||||||
@ -28,7 +30,6 @@ set(IXSNAKE_INCLUDE_DIRS
|
|||||||
../ixcore
|
../ixcore
|
||||||
../ixcrypto
|
../ixcrypto
|
||||||
../ixwebsocket
|
../ixwebsocket
|
||||||
../ixredis
|
|
||||||
../third_party)
|
../third_party)
|
||||||
|
|
||||||
target_include_directories( ixsnake PUBLIC ${IXSNAKE_INCLUDE_DIRS} )
|
target_include_directories( ixsnake PUBLIC ${IXSNAKE_INCLUDE_DIRS} )
|
||||||
|
@ -26,12 +26,6 @@ namespace snake
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto roles = appConfig.apps[appkey]["roles"];
|
auto roles = appConfig.apps[appkey]["roles"];
|
||||||
if (roles.count(role) == 0)
|
|
||||||
{
|
|
||||||
std::cerr << "Missing role " << role << std::endl;
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto channel = roles[role]["secret"];
|
auto channel = roles[role]["secret"];
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
|
|
||||||
namespace snake
|
namespace snake
|
||||||
{
|
{
|
||||||
@ -33,9 +33,6 @@ namespace snake
|
|||||||
// Misc
|
// Misc
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool disablePong;
|
bool disablePong;
|
||||||
|
|
||||||
// If non empty, every published message gets republished to a given channel
|
|
||||||
std::string republishChannel;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isAppKeyValid(const AppConfig& appConfig, std::string appkey);
|
bool isAppKeyValid(const AppConfig& appConfig, std::string appkey);
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ixwebsocket/IXSocket.h>
|
|
||||||
#include <ixwebsocket/IXSocketFactory.h>
|
#include <ixwebsocket/IXSocketFactory.h>
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@ -29,7 +28,10 @@ namespace ix
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CancellationRequest cancellationRequest = []() -> bool { return false; };
|
CancellationRequest cancellationRequest = []() -> bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
return _socket->connect(hostname, port, errMsg, cancellationRequest);
|
return _socket->connect(hostname, port, errMsg, cancellationRequest);
|
||||||
@ -250,17 +252,14 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string RedisClient::prepareXaddCommand(const std::string& stream,
|
std::string RedisClient::prepareXaddCommand(
|
||||||
const std::string& message,
|
const std::string& stream,
|
||||||
int maxLen)
|
const std::string& message)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "*8\r\n";
|
ss << "*5\r\n";
|
||||||
ss << writeString("XADD");
|
ss << writeString("XADD");
|
||||||
ss << writeString(stream);
|
ss << writeString(stream);
|
||||||
ss << writeString("MAXLEN");
|
|
||||||
ss << writeString("~");
|
|
||||||
ss << writeString(std::to_string(maxLen));
|
|
||||||
ss << writeString("*");
|
ss << writeString("*");
|
||||||
ss << writeString("field");
|
ss << writeString("field");
|
||||||
ss << writeString(message);
|
ss << writeString(message);
|
||||||
@ -270,7 +269,6 @@ namespace ix
|
|||||||
|
|
||||||
std::string RedisClient::xadd(const std::string& stream,
|
std::string RedisClient::xadd(const std::string& stream,
|
||||||
const std::string& message,
|
const std::string& message,
|
||||||
int maxLen,
|
|
||||||
std::string& errMsg)
|
std::string& errMsg)
|
||||||
{
|
{
|
||||||
errMsg.clear();
|
errMsg.clear();
|
||||||
@ -281,7 +279,7 @@ namespace ix
|
|||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string command = prepareXaddCommand(stream, message, maxLen);
|
std::string command = prepareXaddCommand(stream, message);
|
||||||
|
|
||||||
bool sent = _socket->writeBytes(command, nullptr);
|
bool sent = _socket->writeBytes(command, nullptr);
|
||||||
if (!sent)
|
if (!sent)
|
||||||
@ -330,9 +328,7 @@ namespace ix
|
|||||||
return streamId;
|
return streamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisClient::sendCommand(const std::string& commands,
|
bool RedisClient::sendCommand(const std::string& commands, int commandsCount, std::string& errMsg)
|
||||||
int commandsCount,
|
|
||||||
std::string& errMsg)
|
|
||||||
{
|
{
|
||||||
bool sent = _socket->writeBytes(commands, nullptr);
|
bool sent = _socket->writeBytes(commands, nullptr);
|
||||||
if (!sent)
|
if (!sent)
|
||||||
@ -354,104 +350,4 @@ namespace ix
|
|||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<RespType, std::string> RedisClient::send(
|
|
||||||
const std::vector<std::string>& args,
|
|
||||||
std::string& errMsg)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "*";
|
|
||||||
ss << std::to_string(args.size());
|
|
||||||
ss << "\r\n";
|
|
||||||
|
|
||||||
for (auto&& arg : args)
|
|
||||||
{
|
|
||||||
ss << writeString(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
|
||||||
if (!sent)
|
|
||||||
{
|
|
||||||
errMsg = "Cannot write bytes to socket";
|
|
||||||
return std::make_pair(RespType::Error, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return readResponse(errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<RespType, std::string> RedisClient::readResponse(std::string& errMsg)
|
|
||||||
{
|
|
||||||
// Read result
|
|
||||||
auto pollResult = _socket->isReadyToRead(-1);
|
|
||||||
if (pollResult == PollResultType::Error)
|
|
||||||
{
|
|
||||||
errMsg = "Error while polling for result";
|
|
||||||
return std::make_pair(RespType::Error, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// First line is the string length
|
|
||||||
auto lineResult = _socket->readLine(nullptr);
|
|
||||||
auto lineValid = lineResult.first;
|
|
||||||
auto line = lineResult.second;
|
|
||||||
|
|
||||||
if (!lineValid)
|
|
||||||
{
|
|
||||||
errMsg = "Error while polling for result";
|
|
||||||
return std::make_pair(RespType::Error, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string response;
|
|
||||||
|
|
||||||
if (line[0] == '+') // Simple string
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
response = line.substr(1, line.size() - 3);
|
|
||||||
return std::make_pair(RespType::String, response);
|
|
||||||
}
|
|
||||||
else if (line[0] == '-') // Errors
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
response = line.substr(1, line.size() - 3);
|
|
||||||
return std::make_pair(RespType::Error, response);
|
|
||||||
}
|
|
||||||
else if (line[0] == ':') // Integers
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
response = line.substr(1, line.size() - 3);
|
|
||||||
return std::make_pair(RespType::Integer, response);
|
|
||||||
}
|
|
||||||
else if (line[0] == '$') // Bulk strings
|
|
||||||
{
|
|
||||||
int stringSize;
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << line.substr(1, line.size() - 1);
|
|
||||||
ss >> stringSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the result, which is the stream id computed by the redis server
|
|
||||||
lineResult = _socket->readLine(nullptr);
|
|
||||||
lineValid = lineResult.first;
|
|
||||||
line = lineResult.second;
|
|
||||||
|
|
||||||
std::string str = line.substr(0, stringSize);
|
|
||||||
return std::make_pair(RespType::String, str);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errMsg = "Unhandled response type";
|
|
||||||
return std::make_pair(RespType::Unknown, std::string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string RedisClient::getRespTypeDescription(RespType respType)
|
|
||||||
{
|
|
||||||
switch (respType)
|
|
||||||
{
|
|
||||||
case RespType::Integer: return "integer";
|
|
||||||
case RespType::Error: return "error";
|
|
||||||
case RespType::String: return "string";
|
|
||||||
default: return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace ix
|
} // namespace ix
|
@ -10,18 +10,11 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <ixwebsocket/IXSocket.h>
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
enum class RespType : int
|
|
||||||
{
|
|
||||||
String = 0,
|
|
||||||
Error = 1,
|
|
||||||
Integer = 2,
|
|
||||||
Unknown = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
class RedisClient
|
class RedisClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -46,24 +39,18 @@ namespace ix
|
|||||||
const OnRedisSubscribeCallback& callback);
|
const OnRedisSubscribeCallback& callback);
|
||||||
|
|
||||||
// XADD
|
// XADD
|
||||||
std::string xadd(const std::string& channel,
|
std::string xadd(
|
||||||
const std::string& message,
|
const std::string& channel,
|
||||||
int maxLen,
|
const std::string& message,
|
||||||
std::string& errMsg);
|
|
||||||
std::string prepareXaddCommand(const std::string& stream,
|
|
||||||
const std::string& message,
|
|
||||||
int maxLen);
|
|
||||||
std::string readXaddReply(std::string& errMsg);
|
|
||||||
bool sendCommand(
|
|
||||||
const std::string& commands, int commandsCount, std::string& errMsg);
|
|
||||||
|
|
||||||
// Arbitrary commands
|
|
||||||
std::pair<RespType, std::string> send(
|
|
||||||
const std::vector<std::string>& args,
|
|
||||||
std::string& errMsg);
|
std::string& errMsg);
|
||||||
std::pair<RespType, std::string> readResponse(std::string& errMsg);
|
|
||||||
|
|
||||||
std::string getRespTypeDescription(RespType respType);
|
std::string prepareXaddCommand(
|
||||||
|
const std::string& stream,
|
||||||
|
const std::string& message);
|
||||||
|
|
||||||
|
std::string readXaddReply(std::string& errMsg);
|
||||||
|
|
||||||
|
bool sendCommand(const std::string& commands, int commandsCount, std::string& errMsg);
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
@ -6,18 +6,17 @@
|
|||||||
|
|
||||||
#include "IXRedisServer.h"
|
#include "IXRedisServer.h"
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <ixwebsocket/IXCancellationRequest.h>
|
|
||||||
#include <ixwebsocket/IXNetSystem.h>
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
#include <ixwebsocket/IXSocket.h>
|
|
||||||
#include <ixwebsocket/IXSocketConnect.h>
|
#include <ixwebsocket/IXSocketConnect.h>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
#include <ixwebsocket/IXCancellationRequest.h>
|
||||||
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
RedisServer::RedisServer(
|
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
||||||
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
|
||||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||||
, _connectedClientsCount(0)
|
, _connectedClientsCount(0)
|
||||||
, _stopHandlingConnections(false)
|
, _stopHandlingConnections(false)
|
||||||
@ -45,11 +44,8 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RedisServer::handleConnection(std::unique_ptr<Socket> socket,
|
void RedisServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
|
||||||
{
|
{
|
||||||
logInfo("New connection from remote ip " + connectionInfo->remoteIp);
|
|
||||||
|
|
||||||
_connectedClientsCount++;
|
_connectedClientsCount++;
|
||||||
|
|
||||||
while (!_stopHandlingConnections)
|
while (!_stopHandlingConnections)
|
||||||
@ -115,10 +111,11 @@ namespace ix
|
|||||||
it.second.erase(socket.get());
|
it.second.erase(socket.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto&& it : _subscribers)
|
for (auto it : _subscribers)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Subscription id: " << it.first << " #subscribers: " << it.second.size();
|
ss << "Subscription id: " << it.first
|
||||||
|
<< " #subscribers: " << it.second.size();
|
||||||
|
|
||||||
logInfo(ss.str());
|
logInfo(ss.str());
|
||||||
}
|
}
|
||||||
@ -129,7 +126,8 @@ namespace ix
|
|||||||
return _connectedClientsCount;
|
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;
|
return str.compare(0, start.length(), start) == 0;
|
||||||
}
|
}
|
||||||
@ -146,8 +144,9 @@ namespace ix
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::parseRequest(std::unique_ptr<Socket>& socket,
|
bool RedisServer::parseRequest(
|
||||||
std::vector<std::string>& tokens)
|
std::unique_ptr<Socket>& socket,
|
||||||
|
std::vector<std::string>& tokens)
|
||||||
{
|
{
|
||||||
// Parse first line
|
// Parse first line
|
||||||
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
||||||
@ -191,8 +190,9 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::handleCommand(std::unique_ptr<Socket>& socket,
|
bool RedisServer::handleCommand(
|
||||||
const std::vector<std::string>& tokens)
|
std::unique_ptr<Socket>& socket,
|
||||||
|
const std::vector<std::string>& tokens)
|
||||||
{
|
{
|
||||||
if (tokens.size() != 1) return false;
|
if (tokens.size() != 1) return false;
|
||||||
|
|
||||||
@ -207,30 +207,31 @@ namespace ix
|
|||||||
//
|
//
|
||||||
ss << "*6\r\n";
|
ss << "*6\r\n";
|
||||||
ss << writeString("publish"); // 1
|
ss << writeString("publish"); // 1
|
||||||
ss << ":3\r\n"; // 2
|
ss << ":3\r\n"; // 2
|
||||||
ss << "*0\r\n"; // 3
|
ss << "*0\r\n"; // 3
|
||||||
ss << ":1\r\n"; // 4
|
ss << ":1\r\n"; // 4
|
||||||
ss << ":2\r\n"; // 5
|
ss << ":2\r\n"; // 5
|
||||||
ss << ":1\r\n"; // 6
|
ss << ":1\r\n"; // 6
|
||||||
|
|
||||||
//
|
//
|
||||||
// subscribe
|
// subscribe
|
||||||
//
|
//
|
||||||
ss << "*6\r\n";
|
ss << "*6\r\n";
|
||||||
ss << writeString("subscribe"); // 1
|
ss << writeString("subscribe"); // 1
|
||||||
ss << ":2\r\n"; // 2
|
ss << ":2\r\n"; // 2
|
||||||
ss << "*0\r\n"; // 3
|
ss << "*0\r\n"; // 3
|
||||||
ss << ":1\r\n"; // 4
|
ss << ":1\r\n"; // 4
|
||||||
ss << ":1\r\n"; // 5
|
ss << ":1\r\n"; // 5
|
||||||
ss << ":1\r\n"; // 6
|
ss << ":1\r\n"; // 6
|
||||||
|
|
||||||
socket->writeBytes(ss.str(), cb);
|
socket->writeBytes(ss.str(), cb);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::handleSubscribe(std::unique_ptr<Socket>& socket,
|
bool RedisServer::handleSubscribe(
|
||||||
const std::vector<std::string>& tokens)
|
std::unique_ptr<Socket>& socket,
|
||||||
|
const std::vector<std::string>& tokens)
|
||||||
{
|
{
|
||||||
if (tokens.size() != 2) return false;
|
if (tokens.size() != 2) return false;
|
||||||
|
|
||||||
@ -249,8 +250,9 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RedisServer::handlePublish(std::unique_ptr<Socket>& socket,
|
bool RedisServer::handlePublish(
|
||||||
const std::vector<std::string>& tokens)
|
std::unique_ptr<Socket>& socket,
|
||||||
|
const std::vector<std::string>& tokens)
|
||||||
{
|
{
|
||||||
if (tokens.size() != 3) return false;
|
if (tokens.size() != 3) return false;
|
||||||
|
|
||||||
@ -279,7 +281,9 @@ namespace ix
|
|||||||
|
|
||||||
// return the number of clients that received the message.
|
// return the number of clients that received the message.
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << ":" << std::to_string(subscribers.size()) << "\r\n";
|
ss << ":"
|
||||||
|
<< std::to_string(subscribers.size())
|
||||||
|
<< "\r\n";
|
||||||
socket->writeBytes(ss.str(), cb);
|
socket->writeBytes(ss.str(), cb);
|
||||||
|
|
||||||
return true;
|
return true;
|
@ -6,13 +6,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ixwebsocket/IXSocket.h>
|
#include "IXSocketServer.h"
|
||||||
#include <ixwebsocket/IXSocketServer.h>
|
#include "IXSocket.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility> // pair
|
#include <utility> // pair
|
||||||
@ -44,21 +44,24 @@ namespace ix
|
|||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState) final;
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) final;
|
|
||||||
virtual size_t getConnectedClientsCount() final;
|
virtual size_t getConnectedClientsCount() final;
|
||||||
|
|
||||||
bool startsWith(const std::string& str, const std::string& start);
|
bool startsWith(const std::string& str, const std::string& start);
|
||||||
std::string writeString(const std::string& str);
|
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,
|
bool handleSubscribe(std::unique_ptr<Socket>& socket,
|
||||||
const std::vector<std::string>& tokens);
|
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);
|
void cleanupSubscribers(std::unique_ptr<Socket>& socket);
|
||||||
};
|
};
|
@ -6,22 +6,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ixredis/IXRedisClient.h>
|
#include "IXRedisClient.h"
|
||||||
#include <thread>
|
#include <future>
|
||||||
#include <ixwebsocket/IXConnectionState.h>
|
#include <ixwebsocket/IXConnectionState.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "IXStreamSql.h"
|
|
||||||
|
|
||||||
namespace snake
|
namespace snake
|
||||||
{
|
{
|
||||||
class SnakeConnectionState : public ix::ConnectionState
|
class SnakeConnectionState : public ix::ConnectionState
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~SnakeConnectionState()
|
|
||||||
{
|
|
||||||
stopSubScriptionThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getNonce()
|
std::string getNonce()
|
||||||
{
|
{
|
||||||
return _nonce;
|
return _nonce;
|
||||||
@ -36,7 +30,6 @@ namespace snake
|
|||||||
{
|
{
|
||||||
return _appkey;
|
return _appkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAppkey(const std::string& appkey)
|
void setAppkey(const std::string& appkey)
|
||||||
{
|
{
|
||||||
_appkey = appkey;
|
_appkey = appkey;
|
||||||
@ -46,7 +39,6 @@ namespace snake
|
|||||||
{
|
{
|
||||||
return _role;
|
return _role;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRole(const std::string& role)
|
void setRole(const std::string& role)
|
||||||
{
|
{
|
||||||
_role = role;
|
_role = role;
|
||||||
@ -57,24 +49,7 @@ namespace snake
|
|||||||
return _redisClient;
|
return _redisClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopSubScriptionThread()
|
std::future<void> fut;
|
||||||
{
|
|
||||||
if (subscriptionThread.joinable())
|
|
||||||
{
|
|
||||||
subscriptionRedisClient.stop();
|
|
||||||
subscriptionThread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We could make those accessible through methods
|
|
||||||
std::thread subscriptionThread;
|
|
||||||
std::string appChannel;
|
|
||||||
std::string subscriptionId;
|
|
||||||
uint64_t id;
|
|
||||||
std::unique_ptr<StreamSql> streamSql;
|
|
||||||
ix::RedisClient subscriptionRedisClient;
|
|
||||||
ix::RedisClient::OnRedisSubscribeResponseCallback onRedisSubscribeResponseCallback;
|
|
||||||
ix::RedisClient::OnRedisSubscribeCallback onRedisSubscribeCallback;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _nonce;
|
std::string _nonce;
|
||||||
|
@ -10,30 +10,29 @@
|
|||||||
#include "IXSnakeConnectionState.h"
|
#include "IXSnakeConnectionState.h"
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
#include <ixcrypto/IXHMac.h>
|
#include <ixcrypto/IXHMac.h>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace snake
|
namespace snake
|
||||||
{
|
{
|
||||||
void handleError(const std::string& action,
|
void handleError(const std::string& action,
|
||||||
ix::WebSocket& ws,
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
uint64_t pduId,
|
nlohmann::json pdu,
|
||||||
const std::string& errMsg)
|
const std::string& errMsg)
|
||||||
{
|
{
|
||||||
std::string actionError(action);
|
std::string actionError(action);
|
||||||
actionError += "/error";
|
actionError += "/error";
|
||||||
|
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", actionError}, {"id", pduId}, {"body", {{"reason", errMsg}}}};
|
{"action", actionError}, {"id", pdu.value("id", 1)}, {"body", {{"reason", errMsg}}}};
|
||||||
ws.sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleHandshake(std::shared_ptr<SnakeConnectionState> state,
|
void handleHandshake(std::shared_ptr<SnakeConnectionState> state,
|
||||||
ix::WebSocket& ws,
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
const nlohmann::json& pdu,
|
const nlohmann::json& pdu)
|
||||||
uint64_t pduId)
|
|
||||||
{
|
{
|
||||||
std::string role = pdu["body"]["data"]["role"];
|
std::string role = pdu["body"]["data"]["role"];
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ namespace snake
|
|||||||
|
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "auth/handshake/ok"},
|
{"action", "auth/handshake/ok"},
|
||||||
{"id", pduId},
|
{"id", pdu.value("id", 1)},
|
||||||
{"body",
|
{"body",
|
||||||
{
|
{
|
||||||
{"data", {{"nonce", state->getNonce()}, {"connection_id", state->getId()}}},
|
{"data", {{"nonce", state->getNonce()}, {"connection_id", state->getId()}}},
|
||||||
@ -50,14 +49,13 @@ namespace snake
|
|||||||
|
|
||||||
auto serializedResponse = response.dump();
|
auto serializedResponse = response.dump();
|
||||||
|
|
||||||
ws.sendText(serializedResponse);
|
ws->sendText(serializedResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleAuth(std::shared_ptr<SnakeConnectionState> state,
|
void handleAuth(std::shared_ptr<SnakeConnectionState> state,
|
||||||
ix::WebSocket& ws,
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const nlohmann::json& pdu,
|
const nlohmann::json& pdu)
|
||||||
uint64_t pduId)
|
|
||||||
{
|
{
|
||||||
auto secret = getRoleSecret(appConfig, state->appkey(), state->role());
|
auto secret = getRoleSecret(appConfig, state->appkey(), state->role());
|
||||||
|
|
||||||
@ -65,9 +63,9 @@ namespace snake
|
|||||||
{
|
{
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "auth/authenticate/error"},
|
{"action", "auth/authenticate/error"},
|
||||||
{"id", pduId},
|
{"id", pdu.value("id", 1)},
|
||||||
{"body", {{"error", "authentication_failed"}, {"reason", "invalid secret"}}}};
|
{"body", {{"error", "authentication_failed"}, {"reason", "invalid secret"}}}};
|
||||||
ws.sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,21 +79,19 @@ namespace snake
|
|||||||
{"action", "auth/authenticate/error"},
|
{"action", "auth/authenticate/error"},
|
||||||
{"id", pdu.value("id", 1)},
|
{"id", pdu.value("id", 1)},
|
||||||
{"body", {{"error", "authentication_failed"}, {"reason", "invalid hash"}}}};
|
{"body", {{"error", "authentication_failed"}, {"reason", "invalid hash"}}}};
|
||||||
ws.sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "auth/authenticate/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
{"action", "auth/authenticate/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
||||||
|
|
||||||
ws.sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlePublish(std::shared_ptr<SnakeConnectionState> state,
|
void handlePublish(std::shared_ptr<SnakeConnectionState> state,
|
||||||
ix::WebSocket& ws,
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
const AppConfig& appConfig,
|
const nlohmann::json& pdu)
|
||||||
const nlohmann::json& pdu,
|
|
||||||
uint64_t pduId)
|
|
||||||
{
|
{
|
||||||
std::vector<std::string> channels;
|
std::vector<std::string> channels;
|
||||||
|
|
||||||
@ -115,16 +111,10 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Missing channels or channel field in publish data";
|
ss << "Missing channels or channel field in publish data";
|
||||||
handleError("rtm/publish", ws, pduId, ss.str());
|
handleError("rtm/publish", ws, pdu, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add an extra channel if the config has one specified
|
|
||||||
if (!appConfig.republishChannel.empty())
|
|
||||||
{
|
|
||||||
channels.push_back(appConfig.republishChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto&& channel : channels)
|
for (auto&& channel : channels)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@ -135,7 +125,7 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot publish to redis host " << errMsg;
|
ss << "Cannot publish to redis host " << errMsg;
|
||||||
handleError("rtm/publish", ws, pduId, ss.str());
|
handleError("rtm/publish", ws, pdu, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,27 +133,26 @@ namespace snake
|
|||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "rtm/publish/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
{"action", "rtm/publish/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
||||||
|
|
||||||
ws.sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// FIXME: this is not cancellable. We should be able to cancel the redis subscription
|
// FIXME: this is not cancellable. We should be able to cancel the redis subscription
|
||||||
//
|
//
|
||||||
void handleSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
void handleRedisSubscription(std::shared_ptr<SnakeConnectionState> state,
|
||||||
ix::WebSocket& ws,
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const nlohmann::json& pdu,
|
const nlohmann::json& pdu)
|
||||||
uint64_t pduId)
|
|
||||||
{
|
{
|
||||||
std::string channel = pdu["body"]["channel"];
|
std::string channel = pdu["body"]["channel"];
|
||||||
state->subscriptionId = channel;
|
std::string subscriptionId = channel;
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << state->appkey() << "::" << channel;
|
ss << state->appkey() << "::" << channel;
|
||||||
|
|
||||||
state->appChannel = ss.str();
|
std::string appChannel(ss.str());
|
||||||
|
|
||||||
ix::RedisClient& redisClient = state->subscriptionRedisClient;
|
ix::RedisClient redisClient;
|
||||||
int port = appConfig.redisPort;
|
int port = appConfig.redisPort;
|
||||||
|
|
||||||
auto urls = appConfig.redisHosts;
|
auto urls = appConfig.redisHosts;
|
||||||
@ -174,7 +163,7 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot connect to redis host " << hostname << ":" << port;
|
ss << "Cannot connect to redis host " << hostname << ":" << port;
|
||||||
handleError("rtm/subscribe", ws, pduId, ss.str());
|
handleError("rtm/subscribe", ws, pdu, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,130 +175,104 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot authenticated to redis";
|
ss << "Cannot authenticated to redis";
|
||||||
handleError("rtm/subscribe", ws, pduId, ss.str());
|
handleError("rtm/subscribe", ws, pdu, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string filterStr;
|
int id = 0;
|
||||||
if (pdu["body"].find("filter") != pdu["body"].end())
|
auto callback = [ws, &id, &subscriptionId](const std::string& messageStr) {
|
||||||
{
|
|
||||||
std::string filterStr = pdu["body"]["filter"];
|
|
||||||
}
|
|
||||||
state->streamSql = std::make_unique<StreamSql>(filterStr);
|
|
||||||
state->id = 0;
|
|
||||||
state->onRedisSubscribeCallback = [&ws, state](const std::string& messageStr) {
|
|
||||||
auto msg = nlohmann::json::parse(messageStr);
|
auto msg = nlohmann::json::parse(messageStr);
|
||||||
|
|
||||||
msg = msg["body"]["message"];
|
msg = msg["body"]["message"];
|
||||||
|
|
||||||
if (state->streamSql->valid() && !state->streamSql->match(msg))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "rtm/subscription/data"},
|
{"action", "rtm/subscription/data"},
|
||||||
{"id", state->id++},
|
{"id", id++},
|
||||||
{"body",
|
{"body", {{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||||
{{"subscription_id", state->subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
|
||||||
|
|
||||||
ws.sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
};
|
};
|
||||||
|
|
||||||
state->onRedisSubscribeResponseCallback = [&ws, state, pduId](const std::string& redisResponse) {
|
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Redis Response: " << redisResponse << "...";
|
ss << "Redis Response: " << redisResponse << "...";
|
||||||
ix::CoreLogger::log(ss.str().c_str());
|
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
||||||
{"id", pduId},
|
{"id", pdu.value("id", 1)},
|
||||||
{"body", {{"subscription_id", state->subscriptionId}}}};
|
{"body", {{"subscription_id", subscriptionId}}}};
|
||||||
ws.sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Subscribing to " << state->appChannel << "...";
|
ss << "Subscribing to " << appChannel << "...";
|
||||||
ix::CoreLogger::log(ss.str().c_str());
|
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto subscription = [&redisClient, state, &ws, pduId]
|
if (!redisClient.subscribe(appChannel, responseCallback, callback))
|
||||||
{
|
{
|
||||||
if (!redisClient.subscribe(state->appChannel,
|
std::stringstream ss;
|
||||||
state->onRedisSubscribeResponseCallback,
|
ss << "Error subscribing to channel " << appChannel;
|
||||||
state->onRedisSubscribeCallback))
|
handleError("rtm/subscribe", ws, pdu, ss.str());
|
||||||
{
|
return;
|
||||||
std::stringstream ss;
|
}
|
||||||
ss << "Error subscribing to channel " << state->appChannel;
|
}
|
||||||
handleError("rtm/subscribe", ws, pduId, ss.str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
state->subscriptionThread = std::thread(subscription);
|
void handleSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||||
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
|
const AppConfig& appConfig,
|
||||||
|
const nlohmann::json& pdu)
|
||||||
|
{
|
||||||
|
state->fut =
|
||||||
|
std::async(std::launch::async, handleRedisSubscription, state, ws, appConfig, pdu);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleUnSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
void handleUnSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||||
ix::WebSocket& ws,
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
const nlohmann::json& pdu,
|
const nlohmann::json& pdu)
|
||||||
uint64_t pduId)
|
|
||||||
{
|
{
|
||||||
// extract subscription_id
|
// extract subscription_id
|
||||||
auto body = pdu["body"];
|
auto body = pdu["body"];
|
||||||
auto subscriptionId = body["subscription_id"];
|
auto subscriptionId = body["subscription_id"];
|
||||||
|
|
||||||
state->stopSubScriptionThread();
|
state->redisClient().stop();
|
||||||
|
|
||||||
nlohmann::json response = {{"action", "rtm/unsubscribe/ok"},
|
nlohmann::json response = {{"action", "rtm/unsubscribe/ok"},
|
||||||
{"id", pduId},
|
{"id", pdu.value("id", 1)},
|
||||||
{"body", {{"subscription_id", subscriptionId}}}};
|
{"body", {{"subscription_id", subscriptionId}}}};
|
||||||
ws.sendText(response.dump());
|
ws->sendText(response.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
||||||
ix::WebSocket& ws,
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const std::string& str)
|
const std::string& str)
|
||||||
{
|
{
|
||||||
nlohmann::json pdu;
|
auto pdu = nlohmann::json::parse(str);
|
||||||
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"];
|
auto action = pdu["action"];
|
||||||
uint64_t pduId = pdu.value("id", 1);
|
|
||||||
|
|
||||||
if (action == "auth/handshake")
|
if (action == "auth/handshake")
|
||||||
{
|
{
|
||||||
handleHandshake(state, ws, pdu, pduId);
|
handleHandshake(state, ws, pdu);
|
||||||
}
|
}
|
||||||
else if (action == "auth/authenticate")
|
else if (action == "auth/authenticate")
|
||||||
{
|
{
|
||||||
handleAuth(state, ws, appConfig, pdu, pduId);
|
handleAuth(state, ws, appConfig, pdu);
|
||||||
}
|
}
|
||||||
else if (action == "rtm/publish")
|
else if (action == "rtm/publish")
|
||||||
{
|
{
|
||||||
handlePublish(state, ws, appConfig, pdu, pduId);
|
handlePublish(state, ws, pdu);
|
||||||
}
|
}
|
||||||
else if (action == "rtm/subscribe")
|
else if (action == "rtm/subscribe")
|
||||||
{
|
{
|
||||||
handleSubscribe(state, ws, appConfig, pdu, pduId);
|
handleSubscribe(state, ws, appConfig, pdu);
|
||||||
}
|
}
|
||||||
else if (action == "rtm/unsubscribe")
|
else if (action == "rtm/unsubscribe")
|
||||||
{
|
{
|
||||||
handleUnSubscribe(state, ws, pdu, pduId);
|
handleUnSubscribe(state, ws, pdu);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -20,7 +20,7 @@ namespace snake
|
|||||||
struct AppConfig;
|
struct AppConfig;
|
||||||
|
|
||||||
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
||||||
ix::WebSocket& ws,
|
std::shared_ptr<ix::WebSocket> ws,
|
||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const std::string& str);
|
const std::string& str);
|
||||||
} // namespace snake
|
} // namespace snake
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
#include "IXSnakeConnectionState.h"
|
#include "IXSnakeConnectionState.h"
|
||||||
#include "IXSnakeProtocol.h"
|
#include "IXSnakeProtocol.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ixcore/utils/IXCoreLogger.h>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
|
|
||||||
|
|
||||||
namespace snake
|
namespace snake
|
||||||
@ -29,7 +29,7 @@ namespace snake
|
|||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Listening on " << appConfig.hostname << ":" << appConfig.port;
|
ss << "Listening on " << appConfig.hostname << ":" << appConfig.port;
|
||||||
ix::CoreLogger::log(ss.str().c_str());
|
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -59,68 +59,62 @@ namespace snake
|
|||||||
};
|
};
|
||||||
_server.setConnectionStateFactory(factory);
|
_server.setConnectionStateFactory(factory);
|
||||||
|
|
||||||
_server.setOnClientMessageCallback(
|
_server.setOnConnectionCallback(
|
||||||
[this](std::shared_ptr<ix::ConnectionState> connectionState,
|
[this](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
ix::ConnectionInfo& connectionInfo,
|
std::shared_ptr<ix::ConnectionState> connectionState) {
|
||||||
ix::WebSocket& webSocket,
|
|
||||||
const ix::WebSocketMessagePtr& msg) {
|
|
||||||
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
||||||
auto remoteIp = connectionInfo.remoteIp;
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "[" << state->getId() << "] ";
|
|
||||||
|
|
||||||
ix::LogLevel logLevel = ix::LogLevel::Debug;
|
webSocket->setOnMessageCallback(
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
[this, webSocket, state](const ix::WebSocketMessagePtr& msg) {
|
||||||
{
|
std::stringstream ss;
|
||||||
ss << "New connection" << std::endl;
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
ss << "remote ip: " << remoteIp << std::endl;
|
{
|
||||||
ss << "id: " << state->getId() << std::endl;
|
ss << "New connection" << std::endl;
|
||||||
ss << "Uri: " << msg->openInfo.uri << std::endl;
|
ss << "id: " << state->getId() << std::endl;
|
||||||
ss << "Headers:" << std::endl;
|
ss << "Uri: " << msg->openInfo.uri << std::endl;
|
||||||
for (auto it : msg->openInfo.headers)
|
ss << "Headers:" << std::endl;
|
||||||
{
|
for (auto it : msg->openInfo.headers)
|
||||||
ss << it.first << ": " << it.second << std::endl;
|
{
|
||||||
}
|
ss << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
std::string appkey = parseAppKey(msg->openInfo.uri);
|
std::string appkey = parseAppKey(msg->openInfo.uri);
|
||||||
state->setAppkey(appkey);
|
state->setAppkey(appkey);
|
||||||
|
|
||||||
// Connect to redis first
|
// Connect to redis first
|
||||||
if (!state->redisClient().connect(_appConfig.redisHosts[0],
|
if (!state->redisClient().connect(_appConfig.redisHosts[0],
|
||||||
_appConfig.redisPort))
|
_appConfig.redisPort))
|
||||||
{
|
{
|
||||||
ss << "Cannot connect to redis host" << std::endl;
|
ss << "Cannot connect to redis host" << std::endl;
|
||||||
logLevel = ix::LogLevel::Error;
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
{
|
||||||
{
|
ss << "Closed connection"
|
||||||
ss << "Closed connection"
|
<< " code " << msg->closeInfo.code << " reason "
|
||||||
<< " code " << msg->closeInfo.code << " reason "
|
<< msg->closeInfo.reason << std::endl;
|
||||||
<< msg->closeInfo.reason << std::endl;
|
}
|
||||||
}
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
{
|
||||||
{
|
std::stringstream ss;
|
||||||
std::stringstream ss;
|
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
||||||
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
}
|
||||||
logLevel = ix::LogLevel::Error;
|
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
||||||
}
|
{
|
||||||
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
ss << "Received message fragment" << std::endl;
|
||||||
{
|
}
|
||||||
ss << "Received message fragment" << std::endl;
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
}
|
{
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
ss << "Received " << msg->wireSize << " bytes" << std::endl;
|
||||||
{
|
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
||||||
ss << "Received " << msg->wireSize << " bytes" << " " << msg->str << std::endl;
|
}
|
||||||
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
|
||||||
}
|
|
||||||
|
|
||||||
ix::CoreLogger::log(ss.str().c_str(), logLevel);
|
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
auto res = _server.listen();
|
auto res = _server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXStreamSql.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Super simple hacked up version of a stream sql expression,
|
|
||||||
* that only supports non nested field evaluation
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXStreamSql.h"
|
|
||||||
#include <sstream>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace snake
|
|
||||||
{
|
|
||||||
StreamSql::StreamSql(const std::string& sqlFilter)
|
|
||||||
: _valid(false)
|
|
||||||
{
|
|
||||||
std::string token;
|
|
||||||
std::stringstream tokenStream(sqlFilter);
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
|
|
||||||
// Split by ' '
|
|
||||||
while (std::getline(tokenStream, token, ' '))
|
|
||||||
{
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
_valid = tokens.size() == 8;
|
|
||||||
if (!_valid) return;
|
|
||||||
|
|
||||||
_field = tokens[5];
|
|
||||||
_operator = tokens[6];
|
|
||||||
_value = tokens[7];
|
|
||||||
|
|
||||||
// remove single quotes
|
|
||||||
_value = _value.substr(1, _value.size() - 2);
|
|
||||||
|
|
||||||
if (_operator == "LIKE")
|
|
||||||
{
|
|
||||||
_value = _value.substr(1, _value.size() - 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StreamSql::valid() const
|
|
||||||
{
|
|
||||||
return _valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StreamSql::match(const nlohmann::json& msg)
|
|
||||||
{
|
|
||||||
if (!_valid) return false;
|
|
||||||
|
|
||||||
if (msg.find(_field) == msg.end())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string value = msg[_field];
|
|
||||||
return value == _value;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace snake
|
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXStreamSql.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include "nlohmann/json.hpp"
|
|
||||||
|
|
||||||
namespace snake
|
|
||||||
{
|
|
||||||
class StreamSql
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
StreamSql(const std::string& sqlFilter = std::string());
|
|
||||||
~StreamSql() = default;
|
|
||||||
|
|
||||||
bool match(const nlohmann::json& msg);
|
|
||||||
bool valid() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string _field;
|
|
||||||
std::string _operator;
|
|
||||||
std::string _value;
|
|
||||||
bool _valid;
|
|
||||||
};
|
|
||||||
}
|
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -14,8 +13,6 @@ namespace ix
|
|||||||
CancellationRequest makeCancellationRequestWithTimeout(
|
CancellationRequest makeCancellationRequestWithTimeout(
|
||||||
int secs, std::atomic<bool>& requestInitCancellation)
|
int secs, std::atomic<bool>& requestInitCancellation)
|
||||||
{
|
{
|
||||||
assert(secs > 0);
|
|
||||||
|
|
||||||
auto start = std::chrono::system_clock::now();
|
auto start = std::chrono::system_clock::now();
|
||||||
auto timeout = std::chrono::seconds(secs);
|
auto timeout = std::chrono::seconds(secs);
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXConnectionInfo.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
struct ConnectionInfo
|
|
||||||
{
|
|
||||||
std::string remoteIp;
|
|
||||||
int remotePort;
|
|
||||||
|
|
||||||
ConnectionInfo(const std::string& r = std::string(), int p = 0)
|
|
||||||
: remoteIp(r)
|
|
||||||
, remotePort(p)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
@ -4,19 +4,6 @@
|
|||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
* 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 "IXDNSLookup.h"
|
||||||
|
|
||||||
#include "IXNetSystem.h"
|
#include "IXNetSystem.h"
|
||||||
|
@ -78,12 +78,12 @@ namespace ix
|
|||||||
WebSocketHttpHeaders extraHeaders;
|
WebSocketHttpHeaders extraHeaders;
|
||||||
std::string body;
|
std::string body;
|
||||||
std::string multipartBoundary;
|
std::string multipartBoundary;
|
||||||
int connectTimeout = 60;
|
int connectTimeout;
|
||||||
int transferTimeout = 1800;
|
int transferTimeout;
|
||||||
bool followRedirects = true;
|
bool followRedirects;
|
||||||
int maxRedirects = 5;
|
int maxRedirects;
|
||||||
bool verbose = false;
|
bool verbose;
|
||||||
bool compress = true;
|
bool compress;
|
||||||
Logger logger;
|
Logger logger;
|
||||||
OnProgressCallback onProgressCallback;
|
OnProgressCallback onProgressCallback;
|
||||||
};
|
};
|
||||||
|
@ -16,10 +16,7 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -28,12 +25,10 @@ namespace ix
|
|||||||
const std::string HttpClient::kHead = "HEAD";
|
const std::string HttpClient::kHead = "HEAD";
|
||||||
const std::string HttpClient::kDel = "DEL";
|
const std::string HttpClient::kDel = "DEL";
|
||||||
const std::string HttpClient::kPut = "PUT";
|
const std::string HttpClient::kPut = "PUT";
|
||||||
const std::string HttpClient::kPatch = "PATCH";
|
|
||||||
|
|
||||||
HttpClient::HttpClient(bool async)
|
HttpClient::HttpClient(bool async)
|
||||||
: _async(async)
|
: _async(async)
|
||||||
, _stop(false)
|
, _stop(false)
|
||||||
, _forceBody(false)
|
|
||||||
{
|
{
|
||||||
if (!_async) return;
|
if (!_async) return;
|
||||||
|
|
||||||
@ -54,11 +49,6 @@ namespace ix
|
|||||||
_tlsOptions = tlsOptions;
|
_tlsOptions = tlsOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClient::setForceBody(bool value)
|
|
||||||
{
|
|
||||||
_forceBody = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
|
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
|
||||||
{
|
{
|
||||||
auto request = std::make_shared<HttpRequestArgs>();
|
auto request = std::make_shared<HttpRequestArgs>();
|
||||||
@ -130,7 +120,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
// We only have one socket connection, so we cannot
|
// We only have one socket connection, so we cannot
|
||||||
// make multiple requests concurrently.
|
// make multiple requests concurrently.
|
||||||
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
uint64_t uploadSize = 0;
|
uint64_t uploadSize = 0;
|
||||||
uint64_t downloadSize = 0;
|
uint64_t downloadSize = 0;
|
||||||
@ -177,13 +167,11 @@ namespace ix
|
|||||||
ss << verb << " " << path << " HTTP/1.1\r\n";
|
ss << verb << " " << path << " HTTP/1.1\r\n";
|
||||||
ss << "Host: " << host << "\r\n";
|
ss << "Host: " << host << "\r\n";
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
if (args->compress)
|
if (args->compress)
|
||||||
{
|
{
|
||||||
ss << "Accept-Encoding: gzip"
|
ss << "Accept-Encoding: gzip"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// Append extra headers
|
// Append extra headers
|
||||||
for (auto&& it : args->extraHeaders)
|
for (auto&& it : args->extraHeaders)
|
||||||
@ -204,7 +192,7 @@ namespace ix
|
|||||||
ss << "User-Agent: " << userAgent() << "\r\n";
|
ss << "User-Agent: " << userAgent() << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
|
if (verb == kPost || verb == kPut)
|
||||||
{
|
{
|
||||||
ss << "Content-Length: " << body.size() << "\r\n";
|
ss << "Content-Length: " << body.size() << "\r\n";
|
||||||
|
|
||||||
@ -232,10 +220,11 @@ namespace ix
|
|||||||
|
|
||||||
std::string req(ss.str());
|
std::string req(ss.str());
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
|
std::atomic<bool> requestInitCancellation(false);
|
||||||
|
|
||||||
// Make a cancellation object dealing with connection timeout
|
// Make a cancellation object dealing with connection timeout
|
||||||
auto isCancellationRequested =
|
auto isCancellationRequested =
|
||||||
makeCancellationRequestWithTimeout(args->connectTimeout, _stop);
|
makeCancellationRequestWithTimeout(args->connectTimeout, requestInitCancellation);
|
||||||
|
|
||||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
||||||
if (!success)
|
if (!success)
|
||||||
@ -253,7 +242,8 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make a new cancellation object dealing with transfer timeout
|
// Make a new cancellation object dealing with transfer timeout
|
||||||
isCancellationRequested = makeCancellationRequestWithTimeout(args->transferTimeout, _stop);
|
isCancellationRequested =
|
||||||
|
makeCancellationRequestWithTimeout(args->transferTimeout, requestInitCancellation);
|
||||||
|
|
||||||
if (args->verbose)
|
if (args->verbose)
|
||||||
{
|
{
|
||||||
@ -500,7 +490,6 @@ namespace ix
|
|||||||
|
|
||||||
downloadSize = payload.size();
|
downloadSize = payload.size();
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
// If the content was compressed with gzip, decode it
|
// If the content was compressed with gzip, decode it
|
||||||
if (headers["Content-Encoding"] == "gzip")
|
if (headers["Content-Encoding"] == "gzip")
|
||||||
{
|
{
|
||||||
@ -519,7 +508,6 @@ namespace ix
|
|||||||
}
|
}
|
||||||
payload = decompressedPayload;
|
payload = decompressedPayload;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
@ -574,20 +562,6 @@ namespace ix
|
|||||||
return request(url, kPut, body, args);
|
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::string HttpClient::urlEncode(const std::string& value)
|
||||||
{
|
{
|
||||||
std::ostringstream escaped;
|
std::ostringstream escaped;
|
||||||
@ -679,7 +653,6 @@ namespace ix
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
|
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
|
||||||
{
|
{
|
||||||
z_stream inflateState;
|
z_stream inflateState;
|
||||||
@ -724,7 +697,6 @@ namespace ix
|
|||||||
inflateEnd(&inflateState);
|
inflateEnd(&inflateState);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
|
@ -46,19 +46,12 @@ namespace ix
|
|||||||
const std::string& body,
|
const std::string& body,
|
||||||
HttpRequestArgsPtr args);
|
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,
|
HttpResponsePtr request(const std::string& url,
|
||||||
const std::string& verb,
|
const std::string& verb,
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
HttpRequestArgsPtr args,
|
HttpRequestArgsPtr args,
|
||||||
int redirects = 0);
|
int redirects = 0);
|
||||||
void setForceBody(bool value);
|
|
||||||
// Async API
|
// Async API
|
||||||
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
|
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
|
||||||
const std::string& verb = HttpClient::kGet);
|
const std::string& verb = HttpClient::kGet);
|
||||||
@ -85,17 +78,15 @@ namespace ix
|
|||||||
const static std::string kHead;
|
const static std::string kHead;
|
||||||
const static std::string kDel;
|
const static std::string kDel;
|
||||||
const static std::string kPut;
|
const static std::string kPut;
|
||||||
const static std::string kPatch;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void log(const std::string& msg, HttpRequestArgsPtr args);
|
void log(const std::string& msg, HttpRequestArgsPtr args);
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
bool gzipInflate(const std::string& in, std::string& out);
|
bool gzipInflate(const std::string& in, std::string& out);
|
||||||
#endif
|
|
||||||
|
|
||||||
// Async API background thread runner
|
// Async API background thread runner
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
// Async API
|
// Async API
|
||||||
bool _async;
|
bool _async;
|
||||||
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
|
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
|
||||||
@ -105,12 +96,8 @@ namespace ix
|
|||||||
std::thread _thread;
|
std::thread _thread;
|
||||||
|
|
||||||
std::unique_ptr<Socket> _socket;
|
std::unique_ptr<Socket> _socket;
|
||||||
std::recursive_mutex _mutex; // to protect accessing the _socket (only one socket per
|
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
|
||||||
// client) the mutex needs to be recursive as this function
|
|
||||||
// might be called recursively to follow HTTP redirections
|
|
||||||
|
|
||||||
SocketTLSOptions _tlsOptions;
|
SocketTLSOptions _tlsOptions;
|
||||||
|
|
||||||
bool _forceBody;
|
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -9,15 +9,10 @@
|
|||||||
#include "IXNetSystem.h"
|
#include "IXNetSystem.h"
|
||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
#include "IXUserAgent.h"
|
#include "IXUserAgent.h"
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
#include <zlib.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
|
std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
|
||||||
@ -43,51 +38,6 @@ namespace
|
|||||||
auto vec = res.second;
|
auto vec = res.second;
|
||||||
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
std::string gzipCompress(const std::string& str)
|
|
||||||
{
|
|
||||||
z_stream zs; // z_stream is zlib's control structure
|
|
||||||
memset(&zs, 0, sizeof(zs));
|
|
||||||
|
|
||||||
// deflateInit2 configure the file format: request gzip instead of deflate
|
|
||||||
const int windowBits = 15;
|
|
||||||
const int GZIP_ENCODING = 16;
|
|
||||||
|
|
||||||
deflateInit2(&zs,
|
|
||||||
Z_DEFAULT_COMPRESSION,
|
|
||||||
Z_DEFLATED,
|
|
||||||
windowBits | GZIP_ENCODING,
|
|
||||||
8,
|
|
||||||
Z_DEFAULT_STRATEGY);
|
|
||||||
|
|
||||||
zs.next_in = (Bytef*) str.data();
|
|
||||||
zs.avail_in = (uInt) str.size(); // set the z_stream's input
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
char outbuffer[32768];
|
|
||||||
std::string outstring;
|
|
||||||
|
|
||||||
// retrieve the compressed bytes blockwise
|
|
||||||
do
|
|
||||||
{
|
|
||||||
zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
|
|
||||||
zs.avail_out = sizeof(outbuffer);
|
|
||||||
|
|
||||||
ret = deflate(&zs, Z_FINISH);
|
|
||||||
|
|
||||||
if (outstring.size() < zs.total_out)
|
|
||||||
{
|
|
||||||
// append the block to the output string
|
|
||||||
outstring.append(outbuffer, zs.total_out - outstring.size());
|
|
||||||
}
|
|
||||||
} while (ret == Z_OK);
|
|
||||||
|
|
||||||
deflateEnd(&zs);
|
|
||||||
|
|
||||||
return outstring;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -120,8 +70,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
|
||||||
{
|
{
|
||||||
_connectedClientsCount++;
|
_connectedClientsCount++;
|
||||||
|
|
||||||
@ -130,8 +79,7 @@ namespace ix
|
|||||||
|
|
||||||
if (std::get<0>(ret))
|
if (std::get<0>(ret))
|
||||||
{
|
{
|
||||||
auto response =
|
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
||||||
_onConnectionCallback(std::get<2>(ret), connectionState, std::move(connectionInfo));
|
|
||||||
if (!Http::sendResponse(response, socket))
|
if (!Http::sendResponse(response, socket))
|
||||||
{
|
{
|
||||||
logError("Cannot send response");
|
logError("Cannot send response");
|
||||||
@ -151,8 +99,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this](HttpRequestPtr request,
|
[this](HttpRequestPtr request,
|
||||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr {
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
|
||||||
std::string uri(request->uri);
|
std::string uri(request->uri);
|
||||||
if (uri.empty() || uri == "/")
|
if (uri.empty() || uri == "/")
|
||||||
{
|
{
|
||||||
@ -173,19 +120,9 @@ namespace ix
|
|||||||
|
|
||||||
std::string content = res.second;
|
std::string content = res.second;
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
std::string acceptEncoding = request->headers["Accept-encoding"];
|
|
||||||
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
|
|
||||||
{
|
|
||||||
content = gzipCompress(content);
|
|
||||||
headers["Content-Encoding"] = "gzip";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Log request
|
// Log request
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << connectionInfo->remoteIp << ":" << connectionInfo->remotePort << " "
|
ss << request->method << " " << request->headers["User-Agent"] << " "
|
||||||
<< request->method << " " << request->headers["User-Agent"] << " "
|
|
||||||
<< request->uri << " " << content.size();
|
<< request->uri << " " << content.size();
|
||||||
logInfo(ss.str());
|
logInfo(ss.str());
|
||||||
|
|
||||||
@ -209,16 +146,15 @@ namespace ix
|
|||||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||||
//
|
//
|
||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this, redirectUrl](HttpRequestPtr request,
|
[this,
|
||||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
redirectUrl](HttpRequestPtr request,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr {
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
headers["Server"] = userAgent();
|
headers["Server"] = userAgent();
|
||||||
|
|
||||||
// Log request
|
// Log request
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << connectionInfo->remoteIp << ":" << connectionInfo->remotePort << " "
|
ss << request->method << " " << request->headers["User-Agent"] << " "
|
||||||
<< request->method << " " << request->headers["User-Agent"] << " "
|
|
||||||
<< request->uri;
|
<< request->uri;
|
||||||
logInfo(ss.str());
|
logInfo(ss.str());
|
||||||
|
|
||||||
|
@ -23,9 +23,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using OnConnectionCallback =
|
using OnConnectionCallback =
|
||||||
std::function<HttpResponsePtr(HttpRequestPtr,
|
std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
|
||||||
std::shared_ptr<ConnectionState>,
|
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo)>;
|
|
||||||
|
|
||||||
HttpServer(int port = SocketServer::kDefaultPort,
|
HttpServer(int port = SocketServer::kDefaultPort,
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
@ -46,8 +44,7 @@ namespace ix
|
|||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState) final;
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) final;
|
|
||||||
virtual size_t getConnectedClientsCount() final;
|
virtual size_t getConnectedClientsCount() final;
|
||||||
|
|
||||||
void setDefaultConnectionCallback();
|
void setDefaultConnectionCallback();
|
||||||
|
@ -19,7 +19,6 @@ typedef unsigned long int nfds_t;
|
|||||||
#else
|
#else
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <netinet/ip.h>
|
#include <netinet/ip.h>
|
||||||
|
115
ixwebsocket/IXSelectInterruptEventFd.cpp
Normal file
115
ixwebsocket/IXSelectInterruptEventFd.cpp
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
31
ixwebsocket/IXSelectInterruptEventFd.h
Normal file
31
ixwebsocket/IXSelectInterruptEventFd.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
@ -5,10 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// On UNIX we use pipes to wake up select. There is no way to do that
|
// On macOS we use UNIX pipes to wake up select.
|
||||||
// on Windows so this file is compiled out on Windows.
|
|
||||||
//
|
//
|
||||||
#ifndef _WIN32
|
|
||||||
|
|
||||||
#include "IXSelectInterruptPipe.h"
|
#include "IXSelectInterruptPipe.h"
|
||||||
|
|
||||||
@ -146,5 +144,3 @@ namespace ix
|
|||||||
return _fildes[kPipeReadIndex];
|
return _fildes[kPipeReadIndex];
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
#endif // !_WIN32
|
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#include "IXSetThreadName.h"
|
|
||||||
|
|
||||||
// unix systems
|
|
||||||
#if defined(__APPLE__) || defined(__linux__) || defined(BSD)
|
|
||||||
#include <pthread.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// freebsd needs this header as well
|
|
||||||
#if defined(BSD)
|
|
||||||
#include <pthread_np.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Windows
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <Windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
|
||||||
|
|
||||||
#pragma pack(push, 8)
|
|
||||||
typedef struct tagTHREADNAME_INFO
|
|
||||||
{
|
|
||||||
DWORD dwType; // Must be 0x1000.
|
|
||||||
LPCSTR szName; // Pointer to name (in user addr space).
|
|
||||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
|
||||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
|
||||||
} THREADNAME_INFO;
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
void SetThreadName(DWORD dwThreadID, const char* threadName)
|
|
||||||
{
|
|
||||||
THREADNAME_INFO info;
|
|
||||||
info.dwType = 0x1000;
|
|
||||||
info.szName = threadName;
|
|
||||||
info.dwThreadID = dwThreadID;
|
|
||||||
info.dwFlags = 0;
|
|
||||||
|
|
||||||
__try
|
|
||||||
{
|
|
||||||
RaiseException(
|
|
||||||
MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info);
|
|
||||||
}
|
|
||||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void setThreadName(const std::string& name)
|
|
||||||
{
|
|
||||||
#if defined(__APPLE__)
|
|
||||||
//
|
|
||||||
// Apple reserves 16 bytes for its thread names
|
|
||||||
// Notice that the Apple version of pthread_setname_np
|
|
||||||
// does not take a pthread_t argument
|
|
||||||
//
|
|
||||||
pthread_setname_np(name.substr(0, 63).c_str());
|
|
||||||
#elif defined(__linux__)
|
|
||||||
//
|
|
||||||
// Linux only reserves 16 bytes for its thread names
|
|
||||||
// See prctl and PR_SET_NAME property in
|
|
||||||
// http://man7.org/linux/man-pages/man2/prctl.2.html
|
|
||||||
//
|
|
||||||
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
SetThreadName(-1, name.c_str());
|
|
||||||
#elif defined(BSD)
|
|
||||||
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
|
|
||||||
#else
|
|
||||||
// ... assert here ?
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
@ -376,8 +376,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (isCancellationRequested && isCancellationRequested())
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
{
|
{
|
||||||
const std::string errorMsg("Cancellation Requested");
|
return std::make_pair(false, std::string());
|
||||||
return std::make_pair(false, errorMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size = std::min(kChunkSize, length - output.size());
|
size_t size = std::min(kChunkSize, length - output.size());
|
||||||
@ -389,8 +388,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else if (ret <= 0 && !Socket::isWaitNeeded())
|
else if (ret <= 0 && !Socket::isWaitNeeded())
|
||||||
{
|
{
|
||||||
const std::string errorMsg("Recv Error");
|
return std::make_pair(false, std::string());
|
||||||
return std::make_pair(false, errorMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
|
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
|
||||||
@ -399,8 +397,7 @@ namespace ix
|
|||||||
// This way we are not busy looping
|
// This way we are not busy looping
|
||||||
if (isReadyToRead(1) == PollResultType::Error)
|
if (isReadyToRead(1) == PollResultType::Error)
|
||||||
{
|
{
|
||||||
const std::string errorMsg("Poll Error");
|
return std::make_pair(false, std::string());
|
||||||
return std::make_pair(false, errorMsg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketAppleSSL.cpp
|
* IXSocketAppleSSL.cpp
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||||
*
|
*
|
||||||
* Adapted from Satori SDK Apple SSL code.
|
* Adapted from Satori SDK Apple SSL code.
|
||||||
*/
|
*/
|
||||||
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
|
|
||||||
|
|
||||||
#include "IXSocketAppleSSL.h"
|
#include "IXSocketAppleSSL.h"
|
||||||
|
|
||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
@ -309,5 +307,3 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketAppleSSL.h
|
* IXSocketAppleSSL.h
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
#ifdef IXWEBSOCKET_USE_SECURE_TRANSPORT
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@ -48,5 +47,3 @@ namespace ix
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
#endif // IXWEBSOCKET_USE_SECURE_TRANSPORT
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketMbedTLS.cpp
|
* IXSocketMbedTLS.cpp
|
||||||
* Author: Benjamin Sergeant, Max Weisel
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
*
|
*
|
||||||
* Some code taken from
|
* Some code taken from
|
||||||
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
|
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
|
||||||
* and mini_client.c example from mbedtls
|
* and mini_client.c example from mbedtls
|
||||||
*/
|
*/
|
||||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
|
||||||
|
|
||||||
#include "IXSocketMbedTLS.h"
|
#include "IXSocketMbedTLS.h"
|
||||||
|
|
||||||
@ -43,55 +42,6 @@ namespace ix
|
|||||||
mbedtls_pk_init(&_pkey);
|
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)
|
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
|
||||||
{
|
{
|
||||||
initMBedTLS();
|
initMBedTLS();
|
||||||
@ -145,36 +95,18 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
|
||||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||||
|
|
||||||
|
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
||||||
|
|
||||||
if (_tlsOptions.isUsingSystemDefaults())
|
if (_tlsOptions.isUsingSystemDefaults())
|
||||||
{
|
{
|
||||||
if (!loadSystemCertificates(errMsg))
|
; // FIXME
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
|
||||||
{
|
{
|
||||||
if (_tlsOptions.isUsingInMemoryCAs())
|
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||||
{
|
return false;
|
||||||
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);
|
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
||||||
@ -348,5 +280,3 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
#endif // IXWEBSOCKET_USE_MBED_TLS
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketMbedTLS.h
|
* IXSocketMbedTLS.h
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@ -52,9 +51,6 @@ namespace ix
|
|||||||
|
|
||||||
bool init(const std::string& host, bool isClient, std::string& errMsg);
|
bool init(const std::string& host, bool isClient, std::string& errMsg);
|
||||||
void initMBedTLS();
|
void initMBedTLS();
|
||||||
bool loadSystemCertificates(std::string& errMsg);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
#endif // IXWEBSOCKET_USE_MBED_TLS
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketOpenSSL.cpp
|
* IXSocketOpenSSL.cpp
|
||||||
* Author: Benjamin Sergeant, Matt DeBoer, Max Weisel
|
* Author: Benjamin Sergeant, Matt DeBoer
|
||||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||||
*
|
*
|
||||||
* Adapted from Satori SDK OpenSSL code.
|
* Adapted from Satori SDK OpenSSL code.
|
||||||
*/
|
*/
|
||||||
#ifdef IXWEBSOCKET_USE_OPEN_SSL
|
|
||||||
|
|
||||||
#include "IXSocketOpenSSL.h"
|
#include "IXSocketOpenSSL.h"
|
||||||
|
|
||||||
@ -85,8 +84,6 @@ namespace ix
|
|||||||
|
|
||||||
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
||||||
std::once_flag SocketOpenSSL::_openSSLInitFlag;
|
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)
|
SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd)
|
||||||
: Socket(fd)
|
: Socket(fd)
|
||||||
@ -108,11 +105,6 @@ namespace ix
|
|||||||
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
|
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
|
||||||
#else
|
#else
|
||||||
(void) OPENSSL_config(nullptr);
|
(void) OPENSSL_config(nullptr);
|
||||||
|
|
||||||
if (CRYPTO_get_locking_callback() == nullptr)
|
|
||||||
{
|
|
||||||
CRYPTO_set_locking_callback(SocketOpenSSL::openSSLLockingCallback);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
(void) OpenSSL_add_ssl_algorithms();
|
(void) OpenSSL_add_ssl_algorithms();
|
||||||
@ -121,21 +113,6 @@ namespace ix
|
|||||||
_openSSLInitializationSuccessful = true;
|
_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)
|
std::string SocketOpenSSL::getSSLError(int ret)
|
||||||
{
|
{
|
||||||
unsigned long e;
|
unsigned long e;
|
||||||
@ -217,66 +194,6 @@ namespace ix
|
|||||||
return ctx;
|
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
|
* Check whether a hostname matches a pattern
|
||||||
*/
|
*/
|
||||||
@ -485,32 +402,20 @@ namespace ix
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else if (SSL_CTX_load_verify_locations(
|
||||||
|
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
|
||||||
{
|
{
|
||||||
if (_tlsOptions.isUsingInMemoryCAs())
|
auto sslErr = ERR_get_error();
|
||||||
{
|
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + _tlsOptions.caFile +
|
||||||
// Load from memory
|
"\") failed: ";
|
||||||
openSSLAddCARootsFromString(_tlsOptions.caFile);
|
errMsg += ERR_error_string(sslErr, nullptr);
|
||||||
}
|
return false;
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
@ -620,35 +525,26 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_tlsOptions.isUsingInMemoryCAs())
|
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)
|
||||||
{
|
{
|
||||||
// Load from memory
|
auto sslErr = ERR_get_error();
|
||||||
openSSLAddCARootsFromString(_tlsOptions.caFile);
|
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + _tlsOptions.caFile +
|
||||||
|
"') failed: ";
|
||||||
|
errMsg += ERR_error_string(sslErr, nullptr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const char* root_ca_file = _tlsOptions.caFile.c_str();
|
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
|
||||||
STACK_OF(X509_NAME) * rootCAs;
|
if (SSL_CTX_load_verify_locations(_ssl_context, root_ca_file, nullptr) != 1)
|
||||||
rootCAs = SSL_load_client_CA_file(root_ca_file);
|
|
||||||
if (rootCAs == NULL)
|
|
||||||
{
|
{
|
||||||
auto sslErr = ERR_get_error();
|
auto sslErr = ERR_get_error();
|
||||||
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" +
|
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
||||||
_tlsOptions.caFile + "') failed: ";
|
_tlsOptions.caFile + "\") failed: ";
|
||||||
errMsg += ERR_error_string(sslErr, nullptr);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -835,5 +731,3 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
#endif // IXWEBSOCKET_USE_OPEN_SSL
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* IXSocketOpenSSL.h
|
* IXSocketOpenSSL.h
|
||||||
* Author: Benjamin Sergeant, Matt DeBoer
|
* Author: Benjamin Sergeant, Matt DeBoer
|
||||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
#ifdef IXWEBSOCKET_USE_OPEN_SSL
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@ -40,7 +39,6 @@ namespace ix
|
|||||||
void openSSLInitialize();
|
void openSSLInitialize();
|
||||||
std::string getSSLError(int ret);
|
std::string getSSLError(int ret);
|
||||||
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
||||||
bool openSSLAddCARootsFromString(const std::string roots);
|
|
||||||
bool openSSLClientHandshake(const std::string& hostname,
|
bool openSSLClientHandshake(const std::string& hostname,
|
||||||
std::string& errMsg,
|
std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
@ -49,9 +47,6 @@ namespace ix
|
|||||||
bool handleTLSOptions(std::string& errMsg);
|
bool handleTLSOptions(std::string& errMsg);
|
||||||
bool openSSLServerHandshake(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* _ssl_connection;
|
||||||
SSL_CTX* _ssl_context;
|
SSL_CTX* _ssl_context;
|
||||||
const SSL_METHOD* _ssl_method;
|
const SSL_METHOD* _ssl_method;
|
||||||
@ -61,9 +56,6 @@ namespace ix
|
|||||||
|
|
||||||
static std::once_flag _openSSLInitFlag;
|
static std::once_flag _openSSLInitFlag;
|
||||||
static std::atomic<bool> _openSSLInitializationSuccessful;
|
static std::atomic<bool> _openSSLInitializationSuccessful;
|
||||||
static std::unique_ptr<std::mutex[]> _openSSLMutexes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
#endif // IXWEBSOCKET_USE_OPEN_SSL
|
|
||||||
|
@ -22,7 +22,7 @@ namespace ix
|
|||||||
const int SocketServer::kDefaultPort(8080);
|
const int SocketServer::kDefaultPort(8080);
|
||||||
const std::string SocketServer::kDefaultHost("127.0.0.1");
|
const std::string SocketServer::kDefaultHost("127.0.0.1");
|
||||||
const int SocketServer::kDefaultTcpBacklog(5);
|
const int SocketServer::kDefaultTcpBacklog(5);
|
||||||
const size_t SocketServer::kDefaultMaxConnections(128);
|
const size_t SocketServer::kDefaultMaxConnections(32);
|
||||||
const int SocketServer::kDefaultAddressFamily(AF_INET);
|
const int SocketServer::kDefaultAddressFamily(AF_INET);
|
||||||
|
|
||||||
SocketServer::SocketServer(
|
SocketServer::SocketServer(
|
||||||
@ -276,7 +276,6 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Accept a connection.
|
// Accept a connection.
|
||||||
// FIXME: Is this working for ipv6 ?
|
|
||||||
struct sockaddr_in client; // client address information
|
struct sockaddr_in client; // client address information
|
||||||
int clientFd; // socket connected to client
|
int clientFd; // socket connected to client
|
||||||
socklen_t addressLen = sizeof(client);
|
socklen_t addressLen = sizeof(client);
|
||||||
@ -308,45 +307,6 @@ namespace ix
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo;
|
|
||||||
|
|
||||||
if (_addressFamily == AF_INET)
|
|
||||||
{
|
|
||||||
char remoteIp[INET_ADDRSTRLEN];
|
|
||||||
if (inet_ntop(AF_INET, &client.sin_addr, remoteIp, INET_ADDRSTRLEN) == nullptr)
|
|
||||||
{
|
|
||||||
int err = Socket::getErrno();
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::run() error calling inet_ntop (ipv4): " << err << ", "
|
|
||||||
<< strerror(err);
|
|
||||||
logError(ss.str());
|
|
||||||
|
|
||||||
Socket::closeSocket(clientFd);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionInfo = std::make_unique<ConnectionInfo>(remoteIp, client.sin_port);
|
|
||||||
}
|
|
||||||
else // AF_INET6
|
|
||||||
{
|
|
||||||
char remoteIp[INET6_ADDRSTRLEN];
|
|
||||||
if (inet_ntop(AF_INET6, &client.sin_addr, remoteIp, INET6_ADDRSTRLEN) == nullptr)
|
|
||||||
{
|
|
||||||
int err = Socket::getErrno();
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::run() error calling inet_ntop (ipv6): " << err << ", "
|
|
||||||
<< strerror(err);
|
|
||||||
logError(ss.str());
|
|
||||||
|
|
||||||
Socket::closeSocket(clientFd);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionInfo = std::make_unique<ConnectionInfo>(remoteIp, client.sin_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<ConnectionState> connectionState;
|
std::shared_ptr<ConnectionState> connectionState;
|
||||||
if (_connectionStateFactory)
|
if (_connectionStateFactory)
|
||||||
{
|
{
|
||||||
@ -379,13 +339,10 @@ namespace ix
|
|||||||
|
|
||||||
// Launch the handleConnection work asynchronously in its own thread.
|
// Launch the handleConnection work asynchronously in its own thread.
|
||||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||||
_connectionsThreads.push_back(
|
_connectionsThreads.push_back(std::make_pair(
|
||||||
std::make_pair(connectionState,
|
connectionState,
|
||||||
std::thread(&SocketServer::handleConnection,
|
std::thread(
|
||||||
this,
|
&SocketServer::handleConnection, this, std::move(socket), connectionState)));
|
||||||
std::move(socket),
|
|
||||||
connectionState,
|
|
||||||
std::move(connectionInfo))));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXConnectionInfo.h"
|
|
||||||
#include "IXConnectionState.h"
|
#include "IXConnectionState.h"
|
||||||
#include "IXSocketTLSOptions.h"
|
#include "IXSocketTLSOptions.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@ -103,8 +102,7 @@ namespace ix
|
|||||||
ConnectionStateFactory _connectionStateFactory;
|
ConnectionStateFactory _connectionStateFactory;
|
||||||
|
|
||||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) = 0;
|
|
||||||
virtual size_t getConnectedClientsCount() = 0;
|
virtual size_t getConnectedClientsCount() = 0;
|
||||||
|
|
||||||
// Returns true if all connection threads are joined
|
// Returns true if all connection threads are joined
|
||||||
|
@ -15,7 +15,6 @@ namespace ix
|
|||||||
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
|
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
|
||||||
const char* kTLSCAFileDisableVerify = "NONE";
|
const char* kTLSCAFileDisableVerify = "NONE";
|
||||||
const char* kTLSCiphersUseDefault = "DEFAULT";
|
const char* kTLSCiphersUseDefault = "DEFAULT";
|
||||||
const char* kTLSInMemoryMarker = "-----BEGIN CERTIFICATE-----";
|
|
||||||
|
|
||||||
bool SocketTLSOptions::isValid() const
|
bool SocketTLSOptions::isValid() const
|
||||||
{
|
{
|
||||||
@ -59,11 +58,6 @@ namespace ix
|
|||||||
return caFile == kTLSCAFileUseSystemDefaults;
|
return caFile == kTLSCAFileUseSystemDefaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SocketTLSOptions::isUsingInMemoryCAs() const
|
|
||||||
{
|
|
||||||
return caFile.find(kTLSInMemoryMarker) != std::string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
||||||
{
|
{
|
||||||
return caFile == kTLSCAFileDisableVerify;
|
return caFile == kTLSCAFileDisableVerify;
|
||||||
|
@ -37,8 +37,6 @@ namespace ix
|
|||||||
|
|
||||||
bool isUsingSystemDefaults() const;
|
bool isUsingSystemDefaults() const;
|
||||||
|
|
||||||
bool isUsingInMemoryCAs() const;
|
|
||||||
|
|
||||||
bool isPeerVerifyDisabled() const;
|
bool isPeerVerifyDisabled() const;
|
||||||
|
|
||||||
bool isUsingDefaultCiphers() const;
|
bool isUsingDefaultCiphers() const;
|
||||||
|
@ -44,18 +44,6 @@ namespace ix
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UdpSocket::isWaitNeeded()
|
|
||||||
{
|
|
||||||
int err = getErrno();
|
|
||||||
|
|
||||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UdpSocket::closeSocket(int fd)
|
void UdpSocket::closeSocket(int fd)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -74,13 +62,6 @@ namespace ix
|
|||||||
return false;
|
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));
|
memset(&_server, 0, sizeof(_server));
|
||||||
_server.sin_family = AF_INET;
|
_server.sin_family = AF_INET;
|
||||||
_server.sin_port = htons(port);
|
_server.sin_port = htons(port);
|
||||||
@ -112,15 +93,4 @@ namespace ix
|
|||||||
return (ssize_t)::sendto(
|
return (ssize_t)::sendto(
|
||||||
_sockfd, buffer.data(), buffer.size(), 0, (struct sockaddr*) &_server, sizeof(_server));
|
_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
|
} // namespace ix
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user