Compare commits
39 Commits
v9.9.0
...
feature/zl
Author | SHA1 | Date | |
---|---|---|---|
|
5036e338c7 | ||
|
80fb8cfb59 | ||
|
43c0ae0812 | ||
|
dcc447ec4a | ||
|
5127094f0e | ||
|
2e3d625c1e | ||
|
029289413c | ||
|
4d51098c86 | ||
|
c2b05af022 | ||
|
e85f975ab0 | ||
|
dc77d62a5d | ||
|
4f41f209a2 | ||
|
5940e53d77 | ||
|
22dffd5b7e | ||
|
af2f31045d | ||
|
5daa59f9f3 | ||
|
2ea9d06a93 | ||
|
847fc142d1 | ||
|
0388459bd0 | ||
|
9a47ec1217 | ||
|
45a40c8640 | ||
|
e34f1c30d6 | ||
|
c14a4c0e3e | ||
|
b146e93a3a | ||
|
9957ec9724 | ||
|
78a42f61bd | ||
|
e78019dad6 | ||
|
0f026c5da2 | ||
|
c26a2d5d39 | ||
|
2798886c0b | ||
|
ffde283a4b | ||
|
f7031d0d3e | ||
|
595e6c57df | ||
|
87709c201e | ||
|
e70d83ace1 | ||
|
ca829a3a98 | ||
|
26a1e63626 | ||
|
c98959b895 | ||
|
baf18648e9 |
13
.github/workflows/unittest_linux.yml
vendored
13
.github/workflows/unittest_linux.yml
vendored
@@ -1,13 +0,0 @@
|
|||||||
name: linux
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
linux:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: make test_make
|
|
||||||
run: make test_make
|
|
15
.github/workflows/unittest_mac_tsan_mbedtls.yml
vendored
15
.github/workflows/unittest_mac_tsan_mbedtls.yml
vendored
@@ -1,15 +0,0 @@
|
|||||||
name: mac_tsan_mbedtls
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
mac_tsan_mbedtls:
|
|
||||||
runs-on: macOS-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: install mbedtls
|
|
||||||
run: brew install mbedtls
|
|
||||||
- name: make test
|
|
||||||
run: make test_tsan_mbedtls
|
|
15
.github/workflows/unittest_mac_tsan_openssl.yml
vendored
15
.github/workflows/unittest_mac_tsan_openssl.yml
vendored
@@ -1,15 +0,0 @@
|
|||||||
name: mac_tsan_openssl
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
mac_tsan_openssl:
|
|
||||||
runs-on: macOS-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: install openssl
|
|
||||||
run: brew install openssl@1.1
|
|
||||||
- name: make test
|
|
||||||
run: make test_tsan_openssl
|
|
@@ -1,13 +0,0 @@
|
|||||||
name: mac_tsan_sectransport
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
mac_tsan_sectransport:
|
|
||||||
runs-on: macOS-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: make test_tsan
|
|
||||||
run: make test_tsan
|
|
40
.github/workflows/unittest_uwp.yml
vendored
40
.github/workflows/unittest_uwp.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
name: uwp
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
uwp:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
|
||||||
- run: |
|
|
||||||
vcpkg install zlib:x64-uwp
|
|
||||||
- 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 ..
|
|
||||||
- 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
|
|
6
.github/workflows/unittest_windows.yml
vendored
6
.github/workflows/unittest_windows.yml
vendored
@@ -10,10 +10,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||||
- run: |
|
|
||||||
vcpkg install zlib:x64-windows
|
|
||||||
- run: |
|
- run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 ..
|
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 ..
|
||||||
- run: cmake --build build
|
- run: cmake --build build
|
||||||
|
- run: ../build/test/ixwebsocket_unittest.exe
|
||||||
|
working-directory: test
|
||||||
|
@@ -30,12 +30,15 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXConnectionState.cpp
|
ixwebsocket/IXConnectionState.cpp
|
||||||
ixwebsocket/IXDNSLookup.cpp
|
ixwebsocket/IXDNSLookup.cpp
|
||||||
ixwebsocket/IXExponentialBackoff.cpp
|
ixwebsocket/IXExponentialBackoff.cpp
|
||||||
|
ixwebsocket/IXGetFreePort.cpp
|
||||||
ixwebsocket/IXHttp.cpp
|
ixwebsocket/IXHttp.cpp
|
||||||
ixwebsocket/IXHttpClient.cpp
|
ixwebsocket/IXHttpClient.cpp
|
||||||
ixwebsocket/IXHttpServer.cpp
|
ixwebsocket/IXHttpServer.cpp
|
||||||
ixwebsocket/IXNetSystem.cpp
|
ixwebsocket/IXNetSystem.cpp
|
||||||
ixwebsocket/IXSelectInterrupt.cpp
|
ixwebsocket/IXSelectInterrupt.cpp
|
||||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||||
|
ixwebsocket/IXSelectInterruptPipe.cpp
|
||||||
|
ixwebsocket/IXSetThreadName.cpp
|
||||||
ixwebsocket/IXSocket.cpp
|
ixwebsocket/IXSocket.cpp
|
||||||
ixwebsocket/IXSocketConnect.cpp
|
ixwebsocket/IXSocketConnect.cpp
|
||||||
ixwebsocket/IXSocketFactory.cpp
|
ixwebsocket/IXSocketFactory.cpp
|
||||||
@@ -51,6 +54,7 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
||||||
|
ixwebsocket/IXWebSocketProxyServer.cpp
|
||||||
ixwebsocket/IXWebSocketServer.cpp
|
ixwebsocket/IXWebSocketServer.cpp
|
||||||
ixwebsocket/IXWebSocketTransport.cpp
|
ixwebsocket/IXWebSocketTransport.cpp
|
||||||
)
|
)
|
||||||
@@ -62,6 +66,7 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXConnectionState.h
|
ixwebsocket/IXConnectionState.h
|
||||||
ixwebsocket/IXDNSLookup.h
|
ixwebsocket/IXDNSLookup.h
|
||||||
ixwebsocket/IXExponentialBackoff.h
|
ixwebsocket/IXExponentialBackoff.h
|
||||||
|
ixwebsocket/IXGetFreePort.h
|
||||||
ixwebsocket/IXHttp.h
|
ixwebsocket/IXHttp.h
|
||||||
ixwebsocket/IXHttpClient.h
|
ixwebsocket/IXHttpClient.h
|
||||||
ixwebsocket/IXHttpServer.h
|
ixwebsocket/IXHttpServer.h
|
||||||
@@ -69,6 +74,7 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXProgressCallback.h
|
ixwebsocket/IXProgressCallback.h
|
||||||
ixwebsocket/IXSelectInterrupt.h
|
ixwebsocket/IXSelectInterrupt.h
|
||||||
ixwebsocket/IXSelectInterruptFactory.h
|
ixwebsocket/IXSelectInterruptFactory.h
|
||||||
|
ixwebsocket/IXSelectInterruptPipe.h
|
||||||
ixwebsocket/IXSetThreadName.h
|
ixwebsocket/IXSetThreadName.h
|
||||||
ixwebsocket/IXSocket.h
|
ixwebsocket/IXSocket.h
|
||||||
ixwebsocket/IXSocketConnect.h
|
ixwebsocket/IXSocketConnect.h
|
||||||
@@ -93,29 +99,13 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
|
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
|
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
|
||||||
|
ixwebsocket/IXWebSocketProxyServer.h
|
||||||
ixwebsocket/IXWebSocketSendInfo.h
|
ixwebsocket/IXWebSocketSendInfo.h
|
||||||
ixwebsocket/IXWebSocketServer.h
|
ixwebsocket/IXWebSocketServer.h
|
||||||
ixwebsocket/IXWebSocketTransport.h
|
ixwebsocket/IXWebSocketTransport.h
|
||||||
ixwebsocket/IXWebSocketVersion.h
|
ixwebsocket/IXWebSocketVersion.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (UNIX)
|
|
||||||
# Linux, Mac, iOS, Android
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.cpp )
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.h )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Platform specific code
|
|
||||||
if (APPLE)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
|
|
||||||
elseif (WIN32)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
|
|
||||||
elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp)
|
|
||||||
else()
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
option(USE_TLS "Enable TLS support" FALSE)
|
option(USE_TLS "Enable TLS support" FALSE)
|
||||||
|
|
||||||
if (USE_TLS)
|
if (USE_TLS)
|
||||||
@@ -200,10 +190,18 @@ if (USE_TLS)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Use ZLIB_ROOT CMake variable if you need to use your own zlib
|
option(USE_ZLIB "Enable zlib support" TRUE)
|
||||||
find_package(ZLIB REQUIRED)
|
|
||||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
if (USE_ZLIB)
|
||||||
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
|
# 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})
|
||||||
|
|
||||||
|
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
||||||
|
23
docker/Dockerfile.ubuntu_groovy
Normal file
23
docker/Dockerfile.ubuntu_groovy
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Build time
|
||||||
|
FROM ubuntu:groovy as build
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt-get update
|
||||||
|
|
||||||
|
RUN apt-get -y install g++ libssl-dev libz-dev make python ninja-build
|
||||||
|
RUN apt-get -y install cmake
|
||||||
|
RUN apt-get -y install gdb
|
||||||
|
|
||||||
|
COPY . /opt
|
||||||
|
WORKDIR /opt
|
||||||
|
|
||||||
|
#
|
||||||
|
# To use the container interactively for debugging/building
|
||||||
|
# 1. Build with
|
||||||
|
# CMD ["ls"]
|
||||||
|
# 2. Run with
|
||||||
|
# docker run --entrypoint sh -it docker-game-eng-dev.addsrv.com/ws:9.10.6
|
||||||
|
#
|
||||||
|
|
||||||
|
RUN ["make", "test"]
|
||||||
|
# CMD ["ls"]
|
@@ -1,6 +1,70 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All changes to this project will be documented in this file.
|
All changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [10.1.1] - 2020-07-29
|
||||||
|
|
||||||
|
(websocket client) onProgressCallback not called for short messages on a websocket (fix #233)
|
||||||
|
|
||||||
|
## [10.1.0] - 2020-07-29
|
||||||
|
|
||||||
|
(websocket client) heartbeat is not sent at the requested frequency (fix #232)
|
||||||
|
|
||||||
|
## [10.0.3] - 2020-07-28
|
||||||
|
|
||||||
|
compiler warning fixes
|
||||||
|
|
||||||
|
## [10.0.2] - 2020-07-28
|
||||||
|
|
||||||
|
(ixcobra) CobraConnection: unsubscribe from all subscriptions when disconnecting
|
||||||
|
|
||||||
|
## [10.0.1] - 2020-07-27
|
||||||
|
|
||||||
|
(socket utility) move ix::getFreePort to ixwebsocket library
|
||||||
|
|
||||||
|
## [10.0.0] - 2020-07-25
|
||||||
|
|
||||||
|
(ixwebsocket server) change legacy api with 2 nested callbacks, so that the first api takes a weak_ptr<WebSocket> as its first argument
|
||||||
|
|
||||||
|
## [9.10.7] - 2020-07-25
|
||||||
|
|
||||||
|
(ixwebsocket) add WebSocketProxyServer, from ws. Still need to make the interface better.
|
||||||
|
|
||||||
|
## [9.10.6] - 2020-07-24
|
||||||
|
|
||||||
|
(ws) port broadcast_server sub-command to the new server API
|
||||||
|
|
||||||
|
## [9.10.5] - 2020-07-24
|
||||||
|
|
||||||
|
(unittest) port most unittests to the new server API
|
||||||
|
|
||||||
|
## [9.10.3] - 2020-07-24
|
||||||
|
|
||||||
|
(ws) port ws transfer to the new server API
|
||||||
|
|
||||||
|
## [9.10.2] - 2020-07-24
|
||||||
|
|
||||||
|
(websocket client) reset WebSocketTransport onClose callback in the WebSocket destructor
|
||||||
|
|
||||||
|
## [9.10.1] - 2020-07-24
|
||||||
|
|
||||||
|
(websocket server) reset client websocket callback when the connection is closed
|
||||||
|
|
||||||
|
## [9.10.0] - 2020-07-23
|
||||||
|
|
||||||
|
(websocket server) add a new simpler API to handle client connections / that API does not trigger a memory leak while the previous one did
|
||||||
|
|
||||||
|
## [9.9.3] - 2020-07-17
|
||||||
|
|
||||||
|
(build) merge platform specific files which were used to have different implementations for setting a thread name into a single file, to make it easier to include every source files and build the ixwebsocket library (fix #226)
|
||||||
|
|
||||||
|
## [9.9.2] - 2020-07-10
|
||||||
|
|
||||||
|
(socket server) bump default max connection count from 32 to 128
|
||||||
|
|
||||||
|
## [9.9.1] - 2020-07-10
|
||||||
|
|
||||||
|
(snake) implement super simple stream sql expression support in snake server
|
||||||
|
|
||||||
## [9.9.0] - 2020-07-08
|
## [9.9.0] - 2020-07-08
|
||||||
|
|
||||||
(socket+websocket+http+redis+snake servers) expose the remote ip and remote port when a new connection is made
|
(socket+websocket+http+redis+snake servers) expose the remote ip and remote port when a new connection is made
|
||||||
|
134
docs/usage.md
134
docs/usage.md
@@ -246,6 +246,10 @@ uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
|
|||||||
|
|
||||||
## WebSocket server API
|
## WebSocket server API
|
||||||
|
|
||||||
|
### Legacy api
|
||||||
|
|
||||||
|
This api was actually changed to take a weak_ptr<WebSocket> as the first argument to setOnConnectionCallback ; previously it would take a shared_ptr<WebSocket> which was creating cycles and then memory leaks problems.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
|
||||||
@@ -256,41 +260,49 @@ uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
|
|||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<WebSocket> webSocket,
|
[&server](std::weak_ptr<WebSocket> webSocket,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||||
{
|
{
|
||||||
std::cout << "Remote ip: " << connectionInfo->remoteIp << std::endl;
|
std::cout << "Remote ip: " << connectionInfo->remoteIp << std::endl;
|
||||||
|
|
||||||
webSocket->setOnMessageCallback(
|
auto ws = webSocket.lock();
|
||||||
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
|
if (ws)
|
||||||
{
|
{
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
ws->setOnMessageCallback(
|
||||||
|
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
|
||||||
{
|
{
|
||||||
std::cout << "New connection" << std::endl;
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
|
|
||||||
// A connection state object is available, and has a default id
|
|
||||||
// You can subclass ConnectionState and pass an alternate factory
|
|
||||||
// to override it. It is useful if you want to store custom
|
|
||||||
// attributes per connection (authenticated bool flag, attributes, etc...)
|
|
||||||
std::cout << "id: " << connectionState->getId() << std::endl;
|
|
||||||
|
|
||||||
// The uri the client did connect to.
|
|
||||||
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
|
|
||||||
|
|
||||||
std::cout << "Headers:" << std::endl;
|
|
||||||
for (auto it : msg->openInfo.headers)
|
|
||||||
{
|
{
|
||||||
std::cout << it.first << ": " << it.second << std::endl;
|
std::cout << "New connection" << std::endl;
|
||||||
|
|
||||||
|
// A connection state object is available, and has a default id
|
||||||
|
// You can subclass ConnectionState and pass an alternate factory
|
||||||
|
// to override it. It is useful if you want to store custom
|
||||||
|
// attributes per connection (authenticated bool flag, attributes, etc...)
|
||||||
|
std::cout << "id: " << connectionState->getId() << std::endl;
|
||||||
|
|
||||||
|
// The uri the client did connect to.
|
||||||
|
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
|
||||||
|
|
||||||
|
std::cout << "Headers:" << std::endl;
|
||||||
|
for (auto it : msg->openInfo.headers)
|
||||||
|
{
|
||||||
|
std::cout << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
// For an echo server, we just send back to the client whatever was received by the server
|
||||||
|
// All connected clients are available in an std::set. See the broadcast cpp example.
|
||||||
|
// Second parameter tells whether we are sending the message in binary or text mode.
|
||||||
|
// Here we send it in the same mode as it was received.
|
||||||
|
auto ws = webSocket.lock();
|
||||||
|
if (ws)
|
||||||
|
{
|
||||||
|
ws->send(msg->str, msg->binary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
// For an echo server, we just send back to the client whatever was received by the server
|
|
||||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
|
||||||
// Second parameter tells whether we are sending the message in binary or text mode.
|
|
||||||
// Here we send it in the same mode as it was received.
|
|
||||||
webSocket->send(msg->str, msg->binary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -312,6 +324,74 @@ server.wait();
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### New api
|
||||||
|
|
||||||
|
The new API does not require to use 2 nested callbacks, which is a bit annoying. The real fix is that there was a memory leak due to a shared_ptr cycle, due to passing down a shared_ptr<WebSocket> down to the callbacks.
|
||||||
|
|
||||||
|
The webSocket reference is guaranteed to be always valid ; by design the callback will never be invoked with a null webSocket object.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
// Run a server on localhost at a given port.
|
||||||
|
// Bound host name, max connections and listen backlog can also be passed in as parameters.
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
|
server.setOnClientMessageCallback(std::shared_ptr<ConnectionState> connectionState,
|
||||||
|
ConnectionInfo& connectionInfo,
|
||||||
|
WebSocket& webSocket,
|
||||||
|
const WebSocketMessagePtr& msg)
|
||||||
|
{
|
||||||
|
// The ConnectionInfo object contains information about the connection,
|
||||||
|
// at this point only the client ip address and the port.
|
||||||
|
std::cout << "Remote ip: " << connectionInfo.remoteIp << std::endl;
|
||||||
|
|
||||||
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
std::cout << "New connection" << std::endl;
|
||||||
|
|
||||||
|
// A connection state object is available, and has a default id
|
||||||
|
// You can subclass ConnectionState and pass an alternate factory
|
||||||
|
// to override it. It is useful if you want to store custom
|
||||||
|
// attributes per connection (authenticated bool flag, attributes, etc...)
|
||||||
|
std::cout << "id: " << connectionState->getId() << std::endl;
|
||||||
|
|
||||||
|
// The uri the client did connect to.
|
||||||
|
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
|
||||||
|
|
||||||
|
std::cout << "Headers:" << std::endl;
|
||||||
|
for (auto it : msg->openInfo.headers)
|
||||||
|
{
|
||||||
|
std::cout << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
// For an echo server, we just send back to the client whatever was received by the server
|
||||||
|
// All connected clients are available in an std::set. See the broadcast cpp example.
|
||||||
|
// Second parameter tells whether we are sending the message in binary or text mode.
|
||||||
|
// Here we send it in the same mode as it was received.
|
||||||
|
webSocket.send(msg->str, msg->binary);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
if (!res.first)
|
||||||
|
{
|
||||||
|
// Error handling
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the server in the background. Server can be stoped by calling server.stop()
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
// Block until server.stop() is called.
|
||||||
|
server.wait();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## HTTP client API
|
## HTTP client API
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
@@ -111,6 +111,12 @@ namespace ix
|
|||||||
|
|
||||||
void CobraConnection::disconnect()
|
void CobraConnection::disconnect()
|
||||||
{
|
{
|
||||||
|
auto subscriptionIds = getSubscriptionsIds();
|
||||||
|
for (auto&& subscriptionId : subscriptionIds)
|
||||||
|
{
|
||||||
|
unsubscribe(subscriptionId);
|
||||||
|
}
|
||||||
|
|
||||||
_authenticated = false;
|
_authenticated = false;
|
||||||
_webSocket->stop();
|
_webSocket->stop();
|
||||||
}
|
}
|
||||||
@@ -614,6 +620,18 @@ namespace ix
|
|||||||
_webSocket->send(pdu.toStyledString());
|
_webSocket->send(pdu.toStyledString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> CobraConnection::getSubscriptionsIds()
|
||||||
|
{
|
||||||
|
std::vector<std::string> subscriptionIds;
|
||||||
|
std::lock_guard<std::mutex> lock(_cbsMutex);
|
||||||
|
|
||||||
|
for (auto&& it : _cbs)
|
||||||
|
{
|
||||||
|
subscriptionIds.push_back(it.first);
|
||||||
|
}
|
||||||
|
return subscriptionIds;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Enqueue strategy drops old messages when we are at full capacity
|
// Enqueue strategy drops old messages when we are at full capacity
|
||||||
//
|
//
|
||||||
|
@@ -163,6 +163,9 @@ namespace ix
|
|||||||
/// Tells whether the internal queue is empty or not
|
/// Tells whether the internal queue is empty or not
|
||||||
bool isQueueEmpty();
|
bool isQueueEmpty();
|
||||||
|
|
||||||
|
/// Retrieve all subscriptions ids
|
||||||
|
std::vector<std::string> getSubscriptionsIds();
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Member variables
|
/// Member variables
|
||||||
///
|
///
|
||||||
|
@@ -7,12 +7,14 @@ set (IXSNAKE_SOURCES
|
|||||||
ixsnake/IXSnakeServer.cpp
|
ixsnake/IXSnakeServer.cpp
|
||||||
ixsnake/IXSnakeProtocol.cpp
|
ixsnake/IXSnakeProtocol.cpp
|
||||||
ixsnake/IXAppConfig.cpp
|
ixsnake/IXAppConfig.cpp
|
||||||
|
ixsnake/IXStreamSql.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set (IXSNAKE_HEADERS
|
set (IXSNAKE_HEADERS
|
||||||
ixsnake/IXSnakeServer.h
|
ixsnake/IXSnakeServer.h
|
||||||
ixsnake/IXSnakeProtocol.h
|
ixsnake/IXSnakeProtocol.h
|
||||||
ixsnake/IXAppConfig.h
|
ixsnake/IXAppConfig.h
|
||||||
|
ixsnake/IXStreamSql.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(ixsnake STATIC
|
add_library(ixsnake STATIC
|
||||||
|
@@ -26,6 +26,12 @@ namespace snake
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto roles = appConfig.apps[appkey]["roles"];
|
auto roles = appConfig.apps[appkey]["roles"];
|
||||||
|
if (roles.count(role) == 0)
|
||||||
|
{
|
||||||
|
std::cerr << "Missing role " << role << std::endl;
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
auto channel = roles[role]["secret"];
|
auto channel = roles[role]["secret"];
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
@@ -33,6 +33,9 @@ namespace snake
|
|||||||
// Misc
|
// Misc
|
||||||
bool verbose;
|
bool verbose;
|
||||||
bool disablePong;
|
bool disablePong;
|
||||||
|
|
||||||
|
// If non empty, every published message gets republished to a given channel
|
||||||
|
std::string republishChannel;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isAppKeyValid(const AppConfig& appConfig, std::string appkey);
|
bool isAppKeyValid(const AppConfig& appConfig, std::string appkey);
|
||||||
|
@@ -7,15 +7,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ixredis/IXRedisClient.h>
|
#include <ixredis/IXRedisClient.h>
|
||||||
#include <future>
|
#include <thread>
|
||||||
#include <ixwebsocket/IXConnectionState.h>
|
#include <ixwebsocket/IXConnectionState.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include "IXStreamSql.h"
|
||||||
|
|
||||||
namespace snake
|
namespace snake
|
||||||
{
|
{
|
||||||
class SnakeConnectionState : public ix::ConnectionState
|
class SnakeConnectionState : public ix::ConnectionState
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
virtual ~SnakeConnectionState()
|
||||||
|
{
|
||||||
|
stopSubScriptionThread();
|
||||||
|
}
|
||||||
|
|
||||||
std::string getNonce()
|
std::string getNonce()
|
||||||
{
|
{
|
||||||
return _nonce;
|
return _nonce;
|
||||||
@@ -30,6 +36,7 @@ namespace snake
|
|||||||
{
|
{
|
||||||
return _appkey;
|
return _appkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAppkey(const std::string& appkey)
|
void setAppkey(const std::string& appkey)
|
||||||
{
|
{
|
||||||
_appkey = appkey;
|
_appkey = appkey;
|
||||||
@@ -39,6 +46,7 @@ namespace snake
|
|||||||
{
|
{
|
||||||
return _role;
|
return _role;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRole(const std::string& role)
|
void setRole(const std::string& role)
|
||||||
{
|
{
|
||||||
_role = role;
|
_role = role;
|
||||||
@@ -49,7 +57,24 @@ namespace snake
|
|||||||
return _redisClient;
|
return _redisClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::future<void> fut;
|
void stopSubScriptionThread()
|
||||||
|
{
|
||||||
|
if (subscriptionThread.joinable())
|
||||||
|
{
|
||||||
|
subscriptionRedisClient.stop();
|
||||||
|
subscriptionThread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could make those accessible through methods
|
||||||
|
std::thread subscriptionThread;
|
||||||
|
std::string appChannel;
|
||||||
|
std::string subscriptionId;
|
||||||
|
uint64_t id;
|
||||||
|
std::unique_ptr<StreamSql> streamSql;
|
||||||
|
ix::RedisClient subscriptionRedisClient;
|
||||||
|
ix::RedisClient::OnRedisSubscribeResponseCallback onRedisSubscribeResponseCallback;
|
||||||
|
ix::RedisClient::OnRedisSubscribeCallback onRedisSubscribeCallback;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _nonce;
|
std::string _nonce;
|
||||||
|
@@ -18,21 +18,22 @@
|
|||||||
namespace snake
|
namespace snake
|
||||||
{
|
{
|
||||||
void handleError(const std::string& action,
|
void handleError(const std::string& action,
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
ix::WebSocket& ws,
|
||||||
nlohmann::json pdu,
|
uint64_t pduId,
|
||||||
const std::string& errMsg)
|
const std::string& errMsg)
|
||||||
{
|
{
|
||||||
std::string actionError(action);
|
std::string actionError(action);
|
||||||
actionError += "/error";
|
actionError += "/error";
|
||||||
|
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", actionError}, {"id", pdu.value("id", 1)}, {"body", {{"reason", errMsg}}}};
|
{"action", actionError}, {"id", pduId}, {"body", {{"reason", errMsg}}}};
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleHandshake(std::shared_ptr<SnakeConnectionState> state,
|
void handleHandshake(std::shared_ptr<SnakeConnectionState> state,
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
ix::WebSocket& ws,
|
||||||
const nlohmann::json& pdu)
|
const nlohmann::json& pdu,
|
||||||
|
uint64_t pduId)
|
||||||
{
|
{
|
||||||
std::string role = pdu["body"]["data"]["role"];
|
std::string role = pdu["body"]["data"]["role"];
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ namespace snake
|
|||||||
|
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "auth/handshake/ok"},
|
{"action", "auth/handshake/ok"},
|
||||||
{"id", pdu.value("id", 1)},
|
{"id", pduId},
|
||||||
{"body",
|
{"body",
|
||||||
{
|
{
|
||||||
{"data", {{"nonce", state->getNonce()}, {"connection_id", state->getId()}}},
|
{"data", {{"nonce", state->getNonce()}, {"connection_id", state->getId()}}},
|
||||||
@@ -49,13 +50,14 @@ namespace snake
|
|||||||
|
|
||||||
auto serializedResponse = response.dump();
|
auto serializedResponse = response.dump();
|
||||||
|
|
||||||
ws->sendText(serializedResponse);
|
ws.sendText(serializedResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleAuth(std::shared_ptr<SnakeConnectionState> state,
|
void handleAuth(std::shared_ptr<SnakeConnectionState> state,
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
ix::WebSocket& ws,
|
||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const nlohmann::json& pdu)
|
const nlohmann::json& pdu,
|
||||||
|
uint64_t pduId)
|
||||||
{
|
{
|
||||||
auto secret = getRoleSecret(appConfig, state->appkey(), state->role());
|
auto secret = getRoleSecret(appConfig, state->appkey(), state->role());
|
||||||
|
|
||||||
@@ -63,9 +65,9 @@ namespace snake
|
|||||||
{
|
{
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "auth/authenticate/error"},
|
{"action", "auth/authenticate/error"},
|
||||||
{"id", pdu.value("id", 1)},
|
{"id", pduId},
|
||||||
{"body", {{"error", "authentication_failed"}, {"reason", "invalid secret"}}}};
|
{"body", {{"error", "authentication_failed"}, {"reason", "invalid secret"}}}};
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,19 +81,21 @@ namespace snake
|
|||||||
{"action", "auth/authenticate/error"},
|
{"action", "auth/authenticate/error"},
|
||||||
{"id", pdu.value("id", 1)},
|
{"id", pdu.value("id", 1)},
|
||||||
{"body", {{"error", "authentication_failed"}, {"reason", "invalid hash"}}}};
|
{"body", {{"error", "authentication_failed"}, {"reason", "invalid hash"}}}};
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "auth/authenticate/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
{"action", "auth/authenticate/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
||||||
|
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlePublish(std::shared_ptr<SnakeConnectionState> state,
|
void handlePublish(std::shared_ptr<SnakeConnectionState> state,
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
ix::WebSocket& ws,
|
||||||
const nlohmann::json& pdu)
|
const AppConfig& appConfig,
|
||||||
|
const nlohmann::json& pdu,
|
||||||
|
uint64_t pduId)
|
||||||
{
|
{
|
||||||
std::vector<std::string> channels;
|
std::vector<std::string> channels;
|
||||||
|
|
||||||
@@ -111,10 +115,16 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Missing channels or channel field in publish data";
|
ss << "Missing channels or channel field in publish data";
|
||||||
handleError("rtm/publish", ws, pdu, ss.str());
|
handleError("rtm/publish", ws, pduId, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add an extra channel if the config has one specified
|
||||||
|
if (!appConfig.republishChannel.empty())
|
||||||
|
{
|
||||||
|
channels.push_back(appConfig.republishChannel);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto&& channel : channels)
|
for (auto&& channel : channels)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@@ -125,7 +135,7 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot publish to redis host " << errMsg;
|
ss << "Cannot publish to redis host " << errMsg;
|
||||||
handleError("rtm/publish", ws, pdu, ss.str());
|
handleError("rtm/publish", ws, pduId, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,26 +143,27 @@ namespace snake
|
|||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "rtm/publish/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
{"action", "rtm/publish/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
||||||
|
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// FIXME: this is not cancellable. We should be able to cancel the redis subscription
|
// FIXME: this is not cancellable. We should be able to cancel the redis subscription
|
||||||
//
|
//
|
||||||
void handleRedisSubscription(std::shared_ptr<SnakeConnectionState> state,
|
void handleSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
ix::WebSocket& ws,
|
||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const nlohmann::json& pdu)
|
const nlohmann::json& pdu,
|
||||||
|
uint64_t pduId)
|
||||||
{
|
{
|
||||||
std::string channel = pdu["body"]["channel"];
|
std::string channel = pdu["body"]["channel"];
|
||||||
std::string subscriptionId = channel;
|
state->subscriptionId = channel;
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << state->appkey() << "::" << channel;
|
ss << state->appkey() << "::" << channel;
|
||||||
|
|
||||||
std::string appChannel(ss.str());
|
state->appChannel = ss.str();
|
||||||
|
|
||||||
ix::RedisClient redisClient;
|
ix::RedisClient& redisClient = state->subscriptionRedisClient;
|
||||||
int port = appConfig.redisPort;
|
int port = appConfig.redisPort;
|
||||||
|
|
||||||
auto urls = appConfig.redisHosts;
|
auto urls = appConfig.redisHosts;
|
||||||
@@ -163,7 +174,7 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot connect to redis host " << hostname << ":" << port;
|
ss << "Cannot connect to redis host " << hostname << ":" << port;
|
||||||
handleError("rtm/subscribe", ws, pdu, ss.str());
|
handleError("rtm/subscribe", ws, pduId, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,80 +186,90 @@ namespace snake
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot authenticated to redis";
|
ss << "Cannot authenticated to redis";
|
||||||
handleError("rtm/subscribe", ws, pdu, ss.str());
|
handleError("rtm/subscribe", ws, pduId, ss.str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int id = 0;
|
std::string filterStr;
|
||||||
auto callback = [ws, &id, &subscriptionId](const std::string& messageStr) {
|
if (pdu["body"].find("filter") != pdu["body"].end())
|
||||||
|
{
|
||||||
|
std::string filterStr = pdu["body"]["filter"];
|
||||||
|
}
|
||||||
|
state->streamSql = std::make_unique<StreamSql>(filterStr);
|
||||||
|
state->id = 0;
|
||||||
|
state->onRedisSubscribeCallback = [&ws, state](const std::string& messageStr) {
|
||||||
auto msg = nlohmann::json::parse(messageStr);
|
auto msg = nlohmann::json::parse(messageStr);
|
||||||
|
|
||||||
msg = msg["body"]["message"];
|
msg = msg["body"]["message"];
|
||||||
|
|
||||||
|
if (state->streamSql->valid() && !state->streamSql->match(msg))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"action", "rtm/subscription/data"},
|
{"action", "rtm/subscription/data"},
|
||||||
{"id", id++},
|
{"id", state->id++},
|
||||||
{"body",
|
{"body",
|
||||||
{{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
{{"subscription_id", state->subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||||
|
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
};
|
};
|
||||||
|
|
||||||
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
|
state->onRedisSubscribeResponseCallback = [&ws, state, pduId](const std::string& redisResponse) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Redis Response: " << redisResponse << "...";
|
ss << "Redis Response: " << redisResponse << "...";
|
||||||
ix::CoreLogger::log(ss.str().c_str());
|
ix::CoreLogger::log(ss.str().c_str());
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
||||||
{"id", pdu.value("id", 1)},
|
{"id", pduId},
|
||||||
{"body", {{"subscription_id", subscriptionId}}}};
|
{"body", {{"subscription_id", state->subscriptionId}}}};
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Subscribing to " << appChannel << "...";
|
ss << "Subscribing to " << state->appChannel << "...";
|
||||||
ix::CoreLogger::log(ss.str().c_str());
|
ix::CoreLogger::log(ss.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!redisClient.subscribe(appChannel, responseCallback, callback))
|
auto subscription = [&redisClient, state, &ws, pduId]
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
if (!redisClient.subscribe(state->appChannel,
|
||||||
ss << "Error subscribing to channel " << appChannel;
|
state->onRedisSubscribeResponseCallback,
|
||||||
handleError("rtm/subscribe", ws, pdu, ss.str());
|
state->onRedisSubscribeCallback))
|
||||||
return;
|
{
|
||||||
}
|
std::stringstream ss;
|
||||||
}
|
ss << "Error subscribing to channel " << state->appChannel;
|
||||||
|
handleError("rtm/subscribe", ws, pduId, ss.str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void handleSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
state->subscriptionThread = std::thread(subscription);
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
|
||||||
const AppConfig& appConfig,
|
|
||||||
const nlohmann::json& pdu)
|
|
||||||
{
|
|
||||||
state->fut =
|
|
||||||
std::async(std::launch::async, handleRedisSubscription, state, ws, appConfig, pdu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleUnSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
void handleUnSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
ix::WebSocket& ws,
|
||||||
const nlohmann::json& pdu)
|
const nlohmann::json& pdu,
|
||||||
|
uint64_t pduId)
|
||||||
{
|
{
|
||||||
// extract subscription_id
|
// extract subscription_id
|
||||||
auto body = pdu["body"];
|
auto body = pdu["body"];
|
||||||
auto subscriptionId = body["subscription_id"];
|
auto subscriptionId = body["subscription_id"];
|
||||||
|
|
||||||
state->redisClient().stop();
|
state->stopSubScriptionThread();
|
||||||
|
|
||||||
nlohmann::json response = {{"action", "rtm/unsubscribe/ok"},
|
nlohmann::json response = {{"action", "rtm/unsubscribe/ok"},
|
||||||
{"id", pdu.value("id", 1)},
|
{"id", pduId},
|
||||||
{"body", {{"subscription_id", subscriptionId}}}};
|
{"body", {{"subscription_id", subscriptionId}}}};
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
ix::WebSocket& ws,
|
||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const std::string& str)
|
const std::string& str)
|
||||||
{
|
{
|
||||||
@@ -263,31 +284,32 @@ namespace snake
|
|||||||
ss << "malformed json pdu: " << e.what() << " -> " << str << "";
|
ss << "malformed json pdu: " << e.what() << " -> " << str << "";
|
||||||
|
|
||||||
nlohmann::json response = {{"body", {{"error", "invalid_json"}, {"reason", ss.str()}}}};
|
nlohmann::json response = {{"body", {{"error", "invalid_json"}, {"reason", ss.str()}}}};
|
||||||
ws->sendText(response.dump());
|
ws.sendText(response.dump());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto action = pdu["action"];
|
auto action = pdu["action"];
|
||||||
|
uint64_t pduId = pdu.value("id", 1);
|
||||||
|
|
||||||
if (action == "auth/handshake")
|
if (action == "auth/handshake")
|
||||||
{
|
{
|
||||||
handleHandshake(state, ws, pdu);
|
handleHandshake(state, ws, pdu, pduId);
|
||||||
}
|
}
|
||||||
else if (action == "auth/authenticate")
|
else if (action == "auth/authenticate")
|
||||||
{
|
{
|
||||||
handleAuth(state, ws, appConfig, pdu);
|
handleAuth(state, ws, appConfig, pdu, pduId);
|
||||||
}
|
}
|
||||||
else if (action == "rtm/publish")
|
else if (action == "rtm/publish")
|
||||||
{
|
{
|
||||||
handlePublish(state, ws, pdu);
|
handlePublish(state, ws, appConfig, pdu, pduId);
|
||||||
}
|
}
|
||||||
else if (action == "rtm/subscribe")
|
else if (action == "rtm/subscribe")
|
||||||
{
|
{
|
||||||
handleSubscribe(state, ws, appConfig, pdu);
|
handleSubscribe(state, ws, appConfig, pdu, pduId);
|
||||||
}
|
}
|
||||||
else if (action == "rtm/unsubscribe")
|
else if (action == "rtm/unsubscribe")
|
||||||
{
|
{
|
||||||
handleUnSubscribe(state, ws, pdu);
|
handleUnSubscribe(state, ws, pdu, pduId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -20,7 +20,7 @@ namespace snake
|
|||||||
struct AppConfig;
|
struct AppConfig;
|
||||||
|
|
||||||
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
||||||
std::shared_ptr<ix::WebSocket> ws,
|
ix::WebSocket& ws,
|
||||||
const AppConfig& appConfig,
|
const AppConfig& appConfig,
|
||||||
const std::string& str);
|
const std::string& str);
|
||||||
} // namespace snake
|
} // namespace snake
|
||||||
|
@@ -59,68 +59,68 @@ namespace snake
|
|||||||
};
|
};
|
||||||
_server.setConnectionStateFactory(factory);
|
_server.setConnectionStateFactory(factory);
|
||||||
|
|
||||||
_server.setOnConnectionCallback(
|
_server.setOnClientMessageCallback(
|
||||||
[this](std::shared_ptr<ix::WebSocket> webSocket,
|
[this](std::shared_ptr<ix::ConnectionState> connectionState,
|
||||||
std::shared_ptr<ix::ConnectionState> connectionState,
|
ix::ConnectionInfo& connectionInfo,
|
||||||
std::unique_ptr<ix::ConnectionInfo> connectionInfo) {
|
ix::WebSocket& webSocket,
|
||||||
|
const ix::WebSocketMessagePtr& msg) {
|
||||||
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
|
|
||||||
webSocket->setOnMessageCallback(
|
std::stringstream ss;
|
||||||
[this, webSocket, state, remoteIp](const ix::WebSocketMessagePtr& msg) {
|
ss << "[" << state->getId() << "] ";
|
||||||
std::stringstream ss;
|
|
||||||
ix::LogLevel logLevel = ix::LogLevel::Debug;
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
ss << "New connection" << std::endl;
|
|
||||||
ss << "remote ip: " << remoteIp << std::endl;
|
|
||||||
ss << "id: " << state->getId() << std::endl;
|
|
||||||
ss << "Uri: " << msg->openInfo.uri << std::endl;
|
|
||||||
ss << "Headers:" << std::endl;
|
|
||||||
for (auto it : msg->openInfo.headers)
|
|
||||||
{
|
|
||||||
ss << it.first << ": " << it.second << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string appkey = parseAppKey(msg->openInfo.uri);
|
ix::LogLevel logLevel = ix::LogLevel::Debug;
|
||||||
state->setAppkey(appkey);
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
ss << "New connection" << std::endl;
|
||||||
|
ss << "remote ip: " << remoteIp << std::endl;
|
||||||
|
ss << "id: " << state->getId() << std::endl;
|
||||||
|
ss << "Uri: " << msg->openInfo.uri << std::endl;
|
||||||
|
ss << "Headers:" << std::endl;
|
||||||
|
for (auto it : msg->openInfo.headers)
|
||||||
|
{
|
||||||
|
ss << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
// Connect to redis first
|
std::string appkey = parseAppKey(msg->openInfo.uri);
|
||||||
if (!state->redisClient().connect(_appConfig.redisHosts[0],
|
state->setAppkey(appkey);
|
||||||
_appConfig.redisPort))
|
|
||||||
{
|
|
||||||
ss << "Cannot connect to redis host" << std::endl;
|
|
||||||
logLevel = ix::LogLevel::Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
|
||||||
{
|
|
||||||
ss << "Closed connection"
|
|
||||||
<< " code " << msg->closeInfo.code << " reason "
|
|
||||||
<< msg->closeInfo.reason << std::endl;
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
|
||||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
|
||||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
|
||||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
|
||||||
logLevel = ix::LogLevel::Error;
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
|
||||||
{
|
|
||||||
ss << "Received message fragment" << std::endl;
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
ss << "Received " << msg->wireSize << " bytes" << std::endl;
|
|
||||||
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
|
||||||
}
|
|
||||||
|
|
||||||
ix::CoreLogger::log(ss.str().c_str(), logLevel);
|
// Connect to redis first
|
||||||
});
|
if (!state->redisClient().connect(_appConfig.redisHosts[0],
|
||||||
});
|
_appConfig.redisPort))
|
||||||
|
{
|
||||||
|
ss << "Cannot connect to redis host" << std::endl;
|
||||||
|
logLevel = ix::LogLevel::Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
ss << "Closed connection"
|
||||||
|
<< " code " << msg->closeInfo.code << " reason "
|
||||||
|
<< msg->closeInfo.reason << std::endl;
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
||||||
|
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||||
|
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||||
|
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||||
|
logLevel = ix::LogLevel::Error;
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
||||||
|
{
|
||||||
|
ss << "Received message fragment" << std::endl;
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
ss << "Received " << msg->wireSize << " bytes" << " " << msg->str << std::endl;
|
||||||
|
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
||||||
|
}
|
||||||
|
|
||||||
|
ix::CoreLogger::log(ss.str().c_str(), logLevel);
|
||||||
|
});
|
||||||
|
|
||||||
auto res = _server.listen();
|
auto res = _server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
|
63
ixsnake/ixsnake/IXStreamSql.cpp
Normal file
63
ixsnake/ixsnake/IXStreamSql.cpp
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* IXStreamSql.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Super simple hacked up version of a stream sql expression,
|
||||||
|
* that only supports non nested field evaluation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXStreamSql.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace snake
|
||||||
|
{
|
||||||
|
StreamSql::StreamSql(const std::string& sqlFilter)
|
||||||
|
: _valid(false)
|
||||||
|
{
|
||||||
|
std::string token;
|
||||||
|
std::stringstream tokenStream(sqlFilter);
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
|
||||||
|
// Split by ' '
|
||||||
|
while (std::getline(tokenStream, token, ' '))
|
||||||
|
{
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
_valid = tokens.size() == 8;
|
||||||
|
if (!_valid) return;
|
||||||
|
|
||||||
|
_field = tokens[5];
|
||||||
|
_operator = tokens[6];
|
||||||
|
_value = tokens[7];
|
||||||
|
|
||||||
|
// remove single quotes
|
||||||
|
_value = _value.substr(1, _value.size() - 2);
|
||||||
|
|
||||||
|
if (_operator == "LIKE")
|
||||||
|
{
|
||||||
|
_value = _value.substr(1, _value.size() - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StreamSql::valid() const
|
||||||
|
{
|
||||||
|
return _valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StreamSql::match(const nlohmann::json& msg)
|
||||||
|
{
|
||||||
|
if (!_valid) return false;
|
||||||
|
|
||||||
|
if (msg.find(_field) == msg.end())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string value = msg[_field];
|
||||||
|
return value == _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace snake
|
29
ixsnake/ixsnake/IXStreamSql.h
Normal file
29
ixsnake/ixsnake/IXStreamSql.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* IXStreamSql.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "nlohmann/json.hpp"
|
||||||
|
|
||||||
|
namespace snake
|
||||||
|
{
|
||||||
|
class StreamSql
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StreamSql(const std::string& sqlFilter = std::string());
|
||||||
|
~StreamSql() = default;
|
||||||
|
|
||||||
|
bool match(const nlohmann::json& msg);
|
||||||
|
bool valid() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _field;
|
||||||
|
std::string _operator;
|
||||||
|
std::string _value;
|
||||||
|
bool _valid;
|
||||||
|
};
|
||||||
|
}
|
@@ -16,7 +16,10 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@@ -174,11 +177,13 @@ namespace ix
|
|||||||
ss << verb << " " << path << " HTTP/1.1\r\n";
|
ss << verb << " " << path << " HTTP/1.1\r\n";
|
||||||
ss << "Host: " << host << "\r\n";
|
ss << "Host: " << host << "\r\n";
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
if (args->compress)
|
if (args->compress)
|
||||||
{
|
{
|
||||||
ss << "Accept-Encoding: gzip"
|
ss << "Accept-Encoding: gzip"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Append extra headers
|
// Append extra headers
|
||||||
for (auto&& it : args->extraHeaders)
|
for (auto&& it : args->extraHeaders)
|
||||||
@@ -495,6 +500,7 @@ namespace ix
|
|||||||
|
|
||||||
downloadSize = payload.size();
|
downloadSize = payload.size();
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
// If the content was compressed with gzip, decode it
|
// If the content was compressed with gzip, decode it
|
||||||
if (headers["Content-Encoding"] == "gzip")
|
if (headers["Content-Encoding"] == "gzip")
|
||||||
{
|
{
|
||||||
@@ -513,6 +519,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
payload = decompressedPayload;
|
payload = decompressedPayload;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return std::make_shared<HttpResponse>(code,
|
return std::make_shared<HttpResponse>(code,
|
||||||
description,
|
description,
|
||||||
@@ -672,6 +679,7 @@ namespace ix
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
|
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
|
||||||
{
|
{
|
||||||
z_stream inflateState;
|
z_stream inflateState;
|
||||||
@@ -716,6 +724,7 @@ namespace ix
|
|||||||
inflateEnd(&inflateState);
|
inflateEnd(&inflateState);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
|
@@ -90,7 +90,9 @@ namespace ix
|
|||||||
private:
|
private:
|
||||||
void log(const std::string& msg, HttpRequestArgsPtr args);
|
void log(const std::string& msg, HttpRequestArgsPtr args);
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
bool gzipInflate(const std::string& in, std::string& out);
|
bool gzipInflate(const std::string& in, std::string& out);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Async API background thread runner
|
// Async API background thread runner
|
||||||
void run();
|
void run();
|
||||||
|
@@ -13,7 +13,10 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -41,6 +44,7 @@ namespace
|
|||||||
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
std::string gzipCompress(const std::string& str)
|
std::string gzipCompress(const std::string& str)
|
||||||
{
|
{
|
||||||
z_stream zs; // z_stream is zlib's control structure
|
z_stream zs; // z_stream is zlib's control structure
|
||||||
@@ -83,6 +87,7 @@ namespace
|
|||||||
|
|
||||||
return outstring;
|
return outstring;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@@ -125,9 +130,8 @@ namespace ix
|
|||||||
|
|
||||||
if (std::get<0>(ret))
|
if (std::get<0>(ret))
|
||||||
{
|
{
|
||||||
auto response = _onConnectionCallback(std::get<2>(ret),
|
auto response =
|
||||||
connectionState,
|
_onConnectionCallback(std::get<2>(ret), connectionState, std::move(connectionInfo));
|
||||||
std::move(connectionInfo));
|
|
||||||
if (!Http::sendResponse(response, socket))
|
if (!Http::sendResponse(response, socket))
|
||||||
{
|
{
|
||||||
logError("Cannot send response");
|
logError("Cannot send response");
|
||||||
@@ -169,12 +173,14 @@ namespace ix
|
|||||||
|
|
||||||
std::string content = res.second;
|
std::string content = res.second;
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
std::string acceptEncoding = request->headers["Accept-encoding"];
|
std::string acceptEncoding = request->headers["Accept-encoding"];
|
||||||
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
|
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
|
||||||
{
|
{
|
||||||
content = gzipCompress(content);
|
content = gzipCompress(content);
|
||||||
headers["Content-Encoding"] = "gzip";
|
headers["Content-Encoding"] = "gzip";
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Log request
|
// Log request
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@@ -203,10 +209,9 @@ namespace ix
|
|||||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||||
//
|
//
|
||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this,
|
[this, redirectUrl](HttpRequestPtr request,
|
||||||
redirectUrl](HttpRequestPtr request,
|
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
headers["Server"] = userAgent();
|
headers["Server"] = userAgent();
|
||||||
|
|
||||||
|
@@ -5,8 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// On macOS we use UNIX pipes to wake up select.
|
// On UNIX we use pipes to wake up select. There is no way to do that
|
||||||
|
// on Windows so this file is compiled out on Windows.
|
||||||
//
|
//
|
||||||
|
#ifndef _WIN32
|
||||||
|
|
||||||
#include "IXSelectInterruptPipe.h"
|
#include "IXSelectInterruptPipe.h"
|
||||||
|
|
||||||
@@ -144,3 +146,5 @@ namespace ix
|
|||||||
return _fildes[kPipeReadIndex];
|
return _fildes[kPipeReadIndex];
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
|
||||||
|
#endif // !_WIN32
|
||||||
|
81
ixwebsocket/IXSetThreadName.cpp
Normal file
81
ixwebsocket/IXSetThreadName.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* IXSetThreadName.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
#include "IXSetThreadName.h"
|
||||||
|
|
||||||
|
// unix systems
|
||||||
|
#if defined(__APPLE__) || defined(__linux__) || defined(BSD)
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// freebsd needs this header as well
|
||||||
|
#if defined(BSD)
|
||||||
|
#include <pthread_np.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||||
|
|
||||||
|
#pragma pack(push, 8)
|
||||||
|
typedef struct tagTHREADNAME_INFO
|
||||||
|
{
|
||||||
|
DWORD dwType; // Must be 0x1000.
|
||||||
|
LPCSTR szName; // Pointer to name (in user addr space).
|
||||||
|
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||||
|
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||||
|
} THREADNAME_INFO;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
void SetThreadName(DWORD dwThreadID, const char* threadName)
|
||||||
|
{
|
||||||
|
THREADNAME_INFO info;
|
||||||
|
info.dwType = 0x1000;
|
||||||
|
info.szName = threadName;
|
||||||
|
info.dwThreadID = dwThreadID;
|
||||||
|
info.dwFlags = 0;
|
||||||
|
|
||||||
|
__try
|
||||||
|
{
|
||||||
|
RaiseException(
|
||||||
|
MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info);
|
||||||
|
}
|
||||||
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setThreadName(const std::string& name)
|
||||||
|
{
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
//
|
||||||
|
// Apple reserves 16 bytes for its thread names
|
||||||
|
// Notice that the Apple version of pthread_setname_np
|
||||||
|
// does not take a pthread_t argument
|
||||||
|
//
|
||||||
|
pthread_setname_np(name.substr(0, 63).c_str());
|
||||||
|
#elif defined(__linux__)
|
||||||
|
//
|
||||||
|
// Linux only reserves 16 bytes for its thread names
|
||||||
|
// See prctl and PR_SET_NAME property in
|
||||||
|
// http://man7.org/linux/man-pages/man2/prctl.2.html
|
||||||
|
//
|
||||||
|
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
SetThreadName(-1, name.c_str());
|
||||||
|
#elif defined(BSD)
|
||||||
|
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
|
||||||
|
#else
|
||||||
|
// ... assert here ?
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace ix
|
@@ -22,7 +22,7 @@ namespace ix
|
|||||||
const int SocketServer::kDefaultPort(8080);
|
const int SocketServer::kDefaultPort(8080);
|
||||||
const std::string SocketServer::kDefaultHost("127.0.0.1");
|
const std::string SocketServer::kDefaultHost("127.0.0.1");
|
||||||
const int SocketServer::kDefaultTcpBacklog(5);
|
const int SocketServer::kDefaultTcpBacklog(5);
|
||||||
const size_t SocketServer::kDefaultMaxConnections(32);
|
const size_t SocketServer::kDefaultMaxConnections(128);
|
||||||
const int SocketServer::kDefaultAddressFamily(AF_INET);
|
const int SocketServer::kDefaultAddressFamily(AF_INET);
|
||||||
|
|
||||||
SocketServer::SocketServer(
|
SocketServer::SocketServer(
|
||||||
@@ -379,10 +379,13 @@ namespace ix
|
|||||||
|
|
||||||
// Launch the handleConnection work asynchronously in its own thread.
|
// Launch the handleConnection work asynchronously in its own thread.
|
||||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||||
_connectionsThreads.push_back(std::make_pair(
|
_connectionsThreads.push_back(
|
||||||
connectionState,
|
std::make_pair(connectionState,
|
||||||
std::thread(
|
std::thread(&SocketServer::handleConnection,
|
||||||
&SocketServer::handleConnection, this, std::move(socket), connectionState, std::move(connectionInfo))));
|
this,
|
||||||
|
std::move(socket),
|
||||||
|
connectionState,
|
||||||
|
std::move(connectionInfo))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,9 @@
|
|||||||
|
|
||||||
#include "IXWebSocketVersion.h"
|
#include "IXWebSocketVersion.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// Platform name
|
// Platform name
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
@@ -77,8 +79,10 @@ namespace ix
|
|||||||
ss << " nossl";
|
ss << " nossl";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
// Zlib version
|
// Zlib version
|
||||||
ss << " zlib " << ZLIB_VERSION;
|
ss << " zlib " << ZLIB_VERSION;
|
||||||
|
#endif
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
@@ -46,6 +46,7 @@ namespace ix
|
|||||||
WebSocket::~WebSocket()
|
WebSocket::~WebSocket()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
|
_ws.setOnCloseCallback(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::setUrl(const std::string& url)
|
void WebSocket::setUrl(const std::string& url)
|
||||||
|
@@ -28,21 +28,26 @@ namespace ix
|
|||||||
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
|
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
|
||||||
: _compressBufferSize(kBufferSize)
|
: _compressBufferSize(kBufferSize)
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
memset(&_deflateState, 0, sizeof(_deflateState));
|
memset(&_deflateState, 0, sizeof(_deflateState));
|
||||||
|
|
||||||
_deflateState.zalloc = Z_NULL;
|
_deflateState.zalloc = Z_NULL;
|
||||||
_deflateState.zfree = Z_NULL;
|
_deflateState.zfree = Z_NULL;
|
||||||
_deflateState.opaque = Z_NULL;
|
_deflateState.opaque = Z_NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
|
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
deflateEnd(&_deflateState);
|
deflateEnd(&_deflateState);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
|
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
|
||||||
bool clientNoContextTakeOver)
|
bool clientNoContextTakeOver)
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
int ret = deflateInit2(&_deflateState,
|
int ret = deflateInit2(&_deflateState,
|
||||||
Z_DEFAULT_COMPRESSION,
|
Z_DEFAULT_COMPRESSION,
|
||||||
Z_DEFLATED,
|
Z_DEFLATED,
|
||||||
@@ -57,6 +62,9 @@ namespace ix
|
|||||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -96,6 +104,7 @@ namespace ix
|
|||||||
template<typename T, typename S>
|
template<typename T, typename S>
|
||||||
bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out)
|
bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out)
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
//
|
//
|
||||||
// 7.2.1. Compression
|
// 7.2.1. Compression
|
||||||
//
|
//
|
||||||
@@ -152,6 +161,9 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -160,6 +172,7 @@ namespace ix
|
|||||||
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
|
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
|
||||||
: _compressBufferSize(kBufferSize)
|
: _compressBufferSize(kBufferSize)
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
memset(&_inflateState, 0, sizeof(_inflateState));
|
memset(&_inflateState, 0, sizeof(_inflateState));
|
||||||
|
|
||||||
_inflateState.zalloc = Z_NULL;
|
_inflateState.zalloc = Z_NULL;
|
||||||
@@ -167,16 +180,20 @@ namespace ix
|
|||||||
_inflateState.opaque = Z_NULL;
|
_inflateState.opaque = Z_NULL;
|
||||||
_inflateState.avail_in = 0;
|
_inflateState.avail_in = 0;
|
||||||
_inflateState.next_in = Z_NULL;
|
_inflateState.next_in = Z_NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
|
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
inflateEnd(&_inflateState);
|
inflateEnd(&_inflateState);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
|
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
|
||||||
bool clientNoContextTakeOver)
|
bool clientNoContextTakeOver)
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
|
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
|
||||||
|
|
||||||
if (ret != Z_OK) return false;
|
if (ret != Z_OK) return false;
|
||||||
@@ -186,10 +203,14 @@ namespace ix
|
|||||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
|
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
//
|
//
|
||||||
// 7.2.2. Decompression
|
// 7.2.2. Decompression
|
||||||
//
|
//
|
||||||
@@ -226,5 +247,8 @@ namespace ix
|
|||||||
} while (_inflateState.avail_out == 0);
|
} while (_inflateState.avail_out == 0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
#include "zlib.h"
|
#include "zlib.h"
|
||||||
|
#endif
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -34,7 +36,10 @@ namespace ix
|
|||||||
int _flush;
|
int _flush;
|
||||||
size_t _compressBufferSize;
|
size_t _compressBufferSize;
|
||||||
std::unique_ptr<unsigned char[]> _compressBuffer;
|
std::unique_ptr<unsigned char[]> _compressBuffer;
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
z_stream _deflateState;
|
z_stream _deflateState;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class WebSocketPerMessageDeflateDecompressor
|
class WebSocketPerMessageDeflateDecompressor
|
||||||
@@ -50,7 +55,10 @@ namespace ix
|
|||||||
int _flush;
|
int _flush;
|
||||||
size_t _compressBufferSize;
|
size_t _compressBufferSize;
|
||||||
std::unique_ptr<unsigned char[]> _compressBuffer;
|
std::unique_ptr<unsigned char[]> _compressBuffer;
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
z_stream _inflateState;
|
z_stream _inflateState;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@@ -61,6 +61,7 @@ namespace ix
|
|||||||
_clientMaxWindowBits = kDefaultClientMaxWindowBits;
|
_clientMaxWindowBits = kDefaultClientMaxWindowBits;
|
||||||
_serverMaxWindowBits = kDefaultServerMaxWindowBits;
|
_serverMaxWindowBits = kDefaultServerMaxWindowBits;
|
||||||
|
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
// Split by ;
|
// Split by ;
|
||||||
std::string token;
|
std::string token;
|
||||||
std::stringstream tokenStream(extension);
|
std::stringstream tokenStream(extension);
|
||||||
@@ -112,6 +113,7 @@ namespace ix
|
|||||||
sanitizeClientMaxWindowBits();
|
sanitizeClientMaxWindowBits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits()
|
void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits()
|
||||||
@@ -126,6 +128,7 @@ namespace ix
|
|||||||
|
|
||||||
std::string WebSocketPerMessageDeflateOptions::generateHeader()
|
std::string WebSocketPerMessageDeflateOptions::generateHeader()
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Sec-WebSocket-Extensions: permessage-deflate";
|
ss << "Sec-WebSocket-Extensions: permessage-deflate";
|
||||||
|
|
||||||
@@ -138,11 +141,18 @@ namespace ix
|
|||||||
ss << "\r\n";
|
ss << "\r\n";
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
|
#else
|
||||||
|
return std::string();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateOptions::enabled() const
|
bool WebSocketPerMessageDeflateOptions::enabled() const
|
||||||
{
|
{
|
||||||
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
return _enabled;
|
return _enabled;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
|
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
|
||||||
|
123
ixwebsocket/IXWebSocketProxyServer.cpp
Normal file
123
ixwebsocket/IXWebSocketProxyServer.cpp
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketProxyServer.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXWebSocketProxyServer.h"
|
||||||
|
|
||||||
|
#include "IXWebSocketServer.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class ProxyConnectionState : public ix::ConnectionState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ProxyConnectionState()
|
||||||
|
: _connected(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ix::WebSocket& webSocket()
|
||||||
|
{
|
||||||
|
return _serverWebSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConnected()
|
||||||
|
{
|
||||||
|
return _connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setConnected()
|
||||||
|
{
|
||||||
|
_connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ix::WebSocket _serverWebSocket;
|
||||||
|
bool _connected;
|
||||||
|
};
|
||||||
|
|
||||||
|
int websocket_proxy_server_main(int port,
|
||||||
|
const std::string& hostname,
|
||||||
|
const ix::SocketTLSOptions& tlsOptions,
|
||||||
|
const std::string& remoteUrl,
|
||||||
|
bool /*verbose*/)
|
||||||
|
{
|
||||||
|
ix::WebSocketServer server(port, hostname);
|
||||||
|
server.setTLSOptions(tlsOptions);
|
||||||
|
|
||||||
|
auto factory = []() -> std::shared_ptr<ix::ConnectionState> {
|
||||||
|
return std::make_shared<ProxyConnectionState>();
|
||||||
|
};
|
||||||
|
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 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)
|
||||||
|
{
|
||||||
|
ws->send(msg->str, msg->binary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
server.wait();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace ix
|
20
ixwebsocket/IXWebSocketProxyServer.h
Normal file
20
ixwebsocket/IXWebSocketProxyServer.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketProxyServer.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019-2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXSocketTLSOptions.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int websocket_proxy_server_main(int port,
|
||||||
|
const std::string& hostname,
|
||||||
|
const ix::SocketTLSOptions& tlsOptions,
|
||||||
|
const std::string& remoteUrl,
|
||||||
|
bool verbose);
|
||||||
|
} // namespace ix
|
@@ -71,6 +71,11 @@ namespace ix
|
|||||||
_onConnectionCallback = callback;
|
_onConnectionCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebSocketServer::setOnClientMessageCallback(const OnClientMessageCallback& callback)
|
||||||
|
{
|
||||||
|
_onClientMessageCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
|
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||||
@@ -78,7 +83,26 @@ namespace ix
|
|||||||
setThreadName("WebSocketServer::" + connectionState->getId());
|
setThreadName("WebSocketServer::" + connectionState->getId());
|
||||||
|
|
||||||
auto webSocket = std::make_shared<WebSocket>();
|
auto webSocket = std::make_shared<WebSocket>();
|
||||||
_onConnectionCallback(webSocket, connectionState, std::move(connectionInfo));
|
if (_onConnectionCallback)
|
||||||
|
{
|
||||||
|
_onConnectionCallback(webSocket, connectionState, std::move(connectionInfo));
|
||||||
|
}
|
||||||
|
else if (_onClientMessageCallback)
|
||||||
|
{
|
||||||
|
webSocket->setOnMessageCallback(
|
||||||
|
[this, &ws = *webSocket.get(), connectionState, &ci = *connectionInfo.get()](
|
||||||
|
const WebSocketMessagePtr& msg) {
|
||||||
|
_onClientMessageCallback(connectionState, ci, ws, msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logError(
|
||||||
|
"WebSocketServer Application developer error: No server callback is registerered.");
|
||||||
|
logError("Missing call to setOnConnectionCallback or setOnClientMessageCallback.");
|
||||||
|
connectionState->setTerminated();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
webSocket->disableAutomaticReconnection();
|
webSocket->disableAutomaticReconnection();
|
||||||
|
|
||||||
@@ -112,6 +136,8 @@ namespace ix
|
|||||||
logError(ss.str());
|
logError(ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webSocket->setOnMessageCallback(nullptr);
|
||||||
|
|
||||||
// Remove this client from our client set
|
// Remove this client from our client set
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_clientsMutex);
|
std::lock_guard<std::mutex> lock(_clientsMutex);
|
||||||
|
@@ -23,9 +23,15 @@ namespace ix
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using OnConnectionCallback =
|
using OnConnectionCallback =
|
||||||
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>,
|
std::function<void(std::weak_ptr<WebSocket>,
|
||||||
|
std::shared_ptr<ConnectionState>,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo)>;
|
std::unique_ptr<ConnectionInfo> connectionInfo)>;
|
||||||
|
|
||||||
|
using OnClientMessageCallback = std::function<void(std::shared_ptr<ConnectionState>,
|
||||||
|
ConnectionInfo&,
|
||||||
|
WebSocket&,
|
||||||
|
const WebSocketMessagePtr&)>;
|
||||||
|
|
||||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
WebSocketServer(int port = SocketServer::kDefaultPort,
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
@@ -40,6 +46,7 @@ namespace ix
|
|||||||
void disablePerMessageDeflate();
|
void disablePerMessageDeflate();
|
||||||
|
|
||||||
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
||||||
|
void setOnClientMessageCallback(const OnClientMessageCallback& callback);
|
||||||
|
|
||||||
// Get all the connected clients
|
// Get all the connected clients
|
||||||
std::set<std::shared_ptr<WebSocket>> getClients();
|
std::set<std::shared_ptr<WebSocket>> getClients();
|
||||||
@@ -53,6 +60,7 @@ namespace ix
|
|||||||
bool _enablePerMessageDeflate;
|
bool _enablePerMessageDeflate;
|
||||||
|
|
||||||
OnConnectionCallback _onConnectionCallback;
|
OnConnectionCallback _onConnectionCallback;
|
||||||
|
OnClientMessageCallback _onClientMessageCallback;
|
||||||
|
|
||||||
std::mutex _clientsMutex;
|
std::mutex _clientsMutex;
|
||||||
std::set<std::shared_ptr<WebSocket>> _clients;
|
std::set<std::shared_ptr<WebSocket>> _clients;
|
||||||
|
@@ -65,7 +65,6 @@ namespace ix
|
|||||||
, _receivedMessageCompressed(false)
|
, _receivedMessageCompressed(false)
|
||||||
, _readyState(ReadyState::CLOSED)
|
, _readyState(ReadyState::CLOSED)
|
||||||
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
|
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
|
||||||
, _closeReason(WebSocketCloseConstants::kInternalErrorMessage)
|
|
||||||
, _closeWireSize(0)
|
, _closeWireSize(0)
|
||||||
, _closeRemote(false)
|
, _closeRemote(false)
|
||||||
, _enablePerMessageDeflate(false)
|
, _enablePerMessageDeflate(false)
|
||||||
@@ -77,6 +76,7 @@ namespace ix
|
|||||||
, _pingCount(0)
|
, _pingCount(0)
|
||||||
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
||||||
{
|
{
|
||||||
|
setCloseReason(WebSocketCloseConstants::kInternalErrorMessage);
|
||||||
_readbuf.resize(kChunkSize);
|
_readbuf.resize(kChunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,10 +179,12 @@ namespace ix
|
|||||||
|
|
||||||
if (readyState == ReadyState::CLOSED)
|
if (readyState == ReadyState::CLOSED)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
if (_onCloseCallback)
|
||||||
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote);
|
{
|
||||||
|
_onCloseCallback(_closeCode, getCloseReason(), _closeWireSize, _closeRemote);
|
||||||
|
}
|
||||||
|
setCloseReason(WebSocketCloseConstants::kInternalErrorMessage);
|
||||||
_closeCode = WebSocketCloseConstants::kInternalErrorCode;
|
_closeCode = WebSocketCloseConstants::kInternalErrorCode;
|
||||||
_closeReason = WebSocketCloseConstants::kInternalErrorMessage;
|
|
||||||
_closeWireSize = 0;
|
_closeWireSize = 0;
|
||||||
_closeRemote = false;
|
_closeRemote = false;
|
||||||
}
|
}
|
||||||
@@ -261,9 +263,10 @@ namespace ix
|
|||||||
{
|
{
|
||||||
// compute lasting delay to wait for next ping / timeout, if at least one set
|
// compute lasting delay to wait for next ping / timeout, if at least one set
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
lastingTimeoutDelayInMs = (int) std::chrono::duration_cast<std::chrono::milliseconds>(
|
int timeSinceLastPingMs = (int) std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
now - _lastSendPingTimePoint)
|
now - _lastSendPingTimePoint)
|
||||||
.count();
|
.count();
|
||||||
|
lastingTimeoutDelayInMs = (1000 * _pingIntervalSecs) - timeSinceLastPingMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -639,11 +642,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
// we got the CLOSE frame answer from our close, so we can close the connection
|
// we got the CLOSE frame answer from our close, so we can close the connection
|
||||||
// if the code/reason are the same
|
// if the code/reason are the same
|
||||||
bool identicalReason;
|
bool identicalReason = _closeCode == code && getCloseReason() == reason;
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
|
||||||
identicalReason = _closeCode == code && _closeReason == reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identicalReason)
|
if (identicalReason)
|
||||||
{
|
{
|
||||||
@@ -797,6 +796,11 @@ namespace ix
|
|||||||
if (wireSize < kChunkSize)
|
if (wireSize < kChunkSize)
|
||||||
{
|
{
|
||||||
success = sendFragment(type, true, message_begin, message_end, compress);
|
success = sendFragment(type, true, message_begin, message_end, compress);
|
||||||
|
|
||||||
|
if (onProgressCallback)
|
||||||
|
{
|
||||||
|
onProgressCallback(0, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1081,13 +1085,10 @@ namespace ix
|
|||||||
{
|
{
|
||||||
closeSocket();
|
closeSocket();
|
||||||
|
|
||||||
{
|
setCloseReason(reason);
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
_closeCode = code;
|
||||||
_closeCode = code;
|
_closeWireSize = closeWireSize;
|
||||||
_closeReason = reason;
|
_closeRemote = remote;
|
||||||
_closeWireSize = closeWireSize;
|
|
||||||
_closeRemote = remote;
|
|
||||||
}
|
|
||||||
|
|
||||||
setReadyState(ReadyState::CLOSED);
|
setReadyState(ReadyState::CLOSED);
|
||||||
_requestInitCancellation = false;
|
_requestInitCancellation = false;
|
||||||
@@ -1107,13 +1108,11 @@ namespace ix
|
|||||||
closeWireSize = reason.size();
|
closeWireSize = reason.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
setCloseReason(reason);
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
_closeCode = code;
|
||||||
_closeCode = code;
|
_closeWireSize = closeWireSize;
|
||||||
_closeReason = reason;
|
_closeRemote = remote;
|
||||||
_closeWireSize = closeWireSize;
|
|
||||||
_closeRemote = remote;
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
||||||
_closingTimePoint = std::chrono::steady_clock::now();
|
_closingTimePoint = std::chrono::steady_clock::now();
|
||||||
@@ -1158,4 +1157,15 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebSocketTransport::setCloseReason(const std::string& reason)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_closeReasonMutex);
|
||||||
|
_closeReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& WebSocketTransport::getCloseReason() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_closeReasonMutex);
|
||||||
|
return _closeReason;
|
||||||
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@@ -178,11 +178,11 @@ namespace ix
|
|||||||
std::atomic<ReadyState> _readyState;
|
std::atomic<ReadyState> _readyState;
|
||||||
|
|
||||||
OnCloseCallback _onCloseCallback;
|
OnCloseCallback _onCloseCallback;
|
||||||
uint16_t _closeCode;
|
|
||||||
std::string _closeReason;
|
std::string _closeReason;
|
||||||
size_t _closeWireSize;
|
mutable std::mutex _closeReasonMutex;
|
||||||
bool _closeRemote;
|
std::atomic<uint16_t> _closeCode;
|
||||||
mutable std::mutex _closeDataMutex;
|
std::atomic<size_t> _closeWireSize;
|
||||||
|
std::atomic<bool> _closeRemote;
|
||||||
|
|
||||||
// Data used for Per Message Deflate compression (with zlib)
|
// Data used for Per Message Deflate compression (with zlib)
|
||||||
WebSocketPerMessageDeflatePtr _perMessageDeflate;
|
WebSocketPerMessageDeflatePtr _perMessageDeflate;
|
||||||
@@ -267,5 +267,8 @@ namespace ix
|
|||||||
void unmaskReceiveBuffer(const wsheader_type& ws);
|
void unmaskReceiveBuffer(const wsheader_type& ws);
|
||||||
|
|
||||||
std::string getMergedChunks() const;
|
std::string getMergedChunks() const;
|
||||||
|
|
||||||
|
void setCloseReason(const std::string& reason);
|
||||||
|
const std::string& getCloseReason() const;
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@@ -6,4 +6,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define IX_WEBSOCKET_VERSION "9.9.0"
|
#define IX_WEBSOCKET_VERSION "10.1.1"
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName_apple.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#include "../IXSetThreadName.h"
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
void setThreadName(const std::string& name)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Apple reserves 16 bytes for its thread names
|
|
||||||
// Notice that the Apple version of pthread_setname_np
|
|
||||||
// does not take a pthread_t argument
|
|
||||||
//
|
|
||||||
pthread_setname_np(name.substr(0, 63).c_str());
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
@@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName_freebsd.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#include "../IXSetThreadName.h"
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <pthread_np.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
void setThreadName(const std::string& name)
|
|
||||||
{
|
|
||||||
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName_linux.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#include "../IXSetThreadName.h"
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
void setThreadName(const std::string& name)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Linux only reserves 16 bytes for its thread names
|
|
||||||
// See prctl and PR_SET_NAME property in
|
|
||||||
// http://man7.org/linux/man-pages/man2/prctl.2.html
|
|
||||||
//
|
|
||||||
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName_windows.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#include "../IXSetThreadName.h"
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
|
||||||
|
|
||||||
#pragma pack(push, 8)
|
|
||||||
typedef struct tagTHREADNAME_INFO
|
|
||||||
{
|
|
||||||
DWORD dwType; // Must be 0x1000.
|
|
||||||
LPCSTR szName; // Pointer to name (in user addr space).
|
|
||||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
|
||||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
|
||||||
} THREADNAME_INFO;
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
void SetThreadName(DWORD dwThreadID, const char* threadName)
|
|
||||||
{
|
|
||||||
THREADNAME_INFO info;
|
|
||||||
info.dwType = 0x1000;
|
|
||||||
info.szName = threadName;
|
|
||||||
info.dwThreadID = dwThreadID;
|
|
||||||
info.dwFlags = 0;
|
|
||||||
|
|
||||||
__try
|
|
||||||
{
|
|
||||||
RaiseException(
|
|
||||||
MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info);
|
|
||||||
}
|
|
||||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setThreadName(const std::string& name)
|
|
||||||
{
|
|
||||||
SetThreadName(-1, name.c_str());
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
15
makefile
15
makefile
@@ -34,7 +34,7 @@ ws:
|
|||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
|
||||||
|
|
||||||
ws_install:
|
ws_install:
|
||||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && make -j 4 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_openssl:
|
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)
|
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)
|
||||||
@@ -174,7 +174,7 @@ autobahn_report:
|
|||||||
cp -rvf ~/sandbox/reports/clients/* ../bsergean.github.io/IXWebSocket/autobahn/
|
cp -rvf ~/sandbox/reports/clients/* ../bsergean.github.io/IXWebSocket/autobahn/
|
||||||
|
|
||||||
httpd:
|
httpd:
|
||||||
clang++ --std=c++14 --stdlib=libc++ httpd.cpp \
|
clang++ --std=c++14 --stdlib=libc++ -o ixhttpd httpd.cpp \
|
||||||
ixwebsocket/IXSelectInterruptFactory.cpp \
|
ixwebsocket/IXSelectInterruptFactory.cpp \
|
||||||
ixwebsocket/IXCancellationRequest.cpp \
|
ixwebsocket/IXCancellationRequest.cpp \
|
||||||
ixwebsocket/IXSocketTLSOptions.cpp \
|
ixwebsocket/IXSocketTLSOptions.cpp \
|
||||||
@@ -193,11 +193,11 @@ httpd:
|
|||||||
ixwebsocket/IXConnectionState.cpp \
|
ixwebsocket/IXConnectionState.cpp \
|
||||||
ixwebsocket/IXUrlParser.cpp \
|
ixwebsocket/IXUrlParser.cpp \
|
||||||
ixwebsocket/IXSelectInterrupt.cpp \
|
ixwebsocket/IXSelectInterrupt.cpp \
|
||||||
ixwebsocket/apple/IXSetThreadName_apple.cpp \
|
ixwebsocket/IXSetThreadName.cpp \
|
||||||
-lz
|
-lz
|
||||||
|
|
||||||
httpd_linux:
|
httpd_linux:
|
||||||
g++ --std=c++11 -o ixhttpd httpd.cpp -Iixwebsocket \
|
g++ --std=c++14 -o ixhttpd httpd.cpp -Iixwebsocket \
|
||||||
ixwebsocket/IXSelectInterruptFactory.cpp \
|
ixwebsocket/IXSelectInterruptFactory.cpp \
|
||||||
ixwebsocket/IXCancellationRequest.cpp \
|
ixwebsocket/IXCancellationRequest.cpp \
|
||||||
ixwebsocket/IXSocketTLSOptions.cpp \
|
ixwebsocket/IXSocketTLSOptions.cpp \
|
||||||
@@ -216,7 +216,7 @@ httpd_linux:
|
|||||||
ixwebsocket/IXConnectionState.cpp \
|
ixwebsocket/IXConnectionState.cpp \
|
||||||
ixwebsocket/IXUrlParser.cpp \
|
ixwebsocket/IXUrlParser.cpp \
|
||||||
ixwebsocket/IXSelectInterrupt.cpp \
|
ixwebsocket/IXSelectInterrupt.cpp \
|
||||||
ixwebsocket/linux/IXSetThreadName_linux.cpp \
|
ixwebsocket/IXSetThreadName.cpp \
|
||||||
-lz -lpthread
|
-lz -lpthread
|
||||||
cp -f ixhttpd /usr/local/bin
|
cp -f ixhttpd /usr/local/bin
|
||||||
|
|
||||||
@@ -238,9 +238,12 @@ install_cmake_for_linux:
|
|||||||
doc:
|
doc:
|
||||||
mkdocs gh-deploy
|
mkdocs gh-deploy
|
||||||
|
|
||||||
change:
|
change: format
|
||||||
vim ixwebsocket/IXWebSocketVersion.h docs/CHANGELOG.md
|
vim ixwebsocket/IXWebSocketVersion.h docs/CHANGELOG.md
|
||||||
|
|
||||||
|
commit:
|
||||||
|
git commit -am "`sh tools/extract_latest_change.sh`"
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
.PHONY: ws
|
.PHONY: ws
|
||||||
|
@@ -37,11 +37,11 @@ set (SOURCES
|
|||||||
|
|
||||||
test_runner.cpp
|
test_runner.cpp
|
||||||
IXTest.cpp
|
IXTest.cpp
|
||||||
IXGetFreePort.cpp
|
|
||||||
../third_party/msgpack11/msgpack11.cpp
|
../third_party/msgpack11/msgpack11.cpp
|
||||||
|
|
||||||
IXSocketTest.cpp
|
IXSocketTest.cpp
|
||||||
IXSocketConnectTest.cpp
|
IXSocketConnectTest.cpp
|
||||||
|
# IXWebSocketLeakTest.cpp # commented until we have a fix for #224
|
||||||
IXWebSocketServerTest.cpp
|
IXWebSocketServerTest.cpp
|
||||||
IXWebSocketTestConnectionDisconnection.cpp
|
IXWebSocketTestConnectionDisconnection.cpp
|
||||||
IXUrlParserTest.cpp
|
IXUrlParserTest.cpp
|
||||||
@@ -56,6 +56,7 @@ set (SOURCES
|
|||||||
IXWebSocketChatTest.cpp
|
IXWebSocketChatTest.cpp
|
||||||
IXWebSocketBroadcastTest.cpp
|
IXWebSocketBroadcastTest.cpp
|
||||||
IXWebSocketPerMessageDeflateCompressorTest.cpp
|
IXWebSocketPerMessageDeflateCompressorTest.cpp
|
||||||
|
IXStreamSqlTest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Some unittest don't work on windows yet
|
# Some unittest don't work on windows yet
|
||||||
|
@@ -108,7 +108,7 @@ namespace
|
|||||||
}
|
}
|
||||||
else if (event->type == ix::CobraEventType::UnSubscribed)
|
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber: ununexpected from channel " << event->subscriptionId;
|
TLogger() << "Subscriber: unsubscribed from channel " << event->subscriptionId;
|
||||||
if (event->subscriptionId != channel)
|
if (event->subscriptionId != channel)
|
||||||
{
|
{
|
||||||
TLogger() << "Subscriber: unexpected channel " << event->subscriptionId;
|
TLogger() << "Subscriber: unexpected channel " << event->subscriptionId;
|
||||||
|
@@ -92,6 +92,9 @@ TEST_CASE("Cobra_to_stdout_bot", "[cobra_bots]")
|
|||||||
cobraBotConfig.enableHeartbeat = false;
|
cobraBotConfig.enableHeartbeat = false;
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
|
|
||||||
|
cobraBotConfig.filter =
|
||||||
|
std::string("select * from `") + channel + "` where id = 'sms_metric_A_id'";
|
||||||
|
|
||||||
// We could try to capture the output ... not sure how.
|
// We could try to capture the output ... not sure how.
|
||||||
bool fluentd = true;
|
bool fluentd = true;
|
||||||
|
|
||||||
|
@@ -4,9 +4,9 @@
|
|||||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXGetFreePort.h"
|
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <ixwebsocket/IXGetFreePort.h>
|
||||||
#include <ixwebsocket/IXHttpClient.h>
|
#include <ixwebsocket/IXHttpClient.h>
|
||||||
#include <ixwebsocket/IXHttpServer.h>
|
#include <ixwebsocket/IXHttpServer.h>
|
||||||
|
|
||||||
|
42
test/IXStreamSqlTest.cpp
Normal file
42
test/IXStreamSqlTest.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* IXStreamSqlTest.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXTest.h"
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <ixsnake/IXStreamSql.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
TEST_CASE("stream_sql", "[streamsql]")
|
||||||
|
{
|
||||||
|
SECTION("expression A")
|
||||||
|
{
|
||||||
|
snake::StreamSql streamSql(
|
||||||
|
"select * from subscriber_republished_v1_neo where session LIKE '%123456%'");
|
||||||
|
|
||||||
|
nlohmann::json msg = {{"session", "123456"}, {"id", "foo_id"}, {"timestamp", 12}};
|
||||||
|
|
||||||
|
CHECK(streamSql.match(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("expression A")
|
||||||
|
{
|
||||||
|
snake::StreamSql streamSql("select * from `subscriber_republished_v1_neo` where "
|
||||||
|
"session = '30091320ed8d4e50b758f8409b83bed7'");
|
||||||
|
|
||||||
|
nlohmann::json msg = {{"session", "30091320ed8d4e50b758f8409b83bed7"},
|
||||||
|
{"id", "foo_id"},
|
||||||
|
{"timestamp", 12}};
|
||||||
|
|
||||||
|
CHECK(streamSql.match(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ix
|
@@ -84,39 +84,38 @@ namespace ix
|
|||||||
|
|
||||||
bool startWebSocketEchoServer(ix::WebSocketServer& server)
|
bool startWebSocketEchoServer(ix::WebSocketServer& server)
|
||||||
{
|
{
|
||||||
server.setOnConnectionCallback([&server](std::shared_ptr<ix::WebSocket> webSocket,
|
server.setOnClientMessageCallback(
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
[&server](std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
ConnectionInfo& connectionInfo,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
WebSocket& webSocket,
|
||||||
webSocket->setOnMessageCallback(
|
const ix::WebSocketMessagePtr& msg) {
|
||||||
[webSocket, connectionState, remoteIp, &server](const ix::WebSocketMessagePtr& msg) {
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
TLogger() << "New connection";
|
||||||
|
TLogger() << "Remote ip: " << remoteIp;
|
||||||
|
TLogger() << "Uri: " << msg->openInfo.uri;
|
||||||
|
TLogger() << "Headers:";
|
||||||
|
for (auto it : msg->openInfo.headers)
|
||||||
{
|
{
|
||||||
TLogger() << "New connection";
|
TLogger() << it.first << ": " << it.second;
|
||||||
TLogger() << "Remote ip: " << remoteIp;
|
}
|
||||||
TLogger() << "Uri: " << msg->openInfo.uri;
|
}
|
||||||
TLogger() << "Headers:";
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
for (auto it : msg->openInfo.headers)
|
{
|
||||||
|
TLogger() << "Closed connection";
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
for (auto&& client : server.getClients())
|
||||||
|
{
|
||||||
|
if (client.get() != &webSocket)
|
||||||
{
|
{
|
||||||
TLogger() << it.first << ": " << it.second;
|
client->send(msg->str, msg->binary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
}
|
||||||
{
|
});
|
||||||
TLogger() << "Closed connection";
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
for (auto&& client : server.getClients())
|
|
||||||
{
|
|
||||||
if (client != webSocket)
|
|
||||||
{
|
|
||||||
client->send(msg->str, msg->binary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
|
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXGetFreePort.h"
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ixsnake/IXAppConfig.h>
|
#include <ixsnake/IXAppConfig.h>
|
||||||
|
#include <ixwebsocket/IXGetFreePort.h>
|
||||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@@ -189,13 +189,14 @@ namespace
|
|||||||
bool preferTLS = true;
|
bool preferTLS = true;
|
||||||
server.setTLSOptions(makeServerTLSOptions(preferTLS));
|
server.setTLSOptions(makeServerTLSOptions(preferTLS));
|
||||||
|
|
||||||
server.setOnConnectionCallback([&server, &connectionId](
|
server.setOnClientMessageCallback(
|
||||||
std::shared_ptr<ix::WebSocket> webSocket,
|
[&server, &connectionId](std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
ConnectionInfo& connectionInfo,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
WebSocket& webSocket,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
const ix::WebSocketMessagePtr& msg) {
|
||||||
webSocket->setOnMessageCallback([webSocket, connectionState, remoteIp, &connectionId, &server](
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
const ix::WebSocketMessagePtr& msg) {
|
|
||||||
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
TLogger() << "New connection";
|
TLogger() << "New connection";
|
||||||
@@ -219,14 +220,13 @@ namespace
|
|||||||
{
|
{
|
||||||
for (auto&& client : server.getClients())
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
if (client != webSocket)
|
if (client.get() != &webSocket)
|
||||||
{
|
{
|
||||||
client->send(msg->str, msg->binary);
|
client->send(msg->str, msg->binary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
|
@@ -193,41 +193,39 @@ namespace
|
|||||||
|
|
||||||
bool startServer(ix::WebSocketServer& server)
|
bool startServer(ix::WebSocketServer& server)
|
||||||
{
|
{
|
||||||
server.setOnConnectionCallback([&server](std::shared_ptr<ix::WebSocket> webSocket,
|
server.setOnClientMessageCallback(
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
[&server](std::shared_ptr<ConnectionState> connectionState,
|
||||||
|
ConnectionInfo& connectionInfo,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
WebSocket& webSocket,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
const ix::WebSocketMessagePtr& msg) {
|
||||||
webSocket->setOnMessageCallback(
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
[webSocket, connectionState, remoteIp, &server](const ix::WebSocketMessagePtr& msg) {
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
{
|
||||||
|
TLogger() << "New connection";
|
||||||
|
TLogger() << "remote ip: " << remoteIp;
|
||||||
|
TLogger() << "id: " << connectionState->getId();
|
||||||
|
TLogger() << "Uri: " << msg->openInfo.uri;
|
||||||
|
TLogger() << "Headers:";
|
||||||
|
for (auto it : msg->openInfo.headers)
|
||||||
{
|
{
|
||||||
TLogger() << "New connection";
|
TLogger() << it.first << ": " << it.second;
|
||||||
TLogger() << "remote ip: " << remoteIp;
|
}
|
||||||
TLogger() << "id: " << connectionState->getId();
|
}
|
||||||
TLogger() << "Uri: " << msg->openInfo.uri;
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
TLogger() << "Headers:";
|
{
|
||||||
for (auto it : msg->openInfo.headers)
|
log("Closed connection");
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
for (auto&& client : server.getClients())
|
||||||
|
{
|
||||||
|
if (client.get() != &webSocket)
|
||||||
{
|
{
|
||||||
TLogger() << it.first << ": " << it.second;
|
client->sendBinary(msg->str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
}
|
||||||
{
|
});
|
||||||
log("Closed connection");
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
for (auto&& client : server.getClients())
|
|
||||||
{
|
|
||||||
if (client != webSocket)
|
|
||||||
{
|
|
||||||
client->sendBinary(msg->str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
|
@@ -168,45 +168,38 @@ namespace
|
|||||||
std::mutex& mutexWrite)
|
std::mutex& mutexWrite)
|
||||||
{
|
{
|
||||||
// A dev/null server
|
// A dev/null server
|
||||||
server.setOnConnectionCallback(
|
server.setOnClientMessageCallback(
|
||||||
[&receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](
|
[&receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](
|
||||||
std::shared_ptr<ix::WebSocket> webSocket,
|
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
ConnectionInfo& connectionInfo,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
WebSocket& /*webSocket*/,
|
||||||
webSocket->setOnMessageCallback([webSocket,
|
const ix::WebSocketMessagePtr& msg) {
|
||||||
connectionState,
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
remoteIp,
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
&receivedCloseCode,
|
{
|
||||||
&receivedCloseReason,
|
TLogger() << "New server connection";
|
||||||
&receivedCloseRemote,
|
TLogger() << "remote ip: " << remoteIp;
|
||||||
&mutexWrite](const ix::WebSocketMessagePtr& msg) {
|
TLogger() << "id: " << connectionState->getId();
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
TLogger() << "Uri: " << msg->openInfo.uri;
|
||||||
|
TLogger() << "Headers:";
|
||||||
|
for (auto it : msg->openInfo.headers)
|
||||||
{
|
{
|
||||||
TLogger() << "New server connection";
|
TLogger() << it.first << ": " << it.second;
|
||||||
TLogger() << "remote ip: " << remoteIp;
|
|
||||||
TLogger() << "id: " << connectionState->getId();
|
|
||||||
TLogger() << "Uri: " << msg->openInfo.uri;
|
|
||||||
TLogger() << "Headers:";
|
|
||||||
for (auto it : msg->openInfo.headers)
|
|
||||||
{
|
|
||||||
TLogger() << it.first << ": " << it.second;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
}
|
||||||
{
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
std::stringstream ss;
|
{
|
||||||
ss << "Server closed connection(" << msg->closeInfo.code << ","
|
std::stringstream ss;
|
||||||
<< msg->closeInfo.reason << ")";
|
ss << "Server closed connection(" << msg->closeInfo.code << ","
|
||||||
log(ss.str());
|
<< msg->closeInfo.reason << ")";
|
||||||
|
log(ss.str());
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lck(mutexWrite);
|
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||||
|
|
||||||
receivedCloseCode = msg->closeInfo.code;
|
receivedCloseCode = msg->closeInfo.code;
|
||||||
receivedCloseReason = std::string(msg->closeInfo.reason);
|
receivedCloseReason = std::string(msg->closeInfo.reason);
|
||||||
receivedCloseRemote = msg->closeInfo.remote;
|
receivedCloseRemote = msg->closeInfo.remote;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
|
183
test/IXWebSocketLeakTest.cpp
Normal file
183
test/IXWebSocketLeakTest.cpp
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketServer.cpp
|
||||||
|
* Author: Benjamin Sergeant, @marcelkauf
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXTest.h"
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class WebSocketClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WebSocketClient(int port);
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
bool isReady() const;
|
||||||
|
bool hasConnectionError() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ix::WebSocket _webSocket;
|
||||||
|
int _port;
|
||||||
|
std::atomic<bool> _connectionError;
|
||||||
|
};
|
||||||
|
|
||||||
|
WebSocketClient::WebSocketClient(int port)
|
||||||
|
: _port(port)
|
||||||
|
, _connectionError(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebSocketClient::hasConnectionError() const
|
||||||
|
{
|
||||||
|
return _connectionError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebSocketClient::isReady() const
|
||||||
|
{
|
||||||
|
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::stop()
|
||||||
|
{
|
||||||
|
_webSocket.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::start()
|
||||||
|
{
|
||||||
|
std::string url;
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "ws://localhost:" << _port << "/";
|
||||||
|
|
||||||
|
url = ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
_webSocket.setUrl(url);
|
||||||
|
_webSocket.disableAutomaticReconnection();
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
log(std::string("Connecting to url: ") + url);
|
||||||
|
|
||||||
|
_webSocket.setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) {
|
||||||
|
std::stringstream ss;
|
||||||
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
log("client connected");
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
log("client disconnected");
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
|
{
|
||||||
|
_connectionError = true;
|
||||||
|
log("error");
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||||
|
{
|
||||||
|
log("pong");
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Ping)
|
||||||
|
{
|
||||||
|
log("ping");
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
log("message");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log("invalid type");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_webSocket.start();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Websocket leak test")
|
||||||
|
{
|
||||||
|
SECTION("Websocket destructor is called when closing the connection.")
|
||||||
|
{
|
||||||
|
// stores the server websocket in order to check the use_count
|
||||||
|
std::shared_ptr<WebSocket> webSocketPtr;
|
||||||
|
|
||||||
|
{
|
||||||
|
int port = getFreePort();
|
||||||
|
WebSocketServer server(port);
|
||||||
|
|
||||||
|
server.setOnConnectionCallback(
|
||||||
|
[&webSocketPtr](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState,
|
||||||
|
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
||||||
|
// original ptr in WebSocketServer::handleConnection and the callback argument
|
||||||
|
REQUIRE(webSocket.use_count() == 2);
|
||||||
|
webSocket->setOnMessageCallback([&webSocketPtr, webSocket, connectionState](
|
||||||
|
const ix::WebSocketMessagePtr& msg) {
|
||||||
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
log(std::string("New connection id: ") + connectionState->getId());
|
||||||
|
// original ptr in WebSocketServer::handleConnection, captured ptr of
|
||||||
|
// this callback, and ptr in WebSocketServer::_clients
|
||||||
|
REQUIRE(webSocket.use_count() == 3);
|
||||||
|
webSocketPtr = std::shared_ptr<WebSocket>(webSocket);
|
||||||
|
REQUIRE(webSocket.use_count() == 4);
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
log(std::string("Client closed connection id: ") +
|
||||||
|
connectionState->getId());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log(std::string(msg->str));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// original ptr in WebSocketServer::handleConnection, argument of this callback,
|
||||||
|
// and captured ptr in websocket callback
|
||||||
|
REQUIRE(webSocket.use_count() == 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen();
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
WebSocketClient webSocketClient(port);
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
REQUIRE(!webSocketClient.hasConnectionError());
|
||||||
|
if (webSocketClient.isReady()) break;
|
||||||
|
ix::msleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
// same value as in Open-handler above
|
||||||
|
REQUIRE(webSocketPtr.use_count() == 4);
|
||||||
|
|
||||||
|
ix::msleep(500);
|
||||||
|
webSocketClient.stop();
|
||||||
|
ix::msleep(500);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
// websocket should only be referenced by webSocketPtr but is still used by the
|
||||||
|
// websocket callback
|
||||||
|
REQUIRE(webSocketPtr.use_count() == 1);
|
||||||
|
webSocketPtr->setOnMessageCallback(nullptr);
|
||||||
|
// websocket should only be referenced by webSocketPtr
|
||||||
|
REQUIRE(webSocketPtr.use_count() == 1);
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
// websocket should only be referenced by webSocketPtr
|
||||||
|
REQUIRE(webSocketPtr.use_count() == 1);
|
||||||
|
}
|
||||||
|
}
|
@@ -33,13 +33,14 @@ namespace ix
|
|||||||
};
|
};
|
||||||
server.setConnectionStateFactory(factory);
|
server.setConnectionStateFactory(factory);
|
||||||
|
|
||||||
server.setOnConnectionCallback([&server, &connectionId](
|
server.setOnClientMessageCallback(
|
||||||
std::shared_ptr<ix::WebSocket> webSocket,
|
[&server, &connectionId](std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
ConnectionInfo& connectionInfo,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
WebSocket& webSocket,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
const ix::WebSocketMessagePtr& msg) {
|
||||||
webSocket->setOnMessageCallback([webSocket, connectionState, remoteIp, &connectionId, &server](
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
const ix::WebSocketMessagePtr& msg) {
|
|
||||||
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
TLogger() << "New connection";
|
TLogger() << "New connection";
|
||||||
@@ -63,14 +64,13 @@ namespace ix
|
|||||||
{
|
{
|
||||||
for (auto&& client : server.getClients())
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
if (client != webSocket)
|
if (client.get() != &webSocket)
|
||||||
{
|
{
|
||||||
client->send(msg->str, msg->binary);
|
client->send(msg->str, msg->binary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
|
@@ -16,42 +16,40 @@ using namespace ix;
|
|||||||
|
|
||||||
bool startServer(ix::WebSocketServer& server, std::string& subProtocols)
|
bool startServer(ix::WebSocketServer& server, std::string& subProtocols)
|
||||||
{
|
{
|
||||||
server.setOnConnectionCallback(
|
server.setOnClientMessageCallback(
|
||||||
[&server, &subProtocols](std::shared_ptr<ix::WebSocket> webSocket,
|
[&server, &subProtocols](std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
ConnectionInfo& connectionInfo,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
WebSocket& webSocket,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
const ix::WebSocketMessagePtr& msg) {
|
||||||
webSocket->setOnMessageCallback([webSocket, connectionState, remoteIp, &server, &subProtocols](
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
const ix::WebSocketMessagePtr& msg) {
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
{
|
||||||
|
TLogger() << "New connection";
|
||||||
|
TLogger() << "remote ip: " << remoteIp;
|
||||||
|
TLogger() << "id: " << connectionState->getId();
|
||||||
|
TLogger() << "Uri: " << msg->openInfo.uri;
|
||||||
|
TLogger() << "Headers:";
|
||||||
|
for (auto it : msg->openInfo.headers)
|
||||||
{
|
{
|
||||||
TLogger() << "New connection";
|
TLogger() << it.first << ": " << it.second;
|
||||||
TLogger() << "remote ip: " << remoteIp;
|
}
|
||||||
TLogger() << "id: " << connectionState->getId();
|
|
||||||
TLogger() << "Uri: " << msg->openInfo.uri;
|
|
||||||
TLogger() << "Headers:";
|
|
||||||
for (auto it : msg->openInfo.headers)
|
|
||||||
{
|
|
||||||
TLogger() << it.first << ": " << it.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
subProtocols = msg->openInfo.headers["Sec-WebSocket-Protocol"];
|
subProtocols = msg->openInfo.headers["Sec-WebSocket-Protocol"];
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
log("Closed connection");
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
log("Closed connection");
|
if (client.get() != &webSocket)
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
for (auto&& client : server.getClients())
|
|
||||||
{
|
{
|
||||||
if (client != webSocket)
|
client->sendBinary(msg->str);
|
||||||
{
|
|
||||||
client->sendBinary(msg->str);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
|
@@ -10,10 +10,18 @@
|
|||||||
#include <ixwebsocket/IXNetSystem.h>
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <signal.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
ix::initNetSystem();
|
ix::initNetSystem();
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#endif
|
||||||
|
|
||||||
ix::CoreLogger::LogFunc logFunc = [](const char* msg, ix::LogLevel level) {
|
ix::CoreLogger::LogFunc logFunc = [](const char* msg, ix::LogLevel level) {
|
||||||
switch (level)
|
switch (level)
|
||||||
{
|
{
|
||||||
@@ -49,6 +57,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
ix::CoreLogger::setLogFunction(logFunc);
|
ix::CoreLogger::setLogFunction(logFunc);
|
||||||
|
spdlog::set_level(spdlog::level::debug);
|
||||||
|
|
||||||
int result = Catch::Session().run(argc, argv);
|
int result = Catch::Session().run(argc, argv);
|
||||||
|
|
||||||
|
3
tools/extract_latest_change.sh
Normal file
3
tools/extract_latest_change.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
grep -A 3 '^##' docs/CHANGELOG.md | head -n 3 | tail -n 1
|
@@ -20,7 +20,6 @@ option(USE_TLS "Add TLS support" ON)
|
|||||||
|
|
||||||
include_directories(ws .)
|
include_directories(ws .)
|
||||||
include_directories(ws ..)
|
include_directories(ws ..)
|
||||||
include_directories(ws ../third_party)
|
|
||||||
include_directories(ws ../third_party/spdlog/include)
|
include_directories(ws ../third_party/spdlog/include)
|
||||||
include_directories(ws ../third_party/cpp-linenoise)
|
include_directories(ws ../third_party/cpp-linenoise)
|
||||||
|
|
||||||
@@ -66,7 +65,6 @@ add_executable(ws
|
|||||||
ws_cobra_publish.cpp
|
ws_cobra_publish.cpp
|
||||||
ws_httpd.cpp
|
ws_httpd.cpp
|
||||||
ws_autobahn.cpp
|
ws_autobahn.cpp
|
||||||
ws_proxy_server.cpp
|
|
||||||
ws_sentry_minidump_upload.cpp
|
ws_sentry_minidump_upload.cpp
|
||||||
ws_dns_lookup.cpp
|
ws_dns_lookup.cpp
|
||||||
ws.cpp)
|
ws.cpp)
|
||||||
|
@@ -14,6 +14,7 @@ function cleanup_and_exit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WITH_TLS=${WITH_TLS:-0}
|
WITH_TLS=${WITH_TLS:-0}
|
||||||
|
BLOCKS=${BLOCKS:-20000}
|
||||||
|
|
||||||
rm -rf /tmp/ws_test
|
rm -rf /tmp/ws_test
|
||||||
mkdir -p /tmp/ws_test
|
mkdir -p /tmp/ws_test
|
||||||
@@ -57,7 +58,7 @@ ws receive "${protocol}127.0.0.1:8090" ${delay} --pidfile /tmp/ws_test/pidfile.r
|
|||||||
|
|
||||||
mkdir -p /tmp/ws_test/send
|
mkdir -p /tmp/ws_test/send
|
||||||
cd /tmp/ws_test/send
|
cd /tmp/ws_test/send
|
||||||
dd if=/dev/urandom of=/tmp/ws_test/send/20M_file count=20000 bs=1024
|
dd if=/dev/urandom of=/tmp/ws_test/send/20M_file count=$BLOCKS bs=1024
|
||||||
|
|
||||||
# Start the sender job
|
# Start the sender job
|
||||||
ws send ${client_tls} --pidfile /tmp/ws_test/pidfile.send "${protocol}127.0.0.1:8090" /tmp/ws_test/send/20M_file
|
ws send ${client_tls} --pidfile /tmp/ws_test/pidfile.send "${protocol}127.0.0.1:8090" /tmp/ws_test/send/20M_file
|
||||||
|
45
ws/ws.cpp
45
ws/ws.cpp
@@ -22,6 +22,7 @@
|
|||||||
#include <ixwebsocket/IXNetSystem.h>
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
#include <ixwebsocket/IXSocket.h>
|
#include <ixwebsocket/IXSocket.h>
|
||||||
#include <ixwebsocket/IXUserAgent.h>
|
#include <ixwebsocket/IXUserAgent.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketProxyServer.h>
|
||||||
#include <spdlog/sinks/basic_file_sink.h>
|
#include <spdlog/sinks/basic_file_sink.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -74,6 +75,7 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
ix::CoreLogger::setLogFunction(logFunc);
|
ix::CoreLogger::setLogFunction(logFunc);
|
||||||
|
spdlog::set_level(spdlog::level::debug);
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
@@ -122,6 +124,7 @@ int main(int argc, char** argv)
|
|||||||
std::string key;
|
std::string key;
|
||||||
std::string logfile;
|
std::string logfile;
|
||||||
std::string scriptPath;
|
std::string scriptPath;
|
||||||
|
std::string republishChannel;
|
||||||
ix::SocketTLSOptions tlsOptions;
|
ix::SocketTLSOptions tlsOptions;
|
||||||
ix::CobraConfig cobraConfig;
|
ix::CobraConfig cobraConfig;
|
||||||
ix::CobraBotConfig cobraBotConfig;
|
ix::CobraBotConfig cobraBotConfig;
|
||||||
@@ -391,6 +394,7 @@ int main(int argc, char** argv)
|
|||||||
snakeApp->add_option("--redis_password", redisPassword, "Redis password");
|
snakeApp->add_option("--redis_password", redisPassword, "Redis password");
|
||||||
snakeApp->add_option("--apps_config_path", appsConfigPath, "Path to auth data")
|
snakeApp->add_option("--apps_config_path", appsConfigPath, "Path to auth data")
|
||||||
->check(CLI::ExistingPath);
|
->check(CLI::ExistingPath);
|
||||||
|
snakeApp->add_option("--republish_channel", republishChannel, "Republish channel");
|
||||||
snakeApp->add_flag("-v", verbose, "Verbose");
|
snakeApp->add_flag("-v", verbose, "Verbose");
|
||||||
snakeApp->add_flag("-d", disablePong, "Disable Pongs");
|
snakeApp->add_flag("-d", disablePong, "Disable Pongs");
|
||||||
addTLSOptions(snakeApp);
|
addTLSOptions(snakeApp);
|
||||||
@@ -482,7 +486,24 @@ int main(int argc, char** argv)
|
|||||||
cobraBotConfig.cobraConfig.socketTLSOptions = tlsOptions;
|
cobraBotConfig.cobraConfig.socketTLSOptions = tlsOptions;
|
||||||
|
|
||||||
int ret = 1;
|
int ret = 1;
|
||||||
if (app.got_subcommand("transfer"))
|
if (app.got_subcommand("connect"))
|
||||||
|
{
|
||||||
|
ret = ix::ws_connect_main(url,
|
||||||
|
headers,
|
||||||
|
disableAutomaticReconnection,
|
||||||
|
disablePerMessageDeflate,
|
||||||
|
binaryMode,
|
||||||
|
maxWaitBetweenReconnectionRetries,
|
||||||
|
tlsOptions,
|
||||||
|
subprotocol,
|
||||||
|
pingIntervalSecs);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("echo_server"))
|
||||||
|
{
|
||||||
|
ret = ix::ws_echo_server_main(
|
||||||
|
port, greetings, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("transfer"))
|
||||||
{
|
{
|
||||||
ret = ix::ws_transfer_main(port, hostname, tlsOptions);
|
ret = ix::ws_transfer_main(port, hostname, tlsOptions);
|
||||||
}
|
}
|
||||||
@@ -495,27 +516,10 @@ int main(int argc, char** argv)
|
|||||||
bool enablePerMessageDeflate = false;
|
bool enablePerMessageDeflate = false;
|
||||||
ret = ix::ws_receive_main(url, enablePerMessageDeflate, delayMs, tlsOptions);
|
ret = ix::ws_receive_main(url, enablePerMessageDeflate, delayMs, tlsOptions);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("connect"))
|
|
||||||
{
|
|
||||||
ret = ix::ws_connect_main(url,
|
|
||||||
headers,
|
|
||||||
disableAutomaticReconnection,
|
|
||||||
disablePerMessageDeflate,
|
|
||||||
binaryMode,
|
|
||||||
maxWaitBetweenReconnectionRetries,
|
|
||||||
tlsOptions,
|
|
||||||
subprotocol,
|
|
||||||
pingIntervalSecs);
|
|
||||||
}
|
|
||||||
else if (app.got_subcommand("chat"))
|
else if (app.got_subcommand("chat"))
|
||||||
{
|
{
|
||||||
ret = ix::ws_chat_main(url, user);
|
ret = ix::ws_chat_main(url, user);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("echo_server"))
|
|
||||||
{
|
|
||||||
ret = ix::ws_echo_server_main(
|
|
||||||
port, greetings, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong);
|
|
||||||
}
|
|
||||||
else if (app.got_subcommand("broadcast_server"))
|
else if (app.got_subcommand("broadcast_server"))
|
||||||
{
|
{
|
||||||
ret = ix::ws_broadcast_server_main(port, hostname, tlsOptions);
|
ret = ix::ws_broadcast_server_main(port, hostname, tlsOptions);
|
||||||
@@ -637,7 +641,8 @@ int main(int argc, char** argv)
|
|||||||
verbose,
|
verbose,
|
||||||
appsConfigPath,
|
appsConfigPath,
|
||||||
tlsOptions,
|
tlsOptions,
|
||||||
disablePong);
|
disablePong,
|
||||||
|
republishChannel);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("httpd"))
|
else if (app.got_subcommand("httpd"))
|
||||||
{
|
{
|
||||||
@@ -653,7 +658,7 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
else if (app.got_subcommand("proxy_server"))
|
else if (app.got_subcommand("proxy_server"))
|
||||||
{
|
{
|
||||||
ret = ix::ws_proxy_server_main(port, hostname, tlsOptions, remoteHost, verbose);
|
ret = ix::websocket_proxy_server_main(port, hostname, tlsOptions, remoteHost, verbose);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("upload_minidump"))
|
else if (app.got_subcommand("upload_minidump"))
|
||||||
{
|
{
|
||||||
|
9
ws/ws.h
9
ws/ws.h
@@ -103,7 +103,8 @@ namespace ix
|
|||||||
bool verbose,
|
bool verbose,
|
||||||
const std::string& appsConfigPath,
|
const std::string& appsConfigPath,
|
||||||
const ix::SocketTLSOptions& tlsOptions,
|
const ix::SocketTLSOptions& tlsOptions,
|
||||||
bool disablePong);
|
bool disablePong,
|
||||||
|
const std::string& republishChannel);
|
||||||
|
|
||||||
int ws_httpd_main(int port,
|
int ws_httpd_main(int port,
|
||||||
const std::string& hostname,
|
const std::string& hostname,
|
||||||
@@ -115,12 +116,6 @@ namespace ix
|
|||||||
|
|
||||||
int ws_redis_server_main(int port, const std::string& hostname);
|
int ws_redis_server_main(int port, const std::string& hostname);
|
||||||
|
|
||||||
int ws_proxy_server_main(int port,
|
|
||||||
const std::string& hostname,
|
|
||||||
const ix::SocketTLSOptions& tlsOptions,
|
|
||||||
const std::string& remoteHost,
|
|
||||||
bool verbose);
|
|
||||||
|
|
||||||
int ws_sentry_minidump_upload(const std::string& metadataPath,
|
int ws_sentry_minidump_upload(const std::string& metadataPath,
|
||||||
const std::string& minidump,
|
const std::string& minidump,
|
||||||
const std::string& project,
|
const std::string& project,
|
||||||
|
@@ -20,12 +20,12 @@ namespace ix
|
|||||||
ix::WebSocketServer server(port, hostname);
|
ix::WebSocketServer server(port, hostname);
|
||||||
server.setTLSOptions(tlsOptions);
|
server.setTLSOptions(tlsOptions);
|
||||||
|
|
||||||
server.setOnConnectionCallback([&server](std::shared_ptr<WebSocket> webSocket,
|
server.setOnClientMessageCallback(
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
[&server](std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
ConnectionInfo& connectionInfo,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
WebSocket& webSocket,
|
||||||
webSocket->setOnMessageCallback([webSocket, connectionState, remoteIp, &server](
|
const WebSocketMessagePtr& msg) {
|
||||||
const WebSocketMessagePtr& msg) {
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
spdlog::info("New connection");
|
spdlog::info("New connection");
|
||||||
@@ -63,7 +63,7 @@ namespace ix
|
|||||||
|
|
||||||
for (auto&& client : server.getClients())
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
if (client != webSocket)
|
if (client.get() != &webSocket)
|
||||||
{
|
{
|
||||||
client->send(msg->str, msg->binary, [](int current, int total) -> bool {
|
client->send(msg->str, msg->binary, [](int current, int total) -> bool {
|
||||||
spdlog::info("Step {} out of {}", current, total);
|
spdlog::info("Step {} out of {}", current, total);
|
||||||
@@ -82,7 +82,6 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <ixcobra/IXCobraMetricsPublisher.h>
|
#include <ixcobra/IXCobraMetricsPublisher.h>
|
||||||
#include <jsoncpp/json/json.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <ixcobra/IXCobraMetricsPublisher.h>
|
#include <ixcobra/IXCobraMetricsPublisher.h>
|
||||||
#include <jsoncpp/json/json.h>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
@@ -200,7 +200,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Pong)
|
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
spdlog::info("Received pong");
|
spdlog::info("Received pong {}", msg->str);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -42,50 +42,48 @@ namespace ix
|
|||||||
server.disablePong();
|
server.disablePong();
|
||||||
}
|
}
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnClientMessageCallback(
|
||||||
[greetings](std::shared_ptr<ix::WebSocket> webSocket,
|
[greetings](std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
ConnectionInfo& connectionInfo,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
WebSocket& webSocket,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
const WebSocketMessagePtr& msg) {
|
||||||
webSocket->setOnMessageCallback(
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
[webSocket, connectionState, remoteIp, greetings](const WebSocketMessagePtr& msg) {
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
{
|
||||||
{
|
spdlog::info("New connection");
|
||||||
spdlog::info("New connection");
|
spdlog::info("remote ip: {}", remoteIp);
|
||||||
spdlog::info("remote ip: {}", remoteIp);
|
spdlog::info("id: {}", connectionState->getId());
|
||||||
spdlog::info("id: {}", connectionState->getId());
|
spdlog::info("Uri: {}", msg->openInfo.uri);
|
||||||
spdlog::info("Uri: {}", msg->openInfo.uri);
|
spdlog::info("Headers:");
|
||||||
spdlog::info("Headers:");
|
for (auto it : msg->openInfo.headers)
|
||||||
for (auto it : msg->openInfo.headers)
|
{
|
||||||
{
|
spdlog::info("{}: {}", it.first, it.second);
|
||||||
spdlog::info("{}: {}", it.first, it.second);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (greetings)
|
if (greetings)
|
||||||
{
|
{
|
||||||
webSocket->sendText("Welcome !");
|
webSocket.sendText("Welcome !");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
spdlog::info("Closed connection: client id {} code {} reason {}",
|
spdlog::info("Closed connection: client id {} code {} reason {}",
|
||||||
connectionState->getId(),
|
connectionState->getId(),
|
||||||
msg->closeInfo.code,
|
msg->closeInfo.code,
|
||||||
msg->closeInfo.reason);
|
msg->closeInfo.reason);
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
spdlog::error("Connection error: {}", msg->errorInfo.reason);
|
spdlog::error("Connection error: {}", msg->errorInfo.reason);
|
||||||
spdlog::error("#retries: {}", msg->errorInfo.retries);
|
spdlog::error("#retries: {}", msg->errorInfo.retries);
|
||||||
spdlog::error("Wait time(ms): {}", msg->errorInfo.wait_time);
|
spdlog::error("Wait time(ms): {}", msg->errorInfo.wait_time);
|
||||||
spdlog::error("HTTP Status: {}", msg->errorInfo.http_status);
|
spdlog::error("HTTP Status: {}", msg->errorInfo.http_status);
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
spdlog::info("Received {} bytes", msg->wireSize);
|
spdlog::info("Received {} bytes", msg->wireSize);
|
||||||
webSocket->send(msg->str, msg->binary);
|
webSocket.send(msg->str, msg->binary);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
|
@@ -1,176 +0,0 @@
|
|||||||
/*
|
|
||||||
* ws_proxy_server.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class ProxyConnectionState : public ix::ConnectionState
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ProxyConnectionState()
|
|
||||||
: _connected(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ix::WebSocket& webSocket()
|
|
||||||
{
|
|
||||||
return _serverWebSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isConnected()
|
|
||||||
{
|
|
||||||
return _connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setConnected()
|
|
||||||
{
|
|
||||||
_connected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ix::WebSocket _serverWebSocket;
|
|
||||||
bool _connected;
|
|
||||||
};
|
|
||||||
|
|
||||||
int ws_proxy_server_main(int port,
|
|
||||||
const std::string& hostname,
|
|
||||||
const ix::SocketTLSOptions& tlsOptions,
|
|
||||||
const std::string& remoteUrl,
|
|
||||||
bool verbose)
|
|
||||||
{
|
|
||||||
spdlog::info("Listening on {}:{}", hostname, port);
|
|
||||||
|
|
||||||
ix::WebSocketServer server(port, hostname);
|
|
||||||
server.setTLSOptions(tlsOptions);
|
|
||||||
|
|
||||||
auto factory = []() -> std::shared_ptr<ix::ConnectionState> {
|
|
||||||
return std::make_shared<ProxyConnectionState>();
|
|
||||||
};
|
|
||||||
server.setConnectionStateFactory(factory);
|
|
||||||
|
|
||||||
server.setOnConnectionCallback([remoteUrl,
|
|
||||||
verbose](std::shared_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 connection
|
|
||||||
state->webSocket().setOnMessageCallback([webSocket, state, remoteIp, verbose](
|
|
||||||
const WebSocketMessagePtr& msg) {
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
spdlog::info("New connection to remote server");
|
|
||||||
spdlog::info("remote ip: {}", remoteIp);
|
|
||||||
spdlog::info("id: {}", state->getId());
|
|
||||||
spdlog::info("Uri: {}", msg->openInfo.uri);
|
|
||||||
spdlog::info("Headers:");
|
|
||||||
for (auto it : msg->openInfo.headers)
|
|
||||||
{
|
|
||||||
spdlog::info("{}: {}", it.first, it.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
|
||||||
{
|
|
||||||
spdlog::info("Closed remote server connection: client id {} code {} reason {}",
|
|
||||||
state->getId(),
|
|
||||||
msg->closeInfo.code,
|
|
||||||
msg->closeInfo.reason);
|
|
||||||
state->setTerminated();
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
|
||||||
{
|
|
||||||
spdlog::error("Connection error: {}", msg->errorInfo.reason);
|
|
||||||
spdlog::error("#retries: {}", msg->errorInfo.retries);
|
|
||||||
spdlog::error("Wait time(ms): {}", msg->errorInfo.wait_time);
|
|
||||||
spdlog::error("HTTP Status: {}", msg->errorInfo.http_status);
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
spdlog::info("Received {} bytes from server", msg->wireSize);
|
|
||||||
if (verbose)
|
|
||||||
{
|
|
||||||
spdlog::info("payload {}", msg->str);
|
|
||||||
}
|
|
||||||
|
|
||||||
webSocket->send(msg->str, msg->binary);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Client connection
|
|
||||||
webSocket->setOnMessageCallback(
|
|
||||||
[state, remoteUrl, verbose](const WebSocketMessagePtr& msg) {
|
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
spdlog::info("New connection from client");
|
|
||||||
spdlog::info("id: {}", state->getId());
|
|
||||||
spdlog::info("Uri: {}", msg->openInfo.uri);
|
|
||||||
spdlog::info("Headers:");
|
|
||||||
for (auto it : msg->openInfo.headers)
|
|
||||||
{
|
|
||||||
spdlog::info("{}: {}", it.first, it.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
spdlog::info("waiting for server connection establishment");
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
}
|
|
||||||
spdlog::info("server connection established");
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
|
||||||
{
|
|
||||||
spdlog::info("Closed client connection: client id {} code {} reason {}",
|
|
||||||
state->getId(),
|
|
||||||
msg->closeInfo.code,
|
|
||||||
msg->closeInfo.reason);
|
|
||||||
state->webSocket().close(msg->closeInfo.code, msg->closeInfo.reason);
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
|
||||||
{
|
|
||||||
spdlog::error("Connection error: {}", msg->errorInfo.reason);
|
|
||||||
spdlog::error("#retries: {}", msg->errorInfo.retries);
|
|
||||||
spdlog::error("Wait time(ms): {}", msg->errorInfo.wait_time);
|
|
||||||
spdlog::error("HTTP Status: {}", msg->errorInfo.http_status);
|
|
||||||
}
|
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
spdlog::info("Received {} bytes from client", msg->wireSize);
|
|
||||||
if (verbose)
|
|
||||||
{
|
|
||||||
spdlog::info("payload {}", msg->str);
|
|
||||||
}
|
|
||||||
|
|
||||||
state->webSocket().send(msg->str, msg->binary);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
auto res = server.listen();
|
|
||||||
if (!res.first)
|
|
||||||
{
|
|
||||||
spdlog::info(res.second);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.start();
|
|
||||||
server.wait();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} // namespace ix
|
|
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <ixsentry/IXSentryClient.h>
|
#include <ixsentry/IXSentryClient.h>
|
||||||
#include <jsoncpp/json/json.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
@@ -45,7 +45,8 @@ namespace ix
|
|||||||
bool verbose,
|
bool verbose,
|
||||||
const std::string& appsConfigPath,
|
const std::string& appsConfigPath,
|
||||||
const SocketTLSOptions& socketTLSOptions,
|
const SocketTLSOptions& socketTLSOptions,
|
||||||
bool disablePong)
|
bool disablePong,
|
||||||
|
const std::string& republishChannel)
|
||||||
{
|
{
|
||||||
snake::AppConfig appConfig;
|
snake::AppConfig appConfig;
|
||||||
appConfig.port = port;
|
appConfig.port = port;
|
||||||
@@ -55,6 +56,7 @@ namespace ix
|
|||||||
appConfig.redisPassword = redisPassword;
|
appConfig.redisPassword = redisPassword;
|
||||||
appConfig.socketTLSOptions = socketTLSOptions;
|
appConfig.socketTLSOptions = socketTLSOptions;
|
||||||
appConfig.disablePong = disablePong;
|
appConfig.disablePong = disablePong;
|
||||||
|
appConfig.republishChannel = republishChannel;
|
||||||
|
|
||||||
// Parse config file
|
// Parse config file
|
||||||
auto str = readAsString(appsConfigPath);
|
auto str = readAsString(appsConfigPath);
|
||||||
|
@@ -19,12 +19,12 @@ namespace ix
|
|||||||
ix::WebSocketServer server(port, hostname);
|
ix::WebSocketServer server(port, hostname);
|
||||||
server.setTLSOptions(tlsOptions);
|
server.setTLSOptions(tlsOptions);
|
||||||
|
|
||||||
server.setOnConnectionCallback([&server](std::shared_ptr<ix::WebSocket> webSocket,
|
server.setOnClientMessageCallback(
|
||||||
std::shared_ptr<ConnectionState> connectionState,
|
[&server](std::shared_ptr<ConnectionState> connectionState,
|
||||||
std::unique_ptr<ConnectionInfo> connectionInfo) {
|
ConnectionInfo& connectionInfo,
|
||||||
auto remoteIp = connectionInfo->remoteIp;
|
WebSocket& webSocket,
|
||||||
webSocket->setOnMessageCallback([webSocket, connectionState, remoteIp, &server](
|
const WebSocketMessagePtr& msg) {
|
||||||
const WebSocketMessagePtr& msg) {
|
auto remoteIp = connectionInfo.remoteIp;
|
||||||
if (msg->type == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
spdlog::info("ws_transfer: New connection");
|
spdlog::info("ws_transfer: New connection");
|
||||||
@@ -43,7 +43,7 @@ namespace ix
|
|||||||
connectionState->getId(),
|
connectionState->getId(),
|
||||||
msg->closeInfo.code,
|
msg->closeInfo.code,
|
||||||
msg->closeInfo.reason);
|
msg->closeInfo.reason);
|
||||||
auto remaining = server.getClients().erase(webSocket);
|
auto remaining = server.getClients().size() - 1;
|
||||||
spdlog::info("ws_transfer: {} remaining clients", remaining);
|
spdlog::info("ws_transfer: {} remaining clients", remaining);
|
||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
@@ -65,7 +65,7 @@ namespace ix
|
|||||||
size_t receivers = 0;
|
size_t receivers = 0;
|
||||||
for (auto&& client : server.getClients())
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
if (client != webSocket)
|
if (client.get() != &webSocket)
|
||||||
{
|
{
|
||||||
auto readyState = client->getReadyState();
|
auto readyState = client->getReadyState();
|
||||||
auto id = connectionState->getId();
|
auto id = connectionState->getId();
|
||||||
@@ -119,7 +119,6 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
auto res = server.listen();
|
auto res = server.listen();
|
||||||
if (!res.first)
|
if (!res.first)
|
||||||
|
Reference in New Issue
Block a user