Compare commits
1 Commits
v10.5.5
...
feature/we
Author | SHA1 | Date | |
---|---|---|---|
cc588f9c05 |
@ -1,21 +1,60 @@
|
|||||||
name: uwp
|
name: unittest
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
uwp:
|
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_mbedtls:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||||
|
- run: |
|
||||||
|
vcpkg install zlib:x64-windows
|
||||||
|
vcpkg install mbedtls:x64-windows
|
||||||
- run: |
|
- run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
cd 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 ..
|
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
|
- 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 with OpenSSL is working but disabled as it takes 13 minutes (10 for openssl) to build with vcpkg
|
||||||
#
|
#
|
||||||
@ -36,3 +75,4 @@ jobs:
|
|||||||
# # Running the unittest does not work, the binary cannot be found
|
# # Running the unittest does not work, the binary cannot be found
|
||||||
# #- run: ../build/test/ixwebsocket_unittest.exe
|
# #- run: ../build/test/ixwebsocket_unittest.exe
|
||||||
# # working-directory: test
|
# # working-directory: test
|
||||||
|
|
66
.github/workflows/docker.yml
vendored
66
.github/workflows/docker.yml
vendored
@ -1,66 +0,0 @@
|
|||||||
name: docker
|
|
||||||
|
|
||||||
# When its time to do a release do a build for amd64
|
|
||||||
# and push all of them to Docker Hub.
|
|
||||||
# Only trigger on semver shaped tags.
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*.*.*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
login:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Prepare
|
|
||||||
id: prep
|
|
||||||
run: |
|
|
||||||
DOCKER_IMAGE=machinezone/ws
|
|
||||||
VERSION=edge
|
|
||||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
|
||||||
VERSION=${GITHUB_REF#refs/tags/v}
|
|
||||||
fi
|
|
||||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
|
||||||
VERSION=nightly
|
|
||||||
fi
|
|
||||||
TAGS="${DOCKER_IMAGE}:${VERSION}"
|
|
||||||
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
||||||
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
|
|
||||||
fi
|
|
||||||
echo ::set-output name=tags::${TAGS}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@master
|
|
||||||
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
|
|
||||||
- name: Login to GitHub Package Registry
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2-build-push
|
|
||||||
with:
|
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
target: prod
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.prep.outputs.tags }}
|
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
|
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
|
|
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]
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
# Find package structure taken from libcurl
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
|
|
||||||
find_path(DEFLATE_INCLUDE_DIRS libdeflate.h)
|
|
||||||
find_library(DEFLATE_LIBRARY deflate)
|
|
||||||
|
|
||||||
find_package_handle_standard_args(DEFLATE
|
|
||||||
FOUND_VAR
|
|
||||||
DEFLATE_FOUND
|
|
||||||
REQUIRED_VARS
|
|
||||||
DEFLATE_LIBRARY
|
|
||||||
DEFLATE_INCLUDE_DIRS
|
|
||||||
FAIL_MESSAGE
|
|
||||||
"Could NOT find deflate"
|
|
||||||
)
|
|
||||||
|
|
||||||
set(DEFLATE_INCLUDE_DIRS ${DEFLATE_INCLUDE_DIRS})
|
|
||||||
set(DEFLATE_LIBRARIES ${DEFLATE_LIBRARY})
|
|
@ -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)
|
||||||
|
191
CMakeLists.txt
191
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,16 +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/IXGzipCodec.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
|
||||||
@ -52,12 +44,13 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXWebSocketCloseConstants.cpp
|
ixwebsocket/IXWebSocketCloseConstants.cpp
|
||||||
ixwebsocket/IXWebSocketHandshake.cpp
|
ixwebsocket/IXWebSocketHandshake.cpp
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||||
|
ixwebsocket/IXWebSocketMessage.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
|
||||||
@ -66,8 +59,6 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXConnectionState.h
|
ixwebsocket/IXConnectionState.h
|
||||||
ixwebsocket/IXDNSLookup.h
|
ixwebsocket/IXDNSLookup.h
|
||||||
ixwebsocket/IXExponentialBackoff.h
|
ixwebsocket/IXExponentialBackoff.h
|
||||||
ixwebsocket/IXGetFreePort.h
|
|
||||||
ixwebsocket/IXGzipCodec.h
|
|
||||||
ixwebsocket/IXHttp.h
|
ixwebsocket/IXHttp.h
|
||||||
ixwebsocket/IXHttpClient.h
|
ixwebsocket/IXHttpClient.h
|
||||||
ixwebsocket/IXHttpServer.h
|
ixwebsocket/IXHttpServer.h
|
||||||
@ -75,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
|
||||||
@ -91,7 +81,6 @@ 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
|
||||||
@ -100,43 +89,54 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
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()
|
||||||
|
|
||||||
@ -151,72 +151,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()
|
|
||||||
|
|
||||||
# brew install libdeflate
|
|
||||||
find_package(DEFLATE)
|
|
||||||
if (DEFLATE_FOUND)
|
|
||||||
include_directories(${DEFLATE_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(ixwebsocket ${DEFLATE_LIBRARIES})
|
|
||||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_DEFLATE)
|
|
||||||
endif()
|
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)
|
||||||
@ -224,6 +168,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}
|
||||||
@ -234,40 +221,24 @@ 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)
|
||||||
|
|
||||||
include(FetchContent)
|
add_subdirectory(third_party/spdlog spdlog)
|
||||||
FetchContent_Declare(spdlog
|
|
||||||
GIT_REPOSITORY "https://github.com/gabime/spdlog"
|
|
||||||
GIT_TAG "v1.8.0"
|
|
||||||
GIT_SHALLOW 1)
|
|
||||||
|
|
||||||
FetchContent_MakeAvailable(spdlog)
|
|
||||||
|
|
||||||
if (USE_WS)
|
if (USE_WS)
|
||||||
add_subdirectory(ws)
|
add_subdirectory(ws)
|
||||||
|
104
README.md
104
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. Note that the MinGW compiler is not supported at this point. 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,28 +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
|
|
||||||
- [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots
|
|
||||||
|
|
||||||
## 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,11 +1,67 @@
|
|||||||
version: "3.3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
push:
|
# snake:
|
||||||
entrypoint: ws push_server --host 0.0.0.0
|
# image: bsergean/ws:build
|
||||||
image: ${DOCKER_REPO}/ws:build
|
# entrypoint: ws snake --port 8767 --host 0.0.0.0 --redis_hosts redis1
|
||||||
|
# ports:
|
||||||
|
# - "8767:8767"
|
||||||
|
# networks:
|
||||||
|
# - ws-net
|
||||||
|
# depends_on:
|
||||||
|
# - redis1
|
||||||
|
|
||||||
autoroute:
|
# proxy:
|
||||||
entrypoint: ws autoroute ws://push:8008
|
# image: bsergean/ws:build
|
||||||
image: ${DOCKER_REPO}/ws:build
|
# entrypoint: strace ws proxy_server --remote_host 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765 -v
|
||||||
depends_on:
|
# ports:
|
||||||
- push
|
# - "8765:8765"
|
||||||
|
# networks:
|
||||||
|
# - ws-net
|
||||||
|
|
||||||
|
#pyproxy:
|
||||||
|
# image: bsergean/ws_proxy:build
|
||||||
|
# entrypoint: /usr/bin/ws_proxy.py --remote_url 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765
|
||||||
|
# ports:
|
||||||
|
# - "8765:8765"
|
||||||
|
# networks:
|
||||||
|
# - ws-net
|
||||||
|
|
||||||
|
# # ws:
|
||||||
|
# # security_opt:
|
||||||
|
# # - seccomp:unconfined
|
||||||
|
# # cap_add:
|
||||||
|
# # - SYS_PTRACE
|
||||||
|
# # stdin_open: true
|
||||||
|
# # tty: true
|
||||||
|
# # image: bsergean/ws:build
|
||||||
|
# # entrypoint: sh
|
||||||
|
# # networks:
|
||||||
|
# # - ws-net
|
||||||
|
# # depends_on:
|
||||||
|
# # - redis1
|
||||||
|
# #
|
||||||
|
# # redis1:
|
||||||
|
# # image: redis:alpine
|
||||||
|
# # networks:
|
||||||
|
# # - ws-net
|
||||||
|
# #
|
||||||
|
# # statsd:
|
||||||
|
# # image: jaconel/statsd
|
||||||
|
# # ports:
|
||||||
|
# # - "8125:8125"
|
||||||
|
# # environment:
|
||||||
|
# # - STATSD_DUMP_MSG=true
|
||||||
|
# # - GRAPHITE_HOST=127.0.0.1
|
||||||
|
# # networks:
|
||||||
|
# # - ws-net
|
||||||
|
|
||||||
|
compile:
|
||||||
|
image: alpine
|
||||||
|
entrypoint: sh
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
volumes:
|
||||||
|
- /Users/bsergeant/src/foss:/home/bsergean/src/foss
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ws-net:
|
||||||
|
@ -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 git
|
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 strace && \
|
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,419 +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.5.5] - 2020-11-07
|
|
||||||
|
|
||||||
(ws autoroute) Display result in compliant way (AUTOROUTE IXWebSocket :: N ms) so that result can be parsed easily
|
|
||||||
|
|
||||||
## [10.5.4] - 2020-10-30
|
|
||||||
|
|
||||||
(ws gunzip + IXGZipCodec) Can decompress gziped data with libdeflate. ws gunzip computed output filename was incorrect (was the extension aka gz) instead of the file without the extension. Also check whether the output file is writeable.
|
|
||||||
|
|
||||||
## [10.5.3] - 2020-10-19
|
|
||||||
|
|
||||||
(http code) With zlib disabled, some code should not be reached
|
|
||||||
|
|
||||||
## [10.5.2] - 2020-10-12
|
|
||||||
|
|
||||||
(ws curl) Add support for --data-binary option, to set the request body. When present the request will be sent with the POST verb
|
|
||||||
|
|
||||||
## [10.5.1] - 2020-10-09
|
|
||||||
|
|
||||||
(http client + server + ws) Add support for compressing http client requests with gzip. --compress_request argument is used in ws to enable this. The Content-Encoding is set to gzip, and decoded on the server side if present.
|
|
||||||
|
|
||||||
## [10.5.0] - 2020-09-30
|
|
||||||
|
|
||||||
(http client + server + ws) Add support for uploading files with ws -F foo=@filename, new -D http server option to debug incoming client requests, internal api changed for http POST, PUT and PATCH to supply an HttpFormDataParameters
|
|
||||||
|
|
||||||
## [10.4.9] - 2020-09-30
|
|
||||||
|
|
||||||
(http server + utility code) Add support for doing gzip compression with libdeflate library, if available
|
|
||||||
|
|
||||||
## [10.4.8] - 2020-09-30
|
|
||||||
|
|
||||||
(cmake) Stop using FetchContent cmake module to retrieve jsoncpp third party dependency
|
|
||||||
|
|
||||||
## [10.4.7] - 2020-09-28
|
|
||||||
|
|
||||||
(ws) add gzip and gunzip ws sub commands
|
|
||||||
|
|
||||||
## [10.4.6] - 2020-09-26
|
|
||||||
|
|
||||||
(cmake) use FetchContent cmake module to retrieve jsoncpp third party dependency
|
|
||||||
|
|
||||||
## [10.4.5] - 2020-09-26
|
|
||||||
|
|
||||||
(cmake) use FetchContent cmake module to retrieve spdlog third party dependency
|
|
||||||
|
|
||||||
## [10.4.4] - 2020-09-22
|
|
||||||
|
|
||||||
(cobra connection) retrieve cobra server connection id from the cobra handshake message and display it in ws clients, metrics publisher and bots
|
|
||||||
|
|
||||||
## [10.4.3] - 2020-09-22
|
|
||||||
|
|
||||||
(cobra 2 cobra) specify as an HTTP header which channel we will republish to
|
|
||||||
|
|
||||||
## [10.4.2] - 2020-09-18
|
|
||||||
|
|
||||||
(cobra bots) change an error log to a warning log when reconnecting because no messages were received for a minute
|
|
||||||
|
|
||||||
## [10.4.1] - 2020-09-18
|
|
||||||
|
|
||||||
(cobra connection and bots) set an HTTP header when connecting to help with debugging bots
|
|
||||||
|
|
||||||
## [10.4.0] - 2020-09-12
|
|
||||||
|
|
||||||
(http server) read body request when the Content-Length is specified + set timeout to read the request to 30 seconds max by default, and make it configurable as a constructor parameter
|
|
||||||
|
|
||||||
## [10.3.5] - 2020-09-09
|
|
||||||
|
|
||||||
(ws) autoroute command exit on its own once all messages have been received
|
|
||||||
|
|
||||||
## [10.3.4] - 2020-09-04
|
|
||||||
|
|
||||||
(docker) ws docker file installs strace
|
|
||||||
|
|
||||||
## [10.3.3] - 2020-09-02
|
|
||||||
|
|
||||||
(ws) echo_client command renamed to autoroute. Command exit once the server close the connection. push_server commands exit once N messages have been sent.
|
|
||||||
|
|
||||||
## [10.3.2] - 2020-08-31
|
|
||||||
|
|
||||||
(ws + cobra bots) add a cobra_to_cobra ws subcommand to subscribe to a channel and republish received events to a different channel
|
|
||||||
|
|
||||||
## [10.3.1] - 2020-08-28
|
|
||||||
|
|
||||||
(socket servers) merge the ConnectionInfo class with the ConnectionState one, which simplify all the server apis
|
|
||||||
|
|
||||||
## [10.3.0] - 2020-08-26
|
|
||||||
|
|
||||||
(ws) set the main thread name, to help with debugging in XCode, gdb, lldb etc...
|
|
||||||
|
|
||||||
## [10.2.9] - 2020-08-19
|
|
||||||
|
|
||||||
(ws) cobra to python bot / take a module python name as argument foo.bar.baz instead of a path foo/bar/baz.py
|
|
||||||
|
|
||||||
## [10.2.8] - 2020-08-19
|
|
||||||
|
|
||||||
(ws) on Linux with mbedtls, when the system ca certs are specified (the default) pick up sensible OS supplied paths (tested with CentOS and Alpine)
|
|
||||||
|
|
||||||
## [10.2.7] - 2020-08-18
|
|
||||||
|
|
||||||
(ws push_server) on the server side, stop sending and close the connection when the remote end has disconnected
|
|
||||||
|
|
||||||
## [10.2.6] - 2020-08-17
|
|
||||||
|
|
||||||
(ixwebsocket) replace std::unique_ptr<unsigned char[]> with std::array for some fixed arrays (which are in C++11)
|
|
||||||
|
|
||||||
## [10.2.5] - 2020-08-15
|
|
||||||
|
|
||||||
(ws) merge all ws_*.cpp files into a single one to speedup compilation
|
|
||||||
|
|
||||||
## [10.2.4] - 2020-08-15
|
|
||||||
|
|
||||||
(socket server) in the loop accepting connections, call select without a timeout on unix to avoid busy looping, and only wake up when a new connection happens
|
|
||||||
|
|
||||||
## [10.2.3] - 2020-08-15
|
|
||||||
|
|
||||||
(socket server) instead of busy looping with a sleep, only wake up the GC thread when a new thread will have to be joined, (we know that thanks to the ConnectionState OnSetTerminated callback
|
|
||||||
|
|
||||||
## [10.2.2] - 2020-08-15
|
|
||||||
|
|
||||||
(socket server) add a callback to the ConnectionState to be invoked when the connection is terminated. This will be used by the SocketServer in the future to know on time that the associated connection thread can be terminated.
|
|
||||||
|
|
||||||
## [10.2.1] - 2020-08-15
|
|
||||||
|
|
||||||
(socket server) do not create a select interrupt object everytime when polling for notifications while waiting for new connections, instead use a persistent one which is a member variable
|
|
||||||
|
|
||||||
## [10.2.0] - 2020-08-14
|
|
||||||
|
|
||||||
(ixwebsocket client) handle HTTP redirects
|
|
||||||
|
|
||||||
## [10.2.0] - 2020-08-13
|
|
||||||
|
|
||||||
(ws) upgrade to latest version of nlohmann json (3.9.1 from 3.2.0)
|
|
||||||
|
|
||||||
## [10.1.9] - 2020-08-13
|
|
||||||
|
|
||||||
(websocket proxy server) add ability to map different hosts to different websocket servers, using a json config file
|
|
||||||
|
|
||||||
## [10.1.8] - 2020-08-12
|
|
||||||
|
|
||||||
(ws) on macOS, with OpenSSL or MbedTLS, use /etc/ssl/cert.pem as the system certs
|
|
||||||
|
|
||||||
## [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
|
## [9.2.5] - 2020-04-13
|
||||||
|
|
||||||
(websocket) WebSocketMessagePtr is a unique_ptr instead of a shared_ptr
|
(websocket) WebSocketMessagePtr is a unique_ptr instead of a shared_ptr
|
||||||
|
@ -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
|
|
||||||
```
|
|
180
docs/usage.md
180
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,49 +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::cout << "Remote ip: " << connectionState->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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -342,73 +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,
|
|
||||||
WebSocket& webSocket,
|
|
||||||
const WebSocketMessagePtr& msg)
|
|
||||||
{
|
|
||||||
// The ConnectionState object contains information about the connection,
|
|
||||||
// at this point only the client ip address and the port.
|
|
||||||
std::cout << "Remote ip: " << connectionState->getRemoteIp();
|
|
||||||
|
|
||||||
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
|
||||||
@ -458,25 +358,18 @@ out = httpClient.get(url, args);
|
|||||||
// POST request with parameters
|
// POST request with parameters
|
||||||
HttpParameters httpParameters;
|
HttpParameters httpParameters;
|
||||||
httpParameters["foo"] = "bar";
|
httpParameters["foo"] = "bar";
|
||||||
|
out = httpClient.post(url, httpParameters, args);
|
||||||
// HTTP form data can be passed in as well, for multi-part upload of files
|
|
||||||
HttpFormDataParameters httpFormDataParameters;
|
|
||||||
httpParameters["baz"] = "booz";
|
|
||||||
|
|
||||||
out = httpClient.post(url, httpParameters, httpFormDataParameters, args);
|
|
||||||
|
|
||||||
// POST request with a body
|
// POST request with a body
|
||||||
out = httpClient.post(url, std::string("foo=bar"), args);
|
out = httpClient.post(url, std::string("foo=bar"), args);
|
||||||
|
|
||||||
// PUT and PATCH are available too.
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Result
|
// Result
|
||||||
//
|
//
|
||||||
auto statusCode = response->statusCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
|
auto statusCode = response->statusCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
|
||||||
auto errorCode = response->errorCode; // 200, 404, etc...
|
auto errorCode = response->errorCode; // 200, 404, etc...
|
||||||
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string)
|
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string)
|
||||||
auto body = response->body; // All the bytes from the response as an std::string
|
auto payload = response->payload; // All the bytes from the response as an std::string
|
||||||
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
|
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
|
||||||
auto uploadSize = response->uploadSize; // Byte count of uploaded data
|
auto uploadSize = response->uploadSize; // Byte count of uploaded data
|
||||||
auto downloadSize = response->downloadSize; // Byte count of downloaded data
|
auto downloadSize = response->downloadSize; // Byte count of downloaded data
|
||||||
@ -499,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
|
||||||
@ -524,13 +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) -> HttpResponsePtr
|
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||||
{
|
{
|
||||||
// Build a string for the response
|
// Build a string for the response
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << connectionState->getRemoteIp();
|
ss << request->method
|
||||||
<< " "
|
|
||||||
<< request->method
|
|
||||||
<< " "
|
<< " "
|
||||||
<< request->uri;
|
<< request->uri;
|
||||||
|
|
||||||
@ -558,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
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@ -572,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.
|
||||||
|
|
||||||
|
18
docs/ws.md
18
docs/ws.md
@ -204,24 +204,6 @@ Listening on 127.0.0.1:8008
|
|||||||
|
|
||||||
If you connect to ws://127.0.0.1:8008, the proxy will connect to ws://127.0.0.1:9000 and pass all traffic to this server.
|
If you connect to ws://127.0.0.1:8008, the proxy will connect to ws://127.0.0.1:9000 and pass all traffic to this server.
|
||||||
|
|
||||||
You can also use a more complex setup if you want to redirect to different websocket servers based on the hostname your client is trying to connect to. If you have multiple CNAME aliases that point to the same server.
|
|
||||||
|
|
||||||
A JSON config file is used to express that mapping ; here connecting to echo.jeanserge.com will proxy the client to ws://localhost:8008 on the local machine (which actually runs ws echo_server), while connecting to bavarde.jeanserge.com will proxy the client to ws://localhost:5678 where a cobra python server is running. As a side note you will need a wildcard SSL certificate if you want to have SSL enabled on that machine.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"remote_urls": {
|
|
||||||
"echo.jeanserge.com": "ws://localhost:8008",
|
|
||||||
"bavarde.jeanserge.com": "ws://localhost:5678"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
The --config_path option is required to instruct ws proxy_server to read that file.
|
|
||||||
|
|
||||||
```
|
|
||||||
ws proxy_server --config_path proxyConfig.json --port 8765
|
|
||||||
```
|
|
||||||
|
|
||||||
## File transfer
|
## File transfer
|
||||||
|
|
||||||
```
|
```
|
||||||
|
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,25 +4,16 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
set (IXBOTS_SOURCES
|
set (IXBOTS_SOURCES
|
||||||
ixbots/IXCobraBot.cpp
|
|
||||||
ixbots/IXCobraToCobraBot.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/IXCobraToCobraBot.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
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,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
|
||||||
@ -47,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,326 +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;
|
|
||||||
|
|
||||||
config.headers["X-Cobra-Channel"] = channel;
|
|
||||||
|
|
||||||
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::warn(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::Handshake)
|
|
||||||
{
|
|
||||||
CoreLogger::info("Subscriber: Cobra handshake connection id: " + event->connectionId);
|
|
||||||
}
|
|
||||||
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,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXCobraToCobraBot.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXCobraToCobraBot.h"
|
|
||||||
|
|
||||||
#include "IXCobraBot.h"
|
|
||||||
#include <ixcobra/IXCobraMetricsPublisher.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
int64_t cobra_to_cobra_bot(const ix::CobraBotConfig& cobraBotConfig,
|
|
||||||
const std::string& republishChannel,
|
|
||||||
const std::string& publisherRolename,
|
|
||||||
const std::string& publisherRolesecret)
|
|
||||||
{
|
|
||||||
CobraBot bot;
|
|
||||||
|
|
||||||
CobraMetricsPublisher cobraMetricsPublisher;
|
|
||||||
CobraConfig cobraPublisherConfig = cobraBotConfig.cobraConfig;
|
|
||||||
cobraPublisherConfig.rolename = publisherRolename;
|
|
||||||
cobraPublisherConfig.rolesecret = publisherRolesecret;
|
|
||||||
cobraPublisherConfig.headers["X-Cobra-Republish-Channel"] = republishChannel;
|
|
||||||
|
|
||||||
cobraMetricsPublisher.configure(cobraPublisherConfig, republishChannel);
|
|
||||||
|
|
||||||
bot.setOnBotMessageCallback(
|
|
||||||
[&republishChannel, &cobraMetricsPublisher](const Json::Value& msg,
|
|
||||||
const std::string& /*position*/,
|
|
||||||
std::atomic<bool>& /*throttled*/,
|
|
||||||
std::atomic<bool>& /*fatalCobraError*/,
|
|
||||||
std::atomic<uint64_t>& sentCount) -> void {
|
|
||||||
Json::Value msgWithNoId(msg);
|
|
||||||
msgWithNoId.removeMember("id");
|
|
||||||
|
|
||||||
cobraMetricsPublisher.push(republishChannel, msg);
|
|
||||||
sentCount++;
|
|
||||||
});
|
|
||||||
|
|
||||||
return bot.run(cobraBotConfig);
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXCobraToCobraBot.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_cobra_bot(const ix::CobraBotConfig& config,
|
|
||||||
const std::string& republishChannel,
|
|
||||||
const std::string& publisherRolename,
|
|
||||||
const std::string& publisherRolesecret);
|
|
||||||
} // namespace ix
|
|
@ -1,329 +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& moduleName)
|
|
||||||
{
|
|
||||||
#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
|
|
||||||
|
|
||||||
PyObject* pyModuleName = PyUnicode_DecodeFSDefault(moduleName.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& moduleName);
|
|
||||||
} // 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->body);
|
|
||||||
|
|
||||||
// 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,9 +6,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
|
||||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -20,7 +19,6 @@ namespace ix
|
|||||||
std::string rolesecret;
|
std::string rolesecret;
|
||||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions;
|
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions;
|
||||||
SocketTLSOptions socketTLSOptions;
|
SocketTLSOptions socketTLSOptions;
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
|
|
||||||
CobraConfig(const std::string& a = std::string(),
|
CobraConfig(const std::string& a = std::string(),
|
||||||
const std::string& e = std::string(),
|
const std::string& e = std::string(),
|
||||||
|
@ -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,18 +87,16 @@ 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,
|
||||||
CobraConnection::MsgId msgId,
|
CobraConnection::MsgId msgId)
|
||||||
const std::string& connectionId)
|
|
||||||
{
|
{
|
||||||
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, connectionId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,14 +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,
|
|
||||||
const WebSocketHttpHeaders& headers)
|
|
||||||
{
|
{
|
||||||
_roleName = rolename;
|
_roleName = rolename;
|
||||||
_roleSecret = rolesecret;
|
_roleSecret = rolesecret;
|
||||||
@ -271,7 +268,6 @@ namespace ix
|
|||||||
_webSocket->setUrl(url);
|
_webSocket->setUrl(url);
|
||||||
_webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
_webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
||||||
_webSocket->setTLSOptions(socketTLSOptions);
|
_webSocket->setTLSOptions(socketTLSOptions);
|
||||||
_webSocket->setExtraHeaders(headers);
|
|
||||||
|
|
||||||
// Send a websocket ping every N seconds (N = 30) now
|
// Send a websocket ping every N seconds (N = 30) now
|
||||||
// This should keep the connection open and prevent some load balancers such as
|
// This should keep the connection open and prevent some load balancers such as
|
||||||
@ -286,8 +282,7 @@ namespace ix
|
|||||||
config.rolename,
|
config.rolename,
|
||||||
config.rolesecret,
|
config.rolesecret,
|
||||||
config.webSocketPerMessageDeflateOptions,
|
config.webSocketPerMessageDeflateOptions,
|
||||||
config.socketTLSOptions,
|
config.socketTLSOptions);
|
||||||
config.headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -353,18 +348,6 @@ namespace ix
|
|||||||
|
|
||||||
if (!nonce.isString()) return false;
|
if (!nonce.isString()) return false;
|
||||||
|
|
||||||
if (!data.isMember("connection_id")) return false;
|
|
||||||
Json::Value connectionId = data["connection_id"];
|
|
||||||
|
|
||||||
if (!connectionId.isString()) return false;
|
|
||||||
|
|
||||||
invokeEventCallback(ix::CobraEventType::Handshake,
|
|
||||||
std::string(),
|
|
||||||
WebSocketHttpHeaders(),
|
|
||||||
std::string(),
|
|
||||||
0,
|
|
||||||
connectionId.asString());
|
|
||||||
|
|
||||||
return sendAuthMessage(nonce.asString());
|
return sendAuthMessage(nonce.asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -413,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;
|
||||||
}
|
}
|
||||||
@ -432,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;
|
||||||
}
|
}
|
||||||
@ -481,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);
|
||||||
|
|
||||||
@ -515,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);
|
||||||
|
|
||||||
@ -584,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())
|
||||||
{
|
{
|
||||||
@ -636,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
|
||||||
//
|
//
|
||||||
@ -695,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)>;
|
||||||
@ -56,8 +74,7 @@ namespace ix
|
|||||||
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);
|
||||||
const WebSocketHttpHeaders& headers);
|
|
||||||
|
|
||||||
void configure(const ix::CobraConfig& config);
|
void configure(const ix::CobraConfig& config);
|
||||||
|
|
||||||
@ -89,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
|
||||||
@ -122,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();
|
||||||
@ -154,21 +171,16 @@ 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(),
|
||||||
uint64_t msgId = std::numeric_limits<uint64_t>::max(),
|
uint64_t msgId = std::numeric_limits<uint64_t>::max());
|
||||||
const std::string& connectionId = std::string());
|
|
||||||
|
|
||||||
void invokeErrorCallback(const std::string& errorMsg, const std::string& serializedPdu);
|
void invokeErrorCallback(const std::string& errorMsg, const std::string& serializedPdu);
|
||||||
|
|
||||||
/// 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,44 +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
|
|
||||||
const std::string& connectionId;
|
|
||||||
|
|
||||||
CobraEvent(ix::CobraEventType t,
|
|
||||||
const std::string& e,
|
|
||||||
const ix::WebSocketHttpHeaders& h,
|
|
||||||
const std::string& s,
|
|
||||||
uint64_t m,
|
|
||||||
const std::string& c)
|
|
||||||
: type(t)
|
|
||||||
, errMsg(e)
|
|
||||||
, headers(h)
|
|
||||||
, subscriptionId(s)
|
|
||||||
, msgId(m)
|
|
||||||
, connectionId(c)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using CobraEventPtr = std::unique_ptr<CobraEvent>;
|
|
||||||
} // namespace ix
|
|
@ -1,26 +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,
|
|
||||||
Handshake = 11
|
|
||||||
};
|
|
||||||
}
|
|
@ -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,87 +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::LogLevel logLevel = LogLevel::Info;
|
(ix::CobraConnectionEventType eventType,
|
||||||
|
const std::string& errMsg,
|
||||||
if (event->type == ix::CobraEventType::Open)
|
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::Handshake)
|
|
||||||
{
|
|
||||||
ss << "Cobra handshake connection id: " << event->connectionId;
|
|
||||||
}
|
|
||||||
else if (event->type == ix::CobraEventType::Authenticated)
|
|
||||||
{
|
|
||||||
ss << "Authenticated";
|
|
||||||
}
|
|
||||||
else if (event->type == ix::CobraEventType::Error)
|
|
||||||
{
|
|
||||||
ss << "Error: " << event->errMsg;
|
|
||||||
logLevel = ix::LogLevel::Error;
|
|
||||||
}
|
|
||||||
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";
|
|
||||||
logLevel = ix::LogLevel::Debug;
|
|
||||||
}
|
|
||||||
else if (event->type == ix::CobraEventType::Pong)
|
|
||||||
{
|
|
||||||
ss << "Received websocket pong";
|
|
||||||
}
|
|
||||||
else if (event->type == ix::CobraEventType::HandshakeError)
|
|
||||||
{
|
|
||||||
ss << "Handshake error: " << event->errMsg;
|
|
||||||
logLevel = ix::LogLevel::Error;
|
|
||||||
}
|
|
||||||
else if (event->type == ix::CobraEventType::AuthenticationError)
|
|
||||||
{
|
|
||||||
ss << "Authentication error: " << event->errMsg;
|
|
||||||
logLevel = ix::LogLevel::Error;
|
|
||||||
}
|
|
||||||
else if (event->type == ix::CobraEventType::SubscriptionError)
|
|
||||||
{
|
|
||||||
ss << "Subscription error: " << event->errMsg;
|
|
||||||
logLevel = ix::LogLevel::Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreLogger::log(ss.str().c_str(), logLevel);
|
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());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,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)
|
||||||
@ -171,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:
|
||||||
{
|
{
|
||||||
@ -187,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,16 +19,4 @@ namespace ix
|
|||||||
|
|
||||||
return hashAddress;
|
return hashAddress;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
uint64_t djb2HashStr(const std::string& data)
|
|
||||||
{
|
|
||||||
uint64_t hashAddress = 5381;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < data.size(); ++i)
|
|
||||||
{
|
|
||||||
hashAddress = ((hashAddress << 5) + hashAddress) + data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashAddress;
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
||||||
|
@ -8,10 +8,8 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
uint64_t djb2Hash(const std::vector<uint8_t>& data);
|
uint64_t djb2Hash(const std::vector<uint8_t>& data);
|
||||||
uint64_t djb2HashStr(const std::string& data);
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
@ -47,8 +46,6 @@ 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)
|
||||||
{
|
{
|
||||||
logInfo("New connection from remote ip " + connectionState->getRemoteIp());
|
|
||||||
|
|
||||||
_connectedClientsCount++;
|
_connectedClientsCount++;
|
||||||
|
|
||||||
while (!_stopHandlingConnections)
|
while (!_stopHandlingConnections)
|
||||||
@ -114,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());
|
||||||
}
|
}
|
||||||
@ -128,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;
|
||||||
}
|
}
|
||||||
@ -145,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);
|
||||||
@ -190,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;
|
||||||
|
|
||||||
@ -206,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;
|
||||||
|
|
||||||
@ -248,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;
|
||||||
|
|
||||||
@ -278,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
|
||||||
@ -50,14 +50,18 @@ namespace ix
|
|||||||
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,67 +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::WebSocket& webSocket,
|
std::shared_ptr<ix::ConnectionState> connectionState) {
|
||||||
const ix::WebSocketMessagePtr& msg) {
|
|
||||||
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
||||||
auto remoteIp = connectionState->getRemoteIp();
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
|
@ -12,8 +12,10 @@ namespace ix
|
|||||||
{
|
{
|
||||||
Bench::Bench(const std::string& description)
|
Bench::Bench(const std::string& description)
|
||||||
: _description(description)
|
: _description(description)
|
||||||
|
, _start(std::chrono::high_resolution_clock::now())
|
||||||
|
, _reported(false)
|
||||||
{
|
{
|
||||||
reset();
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bench::~Bench()
|
Bench::~Bench()
|
||||||
@ -24,38 +26,19 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bench::reset()
|
|
||||||
{
|
|
||||||
_start = std::chrono::high_resolution_clock::now();
|
|
||||||
_reported = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bench::report()
|
void Bench::report()
|
||||||
{
|
{
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
|
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
|
||||||
|
|
||||||
_duration = microseconds.count();
|
_ms = milliseconds.count();
|
||||||
std::cerr << _description << " completed in " << _duration << " us" << std::endl;
|
std::cerr << _description << " completed in " << _ms << "ms" << std::endl;
|
||||||
|
|
||||||
setReported();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bench::record()
|
|
||||||
{
|
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
|
||||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
|
|
||||||
|
|
||||||
_duration = microseconds.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bench::setReported()
|
|
||||||
{
|
|
||||||
_reported = true;
|
_reported = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Bench::getDuration() const
|
uint64_t Bench::getDuration() const
|
||||||
{
|
{
|
||||||
return _duration;
|
return _ms;
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -17,16 +16,13 @@ namespace ix
|
|||||||
Bench(const std::string& description);
|
Bench(const std::string& description);
|
||||||
~Bench();
|
~Bench();
|
||||||
|
|
||||||
void reset();
|
|
||||||
void record();
|
|
||||||
void report();
|
void report();
|
||||||
void setReported();
|
|
||||||
uint64_t getDuration() const;
|
uint64_t getDuration() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _description;
|
std::string _description;
|
||||||
std::chrono::time_point<std::chrono::high_resolution_clock> _start;
|
std::chrono::time_point<std::chrono::high_resolution_clock> _start;
|
||||||
uint64_t _duration;
|
uint64_t _ms;
|
||||||
bool _reported;
|
bool _reported;
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -31,11 +31,6 @@ namespace ix
|
|||||||
return std::make_shared<ConnectionState>();
|
return std::make_shared<ConnectionState>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionState::setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback)
|
|
||||||
{
|
|
||||||
_onSetTerminatedCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConnectionState::isTerminated() const
|
bool ConnectionState::isTerminated() const
|
||||||
{
|
{
|
||||||
return _terminated;
|
return _terminated;
|
||||||
@ -44,30 +39,5 @@ namespace ix
|
|||||||
void ConnectionState::setTerminated()
|
void ConnectionState::setTerminated()
|
||||||
{
|
{
|
||||||
_terminated = true;
|
_terminated = true;
|
||||||
|
|
||||||
if (_onSetTerminatedCallback)
|
|
||||||
{
|
|
||||||
_onSetTerminatedCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& ConnectionState::getRemoteIp()
|
|
||||||
{
|
|
||||||
return _remoteIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ConnectionState::getRemotePort()
|
|
||||||
{
|
|
||||||
return _remotePort;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnectionState::setRemoteIp(const std::string& remoteIp)
|
|
||||||
{
|
|
||||||
_remoteIp = remoteIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnectionState::setRemotePort(int remotePort)
|
|
||||||
{
|
|
||||||
_remotePort = remotePort;
|
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -7,15 +7,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
using OnSetTerminatedCallback = std::function<void()>;
|
|
||||||
|
|
||||||
class ConnectionState
|
class ConnectionState
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -28,27 +25,12 @@ namespace ix
|
|||||||
void setTerminated();
|
void setTerminated();
|
||||||
bool isTerminated() const;
|
bool isTerminated() const;
|
||||||
|
|
||||||
const std::string& getRemoteIp();
|
|
||||||
int getRemotePort();
|
|
||||||
|
|
||||||
static std::shared_ptr<ConnectionState> createConnectionState();
|
static std::shared_ptr<ConnectionState> createConnectionState();
|
||||||
|
|
||||||
private:
|
|
||||||
void setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback);
|
|
||||||
|
|
||||||
void setRemoteIp(const std::string& remoteIp);
|
|
||||||
void setRemotePort(int remotePort);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::atomic<bool> _terminated;
|
std::atomic<bool> _terminated;
|
||||||
std::string _id;
|
std::string _id;
|
||||||
OnSetTerminatedCallback _onSetTerminatedCallback;
|
|
||||||
|
|
||||||
static std::atomic<uint64_t> _globalId;
|
static std::atomic<uint64_t> _globalId;
|
||||||
|
|
||||||
std::string _remoteIp;
|
|
||||||
int _remotePort;
|
|
||||||
|
|
||||||
friend class SocketServer;
|
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // 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"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* IXExponentialBackoff.cpp
|
* IXExponentialBackoff.h
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXGzipCodec.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXGzipCodec.h"
|
|
||||||
|
|
||||||
#include "IXBench.h"
|
|
||||||
#include <array>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
#include <zlib.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_DEFLATE
|
|
||||||
#include <libdeflate.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
std::string gzipCompress(const std::string& str)
|
|
||||||
{
|
|
||||||
#ifdef IXWEBSOCKET_USE_DEFLATE
|
|
||||||
int compressionLevel = 6;
|
|
||||||
struct libdeflate_compressor* compressor;
|
|
||||||
|
|
||||||
compressor = libdeflate_alloc_compressor(compressionLevel);
|
|
||||||
|
|
||||||
const void* uncompressed_data = str.data();
|
|
||||||
size_t uncompressed_size = str.size();
|
|
||||||
void* compressed_data;
|
|
||||||
size_t actual_compressed_size;
|
|
||||||
size_t max_compressed_size;
|
|
||||||
|
|
||||||
max_compressed_size = libdeflate_gzip_compress_bound(compressor, uncompressed_size);
|
|
||||||
compressed_data = malloc(max_compressed_size);
|
|
||||||
|
|
||||||
if (compressed_data == NULL)
|
|
||||||
{
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
actual_compressed_size = libdeflate_gzip_compress(
|
|
||||||
compressor, uncompressed_data, uncompressed_size, compressed_data, max_compressed_size);
|
|
||||||
|
|
||||||
libdeflate_free_compressor(compressor);
|
|
||||||
|
|
||||||
if (actual_compressed_size == 0)
|
|
||||||
{
|
|
||||||
free(compressed_data);
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string out;
|
|
||||||
out.assign(reinterpret_cast<char*>(compressed_data), actual_compressed_size);
|
|
||||||
free(compressed_data);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
#else
|
|
||||||
z_stream zs; // z_stream is zlib's control structure
|
|
||||||
memset(&zs, 0, sizeof(zs));
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_DEFLATE
|
|
||||||
static uint32_t loadDecompressedGzipSize(const uint8_t* p)
|
|
||||||
{
|
|
||||||
return ((uint32_t) p[0] << 0) | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) |
|
|
||||||
((uint32_t) p[3] << 24);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool gzipDecompress(const std::string& in, std::string& out)
|
|
||||||
{
|
|
||||||
#ifdef IXWEBSOCKET_USE_DEFLATE
|
|
||||||
struct libdeflate_decompressor* decompressor;
|
|
||||||
decompressor = libdeflate_alloc_decompressor();
|
|
||||||
|
|
||||||
const void* compressed_data = in.data();
|
|
||||||
size_t compressed_size = in.size();
|
|
||||||
|
|
||||||
// Retrieve uncompressed size from the trailer of the gziped data
|
|
||||||
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&in.front());
|
|
||||||
auto uncompressed_size = loadDecompressedGzipSize(&ptr[compressed_size - 4]);
|
|
||||||
|
|
||||||
// Use it to redimension our output buffer
|
|
||||||
out.resize(uncompressed_size);
|
|
||||||
|
|
||||||
libdeflate_result result = libdeflate_gzip_decompress(
|
|
||||||
decompressor, compressed_data, compressed_size, &out.front(), uncompressed_size, NULL);
|
|
||||||
|
|
||||||
libdeflate_free_decompressor(decompressor);
|
|
||||||
return result == LIBDEFLATE_SUCCESS;
|
|
||||||
#else
|
|
||||||
z_stream inflateState;
|
|
||||||
memset(&inflateState, 0, sizeof(inflateState));
|
|
||||||
|
|
||||||
inflateState.zalloc = Z_NULL;
|
|
||||||
inflateState.zfree = Z_NULL;
|
|
||||||
inflateState.opaque = Z_NULL;
|
|
||||||
inflateState.avail_in = 0;
|
|
||||||
inflateState.next_in = Z_NULL;
|
|
||||||
|
|
||||||
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inflateState.avail_in = (uInt) in.size();
|
|
||||||
inflateState.next_in = (unsigned char*) (const_cast<char*>(in.data()));
|
|
||||||
|
|
||||||
const int kBufferSize = 1 << 14;
|
|
||||||
std::array<unsigned char, kBufferSize> compressBuffer;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
inflateState.avail_out = (uInt) kBufferSize;
|
|
||||||
inflateState.next_out = &compressBuffer.front();
|
|
||||||
|
|
||||||
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
|
|
||||||
|
|
||||||
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
|
|
||||||
{
|
|
||||||
inflateEnd(&inflateState);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.append(reinterpret_cast<char*>(&compressBuffer.front()),
|
|
||||||
kBufferSize - inflateState.avail_out);
|
|
||||||
} while (inflateState.avail_out == 0);
|
|
||||||
|
|
||||||
inflateEnd(&inflateState);
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
} // namespace ix
|
|
@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXGzipCodec.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
std::string gzipCompress(const std::string& str);
|
|
||||||
bool gzipDecompress(const std::string& in, std::string& out);
|
|
||||||
} // namespace ix
|
|
@ -7,7 +7,6 @@
|
|||||||
#include "IXHttp.h"
|
#include "IXHttp.h"
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
#include "IXGzipCodec.h"
|
|
||||||
#include "IXSocket.h"
|
#include "IXSocket.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -94,12 +93,14 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(
|
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(
|
||||||
std::unique_ptr<Socket>& socket, int timeoutSecs)
|
std::unique_ptr<Socket>& socket)
|
||||||
{
|
{
|
||||||
HttpRequestPtr httpRequest;
|
HttpRequestPtr httpRequest;
|
||||||
|
|
||||||
std::atomic<bool> requestInitCancellation(false);
|
std::atomic<bool> requestInitCancellation(false);
|
||||||
|
|
||||||
|
int timeoutSecs = 5; // FIXME
|
||||||
|
|
||||||
auto isCancellationRequested =
|
auto isCancellationRequested =
|
||||||
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
|
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
|
||||||
|
|
||||||
@ -129,53 +130,7 @@ namespace ix
|
|||||||
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
|
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string body;
|
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, headers);
|
||||||
if (headers.find("Content-Length") != headers.end())
|
|
||||||
{
|
|
||||||
int contentLength = 0;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
contentLength = std::stoi(headers["Content-Length"]);
|
|
||||||
}
|
|
||||||
catch (std::exception)
|
|
||||||
{
|
|
||||||
return std::make_tuple(
|
|
||||||
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentLength < 0)
|
|
||||||
{
|
|
||||||
return std::make_tuple(
|
|
||||||
false, "Error: 'Content-Length' should be a positive integer", httpRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto res = socket->readBytes(contentLength, nullptr, isCancellationRequested);
|
|
||||||
if (!res.first)
|
|
||||||
{
|
|
||||||
return std::make_tuple(
|
|
||||||
false, std::string("Error reading request: ") + res.second, httpRequest);
|
|
||||||
}
|
|
||||||
body = res.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the content was compressed with gzip, decode it
|
|
||||||
if (headers["Content-Encoding"] == "gzip")
|
|
||||||
{
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
std::string decompressedPayload;
|
|
||||||
if (!gzipDecompress(body, decompressedPayload))
|
|
||||||
{
|
|
||||||
return std::make_tuple(
|
|
||||||
false, std::string("Error during gzip decompression of the body"), httpRequest);
|
|
||||||
}
|
|
||||||
body = decompressedPayload;
|
|
||||||
#else
|
|
||||||
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
|
|
||||||
return std::make_tuple(false, errorMsg, httpRequest);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, body, headers);
|
|
||||||
return std::make_tuple(true, "", httpRequest);
|
return std::make_tuple(true, "", httpRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +151,7 @@ namespace ix
|
|||||||
|
|
||||||
// Write headers
|
// Write headers
|
||||||
ss.str("");
|
ss.str("");
|
||||||
ss << "Content-Length: " << response->body.size() << "\r\n";
|
ss << "Content-Length: " << response->payload.size() << "\r\n";
|
||||||
for (auto&& it : response->headers)
|
for (auto&& it : response->headers)
|
||||||
{
|
{
|
||||||
ss << it.first << ": " << it.second << "\r\n";
|
ss << it.first << ": " << it.second << "\r\n";
|
||||||
@ -208,6 +163,6 @@ namespace ix
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response->body.empty() ? true : socket->writeBytes(response->body, nullptr);
|
return response->payload.empty() ? true : socket->writeBytes(response->payload, nullptr);
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -39,7 +39,7 @@ namespace ix
|
|||||||
std::string description;
|
std::string description;
|
||||||
HttpErrorCode errorCode;
|
HttpErrorCode errorCode;
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
std::string body;
|
std::string payload;
|
||||||
std::string errorMsg;
|
std::string errorMsg;
|
||||||
uint64_t uploadSize;
|
uint64_t uploadSize;
|
||||||
uint64_t downloadSize;
|
uint64_t downloadSize;
|
||||||
@ -48,7 +48,7 @@ namespace ix
|
|||||||
const std::string& des = std::string(),
|
const std::string& des = std::string(),
|
||||||
const HttpErrorCode& c = HttpErrorCode::Ok,
|
const HttpErrorCode& c = HttpErrorCode::Ok,
|
||||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
||||||
const std::string& b = std::string(),
|
const std::string& p = std::string(),
|
||||||
const std::string& e = std::string(),
|
const std::string& e = std::string(),
|
||||||
uint64_t u = 0,
|
uint64_t u = 0,
|
||||||
uint64_t d = 0)
|
uint64_t d = 0)
|
||||||
@ -56,7 +56,7 @@ namespace ix
|
|||||||
, description(des)
|
, description(des)
|
||||||
, errorCode(c)
|
, errorCode(c)
|
||||||
, headers(h)
|
, headers(h)
|
||||||
, body(b)
|
, payload(p)
|
||||||
, errorMsg(e)
|
, errorMsg(e)
|
||||||
, uploadSize(u)
|
, uploadSize(u)
|
||||||
, downloadSize(d)
|
, downloadSize(d)
|
||||||
@ -78,13 +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;
|
||||||
bool compressRequest = false;
|
|
||||||
Logger logger;
|
Logger logger;
|
||||||
OnProgressCallback onProgressCallback;
|
OnProgressCallback onProgressCallback;
|
||||||
};
|
};
|
||||||
@ -96,18 +95,15 @@ namespace ix
|
|||||||
std::string uri;
|
std::string uri;
|
||||||
std::string method;
|
std::string method;
|
||||||
std::string version;
|
std::string version;
|
||||||
std::string body;
|
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
|
|
||||||
HttpRequest(const std::string& u,
|
HttpRequest(const std::string& u,
|
||||||
const std::string& m,
|
const std::string& m,
|
||||||
const std::string& v,
|
const std::string& v,
|
||||||
const std::string& b,
|
|
||||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
|
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
|
||||||
: uri(u)
|
: uri(u)
|
||||||
, method(m)
|
, method(m)
|
||||||
, version(v)
|
, version(v)
|
||||||
, body(b)
|
|
||||||
, headers(h)
|
, headers(h)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -119,7 +115,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(
|
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(
|
||||||
std::unique_ptr<Socket>& socket, int timeoutSecs);
|
std::unique_ptr<Socket>& socket);
|
||||||
static bool sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket);
|
static bool sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket);
|
||||||
|
|
||||||
static std::pair<std::string, int> parseStatusLine(const std::string& line);
|
static std::pair<std::string, int> parseStatusLine(const std::string& line);
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include "IXHttpClient.h"
|
#include "IXHttpClient.h"
|
||||||
|
|
||||||
#include "IXGzipCodec.h"
|
|
||||||
#include "IXSocketFactory.h"
|
#include "IXSocketFactory.h"
|
||||||
#include "IXUrlParser.h"
|
#include "IXUrlParser.h"
|
||||||
#include "IXUserAgent.h"
|
#include "IXUserAgent.h"
|
||||||
@ -17,6 +16,7 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -25,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;
|
||||||
|
|
||||||
@ -51,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>();
|
||||||
@ -127,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;
|
||||||
@ -174,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)
|
||||||
@ -201,17 +192,8 @@ 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)
|
||||||
{
|
{
|
||||||
// Set request compression header
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
if (args->compressRequest)
|
|
||||||
{
|
|
||||||
ss << "Content-Encoding: gzip"
|
|
||||||
<< "\r\n";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ss << "Content-Length: " << body.size() << "\r\n";
|
ss << "Content-Length: " << body.size() << "\r\n";
|
||||||
|
|
||||||
// Set default Content-Type if unspecified
|
// Set default Content-Type if unspecified
|
||||||
@ -238,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)
|
||||||
@ -259,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)
|
||||||
{
|
{
|
||||||
@ -509,9 +493,8 @@ namespace ix
|
|||||||
// 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")
|
||||||
{
|
{
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
std::string decompressedPayload;
|
std::string decompressedPayload;
|
||||||
if (!gzipDecompress(payload, decompressedPayload))
|
if (!gzipInflate(payload, decompressedPayload))
|
||||||
{
|
{
|
||||||
std::string errorMsg("Error decompressing payload");
|
std::string errorMsg("Error decompressing payload");
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
@ -524,17 +507,6 @@ namespace ix
|
|||||||
downloadSize);
|
downloadSize);
|
||||||
}
|
}
|
||||||
payload = decompressedPayload;
|
payload = decompressedPayload;
|
||||||
#else
|
|
||||||
std::string errorMsg("ixwebsocket was not compiled with gzip support on");
|
|
||||||
return std::make_shared<HttpResponse>(code,
|
|
||||||
description,
|
|
||||||
HttpErrorCode::Gzip,
|
|
||||||
headers,
|
|
||||||
payload,
|
|
||||||
errorMsg,
|
|
||||||
uploadSize,
|
|
||||||
downloadSize);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
@ -562,42 +534,11 @@ namespace ix
|
|||||||
return request(url, kDel, std::string(), args);
|
return request(url, kDel, std::string(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::request(const std::string& url,
|
|
||||||
const std::string& verb,
|
|
||||||
const HttpParameters& httpParameters,
|
|
||||||
const HttpFormDataParameters& httpFormDataParameters,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
std::string body;
|
|
||||||
|
|
||||||
if (httpFormDataParameters.empty())
|
|
||||||
{
|
|
||||||
body = serializeHttpParameters(httpParameters);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string multipartBoundary = generateMultipartBoundary();
|
|
||||||
args->multipartBoundary = multipartBoundary;
|
|
||||||
body = serializeHttpFormDataParameters(
|
|
||||||
multipartBoundary, httpFormDataParameters, httpParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
|
||||||
if (args->compressRequest)
|
|
||||||
{
|
|
||||||
body = gzipCompress(body);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return request(url, verb, body, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::post(const std::string& url,
|
HttpResponsePtr HttpClient::post(const std::string& url,
|
||||||
const HttpParameters& httpParameters,
|
const HttpParameters& httpParameters,
|
||||||
const HttpFormDataParameters& httpFormDataParameters,
|
|
||||||
HttpRequestArgsPtr args)
|
HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
return request(url, kPost, httpParameters, httpFormDataParameters, args);
|
return request(url, kPost, serializeHttpParameters(httpParameters), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::post(const std::string& url,
|
HttpResponsePtr HttpClient::post(const std::string& url,
|
||||||
@ -609,10 +550,9 @@ namespace ix
|
|||||||
|
|
||||||
HttpResponsePtr HttpClient::put(const std::string& url,
|
HttpResponsePtr HttpClient::put(const std::string& url,
|
||||||
const HttpParameters& httpParameters,
|
const HttpParameters& httpParameters,
|
||||||
const HttpFormDataParameters& httpFormDataParameters,
|
|
||||||
HttpRequestArgsPtr args)
|
HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
return request(url, kPut, httpParameters, httpFormDataParameters, args);
|
return request(url, kPut, serializeHttpParameters(httpParameters), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::put(const std::string& url,
|
HttpResponsePtr HttpClient::put(const std::string& url,
|
||||||
@ -622,21 +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,
|
|
||||||
const HttpFormDataParameters& httpFormDataParameters,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
return request(url, kPatch, httpParameters, httpFormDataParameters, 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;
|
||||||
@ -728,6 +653,51 @@ namespace ix
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
|
||||||
|
{
|
||||||
|
z_stream inflateState;
|
||||||
|
std::memset(&inflateState, 0, sizeof(inflateState));
|
||||||
|
|
||||||
|
inflateState.zalloc = Z_NULL;
|
||||||
|
inflateState.zfree = Z_NULL;
|
||||||
|
inflateState.opaque = Z_NULL;
|
||||||
|
inflateState.avail_in = 0;
|
||||||
|
inflateState.next_in = Z_NULL;
|
||||||
|
|
||||||
|
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inflateState.avail_in = (uInt) in.size();
|
||||||
|
inflateState.next_in = (unsigned char*) (const_cast<char*>(in.data()));
|
||||||
|
|
||||||
|
const int kBufferSize = 1 << 14;
|
||||||
|
|
||||||
|
std::unique_ptr<unsigned char[]> compressBuffer =
|
||||||
|
std::make_unique<unsigned char[]>(kBufferSize);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inflateState.avail_out = (uInt) kBufferSize;
|
||||||
|
inflateState.next_out = compressBuffer.get();
|
||||||
|
|
||||||
|
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
|
||||||
|
|
||||||
|
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
|
||||||
|
{
|
||||||
|
inflateEnd(&inflateState);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.append(reinterpret_cast<char*>(compressBuffer.get()),
|
||||||
|
kBufferSize - inflateState.avail_out);
|
||||||
|
} while (inflateState.avail_out == 0);
|
||||||
|
|
||||||
|
inflateEnd(&inflateState);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
if (args->logger)
|
if (args->logger)
|
||||||
|
@ -34,7 +34,6 @@ namespace ix
|
|||||||
|
|
||||||
HttpResponsePtr post(const std::string& url,
|
HttpResponsePtr post(const std::string& url,
|
||||||
const HttpParameters& httpParameters,
|
const HttpParameters& httpParameters,
|
||||||
const HttpFormDataParameters& httpFormDataParameters,
|
|
||||||
HttpRequestArgsPtr args);
|
HttpRequestArgsPtr args);
|
||||||
HttpResponsePtr post(const std::string& url,
|
HttpResponsePtr post(const std::string& url,
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
@ -42,34 +41,17 @@ namespace ix
|
|||||||
|
|
||||||
HttpResponsePtr put(const std::string& url,
|
HttpResponsePtr put(const std::string& url,
|
||||||
const HttpParameters& httpParameters,
|
const HttpParameters& httpParameters,
|
||||||
const HttpFormDataParameters& httpFormDataParameters,
|
|
||||||
HttpRequestArgsPtr args);
|
HttpRequestArgsPtr args);
|
||||||
HttpResponsePtr put(const std::string& url,
|
HttpResponsePtr put(const std::string& url,
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
HttpRequestArgsPtr args);
|
HttpRequestArgsPtr args);
|
||||||
|
|
||||||
HttpResponsePtr patch(const std::string& url,
|
|
||||||
const HttpParameters& httpParameters,
|
|
||||||
const HttpFormDataParameters& httpFormDataParameters,
|
|
||||||
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);
|
||||||
|
|
||||||
HttpResponsePtr request(const std::string& url,
|
|
||||||
const std::string& verb,
|
|
||||||
const HttpParameters& httpParameters,
|
|
||||||
const HttpFormDataParameters& httpFormDataParameters,
|
|
||||||
HttpRequestArgsPtr args);
|
|
||||||
|
|
||||||
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);
|
||||||
@ -96,13 +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);
|
||||||
|
|
||||||
|
bool gzipInflate(const std::string& in, std::string& out);
|
||||||
|
|
||||||
// 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;
|
||||||
@ -112,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
|
||||||
|
@ -6,11 +6,9 @@
|
|||||||
|
|
||||||
#include "IXHttpServer.h"
|
#include "IXHttpServer.h"
|
||||||
|
|
||||||
#include "IXGzipCodec.h"
|
|
||||||
#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>
|
||||||
@ -44,17 +42,10 @@ namespace
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
const int HttpServer::kDefaultTimeoutSecs(30);
|
HttpServer::HttpServer(
|
||||||
|
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
||||||
HttpServer::HttpServer(int port,
|
|
||||||
const std::string& host,
|
|
||||||
int backlog,
|
|
||||||
size_t maxConnections,
|
|
||||||
int addressFamily,
|
|
||||||
int timeoutSecs)
|
|
||||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||||
, _connectedClientsCount(0)
|
, _connectedClientsCount(0)
|
||||||
, _timeoutSecs(timeoutSecs)
|
|
||||||
{
|
{
|
||||||
setDefaultConnectionCallback();
|
setDefaultConnectionCallback();
|
||||||
}
|
}
|
||||||
@ -83,7 +74,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_connectedClientsCount++;
|
_connectedClientsCount++;
|
||||||
|
|
||||||
auto ret = Http::parseRequest(socket, _timeoutSecs);
|
auto ret = Http::parseRequest(socket);
|
||||||
// FIXME: handle errors in parseRequest
|
// FIXME: handle errors in parseRequest
|
||||||
|
|
||||||
if (std::get<0>(ret))
|
if (std::get<0>(ret))
|
||||||
@ -108,7 +99,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this](HttpRequestPtr request,
|
[this](HttpRequestPtr request,
|
||||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr {
|
||||||
std::string uri(request->uri);
|
std::string uri(request->uri);
|
||||||
if (uri.empty() || uri == "/")
|
if (uri.empty() || uri == "/")
|
||||||
{
|
{
|
||||||
@ -129,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 << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
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());
|
||||||
|
|
||||||
@ -167,14 +148,13 @@ namespace ix
|
|||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this,
|
[this,
|
||||||
redirectUrl](HttpRequestPtr request,
|
redirectUrl](HttpRequestPtr request,
|
||||||
std::shared_ptr<ConnectionState> connectionState) -> 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 << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
ss << request->method << " " << request->headers["User-Agent"] << " "
|
||||||
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
|
||||||
<< request->uri;
|
<< request->uri;
|
||||||
logInfo(ss.str());
|
logInfo(ss.str());
|
||||||
|
|
||||||
@ -190,40 +170,4 @@ namespace ix
|
|||||||
301, "OK", HttpErrorCode::Ok, headers, std::string());
|
301, "OK", HttpErrorCode::Ok, headers, std::string());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Display the client parameter and body on the console
|
|
||||||
//
|
|
||||||
void HttpServer::makeDebugServer()
|
|
||||||
{
|
|
||||||
setOnConnectionCallback(
|
|
||||||
[this](HttpRequestPtr request,
|
|
||||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
headers["Server"] = userAgent();
|
|
||||||
|
|
||||||
// Log request
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
|
||||||
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
|
||||||
<< request->uri;
|
|
||||||
logInfo(ss.str());
|
|
||||||
|
|
||||||
logInfo("== Headers == ");
|
|
||||||
for (auto&& it : request->headers)
|
|
||||||
{
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << it.first << ": " << it.second;
|
|
||||||
logInfo(oss.str());
|
|
||||||
}
|
|
||||||
logInfo("");
|
|
||||||
|
|
||||||
logInfo("== Body == ");
|
|
||||||
logInfo(request->body);
|
|
||||||
logInfo("");
|
|
||||||
|
|
||||||
return std::make_shared<HttpResponse>(
|
|
||||||
200, "OK", HttpErrorCode::Ok, headers, std::string("OK"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -29,8 +29,7 @@ namespace ix
|
|||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||||
int addressFamily = SocketServer::kDefaultAddressFamily,
|
int addressFamily = SocketServer::kDefaultAddressFamily);
|
||||||
int timeoutSecs = HttpServer::kDefaultTimeoutSecs);
|
|
||||||
virtual ~HttpServer();
|
virtual ~HttpServer();
|
||||||
virtual void stop() final;
|
virtual void stop() final;
|
||||||
|
|
||||||
@ -38,16 +37,11 @@ namespace ix
|
|||||||
|
|
||||||
void makeRedirectServer(const std::string& redirectUrl);
|
void makeRedirectServer(const std::string& redirectUrl);
|
||||||
|
|
||||||
void makeDebugServer();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Member variables
|
// Member variables
|
||||||
OnConnectionCallback _onConnectionCallback;
|
OnConnectionCallback _onConnectionCallback;
|
||||||
std::atomic<int> _connectedClientsCount;
|
std::atomic<int> _connectedClientsCount;
|
||||||
|
|
||||||
const static int kDefaultTimeoutSecs;
|
|
||||||
int _timeoutSecs;
|
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||||
std::shared_ptr<ConnectionState> connectionState) final;
|
std::shared_ptr<ConnectionState> connectionState) final;
|
||||||
|
@ -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>
|
||||||
|
@ -8,9 +8,6 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
const uint64_t SelectInterrupt::kSendRequest = 1;
|
|
||||||
const uint64_t SelectInterrupt::kCloseRequest = 2;
|
|
||||||
|
|
||||||
SelectInterrupt::SelectInterrupt()
|
SelectInterrupt::SelectInterrupt()
|
||||||
{
|
{
|
||||||
;
|
;
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -24,11 +23,5 @@ namespace ix
|
|||||||
virtual bool clear();
|
virtual bool clear();
|
||||||
virtual uint64_t read();
|
virtual uint64_t read();
|
||||||
virtual int getFd() const;
|
virtual int getFd() const;
|
||||||
|
|
||||||
// Used as special codes for pipe communication
|
|
||||||
static const uint64_t kSendRequest;
|
|
||||||
static const uint64_t kCloseRequest;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using SelectInterruptPtr = std::unique_ptr<SelectInterrupt>;
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
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
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user