Compare commits
89 Commits
feature/zl
...
v11.0.2
Author | SHA1 | Date | |
---|---|---|---|
80432edbd0 | |||
23606b45c7 | |||
2aac0afca3 | |||
508d8c7253 | |||
8f5134528b | |||
738c6040f7 | |||
1350e9b307 | |||
4e2a40e031 | |||
594d2e194a | |||
977a1ed7e1 | |||
8b3789af56 | |||
f60485d9c2 | |||
b05b124cb3 | |||
723c208f22 | |||
21758f1183 | |||
422febf15d | |||
51ec32405d | |||
6a90dc7259 | |||
262f32857f | |||
91fb3992ac | |||
e8b12feaeb | |||
730fbc5b31 | |||
d0562664ad | |||
d9b4beff8b | |||
b2f21840c6 | |||
67cb48537a | |||
fa0408e70b | |||
032ed9af9c | |||
dc84080401 | |||
82e759732b | |||
61dbcc2b84 | |||
e61680ff0f | |||
6f188a5131 | |||
6077f86af8 | |||
93167e3917 | |||
2526a94454 | |||
97cc543e53 | |||
62d220f49a | |||
49995e32f0 | |||
d525c28907 | |||
39c84c7d51 | |||
128bc0afa9 | |||
b04e5c5529 | |||
1e8c421d66 | |||
72d6651ded | |||
a4e5d1b47a | |||
9f51a54a83 | |||
b74f7319c6 | |||
0ad66a27f2 | |||
a40003e85a | |||
5534a7fdf9 | |||
efb245278d | |||
5896d3740f | |||
73b9c0b89b | |||
629c155044 | |||
08640d877f | |||
ed5c63144e | |||
ee69aed2b0 | |||
fcb92f862d | |||
e8e98e667d | |||
e1502017ce | |||
72472f2899 | |||
42f71364ca | |||
3dabd3a556 | |||
0498e2fa98 | |||
2aaf59651e | |||
cd4e51eacf | |||
785842de03 | |||
261095fa12 | |||
ed2ed0f7ae | |||
7ad5ead0f6 | |||
a8284e64e3 | |||
5423a31d5a | |||
53575f8d90 | |||
d3bcbdac26 | |||
8c5b28adce | |||
dcbafae35a | |||
eb197edcec | |||
b8265bf7f2 | |||
e7c4f0b171 | |||
12f36b61ff | |||
b15c4189f5 | |||
74d3278258 | |||
831152b906 | |||
7c81a98632 | |||
6e47c62c06 | |||
bcae7f326d | |||
d719c41e31 | |||
6f0307fb35 |
66
.github/workflows/docker.yml
vendored
Normal file
66
.github/workflows/docker.yml
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
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
|
14
.github/workflows/unittest_linux.yml
vendored
Normal file
14
.github/workflows/unittest_linux.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: linux
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
- name: make test
|
||||
run: make test
|
14
.github/workflows/unittest_linux_asan.yml
vendored
Normal file
14
.github/workflows/unittest_linux_asan.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: linux_asan
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
- name: make test_asan
|
||||
run: make test_asan
|
16
.github/workflows/unittest_mac_tsan_mbedtls.yml
vendored
Normal file
16
.github/workflows/unittest_mac_tsan_mbedtls.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: mac_tsan_mbedtls
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
mac_tsan_mbedtls:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
- name: install mbedtls
|
||||
run: brew install mbedtls
|
||||
- name: make test
|
||||
run: make test_tsan_mbedtls
|
16
.github/workflows/unittest_mac_tsan_openssl.yml
vendored
Normal file
16
.github/workflows/unittest_mac_tsan_openssl.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: mac_tsan_openssl
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
mac_tsan_openssl:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
- name: install openssl
|
||||
run: brew install openssl@1.1
|
||||
- name: make test
|
||||
run: make test_tsan_openssl
|
14
.github/workflows/unittest_mac_tsan_sectransport.yml
vendored
Normal file
14
.github/workflows/unittest_mac_tsan_sectransport.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
name: mac_tsan_sectransport
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
mac_tsan_sectransport:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
- name: make test_tsan_sectransport
|
||||
run: make test_tsan_sectransport
|
38
.github/workflows/unittest_uwp.yml
vendored
Normal file
38
.github/workflows/unittest_uwp.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: uwp
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
uwp:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||
- run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_CXX_COMPILER=cl.exe -DUSE_TEST=1 -DUSE_ZLIB=0 ..
|
||||
- run: cmake --build build
|
||||
|
||||
#
|
||||
# Windows with OpenSSL is working but disabled as it takes 13 minutes (10 for openssl) to build with vcpkg
|
||||
#
|
||||
# windows_openssl:
|
||||
# runs-on: windows-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# - uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||
# - run: |
|
||||
# vcpkg install zlib:x64-windows
|
||||
# vcpkg install openssl:x64-windows
|
||||
# - run: |
|
||||
# mkdir build
|
||||
# cd build
|
||||
# cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_OPEN_SSL=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 ..
|
||||
# - run: cmake --build build
|
||||
#
|
||||
# # Running the unittest does not work, the binary cannot be found
|
||||
# #- run: ../build/test/ixwebsocket_unittest.exe
|
||||
# # working-directory: test
|
5
.github/workflows/unittest_windows.yml
vendored
5
.github/workflows/unittest_windows.yml
vendored
@ -15,5 +15,6 @@ jobs:
|
||||
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
|
||||
|
||||
#- run: ../build/test/ixwebsocket_unittest.exe
|
||||
# working-directory: test
|
||||
|
19
CMake/FindDeflate.cmake
Normal file
19
CMake/FindDeflate.cmake
Normal file
@ -0,0 +1,19 @@
|
||||
# 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})
|
@ -8,7 +8,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
|
||||
|
||||
project(ixwebsocket C CXX)
|
||||
|
||||
set (CMAKE_CXX_STANDARD 14)
|
||||
set (CMAKE_CXX_STANDARD 11)
|
||||
set (CXX_STANDARD_REQUIRED ON)
|
||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
@ -31,6 +31,7 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXDNSLookup.cpp
|
||||
ixwebsocket/IXExponentialBackoff.cpp
|
||||
ixwebsocket/IXGetFreePort.cpp
|
||||
ixwebsocket/IXGzipCodec.cpp
|
||||
ixwebsocket/IXHttp.cpp
|
||||
ixwebsocket/IXHttpClient.cpp
|
||||
ixwebsocket/IXHttpServer.cpp
|
||||
@ -44,6 +45,7 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXSocketFactory.cpp
|
||||
ixwebsocket/IXSocketServer.cpp
|
||||
ixwebsocket/IXSocketTLSOptions.cpp
|
||||
ixwebsocket/IXStrCaseCompare.cpp
|
||||
ixwebsocket/IXUdpSocket.cpp
|
||||
ixwebsocket/IXUrlParser.cpp
|
||||
ixwebsocket/IXUserAgent.cpp
|
||||
@ -62,11 +64,11 @@ set( IXWEBSOCKET_SOURCES
|
||||
set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXBench.h
|
||||
ixwebsocket/IXCancellationRequest.h
|
||||
ixwebsocket/IXConnectionInfo.h
|
||||
ixwebsocket/IXConnectionState.h
|
||||
ixwebsocket/IXDNSLookup.h
|
||||
ixwebsocket/IXExponentialBackoff.h
|
||||
ixwebsocket/IXGetFreePort.h
|
||||
ixwebsocket/IXGzipCodec.h
|
||||
ixwebsocket/IXHttp.h
|
||||
ixwebsocket/IXHttpClient.h
|
||||
ixwebsocket/IXHttpServer.h
|
||||
@ -81,6 +83,7 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXSocketFactory.h
|
||||
ixwebsocket/IXSocketServer.h
|
||||
ixwebsocket/IXSocketTLSOptions.h
|
||||
ixwebsocket/IXStrCaseCompare.h
|
||||
ixwebsocket/IXUdpSocket.h
|
||||
ixwebsocket/IXUrlParser.h
|
||||
ixwebsocket/IXUtf8Validator.h
|
||||
@ -194,13 +197,19 @@ 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)
|
||||
if (ZLIB_FOUND)
|
||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
|
||||
find_package(ZLIB REQUIRED)
|
||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
|
||||
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
|
||||
endif()
|
||||
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()
|
||||
|
||||
if (WIN32)
|
||||
@ -254,12 +263,19 @@ if (USE_WS OR USE_TEST)
|
||||
add_subdirectory(ixsentry)
|
||||
add_subdirectory(ixbots)
|
||||
|
||||
add_subdirectory(third_party/spdlog spdlog)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(spdlog
|
||||
GIT_REPOSITORY "https://github.com/gabime/spdlog"
|
||||
GIT_TAG "v1.8.0"
|
||||
GIT_SHALLOW 1)
|
||||
|
||||
FetchContent_MakeAvailable(spdlog)
|
||||
|
||||
if (USE_WS)
|
||||
add_subdirectory(ws)
|
||||
add_subdirectory(ws)
|
||||
endif()
|
||||
if (USE_TEST)
|
||||
add_subdirectory(test)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
endif()
|
||||
|
21
README.md
21
README.md
@ -2,7 +2,9 @@
|
||||
|
||||
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
|
||||
|
||||
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
|
||||
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Note that the MinGW compiler is not supported at this point. Two important design goals are simplicity and correctness.
|
||||
|
||||
A bad security bug affecting users compiling with SSL enabled and OpenSSL as the backend was just fixed in newly released version 11.0.0. Please upgrade ! (more details in the [https://github.com/machinezone/IXWebSocket/pull/250](PR).
|
||||
|
||||
```cpp
|
||||
/*
|
||||
@ -84,23 +86,26 @@ If your company or project is using this library, feel free to open an issue or
|
||||
- [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] |
|
||||
| Linux | OpenSSL | None | [![Build2][1]][0] |
|
||||
| macOS | Secure Transport | Thread Sanitizer | [![Build2][2]][0] |
|
||||
| macOS | OpenSSL | Thread Sanitizer | [![Build2][3]][0] |
|
||||
| macOS | MbedTLS | Thread Sanitizer | [![Build2][4]][0] |
|
||||
| Windows | Disabled | None | [![Build2][5]][0] |
|
||||
| UWP | Disabled | None | [![Build2][6]][0] |
|
||||
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] |
|
||||
|
||||
[0]: https://github.com/machinezone/IXWebSocket
|
||||
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
|
||||
[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg
|
||||
[3]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg
|
||||
[4]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg
|
||||
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
|
||||
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
|
||||
[7]: https://github.com/machinezone/IXWebSocket
|
||||
[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg
|
||||
|
||||
|
@ -1,67 +1,11 @@
|
||||
version: "3"
|
||||
version: "3.3"
|
||||
services:
|
||||
# snake:
|
||||
# image: bsergean/ws:build
|
||||
# entrypoint: ws snake --port 8767 --host 0.0.0.0 --redis_hosts redis1
|
||||
# ports:
|
||||
# - "8767:8767"
|
||||
# networks:
|
||||
# - ws-net
|
||||
# depends_on:
|
||||
# - redis1
|
||||
push:
|
||||
entrypoint: ws push_server --host 0.0.0.0
|
||||
image: ${DOCKER_REPO}/ws:build
|
||||
|
||||
# proxy:
|
||||
# image: bsergean/ws:build
|
||||
# entrypoint: strace ws proxy_server --remote_host 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765 -v
|
||||
# ports:
|
||||
# - "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:
|
||||
autoroute:
|
||||
entrypoint: ws autoroute ws://push:8008
|
||||
image: ${DOCKER_REPO}/ws:build
|
||||
depends_on:
|
||||
- push
|
||||
|
@ -2,7 +2,7 @@ FROM alpine:3.12 as build
|
||||
|
||||
RUN apk add --no-cache \
|
||||
gcc g++ musl-dev linux-headers \
|
||||
cmake mbedtls-dev make zlib-dev python3-dev ninja
|
||||
cmake mbedtls-dev make zlib-dev python3-dev ninja git
|
||||
|
||||
RUN addgroup -S app && \
|
||||
adduser -S -G app app && \
|
||||
@ -20,7 +20,7 @@ RUN make ws_mbedtls_install && \
|
||||
|
||||
FROM alpine:3.12 as runtime
|
||||
|
||||
RUN apk add --no-cache libstdc++ mbedtls ca-certificates python3 && \
|
||||
RUN apk add --no-cache libstdc++ mbedtls ca-certificates python3 strace && \
|
||||
addgroup -S app && \
|
||||
adduser -S -G app app
|
||||
|
||||
|
26
docker/Dockerfile.centos7
Normal file
26
docker/Dockerfile.centos7
Normal file
@ -0,0 +1,26 @@
|
||||
FROM centos:7 as build
|
||||
|
||||
RUN yum install -y gcc-c++ make zlib-devel openssl-devel redhat-rpm-config
|
||||
|
||||
RUN groupadd app && useradd -g app app
|
||||
RUN chown -R app:app /opt
|
||||
RUN chown -R app:app /usr/local
|
||||
|
||||
WORKDIR /tmp
|
||||
RUN curl -O https://cmake.org/files/v3.14/cmake-3.14.0-Linux-x86_64.tar.gz
|
||||
RUN tar zxvf cmake-3.14.0-Linux-x86_64.tar.gz
|
||||
RUN cp -rf cmake-3.14.0-Linux-x86_64/* /usr/
|
||||
|
||||
RUN yum install -y git
|
||||
|
||||
# There is a bug in CMake where we cannot build from the root top folder
|
||||
# So we build from /opt
|
||||
COPY --chown=app:app . /opt
|
||||
WORKDIR /opt
|
||||
|
||||
USER app
|
||||
RUN [ "make", "ws_no_python" ]
|
||||
RUN [ "rm", "-rf", "build" ]
|
||||
|
||||
ENTRYPOINT ["ws"]
|
||||
CMD ["--help"]
|
27
docker/Dockerfile.ubuntu_precise
Normal file
27
docker/Dockerfile.ubuntu_precise
Normal file
@ -0,0 +1,27 @@
|
||||
# Build time
|
||||
FROM ubuntu:precise as build
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install wget
|
||||
RUN mkdir -p /tmp/cmake
|
||||
WORKDIR /tmp/cmake
|
||||
RUN wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
||||
|
||||
RUN apt-get -y install g++
|
||||
RUN apt-get -y install libssl-dev
|
||||
RUN apt-get -y install libz-dev
|
||||
RUN apt-get -y install make
|
||||
RUN apt-get -y install python
|
||||
RUN apt-get -y install git
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||
|
||||
RUN ["make", "ws_no_python"]
|
||||
|
||||
ENTRYPOINT ["ws"]
|
||||
CMD ["--help"]
|
22
docker/Dockerfile.ubuntu_trusty
Normal file
22
docker/Dockerfile.ubuntu_trusty
Normal file
@ -0,0 +1,22 @@
|
||||
# Build time
|
||||
FROM ubuntu:trusty as build
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install wget
|
||||
RUN mkdir -p /tmp/cmake
|
||||
WORKDIR /tmp/cmake
|
||||
RUN wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
||||
|
||||
RUN apt-get -y install g++ libssl-dev libz-dev make python git
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||
|
||||
RUN ["make", "ws_no_python"]
|
||||
|
||||
ENTRYPOINT ["ws"]
|
||||
CMD ["--help"]
|
@ -1,6 +1,191 @@
|
||||
# Changelog
|
||||
|
||||
All changes to this project will be documented in this file.
|
||||
|
||||
## [11.0.2] - 2020-11-15
|
||||
|
||||
(ixwebsocket) use a C++11 compatible make_unique shim
|
||||
|
||||
## [11.0.1] - 2020-11-11
|
||||
|
||||
(socket) replace a std::vector with an std::array used as a tmp buffer in Socket::readBytes
|
||||
|
||||
## [11.0.0] - 2020-11-11
|
||||
|
||||
(openssl security fix) in the client to server connection, peer verification is not done in all cases. See https://github.com/machinezone/IXWebSocket/pull/250
|
||||
|
||||
## [10.5.7] - 2020-11-07
|
||||
|
||||
(docker) build docker container with zlib disabled
|
||||
|
||||
## [10.5.6] - 2020-11-07
|
||||
|
||||
(cmake) DEFLATE -> Deflate in CMake to stop warnings about casing
|
||||
|
||||
## [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)
|
||||
|
@ -17,6 +17,7 @@ There is a unittest which can be executed by typing `make test`.
|
||||
|
||||
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_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
|
||||
|
37
docs/performance.md
Normal file
37
docs/performance.md
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
## 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
|
||||
```
|
@ -67,9 +67,28 @@ webSocket.stop()
|
||||
|
||||
### Sending messages
|
||||
|
||||
`websocket.send("foo")` will send a message.
|
||||
`WebSocketSendInfo result = websocket.send("foo")` will send a message.
|
||||
|
||||
If the connection was closed and sending failed, the return value will be set to false.
|
||||
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).
|
||||
|
||||
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
|
||||
|
||||
@ -261,10 +280,9 @@ ix::WebSocketServer server(port);
|
||||
|
||||
server.setOnConnectionCallback(
|
||||
[&server](std::weak_ptr<WebSocket> webSocket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
std::cout << "Remote ip: " << connectionInfo->remoteIp << std::endl;
|
||||
std::cout << "Remote ip: " << connectionState->remoteIp << std::endl;
|
||||
|
||||
auto ws = webSocket.lock();
|
||||
if (ws)
|
||||
@ -340,13 +358,12 @@ The webSocket reference is guaranteed to be always valid ; by design the callbac
|
||||
ix::WebSocketServer server(port);
|
||||
|
||||
server.setOnClientMessageCallback(std::shared_ptr<ConnectionState> connectionState,
|
||||
ConnectionInfo& connectionInfo,
|
||||
WebSocket& webSocket,
|
||||
const WebSocketMessagePtr& msg)
|
||||
{
|
||||
// The ConnectionInfo object contains information about the connection,
|
||||
// The ConnectionState object contains information about the connection,
|
||||
// at this point only the client ip address and the port.
|
||||
std::cout << "Remote ip: " << connectionInfo.remoteIp << std::endl;
|
||||
std::cout << "Remote ip: " << connectionState->getRemoteIp();
|
||||
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
@ -441,18 +458,25 @@ out = httpClient.get(url, args);
|
||||
// POST request with parameters
|
||||
HttpParameters httpParameters;
|
||||
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
|
||||
out = httpClient.post(url, std::string("foo=bar"), args);
|
||||
|
||||
// PUT and PATCH are available too.
|
||||
|
||||
//
|
||||
// Result
|
||||
//
|
||||
auto statusCode = response->statusCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, 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 payload = response->payload; // All the bytes from the response as an std::string
|
||||
auto body = response->body; // All the bytes from the response as an std::string
|
||||
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
|
||||
auto uploadSize = response->uploadSize; // Byte count of uploaded data
|
||||
auto downloadSize = response->downloadSize; // Byte count of downloaded data
|
||||
@ -500,12 +524,11 @@ If you want to handle how requests are processed, implement the setOnConnectionC
|
||||
```cpp
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
|
||||
{
|
||||
// Build a string for the response
|
||||
std::stringstream ss;
|
||||
ss << connectionInfo->remoteIp
|
||||
ss << connectionState->getRemoteIp();
|
||||
<< " "
|
||||
<< request->method
|
||||
<< " "
|
||||
|
18
docs/ws.md
18
docs/ws.md
@ -204,6 +204,24 @@ 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.
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
set (IXBOTS_SOURCES
|
||||
ixbots/IXCobraBot.cpp
|
||||
ixbots/IXCobraToCobraBot.cpp
|
||||
ixbots/IXCobraToSentryBot.cpp
|
||||
ixbots/IXCobraToStatsdBot.cpp
|
||||
ixbots/IXCobraToStdoutBot.cpp
|
||||
@ -16,6 +17,7 @@ set (IXBOTS_SOURCES
|
||||
set (IXBOTS_HEADERS
|
||||
ixbots/IXCobraBot.h
|
||||
ixbots/IXCobraBotConfig.h
|
||||
ixbots/IXCobraToCobraBot.h
|
||||
ixbots/IXCobraToSentryBot.h
|
||||
ixbots/IXCobraToStatsdBot.h
|
||||
ixbots/IXCobraToStdoutBot.h
|
||||
|
@ -31,6 +31,8 @@ namespace ix
|
||||
auto limitReceivedEvents = botConfig.limitReceivedEvents;
|
||||
auto batchSize = botConfig.batchSize;
|
||||
|
||||
config.headers["X-Cobra-Channel"] = channel;
|
||||
|
||||
ix::CobraConnection conn;
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
@ -128,7 +130,7 @@ namespace ix
|
||||
ss << "no messages received or sent for "
|
||||
<< heartBeatTimeout << " seconds, reconnecting";
|
||||
|
||||
CoreLogger::error(ss.str());
|
||||
CoreLogger::warn(ss.str());
|
||||
stalledConnection = true;
|
||||
}
|
||||
state = currentState;
|
||||
@ -168,7 +170,11 @@ namespace ix
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Closed)
|
||||
{
|
||||
CoreLogger::info("Subscriber closed: {}" + event->errMsg);
|
||||
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)
|
||||
{
|
||||
|
45
ixbots/ixbots/IXCobraToCobraBot.cpp
Normal file
45
ixbots/ixbots/IXCobraToCobraBot.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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
|
20
ixbots/ixbots/IXCobraToCobraBot.h
Normal file
20
ixbots/ixbots/IXCobraToCobraBot.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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
|
@ -102,7 +102,7 @@ namespace ix
|
||||
{
|
||||
int64_t cobra_to_python_bot(const ix::CobraBotConfig& config,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& scriptPath)
|
||||
const std::string& moduleName)
|
||||
{
|
||||
#ifndef IXBOTS_USE_PYTHON
|
||||
CoreLogger::error("Command is disabled. "
|
||||
@ -113,10 +113,7 @@ namespace ix
|
||||
Py_InitializeEx(0); // 0 arg so that we do not install signal handlers
|
||||
// which prevent us from using Ctrl-C
|
||||
|
||||
size_t lastIndex = scriptPath.find_last_of(".");
|
||||
std::string modulePath = scriptPath.substr(0, lastIndex);
|
||||
|
||||
PyObject* pyModuleName = PyUnicode_DecodeFSDefault(modulePath.c_str());
|
||||
PyObject* pyModuleName = PyUnicode_DecodeFSDefault(moduleName.c_str());
|
||||
|
||||
if (pyModuleName == nullptr)
|
||||
{
|
||||
|
@ -15,5 +15,5 @@ namespace ix
|
||||
{
|
||||
int64_t cobra_to_python_bot(const ix::CobraBotConfig& config,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& scriptPath);
|
||||
const std::string& moduleName);
|
||||
} // namespace ix
|
||||
|
@ -41,7 +41,7 @@ namespace ix
|
||||
else
|
||||
{
|
||||
CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode));
|
||||
CoreLogger::error("Response: " + response->payload);
|
||||
CoreLogger::error("Response: " + response->body);
|
||||
|
||||
// Error 429 Too Many Requests
|
||||
if (response->statusCode == 429)
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -19,6 +20,7 @@ namespace ix
|
||||
std::string rolesecret;
|
||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions;
|
||||
SocketTLSOptions socketTLSOptions;
|
||||
WebSocketHttpHeaders headers;
|
||||
|
||||
CobraConfig(const std::string& a = std::string(),
|
||||
const std::string& e = std::string(),
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXUniquePtr.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
@ -91,13 +92,14 @@ namespace ix
|
||||
const std::string& errorMsg,
|
||||
const WebSocketHttpHeaders& headers,
|
||||
const std::string& subscriptionId,
|
||||
CobraConnection::MsgId msgId)
|
||||
CobraConnection::MsgId msgId,
|
||||
const std::string& connectionId)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_eventCallbackMutex);
|
||||
if (_eventCallback)
|
||||
{
|
||||
_eventCallback(
|
||||
std::make_unique<CobraEvent>(eventType, errorMsg, headers, subscriptionId, msgId));
|
||||
ix::make_unique<CobraEvent>(eventType, errorMsg, headers, subscriptionId, msgId, connectionId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,7 +257,8 @@ namespace ix
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
const SocketTLSOptions& socketTLSOptions,
|
||||
const WebSocketHttpHeaders& headers)
|
||||
{
|
||||
_roleName = rolename;
|
||||
_roleSecret = rolesecret;
|
||||
@ -269,6 +272,7 @@ namespace ix
|
||||
_webSocket->setUrl(url);
|
||||
_webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
||||
_webSocket->setTLSOptions(socketTLSOptions);
|
||||
_webSocket->setExtraHeaders(headers);
|
||||
|
||||
// Send a websocket ping every N seconds (N = 30) now
|
||||
// This should keep the connection open and prevent some load balancers such as
|
||||
@ -283,7 +287,8 @@ namespace ix
|
||||
config.rolename,
|
||||
config.rolesecret,
|
||||
config.webSocketPerMessageDeflateOptions,
|
||||
config.socketTLSOptions);
|
||||
config.socketTLSOptions,
|
||||
config.headers);
|
||||
}
|
||||
|
||||
//
|
||||
@ -349,6 +354,18 @@ namespace ix
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,8 @@ namespace ix
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions);
|
||||
const SocketTLSOptions& socketTLSOptions,
|
||||
const WebSocketHttpHeaders& headers);
|
||||
|
||||
void configure(const ix::CobraConfig& config);
|
||||
|
||||
@ -157,7 +158,9 @@ namespace ix
|
||||
const std::string& errorMsg = std::string(),
|
||||
const WebSocketHttpHeaders& headers = WebSocketHttpHeaders(),
|
||||
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);
|
||||
|
||||
/// Tells whether the internal queue is empty or not
|
||||
|
@ -21,17 +21,20 @@ namespace ix
|
||||
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)
|
||||
uint64_t m,
|
||||
const std::string& c)
|
||||
: type(t)
|
||||
, errMsg(e)
|
||||
, headers(h)
|
||||
, subscriptionId(s)
|
||||
, msgId(m)
|
||||
, connectionId(c)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ namespace ix
|
||||
Pong = 7,
|
||||
HandshakeError = 8,
|
||||
AuthenticationError = 9,
|
||||
SubscriptionError = 10
|
||||
SubscriptionError = 10,
|
||||
Handshake = 11
|
||||
};
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ namespace ix
|
||||
{
|
||||
_cobra_connection.setEventCallback([](const CobraEventPtr& event) {
|
||||
std::stringstream ss;
|
||||
ix::LogLevel logLevel = LogLevel::Info;
|
||||
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
@ -34,6 +35,10 @@ namespace ix
|
||||
ss << it.first << ": " << it.second << 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";
|
||||
@ -41,6 +46,7 @@ namespace ix
|
||||
else if (event->type == ix::CobraEventType::Error)
|
||||
{
|
||||
ss << "Error: " << event->errMsg;
|
||||
logLevel = ix::LogLevel::Error;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Closed)
|
||||
{
|
||||
@ -57,6 +63,7 @@ namespace ix
|
||||
else if (event->type == ix::CobraEventType::Published)
|
||||
{
|
||||
ss << "Published message " << event->msgId << " acked";
|
||||
logLevel = ix::LogLevel::Debug;
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Pong)
|
||||
{
|
||||
@ -65,17 +72,20 @@ namespace ix
|
||||
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());
|
||||
CoreLogger::log(ss.str().c_str(), logLevel);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -19,4 +19,16 @@ namespace ix
|
||||
|
||||
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,8 +8,10 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
uint64_t djb2Hash(const std::vector<uint8_t>& data);
|
||||
uint64_t djb2HashStr(const std::string& data);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
|
||||
namespace ix
|
||||
|
@ -45,10 +45,9 @@ namespace ix
|
||||
}
|
||||
|
||||
void RedisServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
logInfo("New connection from remote ip " + connectionInfo->remoteIp);
|
||||
logInfo("New connection from remote ip " + connectionState->getRemoteIp());
|
||||
|
||||
_connectedClientsCount++;
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility> // pair
|
||||
#include <vector> // pair
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -44,8 +45,7 @@ namespace ix
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) final;
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
bool startsWith(const std::string& str, const std::string& start);
|
||||
|
@ -2,6 +2,17 @@
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
check_c_source_compiles("#include <regex>
|
||||
int main()
|
||||
{
|
||||
const std::regex dsnRegex;
|
||||
std::smatch group;
|
||||
std::regex_match(std::string(), group, dsnRegex);
|
||||
return 0;
|
||||
}"
|
||||
HAVE_STD_REGEX)
|
||||
|
||||
set (IXSENTRY_SOURCES
|
||||
ixsentry/IXSentryClient.cpp
|
||||
@ -28,3 +39,7 @@ set(IXSENTRY_INCLUDE_DIRS
|
||||
${JSONCPP_INCLUDE_DIRS})
|
||||
|
||||
target_include_directories( ixsentry PUBLIC ${IXSENTRY_INCLUDE_DIRS} )
|
||||
|
||||
if (HAVE_STD_REGEX)
|
||||
target_compile_definitions( ixsentry PUBLIC HAVE_STD_REGEX=1 )
|
||||
endif()
|
||||
|
@ -20,9 +20,12 @@ namespace ix
|
||||
SentryClient::SentryClient(const std::string& dsn)
|
||||
: _dsn(dsn)
|
||||
, _validDsn(false)
|
||||
#ifdef HAVE_STD_REGEX
|
||||
, _luaFrameRegex("\t([^/]+):([0-9]+): in function ['<]([^/]+)['>]")
|
||||
#endif
|
||||
, _httpClient(std::make_shared<HttpClient>(true))
|
||||
{
|
||||
#ifdef HAVE_STD_REGEX
|
||||
const std::regex dsnRegex("(http[s]?)://([^:]+):([^@]+)@([^/]+)/([0-9]+)");
|
||||
std::smatch group;
|
||||
|
||||
@ -38,6 +41,7 @@ namespace ix
|
||||
_publicKey = group.str(2);
|
||||
_secretKey = group.str(3);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SentryClient::setTLSOptions(const SocketTLSOptions& tlsOptions)
|
||||
@ -77,6 +81,7 @@ namespace ix
|
||||
{
|
||||
Json::Value frames;
|
||||
|
||||
#ifdef HAVE_STD_REGEX
|
||||
// Split by lines
|
||||
std::string line;
|
||||
std::stringstream tokenStream(stack);
|
||||
@ -107,6 +112,7 @@ namespace ix
|
||||
}
|
||||
|
||||
std::reverse(frames.begin(), frames.end());
|
||||
#endif
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
@ -11,7 +11,9 @@
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <json/json.h>
|
||||
#include <memory>
|
||||
#ifdef HAVE_STD_REGEX
|
||||
#include <regex>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -62,7 +64,9 @@ namespace ix
|
||||
|
||||
Json::FastWriter _jsonWriter;
|
||||
|
||||
#ifdef HAVE_STD_REGEX
|
||||
std::regex _luaFrameRegex;
|
||||
#endif
|
||||
|
||||
std::shared_ptr<HttpClient> _httpClient;
|
||||
};
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXUniquePtr.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace snake
|
||||
@ -196,7 +197,7 @@ namespace snake
|
||||
{
|
||||
std::string filterStr = pdu["body"]["filter"];
|
||||
}
|
||||
state->streamSql = std::make_unique<StreamSql>(filterStr);
|
||||
state->streamSql = ix::make_unique<StreamSql>(filterStr);
|
||||
state->id = 0;
|
||||
state->onRedisSubscribeCallback = [&ws, state](const std::string& messageStr) {
|
||||
auto msg = nlohmann::json::parse(messageStr);
|
||||
|
@ -61,11 +61,10 @@ namespace snake
|
||||
|
||||
_server.setOnClientMessageCallback(
|
||||
[this](std::shared_ptr<ix::ConnectionState> connectionState,
|
||||
ix::ConnectionInfo& connectionInfo,
|
||||
ix::WebSocket& webSocket,
|
||||
const ix::WebSocketMessagePtr& msg) {
|
||||
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
||||
auto remoteIp = connectionInfo.remoteIp;
|
||||
auto remoteIp = connectionState->getRemoteIp();
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "[" << state->getId() << "] ";
|
||||
|
@ -12,10 +12,8 @@ namespace ix
|
||||
{
|
||||
Bench::Bench(const std::string& description)
|
||||
: _description(description)
|
||||
, _start(std::chrono::high_resolution_clock::now())
|
||||
, _reported(false)
|
||||
{
|
||||
;
|
||||
reset();
|
||||
}
|
||||
|
||||
Bench::~Bench()
|
||||
@ -26,19 +24,38 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
void Bench::reset()
|
||||
{
|
||||
_start = std::chrono::high_resolution_clock::now();
|
||||
_reported = false;
|
||||
}
|
||||
|
||||
void Bench::report()
|
||||
{
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - _start);
|
||||
|
||||
_ms = milliseconds.count();
|
||||
std::cerr << _description << " completed in " << _ms << "ms" << std::endl;
|
||||
_duration = microseconds.count();
|
||||
std::cerr << _description << " completed in " << _duration << " us" << 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;
|
||||
}
|
||||
|
||||
uint64_t Bench::getDuration() const
|
||||
{
|
||||
return _ms;
|
||||
return _duration;
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2017-2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <stdint.h>
|
||||
@ -16,13 +17,16 @@ namespace ix
|
||||
Bench(const std::string& description);
|
||||
~Bench();
|
||||
|
||||
void reset();
|
||||
void record();
|
||||
void report();
|
||||
void setReported();
|
||||
uint64_t getDuration() const;
|
||||
|
||||
private:
|
||||
std::string _description;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> _start;
|
||||
uint64_t _ms;
|
||||
uint64_t _duration;
|
||||
bool _reported;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* IXConnectionInfo.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct ConnectionInfo
|
||||
{
|
||||
std::string remoteIp;
|
||||
int remotePort;
|
||||
|
||||
ConnectionInfo(const std::string& r = std::string(), int p = 0)
|
||||
: remoteIp(r)
|
||||
, remotePort(p)
|
||||
{
|
||||
;
|
||||
}
|
||||
};
|
||||
} // namespace ix
|
@ -31,6 +31,11 @@ namespace ix
|
||||
return std::make_shared<ConnectionState>();
|
||||
}
|
||||
|
||||
void ConnectionState::setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback)
|
||||
{
|
||||
_onSetTerminatedCallback = callback;
|
||||
}
|
||||
|
||||
bool ConnectionState::isTerminated() const
|
||||
{
|
||||
return _terminated;
|
||||
@ -39,5 +44,30 @@ namespace ix
|
||||
void ConnectionState::setTerminated()
|
||||
{
|
||||
_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
|
||||
|
@ -7,12 +7,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
using OnSetTerminatedCallback = std::function<void()>;
|
||||
|
||||
class ConnectionState
|
||||
{
|
||||
public:
|
||||
@ -25,12 +28,27 @@ namespace ix
|
||||
void setTerminated();
|
||||
bool isTerminated() const;
|
||||
|
||||
const std::string& getRemoteIp();
|
||||
int getRemotePort();
|
||||
|
||||
static std::shared_ptr<ConnectionState> createConnectionState();
|
||||
|
||||
private:
|
||||
void setOnSetTerminatedCallback(const OnSetTerminatedCallback& callback);
|
||||
|
||||
void setRemoteIp(const std::string& remoteIp);
|
||||
void setRemotePort(int remotePort);
|
||||
|
||||
protected:
|
||||
std::atomic<bool> _terminated;
|
||||
std::string _id;
|
||||
OnSetTerminatedCallback _onSetTerminatedCallback;
|
||||
|
||||
static std::atomic<uint64_t> _globalId;
|
||||
|
||||
std::string _remoteIp;
|
||||
int _remotePort;
|
||||
|
||||
friend class SocketServer;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -68,6 +68,11 @@ namespace ix
|
||||
: resolveUnCancellable(errMsg, isCancellationRequested);
|
||||
}
|
||||
|
||||
void DNSLookup::release(struct addrinfo* addr)
|
||||
{
|
||||
freeaddrinfo(addr);
|
||||
}
|
||||
|
||||
struct addrinfo* DNSLookup::resolveUnCancellable(
|
||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
|
@ -31,6 +31,8 @@ namespace ix
|
||||
const CancellationRequest& isCancellationRequested,
|
||||
bool cancellable = true);
|
||||
|
||||
void release(struct addrinfo* addr);
|
||||
|
||||
private:
|
||||
struct addrinfo* resolveCancellable(std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* IXExponentialBackoff.h
|
||||
* IXExponentialBackoff.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
183
ixwebsocket/IXGzipCodec.cpp
Normal file
183
ixwebsocket/IXGzipCodec.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
std::string gzipCompress(const std::string& str)
|
||||
{
|
||||
#ifndef IXWEBSOCKET_USE_ZLIB
|
||||
return std::string();
|
||||
#else
|
||||
#ifdef IXWEBSOCKET_USE_DEFLATE
|
||||
int compressionLevel = 6;
|
||||
struct libdeflate_compressor* compressor;
|
||||
|
||||
compressor = libdeflate_alloc_compressor(compressionLevel);
|
||||
|
||||
const void* uncompressed_data = str.data();
|
||||
size_t uncompressed_size = str.size();
|
||||
void* compressed_data;
|
||||
size_t actual_compressed_size;
|
||||
size_t max_compressed_size;
|
||||
|
||||
max_compressed_size = libdeflate_gzip_compress_bound(compressor, uncompressed_size);
|
||||
compressed_data = malloc(max_compressed_size);
|
||||
|
||||
if (compressed_data == NULL)
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
|
||||
actual_compressed_size = libdeflate_gzip_compress(
|
||||
compressor, uncompressed_data, uncompressed_size, compressed_data, max_compressed_size);
|
||||
|
||||
libdeflate_free_compressor(compressor);
|
||||
|
||||
if (actual_compressed_size == 0)
|
||||
{
|
||||
free(compressed_data);
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string out;
|
||||
out.assign(reinterpret_cast<char*>(compressed_data), actual_compressed_size);
|
||||
free(compressed_data);
|
||||
|
||||
return out;
|
||||
#else
|
||||
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 // IXWEBSOCKET_USE_DEFLATE
|
||||
#endif // IXWEBSOCKET_USE_ZLIB
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
#ifndef IXWEBSOCKET_USE_ZLIB
|
||||
return false;
|
||||
#else
|
||||
#ifdef IXWEBSOCKET_USE_DEFLATE
|
||||
struct libdeflate_decompressor* decompressor;
|
||||
decompressor = libdeflate_alloc_decompressor();
|
||||
|
||||
const void* compressed_data = in.data();
|
||||
size_t compressed_size = in.size();
|
||||
|
||||
// Retrieve uncompressed size from the trailer of the gziped data
|
||||
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&in.front());
|
||||
auto uncompressed_size = loadDecompressedGzipSize(&ptr[compressed_size - 4]);
|
||||
|
||||
// Use it to redimension our output buffer
|
||||
out.resize(uncompressed_size);
|
||||
|
||||
libdeflate_result result = libdeflate_gzip_decompress(
|
||||
decompressor, compressed_data, compressed_size, &out.front(), uncompressed_size, NULL);
|
||||
|
||||
libdeflate_free_decompressor(decompressor);
|
||||
return result == LIBDEFLATE_SUCCESS;
|
||||
#else
|
||||
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 // IXWEBSOCKET_USE_DEFLATE
|
||||
#endif // IXWEBSOCKET_USE_ZLIB
|
||||
}
|
||||
} // namespace ix
|
15
ixwebsocket/IXGzipCodec.h
Normal file
15
ixwebsocket/IXGzipCodec.h
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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,6 +7,7 @@
|
||||
#include "IXHttp.h"
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
#include "IXGzipCodec.h"
|
||||
#include "IXSocket.h"
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
@ -93,14 +94,12 @@ namespace ix
|
||||
}
|
||||
|
||||
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(
|
||||
std::unique_ptr<Socket>& socket)
|
||||
std::unique_ptr<Socket>& socket, int timeoutSecs)
|
||||
{
|
||||
HttpRequestPtr httpRequest;
|
||||
|
||||
std::atomic<bool> requestInitCancellation(false);
|
||||
|
||||
int timeoutSecs = 5; // FIXME
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
|
||||
|
||||
@ -130,7 +129,53 @@ namespace ix
|
||||
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
|
||||
}
|
||||
|
||||
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, headers);
|
||||
std::string body;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -151,7 +196,7 @@ namespace ix
|
||||
|
||||
// Write headers
|
||||
ss.str("");
|
||||
ss << "Content-Length: " << response->payload.size() << "\r\n";
|
||||
ss << "Content-Length: " << response->body.size() << "\r\n";
|
||||
for (auto&& it : response->headers)
|
||||
{
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
@ -163,6 +208,6 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
return response->payload.empty() ? true : socket->writeBytes(response->payload, nullptr);
|
||||
return response->body.empty() ? true : socket->writeBytes(response->body, nullptr);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -39,7 +39,7 @@ namespace ix
|
||||
std::string description;
|
||||
HttpErrorCode errorCode;
|
||||
WebSocketHttpHeaders headers;
|
||||
std::string payload;
|
||||
std::string body;
|
||||
std::string errorMsg;
|
||||
uint64_t uploadSize;
|
||||
uint64_t downloadSize;
|
||||
@ -48,7 +48,7 @@ namespace ix
|
||||
const std::string& des = std::string(),
|
||||
const HttpErrorCode& c = HttpErrorCode::Ok,
|
||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
||||
const std::string& p = std::string(),
|
||||
const std::string& b = std::string(),
|
||||
const std::string& e = std::string(),
|
||||
uint64_t u = 0,
|
||||
uint64_t d = 0)
|
||||
@ -56,7 +56,7 @@ namespace ix
|
||||
, description(des)
|
||||
, errorCode(c)
|
||||
, headers(h)
|
||||
, payload(p)
|
||||
, body(b)
|
||||
, errorMsg(e)
|
||||
, uploadSize(u)
|
||||
, downloadSize(d)
|
||||
@ -84,6 +84,7 @@ namespace ix
|
||||
int maxRedirects = 5;
|
||||
bool verbose = false;
|
||||
bool compress = true;
|
||||
bool compressRequest = false;
|
||||
Logger logger;
|
||||
OnProgressCallback onProgressCallback;
|
||||
};
|
||||
@ -95,15 +96,18 @@ namespace ix
|
||||
std::string uri;
|
||||
std::string method;
|
||||
std::string version;
|
||||
std::string body;
|
||||
WebSocketHttpHeaders headers;
|
||||
|
||||
HttpRequest(const std::string& u,
|
||||
const std::string& m,
|
||||
const std::string& v,
|
||||
const std::string& b,
|
||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
|
||||
: uri(u)
|
||||
, method(m)
|
||||
, version(v)
|
||||
, body(b)
|
||||
, headers(h)
|
||||
{
|
||||
}
|
||||
@ -115,7 +119,7 @@ namespace ix
|
||||
{
|
||||
public:
|
||||
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(
|
||||
std::unique_ptr<Socket>& socket);
|
||||
std::unique_ptr<Socket>& socket, int timeoutSecs);
|
||||
static bool sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket);
|
||||
|
||||
static std::pair<std::string, int> parseStatusLine(const std::string& line);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXHttpClient.h"
|
||||
|
||||
#include "IXGzipCodec.h"
|
||||
#include "IXSocketFactory.h"
|
||||
#include "IXUrlParser.h"
|
||||
#include "IXUserAgent.h"
|
||||
@ -17,10 +18,6 @@
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
const std::string HttpClient::kPost = "POST";
|
||||
@ -206,6 +203,15 @@ namespace ix
|
||||
|
||||
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
|
||||
{
|
||||
// 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";
|
||||
|
||||
// Set default Content-Type if unspecified
|
||||
@ -500,12 +506,12 @@ namespace ix
|
||||
|
||||
downloadSize = payload.size();
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
// If the content was compressed with gzip, decode it
|
||||
if (headers["Content-Encoding"] == "gzip")
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::string decompressedPayload;
|
||||
if (!gzipInflate(payload, decompressedPayload))
|
||||
if (!gzipDecompress(payload, decompressedPayload))
|
||||
{
|
||||
std::string errorMsg("Error decompressing payload");
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
@ -518,8 +524,18 @@ namespace ix
|
||||
downloadSize);
|
||||
}
|
||||
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,
|
||||
description,
|
||||
@ -546,11 +562,42 @@ namespace ix
|
||||
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,
|
||||
const HttpParameters& httpParameters,
|
||||
const HttpFormDataParameters& httpFormDataParameters,
|
||||
HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kPost, serializeHttpParameters(httpParameters), args);
|
||||
return request(url, kPost, httpParameters, httpFormDataParameters, args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::post(const std::string& url,
|
||||
@ -562,9 +609,10 @@ namespace ix
|
||||
|
||||
HttpResponsePtr HttpClient::put(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
const HttpFormDataParameters& httpFormDataParameters,
|
||||
HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kPut, serializeHttpParameters(httpParameters), args);
|
||||
return request(url, kPut, httpParameters, httpFormDataParameters, args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::put(const std::string& url,
|
||||
@ -576,9 +624,10 @@ namespace ix
|
||||
|
||||
HttpResponsePtr HttpClient::patch(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
const HttpFormDataParameters& httpFormDataParameters,
|
||||
HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kPatch, serializeHttpParameters(httpParameters), args);
|
||||
return request(url, kPatch, httpParameters, httpFormDataParameters, args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::patch(const std::string& url,
|
||||
@ -679,53 +728,6 @@ namespace ix
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
||||
{
|
||||
if (args->logger)
|
||||
|
@ -34,6 +34,7 @@ namespace ix
|
||||
|
||||
HttpResponsePtr post(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
const HttpFormDataParameters& httpFormDataParameters,
|
||||
HttpRequestArgsPtr args);
|
||||
HttpResponsePtr post(const std::string& url,
|
||||
const std::string& body,
|
||||
@ -41,6 +42,7 @@ namespace ix
|
||||
|
||||
HttpResponsePtr put(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
const HttpFormDataParameters& httpFormDataParameters,
|
||||
HttpRequestArgsPtr args);
|
||||
HttpResponsePtr put(const std::string& url,
|
||||
const std::string& body,
|
||||
@ -48,6 +50,7 @@ namespace ix
|
||||
|
||||
HttpResponsePtr patch(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
const HttpFormDataParameters& httpFormDataParameters,
|
||||
HttpRequestArgsPtr args);
|
||||
HttpResponsePtr patch(const std::string& url,
|
||||
const std::string& body,
|
||||
@ -58,7 +61,15 @@ namespace ix
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args,
|
||||
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
|
||||
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
|
||||
const std::string& verb = HttpClient::kGet);
|
||||
@ -90,10 +101,6 @@ namespace ix
|
||||
private:
|
||||
void log(const std::string& msg, HttpRequestArgsPtr args);
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
bool gzipInflate(const std::string& in, std::string& out);
|
||||
#endif
|
||||
|
||||
// Async API background thread runner
|
||||
void run();
|
||||
// Async API
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXHttpServer.h"
|
||||
|
||||
#include "IXGzipCodec.h"
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUserAgent.h"
|
||||
@ -14,10 +15,6 @@
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
|
||||
@ -43,59 +40,21 @@ namespace
|
||||
auto vec = res.second;
|
||||
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||
}
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::string gzipCompress(const std::string& str)
|
||||
{
|
||||
z_stream zs; // z_stream is zlib's control structure
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
|
||||
// deflateInit2 configure the file format: request gzip instead of deflate
|
||||
const int windowBits = 15;
|
||||
const int GZIP_ENCODING = 16;
|
||||
|
||||
deflateInit2(&zs,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
windowBits | GZIP_ENCODING,
|
||||
8,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
|
||||
zs.next_in = (Bytef*) str.data();
|
||||
zs.avail_in = (uInt) str.size(); // set the z_stream's input
|
||||
|
||||
int ret;
|
||||
char outbuffer[32768];
|
||||
std::string outstring;
|
||||
|
||||
// retrieve the compressed bytes blockwise
|
||||
do
|
||||
{
|
||||
zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
|
||||
zs.avail_out = sizeof(outbuffer);
|
||||
|
||||
ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (outstring.size() < zs.total_out)
|
||||
{
|
||||
// append the block to the output string
|
||||
outstring.append(outbuffer, zs.total_out - outstring.size());
|
||||
}
|
||||
} while (ret == Z_OK);
|
||||
|
||||
deflateEnd(&zs);
|
||||
|
||||
return outstring;
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
namespace ix
|
||||
{
|
||||
HttpServer::HttpServer(
|
||||
int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
||||
const int HttpServer::kDefaultTimeoutSecs(30);
|
||||
|
||||
HttpServer::HttpServer(int port,
|
||||
const std::string& host,
|
||||
int backlog,
|
||||
size_t maxConnections,
|
||||
int addressFamily,
|
||||
int timeoutSecs)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||
, _connectedClientsCount(0)
|
||||
, _timeoutSecs(timeoutSecs)
|
||||
{
|
||||
setDefaultConnectionCallback();
|
||||
}
|
||||
@ -120,18 +79,16 @@ namespace ix
|
||||
}
|
||||
|
||||
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
_connectedClientsCount++;
|
||||
|
||||
auto ret = Http::parseRequest(socket);
|
||||
auto ret = Http::parseRequest(socket, _timeoutSecs);
|
||||
// FIXME: handle errors in parseRequest
|
||||
|
||||
if (std::get<0>(ret))
|
||||
{
|
||||
auto response =
|
||||
_onConnectionCallback(std::get<2>(ret), connectionState, std::move(connectionInfo));
|
||||
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
||||
if (!Http::sendResponse(response, socket))
|
||||
{
|
||||
logError("Cannot send response");
|
||||
@ -151,8 +108,7 @@ namespace ix
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
std::string uri(request->uri);
|
||||
if (uri.empty() || uri == "/")
|
||||
{
|
||||
@ -184,8 +140,8 @@ namespace ix
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionInfo->remoteIp << ":" << connectionInfo->remotePort << " "
|
||||
<< request->method << " " << request->headers["User-Agent"] << " "
|
||||
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
||||
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri << " " << content.size();
|
||||
logInfo(ss.str());
|
||||
|
||||
@ -209,16 +165,16 @@ namespace ix
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||
//
|
||||
setOnConnectionCallback(
|
||||
[this, redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
||||
[this,
|
||||
redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionInfo->remoteIp << ":" << connectionInfo->remotePort << " "
|
||||
<< request->method << " " << request->headers["User-Agent"] << " "
|
||||
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
||||
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri;
|
||||
logInfo(ss.str());
|
||||
|
||||
@ -234,4 +190,40 @@ namespace ix
|
||||
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
|
||||
|
@ -23,15 +23,14 @@ namespace ix
|
||||
{
|
||||
public:
|
||||
using OnConnectionCallback =
|
||||
std::function<HttpResponsePtr(HttpRequestPtr,
|
||||
std::shared_ptr<ConnectionState>,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)>;
|
||||
std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
|
||||
|
||||
HttpServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily);
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily,
|
||||
int timeoutSecs = HttpServer::kDefaultTimeoutSecs);
|
||||
virtual ~HttpServer();
|
||||
virtual void stop() final;
|
||||
|
||||
@ -39,15 +38,19 @@ namespace ix
|
||||
|
||||
void makeRedirectServer(const std::string& redirectUrl);
|
||||
|
||||
void makeDebugServer();
|
||||
|
||||
private:
|
||||
// Member variables
|
||||
OnConnectionCallback _onConnectionCallback;
|
||||
std::atomic<int> _connectedClientsCount;
|
||||
|
||||
const static int kDefaultTimeoutSecs;
|
||||
int _timeoutSecs;
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) final;
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
void setDefaultConnectionCallback();
|
||||
|
@ -8,6 +8,9 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
const uint64_t SelectInterrupt::kSendRequest = 1;
|
||||
const uint64_t SelectInterrupt::kCloseRequest = 2;
|
||||
|
||||
SelectInterrupt::SelectInterrupt()
|
||||
{
|
||||
;
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
@ -23,5 +24,11 @@ namespace ix
|
||||
virtual bool clear();
|
||||
virtual uint64_t read();
|
||||
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
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXSelectInterruptFactory.h"
|
||||
|
||||
#include "IXUniquePtr.h"
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#include "IXSelectInterruptPipe.h"
|
||||
#else
|
||||
@ -17,9 +18,9 @@ namespace ix
|
||||
SelectInterruptPtr createSelectInterrupt()
|
||||
{
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
return std::make_unique<SelectInterruptPipe>();
|
||||
return ix::make_unique<SelectInterruptPipe>();
|
||||
#else
|
||||
return std::make_unique<SelectInterrupt>();
|
||||
return ix::make_unique<SelectInterrupt>();
|
||||
#endif
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "IXSelectInterruptFactory.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
@ -18,6 +19,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
@ -27,9 +29,6 @@ namespace ix
|
||||
{
|
||||
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
|
||||
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
|
||||
const uint64_t Socket::kSendRequest = 1;
|
||||
const uint64_t Socket::kCloseRequest = 2;
|
||||
constexpr size_t Socket::kChunkSize;
|
||||
|
||||
Socket::Socket(int fd)
|
||||
: _sockfd(fd)
|
||||
@ -96,11 +95,11 @@ namespace ix
|
||||
{
|
||||
uint64_t value = selectInterrupt->read();
|
||||
|
||||
if (value == kSendRequest)
|
||||
if (value == SelectInterrupt::kSendRequest)
|
||||
{
|
||||
pollResult = PollResultType::SendRequest;
|
||||
}
|
||||
else if (value == kCloseRequest)
|
||||
else if (value == SelectInterrupt::kCloseRequest)
|
||||
{
|
||||
pollResult = PollResultType::CloseRequest;
|
||||
}
|
||||
@ -366,10 +365,7 @@ namespace ix
|
||||
const OnProgressCallback& onProgressCallback,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
if (_readBuffer.empty())
|
||||
{
|
||||
_readBuffer.resize(kChunkSize);
|
||||
}
|
||||
std::array<uint8_t, 1 << 14> readBuffer;
|
||||
|
||||
std::vector<uint8_t> output;
|
||||
while (output.size() != length)
|
||||
@ -380,12 +376,12 @@ namespace ix
|
||||
return std::make_pair(false, errorMsg);
|
||||
}
|
||||
|
||||
size_t size = std::min(kChunkSize, length - output.size());
|
||||
ssize_t ret = recv((char*) &_readBuffer[0], size);
|
||||
size_t size = std::min(readBuffer.size(), length - output.size());
|
||||
ssize_t ret = recv((char*) &readBuffer[0], size);
|
||||
|
||||
if (ret > 0)
|
||||
{
|
||||
output.insert(output.end(), _readBuffer.begin(), _readBuffer.begin() + ret);
|
||||
output.insert(output.end(), readBuffer.begin(), readBuffer.begin() + ret);
|
||||
}
|
||||
else if (ret <= 0 && !Socket::isWaitNeeded())
|
||||
{
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <BaseTsd.h>
|
||||
@ -34,12 +33,10 @@ typedef SSIZE_T ssize_t;
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
#include "IXProgressCallback.h"
|
||||
#include "IXSelectInterrupt.h"
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SelectInterrupt;
|
||||
using SelectInterruptPtr = std::unique_ptr<SelectInterrupt>;
|
||||
|
||||
enum class PollResultType
|
||||
{
|
||||
ReadyForRead = 0,
|
||||
@ -96,11 +93,6 @@ namespace ix
|
||||
int sockfd,
|
||||
const SelectInterruptPtr& selectInterrupt);
|
||||
|
||||
|
||||
// Used as special codes for pipe communication
|
||||
static const uint64_t kSendRequest;
|
||||
static const uint64_t kCloseRequest;
|
||||
|
||||
protected:
|
||||
std::atomic<int> _sockfd;
|
||||
std::mutex _socketMutex;
|
||||
@ -109,10 +101,6 @@ namespace ix
|
||||
static const int kDefaultPollTimeout;
|
||||
static const int kDefaultPollNoTimeout;
|
||||
|
||||
// Buffer for reading from our socket. That buffer is never resized.
|
||||
std::vector<uint8_t> _readBuffer;
|
||||
static constexpr size_t kChunkSize = 1 << 15;
|
||||
|
||||
SelectInterruptPtr _selectInterrupt;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSelectInterrupt.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXUniquePtr.h"
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
@ -65,7 +66,7 @@ namespace ix
|
||||
|
||||
int timeoutMs = 10;
|
||||
bool readyToRead = false;
|
||||
auto selectInterrupt = std::make_unique<SelectInterrupt>();
|
||||
auto selectInterrupt = ix::make_unique<SelectInterrupt>();
|
||||
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt);
|
||||
|
||||
if (pollResult == PollResultType::Timeout)
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXSocketFactory.h"
|
||||
|
||||
#include "IXUniquePtr.h"
|
||||
#ifdef IXWEBSOCKET_USE_TLS
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
@ -35,17 +36,17 @@ namespace ix
|
||||
|
||||
if (!tls)
|
||||
{
|
||||
socket = std::make_unique<Socket>(fd);
|
||||
socket = ix::make_unique<Socket>(fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_TLS
|
||||
#if defined(IXWEBSOCKET_USE_MBED_TLS)
|
||||
socket = std::make_unique<SocketMbedTLS>(tlsOptions, fd);
|
||||
socket = ix::make_unique<SocketMbedTLS>(tlsOptions, fd);
|
||||
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
socket = std::make_unique<SocketOpenSSL>(tlsOptions, fd);
|
||||
socket = ix::make_unique<SocketOpenSSL>(tlsOptions, fd);
|
||||
#elif defined(__APPLE__)
|
||||
socket = std::make_unique<SocketAppleSSL>(tlsOptions, fd);
|
||||
socket = ix::make_unique<SocketAppleSSL>(tlsOptions, fd);
|
||||
#endif
|
||||
#else
|
||||
errorMsg = "TLS support is not enabled on this platform.";
|
||||
|
@ -10,8 +10,10 @@
|
||||
#include "IXSocketOpenSSL.h"
|
||||
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUniquePtr.h"
|
||||
#include <cassert>
|
||||
#include <errno.h>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#include <Shlwapi.h>
|
||||
#else
|
||||
@ -85,8 +87,7 @@ namespace ix
|
||||
|
||||
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
||||
std::once_flag SocketOpenSSL::_openSSLInitFlag;
|
||||
std::unique_ptr<std::mutex[]> SocketOpenSSL::_openSSLMutexes =
|
||||
std::make_unique<std::mutex[]>(CRYPTO_num_locks());
|
||||
std::vector<std::unique_ptr<std::mutex>> openSSLMutexes;
|
||||
|
||||
SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
@ -111,6 +112,11 @@ namespace ix
|
||||
|
||||
if (CRYPTO_get_locking_callback() == nullptr)
|
||||
{
|
||||
openSSLMutexes.clear();
|
||||
for (int i = 0; i < CRYPTO_num_locks(); ++i)
|
||||
{
|
||||
openSSLMutexes.push_back(ix::make_unique<std::mutex>());
|
||||
}
|
||||
CRYPTO_set_locking_callback(SocketOpenSSL::openSSLLockingCallback);
|
||||
}
|
||||
#endif
|
||||
@ -128,11 +134,11 @@ namespace ix
|
||||
{
|
||||
if (mode & CRYPTO_LOCK)
|
||||
{
|
||||
_openSSLMutexes[type].lock();
|
||||
openSSLMutexes[type]->lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
_openSSLMutexes[type].unlock();
|
||||
openSSLMutexes[type]->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@ -503,14 +509,13 @@ namespace ix
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(
|
||||
_ssl_context, SSL_VERIFY_PEER, [](int preverify, X509_STORE_CTX*) -> int {
|
||||
return preverify;
|
||||
});
|
||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||
}
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(_ssl_context,
|
||||
SSL_VERIFY_PEER,
|
||||
[](int preverify, X509_STORE_CTX*) -> int { return preverify; });
|
||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -61,7 +61,6 @@ namespace ix
|
||||
|
||||
static std::once_flag _openSSLInitFlag;
|
||||
static std::atomic<bool> _openSSLInitializationSuccessful;
|
||||
static std::unique_ptr<std::mutex[]> _openSSLMutexes;
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSelectInterrupt.h"
|
||||
#include "IXSelectInterruptFactory.h"
|
||||
#include "IXSetThreadName.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketConnect.h"
|
||||
@ -36,6 +37,7 @@ namespace ix
|
||||
, _stop(false)
|
||||
, _stopGc(false)
|
||||
, _connectionStateFactory(&ConnectionState::createConnectionState)
|
||||
, _acceptSelectInterrupt(createSelectInterrupt())
|
||||
{
|
||||
}
|
||||
|
||||
@ -58,6 +60,16 @@ namespace ix
|
||||
|
||||
std::pair<bool, std::string> SocketServer::listen()
|
||||
{
|
||||
std::string acceptSelectInterruptInitErrorMsg;
|
||||
if (!_acceptSelectInterrupt->init(acceptSelectInterruptInitErrorMsg))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error in SelectInterrupt::init: "
|
||||
<< acceptSelectInterruptInitErrorMsg;
|
||||
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
if (_addressFamily != AF_INET && _addressFamily != AF_INET6)
|
||||
{
|
||||
std::string errMsg("SocketServer::listen() AF_INET and AF_INET6 are currently "
|
||||
@ -193,6 +205,12 @@ namespace ix
|
||||
if (_thread.joinable())
|
||||
{
|
||||
_stop = true;
|
||||
// Wake up select
|
||||
if (!_acceptSelectInterrupt->notify(SelectInterrupt::kCloseRequest))
|
||||
{
|
||||
logError("SocketServer::stop: Cannot wake up from select");
|
||||
}
|
||||
|
||||
_thread.join();
|
||||
_stop = false;
|
||||
}
|
||||
@ -201,6 +219,7 @@ namespace ix
|
||||
if (_gcThread.joinable())
|
||||
{
|
||||
_stopGc = true;
|
||||
_conditionVariableGC.notify_one();
|
||||
_gcThread.join();
|
||||
_stopGc = false;
|
||||
}
|
||||
@ -249,18 +268,22 @@ namespace ix
|
||||
// Set the socket to non blocking mode, so that accept calls are not blocking
|
||||
SocketConnect::configure(_serverFd);
|
||||
|
||||
setThreadName("SocketServer::listen");
|
||||
setThreadName("SocketServer::accept");
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (_stop) return;
|
||||
|
||||
// Use poll to check whether a new connection is in progress
|
||||
int timeoutMs = 10;
|
||||
int timeoutMs = -1;
|
||||
#ifdef _WIN32
|
||||
// select cannot be interrupted on Windows so we need to pass a small timeout
|
||||
timeoutMs = 10;
|
||||
#endif
|
||||
|
||||
bool readyToRead = true;
|
||||
auto selectInterrupt = std::make_unique<SelectInterrupt>();
|
||||
PollResultType pollResult =
|
||||
Socket::poll(readyToRead, timeoutMs, _serverFd, selectInterrupt);
|
||||
Socket::poll(readyToRead, timeoutMs, _serverFd, _acceptSelectInterrupt);
|
||||
|
||||
if (pollResult == PollResultType::Error)
|
||||
{
|
||||
@ -308,12 +331,14 @@ namespace ix
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo;
|
||||
// Retrieve connection info, the ip address of the remote peer/client)
|
||||
std::string remoteIp;
|
||||
int remotePort;
|
||||
|
||||
if (_addressFamily == AF_INET)
|
||||
{
|
||||
char remoteIp[INET_ADDRSTRLEN];
|
||||
if (inet_ntop(AF_INET, &client.sin_addr, remoteIp, INET_ADDRSTRLEN) == nullptr)
|
||||
char remoteIp4[INET_ADDRSTRLEN];
|
||||
if (inet_ntop(AF_INET, &client.sin_addr, remoteIp4, INET_ADDRSTRLEN) == nullptr)
|
||||
{
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
@ -326,12 +351,13 @@ namespace ix
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionInfo = std::make_unique<ConnectionInfo>(remoteIp, client.sin_port);
|
||||
remotePort = client.sin_port;
|
||||
remoteIp = remoteIp4;
|
||||
}
|
||||
else // AF_INET6
|
||||
{
|
||||
char remoteIp[INET6_ADDRSTRLEN];
|
||||
if (inet_ntop(AF_INET6, &client.sin_addr, remoteIp, INET6_ADDRSTRLEN) == nullptr)
|
||||
char remoteIp6[INET6_ADDRSTRLEN];
|
||||
if (inet_ntop(AF_INET6, &client.sin_addr, remoteIp6, INET6_ADDRSTRLEN) == nullptr)
|
||||
{
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
@ -344,7 +370,8 @@ namespace ix
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionInfo = std::make_unique<ConnectionInfo>(remoteIp, client.sin_port);
|
||||
remotePort = client.sin_port;
|
||||
remoteIp = remoteIp6;
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectionState> connectionState;
|
||||
@ -352,6 +379,9 @@ namespace ix
|
||||
{
|
||||
connectionState = _connectionStateFactory();
|
||||
}
|
||||
connectionState->setOnSetTerminatedCallback([this] { onSetTerminatedCallback(); });
|
||||
connectionState->setRemoteIp(remoteIp);
|
||||
connectionState->setRemotePort(remotePort);
|
||||
|
||||
if (_stop) return;
|
||||
|
||||
@ -379,13 +409,10 @@ namespace ix
|
||||
|
||||
// Launch the handleConnection work asynchronously in its own thread.
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
_connectionsThreads.push_back(
|
||||
std::make_pair(connectionState,
|
||||
std::thread(&SocketServer::handleConnection,
|
||||
this,
|
||||
std::move(socket),
|
||||
connectionState,
|
||||
std::move(connectionInfo))));
|
||||
_connectionsThreads.push_back(std::make_pair(
|
||||
connectionState,
|
||||
std::thread(
|
||||
&SocketServer::handleConnection, this, std::move(socket), connectionState)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,8 +438,14 @@ namespace ix
|
||||
break;
|
||||
}
|
||||
|
||||
// Sleep a little bit then keep cleaning up
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// Unless we are stopping the server, wait for a connection
|
||||
// to be terminated to run the threads GC, instead of busy waiting
|
||||
// with a sleep
|
||||
if (!_stopGc)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
|
||||
_conditionVariableGC.wait(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,4 +453,11 @@ namespace ix
|
||||
{
|
||||
_socketTLSOptions = socketTLSOptions;
|
||||
}
|
||||
|
||||
void SocketServer::onSetTerminatedCallback()
|
||||
{
|
||||
// a connection got terminated, we can run the connection thread GC,
|
||||
// so wake up the thread responsible for that
|
||||
_conditionVariableGC.notify_one();
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXConnectionInfo.h"
|
||||
#include "IXConnectionState.h"
|
||||
#include "IXSelectInterrupt.h"
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
@ -84,6 +84,7 @@ namespace ix
|
||||
// background thread to wait for incoming connections
|
||||
std::thread _thread;
|
||||
void run();
|
||||
void onSetTerminatedCallback();
|
||||
|
||||
// background thread to cleanup (join) terminated threads
|
||||
std::atomic<bool> _stopGc;
|
||||
@ -103,8 +104,7 @@ namespace ix
|
||||
ConnectionStateFactory _connectionStateFactory;
|
||||
|
||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) = 0;
|
||||
std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||
virtual size_t getConnectedClientsCount() = 0;
|
||||
|
||||
// Returns true if all connection threads are joined
|
||||
@ -112,5 +112,13 @@ namespace ix
|
||||
size_t getConnectionsThreadsCount();
|
||||
|
||||
SocketTLSOptions _socketTLSOptions;
|
||||
|
||||
// to wake up from select
|
||||
SelectInterruptPtr _acceptSelectInterrupt;
|
||||
|
||||
// used by the gc thread, to know that a thread needs to be garbage collected
|
||||
// as a connection
|
||||
std::condition_variable _conditionVariableGC;
|
||||
std::mutex _conditionVariableMutexGC;
|
||||
};
|
||||
} // namespace ix
|
||||
|
37
ixwebsocket/IXStrCaseCompare.cpp
Normal file
37
ixwebsocket/IXStrCaseCompare.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* IXStrCaseCompare.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXStrCaseCompare.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1,
|
||||
const unsigned char& c2) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
|
||||
#else
|
||||
return std::tolower(c1) < std::tolower(c2);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CaseInsensitiveLess::cmp(const std::string& s1, const std::string& s2)
|
||||
{
|
||||
return std::lexicographical_compare(s1.begin(),
|
||||
s1.end(), // source range
|
||||
s2.begin(),
|
||||
s2.end(), // dest range
|
||||
NocaseCompare()); // comparison
|
||||
}
|
||||
|
||||
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const
|
||||
{
|
||||
return CaseInsensitiveLess::cmp(s1, s2);
|
||||
}
|
||||
} // namespace ix
|
25
ixwebsocket/IXStrCaseCompare.h
Normal file
25
ixwebsocket/IXStrCaseCompare.h
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* IXStrCaseCompare.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct CaseInsensitiveLess
|
||||
{
|
||||
// Case Insensitive compare_less binary function
|
||||
struct NocaseCompare
|
||||
{
|
||||
bool operator()(const unsigned char& c1, const unsigned char& c2) const;
|
||||
};
|
||||
|
||||
static bool cmp(const std::string& s1, const std::string& s2);
|
||||
|
||||
bool operator()(const std::string& s1, const std::string& s2) const;
|
||||
};
|
||||
} // namespace ix
|
18
ixwebsocket/IXUniquePtr.h
Normal file
18
ixwebsocket/IXUniquePtr.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* IXUniquePtr.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
template<typename T, typename... Args>
|
||||
std::unique_ptr<T> make_unique(Args&&... args)
|
||||
{
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
} // namespace ix
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "IXExponentialBackoff.h"
|
||||
#include "IXSetThreadName.h"
|
||||
#include "IXUniquePtr.h"
|
||||
#include "IXUtf8Validator.h"
|
||||
#include "IXWebSocketHandshake.h"
|
||||
#include <cassert>
|
||||
@ -34,12 +35,12 @@ namespace ix
|
||||
_ws.setOnCloseCallback(
|
||||
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
|
||||
_onMessageCallback(
|
||||
std::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
|
||||
"",
|
||||
wireSize,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(code, reason, remote)));
|
||||
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
|
||||
"",
|
||||
wireSize,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(code, reason, remote)));
|
||||
});
|
||||
}
|
||||
|
||||
@ -54,6 +55,7 @@ namespace ix
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
_url = url;
|
||||
}
|
||||
|
||||
void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
@ -194,7 +196,7 @@ namespace ix
|
||||
return status;
|
||||
}
|
||||
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(
|
||||
_onMessageCallback(ix::make_unique<WebSocketMessage>(
|
||||
WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
@ -226,12 +228,12 @@ namespace ix
|
||||
}
|
||||
|
||||
_onMessageCallback(
|
||||
std::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(status.uri, status.headers),
|
||||
WebSocketCloseInfo()));
|
||||
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(status.uri, status.headers),
|
||||
WebSocketCloseInfo()));
|
||||
|
||||
if (_pingIntervalSecs > 0)
|
||||
{
|
||||
@ -311,12 +313,12 @@ namespace ix
|
||||
connectErr.reason = status.errorStr;
|
||||
connectErr.http_status = status.http_status;
|
||||
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
|
||||
"",
|
||||
0,
|
||||
connectErr,
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo()));
|
||||
_onMessageCallback(ix::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
|
||||
"",
|
||||
0,
|
||||
connectErr,
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -387,13 +389,13 @@ namespace ix
|
||||
|
||||
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
|
||||
|
||||
_onMessageCallback(std::make_unique<WebSocketMessage>(webSocketMessageType,
|
||||
msg,
|
||||
wireSize,
|
||||
webSocketErrorInfo,
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(),
|
||||
binary));
|
||||
_onMessageCallback(ix::make_unique<WebSocketMessage>(webSocketMessageType,
|
||||
msg,
|
||||
wireSize,
|
||||
webSocketErrorInfo,
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(),
|
||||
binary));
|
||||
|
||||
WebSocket::invokeTrafficTrackerCallback(wireSize, true);
|
||||
});
|
||||
@ -405,6 +407,11 @@ namespace ix
|
||||
_onMessageCallback = callback;
|
||||
}
|
||||
|
||||
bool WebSocket::isOnMessageCallbackRegistered() const
|
||||
{
|
||||
return _onMessageCallback != nullptr;
|
||||
}
|
||||
|
||||
void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback)
|
||||
{
|
||||
_onTrafficTrackerCallback = callback;
|
||||
|
@ -84,6 +84,7 @@ namespace ix
|
||||
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
|
||||
|
||||
void setOnMessageCallback(const OnMessageCallback& callback);
|
||||
bool isOnMessageCallbackRegistered() const;
|
||||
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
||||
static void resetTrafficTrackerCallback();
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "IXUrlParser.h"
|
||||
#include "IXUserAgent.h"
|
||||
#include "IXWebSocketHandshakeKeyGen.h"
|
||||
#include "IXStrCaseCompare.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
@ -35,9 +36,7 @@ namespace ix
|
||||
|
||||
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
||||
{
|
||||
return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) {
|
||||
return tolower(a) == tolower(b);
|
||||
});
|
||||
return CaseInsensitiveLess::cmp(a, b);
|
||||
}
|
||||
|
||||
std::string WebSocketHandshake::genRandomString(const int len)
|
||||
@ -170,20 +169,11 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Expecting HTTP/1.1, got " << httpVersion << ". "
|
||||
<< "Rejecting connection to " << host << ":" << port << ", status: " << status
|
||||
<< "Rejecting connection to " << url << ", status: " << status
|
||||
<< ", HTTP Status line: " << line;
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
// We want an 101 HTTP status
|
||||
if (status != 101)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Expecting status 101 (Switching Protocol), got " << status
|
||||
<< " status connecting to " << host << ":" << port << ", HTTP Status line: " << line;
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
@ -193,6 +183,17 @@ namespace ix
|
||||
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
|
||||
}
|
||||
|
||||
// We want an 101 HTTP status for websocket, otherwise it could be
|
||||
// a redirection (like 301)
|
||||
if (status != 101)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Expecting status 101 (Switching Protocol), got " << status
|
||||
<< " status connecting to " << url << ", HTTP Status line: " << line;
|
||||
|
||||
return WebSocketInitResult(false, status, ss.str(), headers, path);
|
||||
}
|
||||
|
||||
// Check the presence of the connection field
|
||||
if (headers.find("connection") == headers.end())
|
||||
{
|
||||
|
@ -12,25 +12,6 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1,
|
||||
const unsigned char& c2) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
|
||||
#else
|
||||
return std::tolower(c1) < std::tolower(c2);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const
|
||||
{
|
||||
return std::lexicographical_compare(s1.begin(),
|
||||
s1.end(), // source range
|
||||
s2.begin(),
|
||||
s2.end(), // dest range
|
||||
NocaseCompare()); // comparison
|
||||
}
|
||||
|
||||
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
||||
std::unique_ptr<Socket>& socket, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
|
@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
#include "IXStrCaseCompare.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -15,17 +16,6 @@ namespace ix
|
||||
{
|
||||
class Socket;
|
||||
|
||||
struct CaseInsensitiveLess
|
||||
{
|
||||
// Case Insensitive compare_less binary function
|
||||
struct NocaseCompare
|
||||
{
|
||||
bool operator()(const unsigned char& c1, const unsigned char& c2) const;
|
||||
};
|
||||
|
||||
bool operator()(const std::string& s1, const std::string& s2) const;
|
||||
};
|
||||
|
||||
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
|
||||
|
||||
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
||||
|
@ -48,14 +48,15 @@
|
||||
|
||||
#include "IXWebSocketPerMessageDeflate.h"
|
||||
|
||||
#include "IXUniquePtr.h"
|
||||
#include "IXWebSocketPerMessageDeflateCodec.h"
|
||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||
|
||||
namespace ix
|
||||
{
|
||||
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
|
||||
: _compressor(std::make_unique<WebSocketPerMessageDeflateCompressor>())
|
||||
, _decompressor(std::make_unique<WebSocketPerMessageDeflateDecompressor>())
|
||||
: _compressor(ix::make_unique<WebSocketPerMessageDeflateCompressor>())
|
||||
, _decompressor(ix::make_unique<WebSocketPerMessageDeflateDecompressor>())
|
||||
{
|
||||
;
|
||||
}
|
||||
|
@ -16,8 +16,6 @@ namespace
|
||||
// is treated as a char* and the null termination (\x00) makes it
|
||||
// look like an empty string.
|
||||
const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
|
||||
|
||||
const int kBufferSize = 1 << 14;
|
||||
} // namespace
|
||||
|
||||
namespace ix
|
||||
@ -26,7 +24,6 @@ namespace ix
|
||||
// Compressor
|
||||
//
|
||||
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
|
||||
: _compressBufferSize(kBufferSize)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
memset(&_deflateState, 0, sizeof(_deflateState));
|
||||
@ -57,8 +54,6 @@ namespace ix
|
||||
|
||||
if (ret != Z_OK) return false;
|
||||
|
||||
_compressBuffer = std::make_unique<unsigned char[]>(_compressBufferSize);
|
||||
|
||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||
|
||||
return true;
|
||||
@ -145,14 +140,14 @@ namespace ix
|
||||
do
|
||||
{
|
||||
// Output to local buffer
|
||||
_deflateState.avail_out = (uInt) _compressBufferSize;
|
||||
_deflateState.next_out = _compressBuffer.get();
|
||||
_deflateState.avail_out = (uInt) _compressBuffer.size();
|
||||
_deflateState.next_out = &_compressBuffer.front();
|
||||
|
||||
deflate(&_deflateState, _flush);
|
||||
|
||||
output = _compressBufferSize - _deflateState.avail_out;
|
||||
output = _compressBuffer.size() - _deflateState.avail_out;
|
||||
|
||||
out.insert(out.end(), _compressBuffer.get(), _compressBuffer.get() + output);
|
||||
out.insert(out.end(), _compressBuffer.begin(), _compressBuffer.begin() + output);
|
||||
} while (_deflateState.avail_out == 0);
|
||||
|
||||
if (endsWithEmptyUnCompressedBlock(out))
|
||||
@ -170,7 +165,6 @@ namespace ix
|
||||
// Decompressor
|
||||
//
|
||||
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
|
||||
: _compressBufferSize(kBufferSize)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
memset(&_inflateState, 0, sizeof(_inflateState));
|
||||
@ -198,8 +192,6 @@ namespace ix
|
||||
|
||||
if (ret != Z_OK) return false;
|
||||
|
||||
_compressBuffer = std::make_unique<unsigned char[]>(_compressBufferSize);
|
||||
|
||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||
|
||||
return true;
|
||||
@ -232,8 +224,8 @@ namespace ix
|
||||
|
||||
do
|
||||
{
|
||||
_inflateState.avail_out = (uInt) _compressBufferSize;
|
||||
_inflateState.next_out = _compressBuffer.get();
|
||||
_inflateState.avail_out = (uInt) _compressBuffer.size();
|
||||
_inflateState.next_out = &_compressBuffer.front();
|
||||
|
||||
int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
|
||||
|
||||
@ -242,8 +234,8 @@ namespace ix
|
||||
return false; // zlib error
|
||||
}
|
||||
|
||||
out.append(reinterpret_cast<char*>(_compressBuffer.get()),
|
||||
_compressBufferSize - _inflateState.avail_out);
|
||||
out.append(reinterpret_cast<char*>(&_compressBuffer.front()),
|
||||
_compressBuffer.size() - _inflateState.avail_out);
|
||||
} while (_inflateState.avail_out == 0);
|
||||
|
||||
return true;
|
||||
|
@ -9,7 +9,7 @@
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
#include "zlib.h"
|
||||
#endif
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -34,8 +34,7 @@ namespace ix
|
||||
bool endsWithEmptyUnCompressedBlock(const T& value);
|
||||
|
||||
int _flush;
|
||||
size_t _compressBufferSize;
|
||||
std::unique_ptr<unsigned char[]> _compressBuffer;
|
||||
std::array<unsigned char, 1 << 14> _compressBuffer;
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
z_stream _deflateState;
|
||||
@ -53,8 +52,7 @@ namespace ix
|
||||
|
||||
private:
|
||||
int _flush;
|
||||
size_t _compressBufferSize;
|
||||
std::unique_ptr<unsigned char[]> _compressBuffer;
|
||||
std::array<unsigned char, 1 << 14> _compressBuffer;
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
z_stream _inflateState;
|
||||
|
@ -43,6 +43,7 @@ namespace ix
|
||||
const std::string& hostname,
|
||||
const ix::SocketTLSOptions& tlsOptions,
|
||||
const std::string& remoteUrl,
|
||||
const RemoteUrlsMapping& remoteUrlsMapping,
|
||||
bool /*verbose*/)
|
||||
{
|
||||
ix::WebSocketServer server(port, hostname);
|
||||
@ -53,61 +54,74 @@ namespace ix
|
||||
};
|
||||
server.setConnectionStateFactory(factory);
|
||||
|
||||
server.setOnConnectionCallback([remoteUrl](std::weak_ptr<ix::WebSocket> webSocket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
||||
auto state = std::dynamic_pointer_cast<ProxyConnectionState>(connectionState);
|
||||
auto remoteIp = connectionInfo->remoteIp;
|
||||
server.setOnConnectionCallback(
|
||||
[remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
|
||||
std::shared_ptr<ConnectionState> connectionState) {
|
||||
auto state = std::dynamic_pointer_cast<ProxyConnectionState>(connectionState);
|
||||
auto remoteIp = connectionState->getRemoteIp();
|
||||
|
||||
// Server connection
|
||||
state->webSocket().setOnMessageCallback(
|
||||
[webSocket, state, remoteIp](const WebSocketMessagePtr& msg) {
|
||||
if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
state->setTerminated();
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
auto ws = webSocket.lock();
|
||||
if (ws)
|
||||
// Server connection
|
||||
state->webSocket().setOnMessageCallback(
|
||||
[webSocket, state, remoteIp](const WebSocketMessagePtr& msg) {
|
||||
if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
ws->send(msg->str, msg->binary);
|
||||
state->setTerminated();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Client connection
|
||||
auto ws = webSocket.lock();
|
||||
if (ws)
|
||||
{
|
||||
ws->setOnMessageCallback([state, remoteUrl](const WebSocketMessagePtr& msg) {
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
// Connect to the 'real' server
|
||||
std::string url(remoteUrl);
|
||||
url += msg->openInfo.uri;
|
||||
state->webSocket().setUrl(url);
|
||||
state->webSocket().disableAutomaticReconnection();
|
||||
state->webSocket().start();
|
||||
|
||||
// we should sleep here for a bit until we've established the
|
||||
// connection with the remote server
|
||||
while (state->webSocket().getReadyState() != ReadyState::Open)
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
auto ws = webSocket.lock();
|
||||
if (ws)
|
||||
{
|
||||
ws->send(msg->str, msg->binary);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
state->webSocket().close(msg->closeInfo.code, msg->closeInfo.reason);
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
state->webSocket().send(msg->str, msg->binary);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Client connection
|
||||
auto ws = webSocket.lock();
|
||||
if (ws)
|
||||
{
|
||||
ws->setOnMessageCallback([state, remoteUrl, remoteUrlsMapping](
|
||||
const WebSocketMessagePtr& msg) {
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
// Connect to the 'real' server
|
||||
std::string url(remoteUrl);
|
||||
|
||||
// maybe we want a different url based on the mapping
|
||||
std::string host = msg->openInfo.headers["Host"];
|
||||
auto it = remoteUrlsMapping.find(host);
|
||||
if (it != remoteUrlsMapping.end())
|
||||
{
|
||||
url = it->second;
|
||||
}
|
||||
|
||||
// append the uri to form the full url
|
||||
// (say ws://localhost:1234/foo/?bar=baz)
|
||||
url += msg->openInfo.uri;
|
||||
|
||||
state->webSocket().setUrl(url);
|
||||
state->webSocket().disableAutomaticReconnection();
|
||||
state->webSocket().start();
|
||||
|
||||
// we should sleep here for a bit until we've established the
|
||||
// connection with the remote server
|
||||
while (state->webSocket().getReadyState() != ReadyState::Open)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
state->webSocket().close(msg->closeInfo.code, msg->closeInfo.reason);
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
state->webSocket().send(msg->str, msg->binary);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
auto res = server.listen();
|
||||
if (!res.first)
|
||||
|
@ -7,14 +7,18 @@
|
||||
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
using RemoteUrlsMapping = std::map<std::string, std::string>;
|
||||
|
||||
int websocket_proxy_server_main(int port,
|
||||
const std::string& hostname,
|
||||
const ix::SocketTLSOptions& tlsOptions,
|
||||
const std::string& remoteUrl,
|
||||
const RemoteUrlsMapping& remoteUrlsMapping,
|
||||
bool verbose);
|
||||
} // namespace ix
|
||||
|
@ -77,22 +77,29 @@ namespace ix
|
||||
}
|
||||
|
||||
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
setThreadName("WebSocketServer::" + connectionState->getId());
|
||||
|
||||
auto webSocket = std::make_shared<WebSocket>();
|
||||
if (_onConnectionCallback)
|
||||
{
|
||||
_onConnectionCallback(webSocket, connectionState, std::move(connectionInfo));
|
||||
_onConnectionCallback(webSocket, connectionState);
|
||||
|
||||
if (!webSocket->isOnMessageCallbackRegistered())
|
||||
{
|
||||
logError("WebSocketServer Application developer error: Server callback improperly "
|
||||
"registerered.");
|
||||
logError("Missing call to setOnMessageCallback inside setOnConnectionCallback.");
|
||||
connectionState->setTerminated();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (_onClientMessageCallback)
|
||||
{
|
||||
webSocket->setOnMessageCallback(
|
||||
[this, &ws = *webSocket.get(), connectionState, &ci = *connectionInfo.get()](
|
||||
const WebSocketMessagePtr& msg) {
|
||||
_onClientMessageCallback(connectionState, ci, ws, msg);
|
||||
[this, &ws = *webSocket.get(), connectionState](const WebSocketMessagePtr& msg) {
|
||||
_onClientMessageCallback(connectionState, ws, msg);
|
||||
});
|
||||
}
|
||||
else
|
||||
|
@ -23,14 +23,10 @@ namespace ix
|
||||
{
|
||||
public:
|
||||
using OnConnectionCallback =
|
||||
std::function<void(std::weak_ptr<WebSocket>,
|
||||
std::shared_ptr<ConnectionState>,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)>;
|
||||
std::function<void(std::weak_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
|
||||
|
||||
using OnClientMessageCallback = std::function<void(std::shared_ptr<ConnectionState>,
|
||||
ConnectionInfo&,
|
||||
WebSocket&,
|
||||
const WebSocketMessagePtr&)>;
|
||||
using OnClientMessageCallback = std::function<void(
|
||||
std::shared_ptr<ConnectionState>, WebSocket&, const WebSocketMessagePtr&)>;
|
||||
|
||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
@ -69,8 +65,7 @@ namespace ix
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo);
|
||||
std::shared_ptr<ConnectionState> connectionState);
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
#include "IXSocketFactory.h"
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include "IXUniquePtr.h"
|
||||
#include "IXUrlParser.h"
|
||||
#include "IXUtf8Validator.h"
|
||||
#include "IXWebSocketHandshake.h"
|
||||
@ -107,36 +108,62 @@ namespace ix
|
||||
|
||||
std::string protocol, host, path, query;
|
||||
int port;
|
||||
std::string remoteUrl(url);
|
||||
|
||||
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
||||
WebSocketInitResult result;
|
||||
const int maxRedirections = 10;
|
||||
|
||||
for (int i = 0; i < maxRedirections; ++i)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Could not parse url: '" << url << "'";
|
||||
return WebSocketInitResult(false, 0, ss.str());
|
||||
if (!UrlParser::parse(remoteUrl, protocol, host, path, query, port))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Could not parse url: '" << url << "'";
|
||||
return WebSocketInitResult(false, 0, ss.str());
|
||||
}
|
||||
|
||||
std::string errorMsg;
|
||||
bool tls = protocol == "wss";
|
||||
_socket = createSocket(tls, -1, errorMsg, _socketTLSOptions);
|
||||
_perMessageDeflate = ix::make_unique<WebSocketPerMessageDeflate>();
|
||||
|
||||
if (!_socket)
|
||||
{
|
||||
return WebSocketInitResult(false, 0, errorMsg);
|
||||
}
|
||||
|
||||
WebSocketHandshake webSocketHandshake(_requestInitCancellation,
|
||||
_socket,
|
||||
_perMessageDeflate,
|
||||
_perMessageDeflateOptions,
|
||||
_enablePerMessageDeflate);
|
||||
|
||||
result = webSocketHandshake.clientHandshake(
|
||||
remoteUrl, headers, host, path, port, timeoutSecs);
|
||||
|
||||
if (result.http_status >= 300 && result.http_status < 400)
|
||||
{
|
||||
auto it = result.headers.find("Location");
|
||||
if (it == result.headers.end())
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Missing Location Header for HTTP Redirect response. "
|
||||
<< "Rejecting connection to " << url << ", status: " << result.http_status;
|
||||
result.errorStr = ss.str();
|
||||
break;
|
||||
}
|
||||
|
||||
remoteUrl = it->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.success)
|
||||
{
|
||||
setReadyState(ReadyState::OPEN);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string errorMsg;
|
||||
bool tls = protocol == "wss";
|
||||
_socket = createSocket(tls, -1, errorMsg, _socketTLSOptions);
|
||||
_perMessageDeflate = std::make_unique<WebSocketPerMessageDeflate>();
|
||||
|
||||
if (!_socket)
|
||||
{
|
||||
return WebSocketInitResult(false, 0, errorMsg);
|
||||
}
|
||||
|
||||
WebSocketHandshake webSocketHandshake(_requestInitCancellation,
|
||||
_socket,
|
||||
_perMessageDeflate,
|
||||
_perMessageDeflateOptions,
|
||||
_enablePerMessageDeflate);
|
||||
|
||||
auto result =
|
||||
webSocketHandshake.clientHandshake(url, headers, host, path, port, timeoutSecs);
|
||||
if (result.success)
|
||||
{
|
||||
setReadyState(ReadyState::OPEN);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -151,7 +178,7 @@ namespace ix
|
||||
_blockingSend = true;
|
||||
|
||||
_socket = std::move(socket);
|
||||
_perMessageDeflate = std::make_unique<WebSocketPerMessageDeflate>();
|
||||
_perMessageDeflate = ix::make_unique<WebSocketPerMessageDeflate>();
|
||||
|
||||
WebSocketHandshake webSocketHandshake(_requestInitCancellation,
|
||||
_socket,
|
||||
@ -633,7 +660,7 @@ namespace ix
|
||||
// send back the CLOSE frame
|
||||
sendCloseFrame(code, reason);
|
||||
|
||||
wakeUpFromPoll(Socket::kCloseRequest);
|
||||
wakeUpFromPoll(SelectInterrupt::kCloseRequest);
|
||||
|
||||
bool remote = true;
|
||||
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
|
||||
@ -853,7 +880,7 @@ namespace ix
|
||||
// Request to flush the send buffer on the background thread if it isn't empty
|
||||
if (!isSendBufferEmpty())
|
||||
{
|
||||
wakeUpFromPoll(Socket::kSendRequest);
|
||||
wakeUpFromPoll(SelectInterrupt::kSendRequest);
|
||||
|
||||
// FIXME: we should have a timeout when sending large messages: see #131
|
||||
if (_blockingSend && !flushSendBuffer())
|
||||
@ -1122,7 +1149,7 @@ namespace ix
|
||||
sendCloseFrame(code, reason);
|
||||
|
||||
// wake up the poll, but do not close yet
|
||||
wakeUpFromPoll(Socket::kSendRequest);
|
||||
wakeUpFromPoll(SelectInterrupt::kSendRequest);
|
||||
}
|
||||
|
||||
size_t WebSocketTransport::bufferedAmount() const
|
||||
|
@ -6,4 +6,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define IX_WEBSOCKET_VERSION "10.1.1"
|
||||
#define IX_WEBSOCKET_VERSION "11.0.2"
|
||||
|
101
makefile
101
makefile
@ -28,16 +28,22 @@ brew:
|
||||
# server side ?) and I can't work-around it easily, so we're using mbedtls on
|
||||
# Linux for the SSL backend, which works great.
|
||||
ws_mbedtls_install:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install)
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install)
|
||||
|
||||
ws:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
|
||||
|
||||
ws_install:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=0 -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
|
||||
ws_unity:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
|
||||
|
||||
ws_openssl:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; make -j 4)
|
||||
ws_install:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
|
||||
|
||||
ws_install_release:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
|
||||
|
||||
ws_openssl_install:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install)
|
||||
|
||||
ws_mbedtls:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4)
|
||||
@ -45,6 +51,9 @@ ws_mbedtls:
|
||||
ws_no_ssl:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4)
|
||||
|
||||
ws_no_python:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j4 install)
|
||||
|
||||
uninstall:
|
||||
xargs rm -fv < build/install_manifest.txt
|
||||
|
||||
@ -101,71 +110,30 @@ format:
|
||||
test_server:
|
||||
(cd test && npm i ws && node broadcast-server.js)
|
||||
|
||||
# env TEST=Websocket_server make test
|
||||
# env TEST=Websocket_chat make test
|
||||
# env TEST=heartbeat make test
|
||||
build_test:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. ; ninja install)
|
||||
test:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 ..)
|
||||
(cd build ; ninja)
|
||||
(cd build ; ninja test)
|
||||
|
||||
test: build_test
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_make:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_tsan:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_ubsan:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableUndefinedBehaviorSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_asan: build_test_asan
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
build_test_asan:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableAddressSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
|
||||
test_tsan_openssl:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_ubsan_openssl:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableUndefinedBehaviorSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_tsan_openssl_release:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Release -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -configuration Release -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Release/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
test_asan:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer")
|
||||
(cd build ; ninja)
|
||||
(cd build ; ctest -V .)
|
||||
|
||||
test_tsan_mbedtls:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_MBED_TLS=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
||||
(cd build ; ninja)
|
||||
(cd build ; ninja test)
|
||||
|
||||
build_test_openssl:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_OPEN_SSL=1 -DUSE_TEST=1 .. ; ninja install)
|
||||
test_tsan_openssl:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
||||
(cd build ; ninja)
|
||||
(cd build ; ninja test)
|
||||
|
||||
test_openssl: build_test_openssl
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
build_test_mbedtls:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
|
||||
test_mbedtls: build_test_mbedtls
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_no_ssl:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TEST=1 .. ; make -j 4)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
test_tsan_sectransport:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
|
||||
(cd build ; ninja)
|
||||
(cd build ; ninja test)
|
||||
|
||||
ws_test: ws
|
||||
(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh)
|
||||
@ -241,6 +209,9 @@ doc:
|
||||
change: format
|
||||
vim ixwebsocket/IXWebSocketVersion.h docs/CHANGELOG.md
|
||||
|
||||
change_no_format:
|
||||
vim ixwebsocket/IXWebSocketVersion.h docs/CHANGELOG.md
|
||||
|
||||
commit:
|
||||
git commit -am "`sh tools/extract_latest_change.sh`"
|
||||
|
||||
|
@ -2,73 +2,44 @@
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
cmake_minimum_required (VERSION 3.4.1)
|
||||
cmake_minimum_required (VERSION 3.14)
|
||||
project (ixwebsocket_unittest)
|
||||
|
||||
set (CMAKE_CXX_STANDARD 14)
|
||||
set (CMAKE_CXX_STANDARD 11)
|
||||
|
||||
if (MAC)
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
||||
find_package(Sanitizers)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
option(USE_TLS "Add TLS support" ON)
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
${PROJECT_SOURCE_DIR}/Catch2/single_include
|
||||
../third_party
|
||||
../third_party/msgpack11
|
||||
../third_party/spdlog/include
|
||||
../ws
|
||||
)
|
||||
|
||||
add_definitions(-DSPDLOG_COMPILED_LIB=1)
|
||||
|
||||
find_package(JsonCpp)
|
||||
if (NOT JSONCPP_FOUND)
|
||||
include_directories(../third_party/jsoncpp)
|
||||
set(JSONCPP_SOURCES ../third_party/jsoncpp/jsoncpp.cpp)
|
||||
endif()
|
||||
option(USE_TLS "Add TLS support" ON)
|
||||
|
||||
# Shared sources
|
||||
set (SOURCES
|
||||
${JSONCPP_SOURCES}
|
||||
|
||||
test_runner.cpp
|
||||
IXTest.cpp
|
||||
../third_party/msgpack11/msgpack11.cpp
|
||||
|
||||
IXSocketTest.cpp
|
||||
IXSocketConnectTest.cpp
|
||||
set (TEST_TARGET_NAMES
|
||||
IXSocketTest
|
||||
IXSocketConnectTest
|
||||
# IXWebSocketLeakTest.cpp # commented until we have a fix for #224
|
||||
IXWebSocketServerTest.cpp
|
||||
IXWebSocketTestConnectionDisconnection.cpp
|
||||
IXUrlParserTest.cpp
|
||||
IXWebSocketServerTest.cpp
|
||||
IXHttpClientTest.cpp
|
||||
IXHttpServerTest.cpp
|
||||
IXUnityBuildsTest.cpp
|
||||
IXHttpTest.cpp
|
||||
IXDNSLookupTest.cpp
|
||||
IXWebSocketSubProtocolTest.cpp
|
||||
IXSentryClientTest.cpp
|
||||
IXWebSocketChatTest.cpp
|
||||
IXWebSocketBroadcastTest.cpp
|
||||
IXWebSocketPerMessageDeflateCompressorTest.cpp
|
||||
IXStreamSqlTest.cpp
|
||||
IXWebSocketServerTest
|
||||
IXWebSocketTestConnectionDisconnection
|
||||
IXUrlParserTest
|
||||
IXHttpClientTest
|
||||
IXHttpServerTest
|
||||
IXUnityBuildsTest
|
||||
IXHttpTest
|
||||
IXDNSLookupTest
|
||||
IXWebSocketSubProtocolTest
|
||||
IXSentryClientTest
|
||||
IXWebSocketChatTest
|
||||
IXWebSocketBroadcastTest
|
||||
IXWebSocketPerMessageDeflateCompressorTest
|
||||
IXStreamSqlTest
|
||||
)
|
||||
|
||||
# Some unittest don't work on windows yet
|
||||
# Windows without TLS does not have hmac yet
|
||||
if (UNIX)
|
||||
list(APPEND SOURCES
|
||||
IXWebSocketCloseTest.cpp
|
||||
IXCobraChatTest.cpp
|
||||
IXCobraMetricsPublisherTest.cpp
|
||||
IXCobraToSentryBotTest.cpp
|
||||
IXCobraToStatsdBotTest.cpp
|
||||
IXCobraToStdoutBotTest.cpp
|
||||
list(APPEND TEST_TARGET_NAMES
|
||||
IXWebSocketCloseTest
|
||||
IXCobraChatTest
|
||||
IXCobraMetricsPublisherTest
|
||||
IXCobraToSentryBotTest
|
||||
IXCobraToStatsdBotTest
|
||||
IXCobraToStdoutBotTest
|
||||
)
|
||||
endif()
|
||||
|
||||
@ -78,19 +49,9 @@ endif()
|
||||
|
||||
# Disable tests for now that are failing or not reliable
|
||||
|
||||
add_executable(ixwebsocket_unittest ${SOURCES})
|
||||
|
||||
if (MAC)
|
||||
add_sanitizers(ixwebsocket_unittest)
|
||||
endif()
|
||||
|
||||
if (APPLE AND USE_TLS)
|
||||
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
|
||||
endif()
|
||||
|
||||
if (JSONCPP_FOUND)
|
||||
target_include_directories(ixwebsocket_unittest PUBLIC ${JSONCPP_INCLUDE_DIRS})
|
||||
target_link_libraries(ixwebsocket_unittest ${JSONCPP_LIBRARIES})
|
||||
find_package(JsonCpp)
|
||||
if (NOT JSONCPP_FOUND)
|
||||
set(JSONCPP_SOURCES ../third_party/jsoncpp/jsoncpp.cpp)
|
||||
endif()
|
||||
|
||||
if (USE_PYTHON)
|
||||
@ -104,19 +65,69 @@ if (USE_PYTHON)
|
||||
message("Python_LIBRARIES:${Python_LIBRARIES}")
|
||||
endif()
|
||||
|
||||
# library with the most dependencies come first
|
||||
target_link_libraries(ixwebsocket_unittest ixbots)
|
||||
target_link_libraries(ixwebsocket_unittest ixsnake)
|
||||
target_link_libraries(ixwebsocket_unittest ixcobra)
|
||||
target_link_libraries(ixwebsocket_unittest ixsentry)
|
||||
target_link_libraries(ixwebsocket_unittest ixredis)
|
||||
target_link_libraries(ixwebsocket_unittest ixwebsocket)
|
||||
target_link_libraries(ixwebsocket_unittest ixcrypto)
|
||||
target_link_libraries(ixwebsocket_unittest ixcore)
|
||||
add_library(ixwebsocket_test)
|
||||
target_sources(ixwebsocket_test PRIVATE
|
||||
${JSONCPP_SOURCES}
|
||||
test_runner.cpp
|
||||
IXTest.cpp
|
||||
../third_party/msgpack11/msgpack11.cpp
|
||||
)
|
||||
target_compile_definitions(ixwebsocket_test PRIVATE ${TEST_PROGRAMS_DEFINITIONS})
|
||||
target_include_directories(ixwebsocket_test PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/Catch2/single_include
|
||||
../third_party
|
||||
)
|
||||
target_link_libraries(ixwebsocket_test ixsnake)
|
||||
target_link_libraries(ixwebsocket_test ixcobra)
|
||||
target_link_libraries(ixwebsocket_test ixwebsocket)
|
||||
target_link_libraries(ixwebsocket_test ixcrypto)
|
||||
target_link_libraries(ixwebsocket_test spdlog)
|
||||
|
||||
target_link_libraries(ixwebsocket_unittest spdlog)
|
||||
if (USE_PYTHON)
|
||||
target_link_libraries(ixwebsocket_unittest ${Python_LIBRARIES})
|
||||
endif()
|
||||
foreach(TEST_TARGET_NAME ${TEST_TARGET_NAMES})
|
||||
add_executable(${TEST_TARGET_NAME}
|
||||
${TEST_TARGET_NAME}.cpp
|
||||
)
|
||||
|
||||
install(TARGETS ixwebsocket_unittest DESTINATION bin)
|
||||
target_include_directories(${TEST_TARGET_NAME} PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/Catch2/single_include
|
||||
../third_party
|
||||
../third_party/msgpack11
|
||||
)
|
||||
|
||||
target_compile_definitions(${TEST_TARGET_NAME} PRIVATE SPDLOG_COMPILED_LIB=1)
|
||||
|
||||
if (NOT JSONCPP_FOUND)
|
||||
target_include_directories(${TEST_TARGET_NAME} PRIVATE ../third_party/jsoncpp)
|
||||
endif()
|
||||
|
||||
if (APPLE AND USE_TLS)
|
||||
target_link_libraries(${TEST_TARGET_NAME} "-framework foundation" "-framework security")
|
||||
endif()
|
||||
|
||||
# library with the most dependencies come first
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixwebsocket_test)
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixbots)
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixsnake)
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixcobra)
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixsentry)
|
||||
|
||||
if (JSONCPP_FOUND)
|
||||
target_include_directories(${TEST_TARGET_NAME} PRIVATE ${JSONCPP_INCLUDE_DIRS})
|
||||
target_link_libraries(${TEST_TARGET_NAME} ${JSONCPP_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixredis)
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixwebsocket)
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixcrypto)
|
||||
target_link_libraries(${TEST_TARGET_NAME} ixcore)
|
||||
|
||||
target_link_libraries(${TEST_TARGET_NAME} spdlog)
|
||||
if (USE_PYTHON)
|
||||
target_link_libraries(${TEST_TARGET_NAME} ${Python_LIBRARIES})
|
||||
endif()
|
||||
|
||||
add_test(NAME ${TEST_TARGET_NAME}
|
||||
COMMAND ${TEST_TARGET_NAME}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
endforeach()
|
||||
|
@ -38,35 +38,6 @@ namespace
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void runPublisher(const ix::CobraConfig& config, const std::string& channel)
|
||||
{
|
||||
ix::CobraMetricsPublisher cobraMetricsPublisher;
|
||||
cobraMetricsPublisher.configure(config, channel);
|
||||
cobraMetricsPublisher.setSession(uuid4());
|
||||
cobraMetricsPublisher.enable(true);
|
||||
|
||||
Json::Value msg;
|
||||
msg["fps"] = 60;
|
||||
|
||||
cobraMetricsPublisher.setGenericAttributes("game", "ody");
|
||||
|
||||
// Wait a bit
|
||||
ix::msleep(500);
|
||||
|
||||
// publish some messages
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #1)
|
||||
cobraMetricsPublisher.push("sms_metric_B_id", msg); // (msg #2)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #3)
|
||||
cobraMetricsPublisher.push("sms_metric_D_id", msg); // (msg #4)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #4)
|
||||
cobraMetricsPublisher.push("sms_metric_F_id", msg); // (msg #5)
|
||||
ix::msleep(500);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
||||
@ -95,15 +66,14 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
||||
|
||||
sentryServer.setOnConnectionCallback(
|
||||
[](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionInfo->remoteIp << ":" << connectionInfo->remotePort << " "
|
||||
<< request->method << " " << request->headers["User-Agent"] << " "
|
||||
ss << connectionState->getRemoteIp() << ":" << connectionState->getRemotePort()
|
||||
<< " " << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri;
|
||||
|
||||
if (request->method == "POST")
|
||||
|
@ -20,38 +20,6 @@
|
||||
|
||||
using namespace ix;
|
||||
|
||||
namespace
|
||||
{
|
||||
void runPublisher(const ix::CobraConfig& config, const std::string& channel)
|
||||
{
|
||||
ix::CobraMetricsPublisher cobraMetricsPublisher;
|
||||
cobraMetricsPublisher.configure(config, channel);
|
||||
cobraMetricsPublisher.setSession(uuid4());
|
||||
cobraMetricsPublisher.enable(true);
|
||||
|
||||
Json::Value msg;
|
||||
msg["fps"] = 60;
|
||||
|
||||
cobraMetricsPublisher.setGenericAttributes("game", "ody");
|
||||
|
||||
// Wait a bit
|
||||
ix::msleep(500);
|
||||
|
||||
// publish some messages
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #1)
|
||||
cobraMetricsPublisher.push("sms_metric_B_id", msg); // (msg #2)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #3)
|
||||
cobraMetricsPublisher.push("sms_metric_D_id", msg); // (msg #4)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #4)
|
||||
cobraMetricsPublisher.push("sms_metric_F_id", msg); // (msg #5)
|
||||
ix::msleep(500);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Cobra_to_statsd_bot", "[cobra_bots]")
|
||||
{
|
||||
SECTION("Exchange and count sent/received messages.")
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include <iostream>
|
||||
#include <ixbots/IXCobraToStdoutBot.h>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcobra/IXCobraMetricsPublisher.h>
|
||||
#include <ixcrypto/IXUuid.h>
|
||||
#include <ixredis/IXRedisServer.h>
|
||||
#include <ixsentry/IXSentryClient.h>
|
||||
@ -20,38 +19,6 @@
|
||||
|
||||
using namespace ix;
|
||||
|
||||
namespace
|
||||
{
|
||||
void runPublisher(const ix::CobraConfig& config, const std::string& channel)
|
||||
{
|
||||
ix::CobraMetricsPublisher cobraMetricsPublisher;
|
||||
cobraMetricsPublisher.configure(config, channel);
|
||||
cobraMetricsPublisher.setSession(uuid4());
|
||||
cobraMetricsPublisher.enable(true);
|
||||
|
||||
Json::Value msg;
|
||||
msg["fps"] = 60;
|
||||
|
||||
cobraMetricsPublisher.setGenericAttributes("game", "ody");
|
||||
|
||||
// Wait a bit
|
||||
ix::msleep(500);
|
||||
|
||||
// publish some messages
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #1)
|
||||
cobraMetricsPublisher.push("sms_metric_B_id", msg); // (msg #2)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #3)
|
||||
cobraMetricsPublisher.push("sms_metric_D_id", msg); // (msg #4)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #4)
|
||||
cobraMetricsPublisher.push("sms_metric_F_id", msg); // (msg #5)
|
||||
ix::msleep(500);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Cobra_to_stdout_bot", "[cobra_bots]")
|
||||
{
|
||||
SECTION("Exchange and count sent/received messages.")
|
||||
|
@ -24,6 +24,8 @@ TEST_CASE("dns", "[net]")
|
||||
res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||
std::cerr << "Error message: " << errMsg << std::endl;
|
||||
REQUIRE(res != nullptr);
|
||||
|
||||
dnsLookup->release(res);
|
||||
}
|
||||
|
||||
SECTION("Test resolving a non-existing hostname")
|
||||
|
@ -63,6 +63,54 @@ TEST_CASE("http server", "[httpd]")
|
||||
|
||||
server.stop();
|
||||
}
|
||||
|
||||
SECTION("Posting plain text data to a local HTTP server")
|
||||
{
|
||||
int port = getFreePort();
|
||||
ix::HttpServer server(port, "127.0.0.1");
|
||||
|
||||
server.setOnConnectionCallback(
|
||||
[](HttpRequestPtr request, std::shared_ptr<ConnectionState>) -> HttpResponsePtr {
|
||||
if (request->method == "POST")
|
||||
{
|
||||
return std::make_shared<HttpResponse>(
|
||||
200, "OK", HttpErrorCode::Ok, WebSocketHttpHeaders(), request->body);
|
||||
}
|
||||
|
||||
return std::make_shared<HttpResponse>(400, "BAD REQUEST");
|
||||
});
|
||||
|
||||
auto res = server.listen();
|
||||
REQUIRE(res.first);
|
||||
server.start();
|
||||
|
||||
HttpClient httpClient;
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Content-Type"] = "text/plain";
|
||||
|
||||
std::string url("http://127.0.0.1:");
|
||||
url += std::to_string(port);
|
||||
auto args = httpClient.createRequest(url);
|
||||
|
||||
args->extraHeaders = headers;
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 60;
|
||||
args->verbose = true;
|
||||
args->logger = [](const std::string& msg) { std::cout << msg; };
|
||||
args->body = "Hello World!";
|
||||
|
||||
auto response = httpClient.post(url, args->body, args);
|
||||
|
||||
std::cerr << "Status: " << response->statusCode << std::endl;
|
||||
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
||||
std::cerr << "Body: " << response->body << std::endl;
|
||||
|
||||
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
||||
REQUIRE(response->statusCode == 200);
|
||||
REQUIRE(response->body == args->body);
|
||||
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("http server redirection", "[httpd_redirect]")
|
||||
|
@ -10,6 +10,8 @@
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <ixcobra/IXCobraMetricsPublisher.h>
|
||||
#include <ixcrypto/IXUuid.h>
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <mutex>
|
||||
@ -85,11 +87,10 @@ namespace ix
|
||||
bool startWebSocketEchoServer(ix::WebSocketServer& server)
|
||||
{
|
||||
server.setOnClientMessageCallback(
|
||||
[&server](std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
ConnectionInfo& connectionInfo,
|
||||
[&server](std::shared_ptr<ConnectionState> connectionState,
|
||||
WebSocket& webSocket,
|
||||
const ix::WebSocketMessagePtr& msg) {
|
||||
auto remoteIp = connectionInfo.remoteIp;
|
||||
auto remoteIp = connectionState->getRemoteIp();
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
TLogger() << "New connection";
|
||||
@ -244,4 +245,33 @@ namespace ix
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
void runPublisher(const ix::CobraConfig& config, const std::string& channel)
|
||||
{
|
||||
ix::CobraMetricsPublisher cobraMetricsPublisher;
|
||||
cobraMetricsPublisher.configure(config, channel);
|
||||
cobraMetricsPublisher.setSession(uuid4());
|
||||
cobraMetricsPublisher.enable(true);
|
||||
|
||||
Json::Value msg;
|
||||
msg["fps"] = 60;
|
||||
|
||||
cobraMetricsPublisher.setGenericAttributes("game", "ody");
|
||||
|
||||
// Wait a bit
|
||||
ix::msleep(500);
|
||||
|
||||
// publish some messages
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #1)
|
||||
cobraMetricsPublisher.push("sms_metric_B_id", msg); // (msg #2)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #3)
|
||||
cobraMetricsPublisher.push("sms_metric_D_id", msg); // (msg #4)
|
||||
ix::msleep(500);
|
||||
|
||||
cobraMetricsPublisher.push("sms_metric_A_id", msg); // (msg #4)
|
||||
cobraMetricsPublisher.push("sms_metric_F_id", msg); // (msg #5)
|
||||
ix::msleep(500);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <ixsnake/IXAppConfig.h>
|
||||
#include <ixwebsocket/IXGetFreePort.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
@ -59,4 +60,6 @@ namespace ix
|
||||
std::string getWsScheme(bool preferTLS);
|
||||
|
||||
std::string makeCobraEndpoint(int port, bool preferTLS);
|
||||
|
||||
void runPublisher(const ix::CobraConfig& config, const std::string& channel);
|
||||
} // namespace ix
|
||||
|
@ -18,10 +18,10 @@ using namespace ix;
|
||||
|
||||
namespace
|
||||
{
|
||||
class WebSocketChat
|
||||
class WebSocketBroadcastChat
|
||||
{
|
||||
public:
|
||||
WebSocketChat(const std::string& user, const std::string& session, int port);
|
||||
WebSocketBroadcastChat(const std::string& user, const std::string& session, int port);
|
||||
|
||||
void subscribe(const std::string& channel);
|
||||
void start();
|
||||
@ -47,7 +47,9 @@ namespace
|
||||
mutable std::mutex _mutex;
|
||||
};
|
||||
|
||||
WebSocketChat::WebSocketChat(const std::string& user, const std::string& session, int port)
|
||||
WebSocketBroadcastChat::WebSocketBroadcastChat(const std::string& user,
|
||||
const std::string& session,
|
||||
int port)
|
||||
: _user(user)
|
||||
, _session(session)
|
||||
, _port(port)
|
||||
@ -55,35 +57,35 @@ namespace
|
||||
_webSocket.setTLSOptions(makeClientTLSOptions());
|
||||
}
|
||||
|
||||
size_t WebSocketChat::getReceivedMessagesCount() const
|
||||
size_t WebSocketBroadcastChat::getReceivedMessagesCount() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _receivedMessages.size();
|
||||
}
|
||||
|
||||
const std::vector<std::string>& WebSocketChat::getReceivedMessages() const
|
||||
const std::vector<std::string>& WebSocketBroadcastChat::getReceivedMessages() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _receivedMessages;
|
||||
}
|
||||
|
||||
void WebSocketChat::appendMessage(const std::string& message)
|
||||
void WebSocketBroadcastChat::appendMessage(const std::string& message)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_receivedMessages.push_back(message);
|
||||
}
|
||||
|
||||
bool WebSocketChat::isReady() const
|
||||
bool WebSocketBroadcastChat::isReady() const
|
||||
{
|
||||
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||
}
|
||||
|
||||
void WebSocketChat::stop()
|
||||
void WebSocketBroadcastChat::stop()
|
||||
{
|
||||
_webSocket.stop();
|
||||
}
|
||||
|
||||
void WebSocketChat::start()
|
||||
void WebSocketBroadcastChat::start()
|
||||
{
|
||||
std::string url;
|
||||
{
|
||||
@ -156,7 +158,8 @@ namespace
|
||||
_webSocket.start();
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> WebSocketChat::decodeMessage(const std::string& str)
|
||||
std::pair<std::string, std::string> WebSocketBroadcastChat::decodeMessage(
|
||||
const std::string& str)
|
||||
{
|
||||
std::string errMsg;
|
||||
MsgPack msg = MsgPack::parse(str, errMsg);
|
||||
@ -167,7 +170,7 @@ namespace
|
||||
return std::pair<std::string, std::string>(msg_user, msg_text);
|
||||
}
|
||||
|
||||
std::string WebSocketChat::encodeMessage(const std::string& text)
|
||||
std::string WebSocketBroadcastChat::encodeMessage(const std::string& text)
|
||||
{
|
||||
std::map<MsgPack, MsgPack> obj;
|
||||
obj["user"] = _user;
|
||||
@ -179,7 +182,7 @@ namespace
|
||||
return output;
|
||||
}
|
||||
|
||||
void WebSocketChat::sendMessage(const std::string& text)
|
||||
void WebSocketBroadcastChat::sendMessage(const std::string& text)
|
||||
{
|
||||
_webSocket.sendBinary(encodeMessage(text));
|
||||
}
|
||||
@ -191,11 +194,9 @@ namespace
|
||||
|
||||
server.setOnClientMessageCallback(
|
||||
[&server, &connectionId](std::shared_ptr<ConnectionState> connectionState,
|
||||
ConnectionInfo& connectionInfo,
|
||||
WebSocket& webSocket,
|
||||
const ix::WebSocketMessagePtr& msg) {
|
||||
auto remoteIp = connectionInfo.remoteIp;
|
||||
|
||||
auto remoteIp = connectionState->getRemoteIp();
|
||||
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
@ -250,11 +251,11 @@ TEST_CASE("Websocket_broadcast_server", "[websocket_server]")
|
||||
REQUIRE(startServer(server, connectionId));
|
||||
|
||||
std::string session = ix::generateSessionId();
|
||||
std::vector<std::shared_ptr<WebSocketChat>> chatClients;
|
||||
std::vector<std::shared_ptr<WebSocketBroadcastChat>> chatClients;
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
std::string user("user_" + std::to_string(i));
|
||||
chatClients.push_back(std::make_shared<WebSocketChat>(user, session, port));
|
||||
chatClients.push_back(std::make_shared<WebSocketBroadcastChat>(user, session, port));
|
||||
chatClients[i]->start();
|
||||
ix::msleep(50);
|
||||
}
|
||||
|
@ -195,10 +195,9 @@ namespace
|
||||
{
|
||||
server.setOnClientMessageCallback(
|
||||
[&server](std::shared_ptr<ConnectionState> connectionState,
|
||||
ConnectionInfo& connectionInfo,
|
||||
WebSocket& webSocket,
|
||||
const ix::WebSocketMessagePtr& msg) {
|
||||
auto remoteIp = connectionInfo.remoteIp;
|
||||
auto remoteIp = connectionState->getRemoteIp();
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
TLogger() << "New connection";
|
||||
@ -286,27 +285,27 @@ TEST_CASE("Websocket_chat", "[websocket_chat]")
|
||||
int attempts = 0;
|
||||
while (chatA.getReceivedMessagesCount() != 3 || chatB.getReceivedMessagesCount() != 3)
|
||||
{
|
||||
REQUIRE(attempts++ < 10);
|
||||
CHECK(attempts++ < 10);
|
||||
ix::msleep(1000);
|
||||
}
|
||||
|
||||
chatA.stop();
|
||||
chatB.stop();
|
||||
|
||||
REQUIRE(chatA.getReceivedMessagesCount() == 3);
|
||||
REQUIRE(chatB.getReceivedMessagesCount() == 3);
|
||||
CHECK(chatA.getReceivedMessagesCount() == 3);
|
||||
CHECK(chatB.getReceivedMessagesCount() == 3);
|
||||
|
||||
REQUIRE(chatB.getReceivedMessages()[0] == "from A1");
|
||||
REQUIRE(chatB.getReceivedMessages()[1] == "from A2");
|
||||
REQUIRE(chatB.getReceivedMessages()[2] == "from A3");
|
||||
CHECK(chatB.getReceivedMessages()[0] == "from A1");
|
||||
CHECK(chatB.getReceivedMessages()[1] == "from A2");
|
||||
CHECK(chatB.getReceivedMessages()[2] == "from A3");
|
||||
|
||||
REQUIRE(chatA.getReceivedMessages()[0] == "from B1");
|
||||
REQUIRE(chatA.getReceivedMessages()[1] == "from B2");
|
||||
REQUIRE(chatA.getReceivedMessages()[2].size() == bigMessage.size());
|
||||
CHECK(chatA.getReceivedMessages()[0] == "from B1");
|
||||
CHECK(chatA.getReceivedMessages()[1] == "from B2");
|
||||
CHECK(chatA.getReceivedMessages()[2].size() == bigMessage.size());
|
||||
|
||||
// Give us 1000ms for the server to notice that clients went away
|
||||
ix::msleep(1000);
|
||||
REQUIRE(server.getClients().size() == 0);
|
||||
CHECK(server.getClients().size() == 0);
|
||||
|
||||
ix::reportWebSocketTraffic();
|
||||
}
|
||||
|
@ -171,10 +171,9 @@ namespace
|
||||
server.setOnClientMessageCallback(
|
||||
[&receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
ConnectionInfo& connectionInfo,
|
||||
WebSocket& /*webSocket*/,
|
||||
const ix::WebSocketMessagePtr& msg) {
|
||||
auto remoteIp = connectionInfo.remoteIp;
|
||||
auto remoteIp = connectionState->getRemoteIp();
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
TLogger() << "New server connection";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user