Compare commits
8 Commits
feature/zl
...
feature/lu
Author | SHA1 | Date | |
---|---|---|---|
22fc8e981d | |||
fb0de53efd | |||
62a7483e41 | |||
36fbdd0daa | |||
feff2e38c0 | |||
a040ff06e8 | |||
755493eaf3 | |||
4c61aede2e |
66
.github/workflows/ccpp.yml
vendored
Normal file
66
.github/workflows/ccpp.yml
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
name: unittest
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make test
|
||||
run: make test
|
||||
|
||||
mac_tsan_sectransport:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make test_tsan
|
||||
run: make test_tsan
|
||||
|
||||
mac_tsan_mbedtls:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: install mbedtls
|
||||
run: brew install mbedtls
|
||||
- name: make test
|
||||
run: make test_tsan_mbedtls
|
||||
|
||||
windows_openssl:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||
- run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 ..
|
||||
- run: cmake --build build
|
||||
|
||||
# Running the unittest does not work, the binary cannot be found
|
||||
#- run: ../build/test/ixwebsocket_unittest.exe
|
||||
# working-directory: test
|
||||
|
||||
#
|
||||
# Windows with OpenSSL is working but disabled as it takes 13 minutes (10 for openssl) to build with vcpkg
|
||||
#
|
||||
# windows_openssl:
|
||||
# runs-on: windows-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# - uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||
# - run: |
|
||||
# vcpkg install zlib:x64-windows
|
||||
# vcpkg install openssl:x64-windows
|
||||
# - run: |
|
||||
# mkdir build
|
||||
# cd build
|
||||
# cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_OPEN_SSL=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 ..
|
||||
# - run: cmake --build build
|
||||
#
|
||||
# # Running the unittest does not work, the binary cannot be found
|
||||
# #- run: ../build/test/ixwebsocket_unittest.exe
|
||||
# # working-directory: test
|
||||
|
19
.github/workflows/unittest_windows.yml
vendored
19
.github/workflows/unittest_windows.yml
vendored
@ -1,19 +0,0 @@
|
||||
name: windows
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
||||
- run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 ..
|
||||
- run: cmake --build build
|
||||
- run: ../build/test/ixwebsocket_unittest.exe
|
||||
working-directory: test
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,4 +5,3 @@ ixsnake/ixsnake/.certs/
|
||||
site/
|
||||
ws/.certs/
|
||||
ws/.srl
|
||||
ixhttpd
|
||||
|
@ -7,7 +7,7 @@ find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
|
||||
set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}")
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(MbedTLS DEFAULT_MSG
|
||||
find_package_handle_standard_args(MBEDTLS DEFAULT_MSG
|
||||
MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
|
||||
|
||||
mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
|
||||
|
@ -3,7 +3,7 @@
|
||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1...3.17.2)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
|
||||
|
||||
project(ixwebsocket C CXX)
|
||||
@ -12,10 +12,6 @@ set (CMAKE_CXX_STANDARD 14)
|
||||
set (CXX_STANDARD_REQUIRED ON)
|
||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if (UNIX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||
endif()
|
||||
@ -30,15 +26,12 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXConnectionState.cpp
|
||||
ixwebsocket/IXDNSLookup.cpp
|
||||
ixwebsocket/IXExponentialBackoff.cpp
|
||||
ixwebsocket/IXGetFreePort.cpp
|
||||
ixwebsocket/IXHttp.cpp
|
||||
ixwebsocket/IXHttpClient.cpp
|
||||
ixwebsocket/IXHttpServer.cpp
|
||||
ixwebsocket/IXNetSystem.cpp
|
||||
ixwebsocket/IXSelectInterrupt.cpp
|
||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||
ixwebsocket/IXSelectInterruptPipe.cpp
|
||||
ixwebsocket/IXSetThreadName.cpp
|
||||
ixwebsocket/IXSocket.cpp
|
||||
ixwebsocket/IXSocketConnect.cpp
|
||||
ixwebsocket/IXSocketFactory.cpp
|
||||
@ -54,7 +47,6 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
||||
ixwebsocket/IXWebSocketProxyServer.cpp
|
||||
ixwebsocket/IXWebSocketServer.cpp
|
||||
ixwebsocket/IXWebSocketTransport.cpp
|
||||
)
|
||||
@ -62,11 +54,9 @@ set( IXWEBSOCKET_SOURCES
|
||||
set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXBench.h
|
||||
ixwebsocket/IXCancellationRequest.h
|
||||
ixwebsocket/IXConnectionInfo.h
|
||||
ixwebsocket/IXConnectionState.h
|
||||
ixwebsocket/IXDNSLookup.h
|
||||
ixwebsocket/IXExponentialBackoff.h
|
||||
ixwebsocket/IXGetFreePort.h
|
||||
ixwebsocket/IXHttp.h
|
||||
ixwebsocket/IXHttpClient.h
|
||||
ixwebsocket/IXHttpServer.h
|
||||
@ -74,7 +64,6 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXProgressCallback.h
|
||||
ixwebsocket/IXSelectInterrupt.h
|
||||
ixwebsocket/IXSelectInterruptFactory.h
|
||||
ixwebsocket/IXSelectInterruptPipe.h
|
||||
ixwebsocket/IXSetThreadName.h
|
||||
ixwebsocket/IXSocket.h
|
||||
ixwebsocket/IXSocketConnect.h
|
||||
@ -99,13 +88,29 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
|
||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
|
||||
ixwebsocket/IXWebSocketProxyServer.h
|
||||
ixwebsocket/IXWebSocketSendInfo.h
|
||||
ixwebsocket/IXWebSocketServer.h
|
||||
ixwebsocket/IXWebSocketTransport.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)
|
||||
|
||||
if (USE_TLS)
|
||||
@ -114,11 +119,6 @@ if (USE_TLS)
|
||||
if (NOT USE_MBED_TLS AND NOT USE_OPEN_SSL) # unless we want something else
|
||||
set(USE_SECURE_TRANSPORT ON)
|
||||
endif()
|
||||
# default to mbedtls on windows if nothing is configured
|
||||
elseif (WIN32)
|
||||
if (NOT USE_OPEN_SSL) # unless we want something else
|
||||
set(USE_MBED_TLS ON)
|
||||
endif()
|
||||
else() # default to OpenSSL on all other platforms
|
||||
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
|
||||
set(USE_OPEN_SSL ON)
|
||||
@ -165,14 +165,12 @@ if (USE_TLS)
|
||||
if (APPLE)
|
||||
set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib)
|
||||
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
|
||||
|
||||
# This is for MacPort OpenSSL 1.0
|
||||
# set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /opt/local/lib/openssl-1.0)
|
||||
# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0)
|
||||
endif()
|
||||
|
||||
# Use OPENSSL_ROOT_DIR CMake variable if you need to use your own openssl
|
||||
find_package(OpenSSL REQUIRED)
|
||||
# This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL
|
||||
if (NOT OPENSSL_FOUND)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||
|
||||
add_definitions(${OPENSSL_DEFINITIONS})
|
||||
@ -190,17 +188,17 @@ if (USE_TLS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(USE_ZLIB "Enable zlib support" TRUE)
|
||||
|
||||
if (USE_ZLIB)
|
||||
# Use ZLIB_ROOT CMake variable if you need to use your own zlib
|
||||
# This ZLIB_FOUND check is to help find a cmake manually configured zlib
|
||||
if (NOT ZLIB_FOUND)
|
||||
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 (ZLIB_FOUND)
|
||||
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
|
||||
else()
|
||||
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
|
||||
add_subdirectory(third_party/zlib)
|
||||
target_link_libraries(ixwebsocket zlibstatic)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
@ -227,29 +225,19 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
target_compile_options(ixwebsocket PRIVATE /MP)
|
||||
endif()
|
||||
|
||||
target_include_directories(ixwebsocket PUBLIC
|
||||
$<BUILD_INTERFACE:${IXWEBSOCKET_INCLUDE_DIRS}/>
|
||||
$<INSTALL_INTERFACE:include/ixwebsocket>
|
||||
)
|
||||
target_include_directories(ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS})
|
||||
|
||||
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
|
||||
|
||||
install(TARGETS ixwebsocket
|
||||
EXPORT ixwebsocket
|
||||
ARCHIVE DESTINATION lib
|
||||
PUBLIC_HEADER DESTINATION include/ixwebsocket/
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
|
||||
)
|
||||
|
||||
install(EXPORT ixwebsocket
|
||||
FILE ixwebsocket-config.cmake
|
||||
NAMESPACE ixwebsocket::
|
||||
DESTINATION lib/cmake/ixwebsocket)
|
||||
|
||||
if (USE_WS OR USE_TEST)
|
||||
add_subdirectory(ixcore)
|
||||
add_subdirectory(ixcrypto)
|
||||
add_subdirectory(ixcobra)
|
||||
add_subdirectory(ixredis)
|
||||
add_subdirectory(ixsnake)
|
||||
add_subdirectory(ixsentry)
|
||||
add_subdirectory(ixbots)
|
||||
@ -263,3 +251,7 @@ if (USE_WS OR USE_TEST)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (USE_LUAROCKS)
|
||||
add_subdirectory(luarocks)
|
||||
endif()
|
||||
|
101
README.md
101
README.md
@ -1,72 +1,37 @@
|
||||
## Hello world
|
||||
|
||||

|
||||
|
||||
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
|
||||
|
||||
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
|
||||
|
||||
```cpp
|
||||
/*
|
||||
* main.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
|
||||
*
|
||||
* On macOS
|
||||
* $ mkdir -p build ; cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install
|
||||
* $ clang++ --std=c++14 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
|
||||
* $ ./a.out
|
||||
*/
|
||||
// Required on Windows
|
||||
ix::initNetSystem();
|
||||
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <iostream>
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
int main()
|
||||
{
|
||||
// Required on Windows
|
||||
ix::initNetSystem();
|
||||
std::string url("ws://localhost:8080/");
|
||||
webSocket.setUrl(url);
|
||||
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
std::string url("wss://echo.websocket.org");
|
||||
webSocket.setUrl(url);
|
||||
|
||||
std::cout << "Connecting to " << url << "..." << std::endl;
|
||||
|
||||
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
||||
// when a message or an event (open, close, error) is received
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
std::cout << "received message: " << msg->str << std::endl;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
std::cout << "Connection established" << std::endl;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Now that our callback is setup, we can start our background thread and receive messages
|
||||
webSocket.start();
|
||||
|
||||
// Send a message to the server (default to TEXT mode)
|
||||
webSocket.send("hello world");
|
||||
|
||||
while (true)
|
||||
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
||||
// when a message or an event (open, close, error) is received
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
std::string text;
|
||||
std::cout << "> " << std::flush;
|
||||
std::getline(std::cin, text);
|
||||
|
||||
webSocket.send(text);
|
||||
if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
std::cout << msg->str << std::endl;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
// Now that our callback is setup, we can start our background thread and receive messages
|
||||
webSocket.start();
|
||||
|
||||
// Send a message to the server (default to TEXT mode)
|
||||
webSocket.send("hello world");
|
||||
```
|
||||
|
||||
Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
|
||||
@ -80,27 +45,3 @@ IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version.
|
||||
If your company or project is using this library, feel free to open an issue or PR to amend this list.
|
||||
|
||||
- [Machine Zone](https://www.mz.com)
|
||||
- [Tokio](https://gitlab.com/HCInk/tokio), a discord library focused on audio playback with node bindings.
|
||||
- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework.
|
||||
- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod
|
||||
- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
| OS | TLS | Sanitizer | Status |
|
||||
|-------------------|-------------------|-------------------|-------------------|
|
||||
| Linux | OpenSSL | None | [![Build2][1]][7] |
|
||||
| macOS | Secure Transport | Thread Sanitizer | [![Build2][2]][7] |
|
||||
| macOS | OpenSSL | Thread Sanitizer | [![Build2][3]][7] |
|
||||
| macOS | MbedTLS | Thread Sanitizer | [![Build2][4]][7] |
|
||||
| Windows | Disabled | None | [![Build2][5]][7] |
|
||||
| UWP | Disabled | None | [![Build2][6]][7] |
|
||||
|
||||
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
|
||||
[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg
|
||||
[3]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg
|
||||
[4]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg
|
||||
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
|
||||
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
|
||||
[7]: https://github.com/machinezone/IXWebSocket
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
FROM alpine:3.12 as build
|
||||
FROM alpine:3.11 as build
|
||||
|
||||
RUN apk add --no-cache \
|
||||
gcc g++ musl-dev linux-headers \
|
||||
cmake mbedtls-dev make zlib-dev python3-dev ninja
|
||||
cmake mbedtls-dev make zlib-dev
|
||||
|
||||
RUN addgroup -S app && \
|
||||
adduser -S -G app app && \
|
||||
@ -18,9 +18,9 @@ USER app
|
||||
RUN make ws_mbedtls_install && \
|
||||
sh tools/trim_repo_for_docker.sh
|
||||
|
||||
FROM alpine:3.12 as runtime
|
||||
FROM alpine:3.11 as runtime
|
||||
|
||||
RUN apk add --no-cache libstdc++ mbedtls ca-certificates python3 && \
|
||||
RUN apk add --no-cache libstdc++ mbedtls ca-certificates && \
|
||||
addgroup -S app && \
|
||||
adduser -S -G app app
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
# Build time
|
||||
FROM ubuntu:groovy as build
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
RUN apt-get update
|
||||
|
||||
RUN apt-get -y install g++ libssl-dev libz-dev make python ninja-build
|
||||
RUN apt-get -y install cmake
|
||||
RUN apt-get -y install gdb
|
||||
|
||||
COPY . /opt
|
||||
WORKDIR /opt
|
||||
|
||||
#
|
||||
# To use the container interactively for debugging/building
|
||||
# 1. Build with
|
||||
# CMD ["ls"]
|
||||
# 2. Run with
|
||||
# docker run --entrypoint sh -it docker-game-eng-dev.addsrv.com/ws:9.10.6
|
||||
#
|
||||
|
||||
RUN ["make", "test"]
|
||||
# CMD ["ls"]
|
@ -1,202 +1,6 @@
|
||||
# Changelog
|
||||
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
|
||||
|
||||
(socket+websocket+http+redis+snake servers) expose the remote ip and remote port when a new connection is made
|
||||
|
||||
## [9.8.6] - 2020-07-06
|
||||
|
||||
(cmake) change the way zlib and openssl are searched
|
||||
|
||||
## [9.8.5] - 2020-07-06
|
||||
|
||||
(cobra python bots) remove the test which stop the bot when events do not follow cobra metrics system schema with an id and a device entry
|
||||
|
||||
## [9.8.4] - 2020-06-26
|
||||
|
||||
(cobra bots) remove bots which is not required now that we can use Python extensions
|
||||
|
||||
## [9.8.3] - 2020-06-25
|
||||
|
||||
(cmake) new python code is optional and enabled at cmake time with -DUSE_PYTHON=1
|
||||
|
||||
## [9.8.2] - 2020-06-24
|
||||
|
||||
(cobra bots) new cobra metrics bot to send data to statsd using Python for processing the message
|
||||
|
||||
## [9.8.1] - 2020-06-19
|
||||
|
||||
(cobra metrics to statsd bot) fps slow frame info : do not include os name
|
||||
|
||||
## [9.8.0] - 2020-06-19
|
||||
|
||||
(cobra metrics to statsd bot) send info about memory warnings
|
||||
|
||||
## [9.7.9] - 2020-06-18
|
||||
|
||||
(http client) fix deadlock when following redirects
|
||||
|
||||
## [9.7.8] - 2020-06-18
|
||||
|
||||
(cobra metrics to statsd bot) send info about net requests
|
||||
|
||||
## [9.7.7] - 2020-06-17
|
||||
|
||||
(cobra client and bots) add batch_size subscription option for retrieving multiple messages at once
|
||||
|
||||
## [9.7.6] - 2020-06-15
|
||||
|
||||
(websocket) WebSocketServer is not a final class, so that users can extend it (fix #215)
|
||||
|
||||
## [9.7.5] - 2020-06-15
|
||||
|
||||
(cobra bots) minor aesthetic change, in how we display http headers with a : then space as key value separator instead of :: with no space
|
||||
|
||||
## [9.7.4] - 2020-06-11
|
||||
|
||||
(cobra metrics to statsd bot) change from a statsd type of gauge to a timing one
|
||||
|
||||
## [9.7.3] - 2020-06-11
|
||||
|
||||
(redis cobra bots) capture most used devices in a zset
|
||||
|
||||
## [9.7.2] - 2020-06-11
|
||||
|
||||
(ws) add bare bone redis-cli like sub-command, with command line editing powered by libnoise
|
||||
|
||||
## [9.7.1] - 2020-06-11
|
||||
|
||||
(redis cobra bots) ws cobra metrics to redis / hostname invalid parsing
|
||||
|
||||
## [9.7.0] - 2020-06-11
|
||||
|
||||
(redis cobra bots) xadd with maxlen + fix bug in xadd client implementation and ws cobra metrics to redis command argument parsing
|
||||
|
||||
## [9.6.9] - 2020-06-10
|
||||
|
||||
(redis cobra bots) update the cobra to redis bot to use the bot framework, and change it to report fps metrics into redis streams.
|
||||
|
||||
## [9.6.6] - 2020-06-04
|
||||
|
||||
(statsd cobra bots) statsd improvement: prefix does not need a dot as a suffix, message size can be larger than 256 bytes, error handling was invalid, use core logger for logging instead of std::cerr
|
||||
|
||||
## [9.6.5] - 2020-05-29
|
||||
|
||||
(http server) support gzip compression
|
||||
|
||||
## [9.6.4] - 2020-05-20
|
||||
|
||||
(compiler fix) support clang 5 and earlier (contributed by @LunarWatcher)
|
||||
|
||||
## [9.6.3] - 2020-05-18
|
||||
|
||||
(cmake) revert CMake changes to fix #203 and be able to use an external OpenSSL
|
||||
|
||||
## [9.6.2] - 2020-05-17
|
||||
|
||||
(cmake) make install cmake files optional to not conflict with vcpkg
|
||||
|
||||
## [9.6.1] - 2020-05-17
|
||||
|
||||
(windows + tls) mbedtls is the default windows tls backend + add ability to load system certificates with mbdetls on windows
|
||||
|
||||
## [9.6.0] - 2020-05-12
|
||||
|
||||
(ixbots) add options to limit how many messages per minute should be processed
|
||||
|
||||
## [9.5.9] - 2020-05-12
|
||||
|
||||
(ixbots) add new class to configure a bot to simplify passing options around
|
||||
|
||||
## [9.5.8] - 2020-05-08
|
||||
|
||||
(openssl tls) (openssl < 1.1) logic inversion - crypto locking callback are not registered properly
|
||||
|
||||
## [9.5.7] - 2020-05-08
|
||||
|
||||
(cmake) default TLS back to mbedtls on Windows Universal Platform
|
||||
|
||||
## [9.5.6] - 2020-05-06
|
||||
|
||||
(cobra bots) add a --heartbeat_timeout option to specify when the bot should terminate because no events are received
|
||||
|
||||
## [9.5.5] - 2020-05-06
|
||||
|
||||
(openssl tls) when OpenSSL is older than 1.1, register the crypto locking callback to be thread safe. Should fix lots of CI failures
|
||||
|
||||
## [9.5.4] - 2020-05-04
|
||||
|
||||
(cobra bots) do not use a queue to store messages pending processing, let the bot handle queuing
|
||||
|
||||
## [9.5.3] - 2020-04-29
|
||||
|
||||
(http client) better current request cancellation support when the HttpClient destructor is invoked (see #189)
|
||||
|
||||
## [9.5.2] - 2020-04-27
|
||||
|
||||
(cmake) fix cmake broken tls option parsing
|
||||
|
@ -22,9 +22,8 @@ Options for building:
|
||||
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support
|
||||
* `-DUSE_WS=1` will build the ws interactive command line tool
|
||||
* `-DUSE_TEST=1` will build the unittest
|
||||
* `-DUSE_PYTHON=1` will use Python3 for cobra bots, require Python3 to be installed.
|
||||
|
||||
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/unittest_windows.yml) which have instructions for building dependencies.
|
||||
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/ccpp.yml#L40) which have instructions for building dependencies.
|
||||
|
||||
It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:
|
||||
|
||||
@ -43,19 +42,6 @@ It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/m
|
||||
```
|
||||
vcpkg install ixwebsocket
|
||||
```
|
||||
To use the installed package within a cmake project, use the following:
|
||||
```cmake
|
||||
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") # this is super important in order for cmake to include the vcpkg search/lib paths!
|
||||
|
||||
# find library and its headers
|
||||
find_path(IXWEBSOCKET_INCLUDE_DIR ixwebsocket/IXWebSocket.h)
|
||||
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
|
||||
# include headers
|
||||
include_directories(${IXWEBSOCKET_INCLUDE_DIR})
|
||||
# ...
|
||||
target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUNS
|
||||
|
||||
```
|
||||
|
||||
### Conan
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||

|
||||
|
||||
## Introduction
|
||||
|
||||
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client and server HTTP communication. *TLS* aka *SSL* is supported. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
|
||||
|
148
docs/usage.md
148
docs/usage.md
@ -246,10 +246,6 @@ uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
|
||||
|
||||
## 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
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
|
||||
@ -260,50 +256,39 @@ This api was actually changed to take a weak_ptr<WebSocket> as the first argumen
|
||||
ix::WebSocketServer server(port);
|
||||
|
||||
server.setOnConnectionCallback(
|
||||
[&server](std::weak_ptr<WebSocket> webSocket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||
[&server](std::shared_ptr<WebSocket> webSocket,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
std::cout << "Remote ip: " << connectionInfo->remoteIp << std::endl;
|
||||
|
||||
auto ws = webSocket.lock();
|
||||
if (ws)
|
||||
{
|
||||
ws->setOnMessageCallback(
|
||||
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
|
||||
webSocket->setOnMessageCallback(
|
||||
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
std::cerr << "New connection" << std::endl;
|
||||
|
||||
// A connection state object is available, and has a default id
|
||||
// You can subclass ConnectionState and pass an alternate factory
|
||||
// to override it. It is useful if you want to store custom
|
||||
// attributes per connection (authenticated bool flag, attributes, etc...)
|
||||
std::cerr << "id: " << connectionState->getId() << std::endl;
|
||||
|
||||
// The uri the client did connect to.
|
||||
std::cerr << "Uri: " << msg->openInfo.uri << std::endl;
|
||||
|
||||
std::cerr << "Headers:" << std::endl;
|
||||
for (auto it : msg->openInfo.headers)
|
||||
{
|
||||
std::cout << "New connection" << std::endl;
|
||||
|
||||
// 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);
|
||||
}
|
||||
std::cerr << 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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -324,74 +309,6 @@ server.wait();
|
||||
|
||||
```
|
||||
|
||||
### New api
|
||||
|
||||
The new API does not require to use 2 nested callbacks, which is a bit annoying. The real fix is that there was a memory leak due to a shared_ptr cycle, due to passing down a shared_ptr<WebSocket> down to the callbacks.
|
||||
|
||||
The webSocket reference is guaranteed to be always valid ; by design the callback will never be invoked with a null webSocket object.
|
||||
|
||||
```cpp
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
|
||||
...
|
||||
|
||||
// Run a server on localhost at a given port.
|
||||
// Bound host name, max connections and listen backlog can also be passed in as parameters.
|
||||
ix::WebSocketServer server(port);
|
||||
|
||||
server.setOnClientMessageCallback(std::shared_ptr<ConnectionState> connectionState,
|
||||
ConnectionInfo& connectionInfo,
|
||||
WebSocket& webSocket,
|
||||
const WebSocketMessagePtr& msg)
|
||||
{
|
||||
// The ConnectionInfo object contains information about the connection,
|
||||
// at this point only the client ip address and the port.
|
||||
std::cout << "Remote ip: " << connectionInfo.remoteIp << std::endl;
|
||||
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
std::cout << "New connection" << std::endl;
|
||||
|
||||
// A connection state object is available, and has a default id
|
||||
// You can subclass ConnectionState and pass an alternate factory
|
||||
// to override it. It is useful if you want to store custom
|
||||
// attributes per connection (authenticated bool flag, attributes, etc...)
|
||||
std::cout << "id: " << connectionState->getId() << std::endl;
|
||||
|
||||
// The uri the client did connect to.
|
||||
std::cout << "Uri: " << msg->openInfo.uri << std::endl;
|
||||
|
||||
std::cout << "Headers:" << std::endl;
|
||||
for (auto it : msg->openInfo.headers)
|
||||
{
|
||||
std::cout << it.first << ": " << it.second << std::endl;
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
// For an echo server, we just send back to the client whatever was received by the server
|
||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
||||
// Second parameter tells whether we are sending the message in binary or text mode.
|
||||
// Here we send it in the same mode as it was received.
|
||||
webSocket.send(msg->str, msg->binary);
|
||||
}
|
||||
);
|
||||
|
||||
auto res = server.listen();
|
||||
if (!res.first)
|
||||
{
|
||||
// Error handling
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Run the server in the background. Server can be stoped by calling server.stop()
|
||||
server.start();
|
||||
|
||||
// Block until server.stop() is called.
|
||||
server.wait();
|
||||
|
||||
```
|
||||
|
||||
## HTTP client API
|
||||
|
||||
```cpp
|
||||
@ -475,8 +392,6 @@ bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
|
||||
// ok will be false if your httpClient is not async
|
||||
```
|
||||
|
||||
See this [issue](https://github.com/machinezone/IXWebSocket/issues/209) for links about uploading files with HTTP multipart.
|
||||
|
||||
## HTTP server API
|
||||
|
||||
```cpp
|
||||
@ -500,14 +415,11 @@ If you want to handle how requests are processed, implement the setOnConnectionC
|
||||
```cpp
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||
{
|
||||
// Build a string for the response
|
||||
std::stringstream ss;
|
||||
ss << connectionInfo->remoteIp
|
||||
<< " "
|
||||
<< request->method
|
||||
ss << request->method
|
||||
<< " "
|
||||
<< request->uri;
|
||||
|
||||
|
46
httpd.cpp
46
httpd.cpp
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* httpd.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Buid with make httpd
|
||||
*/
|
||||
|
||||
#include <ixwebsocket/IXHttpServer.h>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 3)
|
||||
{
|
||||
std::cerr << "Usage: " << argv[0]
|
||||
<< " <port> <host>" << std::endl;
|
||||
std::cerr << " " << argv[0] << " 9090 127.0.0.1" << std::endl;
|
||||
std::cerr << " " << argv[0] << " 9090 0.0.0.0" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int port;
|
||||
std::stringstream ss;
|
||||
ss << argv[1];
|
||||
ss >> port;
|
||||
std::string hostname(argv[2]);
|
||||
|
||||
std::cout << "Listening on " << hostname
|
||||
<< ":" << port << std::endl;
|
||||
|
||||
ix::HttpServer server(port, hostname);
|
||||
|
||||
auto res = server.listen();
|
||||
if (!res.first)
|
||||
{
|
||||
std::cout << res.second << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
server.start();
|
||||
server.wait();
|
||||
|
||||
return 0;
|
||||
}
|
@ -8,19 +8,16 @@ set (IXBOTS_SOURCES
|
||||
ixbots/IXCobraToSentryBot.cpp
|
||||
ixbots/IXCobraToStatsdBot.cpp
|
||||
ixbots/IXCobraToStdoutBot.cpp
|
||||
ixbots/IXCobraMetricsToRedisBot.cpp
|
||||
ixbots/IXCobraToPythonBot.cpp
|
||||
ixbots/IXQueueManager.cpp
|
||||
ixbots/IXStatsdClient.cpp
|
||||
)
|
||||
|
||||
set (IXBOTS_HEADERS
|
||||
ixbots/IXCobraBot.h
|
||||
ixbots/IXCobraBotConfig.h
|
||||
ixbots/IXCobraToSentryBot.h
|
||||
ixbots/IXCobraToStatsdBot.h
|
||||
ixbots/IXCobraToStdoutBot.h
|
||||
ixbots/IXCobraMetricsToRedisBot.h
|
||||
ixbots/IXCobraToPythonBot.h
|
||||
ixbots/IXQueueManager.h
|
||||
ixbots/IXStatsdClient.h
|
||||
)
|
||||
|
||||
@ -34,24 +31,14 @@ if (NOT JSONCPP_FOUND)
|
||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||
endif()
|
||||
|
||||
if (USE_PYTHON)
|
||||
target_compile_definitions(ixbots PUBLIC IXBOTS_USE_PYTHON)
|
||||
find_package(Python COMPONENTS Development)
|
||||
endif()
|
||||
|
||||
set(IXBOTS_INCLUDE_DIRS
|
||||
.
|
||||
..
|
||||
../ixcore
|
||||
../ixwebsocket
|
||||
../ixcobra
|
||||
../ixredis
|
||||
../ixsentry
|
||||
${JSONCPP_INCLUDE_DIRS}
|
||||
${SPDLOG_INCLUDE_DIRS})
|
||||
|
||||
if (USE_PYTHON)
|
||||
set(IXBOTS_INCLUDE_DIRS ${IXBOTS_INCLUDE_DIRS} ${Python_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
target_include_directories( ixbots PUBLIC ${IXBOTS_INCLUDE_DIRS} )
|
||||
|
@ -6,9 +6,9 @@
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
|
||||
#include "IXQueueManager.h"
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@ -18,35 +18,32 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t CobraBot::run(const CobraBotConfig& botConfig)
|
||||
int64_t CobraBot::run(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool useQueue,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
{
|
||||
auto config = botConfig.cobraConfig;
|
||||
auto channel = botConfig.channel;
|
||||
auto filter = botConfig.filter;
|
||||
auto position = botConfig.position;
|
||||
auto enableHeartbeat = botConfig.enableHeartbeat;
|
||||
auto heartBeatTimeout = botConfig.heartBeatTimeout;
|
||||
auto runtime = botConfig.runtime;
|
||||
auto maxEventsPerMinute = botConfig.maxEventsPerMinute;
|
||||
auto limitReceivedEvents = botConfig.limitReceivedEvents;
|
||||
auto batchSize = botConfig.batchSize;
|
||||
|
||||
ix::CobraConnection conn;
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
|
||||
Json::FastWriter jsonWriter;
|
||||
std::atomic<uint64_t> sentCount(0);
|
||||
std::atomic<uint64_t> receivedCount(0);
|
||||
uint64_t sentCountTotal(0);
|
||||
uint64_t receivedCountTotal(0);
|
||||
uint64_t sentCountPerSecs(0);
|
||||
uint64_t receivedCountPerSecs(0);
|
||||
std::atomic<int> receivedCountPerMinutes(0);
|
||||
std::atomic<bool> stop(false);
|
||||
std::atomic<bool> throttled(false);
|
||||
std::atomic<bool> fatalCobraError(false);
|
||||
std::atomic<bool> stalledConnection(false);
|
||||
int minuteCounter = 0;
|
||||
|
||||
QueueManager queueManager(maxQueueSize);
|
||||
|
||||
auto timer = [&sentCount,
|
||||
&receivedCount,
|
||||
@ -54,11 +51,7 @@ namespace ix
|
||||
&receivedCountTotal,
|
||||
&sentCountPerSecs,
|
||||
&receivedCountPerSecs,
|
||||
&receivedCountPerMinutes,
|
||||
&minuteCounter,
|
||||
&conn,
|
||||
&stop] {
|
||||
setThreadName("Bot progress");
|
||||
while (!stop)
|
||||
{
|
||||
//
|
||||
@ -75,26 +68,16 @@ namespace ix
|
||||
<< sentCountPerSecs
|
||||
<< " "
|
||||
<< sentCountTotal;
|
||||
|
||||
if (conn.isAuthenticated())
|
||||
{
|
||||
CoreLogger::info(ss.str());
|
||||
}
|
||||
CoreLogger::info(ss.str());
|
||||
|
||||
receivedCountPerSecs = receivedCount - receivedCountTotal;
|
||||
sentCountPerSecs = sentCount - sentCountTotal;
|
||||
sentCountPerSecs = sentCount - receivedCountTotal;
|
||||
|
||||
receivedCountTotal += receivedCountPerSecs;
|
||||
sentCountTotal += sentCountPerSecs;
|
||||
|
||||
auto duration = std::chrono::seconds(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (minuteCounter++ == 60)
|
||||
{
|
||||
receivedCountPerMinutes = 0;
|
||||
minuteCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CoreLogger::info("timer thread done");
|
||||
@ -102,14 +85,7 @@ namespace ix
|
||||
|
||||
std::thread t1(timer);
|
||||
|
||||
auto heartbeat = [&sentCount,
|
||||
&receivedCount,
|
||||
&stop,
|
||||
&enableHeartbeat,
|
||||
&heartBeatTimeout,
|
||||
&stalledConnection]
|
||||
{
|
||||
setThreadName("Bot heartbeat");
|
||||
auto heartbeat = [&sentCount, &receivedCount, &stop, &enableHeartbeat] {
|
||||
std::string state("na");
|
||||
|
||||
if (!enableHeartbeat) return;
|
||||
@ -124,16 +100,12 @@ namespace ix
|
||||
|
||||
if (currentState == state)
|
||||
{
|
||||
ss.str("");
|
||||
ss << "no messages received or sent for "
|
||||
<< heartBeatTimeout << " seconds, reconnecting";
|
||||
|
||||
CoreLogger::error(ss.str());
|
||||
stalledConnection = true;
|
||||
CoreLogger::error("no messages received or sent for 1 minute, exiting");
|
||||
exit(1);
|
||||
}
|
||||
state = currentState;
|
||||
|
||||
auto duration = std::chrono::seconds(heartBeatTimeout);
|
||||
auto duration = std::chrono::minutes(1);
|
||||
std::this_thread::sleep_for(duration);
|
||||
}
|
||||
|
||||
@ -142,6 +114,40 @@ namespace ix
|
||||
|
||||
std::thread t2(heartbeat);
|
||||
|
||||
auto sender =
|
||||
[this, &queueManager, verbose, &sentCount, &stop, &throttled, &fatalCobraError] {
|
||||
while (true)
|
||||
{
|
||||
auto data = queueManager.pop();
|
||||
Json::Value msg = data.first;
|
||||
std::string position = data.second;
|
||||
|
||||
if (stop) break;
|
||||
if (msg.isNull()) continue;
|
||||
|
||||
if (_onBotMessageCallback &&
|
||||
_onBotMessageCallback(msg, position, verbose, throttled, fatalCobraError))
|
||||
{
|
||||
// That might be too noisy
|
||||
if (verbose)
|
||||
{
|
||||
CoreLogger::info("cobra bot: sending succesfull");
|
||||
}
|
||||
++sentCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
CoreLogger::error("cobra bot: error sending");
|
||||
}
|
||||
|
||||
if (stop) break;
|
||||
}
|
||||
|
||||
CoreLogger::info("sender thread done");
|
||||
};
|
||||
|
||||
std::thread t3(sender);
|
||||
|
||||
std::string subscriptionPosition(position);
|
||||
|
||||
conn.setEventCallback([this,
|
||||
@ -149,13 +155,13 @@ namespace ix
|
||||
&channel,
|
||||
&filter,
|
||||
&subscriptionPosition,
|
||||
&jsonWriter,
|
||||
verbose,
|
||||
&throttled,
|
||||
&receivedCount,
|
||||
&receivedCountPerMinutes,
|
||||
maxEventsPerMinute,
|
||||
limitReceivedEvents,
|
||||
batchSize,
|
||||
&fatalCobraError,
|
||||
&useQueue,
|
||||
&queueManager,
|
||||
&sentCount](const CobraEventPtr& event) {
|
||||
if (event->type == ix::CobraEventType::Open)
|
||||
{
|
||||
@ -163,7 +169,7 @@ namespace ix
|
||||
|
||||
for (auto&& it : event->headers)
|
||||
{
|
||||
CoreLogger::info(it.first + ": " + it.second);
|
||||
CoreLogger::info(it.first + "::" + it.second);
|
||||
}
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Closed)
|
||||
@ -176,34 +182,58 @@ namespace ix
|
||||
CoreLogger::info("Subscribing to " + channel);
|
||||
CoreLogger::info("Subscribing at position " + subscriptionPosition);
|
||||
CoreLogger::info("Subscribing with filter " + filter);
|
||||
conn.subscribe(channel, filter, subscriptionPosition, batchSize,
|
||||
[&sentCount, &receivedCountPerMinutes,
|
||||
maxEventsPerMinute, limitReceivedEvents,
|
||||
&throttled, &receivedCount,
|
||||
&subscriptionPosition, &fatalCobraError,
|
||||
this](const Json::Value& msg, const std::string& position) {
|
||||
subscriptionPosition = position;
|
||||
++receivedCount;
|
||||
conn.subscribe(channel,
|
||||
filter,
|
||||
subscriptionPosition,
|
||||
[this,
|
||||
&jsonWriter,
|
||||
verbose,
|
||||
&throttled,
|
||||
&receivedCount,
|
||||
&queueManager,
|
||||
&useQueue,
|
||||
&subscriptionPosition,
|
||||
&fatalCobraError,
|
||||
&sentCount](const Json::Value& msg, const std::string& position) {
|
||||
if (verbose)
|
||||
{
|
||||
CoreLogger::info("Subscriber received message "
|
||||
+ position + " -> " + jsonWriter.write(msg));
|
||||
}
|
||||
|
||||
++receivedCountPerMinutes;
|
||||
if (limitReceivedEvents)
|
||||
{
|
||||
if (receivedCountPerMinutes > maxEventsPerMinute)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
subscriptionPosition = position;
|
||||
|
||||
// If we cannot send to sentry fast enough, drop the message
|
||||
if (throttled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// If we cannot send to sentry fast enough, drop the message
|
||||
if (throttled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_onBotMessageCallback(
|
||||
msg, position, throttled,
|
||||
fatalCobraError, sentCount);
|
||||
});
|
||||
++receivedCount;
|
||||
|
||||
if (useQueue)
|
||||
{
|
||||
queueManager.add(msg, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_onBotMessageCallback &&
|
||||
_onBotMessageCallback(
|
||||
msg, position, verbose, throttled, fatalCobraError))
|
||||
{
|
||||
// That might be too noisy
|
||||
if (verbose)
|
||||
{
|
||||
CoreLogger::info("cobra bot: sending succesfull");
|
||||
}
|
||||
++sentCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
CoreLogger::error("cobra bot: error sending");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::Subscribed)
|
||||
{
|
||||
@ -251,13 +281,6 @@ namespace ix
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (fatalCobraError) break;
|
||||
|
||||
if (stalledConnection)
|
||||
{
|
||||
conn.disconnect();
|
||||
conn.connect();
|
||||
stalledConnection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Run for a duration, used by unittesting now
|
||||
@ -269,13 +292,6 @@ namespace ix
|
||||
std::this_thread::sleep_for(duration);
|
||||
|
||||
if (fatalCobraError) break;
|
||||
|
||||
if (stalledConnection)
|
||||
{
|
||||
conn.disconnect();
|
||||
conn.connect();
|
||||
stalledConnection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,6 +308,9 @@ namespace ix
|
||||
// heartbeat thread
|
||||
if (t2.joinable()) t2.join();
|
||||
|
||||
// sentry sender thread
|
||||
t3.join();
|
||||
|
||||
return fatalCobraError ? -1 : (int64_t) sentCount;
|
||||
}
|
||||
|
||||
@ -299,22 +318,4 @@ namespace ix
|
||||
{
|
||||
_onBotMessageCallback = callback;
|
||||
}
|
||||
|
||||
std::string CobraBot::getDeviceIdentifier(const Json::Value& msg)
|
||||
{
|
||||
std::string deviceId("na");
|
||||
|
||||
auto osName = msg["device"]["os_name"];
|
||||
if (osName == "Android")
|
||||
{
|
||||
deviceId = msg["device"]["model"].asString();
|
||||
}
|
||||
else if (osName == "iOS")
|
||||
{
|
||||
deviceId = msg["device"]["hardware_model"].asString();
|
||||
}
|
||||
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
@ -8,27 +8,34 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <json/json.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
using OnBotMessageCallback = std::function<void(const Json::Value&,
|
||||
using OnBotMessageCallback = std::function<bool(const Json::Value&,
|
||||
const std::string&,
|
||||
const bool verbose,
|
||||
std::atomic<bool>&,
|
||||
std::atomic<bool>&,
|
||||
std::atomic<uint64_t>&)>;
|
||||
std::atomic<bool>&)>;
|
||||
|
||||
class CobraBot
|
||||
{
|
||||
public:
|
||||
CobraBot() = default;
|
||||
|
||||
int64_t run(const CobraBotConfig& botConfig);
|
||||
void setOnBotMessageCallback(const OnBotMessageCallback& callback);
|
||||
int64_t run(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool useQueue,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
|
||||
std::string getDeviceIdentifier(const Json::Value& msg);
|
||||
void setOnBotMessageCallback(const OnBotMessageCallback& callback);
|
||||
|
||||
private:
|
||||
OnBotMessageCallback _onBotMessageCallback;
|
||||
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* IXCobraBotConfig.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <limits>
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct CobraBotConfig
|
||||
{
|
||||
CobraConfig cobraConfig;
|
||||
std::string channel;
|
||||
std::string filter;
|
||||
std::string position = std::string("$");
|
||||
bool enableHeartbeat = true;
|
||||
int heartBeatTimeout = 60;
|
||||
int runtime = -1;
|
||||
int maxEventsPerMinute = std::numeric_limits<int>::max();
|
||||
bool limitReceivedEvents = false;
|
||||
int batchSize = 1;
|
||||
};
|
||||
} // namespace ix
|
@ -1,149 +0,0 @@
|
||||
/*
|
||||
* IXCobraMetricsToRedisBot.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXCobraMetricsToRedisBot.h"
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
#include "IXStatsdClient.h"
|
||||
#include <chrono>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <cctype>
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string removeSpaces(const std::string& str)
|
||||
{
|
||||
std::string out(str);
|
||||
out.erase(
|
||||
std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }),
|
||||
out.end());
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ix
|
||||
{
|
||||
bool processPerfMetricsEventSlowFrames(const Json::Value& msg,
|
||||
RedisClient& redisClient,
|
||||
const std::string& deviceId)
|
||||
{
|
||||
auto frameRateHistogramCounts = msg["data"]["FrameRateHistogramCounts"];
|
||||
|
||||
int slowFrames = 0;
|
||||
slowFrames += frameRateHistogramCounts[4].asInt();
|
||||
slowFrames += frameRateHistogramCounts[5].asInt();
|
||||
slowFrames += frameRateHistogramCounts[6].asInt();
|
||||
slowFrames += frameRateHistogramCounts[7].asInt();
|
||||
|
||||
//
|
||||
// XADD without a device id
|
||||
//
|
||||
std::stringstream ss;
|
||||
ss << msg["id"].asString() << "_slow_frames" << "."
|
||||
<< msg["device"]["game"].asString() << "."
|
||||
<< msg["device"]["os_name"].asString() << "."
|
||||
<< removeSpaces(msg["data"]["Tag"].asString());
|
||||
|
||||
int maxLen;
|
||||
maxLen = 100000;
|
||||
std::string id = ss.str();
|
||||
std::string errMsg;
|
||||
if (redisClient.xadd(id, std::to_string(slowFrames), maxLen, errMsg).empty())
|
||||
{
|
||||
CoreLogger::info(std::string("redis XADD error: ") + errMsg);
|
||||
}
|
||||
|
||||
//
|
||||
// XADD with a device id
|
||||
//
|
||||
ss.str(""); // reset the stringstream
|
||||
ss << msg["id"].asString() << "_slow_frames_by_device" << "."
|
||||
<< deviceId << "."
|
||||
<< msg["device"]["game"].asString() << "."
|
||||
<< msg["device"]["os_name"].asString() << "."
|
||||
<< removeSpaces(msg["data"]["Tag"].asString());
|
||||
|
||||
id = ss.str();
|
||||
maxLen = 1000;
|
||||
if (redisClient.xadd(id, std::to_string(slowFrames), maxLen, errMsg).empty())
|
||||
{
|
||||
CoreLogger::info(std::string("redis XADD error: ") + errMsg);
|
||||
}
|
||||
|
||||
//
|
||||
// Add device to the device zset, and increment the score
|
||||
// so that we know which devices are used more than others
|
||||
// ZINCRBY myzset 1 one
|
||||
//
|
||||
ss.str(""); // reset the stringstream
|
||||
ss << msg["id"].asString() << "_slow_frames_devices" << "."
|
||||
<< msg["device"]["game"].asString();
|
||||
|
||||
id = ss.str();
|
||||
std::vector<std::string> args = {
|
||||
"ZINCRBY", id, "1", deviceId
|
||||
};
|
||||
auto response = redisClient.send(args, errMsg);
|
||||
if (response.first == RespType::Error)
|
||||
{
|
||||
CoreLogger::info(std::string("redis ZINCRBY error: ") + errMsg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t cobra_metrics_to_redis_bot(const ix::CobraBotConfig& config,
|
||||
RedisClient& redisClient,
|
||||
bool verbose)
|
||||
{
|
||||
CobraBot bot;
|
||||
|
||||
bot.setOnBotMessageCallback(
|
||||
[&redisClient, &verbose, &bot]
|
||||
(const Json::Value& msg,
|
||||
const std::string& /*position*/,
|
||||
std::atomic<bool>& /*throttled*/,
|
||||
std::atomic<bool>& /*fatalCobraError*/,
|
||||
std::atomic<uint64_t>& sentCount) -> void {
|
||||
if (msg["device"].isNull())
|
||||
{
|
||||
CoreLogger::info("no device entry, skipping event");
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg["id"].isNull())
|
||||
{
|
||||
CoreLogger::info("no id entry, skipping event");
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Display full message with
|
||||
if (verbose)
|
||||
{
|
||||
CoreLogger::info(msg.toStyledString());
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
if (msg["id"].asString() == "engine_performance_metrics_id")
|
||||
{
|
||||
auto deviceId = bot.getDeviceIdentifier(msg);
|
||||
success = processPerfMetricsEventSlowFrames(msg, redisClient, deviceId);
|
||||
}
|
||||
|
||||
if (success) sentCount++;
|
||||
});
|
||||
|
||||
return bot.run(config);
|
||||
}
|
||||
} // namespace ix
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* IXCobraMetricsToRedisBot.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ixredis/IXRedisClient.h>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t cobra_metrics_to_redis_bot(const ix::CobraBotConfig& config,
|
||||
RedisClient& redisClient,
|
||||
bool verbose);
|
||||
} // namespace ix
|
||||
|
@ -1,332 +0,0 @@
|
||||
/*
|
||||
* IXCobraToPythonBot.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXCobraToPythonBot.h"
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
#include "IXStatsdClient.h"
|
||||
#include <chrono>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <cctype>
|
||||
|
||||
//
|
||||
// I cannot get Windows to easily build on CI (github action) so support
|
||||
// is disabled for now. It should be a simple fix
|
||||
// (linking error about missing debug build)
|
||||
//
|
||||
|
||||
#ifdef IXBOTS_USE_PYTHON
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#endif
|
||||
|
||||
#ifdef IXBOTS_USE_PYTHON
|
||||
namespace
|
||||
{
|
||||
//
|
||||
// This function is unused at this point. It produce a correct output,
|
||||
// but triggers memory leaks when called repeateadly, as I cannot figure out how to
|
||||
// make the reference counting Python functions to work properly (Py_DECREF and friends)
|
||||
//
|
||||
PyObject* jsonToPythonObject(const Json::Value& val)
|
||||
{
|
||||
switch(val.type())
|
||||
{
|
||||
case Json::nullValue:
|
||||
{
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
case Json::intValue:
|
||||
{
|
||||
return PyLong_FromLong(val.asInt64());
|
||||
}
|
||||
|
||||
case Json::uintValue:
|
||||
{
|
||||
return PyLong_FromLong(val.asUInt64());
|
||||
}
|
||||
|
||||
case Json::realValue:
|
||||
{
|
||||
return PyFloat_FromDouble(val.asDouble());
|
||||
}
|
||||
|
||||
case Json::stringValue:
|
||||
{
|
||||
return PyUnicode_FromString(val.asCString());
|
||||
}
|
||||
|
||||
case Json::booleanValue:
|
||||
{
|
||||
return val.asBool() ? Py_True : Py_False;
|
||||
}
|
||||
|
||||
case Json::arrayValue:
|
||||
{
|
||||
PyObject* list = PyList_New(val.size());
|
||||
Py_ssize_t i = 0;
|
||||
for (auto&& it = val.begin(); it != val.end(); ++it)
|
||||
{
|
||||
PyList_SetItem(list, i++, jsonToPythonObject(*it));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
case Json::objectValue:
|
||||
{
|
||||
PyObject* dict = PyDict_New();
|
||||
for (auto&& it = val.begin(); it != val.end(); ++it)
|
||||
{
|
||||
PyObject* key = jsonToPythonObject(it.key());
|
||||
PyObject* value = jsonToPythonObject(*it);
|
||||
|
||||
PyDict_SetItem(dict, key, value);
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t cobra_to_python_bot(const ix::CobraBotConfig& config,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& scriptPath)
|
||||
{
|
||||
#ifndef IXBOTS_USE_PYTHON
|
||||
CoreLogger::error("Command is disabled. "
|
||||
"Needs to be configured with USE_PYTHON=1");
|
||||
return -1;
|
||||
#else
|
||||
CobraBot bot;
|
||||
Py_InitializeEx(0); // 0 arg so that we do not install signal handlers
|
||||
// which prevent us from using Ctrl-C
|
||||
|
||||
size_t lastIndex = scriptPath.find_last_of(".");
|
||||
std::string modulePath = scriptPath.substr(0, lastIndex);
|
||||
|
||||
PyObject* pyModuleName = PyUnicode_DecodeFSDefault(modulePath.c_str());
|
||||
|
||||
if (pyModuleName == nullptr)
|
||||
{
|
||||
CoreLogger::error("Python error: Cannot decode file system path");
|
||||
PyErr_Print();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Import module
|
||||
PyObject* pyModule = PyImport_Import(pyModuleName);
|
||||
Py_DECREF(pyModuleName);
|
||||
if (pyModule == nullptr)
|
||||
{
|
||||
CoreLogger::error("Python error: Cannot import module.");
|
||||
CoreLogger::error("Module name cannot countain dash characters.");
|
||||
CoreLogger::error("Is PYTHONPATH set correctly ?");
|
||||
PyErr_Print();
|
||||
return false;
|
||||
}
|
||||
|
||||
// module main funtion name is named 'run'
|
||||
const std::string entryPoint("run");
|
||||
PyObject* pyFunc = PyObject_GetAttrString(pyModule, entryPoint.c_str());
|
||||
|
||||
if (!pyFunc)
|
||||
{
|
||||
CoreLogger::error("run symbol is missing from module.");
|
||||
PyErr_Print();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PyCallable_Check(pyFunc))
|
||||
{
|
||||
CoreLogger::error("run symbol is not a function.");
|
||||
PyErr_Print();
|
||||
return false;
|
||||
}
|
||||
|
||||
bot.setOnBotMessageCallback(
|
||||
[&statsdClient, pyFunc]
|
||||
(const Json::Value& msg,
|
||||
const std::string& /*position*/,
|
||||
std::atomic<bool>& /*throttled*/,
|
||||
std::atomic<bool>& fatalCobraError,
|
||||
std::atomic<uint64_t>& sentCount) -> void {
|
||||
//
|
||||
// Invoke python script here. First build function parameters, a tuple
|
||||
//
|
||||
const int kVersion = 1; // We can bump this and let the interface evolve
|
||||
|
||||
PyObject *pyArgs = PyTuple_New(2);
|
||||
PyTuple_SetItem(pyArgs, 0, PyLong_FromLong(kVersion)); // First argument
|
||||
|
||||
//
|
||||
// It would be better to create a Python object (a dictionary)
|
||||
// from the json msg, but it is simpler to serialize it to a string
|
||||
// and decode it on the Python side of the fence
|
||||
//
|
||||
PyObject* pySerializedJson = PyUnicode_FromString(msg.toStyledString().c_str());
|
||||
PyTuple_SetItem(pyArgs, 1, pySerializedJson); // Second argument
|
||||
|
||||
// Invoke the python routine
|
||||
PyObject* pyList = PyObject_CallObject(pyFunc, pyArgs);
|
||||
|
||||
// Error calling the function
|
||||
if (pyList == nullptr)
|
||||
{
|
||||
fatalCobraError = true;
|
||||
CoreLogger::error("run() function call failed. Input msg: ");
|
||||
auto serializedMsg = msg.toStyledString();
|
||||
CoreLogger::error(serializedMsg);
|
||||
PyErr_Print();
|
||||
CoreLogger::error("================");
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalid return type
|
||||
if (!PyList_Check(pyList))
|
||||
{
|
||||
fatalCobraError = true;
|
||||
CoreLogger::error("run() return type should be a list");
|
||||
return;
|
||||
}
|
||||
|
||||
// The result is a list of dict containing sufficient info
|
||||
// to send messages to statsd
|
||||
auto listSize = PyList_Size(pyList);
|
||||
|
||||
for (Py_ssize_t i = 0 ; i < listSize; ++i)
|
||||
{
|
||||
PyObject* dict = PyList_GetItem(pyList, i);
|
||||
|
||||
// Make sure this is a dict
|
||||
if (!PyDict_Check(dict))
|
||||
{
|
||||
fatalCobraError = true;
|
||||
CoreLogger::error("list element is not a dict");
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieve object kind
|
||||
//
|
||||
PyObject* pyKind = PyDict_GetItemString(dict, "kind");
|
||||
if (!PyUnicode_Check(pyKind))
|
||||
{
|
||||
fatalCobraError = true;
|
||||
CoreLogger::error("kind entry is not a string");
|
||||
continue;
|
||||
}
|
||||
std::string kind(PyUnicode_AsUTF8(pyKind));
|
||||
|
||||
bool counter = false;
|
||||
bool gauge = false;
|
||||
bool timing = false;
|
||||
|
||||
if (kind == "counter")
|
||||
{
|
||||
counter = true;
|
||||
}
|
||||
else if (kind == "gauge")
|
||||
{
|
||||
gauge = true;
|
||||
}
|
||||
else if (kind == "timing")
|
||||
{
|
||||
timing = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
fatalCobraError = true;
|
||||
CoreLogger::error(std::string("invalid kind entry: ") + kind +
|
||||
". Supported ones are counter, gauge, timing");
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieve object key
|
||||
//
|
||||
PyObject* pyKey = PyDict_GetItemString(dict, "key");
|
||||
if (!PyUnicode_Check(pyKey))
|
||||
{
|
||||
fatalCobraError = true;
|
||||
CoreLogger::error("key entry is not a string");
|
||||
continue;
|
||||
}
|
||||
std::string key(PyUnicode_AsUTF8(pyKey));
|
||||
|
||||
//
|
||||
// Retrieve object value and send data to statsd
|
||||
//
|
||||
PyObject* pyValue = PyDict_GetItemString(dict, "value");
|
||||
|
||||
// Send data to statsd
|
||||
if (PyFloat_Check(pyValue))
|
||||
{
|
||||
double value = PyFloat_AsDouble(pyValue);
|
||||
|
||||
if (counter)
|
||||
{
|
||||
statsdClient.count(key, value);
|
||||
}
|
||||
else if (gauge)
|
||||
{
|
||||
statsdClient.gauge(key, value);
|
||||
}
|
||||
else if (timing)
|
||||
{
|
||||
statsdClient.timing(key, value);
|
||||
}
|
||||
}
|
||||
else if (PyLong_Check(pyValue))
|
||||
{
|
||||
long value = PyLong_AsLong(pyValue);
|
||||
|
||||
if (counter)
|
||||
{
|
||||
statsdClient.count(key, value);
|
||||
}
|
||||
else if (gauge)
|
||||
{
|
||||
statsdClient.gauge(key, value);
|
||||
}
|
||||
else if (timing)
|
||||
{
|
||||
statsdClient.timing(key, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fatalCobraError = true;
|
||||
CoreLogger::error("value entry is neither an int or a float");
|
||||
continue;
|
||||
}
|
||||
|
||||
sentCount++; // should we update this for each statsd object sent ?
|
||||
}
|
||||
|
||||
Py_DECREF(pyArgs);
|
||||
Py_DECREF(pyList);
|
||||
});
|
||||
|
||||
bool status = bot.run(config);
|
||||
|
||||
// Cleanup - we should do something similar in all exit case ...
|
||||
Py_DECREF(pyFunc);
|
||||
Py_DECREF(pyModule);
|
||||
Py_FinalizeEx();
|
||||
|
||||
return status;
|
||||
#endif
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* IXCobraMetricsToStatsdBot.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ixbots/IXStatsdClient.h>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t cobra_to_python_bot(const ix::CobraBotConfig& config,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& scriptPath);
|
||||
} // namespace ix
|
@ -7,6 +7,7 @@
|
||||
#include "IXCobraToSentryBot.h"
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
#include "IXQueueManager.h"
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
@ -16,61 +17,101 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t cobra_to_sentry_bot(const CobraBotConfig& config,
|
||||
int64_t cobra_to_sentry_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose)
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
{
|
||||
CobraBot bot;
|
||||
bot.setOnBotMessageCallback([&sentryClient, &verbose](const Json::Value& msg,
|
||||
bot.setOnBotMessageCallback([&sentryClient](const Json::Value& msg,
|
||||
const std::string& /*position*/,
|
||||
const bool verbose,
|
||||
std::atomic<bool>& throttled,
|
||||
std::atomic<bool>& /*fatalCobraError*/,
|
||||
std::atomic<uint64_t>& sentCount) -> void {
|
||||
sentryClient.send(msg, verbose,
|
||||
[&sentCount, &throttled](const HttpResponsePtr& response) {
|
||||
if (!response)
|
||||
std::atomic<bool> &
|
||||
/*fatalCobraError*/) -> bool {
|
||||
auto ret = sentryClient.send(msg, verbose);
|
||||
HttpResponsePtr response = ret.first;
|
||||
|
||||
if (!response)
|
||||
{
|
||||
CoreLogger::warn("Null HTTP Response");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
for (auto it : response->headers)
|
||||
{
|
||||
CoreLogger::warn("Null HTTP Response");
|
||||
return;
|
||||
CoreLogger::info(it.first + ": " + it.second);
|
||||
}
|
||||
|
||||
if (response->statusCode == 200)
|
||||
{
|
||||
sentCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode));
|
||||
CoreLogger::error("Response: " + response->payload);
|
||||
CoreLogger::info("Upload size: " + std::to_string(response->uploadSize));
|
||||
CoreLogger::info("Download size: " + std::to_string(response->downloadSize));
|
||||
|
||||
// Error 429 Too Many Requests
|
||||
if (response->statusCode == 429)
|
||||
CoreLogger::info("Status: " + std::to_string(response->statusCode));
|
||||
if (response->errorCode != HttpErrorCode::Ok)
|
||||
{
|
||||
CoreLogger::info("error message: " + response->errorMsg);
|
||||
}
|
||||
|
||||
if (response->headers["Content-Type"] != "application/octet-stream")
|
||||
{
|
||||
CoreLogger::info("payload: " + response->payload);
|
||||
}
|
||||
}
|
||||
|
||||
bool success = response->statusCode == 200;
|
||||
|
||||
if (!success)
|
||||
{
|
||||
CoreLogger::error("Error sending data to sentry: " + std::to_string(response->statusCode));
|
||||
CoreLogger::error("Body: " + ret.second);
|
||||
CoreLogger::error("Response: " + response->payload);
|
||||
|
||||
// Error 429 Too Many Requests
|
||||
if (response->statusCode == 429)
|
||||
{
|
||||
auto retryAfter = response->headers["Retry-After"];
|
||||
std::stringstream ss;
|
||||
ss << retryAfter;
|
||||
int seconds;
|
||||
ss >> seconds;
|
||||
|
||||
if (!ss.eof() || ss.fail())
|
||||
{
|
||||
auto retryAfter = response->headers["Retry-After"];
|
||||
std::stringstream ss;
|
||||
ss << retryAfter;
|
||||
int seconds;
|
||||
ss >> seconds;
|
||||
|
||||
if (!ss.eof() || ss.fail())
|
||||
{
|
||||
seconds = 30;
|
||||
CoreLogger::warn("Error parsing Retry-After header. "
|
||||
"Using " + retryAfter + " for the sleep duration");
|
||||
}
|
||||
|
||||
CoreLogger::warn("Error 429 - Too Many Requests. ws will sleep "
|
||||
"and retry after " + retryAfter + " seconds");
|
||||
|
||||
throttled = true;
|
||||
auto duration = std::chrono::seconds(seconds);
|
||||
std::this_thread::sleep_for(duration);
|
||||
throttled = false;
|
||||
seconds = 30;
|
||||
CoreLogger::warn("Error parsing Retry-After header. "
|
||||
"Using " + retryAfter + " for the sleep duration");
|
||||
}
|
||||
|
||||
CoreLogger::warn("Error 429 - Too Many Requests. ws will sleep "
|
||||
"and retry after " + retryAfter + " seconds");
|
||||
|
||||
throttled = true;
|
||||
auto duration = std::chrono::seconds(seconds);
|
||||
std::this_thread::sleep_for(duration);
|
||||
throttled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return success;
|
||||
});
|
||||
|
||||
return bot.run(config);
|
||||
bool useQueue = true;
|
||||
|
||||
return bot.run(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
useQueue,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -6,13 +6,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <ixsentry/IXSentryClient.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t cobra_to_sentry_bot(const CobraBotConfig& config,
|
||||
int64_t cobra_to_sentry_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
SentryClient& sentryClient,
|
||||
bool verbose);
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
} // namespace ix
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "IXCobraToStatsdBot.h"
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
#include "IXQueueManager.h"
|
||||
#include "IXStatsdClient.h"
|
||||
#include <chrono>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
@ -53,34 +54,38 @@ namespace ix
|
||||
return val;
|
||||
}
|
||||
|
||||
int64_t cobra_to_statsd_bot(const ix::CobraBotConfig& config,
|
||||
int64_t cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose)
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
{
|
||||
ix::CobraConnection conn;
|
||||
conn.configure(config);
|
||||
conn.connect();
|
||||
|
||||
auto tokens = parseFields(fields);
|
||||
|
||||
CobraBot bot;
|
||||
bot.setOnBotMessageCallback(
|
||||
[&statsdClient, &tokens, &gauge, &timer, &verbose](const Json::Value& msg,
|
||||
[&statsdClient, &tokens, &gauge, &timer](const Json::Value& msg,
|
||||
const std::string& /*position*/,
|
||||
const bool verbose,
|
||||
std::atomic<bool>& /*throttled*/,
|
||||
std::atomic<bool>& fatalCobraError,
|
||||
std::atomic<uint64_t>& sentCount) -> void {
|
||||
std::atomic<bool>& fatalCobraError) -> bool {
|
||||
std::string id;
|
||||
size_t idx = 0;
|
||||
for (auto&& attr : tokens)
|
||||
{
|
||||
id += ".";
|
||||
auto val = extractAttr(attr, msg);
|
||||
id += val.asString();
|
||||
|
||||
// We add a dot separator unless we are processing the last token
|
||||
if (idx++ != tokens.size() - 1)
|
||||
{
|
||||
id += ".";
|
||||
}
|
||||
}
|
||||
|
||||
if (gauge.empty() && timer.empty())
|
||||
@ -117,7 +122,7 @@ namespace ix
|
||||
{
|
||||
CoreLogger::error("Gauge " + gauge + " is not a numeric type");
|
||||
fatalCobraError = true;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
@ -135,9 +140,19 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
sentCount++;
|
||||
return true;
|
||||
});
|
||||
|
||||
return bot.run(config);
|
||||
bool useQueue = true;
|
||||
|
||||
return bot.run(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
useQueue,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -7,16 +7,22 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <ixbots/IXStatsdClient.h>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t cobra_to_statsd_bot(const ix::CobraBotConfig& config,
|
||||
int64_t cobra_to_statsd_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
StatsdClient& statsdClient,
|
||||
const std::string& fields,
|
||||
const std::string& gauge,
|
||||
const std::string& timer,
|
||||
bool verbose);
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
} // namespace ix
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "IXCobraToStdoutBot.h"
|
||||
|
||||
#include "IXCobraBot.h"
|
||||
#include "IXQueueManager.h"
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@ -63,9 +64,16 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
int64_t cobra_to_stdout_bot(const ix::CobraBotConfig& config,
|
||||
int64_t cobra_to_stdout_bot(const CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
bool fluentd,
|
||||
bool quiet)
|
||||
bool quiet,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime)
|
||||
{
|
||||
CobraBot bot;
|
||||
auto jsonWriter = makeStreamWriter();
|
||||
@ -73,16 +81,27 @@ namespace ix
|
||||
bot.setOnBotMessageCallback(
|
||||
[&fluentd, &quiet, &jsonWriter](const Json::Value& msg,
|
||||
const std::string& position,
|
||||
const bool /*verbose*/,
|
||||
std::atomic<bool>& /*throttled*/,
|
||||
std::atomic<bool>& /*fatalCobraError*/,
|
||||
std::atomic<uint64_t>& sentCount) -> void {
|
||||
std::atomic<bool> &
|
||||
/*fatalCobraError*/) -> bool {
|
||||
if (!quiet)
|
||||
{
|
||||
writeToStdout(fluentd, jsonWriter, msg, position);
|
||||
}
|
||||
sentCount++;
|
||||
return true;
|
||||
});
|
||||
|
||||
return bot.run(config);
|
||||
bool useQueue = false;
|
||||
|
||||
return bot.run(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
useQueue,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -6,13 +6,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "IXCobraBotConfig.h"
|
||||
#include <ixcobra/IXCobraConfig.h>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
int64_t cobra_to_stdout_bot(const ix::CobraBotConfig& config,
|
||||
int64_t cobra_to_stdout_bot(const ix::CobraConfig& config,
|
||||
const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
bool fluentd,
|
||||
bool quiet);
|
||||
bool quiet,
|
||||
bool verbose,
|
||||
size_t maxQueueSize,
|
||||
bool enableHeartbeat,
|
||||
int runtime);
|
||||
} // namespace ix
|
||||
|
67
ixbots/ixbots/IXQueueManager.cpp
Normal file
67
ixbots/ixbots/IXQueueManager.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* IXQueueManager.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXQueueManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
std::pair<Json::Value, std::string> QueueManager::pop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
if (_queues.empty())
|
||||
{
|
||||
Json::Value val;
|
||||
return std::make_pair(val, std::string());
|
||||
}
|
||||
|
||||
std::vector<std::string> games;
|
||||
for (auto it : _queues)
|
||||
{
|
||||
games.push_back(it.first);
|
||||
}
|
||||
|
||||
std::random_shuffle(games.begin(), games.end());
|
||||
std::string game = games[0];
|
||||
|
||||
auto duration = std::chrono::seconds(1);
|
||||
_condition.wait_for(lock, duration);
|
||||
|
||||
if (_queues[game].empty())
|
||||
{
|
||||
Json::Value val;
|
||||
return std::make_pair(val, std::string());
|
||||
}
|
||||
|
||||
auto msg = _queues[game].front();
|
||||
_queues[game].pop();
|
||||
return msg;
|
||||
}
|
||||
|
||||
void QueueManager::add(const Json::Value& msg, const std::string& position)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
|
||||
std::string game;
|
||||
if (msg.isMember("device") && msg["device"].isMember("game"))
|
||||
{
|
||||
game = msg["device"]["game"].asString();
|
||||
}
|
||||
|
||||
if (game.empty()) return;
|
||||
|
||||
// if the sending is not fast enough there is no point
|
||||
// in queuing too many events.
|
||||
if (_queues[game].size() < _maxQueueSize)
|
||||
{
|
||||
_queues[game].push(std::make_pair(msg, position));
|
||||
_condition.notify_one();
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
35
ixbots/ixbots/IXQueueManager.h
Normal file
35
ixbots/ixbots/IXQueueManager.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* IXQueueManager.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <json/json.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class QueueManager
|
||||
{
|
||||
public:
|
||||
QueueManager(size_t maxQueueSize)
|
||||
: _maxQueueSize(maxQueueSize)
|
||||
{
|
||||
}
|
||||
|
||||
std::pair<Json::Value, std::string> pop();
|
||||
void add(const Json::Value& msg, const std::string& position);
|
||||
|
||||
private:
|
||||
std::map<std::string, std::queue<std::pair<Json::Value, std::string>>> _queues;
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _condition;
|
||||
size_t _maxQueueSize;
|
||||
};
|
||||
} // namespace ix
|
@ -39,28 +39,21 @@
|
||||
|
||||
#include "IXStatsdClient.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
StatsdClient::StatsdClient(const std::string& host,
|
||||
int port,
|
||||
const std::string& prefix,
|
||||
bool verbose)
|
||||
StatsdClient::StatsdClient(const std::string& host, int port, const std::string& prefix)
|
||||
: _host(host)
|
||||
, _port(port)
|
||||
, _prefix(prefix)
|
||||
, _stop(false)
|
||||
, _verbose(verbose)
|
||||
{
|
||||
_thread = std::thread([this] {
|
||||
setThreadName("Statsd");
|
||||
|
||||
while (!_stop)
|
||||
{
|
||||
flushQueue();
|
||||
@ -122,15 +115,11 @@ namespace ix
|
||||
{
|
||||
cleanup(key);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << _prefix << "." << key << ":" << value << "|" << type;
|
||||
char buf[256];
|
||||
snprintf(
|
||||
buf, sizeof(buf), "%s%s:%zd|%s\n", _prefix.c_str(), key.c_str(), value, type.c_str());
|
||||
|
||||
if (_verbose)
|
||||
{
|
||||
CoreLogger::info(ss.str());
|
||||
}
|
||||
|
||||
enqueue(ss.str() + "\n");
|
||||
enqueue(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -148,13 +137,10 @@ namespace ix
|
||||
{
|
||||
auto message = _queue.front();
|
||||
auto ret = _socket.sendto(message);
|
||||
if (ret == -1)
|
||||
if (ret != 0)
|
||||
{
|
||||
CoreLogger::error(std::string("statsd error: ") + strerror(UdpSocket::getErrno()));
|
||||
std::cerr << "error: " << strerror(UdpSocket::getErrno()) << std::endl;
|
||||
}
|
||||
|
||||
// we always dequeue regardless of the ability to send the message
|
||||
// so that we keep our queue size under control
|
||||
_queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,7 @@ namespace ix
|
||||
public:
|
||||
StatsdClient(const std::string& host = "127.0.0.1",
|
||||
int port = 8125,
|
||||
const std::string& prefix = "",
|
||||
bool verbose = false);
|
||||
const std::string& prefix = "");
|
||||
~StatsdClient();
|
||||
|
||||
bool init(std::string& errMsg);
|
||||
@ -53,7 +52,6 @@ namespace ix
|
||||
std::mutex _mutex; // for the queue
|
||||
|
||||
std::deque<std::string> _queue;
|
||||
bool _verbose;
|
||||
};
|
||||
|
||||
} // end namespace ix
|
||||
|
@ -111,12 +111,6 @@ namespace ix
|
||||
|
||||
void CobraConnection::disconnect()
|
||||
{
|
||||
auto subscriptionIds = getSubscriptionsIds();
|
||||
for (auto&& subscriptionId : subscriptionIds)
|
||||
{
|
||||
unsubscribe(subscriptionId);
|
||||
}
|
||||
|
||||
_authenticated = false;
|
||||
_webSocket->stop();
|
||||
}
|
||||
@ -568,13 +562,11 @@ namespace ix
|
||||
void CobraConnection::subscribe(const std::string& channel,
|
||||
const std::string& filter,
|
||||
const std::string& position,
|
||||
int batchSize,
|
||||
SubscriptionCallback cb)
|
||||
{
|
||||
// Create and send a subscribe pdu
|
||||
Json::Value body;
|
||||
body["channel"] = channel;
|
||||
body["batch_size"] = batchSize;
|
||||
|
||||
if (!filter.empty())
|
||||
{
|
||||
@ -620,18 +612,6 @@ namespace ix
|
||||
_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
|
||||
//
|
||||
|
@ -88,7 +88,6 @@ namespace ix
|
||||
void subscribe(const std::string& channel,
|
||||
const std::string& filter = std::string(),
|
||||
const std::string& position = std::string(),
|
||||
int batchSize = 1,
|
||||
SubscriptionCallback cb = nullptr);
|
||||
|
||||
/// Unsubscribe from a channel
|
||||
@ -163,9 +162,6 @@ namespace ix
|
||||
/// Tells whether the internal queue is empty or not
|
||||
bool isQueueEmpty();
|
||||
|
||||
/// Retrieve all subscriptions ids
|
||||
std::vector<std::string> getSubscriptionsIds();
|
||||
|
||||
///
|
||||
/// Member variables
|
||||
///
|
||||
|
@ -37,7 +37,9 @@ if (USE_MBED_TLS)
|
||||
target_include_directories(ixcrypto PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||
target_link_libraries(ixcrypto ${MBEDTLS_LIBRARIES})
|
||||
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_MBED_TLS)
|
||||
elseif (USE_OPEN_SSL)
|
||||
elseif (APPLE)
|
||||
elseif (WIN32)
|
||||
else()
|
||||
find_package(OpenSSL REQUIRED)
|
||||
add_definitions(${OPENSSL_DEFINITIONS})
|
||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||
|
@ -1,27 +0,0 @@
|
||||
#
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
|
||||
set (IXREDIS_SOURCES
|
||||
ixredis/IXRedisClient.cpp
|
||||
ixredis/IXRedisServer.cpp
|
||||
)
|
||||
|
||||
set (IXREDIS_HEADERS
|
||||
ixredis/IXRedisClient.h
|
||||
ixredis/IXRedisServer.h
|
||||
)
|
||||
|
||||
add_library(ixredis STATIC
|
||||
${IXREDIS_SOURCES}
|
||||
${IXREDIS_HEADERS}
|
||||
)
|
||||
|
||||
set(IXREDIS_INCLUDE_DIRS
|
||||
.
|
||||
..
|
||||
../ixcore
|
||||
../ixwebsocket)
|
||||
|
||||
target_include_directories( ixredis PUBLIC ${IXREDIS_INCLUDE_DIRS} )
|
@ -226,23 +226,20 @@ namespace ix
|
||||
return _jsonWriter.write(payload);
|
||||
}
|
||||
|
||||
void SentryClient::send(
|
||||
const Json::Value& msg,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg, bool verbose)
|
||||
{
|
||||
auto args = _httpClient->createRequest();
|
||||
args->url = _url;
|
||||
args->verb = HttpClient::kPost;
|
||||
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->logger = [](const std::string& msg) { CoreLogger::log(msg.c_str()); };
|
||||
args->body = computePayload(msg);
|
||||
|
||||
_httpClient->performRequest(args, onResponseCallback);
|
||||
std::string body = computePayload(msg);
|
||||
HttpResponsePtr response = _httpClient->post(_url, body, args);
|
||||
|
||||
return std::make_pair(response, body);
|
||||
}
|
||||
|
||||
// https://sentry.io/api/12345/minidump?sentry_key=abcdefgh");
|
||||
|
@ -21,9 +21,12 @@ namespace ix
|
||||
SentryClient(const std::string& dsn);
|
||||
~SentryClient() = default;
|
||||
|
||||
void send(const Json::Value& msg,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
std::pair<HttpResponsePtr, std::string> send(const Json::Value& msg, bool verbose);
|
||||
|
||||
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||
|
||||
// Mostly for testing
|
||||
void setTLSOptions(const SocketTLSOptions& tlsOptions);
|
||||
|
||||
void uploadMinidump(const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
@ -36,12 +39,6 @@ namespace ix
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||
|
||||
// Mostly for testing
|
||||
void setTLSOptions(const SocketTLSOptions& tlsOptions);
|
||||
|
||||
|
||||
private:
|
||||
int64_t getTimestamp();
|
||||
std::string computeAuthHeader();
|
||||
|
@ -7,14 +7,16 @@ set (IXSNAKE_SOURCES
|
||||
ixsnake/IXSnakeServer.cpp
|
||||
ixsnake/IXSnakeProtocol.cpp
|
||||
ixsnake/IXAppConfig.cpp
|
||||
ixsnake/IXStreamSql.cpp
|
||||
ixsnake/IXRedisClient.cpp
|
||||
ixsnake/IXRedisServer.cpp
|
||||
)
|
||||
|
||||
set (IXSNAKE_HEADERS
|
||||
ixsnake/IXSnakeServer.h
|
||||
ixsnake/IXSnakeProtocol.h
|
||||
ixsnake/IXAppConfig.h
|
||||
ixsnake/IXStreamSql.h
|
||||
ixsnake/IXRedisClient.h
|
||||
ixsnake/IXRedisServer.h
|
||||
)
|
||||
|
||||
add_library(ixsnake STATIC
|
||||
@ -28,7 +30,6 @@ set(IXSNAKE_INCLUDE_DIRS
|
||||
../ixcore
|
||||
../ixcrypto
|
||||
../ixwebsocket
|
||||
../ixredis
|
||||
../third_party)
|
||||
|
||||
target_include_directories( ixsnake PUBLIC ${IXSNAKE_INCLUDE_DIRS} )
|
||||
|
@ -26,12 +26,6 @@ namespace snake
|
||||
}
|
||||
|
||||
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"];
|
||||
return channel;
|
||||
}
|
||||
|
@ -33,9 +33,6 @@ namespace snake
|
||||
// Misc
|
||||
bool verbose;
|
||||
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);
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketFactory.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <sstream>
|
||||
@ -251,16 +250,12 @@ namespace ix
|
||||
}
|
||||
|
||||
std::string RedisClient::prepareXaddCommand(const std::string& stream,
|
||||
const std::string& message,
|
||||
int maxLen)
|
||||
const std::string& message)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "*8\r\n";
|
||||
ss << "*5\r\n";
|
||||
ss << writeString("XADD");
|
||||
ss << writeString(stream);
|
||||
ss << writeString("MAXLEN");
|
||||
ss << writeString("~");
|
||||
ss << writeString(std::to_string(maxLen));
|
||||
ss << writeString("*");
|
||||
ss << writeString("field");
|
||||
ss << writeString(message);
|
||||
@ -270,7 +265,6 @@ namespace ix
|
||||
|
||||
std::string RedisClient::xadd(const std::string& stream,
|
||||
const std::string& message,
|
||||
int maxLen,
|
||||
std::string& errMsg)
|
||||
{
|
||||
errMsg.clear();
|
||||
@ -281,7 +275,7 @@ namespace ix
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string command = prepareXaddCommand(stream, message, maxLen);
|
||||
std::string command = prepareXaddCommand(stream, message);
|
||||
|
||||
bool sent = _socket->writeBytes(command, nullptr);
|
||||
if (!sent)
|
||||
@ -354,104 +348,4 @@ namespace ix
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
std::pair<RespType, std::string> RedisClient::send(
|
||||
const std::vector<std::string>& args,
|
||||
std::string& errMsg)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "*";
|
||||
ss << std::to_string(args.size());
|
||||
ss << "\r\n";
|
||||
|
||||
for (auto&& arg : args)
|
||||
{
|
||||
ss << writeString(arg);
|
||||
}
|
||||
|
||||
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
||||
if (!sent)
|
||||
{
|
||||
errMsg = "Cannot write bytes to socket";
|
||||
return std::make_pair(RespType::Error, "");
|
||||
}
|
||||
|
||||
return readResponse(errMsg);
|
||||
}
|
||||
|
||||
std::pair<RespType, std::string> RedisClient::readResponse(std::string& errMsg)
|
||||
{
|
||||
// Read result
|
||||
auto pollResult = _socket->isReadyToRead(-1);
|
||||
if (pollResult == PollResultType::Error)
|
||||
{
|
||||
errMsg = "Error while polling for result";
|
||||
return std::make_pair(RespType::Error, "");
|
||||
}
|
||||
|
||||
// First line is the string length
|
||||
auto lineResult = _socket->readLine(nullptr);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid)
|
||||
{
|
||||
errMsg = "Error while polling for result";
|
||||
return std::make_pair(RespType::Error, "");
|
||||
}
|
||||
|
||||
std::string response;
|
||||
|
||||
if (line[0] == '+') // Simple string
|
||||
{
|
||||
std::stringstream ss;
|
||||
response = line.substr(1, line.size() - 3);
|
||||
return std::make_pair(RespType::String, response);
|
||||
}
|
||||
else if (line[0] == '-') // Errors
|
||||
{
|
||||
std::stringstream ss;
|
||||
response = line.substr(1, line.size() - 3);
|
||||
return std::make_pair(RespType::Error, response);
|
||||
}
|
||||
else if (line[0] == ':') // Integers
|
||||
{
|
||||
std::stringstream ss;
|
||||
response = line.substr(1, line.size() - 3);
|
||||
return std::make_pair(RespType::Integer, response);
|
||||
}
|
||||
else if (line[0] == '$') // Bulk strings
|
||||
{
|
||||
int stringSize;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << line.substr(1, line.size() - 1);
|
||||
ss >> stringSize;
|
||||
}
|
||||
|
||||
// Read the result, which is the stream id computed by the redis server
|
||||
lineResult = _socket->readLine(nullptr);
|
||||
lineValid = lineResult.first;
|
||||
line = lineResult.second;
|
||||
|
||||
std::string str = line.substr(0, stringSize);
|
||||
return std::make_pair(RespType::String, str);
|
||||
}
|
||||
else
|
||||
{
|
||||
errMsg = "Unhandled response type";
|
||||
return std::make_pair(RespType::Unknown, std::string());
|
||||
}
|
||||
}
|
||||
|
||||
std::string RedisClient::getRespTypeDescription(RespType respType)
|
||||
{
|
||||
switch (respType)
|
||||
{
|
||||
case RespType::Integer: return "integer";
|
||||
case RespType::Error: return "error";
|
||||
case RespType::String: return "string";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
@ -8,20 +8,12 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
enum class RespType : int
|
||||
{
|
||||
String = 0,
|
||||
Error = 1,
|
||||
Integer = 2,
|
||||
Unknown = 3
|
||||
};
|
||||
|
||||
class RedisClient
|
||||
{
|
||||
public:
|
||||
@ -48,22 +40,13 @@ namespace ix
|
||||
// XADD
|
||||
std::string xadd(const std::string& channel,
|
||||
const std::string& message,
|
||||
int maxLen,
|
||||
std::string& errMsg);
|
||||
std::string prepareXaddCommand(const std::string& stream,
|
||||
const std::string& message,
|
||||
int maxLen);
|
||||
|
||||
std::string prepareXaddCommand(const std::string& stream, const std::string& message);
|
||||
|
||||
std::string readXaddReply(std::string& errMsg);
|
||||
bool sendCommand(
|
||||
const std::string& commands, int commandsCount, std::string& errMsg);
|
||||
|
||||
// Arbitrary commands
|
||||
std::pair<RespType, std::string> send(
|
||||
const std::vector<std::string>& args,
|
||||
std::string& errMsg);
|
||||
std::pair<RespType, std::string> readResponse(std::string& errMsg);
|
||||
|
||||
std::string getRespTypeDescription(RespType respType);
|
||||
bool sendCommand(const std::string& commands, int commandsCount, std::string& errMsg);
|
||||
|
||||
void stop();
|
||||
|
@ -45,11 +45,8 @@ namespace ix
|
||||
}
|
||||
|
||||
void RedisServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
logInfo("New connection from remote ip " + connectionInfo->remoteIp);
|
||||
|
||||
_connectedClientsCount++;
|
||||
|
||||
while (!_stopHandlingConnections)
|
||||
@ -115,7 +112,7 @@ namespace ix
|
||||
it.second.erase(socket.get());
|
||||
}
|
||||
|
||||
for (auto&& it : _subscribers)
|
||||
for (auto it : _subscribers)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Subscription id: " << it.first << " #subscribers: " << it.second.size();
|
@ -6,8 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketServer.h>
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketServer.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@ -44,8 +44,7 @@ namespace ix
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) final;
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
bool startsWith(const std::string& str, const std::string& start);
|
@ -6,22 +6,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ixredis/IXRedisClient.h>
|
||||
#include <thread>
|
||||
#include "IXRedisClient.h"
|
||||
#include <future>
|
||||
#include <ixwebsocket/IXConnectionState.h>
|
||||
#include <string>
|
||||
#include "IXStreamSql.h"
|
||||
|
||||
namespace snake
|
||||
{
|
||||
class SnakeConnectionState : public ix::ConnectionState
|
||||
{
|
||||
public:
|
||||
virtual ~SnakeConnectionState()
|
||||
{
|
||||
stopSubScriptionThread();
|
||||
}
|
||||
|
||||
std::string getNonce()
|
||||
{
|
||||
return _nonce;
|
||||
@ -36,7 +30,6 @@ namespace snake
|
||||
{
|
||||
return _appkey;
|
||||
}
|
||||
|
||||
void setAppkey(const std::string& appkey)
|
||||
{
|
||||
_appkey = appkey;
|
||||
@ -46,7 +39,6 @@ namespace snake
|
||||
{
|
||||
return _role;
|
||||
}
|
||||
|
||||
void setRole(const std::string& role)
|
||||
{
|
||||
_role = role;
|
||||
@ -57,24 +49,7 @@ namespace snake
|
||||
return _redisClient;
|
||||
}
|
||||
|
||||
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;
|
||||
std::future<void> fut;
|
||||
|
||||
private:
|
||||
std::string _nonce;
|
||||
|
@ -18,22 +18,21 @@
|
||||
namespace snake
|
||||
{
|
||||
void handleError(const std::string& action,
|
||||
ix::WebSocket& ws,
|
||||
uint64_t pduId,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
nlohmann::json pdu,
|
||||
const std::string& errMsg)
|
||||
{
|
||||
std::string actionError(action);
|
||||
actionError += "/error";
|
||||
|
||||
nlohmann::json response = {
|
||||
{"action", actionError}, {"id", pduId}, {"body", {{"reason", errMsg}}}};
|
||||
ws.sendText(response.dump());
|
||||
{"action", actionError}, {"id", pdu.value("id", 1)}, {"body", {{"reason", errMsg}}}};
|
||||
ws->sendText(response.dump());
|
||||
}
|
||||
|
||||
void handleHandshake(std::shared_ptr<SnakeConnectionState> state,
|
||||
ix::WebSocket& ws,
|
||||
const nlohmann::json& pdu,
|
||||
uint64_t pduId)
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
std::string role = pdu["body"]["data"]["role"];
|
||||
|
||||
@ -42,7 +41,7 @@ namespace snake
|
||||
|
||||
nlohmann::json response = {
|
||||
{"action", "auth/handshake/ok"},
|
||||
{"id", pduId},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body",
|
||||
{
|
||||
{"data", {{"nonce", state->getNonce()}, {"connection_id", state->getId()}}},
|
||||
@ -50,14 +49,13 @@ namespace snake
|
||||
|
||||
auto serializedResponse = response.dump();
|
||||
|
||||
ws.sendText(serializedResponse);
|
||||
ws->sendText(serializedResponse);
|
||||
}
|
||||
|
||||
void handleAuth(std::shared_ptr<SnakeConnectionState> state,
|
||||
ix::WebSocket& ws,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu,
|
||||
uint64_t pduId)
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
auto secret = getRoleSecret(appConfig, state->appkey(), state->role());
|
||||
|
||||
@ -65,9 +63,9 @@ namespace snake
|
||||
{
|
||||
nlohmann::json response = {
|
||||
{"action", "auth/authenticate/error"},
|
||||
{"id", pduId},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {{"error", "authentication_failed"}, {"reason", "invalid secret"}}}};
|
||||
ws.sendText(response.dump());
|
||||
ws->sendText(response.dump());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -81,21 +79,19 @@ namespace snake
|
||||
{"action", "auth/authenticate/error"},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {{"error", "authentication_failed"}, {"reason", "invalid hash"}}}};
|
||||
ws.sendText(response.dump());
|
||||
ws->sendText(response.dump());
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json response = {
|
||||
{"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,
|
||||
ix::WebSocket& ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu,
|
||||
uint64_t pduId)
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
std::vector<std::string> channels;
|
||||
|
||||
@ -115,16 +111,10 @@ namespace snake
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Missing channels or channel field in publish data";
|
||||
handleError("rtm/publish", ws, pduId, ss.str());
|
||||
handleError("rtm/publish", ws, pdu, ss.str());
|
||||
return;
|
||||
}
|
||||
|
||||
// add an extra channel if the config has one specified
|
||||
if (!appConfig.republishChannel.empty())
|
||||
{
|
||||
channels.push_back(appConfig.republishChannel);
|
||||
}
|
||||
|
||||
for (auto&& channel : channels)
|
||||
{
|
||||
std::stringstream ss;
|
||||
@ -135,7 +125,7 @@ namespace snake
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot publish to redis host " << errMsg;
|
||||
handleError("rtm/publish", ws, pduId, ss.str());
|
||||
handleError("rtm/publish", ws, pdu, ss.str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -143,27 +133,26 @@ namespace snake
|
||||
nlohmann::json response = {
|
||||
{"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
|
||||
//
|
||||
void handleSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||
ix::WebSocket& ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu,
|
||||
uint64_t pduId)
|
||||
void handleRedisSubscription(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
std::string channel = pdu["body"]["channel"];
|
||||
state->subscriptionId = channel;
|
||||
std::string subscriptionId = channel;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << state->appkey() << "::" << channel;
|
||||
|
||||
state->appChannel = ss.str();
|
||||
std::string appChannel(ss.str());
|
||||
|
||||
ix::RedisClient& redisClient = state->subscriptionRedisClient;
|
||||
ix::RedisClient redisClient;
|
||||
int port = appConfig.redisPort;
|
||||
|
||||
auto urls = appConfig.redisHosts;
|
||||
@ -174,7 +163,7 @@ namespace snake
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot connect to redis host " << hostname << ":" << port;
|
||||
handleError("rtm/subscribe", ws, pduId, ss.str());
|
||||
handleError("rtm/subscribe", ws, pdu, ss.str());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -186,90 +175,80 @@ namespace snake
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot authenticated to redis";
|
||||
handleError("rtm/subscribe", ws, pduId, ss.str());
|
||||
handleError("rtm/subscribe", ws, pdu, ss.str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string filterStr;
|
||||
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) {
|
||||
int id = 0;
|
||||
auto callback = [ws, &id, &subscriptionId](const std::string& messageStr) {
|
||||
auto msg = nlohmann::json::parse(messageStr);
|
||||
|
||||
msg = msg["body"]["message"];
|
||||
|
||||
if (state->streamSql->valid() && !state->streamSql->match(msg))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json response = {
|
||||
{"action", "rtm/subscription/data"},
|
||||
{"id", state->id++},
|
||||
{"id", id++},
|
||||
{"body",
|
||||
{{"subscription_id", state->subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||
{{"subscription_id", subscriptionId}, {"position", "0-0"}, {"messages", {msg}}}}};
|
||||
|
||||
ws.sendText(response.dump());
|
||||
ws->sendText(response.dump());
|
||||
};
|
||||
|
||||
state->onRedisSubscribeResponseCallback = [&ws, state, pduId](const std::string& redisResponse) {
|
||||
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
|
||||
std::stringstream ss;
|
||||
ss << "Redis Response: " << redisResponse << "...";
|
||||
ix::CoreLogger::log(ss.str().c_str());
|
||||
|
||||
// Success
|
||||
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
||||
{"id", pduId},
|
||||
{"body", {{"subscription_id", state->subscriptionId}}}};
|
||||
ws.sendText(response.dump());
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {{"subscription_id", subscriptionId}}}};
|
||||
ws->sendText(response.dump());
|
||||
};
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Subscribing to " << state->appChannel << "...";
|
||||
ss << "Subscribing to " << appChannel << "...";
|
||||
ix::CoreLogger::log(ss.str().c_str());
|
||||
}
|
||||
|
||||
auto subscription = [&redisClient, state, &ws, pduId]
|
||||
if (!redisClient.subscribe(appChannel, responseCallback, callback))
|
||||
{
|
||||
if (!redisClient.subscribe(state->appChannel,
|
||||
state->onRedisSubscribeResponseCallback,
|
||||
state->onRedisSubscribeCallback))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Error subscribing to channel " << state->appChannel;
|
||||
handleError("rtm/subscribe", ws, pduId, ss.str());
|
||||
return;
|
||||
}
|
||||
};
|
||||
std::stringstream ss;
|
||||
ss << "Error subscribing to channel " << appChannel;
|
||||
handleError("rtm/subscribe", ws, pdu, ss.str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
state->subscriptionThread = std::thread(subscription);
|
||||
void handleSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
state->fut =
|
||||
std::async(std::launch::async, handleRedisSubscription, state, ws, appConfig, pdu);
|
||||
}
|
||||
|
||||
void handleUnSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||
ix::WebSocket& ws,
|
||||
const nlohmann::json& pdu,
|
||||
uint64_t pduId)
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
// extract subscription_id
|
||||
auto body = pdu["body"];
|
||||
auto subscriptionId = body["subscription_id"];
|
||||
|
||||
state->stopSubScriptionThread();
|
||||
state->redisClient().stop();
|
||||
|
||||
nlohmann::json response = {{"action", "rtm/unsubscribe/ok"},
|
||||
{"id", pduId},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {{"subscription_id", subscriptionId}}}};
|
||||
ws.sendText(response.dump());
|
||||
ws->sendText(response.dump());
|
||||
}
|
||||
|
||||
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
||||
ix::WebSocket& ws,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const std::string& str)
|
||||
{
|
||||
@ -284,32 +263,31 @@ namespace snake
|
||||
ss << "malformed json pdu: " << e.what() << " -> " << str << "";
|
||||
|
||||
nlohmann::json response = {{"body", {{"error", "invalid_json"}, {"reason", ss.str()}}}};
|
||||
ws.sendText(response.dump());
|
||||
ws->sendText(response.dump());
|
||||
return;
|
||||
}
|
||||
|
||||
auto action = pdu["action"];
|
||||
uint64_t pduId = pdu.value("id", 1);
|
||||
|
||||
if (action == "auth/handshake")
|
||||
{
|
||||
handleHandshake(state, ws, pdu, pduId);
|
||||
handleHandshake(state, ws, pdu);
|
||||
}
|
||||
else if (action == "auth/authenticate")
|
||||
{
|
||||
handleAuth(state, ws, appConfig, pdu, pduId);
|
||||
handleAuth(state, ws, appConfig, pdu);
|
||||
}
|
||||
else if (action == "rtm/publish")
|
||||
{
|
||||
handlePublish(state, ws, appConfig, pdu, pduId);
|
||||
handlePublish(state, ws, pdu);
|
||||
}
|
||||
else if (action == "rtm/subscribe")
|
||||
{
|
||||
handleSubscribe(state, ws, appConfig, pdu, pduId);
|
||||
handleSubscribe(state, ws, appConfig, pdu);
|
||||
}
|
||||
else if (action == "rtm/unsubscribe")
|
||||
{
|
||||
handleUnSubscribe(state, ws, pdu, pduId);
|
||||
handleUnSubscribe(state, ws, pdu);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ namespace snake
|
||||
struct AppConfig;
|
||||
|
||||
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
||||
ix::WebSocket& ws,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const std::string& str);
|
||||
} // namespace snake
|
||||
|
@ -59,68 +59,65 @@ namespace snake
|
||||
};
|
||||
_server.setConnectionStateFactory(factory);
|
||||
|
||||
_server.setOnClientMessageCallback(
|
||||
[this](std::shared_ptr<ix::ConnectionState> connectionState,
|
||||
ix::ConnectionInfo& connectionInfo,
|
||||
ix::WebSocket& webSocket,
|
||||
const ix::WebSocketMessagePtr& msg) {
|
||||
_server.setOnConnectionCallback(
|
||||
[this](std::shared_ptr<ix::WebSocket> webSocket,
|
||||
std::shared_ptr<ix::ConnectionState> connectionState) {
|
||||
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
||||
auto remoteIp = connectionInfo.remoteIp;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "[" << state->getId() << "] ";
|
||||
|
||||
ix::LogLevel logLevel = ix::LogLevel::Debug;
|
||||
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;
|
||||
}
|
||||
webSocket->setOnMessageCallback(
|
||||
[this, webSocket, state](const ix::WebSocketMessagePtr& msg) {
|
||||
std::stringstream ss;
|
||||
ix::LogLevel logLevel = ix::LogLevel::Debug;
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
ss << "New connection" << std::endl;
|
||||
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);
|
||||
state->setAppkey(appkey);
|
||||
std::string appkey = parseAppKey(msg->openInfo.uri);
|
||||
state->setAppkey(appkey);
|
||||
|
||||
// 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);
|
||||
}
|
||||
// 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" << std::endl;
|
||||
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
||||
}
|
||||
|
||||
ix::CoreLogger::log(ss.str().c_str(), logLevel);
|
||||
});
|
||||
ix::CoreLogger::log(ss.str().c_str(), logLevel);
|
||||
});
|
||||
});
|
||||
|
||||
auto res = _server.listen();
|
||||
if (!res.first)
|
||||
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* IXStreamSql.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Super simple hacked up version of a stream sql expression,
|
||||
* that only supports non nested field evaluation
|
||||
*/
|
||||
|
||||
#include "IXStreamSql.h"
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace snake
|
||||
{
|
||||
StreamSql::StreamSql(const std::string& sqlFilter)
|
||||
: _valid(false)
|
||||
{
|
||||
std::string token;
|
||||
std::stringstream tokenStream(sqlFilter);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// Split by ' '
|
||||
while (std::getline(tokenStream, token, ' '))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
_valid = tokens.size() == 8;
|
||||
if (!_valid) return;
|
||||
|
||||
_field = tokens[5];
|
||||
_operator = tokens[6];
|
||||
_value = tokens[7];
|
||||
|
||||
// remove single quotes
|
||||
_value = _value.substr(1, _value.size() - 2);
|
||||
|
||||
if (_operator == "LIKE")
|
||||
{
|
||||
_value = _value.substr(1, _value.size() - 2);
|
||||
}
|
||||
}
|
||||
|
||||
bool StreamSql::valid() const
|
||||
{
|
||||
return _valid;
|
||||
}
|
||||
|
||||
bool StreamSql::match(const nlohmann::json& msg)
|
||||
{
|
||||
if (!_valid) return false;
|
||||
|
||||
if (msg.find(_field) == msg.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string value = msg[_field];
|
||||
return value == _value;
|
||||
}
|
||||
|
||||
} // namespace snake
|
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* IXStreamSql.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
namespace snake
|
||||
{
|
||||
class StreamSql
|
||||
{
|
||||
public:
|
||||
StreamSql(const std::string& sqlFilter = std::string());
|
||||
~StreamSql() = default;
|
||||
|
||||
bool match(const nlohmann::json& msg);
|
||||
bool valid() const;
|
||||
|
||||
private:
|
||||
std::string _field;
|
||||
std::string _operator;
|
||||
std::string _value;
|
||||
bool _valid;
|
||||
};
|
||||
}
|
@ -6,7 +6,6 @@
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
|
||||
namespace ix
|
||||
@ -14,8 +13,6 @@ namespace ix
|
||||
CancellationRequest makeCancellationRequestWithTimeout(
|
||||
int secs, std::atomic<bool>& requestInitCancellation)
|
||||
{
|
||||
assert(secs > 0);
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto timeout = std::chrono::seconds(secs);
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* IXConnectionInfo.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct ConnectionInfo
|
||||
{
|
||||
std::string remoteIp;
|
||||
int remotePort;
|
||||
|
||||
ConnectionInfo(const std::string& r = std::string(), int p = 0)
|
||||
: remoteIp(r)
|
||||
, remotePort(p)
|
||||
{
|
||||
;
|
||||
}
|
||||
};
|
||||
} // namespace ix
|
@ -4,19 +4,6 @@
|
||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
//
|
||||
// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to returns wchar_t
|
||||
// which is different from all other platforms. We want the non unicode version.
|
||||
// See https://github.com/microsoft/vcpkg/pull/11030
|
||||
// We could do this in IXNetSystem.cpp but so far we are only using gai_strerror in here.
|
||||
//
|
||||
#ifdef _UNICODE
|
||||
#undef _UNICODE
|
||||
#endif
|
||||
#ifdef UNICODE
|
||||
#undef UNICODE
|
||||
#endif
|
||||
|
||||
#include "IXDNSLookup.h"
|
||||
|
||||
#include "IXNetSystem.h"
|
||||
|
@ -16,10 +16,7 @@
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -28,12 +25,10 @@ namespace ix
|
||||
const std::string HttpClient::kHead = "HEAD";
|
||||
const std::string HttpClient::kDel = "DEL";
|
||||
const std::string HttpClient::kPut = "PUT";
|
||||
const std::string HttpClient::kPatch = "PATCH";
|
||||
|
||||
HttpClient::HttpClient(bool async)
|
||||
: _async(async)
|
||||
, _stop(false)
|
||||
, _forceBody(false)
|
||||
{
|
||||
if (!_async) return;
|
||||
|
||||
@ -54,11 +49,6 @@ namespace ix
|
||||
_tlsOptions = tlsOptions;
|
||||
}
|
||||
|
||||
void HttpClient::setForceBody(bool value)
|
||||
{
|
||||
_forceBody = value;
|
||||
}
|
||||
|
||||
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
|
||||
{
|
||||
auto request = std::make_shared<HttpRequestArgs>();
|
||||
@ -130,7 +120,7 @@ namespace ix
|
||||
{
|
||||
// We only have one socket connection, so we cannot
|
||||
// make multiple requests concurrently.
|
||||
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
uint64_t uploadSize = 0;
|
||||
uint64_t downloadSize = 0;
|
||||
@ -177,13 +167,11 @@ namespace ix
|
||||
ss << verb << " " << path << " HTTP/1.1\r\n";
|
||||
ss << "Host: " << host << "\r\n";
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
if (args->compress)
|
||||
{
|
||||
ss << "Accept-Encoding: gzip"
|
||||
<< "\r\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Append extra headers
|
||||
for (auto&& it : args->extraHeaders)
|
||||
@ -204,7 +192,7 @@ namespace ix
|
||||
ss << "User-Agent: " << userAgent() << "\r\n";
|
||||
}
|
||||
|
||||
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
|
||||
if (verb == kPost || verb == kPut)
|
||||
{
|
||||
ss << "Content-Length: " << body.size() << "\r\n";
|
||||
|
||||
@ -232,10 +220,11 @@ namespace ix
|
||||
|
||||
std::string req(ss.str());
|
||||
std::string errMsg;
|
||||
std::atomic<bool> requestInitCancellation(false);
|
||||
|
||||
// Make a cancellation object dealing with connection timeout
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(args->connectTimeout, _stop);
|
||||
makeCancellationRequestWithTimeout(args->connectTimeout, requestInitCancellation);
|
||||
|
||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
||||
if (!success)
|
||||
@ -253,7 +242,8 @@ namespace ix
|
||||
}
|
||||
|
||||
// Make a new cancellation object dealing with transfer timeout
|
||||
isCancellationRequested = makeCancellationRequestWithTimeout(args->transferTimeout, _stop);
|
||||
isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(args->transferTimeout, requestInitCancellation);
|
||||
|
||||
if (args->verbose)
|
||||
{
|
||||
@ -500,7 +490,6 @@ namespace ix
|
||||
|
||||
downloadSize = payload.size();
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
// If the content was compressed with gzip, decode it
|
||||
if (headers["Content-Encoding"] == "gzip")
|
||||
{
|
||||
@ -519,7 +508,6 @@ namespace ix
|
||||
}
|
||||
payload = decompressedPayload;
|
||||
}
|
||||
#endif
|
||||
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
@ -574,20 +562,6 @@ namespace ix
|
||||
return request(url, kPut, body, args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::patch(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kPatch, serializeHttpParameters(httpParameters), args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::patch(const std::string& url,
|
||||
const std::string& body,
|
||||
const HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kPatch, body, args);
|
||||
}
|
||||
|
||||
std::string HttpClient::urlEncode(const std::string& value)
|
||||
{
|
||||
std::ostringstream escaped;
|
||||
@ -679,7 +653,6 @@ namespace ix
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
|
||||
{
|
||||
z_stream inflateState;
|
||||
@ -724,7 +697,6 @@ namespace ix
|
||||
inflateEnd(&inflateState);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
||||
{
|
||||
|
@ -46,19 +46,12 @@ namespace ix
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args);
|
||||
|
||||
HttpResponsePtr patch(const std::string& url,
|
||||
const HttpParameters& httpParameters,
|
||||
HttpRequestArgsPtr args);
|
||||
HttpResponsePtr patch(const std::string& url,
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args);
|
||||
|
||||
HttpResponsePtr request(const std::string& url,
|
||||
const std::string& verb,
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args,
|
||||
int redirects = 0);
|
||||
void setForceBody(bool value);
|
||||
|
||||
// Async API
|
||||
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
|
||||
const std::string& verb = HttpClient::kGet);
|
||||
@ -85,17 +78,15 @@ namespace ix
|
||||
const static std::string kHead;
|
||||
const static std::string kDel;
|
||||
const static std::string kPut;
|
||||
const static std::string kPatch;
|
||||
|
||||
private:
|
||||
void log(const std::string& msg, HttpRequestArgsPtr args);
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
bool gzipInflate(const std::string& in, std::string& out);
|
||||
#endif
|
||||
|
||||
// Async API background thread runner
|
||||
void run();
|
||||
|
||||
// Async API
|
||||
bool _async;
|
||||
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
|
||||
@ -105,12 +96,8 @@ namespace ix
|
||||
std::thread _thread;
|
||||
|
||||
std::unique_ptr<Socket> _socket;
|
||||
std::recursive_mutex _mutex; // to protect accessing the _socket (only one socket per
|
||||
// client) the mutex needs to be recursive as this function
|
||||
// might be called recursively to follow HTTP redirections
|
||||
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
|
||||
|
||||
SocketTLSOptions _tlsOptions;
|
||||
|
||||
bool _forceBody;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -9,15 +9,10 @@
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUserAgent.h"
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
|
||||
@ -43,51 +38,6 @@ namespace
|
||||
auto vec = res.second;
|
||||
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||
}
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::string gzipCompress(const std::string& str)
|
||||
{
|
||||
z_stream zs; // z_stream is zlib's control structure
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
|
||||
// deflateInit2 configure the file format: request gzip instead of deflate
|
||||
const int windowBits = 15;
|
||||
const int GZIP_ENCODING = 16;
|
||||
|
||||
deflateInit2(&zs,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
windowBits | GZIP_ENCODING,
|
||||
8,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
|
||||
zs.next_in = (Bytef*) str.data();
|
||||
zs.avail_in = (uInt) str.size(); // set the z_stream's input
|
||||
|
||||
int ret;
|
||||
char outbuffer[32768];
|
||||
std::string outstring;
|
||||
|
||||
// retrieve the compressed bytes blockwise
|
||||
do
|
||||
{
|
||||
zs.next_out = reinterpret_cast<Bytef*>(outbuffer);
|
||||
zs.avail_out = sizeof(outbuffer);
|
||||
|
||||
ret = deflate(&zs, Z_FINISH);
|
||||
|
||||
if (outstring.size() < zs.total_out)
|
||||
{
|
||||
// append the block to the output string
|
||||
outstring.append(outbuffer, zs.total_out - outstring.size());
|
||||
}
|
||||
} while (ret == Z_OK);
|
||||
|
||||
deflateEnd(&zs);
|
||||
|
||||
return outstring;
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
namespace ix
|
||||
@ -120,8 +70,7 @@ namespace ix
|
||||
}
|
||||
|
||||
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
_connectedClientsCount++;
|
||||
|
||||
@ -130,8 +79,7 @@ namespace ix
|
||||
|
||||
if (std::get<0>(ret))
|
||||
{
|
||||
auto response =
|
||||
_onConnectionCallback(std::get<2>(ret), connectionState, std::move(connectionInfo));
|
||||
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
||||
if (!Http::sendResponse(response, socket))
|
||||
{
|
||||
logError("Cannot send response");
|
||||
@ -151,8 +99,7 @@ namespace ix
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr {
|
||||
std::string uri(request->uri);
|
||||
if (uri.empty() || uri == "/")
|
||||
{
|
||||
@ -173,19 +120,9 @@ namespace ix
|
||||
|
||||
std::string content = res.second;
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::string acceptEncoding = request->headers["Accept-encoding"];
|
||||
if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos)
|
||||
{
|
||||
content = gzipCompress(content);
|
||||
headers["Content-Encoding"] = "gzip";
|
||||
}
|
||||
#endif
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionInfo->remoteIp << ":" << connectionInfo->remotePort << " "
|
||||
<< request->method << " " << request->headers["User-Agent"] << " "
|
||||
ss << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri << " " << content.size();
|
||||
logInfo(ss.str());
|
||||
|
||||
@ -209,16 +146,15 @@ namespace ix
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||
//
|
||||
setOnConnectionCallback(
|
||||
[this, redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
||||
[this,
|
||||
redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionInfo->remoteIp << ":" << connectionInfo->remotePort << " "
|
||||
<< request->method << " " << request->headers["User-Agent"] << " "
|
||||
ss << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri;
|
||||
logInfo(ss.str());
|
||||
|
||||
|
@ -23,9 +23,7 @@ namespace ix
|
||||
{
|
||||
public:
|
||||
using OnConnectionCallback =
|
||||
std::function<HttpResponsePtr(HttpRequestPtr,
|
||||
std::shared_ptr<ConnectionState>,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)>;
|
||||
std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
|
||||
|
||||
HttpServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
@ -46,8 +44,7 @@ namespace ix
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) final;
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
void setDefaultConnectionCallback();
|
||||
|
@ -19,7 +19,6 @@ typedef unsigned long int nfds_t;
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
|
@ -5,10 +5,8 @@
|
||||
*/
|
||||
|
||||
//
|
||||
// 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.
|
||||
// On macOS we use UNIX pipes to wake up select.
|
||||
//
|
||||
#ifndef _WIN32
|
||||
|
||||
#include "IXSelectInterruptPipe.h"
|
||||
|
||||
@ -146,5 +144,3 @@ namespace ix
|
||||
return _fildes[kPipeReadIndex];
|
||||
}
|
||||
} // namespace ix
|
||||
|
||||
#endif // !_WIN32
|
||||
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* IXSetThreadName.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2018 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
#include "IXSetThreadName.h"
|
||||
|
||||
// unix systems
|
||||
#if defined(__APPLE__) || defined(__linux__) || defined(BSD)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
// freebsd needs this header as well
|
||||
#if defined(BSD)
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
// Windows
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||
|
||||
#pragma pack(push, 8)
|
||||
typedef struct tagTHREADNAME_INFO
|
||||
{
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
} THREADNAME_INFO;
|
||||
#pragma pack(pop)
|
||||
|
||||
void SetThreadName(DWORD dwThreadID, const char* threadName)
|
||||
{
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = threadName;
|
||||
info.dwThreadID = dwThreadID;
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try
|
||||
{
|
||||
RaiseException(
|
||||
MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void setThreadName(const std::string& name)
|
||||
{
|
||||
#if defined(__APPLE__)
|
||||
//
|
||||
// Apple reserves 16 bytes for its thread names
|
||||
// Notice that the Apple version of pthread_setname_np
|
||||
// does not take a pthread_t argument
|
||||
//
|
||||
pthread_setname_np(name.substr(0, 63).c_str());
|
||||
#elif defined(__linux__)
|
||||
//
|
||||
// Linux only reserves 16 bytes for its thread names
|
||||
// See prctl and PR_SET_NAME property in
|
||||
// http://man7.org/linux/man-pages/man2/prctl.2.html
|
||||
//
|
||||
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
|
||||
#elif defined(_WIN32)
|
||||
SetThreadName(-1, name.c_str());
|
||||
#elif defined(BSD)
|
||||
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
|
||||
#else
|
||||
// ... assert here ?
|
||||
#endif
|
||||
}
|
||||
} // namespace ix
|
@ -43,55 +43,6 @@ namespace ix
|
||||
mbedtls_pk_init(&_pkey);
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
|
||||
CERT_SYSTEM_STORE_CURRENT_USER;
|
||||
HCERTSTORE systemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, flags, L"Root");
|
||||
|
||||
if (!systemStore)
|
||||
{
|
||||
errorMsg = "CertOpenStore failed with ";
|
||||
errorMsg += std::to_string(GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
PCCERT_CONTEXT certificateIterator = NULL;
|
||||
|
||||
int certificateCount = 0;
|
||||
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
|
||||
{
|
||||
if (certificateIterator->dwCertEncodingType & X509_ASN_ENCODING)
|
||||
{
|
||||
int ret = mbedtls_x509_crt_parse(&_cacert,
|
||||
certificateIterator->pbCertEncoded,
|
||||
certificateIterator->cbCertEncoded);
|
||||
if (ret == 0)
|
||||
{
|
||||
++certificateCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CertFreeCertificateContext(certificateIterator);
|
||||
CertCloseStore(systemStore, 0);
|
||||
|
||||
if (certificateCount == 0)
|
||||
{
|
||||
errorMsg = "No certificates found";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
// On macOS we can query the system cert location from the keychain
|
||||
// On Linux we could try to fetch some local files based on the distribution
|
||||
// On Android we could use JNI to get to the system certs
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
|
||||
{
|
||||
initMBedTLS();
|
||||
@ -145,15 +96,13 @@ namespace ix
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
|
||||
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
||||
|
||||
if (_tlsOptions.isUsingSystemDefaults())
|
||||
{
|
||||
if (!loadSystemCertificates(errMsg))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
; // FIXME
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -52,7 +52,6 @@ namespace ix
|
||||
|
||||
bool init(const std::string& host, bool isClient, std::string& errMsg);
|
||||
void initMBedTLS();
|
||||
bool loadSystemCertificates(std::string& errMsg);
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
@ -85,8 +85,6 @@ namespace ix
|
||||
|
||||
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
||||
std::once_flag SocketOpenSSL::_openSSLInitFlag;
|
||||
std::unique_ptr<std::mutex[]> SocketOpenSSL::_openSSLMutexes =
|
||||
std::make_unique<std::mutex[]>(CRYPTO_num_locks());
|
||||
|
||||
SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
@ -108,11 +106,6 @@ namespace ix
|
||||
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
|
||||
#else
|
||||
(void) OPENSSL_config(nullptr);
|
||||
|
||||
if (CRYPTO_get_locking_callback() == nullptr)
|
||||
{
|
||||
CRYPTO_set_locking_callback(SocketOpenSSL::openSSLLockingCallback);
|
||||
}
|
||||
#endif
|
||||
|
||||
(void) OpenSSL_add_ssl_algorithms();
|
||||
@ -121,21 +114,6 @@ namespace ix
|
||||
_openSSLInitializationSuccessful = true;
|
||||
}
|
||||
|
||||
void SocketOpenSSL::openSSLLockingCallback(int mode,
|
||||
int type,
|
||||
const char* /*file*/,
|
||||
int /*line*/)
|
||||
{
|
||||
if (mode & CRYPTO_LOCK)
|
||||
{
|
||||
_openSSLMutexes[type].lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
_openSSLMutexes[type].unlock();
|
||||
}
|
||||
}
|
||||
|
||||
std::string SocketOpenSSL::getSSLError(int ret)
|
||||
{
|
||||
unsigned long e;
|
||||
|
@ -49,9 +49,6 @@ namespace ix
|
||||
bool handleTLSOptions(std::string& errMsg);
|
||||
bool openSSLServerHandshake(std::string& errMsg);
|
||||
|
||||
// Required for OpenSSL < 1.1
|
||||
static void openSSLLockingCallback(int mode, int type, const char* /*file*/, int /*line*/);
|
||||
|
||||
SSL* _ssl_connection;
|
||||
SSL_CTX* _ssl_context;
|
||||
const SSL_METHOD* _ssl_method;
|
||||
@ -61,7 +58,6 @@ namespace ix
|
||||
|
||||
static std::once_flag _openSSLInitFlag;
|
||||
static std::atomic<bool> _openSSLInitializationSuccessful;
|
||||
static std::unique_ptr<std::mutex[]> _openSSLMutexes;
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
@ -22,7 +22,7 @@ namespace ix
|
||||
const int SocketServer::kDefaultPort(8080);
|
||||
const std::string SocketServer::kDefaultHost("127.0.0.1");
|
||||
const int SocketServer::kDefaultTcpBacklog(5);
|
||||
const size_t SocketServer::kDefaultMaxConnections(128);
|
||||
const size_t SocketServer::kDefaultMaxConnections(32);
|
||||
const int SocketServer::kDefaultAddressFamily(AF_INET);
|
||||
|
||||
SocketServer::SocketServer(
|
||||
@ -276,7 +276,6 @@ namespace ix
|
||||
}
|
||||
|
||||
// Accept a connection.
|
||||
// FIXME: Is this working for ipv6 ?
|
||||
struct sockaddr_in client; // client address information
|
||||
int clientFd; // socket connected to client
|
||||
socklen_t addressLen = sizeof(client);
|
||||
@ -308,45 +307,6 @@ namespace ix
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo;
|
||||
|
||||
if (_addressFamily == AF_INET)
|
||||
{
|
||||
char remoteIp[INET_ADDRSTRLEN];
|
||||
if (inet_ntop(AF_INET, &client.sin_addr, remoteIp, INET_ADDRSTRLEN) == nullptr)
|
||||
{
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error calling inet_ntop (ipv4): " << err << ", "
|
||||
<< strerror(err);
|
||||
logError(ss.str());
|
||||
|
||||
Socket::closeSocket(clientFd);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionInfo = std::make_unique<ConnectionInfo>(remoteIp, client.sin_port);
|
||||
}
|
||||
else // AF_INET6
|
||||
{
|
||||
char remoteIp[INET6_ADDRSTRLEN];
|
||||
if (inet_ntop(AF_INET6, &client.sin_addr, remoteIp, INET6_ADDRSTRLEN) == nullptr)
|
||||
{
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error calling inet_ntop (ipv6): " << err << ", "
|
||||
<< strerror(err);
|
||||
logError(ss.str());
|
||||
|
||||
Socket::closeSocket(clientFd);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionInfo = std::make_unique<ConnectionInfo>(remoteIp, client.sin_port);
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectionState> connectionState;
|
||||
if (_connectionStateFactory)
|
||||
{
|
||||
@ -379,13 +339,10 @@ namespace ix
|
||||
|
||||
// Launch the handleConnection work asynchronously in its own thread.
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
_connectionsThreads.push_back(
|
||||
std::make_pair(connectionState,
|
||||
std::thread(&SocketServer::handleConnection,
|
||||
this,
|
||||
std::move(socket),
|
||||
connectionState,
|
||||
std::move(connectionInfo))));
|
||||
_connectionsThreads.push_back(std::make_pair(
|
||||
connectionState,
|
||||
std::thread(
|
||||
&SocketServer::handleConnection, this, std::move(socket), connectionState)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXConnectionInfo.h"
|
||||
#include "IXConnectionState.h"
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include <atomic>
|
||||
@ -103,8 +102,7 @@ namespace ix
|
||||
ConnectionStateFactory _connectionStateFactory;
|
||||
|
||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) = 0;
|
||||
std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||
virtual size_t getConnectedClientsCount() = 0;
|
||||
|
||||
// Returns true if all connection threads are joined
|
||||
|
@ -44,18 +44,6 @@ namespace ix
|
||||
return err;
|
||||
}
|
||||
|
||||
bool UdpSocket::isWaitNeeded()
|
||||
{
|
||||
int err = getErrno();
|
||||
|
||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UdpSocket::closeSocket(int fd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@ -74,13 +62,6 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
unsigned long nonblocking = 1;
|
||||
ioctlsocket(_sockfd, FIONBIO, &nonblocking);
|
||||
#else
|
||||
fcntl(_sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
|
||||
#endif
|
||||
|
||||
memset(&_server, 0, sizeof(_server));
|
||||
_server.sin_family = AF_INET;
|
||||
_server.sin_port = htons(port);
|
||||
@ -112,15 +93,4 @@ namespace ix
|
||||
return (ssize_t)::sendto(
|
||||
_sockfd, buffer.data(), buffer.size(), 0, (struct sockaddr*) &_server, sizeof(_server));
|
||||
}
|
||||
|
||||
ssize_t UdpSocket::recvfrom(char* buffer, size_t length)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int addressLen = (int) sizeof(_server);
|
||||
#else
|
||||
socklen_t addressLen = (socklen_t) sizeof(_server);
|
||||
#endif
|
||||
return (ssize_t)::recvfrom(
|
||||
_sockfd, buffer, length, 0, (struct sockaddr*) &_server, &addressLen);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -28,12 +28,9 @@ namespace ix
|
||||
// Virtual methods
|
||||
bool init(const std::string& host, int port, std::string& errMsg);
|
||||
ssize_t sendto(const std::string& buffer);
|
||||
ssize_t recvfrom(char* buffer, size_t length);
|
||||
|
||||
void close();
|
||||
|
||||
static int getErrno();
|
||||
static bool isWaitNeeded();
|
||||
static void closeSocket(int fd);
|
||||
|
||||
private:
|
||||
|
@ -32,7 +32,6 @@
|
||||
#include "IXUrlParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
|
@ -8,9 +8,7 @@
|
||||
|
||||
#include "IXWebSocketVersion.h"
|
||||
#include <sstream>
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
// Platform name
|
||||
#if defined(_WIN32)
|
||||
@ -79,10 +77,8 @@ namespace ix
|
||||
ss << " nossl";
|
||||
#endif
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
// Zlib version
|
||||
ss << " zlib " << ZLIB_VERSION;
|
||||
#endif
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ namespace ix
|
||||
WebSocket::~WebSocket()
|
||||
{
|
||||
stop();
|
||||
_ws.setOnCloseCallback(nullptr);
|
||||
}
|
||||
|
||||
void WebSocket::setUrl(const std::string& url)
|
||||
|
@ -28,26 +28,21 @@ namespace ix
|
||||
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
|
||||
: _compressBufferSize(kBufferSize)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
memset(&_deflateState, 0, sizeof(_deflateState));
|
||||
|
||||
_deflateState.zalloc = Z_NULL;
|
||||
_deflateState.zfree = Z_NULL;
|
||||
_deflateState.opaque = Z_NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
deflateEnd(&_deflateState);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
|
||||
bool clientNoContextTakeOver)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
int ret = deflateInit2(&_deflateState,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
@ -62,49 +57,17 @@ namespace ix
|
||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool WebSocketPerMessageDeflateCompressor::endsWithEmptyUnCompressedBlock(const T& value)
|
||||
bool WebSocketPerMessageDeflateCompressor::endsWith(const std::string& value,
|
||||
const std::string& ending)
|
||||
{
|
||||
if (kEmptyUncompressedBlock.size() > value.size()) return false;
|
||||
auto N = value.size();
|
||||
return value[N - 1] == kEmptyUncompressedBlock[3] &&
|
||||
value[N - 2] == kEmptyUncompressedBlock[2] &&
|
||||
value[N - 3] == kEmptyUncompressedBlock[1] &&
|
||||
value[N - 4] == kEmptyUncompressedBlock[0];
|
||||
if (ending.size() > value.size()) return false;
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, std::string& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
|
||||
std::vector<uint8_t>& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
|
||||
std::string& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
|
||||
std::vector<uint8_t>& out)
|
||||
{
|
||||
return compressData(in, out);
|
||||
}
|
||||
|
||||
template<typename T, typename S>
|
||||
bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
//
|
||||
// 7.2.1. Compression
|
||||
//
|
||||
@ -133,8 +96,7 @@ namespace ix
|
||||
// The normal buffer size should be 6 but
|
||||
// we remove the 4 octets from the tail (#4)
|
||||
uint8_t buf[2] = {0x02, 0x00};
|
||||
out.push_back(buf[0]);
|
||||
out.push_back(buf[1]);
|
||||
out.append((char*) (buf), 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -152,18 +114,15 @@ namespace ix
|
||||
|
||||
output = _compressBufferSize - _deflateState.avail_out;
|
||||
|
||||
out.insert(out.end(), _compressBuffer.get(), _compressBuffer.get() + output);
|
||||
out.append((char*) (_compressBuffer.get()), output);
|
||||
} while (_deflateState.avail_out == 0);
|
||||
|
||||
if (endsWithEmptyUnCompressedBlock(out))
|
||||
if (endsWith(out, kEmptyUncompressedBlock))
|
||||
{
|
||||
out.resize(out.size() - 4);
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
@ -172,7 +131,6 @@ namespace ix
|
||||
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
|
||||
: _compressBufferSize(kBufferSize)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
memset(&_inflateState, 0, sizeof(_inflateState));
|
||||
|
||||
_inflateState.zalloc = Z_NULL;
|
||||
@ -180,20 +138,16 @@ namespace ix
|
||||
_inflateState.opaque = Z_NULL;
|
||||
_inflateState.avail_in = 0;
|
||||
_inflateState.next_in = Z_NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
inflateEnd(&_inflateState);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
|
||||
bool clientNoContextTakeOver)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
|
||||
|
||||
if (ret != Z_OK) return false;
|
||||
@ -203,14 +157,10 @@ namespace ix
|
||||
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
//
|
||||
// 7.2.2. Decompression
|
||||
//
|
||||
@ -247,8 +197,5 @@ namespace ix
|
||||
} while (_inflateState.avail_out == 0);
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -6,12 +6,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
#include "zlib.h"
|
||||
#endif
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -23,23 +20,14 @@ namespace ix
|
||||
|
||||
bool init(uint8_t deflateBits, bool clientNoContextTakeOver);
|
||||
bool compress(const std::string& in, std::string& out);
|
||||
bool compress(const std::string& in, std::vector<uint8_t>& out);
|
||||
bool compress(const std::vector<uint8_t>& in, std::string& out);
|
||||
bool compress(const std::vector<uint8_t>& in, std::vector<uint8_t>& out);
|
||||
|
||||
private:
|
||||
template<typename T, typename S>
|
||||
bool compressData(const T& in, S& out);
|
||||
template<typename T>
|
||||
bool endsWithEmptyUnCompressedBlock(const T& value);
|
||||
static bool endsWith(const std::string& value, const std::string& ending);
|
||||
|
||||
int _flush;
|
||||
size_t _compressBufferSize;
|
||||
std::unique_ptr<unsigned char[]> _compressBuffer;
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
z_stream _deflateState;
|
||||
#endif
|
||||
};
|
||||
|
||||
class WebSocketPerMessageDeflateDecompressor
|
||||
@ -55,10 +43,7 @@ namespace ix
|
||||
int _flush;
|
||||
size_t _compressBufferSize;
|
||||
std::unique_ptr<unsigned char[]> _compressBuffer;
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
z_stream _inflateState;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
@ -61,7 +61,6 @@ namespace ix
|
||||
_clientMaxWindowBits = kDefaultClientMaxWindowBits;
|
||||
_serverMaxWindowBits = kDefaultServerMaxWindowBits;
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
// Split by ;
|
||||
std::string token;
|
||||
std::stringstream tokenStream(extension);
|
||||
@ -113,7 +112,6 @@ namespace ix
|
||||
sanitizeClientMaxWindowBits();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits()
|
||||
@ -128,7 +126,6 @@ namespace ix
|
||||
|
||||
std::string WebSocketPerMessageDeflateOptions::generateHeader()
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
std::stringstream ss;
|
||||
ss << "Sec-WebSocket-Extensions: permessage-deflate";
|
||||
|
||||
@ -141,18 +138,11 @@ namespace ix
|
||||
ss << "\r\n";
|
||||
|
||||
return ss.str();
|
||||
#else
|
||||
return std::string();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateOptions::enabled() const
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||
return _enabled;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
|
||||
|
@ -1,123 +0,0 @@
|
||||
/*
|
||||
* 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
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* 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,38 +71,13 @@ namespace ix
|
||||
_onConnectionCallback = callback;
|
||||
}
|
||||
|
||||
void WebSocketServer::setOnClientMessageCallback(const OnClientMessageCallback& callback)
|
||||
{
|
||||
_onClientMessageCallback = callback;
|
||||
}
|
||||
|
||||
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
setThreadName("WebSocketServer::" + connectionState->getId());
|
||||
|
||||
auto webSocket = std::make_shared<WebSocket>();
|
||||
if (_onConnectionCallback)
|
||||
{
|
||||
_onConnectionCallback(webSocket, connectionState, std::move(connectionInfo));
|
||||
}
|
||||
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;
|
||||
}
|
||||
_onConnectionCallback(webSocket, connectionState);
|
||||
|
||||
webSocket->disableAutomaticReconnection();
|
||||
|
||||
@ -136,8 +111,6 @@ namespace ix
|
||||
logError(ss.str());
|
||||
}
|
||||
|
||||
webSocket->setOnMessageCallback(nullptr);
|
||||
|
||||
// Remove this client from our client set
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_clientsMutex);
|
||||
|
@ -19,18 +19,11 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class WebSocketServer : public SocketServer
|
||||
class WebSocketServer final : public SocketServer
|
||||
{
|
||||
public:
|
||||
using OnConnectionCallback =
|
||||
std::function<void(std::weak_ptr<WebSocket>,
|
||||
std::shared_ptr<ConnectionState>,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo)>;
|
||||
|
||||
using OnClientMessageCallback = std::function<void(std::shared_ptr<ConnectionState>,
|
||||
ConnectionInfo&,
|
||||
WebSocket&,
|
||||
const WebSocketMessagePtr&)>;
|
||||
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
|
||||
|
||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
@ -46,7 +39,6 @@ namespace ix
|
||||
void disablePerMessageDeflate();
|
||||
|
||||
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
||||
void setOnClientMessageCallback(const OnClientMessageCallback& callback);
|
||||
|
||||
// Get all the connected clients
|
||||
std::set<std::shared_ptr<WebSocket>> getClients();
|
||||
@ -60,7 +52,6 @@ namespace ix
|
||||
bool _enablePerMessageDeflate;
|
||||
|
||||
OnConnectionCallback _onConnectionCallback;
|
||||
OnClientMessageCallback _onClientMessageCallback;
|
||||
|
||||
std::mutex _clientsMutex;
|
||||
std::set<std::shared_ptr<WebSocket>> _clients;
|
||||
@ -69,8 +60,7 @@ namespace ix
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo);
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -65,6 +65,7 @@ namespace ix
|
||||
, _receivedMessageCompressed(false)
|
||||
, _readyState(ReadyState::CLOSED)
|
||||
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
|
||||
, _closeReason(WebSocketCloseConstants::kInternalErrorMessage)
|
||||
, _closeWireSize(0)
|
||||
, _closeRemote(false)
|
||||
, _enablePerMessageDeflate(false)
|
||||
@ -76,7 +77,6 @@ namespace ix
|
||||
, _pingCount(0)
|
||||
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
||||
{
|
||||
setCloseReason(WebSocketCloseConstants::kInternalErrorMessage);
|
||||
_readbuf.resize(kChunkSize);
|
||||
}
|
||||
|
||||
@ -179,12 +179,10 @@ namespace ix
|
||||
|
||||
if (readyState == ReadyState::CLOSED)
|
||||
{
|
||||
if (_onCloseCallback)
|
||||
{
|
||||
_onCloseCallback(_closeCode, getCloseReason(), _closeWireSize, _closeRemote);
|
||||
}
|
||||
setCloseReason(WebSocketCloseConstants::kInternalErrorMessage);
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote);
|
||||
_closeCode = WebSocketCloseConstants::kInternalErrorCode;
|
||||
_closeReason = WebSocketCloseConstants::kInternalErrorMessage;
|
||||
_closeWireSize = 0;
|
||||
_closeRemote = false;
|
||||
}
|
||||
@ -263,10 +261,9 @@ namespace ix
|
||||
{
|
||||
// compute lasting delay to wait for next ping / timeout, if at least one set
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
int timeSinceLastPingMs = (int) std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
lastingTimeoutDelayInMs = (int) std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - _lastSendPingTimePoint)
|
||||
.count();
|
||||
lastingTimeoutDelayInMs = (1000 * _pingIntervalSecs) - timeSinceLastPingMs;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -329,10 +326,9 @@ namespace ix
|
||||
return _txbuf.empty();
|
||||
}
|
||||
|
||||
template<class Iterator>
|
||||
void WebSocketTransport::appendToSendBuffer(const std::vector<uint8_t>& header,
|
||||
Iterator begin,
|
||||
Iterator end,
|
||||
std::string::const_iterator begin,
|
||||
std::string::const_iterator end,
|
||||
uint64_t message_size,
|
||||
uint8_t masking_key[4])
|
||||
{
|
||||
@ -642,7 +638,11 @@ namespace ix
|
||||
{
|
||||
// we got the CLOSE frame answer from our close, so we can close the connection
|
||||
// if the code/reason are the same
|
||||
bool identicalReason = _closeCode == code && getCloseReason() == reason;
|
||||
bool identicalReason;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
identicalReason = _closeCode == code && _closeReason == reason;
|
||||
}
|
||||
|
||||
if (identicalReason)
|
||||
{
|
||||
@ -750,9 +750,8 @@ namespace ix
|
||||
return static_cast<unsigned>(seconds);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type,
|
||||
const T& message,
|
||||
const std::string& message,
|
||||
bool compress,
|
||||
const OnProgressCallback& onProgressCallback)
|
||||
{
|
||||
@ -765,8 +764,8 @@ namespace ix
|
||||
size_t wireSize = message.size();
|
||||
bool compressionError = false;
|
||||
|
||||
auto message_begin = message.cbegin();
|
||||
auto message_end = message.cend();
|
||||
std::string::const_iterator message_begin = message.begin();
|
||||
std::string::const_iterator message_end = message.end();
|
||||
|
||||
if (compress)
|
||||
{
|
||||
@ -781,8 +780,8 @@ namespace ix
|
||||
compressionError = false;
|
||||
wireSize = _compressedMessage.size();
|
||||
|
||||
message_begin = _compressedMessage.cbegin();
|
||||
message_end = _compressedMessage.cend();
|
||||
message_begin = _compressedMessage.begin();
|
||||
message_end = _compressedMessage.end();
|
||||
}
|
||||
|
||||
{
|
||||
@ -796,11 +795,6 @@ namespace ix
|
||||
if (wireSize < kChunkSize)
|
||||
{
|
||||
success = sendFragment(type, true, message_begin, message_end, compress);
|
||||
|
||||
if (onProgressCallback)
|
||||
{
|
||||
onProgressCallback(0, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -865,11 +859,10 @@ namespace ix
|
||||
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
|
||||
}
|
||||
|
||||
template<class Iterator>
|
||||
bool WebSocketTransport::sendFragment(wsheader_type::opcode_type type,
|
||||
bool fin,
|
||||
Iterator message_begin,
|
||||
Iterator message_end,
|
||||
std::string::const_iterator message_begin,
|
||||
std::string::const_iterator message_end,
|
||||
bool compress)
|
||||
{
|
||||
uint64_t message_size = static_cast<uint64_t>(message_end - message_begin);
|
||||
@ -1062,7 +1055,7 @@ namespace ix
|
||||
else
|
||||
{
|
||||
// no close code/reason set
|
||||
sendData(wsheader_type::CLOSE, std::string(""), compress);
|
||||
sendData(wsheader_type::CLOSE, "", compress);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1085,10 +1078,13 @@ namespace ix
|
||||
{
|
||||
closeSocket();
|
||||
|
||||
setCloseReason(reason);
|
||||
_closeCode = code;
|
||||
_closeWireSize = closeWireSize;
|
||||
_closeRemote = remote;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
_closeCode = code;
|
||||
_closeReason = reason;
|
||||
_closeWireSize = closeWireSize;
|
||||
_closeRemote = remote;
|
||||
}
|
||||
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
_requestInitCancellation = false;
|
||||
@ -1108,11 +1104,13 @@ namespace ix
|
||||
closeWireSize = reason.size();
|
||||
}
|
||||
|
||||
setCloseReason(reason);
|
||||
_closeCode = code;
|
||||
_closeWireSize = closeWireSize;
|
||||
_closeRemote = remote;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||
_closeCode = code;
|
||||
_closeReason = reason;
|
||||
_closeWireSize = closeWireSize;
|
||||
_closeRemote = remote;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
||||
_closingTimePoint = std::chrono::steady_clock::now();
|
||||
@ -1157,15 +1155,4 @@ namespace ix
|
||||
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
|
||||
|
@ -178,11 +178,11 @@ namespace ix
|
||||
std::atomic<ReadyState> _readyState;
|
||||
|
||||
OnCloseCallback _onCloseCallback;
|
||||
uint16_t _closeCode;
|
||||
std::string _closeReason;
|
||||
mutable std::mutex _closeReasonMutex;
|
||||
std::atomic<uint16_t> _closeCode;
|
||||
std::atomic<size_t> _closeWireSize;
|
||||
std::atomic<bool> _closeRemote;
|
||||
size_t _closeWireSize;
|
||||
bool _closeRemote;
|
||||
mutable std::mutex _closeDataMutex;
|
||||
|
||||
// Data used for Per Message Deflate compression (with zlib)
|
||||
WebSocketPerMessageDeflatePtr _perMessageDeflate;
|
||||
@ -239,15 +239,16 @@ namespace ix
|
||||
bool sendOnSocket();
|
||||
bool receiveFromSocket();
|
||||
|
||||
template<class T>
|
||||
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
||||
const T& message,
|
||||
const std::string& message,
|
||||
bool compress,
|
||||
const OnProgressCallback& onProgressCallback = nullptr);
|
||||
|
||||
template<class Iterator>
|
||||
bool sendFragment(
|
||||
wsheader_type::opcode_type type, bool fin, Iterator begin, Iterator end, bool compress);
|
||||
bool sendFragment(wsheader_type::opcode_type type,
|
||||
bool fin,
|
||||
std::string::const_iterator begin,
|
||||
std::string::const_iterator end,
|
||||
bool compress);
|
||||
|
||||
void emitMessage(MessageKind messageKind,
|
||||
const std::string& message,
|
||||
@ -255,11 +256,9 @@ namespace ix
|
||||
const OnMessageCallback& onMessageCallback);
|
||||
|
||||
bool isSendBufferEmpty() const;
|
||||
|
||||
template<class Iterator>
|
||||
void appendToSendBuffer(const std::vector<uint8_t>& header,
|
||||
Iterator begin,
|
||||
Iterator end,
|
||||
std::string::const_iterator begin,
|
||||
std::string::const_iterator end,
|
||||
uint64_t message_size,
|
||||
uint8_t masking_key[4]);
|
||||
|
||||
@ -267,8 +266,5 @@ namespace ix
|
||||
void unmaskReceiveBuffer(const wsheader_type& ws);
|
||||
|
||||
std::string getMergedChunks() const;
|
||||
|
||||
void setCloseReason(const std::string& reason);
|
||||
const std::string& getCloseReason() const;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -6,4 +6,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define IX_WEBSOCKET_VERSION "10.1.1"
|
||||
#define IX_WEBSOCKET_VERSION "9.5.2"
|
||||
|
20
ixwebsocket/apple/IXSetThreadName_apple.cpp
Normal file
20
ixwebsocket/apple/IXSetThreadName_apple.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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
|
16
ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp
Normal file
16
ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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
|
20
ixwebsocket/linux/IXSetThreadName_linux.cpp
Normal file
20
ixwebsocket/linux/IXSetThreadName_linux.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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
|
46
ixwebsocket/windows/IXSetThreadName_windows.cpp
Normal file
46
ixwebsocket/windows/IXSetThreadName_windows.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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
|
30
luarocks/CMakeLists.txt
Normal file
30
luarocks/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
#
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
|
||||
cmake_minimum_required (VERSION 3.4.1)
|
||||
project (luarocks)
|
||||
|
||||
# There's -Weverything too for clang
|
||||
if (NOT WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||
endif()
|
||||
|
||||
set (CMAKE_CXX_STANDARD 14)
|
||||
|
||||
option(USE_TLS "Add TLS support" ON)
|
||||
|
||||
include_directories(luarocks .)
|
||||
|
||||
add_executable(luarocks main.cpp)
|
||||
|
||||
include(FindLua)
|
||||
find_package(lua REQUIRED)
|
||||
target_link_libraries(luarocks ${LUA_LIBRARIES})
|
||||
target_include_directories(luarocks PRIVATE ${LUA_INCLUDE_DIR})
|
||||
|
||||
# library with the most dependencies come first
|
||||
target_link_libraries(luarocks ixwebsocket)
|
||||
|
||||
install(TARGETS luarocks RUNTIME DESTINATION bin)
|
163
luarocks/LuaWebSocket.h
Normal file
163
luarocks/LuaWebSocket.h
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* LuaWebSocket.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
}
|
||||
|
||||
#include "luawrapper.hpp"
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class LuaWebSocket
|
||||
{
|
||||
public:
|
||||
LuaWebSocket()
|
||||
{
|
||||
_webSocket.setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
|
||||
if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||
_queue.push(msg->str);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setUrl(const std::string& url) { _webSocket.setUrl(url); }
|
||||
const std::string& getUrl() { return _webSocket.getUrl(); }
|
||||
void start() { _webSocket.start(); }
|
||||
void send(const std::string& msg) { _webSocket.send(msg); }
|
||||
|
||||
const std::string& getMessage()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||
return _queue.front();
|
||||
}
|
||||
|
||||
bool hasMessage()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||
return !_queue.empty();
|
||||
}
|
||||
|
||||
void popMessage()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||
_queue.pop();
|
||||
}
|
||||
|
||||
private:
|
||||
WebSocket _webSocket;
|
||||
|
||||
std::queue<std::string> _queue;
|
||||
std::mutex _queueMutex;
|
||||
};
|
||||
|
||||
LuaWebSocket* WebSocket_new(lua_State* /*L*/)
|
||||
{
|
||||
LuaWebSocket* webSocket = new LuaWebSocket();
|
||||
return webSocket;
|
||||
}
|
||||
|
||||
int WebSocket_getUrl(lua_State* L)
|
||||
{
|
||||
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||
lua_pushstring(L, luaWebSocket->getUrl().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WebSocket_setUrl(lua_State* L)
|
||||
{
|
||||
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||
const char* url = luaL_checkstring(L, 2);
|
||||
luaWebSocket->setUrl(url);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocket_start(lua_State* L)
|
||||
{
|
||||
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||
luaWebSocket->start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocket_send(lua_State* L)
|
||||
{
|
||||
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||
const char* msg = luaL_checkstring(L, 2);
|
||||
luaWebSocket->send(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WebSocket_getMessage(lua_State* L)
|
||||
{
|
||||
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||
lua_pushstring(L, luaWebSocket->getMessage().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WebSocket_hasMessage(lua_State* L)
|
||||
{
|
||||
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||
lua_pushboolean(L, luaWebSocket->hasMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WebSocket_popMessage(lua_State* L)
|
||||
{
|
||||
LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||
luaWebSocket->popMessage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// FIXME: This should be a static method, or be part of a different module
|
||||
int WebSocket_sleep(lua_State* L)
|
||||
{
|
||||
// LuaWebSocket* luaWebSocket = luaW_check<LuaWebSocket>(L, 1);
|
||||
auto duration = luaL_checkinteger(L, 2);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static luaL_Reg WebSocket_table[] = {
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static luaL_Reg WebSocket_metatable[] = {
|
||||
{ "getUrl", WebSocket_getUrl },
|
||||
{ "setUrl", WebSocket_setUrl },
|
||||
{ "start", WebSocket_start },
|
||||
{ "send", WebSocket_send },
|
||||
{ "getMessage", WebSocket_getMessage },
|
||||
{ "hasMessage", WebSocket_hasMessage },
|
||||
{ "popMessage", WebSocket_popMessage },
|
||||
{ "sleep", WebSocket_sleep },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int luaopen_WebSocket(lua_State* L)
|
||||
{
|
||||
luaW_register<LuaWebSocket>(L,
|
||||
"WebSocket",
|
||||
WebSocket_table,
|
||||
WebSocket_metatable,
|
||||
WebSocket_new
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
56
luarocks/Player.hpp
Normal file
56
luarocks/Player.hpp
Normal file
@ -0,0 +1,56 @@
|
||||
#ifndef PLAYER_HPP
|
||||
#define PLAYER_HPP
|
||||
|
||||
#include <iostream>
|
||||
|
||||
class Player
|
||||
{
|
||||
public:
|
||||
Player(const char* name, unsigned int health) :
|
||||
m_Name(name),
|
||||
m_Health(health)
|
||||
{
|
||||
}
|
||||
|
||||
void info()
|
||||
{
|
||||
std::cout << m_Name << " have " << m_Health << " HP" << std::endl;
|
||||
}
|
||||
|
||||
void say(const char* text)
|
||||
{
|
||||
std::cout << m_Name << ": " << text << std::endl;
|
||||
}
|
||||
|
||||
void heal(Player* target)
|
||||
{
|
||||
target->setHealth(100);
|
||||
}
|
||||
|
||||
const char* getName()
|
||||
{
|
||||
return m_Name;
|
||||
}
|
||||
|
||||
unsigned int getHealth()
|
||||
{
|
||||
return m_Health;
|
||||
}
|
||||
|
||||
bool setHealth(unsigned int health)
|
||||
{
|
||||
if (health >= 0 && health <= 100)
|
||||
{
|
||||
m_Health = health;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* m_Name;
|
||||
unsigned int m_Health;
|
||||
};
|
||||
|
||||
#endif // PLAYER_HPP
|
7
luarocks/README.md
Normal file
7
luarocks/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
Wrapper based on https://github.com/LuaxY/cpp-lua
|
||||
|
||||
Examples to build C++
|
||||
https://github.com/siffiejoe/lua-fltk4lua/blob/master/fltk4lua-scm-0.rockspec
|
||||
https://github.com/lua4web/refser/blob/master/rockspecs/refser-0.2-1.rockspec
|
||||
|
||||
|
30
luarocks/echo_client.lua
Normal file
30
luarocks/echo_client.lua
Normal file
@ -0,0 +1,30 @@
|
||||
--
|
||||
-- make luarocks && (cd luarocks ; ../build/luarocks/luarocks)
|
||||
--
|
||||
-- ... git push test with ssh key
|
||||
--
|
||||
local webSocket = WebSocket.new()
|
||||
|
||||
webSocket:setUrl("ws://localhost:8008")
|
||||
print("Url: " .. webSocket:getUrl())
|
||||
|
||||
-- Start the background thread
|
||||
webSocket:start()
|
||||
|
||||
local i = 0
|
||||
|
||||
while true
|
||||
do
|
||||
print("Sending message...")
|
||||
webSocket:send("msg_" .. tostring(i));
|
||||
i = i + 1
|
||||
|
||||
print("Waiting 1s...")
|
||||
webSocket:sleep(1000)
|
||||
|
||||
if webSocket:hasMessage() then
|
||||
local msg = webSocket:getMessage()
|
||||
print("Received: " .. msg)
|
||||
webSocket:popMessage()
|
||||
end
|
||||
end
|
15
luarocks/functions.hpp
Normal file
15
luarocks/functions.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* functions.hpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int lua_info(lua_State* /*L*/)
|
||||
{
|
||||
std::cout << "C++ Lua v0.1" << std::endl << std::endl;
|
||||
return 0;
|
||||
}
|
709
luarocks/luawrapper.hpp
Normal file
709
luarocks/luawrapper.hpp
Normal file
@ -0,0 +1,709 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2013 Alexander Ames
|
||||
* Alexander.Ames@gmail.com
|
||||
* See Copyright Notice at the end of this file
|
||||
*/
|
||||
|
||||
// API Summary:
|
||||
//
|
||||
// LuaWrapper is a library designed to help bridge the gab between Lua and
|
||||
// C++. It is designed to be small (a single header file), simple, fast,
|
||||
// and typesafe. It has no external dependencies, and does not need to be
|
||||
// precompiled; the header can simply be dropped into a project and used
|
||||
// immediately. It even supports class inheritance to a certain degree. Objects
|
||||
// can be created in either Lua or C++, and passed back and forth.
|
||||
//
|
||||
// The main functions of interest are the following:
|
||||
// luaW_is<T>
|
||||
// luaW_to<T>
|
||||
// luaW_check<T>
|
||||
// luaW_push<T>
|
||||
// luaW_register<T>
|
||||
// luaW_setfuncs<T>
|
||||
// luaW_extend<T, U>
|
||||
// luaW_hold<T>
|
||||
// luaW_release<T>
|
||||
//
|
||||
// These functions allow you to manipulate arbitrary classes just like you
|
||||
// would the primitive types (e.g. numbers or strings). If you are familiar
|
||||
// with the normal Lua API the behavior of these functions should be very
|
||||
// intuative.
|
||||
//
|
||||
// For more information see the README and the comments below
|
||||
|
||||
#ifndef LUA_WRAPPER_H_
|
||||
#define LUA_WRAPPER_H_
|
||||
|
||||
// If you are linking against Lua compiled in C++, define LUAW_NO_EXTERN_C
|
||||
#ifndef LUAW_NO_EXTERN_C
|
||||
extern "C"
|
||||
{
|
||||
#endif // LUAW_NO_EXTERN_C
|
||||
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
#ifndef LUAW_NO_EXTERN_C
|
||||
}
|
||||
#endif // LUAW_NO_EXTERN_C
|
||||
|
||||
#define LUAW_POSTCTOR_KEY "__postctor"
|
||||
#define LUAW_EXTENDS_KEY "__extends"
|
||||
#define LUAW_STORAGE_KEY "storage"
|
||||
#define LUAW_CACHE_KEY "cache"
|
||||
#define LUAW_CACHE_METATABLE_KEY "cachemetatable"
|
||||
#define LUAW_HOLDS_KEY "holds"
|
||||
#define LUAW_WRAPPER_KEY "LuaWrapper"
|
||||
|
||||
// A simple utility function to adjust a given index
|
||||
// Useful for when a parameter index needs to be adjusted
|
||||
// after pushing or popping things off the stack
|
||||
inline int luaW_correctindex(lua_State* /*L*/, int index, int correction)
|
||||
{
|
||||
return index < 0 ? index - correction : index;
|
||||
}
|
||||
|
||||
// These are the default allocator and deallocator. If you would prefer an
|
||||
// alternative option, you may select a different function when registering
|
||||
// your class.
|
||||
template <typename T>
|
||||
T* luaW_defaultallocator(lua_State*)
|
||||
{
|
||||
return new T();
|
||||
}
|
||||
template <typename T>
|
||||
void luaW_defaultdeallocator(lua_State*, T* obj)
|
||||
{
|
||||
delete obj;
|
||||
}
|
||||
|
||||
// The identifier function is responsible for pushing a value unique to each
|
||||
// object on to the stack. Most of the time, this can simply be the address
|
||||
// of the pointer, but sometimes that is not adaquate. For example, if you
|
||||
// are using shared_ptr you would need to push the address of the object the
|
||||
// shared_ptr represents, rather than the address of the shared_ptr itself.
|
||||
template <typename T>
|
||||
void luaW_defaultidentifier(lua_State* L, T* obj)
|
||||
{
|
||||
lua_pushlightuserdata(L, obj);
|
||||
}
|
||||
|
||||
// This class is what is used by LuaWrapper to contain the userdata. data
|
||||
// stores a pointer to the object itself, and cast is used to cast toward the
|
||||
// base class if there is one and it is necessary. Rather than use RTTI and
|
||||
// typid to compare types, I use the clever trick of using the cast to compare
|
||||
// types. Because there is at most one cast per type, I can use it to identify
|
||||
// when and object is the type I want. This is only used internally.
|
||||
struct luaW_Userdata
|
||||
{
|
||||
luaW_Userdata(void* vptr = NULL, luaW_Userdata (*udcast)(const luaW_Userdata&) = NULL)
|
||||
: data(vptr), cast(udcast) {}
|
||||
void* data;
|
||||
luaW_Userdata (*cast)(const luaW_Userdata&);
|
||||
};
|
||||
|
||||
// This class cannot actually to be instantiated. It is used only hold the
|
||||
// table name and other information.
|
||||
template <typename T>
|
||||
class LuaWrapper
|
||||
{
|
||||
public:
|
||||
static const char* classname;
|
||||
static void (*identifier)(lua_State*, T*);
|
||||
static T* (*allocator)(lua_State*);
|
||||
static void (*deallocator)(lua_State*, T*);
|
||||
static luaW_Userdata (*cast)(const luaW_Userdata&);
|
||||
private:
|
||||
LuaWrapper();
|
||||
};
|
||||
template <typename T> const char* LuaWrapper<T>::classname;
|
||||
template <typename T> void (*LuaWrapper<T>::identifier)(lua_State*, T*);
|
||||
template <typename T> T* (*LuaWrapper<T>::allocator)(lua_State*);
|
||||
template <typename T> void (*LuaWrapper<T>::deallocator)(lua_State*, T*);
|
||||
template <typename T> luaW_Userdata (*LuaWrapper<T>::cast)(const luaW_Userdata&);
|
||||
|
||||
// Cast from an object of type T to an object of type U. This template
|
||||
// function is instantiated by calling luaW_extend<T, U>(L). This is only used
|
||||
// internally.
|
||||
template <typename T, typename U>
|
||||
luaW_Userdata luaW_cast(const luaW_Userdata& obj)
|
||||
{
|
||||
return luaW_Userdata(static_cast<U*>(static_cast<T*>(obj.data)), LuaWrapper<U>::cast);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
void luaW_identify(lua_State* L, T* obj)
|
||||
{
|
||||
LuaWrapper<U>::identifier(L, static_cast<U*>(obj));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void luaW_wrapperfield(lua_State* L, const char* field)
|
||||
{
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... LuaWrapper
|
||||
lua_getfield(L, -1, field); // ... LuaWrapper LuaWrapper.field
|
||||
lua_getfield(L, -1, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.field LuaWrapper.field.class
|
||||
lua_replace(L, -3); // ... LuaWrapper.field.class LuaWrapper.field
|
||||
lua_pop(L, 1); // ... LuaWrapper.field.class
|
||||
}
|
||||
|
||||
// Analogous to lua_is(boolean|string|*)
|
||||
//
|
||||
// Returns 1 if the value at the given acceptable index is of type T (or if
|
||||
// strict is false, convertable to type T) and 0 otherwise.
|
||||
template <typename T>
|
||||
bool luaW_is(lua_State *L, int index, bool strict = false)
|
||||
{
|
||||
bool equal = false;// lua_isnil(L, index);
|
||||
if (!equal && lua_isuserdata(L, index) && lua_getmetatable(L, index))
|
||||
{
|
||||
// ... ud ... udmt
|
||||
luaL_getmetatable(L, LuaWrapper<T>::classname); // ... ud ... udmt Tmt
|
||||
equal = lua_rawequal(L, -1, -2) != 0;
|
||||
if (!equal && !strict)
|
||||
{
|
||||
lua_getfield(L, -2, LUAW_EXTENDS_KEY); // ... ud ... udmt Tmt udmt.extends
|
||||
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1))
|
||||
{
|
||||
// ... ud ... udmt Tmt udmt.extends k v
|
||||
equal = lua_rawequal(L, -1, -4) != 0;
|
||||
if (equal)
|
||||
{
|
||||
lua_pop(L, 2); // ... ud ... udmt Tmt udmt.extends
|
||||
break;
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // ... ud ... udmt Tmt
|
||||
}
|
||||
lua_pop(L, 2); // ... ud ...
|
||||
}
|
||||
return equal;
|
||||
}
|
||||
|
||||
// Analogous to lua_to(boolean|string|*)
|
||||
//
|
||||
// Converts the given acceptable index to a T*. That value must be of (or
|
||||
// convertable to) type T; otherwise, returns NULL.
|
||||
template <typename T>
|
||||
T* luaW_to(lua_State* L, int index, bool strict = false)
|
||||
{
|
||||
if (luaW_is<T>(L, index, strict))
|
||||
{
|
||||
luaW_Userdata* pud = static_cast<luaW_Userdata*>(lua_touserdata(L, index));
|
||||
luaW_Userdata ud;
|
||||
while (!strict && LuaWrapper<T>::cast != pud->cast)
|
||||
{
|
||||
ud = pud->cast(*pud);
|
||||
pud = &ud;
|
||||
}
|
||||
return static_cast<T*>(pud->data);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Analogous to luaL_check(boolean|string|*)
|
||||
//
|
||||
// Converts the given acceptable index to a T*. That value must be of (or
|
||||
// convertable to) type T; otherwise, an error is raised.
|
||||
template <typename T>
|
||||
T* luaW_check(lua_State* L, int index, bool strict = false)
|
||||
{
|
||||
T* obj = NULL;
|
||||
if (luaW_is<T>(L, index, strict))
|
||||
{
|
||||
luaW_Userdata* pud = (luaW_Userdata*)lua_touserdata(L, index);
|
||||
luaW_Userdata ud;
|
||||
while (!strict && LuaWrapper<T>::cast != pud->cast)
|
||||
{
|
||||
ud = pud->cast(*pud);
|
||||
pud = &ud;
|
||||
}
|
||||
obj = (T*)pud->data;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *msg = lua_pushfstring(L, "%s expected, got %s", LuaWrapper<T>::classname, luaL_typename(L, index));
|
||||
luaL_argerror(L, index, msg);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* luaW_opt(lua_State* L, int index, T* fallback = NULL, bool strict = false)
|
||||
{
|
||||
if (lua_isnil(L, index))
|
||||
return fallback;
|
||||
else
|
||||
return luaW_check<T>(L, index, strict);
|
||||
}
|
||||
|
||||
// Analogous to lua_push(boolean|string|*)
|
||||
//
|
||||
// Pushes a userdata of type T onto the stack. If this object already exists in
|
||||
// the Lua environment, it will assign the existing storage table to it.
|
||||
// Otherwise, a new storage table will be created for it.
|
||||
template <typename T>
|
||||
void luaW_push(lua_State* L, T* obj)
|
||||
{
|
||||
if (obj)
|
||||
{
|
||||
LuaWrapper<T>::identifier(L, obj); // ... id
|
||||
luaW_wrapperfield<T>(L, LUAW_CACHE_KEY); // ... id cache
|
||||
lua_pushvalue(L, -2); // ... id cache id
|
||||
lua_gettable(L, -2); // ... id cache obj
|
||||
if (lua_isnil(L, -1))
|
||||
{
|
||||
// Create the new luaW_userdata and place it in the cache
|
||||
lua_pop(L, 1); // ... id cache
|
||||
lua_insert(L, -2); // ... cache id
|
||||
luaW_Userdata* ud = static_cast<luaW_Userdata*>(lua_newuserdata(L, sizeof(luaW_Userdata))); // ... cache id obj
|
||||
ud->data = obj;
|
||||
ud->cast = LuaWrapper<T>::cast;
|
||||
lua_pushvalue(L, -1); // ... cache id obj obj
|
||||
lua_insert(L, -4); // ... obj cache id obj
|
||||
lua_settable(L, -3); // ... obj cache
|
||||
|
||||
luaL_getmetatable(L, LuaWrapper<T>::classname); // ... obj cache mt
|
||||
lua_setmetatable(L, -3); // ... obj cache
|
||||
|
||||
lua_pop(L, 1); // ... obj
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_replace(L, -3); // ... obj cache
|
||||
lua_pop(L, 1); // ... obj
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pushnil(L);
|
||||
}
|
||||
}
|
||||
|
||||
// Instructs LuaWrapper that it owns the userdata, and can manage its memory.
|
||||
// When all references to the object are removed, Lua is free to garbage
|
||||
// collect it and delete the object.
|
||||
//
|
||||
// Returns true if luaW_hold took hold of the object, and false if it was
|
||||
// already held
|
||||
template <typename T>
|
||||
bool luaW_hold(lua_State* L, T* obj)
|
||||
{
|
||||
luaW_wrapperfield<T>(L, LUAW_HOLDS_KEY); // ... holds
|
||||
LuaWrapper<T>::identifier(L, obj); // ... holds id
|
||||
lua_pushvalue(L, -1); // ... holds id id
|
||||
lua_gettable(L, -3); // ... holds id hold
|
||||
// If it's not held, hold it
|
||||
if (!lua_toboolean(L, -1))
|
||||
{
|
||||
// Apply hold boolean
|
||||
lua_pop(L, 1); // ... holds id
|
||||
lua_pushboolean(L, true); // ... holds id true
|
||||
lua_settable(L, -3); // ... holds
|
||||
lua_pop(L, 1); // ...
|
||||
return true;
|
||||
}
|
||||
lua_pop(L, 3); // ...
|
||||
return false;
|
||||
}
|
||||
|
||||
// Releases LuaWrapper's hold on an object. This allows the user to remove
|
||||
// all references to an object in Lua and ensure that Lua will not attempt to
|
||||
// garbage collect it.
|
||||
//
|
||||
// This function takes the index of the identifier for an object rather than
|
||||
// the object itself. This is because needs to be able to run after the object
|
||||
// has already been deallocated. A wrapper is provided for when it is more
|
||||
// convenient to pass in the object directly.
|
||||
template <typename T>
|
||||
void luaW_release(lua_State* L, int index)
|
||||
{
|
||||
luaW_wrapperfield<T>(L, LUAW_HOLDS_KEY); // ... id ... holds
|
||||
lua_pushvalue(L, luaW_correctindex(L, index, 1)); // ... id ... holds id
|
||||
lua_pushnil(L); // ... id ... holds id nil
|
||||
lua_settable(L, -3); // ... id ... holds
|
||||
lua_pop(L, 1); // ... id ...
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void luaW_release(lua_State* L, T* obj)
|
||||
{
|
||||
LuaWrapper<T>::identifier(L, obj); // ... id
|
||||
luaW_release<T>(L, -1); // ... id
|
||||
lua_pop(L, 1); // ...
|
||||
}
|
||||
|
||||
// This function is called from Lua, not C++
|
||||
//
|
||||
// Calls the lua post-constructor (LUAW_POSTCTOR_KEY or "__postctor") on a
|
||||
// userdata. Assumes the userdata is on top of the stack, and numargs arguments
|
||||
// are below it. This runs the LUAW_POSTCTOR_KEY function on T's metatable,
|
||||
// using the object as the first argument and whatever else is below it as
|
||||
// the rest of the arguments This exists to allow types to adjust values in
|
||||
// thier storage table, which can not be created until after the constructor is
|
||||
// called.
|
||||
template <typename T>
|
||||
void luaW_postconstructor(lua_State* L, int numargs)
|
||||
{
|
||||
// ... args... ud
|
||||
lua_getfield(L, -1, LUAW_POSTCTOR_KEY); // ... args... ud ud.__postctor
|
||||
if (lua_type(L, -1) == LUA_TFUNCTION)
|
||||
{
|
||||
lua_pushvalue(L, -2); // ... args... ud ud.__postctor ud
|
||||
lua_insert(L, -3 - numargs); // ... ud args... ud ud.__postctor
|
||||
lua_insert(L, -3 - numargs); // ... ud.__postctor ud args... ud
|
||||
lua_insert(L, -3 - numargs); // ... ud ud.__postctor ud args...
|
||||
lua_call(L, numargs + 1, 0); // ... ud
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pop(L, 1); // ... ud
|
||||
}
|
||||
}
|
||||
|
||||
// This function is generally called from Lua, not C++
|
||||
//
|
||||
// Creates an object of type T using the constructor and subsequently calls the
|
||||
// post-constructor on it.
|
||||
template <typename T>
|
||||
inline int luaW_new(lua_State* L, int args)
|
||||
{
|
||||
T* obj = LuaWrapper<T>::allocator(L);
|
||||
luaW_push<T>(L, obj);
|
||||
luaW_hold<T>(L, obj);
|
||||
luaW_postconstructor<T>(L, args);
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int luaW_new(lua_State* L)
|
||||
{
|
||||
return luaW_new<T>(L, lua_gettop(L));
|
||||
}
|
||||
|
||||
// This function is called from Lua, not C++
|
||||
//
|
||||
// The default metamethod to call when indexing into lua userdata representing
|
||||
// an object of type T. This will first check the userdata's environment table
|
||||
// and if it's not found there it will check the metatable. This is done so
|
||||
// individual userdata can be treated as a table, and can hold thier own
|
||||
// values.
|
||||
template <typename T>
|
||||
int luaW_index(lua_State* L)
|
||||
{
|
||||
// obj key
|
||||
T* obj = luaW_to<T>(L, 1);
|
||||
luaW_wrapperfield<T>(L, LUAW_STORAGE_KEY); // obj key storage
|
||||
LuaWrapper<T>::identifier(L, obj); // obj key storage id
|
||||
lua_gettable(L, -2); // obj key storage store
|
||||
|
||||
// Check if storage table exists
|
||||
if (!lua_isnil(L, -1))
|
||||
{
|
||||
lua_pushvalue(L, -3); // obj key storage store key
|
||||
lua_gettable(L, -2); // obj key storage store store[k]
|
||||
}
|
||||
|
||||
// If either there is no storage table or the key wasn't found
|
||||
// then fall back to the metatable
|
||||
if (lua_isnil(L, -1))
|
||||
{
|
||||
lua_settop(L, 2); // obj key
|
||||
lua_getmetatable(L, -2); // obj key mt
|
||||
lua_pushvalue(L, -2); // obj key mt k
|
||||
lua_gettable(L, -2); // obj key mt mt[k]
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This function is called from Lua, not C++
|
||||
//
|
||||
// The default metamethod to call when creating a new index on lua userdata
|
||||
// representing an object of type T. This will index into the the userdata's
|
||||
// environment table that it keeps for personal storage. This is done so
|
||||
// individual userdata can be treated as a table, and can hold thier own
|
||||
// values.
|
||||
template <typename T>
|
||||
int luaW_newindex(lua_State* L)
|
||||
{
|
||||
// obj key value
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
luaW_wrapperfield<T>(L, LUAW_STORAGE_KEY); // obj key value storage
|
||||
LuaWrapper<T>::identifier(L, obj); // obj key value storage id
|
||||
lua_pushvalue(L, -1); // obj key value storage id id
|
||||
lua_gettable(L, -3); // obj key value storage id store
|
||||
|
||||
// Add the storage table if there isn't one already
|
||||
if (lua_isnil(L, -1))
|
||||
{
|
||||
lua_pop(L, 1); // obj key value storage id
|
||||
lua_newtable(L); // obj key value storage id store
|
||||
lua_pushvalue(L, -1); // obj key value storage id store store
|
||||
lua_insert(L, -3); // obj key value storage store id store
|
||||
lua_settable(L, -4); // obj key value storage store
|
||||
}
|
||||
|
||||
lua_pushvalue(L, 2); // obj key value ... store key
|
||||
lua_pushvalue(L, 3); // obj key value ... store key value
|
||||
lua_settable(L, -3); // obj key value ... store
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function is called from Lua, not C++
|
||||
//
|
||||
// The __gc metamethod handles cleaning up userdata. The userdata's reference
|
||||
// count is decremented and if this is the final reference to the userdata its
|
||||
// environment table is nil'd and pointer deleted with the destructor callback.
|
||||
template <typename T>
|
||||
int luaW_gc(lua_State* L)
|
||||
{
|
||||
// obj
|
||||
T* obj = luaW_to<T>(L, 1);
|
||||
LuaWrapper<T>::identifier(L, obj); // obj key value storage id
|
||||
luaW_wrapperfield<T>(L, LUAW_HOLDS_KEY); // obj id counts count holds
|
||||
lua_pushvalue(L, 2); // obj id counts count holds id
|
||||
lua_gettable(L, -2); // obj id counts count holds hold
|
||||
if (lua_toboolean(L, -1) && LuaWrapper<T>::deallocator)
|
||||
{
|
||||
LuaWrapper<T>::deallocator(L, obj);
|
||||
}
|
||||
|
||||
luaW_wrapperfield<T>(L, LUAW_STORAGE_KEY); // obj id counts count holds hold storage
|
||||
lua_pushvalue(L, 2); // obj id counts count holds hold storage id
|
||||
lua_pushnil(L); // obj id counts count holds hold storage id nil
|
||||
lua_settable(L, -3); // obj id counts count holds hold storage
|
||||
|
||||
luaW_release<T>(L, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Thakes two tables and registers them with Lua to the table on the top of the
|
||||
// stack.
|
||||
//
|
||||
// This function is only called from LuaWrapper internally.
|
||||
inline void luaW_registerfuncs(lua_State* L, const luaL_Reg defaulttable[], const luaL_Reg table[])
|
||||
{
|
||||
// ... T
|
||||
#if LUA_VERSION_NUM > 501
|
||||
if (defaulttable)
|
||||
luaL_setfuncs(L, defaulttable, 0); // ... T
|
||||
if (table)
|
||||
luaL_setfuncs(L, table, 0); // ... T
|
||||
#else
|
||||
if (defaulttable)
|
||||
luaL_register(L, NULL, defaulttable); // ... T
|
||||
if (table)
|
||||
luaL_register(L, NULL, table); // ... T
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initializes the LuaWrapper tables used to track internal state.
|
||||
//
|
||||
// This function is only called from LuaWrapper internally.
|
||||
inline void luaW_initialize(lua_State* L)
|
||||
{
|
||||
// Ensure that the LuaWrapper table is set up
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... LuaWrapper
|
||||
if (lua_isnil(L, -1))
|
||||
{
|
||||
lua_newtable(L); // ... nil {}
|
||||
lua_pushvalue(L, -1); // ... nil {} {}
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... nil LuaWrapper
|
||||
|
||||
// Create a storage table
|
||||
lua_newtable(L); // ... LuaWrapper nil {}
|
||||
lua_setfield(L, -2, LUAW_STORAGE_KEY); // ... nil LuaWrapper
|
||||
|
||||
// Create a holds table
|
||||
lua_newtable(L); // ... LuaWrapper {}
|
||||
lua_setfield(L, -2, LUAW_HOLDS_KEY); // ... nil LuaWrapper
|
||||
|
||||
// Create a cache table, with weak values so that the userdata will not
|
||||
// be ref counted
|
||||
lua_newtable(L); // ... nil LuaWrapper {}
|
||||
lua_setfield(L, -2, LUAW_CACHE_KEY); // ... nil LuaWrapper
|
||||
|
||||
lua_newtable(L); // ... nil LuaWrapper {}
|
||||
lua_pushstring(L, "v"); // ... nil LuaWrapper {} "v"
|
||||
lua_setfield(L, -2, "__mode"); // ... nil LuaWrapper {}
|
||||
lua_setfield(L, -2, LUAW_CACHE_METATABLE_KEY); // ... nil LuaWrapper
|
||||
|
||||
lua_pop(L, 1); // ... nil
|
||||
}
|
||||
lua_pop(L, 1); // ...
|
||||
}
|
||||
|
||||
// Run luaW_register or luaW_setfuncs to create a table and metatable for your
|
||||
// class. These functions create a table with filled with the function from
|
||||
// the table argument in addition to the functions new and build (This is
|
||||
// generally for things you think of as static methods in C++). The given
|
||||
// metatable argument becomes a metatable for each object of your class. These
|
||||
// can be thought of as member functions or methods.
|
||||
//
|
||||
// You may also supply constructors and destructors for classes that do not
|
||||
// have a default constructor or that require special set up or tear down. You
|
||||
// may specify NULL as the constructor, which means that you will not be able
|
||||
// to call the new function on your class table. You will need to manually push
|
||||
// objects from C++. By default, the default constructor is used to create
|
||||
// objects and a simple call to delete is used to destroy them.
|
||||
//
|
||||
// By default LuaWrapper uses the address of C++ object to identify unique
|
||||
// objects. In some cases this is not desired, such as in the case of
|
||||
// shared_ptrs. Two shared_ptrs may themselves have unique locations in memory
|
||||
// but still represent the same object. For cases like that, you may specify an
|
||||
// identifier function which is responsible for pushing a key representing your
|
||||
// object on to the stack.
|
||||
//
|
||||
// luaW_register will set table as the new value of the global of the given
|
||||
// name. luaW_setfuncs is identical to luaW_register, but it does not set the
|
||||
// table globally. As with luaL_register and luaL_setfuncs, both funcstions
|
||||
// leave the new table on the top of the stack.
|
||||
template <typename T>
|
||||
void luaW_setfuncs(lua_State* L, const char* classname, const luaL_Reg* table, const luaL_Reg* metatable, T* (*allocator)(lua_State*) = luaW_defaultallocator<T>, void (*deallocator)(lua_State*, T*) = luaW_defaultdeallocator<T>, void (*identifier)(lua_State*, T*) = luaW_defaultidentifier<T>)
|
||||
{
|
||||
luaW_initialize(L);
|
||||
|
||||
LuaWrapper<T>::classname = classname;
|
||||
LuaWrapper<T>::identifier = identifier;
|
||||
LuaWrapper<T>::allocator = allocator;
|
||||
LuaWrapper<T>::deallocator = deallocator;
|
||||
|
||||
const luaL_Reg defaulttable[] =
|
||||
{
|
||||
{ "new", luaW_new<T> },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
const luaL_Reg defaultmetatable[] =
|
||||
{
|
||||
{ "__index", luaW_index<T> },
|
||||
{ "__newindex", luaW_newindex<T> },
|
||||
{ "__gc", luaW_gc<T> },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
// Set up per-type tables
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... LuaWrapper
|
||||
|
||||
lua_getfield(L, -1, LUAW_STORAGE_KEY); // ... LuaWrapper LuaWrapper.storage
|
||||
lua_newtable(L); // ... LuaWrapper LuaWrapper.storage {}
|
||||
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.storage
|
||||
lua_pop(L, 1); // ... LuaWrapper
|
||||
|
||||
lua_getfield(L, -1, LUAW_HOLDS_KEY); // ... LuaWrapper LuaWrapper.holds
|
||||
lua_newtable(L); // ... LuaWrapper LuaWrapper.holds {}
|
||||
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.holds
|
||||
lua_pop(L, 1); // ... LuaWrapper
|
||||
|
||||
lua_getfield(L, -1, LUAW_CACHE_KEY); // ... LuaWrapper LuaWrapper.cache
|
||||
lua_newtable(L); // ... LuaWrapper LuaWrapper.cache {}
|
||||
luaW_wrapperfield<T>(L, LUAW_CACHE_METATABLE_KEY); // ... LuaWrapper LuaWrapper.cache {} cmt
|
||||
lua_setmetatable(L, -2); // ... LuaWrapper LuaWrapper.cache {}
|
||||
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.cache
|
||||
|
||||
lua_pop(L, 2); // ...
|
||||
|
||||
// Open table
|
||||
lua_newtable(L); // ... T
|
||||
luaW_registerfuncs(L, allocator ? defaulttable : NULL, table); // ... T
|
||||
|
||||
// Open metatable, set up extends table
|
||||
luaL_newmetatable(L, classname); // ... T mt
|
||||
lua_newtable(L); // ... T mt {}
|
||||
lua_setfield(L, -2, LUAW_EXTENDS_KEY); // ... T mt
|
||||
luaW_registerfuncs(L, defaultmetatable, metatable); // ... T mt
|
||||
lua_setfield(L, -2, "metatable"); // ... T
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void luaW_register(lua_State* L, const char* classname, const luaL_Reg* table, const luaL_Reg* metatable, T* (*allocator)(lua_State*) = luaW_defaultallocator<T>, void (*deallocator)(lua_State*, T*) = luaW_defaultdeallocator<T>, void (*identifier)(lua_State*, T*) = luaW_defaultidentifier<T>)
|
||||
{
|
||||
luaW_setfuncs(L, classname, table, metatable, allocator, deallocator, identifier); // ... T
|
||||
lua_pushvalue(L, -1); // ... T T
|
||||
lua_setglobal(L, classname); // ... T
|
||||
}
|
||||
|
||||
// luaW_extend is used to declare that class T inherits from class U. All
|
||||
// functions in the base class will be available to the derived class (except
|
||||
// when they share a function name, in which case the derived class's function
|
||||
// wins). This also allows luaW_to<T> to cast your object apropriately, as
|
||||
// casts straight through a void pointer do not work.
|
||||
template <typename T, typename U>
|
||||
void luaW_extend(lua_State* L)
|
||||
{
|
||||
if(!LuaWrapper<T>::classname)
|
||||
luaL_error(L, "attempting to call extend on a type that has not been registered");
|
||||
|
||||
if(!LuaWrapper<U>::classname)
|
||||
luaL_error(L, "attempting to extend %s by a type that has not been registered", LuaWrapper<T>::classname);
|
||||
|
||||
LuaWrapper<T>::cast = luaW_cast<T, U>;
|
||||
LuaWrapper<T>::identifier = luaW_identify<T, U>;
|
||||
|
||||
luaL_getmetatable(L, LuaWrapper<T>::classname); // mt
|
||||
luaL_getmetatable(L, LuaWrapper<U>::classname); // mt emt
|
||||
|
||||
// Point T's metatable __index at U's metatable for inheritance
|
||||
lua_newtable(L); // mt emt {}
|
||||
lua_pushvalue(L, -2); // mt emt {} emt
|
||||
lua_setfield(L, -2, "__index"); // mt emt {}
|
||||
lua_setmetatable(L, -3); // mt emt
|
||||
|
||||
// Set up per-type tables to point at parent type
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, LUAW_WRAPPER_KEY); // ... LuaWrapper
|
||||
|
||||
lua_getfield(L, -1, LUAW_STORAGE_KEY); // ... LuaWrapper LuaWrapper.storage
|
||||
lua_getfield(L, -1, LuaWrapper<U>::classname); // ... LuaWrapper LuaWrapper.storage U
|
||||
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.storage
|
||||
lua_pop(L, 1); // ... LuaWrapper
|
||||
|
||||
lua_getfield(L, -1, LUAW_HOLDS_KEY); // ... LuaWrapper LuaWrapper.holds
|
||||
lua_getfield(L, -1, LuaWrapper<U>::classname); // ... LuaWrapper LuaWrapper.holds U
|
||||
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.holds
|
||||
lua_pop(L, 1); // ... LuaWrapper
|
||||
|
||||
lua_getfield(L, -1, LUAW_CACHE_KEY); // ... LuaWrapper LuaWrapper.cache
|
||||
lua_getfield(L, -1, LuaWrapper<U>::classname); // ... LuaWrapper LuaWrapper.cache U
|
||||
lua_setfield(L, -2, LuaWrapper<T>::classname); // ... LuaWrapper LuaWrapper.cache
|
||||
|
||||
lua_pop(L, 2); // ...
|
||||
|
||||
// Make a list of all types that inherit from U, for type checking
|
||||
lua_getfield(L, -2, LUAW_EXTENDS_KEY); // mt emt mt.extends
|
||||
lua_pushvalue(L, -2); // mt emt mt.extends emt
|
||||
lua_setfield(L, -2, LuaWrapper<U>::classname); // mt emt mt.extends
|
||||
lua_getfield(L, -2, LUAW_EXTENDS_KEY); // mt emt mt.extends emt.extends
|
||||
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1))
|
||||
{
|
||||
// mt emt mt.extends emt.extends k v
|
||||
lua_pushvalue(L, -2); // mt emt mt.extends emt.extends k v k
|
||||
lua_pushvalue(L, -2); // mt emt mt.extends emt.extends k v k
|
||||
lua_rawset(L, -6); // mt emt mt.extends emt.extends k v
|
||||
}
|
||||
|
||||
lua_pop(L, 4); // mt emt
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (c) 2010-2013 Alexander Ames
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#endif // LUA_WRAPPER_H_
|
714
luarocks/luawrapperutil.hpp
Normal file
714
luarocks/luawrapperutil.hpp
Normal file
@ -0,0 +1,714 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2013 Alexander Ames
|
||||
* Alexander.Ames@gmail.com
|
||||
* See Copyright Notice at the end of this file
|
||||
*/
|
||||
|
||||
// API Summary:
|
||||
//
|
||||
// This is a set of utility functions that add to the functionalit of
|
||||
// LuaWrapper. Over time I found myself repeating a few patterns, such as
|
||||
// writing many trvial getter and setter functions, and wanting pass ownership
|
||||
// of one object to another and have the Lua script properly handle that case.
|
||||
//
|
||||
// This file contains the additional functions that I've added but that do
|
||||
// not really belong in the core API.
|
||||
|
||||
#ifndef LUAWRAPPERUTILS_HPP_
|
||||
#define LUAWRAPPERUTILS_HPP_
|
||||
|
||||
#include "luawrapper.hpp"
|
||||
|
||||
#ifndef LUAW_NO_CXX11
|
||||
#include <type_traits>
|
||||
#endif
|
||||
|
||||
#ifndef LUAW_STD
|
||||
#define LUAW_STD std
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// A set of templated luaL_check and lua_push functions for use in the getters
|
||||
// and setters below
|
||||
//
|
||||
// It is often useful to override luaU_check, luaU_to and/or luaU_push to
|
||||
// operate on your own simple types rather than register your type with
|
||||
// LuaWrapper, especially with small objects.
|
||||
//
|
||||
|
||||
template<typename U, typename = void>
|
||||
struct luaU_Impl
|
||||
{
|
||||
static U luaU_check(lua_State* L, int index);
|
||||
static U luaU_to (lua_State* L, int index);
|
||||
static void luaU_push (lua_State* L, const U& value);
|
||||
static void luaU_push (lua_State* L, U& value);
|
||||
};
|
||||
|
||||
template<typename U> U luaU_check(lua_State* L, int index) { return luaU_Impl<U>::luaU_check(L, index); }
|
||||
template<typename U> U luaU_to (lua_State* L, int index) { return luaU_Impl<U>::luaU_to (L, index); }
|
||||
template<typename U> void luaU_push (lua_State* L, const U& value) { luaU_Impl<U>::luaU_push (L, value); }
|
||||
template<typename U> void luaU_push (lua_State* L, U& value) { luaU_Impl<U>::luaU_push (L, value); }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This is slightly different than the previous three functions in that you
|
||||
// shouldn't need to write your own version of it, since it uses luaU_check
|
||||
// automatically.
|
||||
//
|
||||
template<typename U>
|
||||
U luaU_opt(lua_State* L, int index, const U& fallback = U())
|
||||
{
|
||||
if (lua_isnil(L, index))
|
||||
return fallback;
|
||||
else
|
||||
return luaU_Impl<U>::luaU_check(L, index);
|
||||
}
|
||||
|
||||
template<>
|
||||
struct luaU_Impl<bool>
|
||||
{
|
||||
static bool luaU_check(lua_State* L, int index) { return lua_toboolean (L, index) != 0; }
|
||||
static bool luaU_to (lua_State* L, int index) { return lua_toboolean (L, index) != 0; }
|
||||
static void luaU_push (lua_State* L, const bool& value) { lua_pushboolean(L, value); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct luaU_Impl<const char*>
|
||||
{
|
||||
static const char* luaU_check(lua_State* L, int index) { return luaL_checkstring(L, index); }
|
||||
static const char* luaU_to (lua_State* L, int index) { return lua_tostring (L, index); }
|
||||
static void luaU_push (lua_State* L, const char* const& value) { lua_pushstring (L, value); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct luaU_Impl<unsigned int>
|
||||
{
|
||||
static unsigned int luaU_check(lua_State* L, int index) { return static_cast<unsigned int>(luaL_checkinteger(L, index)); }
|
||||
static unsigned int luaU_to (lua_State* L, int index) { return static_cast<unsigned int>(lua_tointeger (L, index)); }
|
||||
static void luaU_push (lua_State* L, const unsigned int& value) { lua_pushinteger (L, value); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct luaU_Impl<int>
|
||||
{
|
||||
static int luaU_check(lua_State* L, int index) { return static_cast<int>(luaL_checkinteger(L, index)); }
|
||||
static int luaU_to (lua_State* L, int index) { return static_cast<int>(lua_tointeger (L, index)); }
|
||||
static void luaU_push (lua_State* L, const int& value) { lua_pushinteger (L, value); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct luaU_Impl<unsigned char>
|
||||
{
|
||||
static unsigned char luaU_check(lua_State* L, int index) { return static_cast<unsigned char>(luaL_checkinteger(L, index)); }
|
||||
static unsigned char luaU_to (lua_State* L, int index) { return static_cast<unsigned char>(lua_tointeger (L, index)); }
|
||||
static void luaU_push (lua_State* L, const unsigned char& value) { lua_pushinteger (L, value); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct luaU_Impl<char>
|
||||
{
|
||||
static char luaU_check(lua_State* L, int index) { return static_cast<char>(luaL_checkinteger(L, index)); }
|
||||
static char luaU_to (lua_State* L, int index) { return static_cast<char>(lua_tointeger (L, index)); }
|
||||
static void luaU_push (lua_State* L, const char& value) { lua_pushinteger (L, value); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct luaU_Impl<float>
|
||||
{
|
||||
static float luaU_check(lua_State* L, int index) { return static_cast<float>(luaL_checknumber(L, index)); }
|
||||
static float luaU_to (lua_State* L, int index) { return static_cast<float>(lua_tonumber (L, index)); }
|
||||
static void luaU_push (lua_State* L, const float& value) { lua_pushnumber (L, value); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct luaU_Impl<double>
|
||||
{
|
||||
static double luaU_check(lua_State* L, int index) { return static_cast<double>(luaL_checknumber(L, index)); }
|
||||
static double luaU_to (lua_State* L, int index) { return static_cast<double>(lua_tonumber (L, index)); }
|
||||
static void luaU_push (lua_State* L, const double& value) { lua_pushnumber (L, value); }
|
||||
};
|
||||
|
||||
#ifndef LUAW_NO_CXX11
|
||||
template<typename T>
|
||||
struct luaU_Impl<T, typename LUAW_STD::enable_if<LUAW_STD::is_enum<T>::value>::type>
|
||||
{
|
||||
static T luaU_check(lua_State* L, int index) { return static_cast<T>(luaL_checkinteger (L, index)); }
|
||||
static T luaU_to (lua_State* L, int index) { return static_cast<T>(lua_tointeger (L, index)); }
|
||||
static void luaU_push (lua_State* L, const T& value) { lua_pushnumber(L, static_cast<int>(value )); }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct luaU_Impl<T*, typename LUAW_STD::enable_if<LUAW_STD::is_class<T>::value>::type>
|
||||
{
|
||||
static T* luaU_check( lua_State* L, int index) { return luaW_check<T>(L, index); }
|
||||
static T* luaU_to ( lua_State* L, int index) { return luaW_to <T>(L, index); }
|
||||
static void luaU_push ( lua_State* L, T*& value) { luaW_push <T>(L, value); }
|
||||
static void luaU_push ( lua_State* L, T* value) { luaW_push <T>(L, value); }
|
||||
};
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// These are just some functions I've always felt should exist
|
||||
//
|
||||
|
||||
template <typename U>
|
||||
inline U luaU_getfield(lua_State* L, int index, const char* field)
|
||||
{
|
||||
#ifndef LUAW_NO_CXX11
|
||||
static_assert(!std::is_same<U, const char*>::value,
|
||||
"luaU_getfield is not safe to use on const char*'s. (The string will be popped from the stack.)");
|
||||
#endif
|
||||
lua_getfield(L, index, field);
|
||||
U val = luaU_to<U>(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
inline U luaU_checkfield(lua_State* L, int index, const char* field)
|
||||
{
|
||||
#ifndef LUAW_NO_CXX11
|
||||
static_assert(!std::is_same<U, const char*>::value,
|
||||
"luaU_checkfield is not safe to use on const char*'s. (The string will be popped from the stack.)");
|
||||
#endif
|
||||
lua_getfield(L, index, field);
|
||||
U val = luaU_check<U>(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
inline void luaU_setfield(lua_State* L, int index, const char* field, U val)
|
||||
{
|
||||
luaU_push<U>(L, val);
|
||||
lua_setfield(L, luaW_correctindex(L, index, 1), field);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// A set of trivial getter and setter templates. These templates are designed
|
||||
// to call trivial getters or setters.
|
||||
//
|
||||
// There are four forms:
|
||||
// 1. Getting or setting a public member variable that is of a primitive type
|
||||
// 2. Getting or setting a public member variable that is a pointer to an
|
||||
// object
|
||||
// 3. Getting or setting a private member variable that is of a primitive type
|
||||
// through a getter or setter
|
||||
// 3. Getting or setting a private member variable that is is a pointer to an
|
||||
// object through a getter or setter
|
||||
//
|
||||
// The interface to all of them is the same however. In addition to plain
|
||||
// getter and setter functions, there is a getset which does both - if an
|
||||
// argument is supplied it attempts to set the value, and in either case it
|
||||
// returns the value. In your lua table declaration in C++ rather than write
|
||||
// individiual wrappers for each getter and setter you may do the following:
|
||||
//
|
||||
// static luaL_reg Foo_metatable[] =
|
||||
// {
|
||||
// { "GetBar", luaU_get<Foo, bool, &Widget::GetBar> },
|
||||
// { "SetBar", luaU_set<Foo, bool, &Widget::SetBar> },
|
||||
// { "Bar", luaU_getset<Foo, bool, &Widget::GetBar, &Widget::SetBar> },
|
||||
// { NULL, NULL }
|
||||
// }
|
||||
//
|
||||
// Getters and setters must have one of the following signatures:
|
||||
// void T::Setter(U value);
|
||||
// void T::Setter(U* value);
|
||||
// void T::Setter(const U& value);
|
||||
// U Getter() const;
|
||||
// U* Getter() const;
|
||||
//
|
||||
// In this example, the variable Foo::bar is private and so it is accessed
|
||||
// through getter and setter functions. If Foo::bar were public, it could be
|
||||
// accessed directly, like so:
|
||||
//
|
||||
// static luaL_reg Foo_metatable[] =
|
||||
// {
|
||||
// { "GetBar", luaU_get<Foo, bool, &Widget::bar> },
|
||||
// { "SetBar", luaU_set<Foo, bool, &Widget::bar> },
|
||||
// { "Bar", luaU_getset<Foo, bool, &Widget::bar> },
|
||||
// }
|
||||
//
|
||||
// In a Lua script, you can now use foo:GetBar(), foo:SetBar() and foo:Bar()
|
||||
//
|
||||
|
||||
template <typename T, typename U, U T::*Member>
|
||||
int luaU_get(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
luaU_push<U>(L, obj->*Member);
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename T, typename U, U* T::*Member>
|
||||
int luaU_get(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
luaW_push<U>(L, obj->*Member);
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename T, typename U, U (T::*Getter)() const>
|
||||
int luaU_get(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
luaU_push<U>(L, (obj->*Getter)());
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename T, typename U, const U& (T::*Getter)() const>
|
||||
int luaU_get(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
luaU_push<U>(L, (obj->*Getter)());
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename T, typename U, U* (T::*Getter)() const>
|
||||
int luaU_get(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
luaW_push<U>(L, (obj->*Getter)());
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename T, typename U, U T::*Member>
|
||||
int luaU_set(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj)
|
||||
obj->*Member = luaU_check<U>(L, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T, typename U, U* T::*Member>
|
||||
int luaU_set(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
obj->*Member = member;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T, typename U, const U* T::*Member>
|
||||
int luaU_set(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
obj->*Member = member;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T, typename U, const U* T::*Member>
|
||||
int luaU_setandrelease(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
obj->*Member = member;
|
||||
if (member)
|
||||
luaW_release<U>(L, member);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T, typename U, void (T::*Setter)(U)>
|
||||
int luaU_set(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj)
|
||||
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T, typename U, void (T::*Setter)(const U&)>
|
||||
int luaU_set(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj)
|
||||
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T, typename U, void (T::*Setter)(U*)>
|
||||
int luaU_set(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
(obj->*Setter)(member);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T, typename U, void (T::*Setter)(U*)>
|
||||
int luaU_setandrelease(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
(obj->*Setter)(member);
|
||||
if (member)
|
||||
luaW_release<U>(L, member);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T, typename U, U T::*Member>
|
||||
int luaU_getset(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj && lua_gettop(L) >= 2)
|
||||
{
|
||||
obj->*Member = luaU_check<U>(L, 2);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaU_push<U>(L, obj->*Member);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U, U* T::*Member>
|
||||
int luaU_getset(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj && lua_gettop(L) >= 2)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
obj->*Member = member;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaW_push<U>(L, obj->*Member);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U, U* T::*Member>
|
||||
int luaU_getsetandrelease(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj && lua_gettop(L) >= 2)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
obj->*Member = member;
|
||||
if (member)
|
||||
luaW_release<U>(L, member);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaW_push<U>(L, obj->*Member);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U, U (T::*Getter)() const, void (T::*Setter)(U)>
|
||||
int luaU_getset(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj && lua_gettop(L) >= 2)
|
||||
{
|
||||
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaU_push<U>(L, (obj->*Getter)());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U, U (T::*Getter)() const, void (T::*Setter)(const U&)>
|
||||
int luaU_getset(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj && lua_gettop(L) >= 2)
|
||||
{
|
||||
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaU_push<U>(L, (obj->*Getter)());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U, const U& (T::*Getter)() const, void (T::*Setter)(const U&)>
|
||||
int luaU_getset(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj && lua_gettop(L) >= 2)
|
||||
{
|
||||
(obj->*Setter)(luaU_check<U>(L, 2));
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaU_push<U>(L, (obj->*Getter)());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U, U* (T::*Getter)() const, void (T::*Setter)(U*)>
|
||||
int luaU_getset(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj && lua_gettop(L) >= 2)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
(obj->*Setter)(member);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaW_push<U>(L, (obj->*Getter)());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U, U* (T::*Getter)() const, void (T::*Setter)(U*)>
|
||||
int luaU_getsetandrelease(lua_State* L)
|
||||
{
|
||||
T* obj = luaW_check<T>(L, 1);
|
||||
if (obj && lua_gettop(L) >= 2)
|
||||
{
|
||||
U* member = luaW_opt<U>(L, 2);
|
||||
(obj->*Setter)(member);
|
||||
if (member)
|
||||
luaW_release<U>(L, member);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
luaW_push<U>(L, (obj->*Getter)());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) && !defined(LUAW_NO_CXX11)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// luaU_func is a special macro that expands into a simple function wrapper.
|
||||
// Unlike the getter setters above, you merely need to name the function you
|
||||
// would like to wrap.
|
||||
//
|
||||
// For example,
|
||||
//
|
||||
// struct Foo
|
||||
// {
|
||||
// int DoSomething(int, const char*);
|
||||
// };
|
||||
//
|
||||
// static luaL_reg Foo_metatable[] =
|
||||
// {
|
||||
// { "DoSomething", luaU_func(&Foo::DoSomething) },
|
||||
// { NULL, NULL }
|
||||
// }
|
||||
//
|
||||
// This macro will expand based on the function signature of Foo::DoSomething
|
||||
// In this example, it would expand into the following wrapper:
|
||||
//
|
||||
// luaU_push(luaW_check<T>(L, 1)->DoSomething(luaU_check<int>(L, 2), luaU_check<const char*>(L, 3)));
|
||||
// return 1;
|
||||
//
|
||||
// In this example there is only one member function called DoSomething. In some
|
||||
// cases there may be multiple overrides for a function. For those cases, an
|
||||
// additional macro has been provided, luaU_funcsig, which takes the entire
|
||||
// function signature. The arguments to the macro reflect the order they would
|
||||
// appear in the function signature: return type, type name, function name, and
|
||||
// finally a list of all the argument types. For example:
|
||||
//
|
||||
// struct Foo
|
||||
// {
|
||||
// int DoSomething (const char*);
|
||||
// int DoSomething (const char*, int);
|
||||
// };
|
||||
//
|
||||
// const struct luaL_Reg Foo_metatable[] =
|
||||
// {
|
||||
// {"DoSomething1", luaU_funcsig(int, Foo, DoSomething, const char*)) },
|
||||
// {"DoSomething1", luaU_funcsig(int, Foo, DoSomething, const char*, int)) },
|
||||
// { NULL, NULL }
|
||||
// };
|
||||
//
|
||||
// These macros and it's underlying templates are somewhat experimental and some
|
||||
// refinements are probably needed. There are cases where it does not
|
||||
// currently work and I expect some changes can be made to refine its behavior.
|
||||
//
|
||||
|
||||
#define luaU_func(memberfunc) &luaU_FuncWrapper<decltype(memberfunc),memberfunc>::call
|
||||
#define luaU_funcsig(returntype, type, funcname, ...) luaU_func(static_cast<returntype (type::*)(__VA_ARGS__)>(&type::funcname))
|
||||
|
||||
template<int... ints> struct luaU_IntPack { };
|
||||
template<int start, int count, int... tail> struct luaU_MakeIntRangeType { typedef typename luaU_MakeIntRangeType<start, count-1, start+count-1, tail...>::type type; };
|
||||
template<int start, int... tail> struct luaU_MakeIntRangeType<start, 0, tail...> { typedef luaU_IntPack<tail...> type; };
|
||||
template<int start, int count> inline typename luaU_MakeIntRangeType<start, count>::type luaU_makeIntRange() { return typename luaU_MakeIntRangeType<start, count>::type(); }
|
||||
template<class MemFunPtrType, MemFunPtrType MemberFunc> struct luaU_FuncWrapper;
|
||||
|
||||
template<class T, class ReturnType, class... Args, ReturnType(T::*MemberFunc)(Args...)>
|
||||
struct luaU_FuncWrapper<ReturnType (T::*)(Args...), MemberFunc>
|
||||
{
|
||||
public:
|
||||
static int call(lua_State* L)
|
||||
{
|
||||
return callImpl(L, luaU_makeIntRange<2,sizeof...(Args)>());
|
||||
}
|
||||
|
||||
private:
|
||||
template<int... indices>
|
||||
static int callImpl(lua_State* L, luaU_IntPack<indices...>)
|
||||
{
|
||||
luaU_push<ReturnType>(L, (luaW_check<T>(L, 1)->*MemberFunc)(luaU_check<Args>(L, indices)...));
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Calls the copy constructor for an object of type T.
|
||||
// Arguments may be passed in, in case they're needed for the postconstructor
|
||||
//
|
||||
// e.g.
|
||||
//
|
||||
// C++:
|
||||
// luaL_reg Foo_Metatable[] =
|
||||
// {
|
||||
// { "clone", luaU_clone<Foo> },
|
||||
// { NULL, NULL }
|
||||
// };
|
||||
//
|
||||
// Lua:
|
||||
// foo = Foo.new()
|
||||
// foo2 = foo:clone()
|
||||
//
|
||||
template <typename T>
|
||||
int luaU_clone(lua_State* L)
|
||||
{
|
||||
// obj ...
|
||||
T* obj = new T(*luaW_check<T>(L, 1));
|
||||
lua_remove(L, 1); // ...
|
||||
int numargs = lua_gettop(L);
|
||||
luaW_push<T>(L, obj); // ... clone
|
||||
luaW_hold<T>(L, obj);
|
||||
luaW_postconstructor<T>(L, numargs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// luaU_build is intended to be used to initialize many values by passing in a
|
||||
// table. They keys of the table are used as function names, and values are
|
||||
// used as arguments to the function. This is intended to be used on functions
|
||||
// that are simple setters.
|
||||
//
|
||||
// For example, if luaU_build is set as the post constructor, you can
|
||||
// initialize an object as so:
|
||||
//
|
||||
// f = Foo.new
|
||||
// {
|
||||
// X = 10;
|
||||
// Y = 20;
|
||||
// }
|
||||
//
|
||||
// After the object is constructed, luaU_build will do the equivalent of
|
||||
// calling f:X(10) and f:Y(20).
|
||||
//
|
||||
template <typename T>
|
||||
int luaU_build(lua_State* L)
|
||||
{
|
||||
// obj {}
|
||||
lua_insert(L, -2); // {} obj
|
||||
if (lua_type(L, 1) == LUA_TTABLE)
|
||||
{
|
||||
for (lua_pushnil(L); lua_next(L, 1); lua_pop(L, 1))
|
||||
{
|
||||
// {} obj k v
|
||||
lua_pushvalue(L, -2); // {} obj k v k
|
||||
lua_gettable(L, -4); // {} obj k v ud[k]
|
||||
lua_pushvalue(L, -4); // {} obj k v ud[k] ud
|
||||
lua_pushvalue(L, -3); // {} obj k v ud[k] ud v
|
||||
lua_call(L, 2, 0); // {} obj k v
|
||||
}
|
||||
// {} ud
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Takes the object of type T at the top of the stack and stores it in on a
|
||||
// table with the name storagetable, on the table at the specified index.
|
||||
//
|
||||
// You may manually call luaW_hold and luaW_release to handle pointer
|
||||
// ownership, but it is often easier to simply store a Lua userdata on a table
|
||||
// that is owned by its parent. This ensures that your object will not be
|
||||
// prematurely freed, and that it can only be destoryed after its parent.
|
||||
//
|
||||
// e.g.
|
||||
//
|
||||
// Foo* parent = luaW_check<Foo>(L, 1);
|
||||
// Bar* child = luaW_check<Bar>(L, 2);
|
||||
// if (parent && child)
|
||||
// {
|
||||
// luaU_store<Bar>(L, 1, "Children");
|
||||
// parent->AddChild(child);
|
||||
// }
|
||||
//
|
||||
template <typename T>
|
||||
void luaU_store(lua_State* L, int index, const char* storagetable, const char* key = NULL)
|
||||
{
|
||||
// ... store ... obj
|
||||
lua_getfield(L, index, storagetable); // ... store ... obj store.storagetable
|
||||
if (key)
|
||||
lua_pushstring(L, key); // ... store ... obj store.storagetable key
|
||||
else
|
||||
LuaWrapper<T>::identifier(L, luaW_to<T>(L, -2)); // ... store ... obj store.storagetable key
|
||||
lua_pushvalue(L, -3); // ... store ... obj store.storagetable key obj
|
||||
lua_settable(L, -3); // ... store ... obj store.storagetable
|
||||
lua_pop(L, 1); // ... store ... obj
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (c) 2010-2011 Alexander Ames
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#endif // LUAWRAPPERUTILS_HPP_
|
64
luarocks/main.cpp
Normal file
64
luarocks/main.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
#include "lauxlib.h"
|
||||
}
|
||||
|
||||
#include "functions.hpp"
|
||||
#include "LuaWebSocket.h"
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
lua_State* L = luaL_newstate();
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Functions
|
||||
lua_register(L, "info", lua_info);
|
||||
|
||||
// Objets
|
||||
ix::luaopen_WebSocket(L);
|
||||
|
||||
//
|
||||
// Simple version does little error handling
|
||||
// luaL_dofile(L, "echo_client.lua");
|
||||
//
|
||||
std::string luaFile("echo_client.lua");
|
||||
int loadStatus = luaL_loadfile(L, luaFile.c_str());
|
||||
if (loadStatus)
|
||||
{
|
||||
std::cerr << "Error loading " << luaFile << std::endl;
|
||||
std::cerr << lua_tostring(L, -1) << std::endl;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "loaded " << luaFile << std::endl;
|
||||
}
|
||||
|
||||
//
|
||||
// Capture lua errors
|
||||
//
|
||||
try
|
||||
{
|
||||
lua_call(L, 0, 0);
|
||||
lua_close(L);
|
||||
}
|
||||
catch (const std::runtime_error& ex)
|
||||
{
|
||||
lua_close(L);
|
||||
std::cerr << ex.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
62
main.cpp
62
main.cpp
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
|
||||
*
|
||||
* On macOS
|
||||
* $ mkdir -p build ; cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install
|
||||
* $ clang++ --std=c++14 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
|
||||
* $ ./a.out
|
||||
*/
|
||||
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
// Required on Windows
|
||||
ix::initNetSystem();
|
||||
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
std::string url("wss://echo.websocket.org");
|
||||
webSocket.setUrl(url);
|
||||
|
||||
std::cout << "Connecting to " << url << "..." << std::endl;
|
||||
|
||||
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
||||
// when a message or an event (open, close, error) is received
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
std::cout << "received message: " << msg->str << std::endl;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
std::cout << "Connection established" << std::endl;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Now that our callback is setup, we can start our background thread and receive messages
|
||||
webSocket.start();
|
||||
|
||||
// Send a message to the server (default to TEXT mode)
|
||||
webSocket.send("hello world");
|
||||
|
||||
while (true)
|
||||
{
|
||||
std::string text;
|
||||
std::cout << "> " << std::flush;
|
||||
std::getline(std::cin, text);
|
||||
|
||||
webSocket.send(text);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
102
makefile
102
makefile
@ -19,28 +19,26 @@ install: brew
|
||||
#
|
||||
# Release, Debug, MinSizeRel, RelWithDebInfo are the build types
|
||||
#
|
||||
# Default rule does not use python as that requires first time users to have Python3 installed
|
||||
#
|
||||
brew:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install)
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4 install)
|
||||
|
||||
# Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the
|
||||
# Docker default target. We've add problem with OpenSSL and TLS 1.3 (on the
|
||||
# server side ?) and I can't work-around it easily, so we're using mbedtls on
|
||||
# Linux for the SSL backend, which works great.
|
||||
ws_mbedtls_install:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install)
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4 install)
|
||||
|
||||
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 -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j 4)
|
||||
|
||||
ws_install:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=0 -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j 4 install)
|
||||
|
||||
ws_openssl:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; make -j 4)
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; make -j 4)
|
||||
|
||||
ws_mbedtls:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4)
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4)
|
||||
|
||||
ws_no_ssl:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4)
|
||||
@ -104,23 +102,17 @@ test_server:
|
||||
# env TEST=Websocket_server make test
|
||||
# env TEST=Websocket_chat make test
|
||||
# env TEST=heartbeat make test
|
||||
build_test:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. ; ninja install)
|
||||
|
||||
test: build_test
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_make:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
test:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_tsan:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_ubsan:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableUndefinedBehaviorSanitizer YES)
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableUndefinedBehaviorSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
@ -128,37 +120,37 @@ test_asan: build_test_asan
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
build_test_asan:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableAddressSanitizer YES)
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableAddressSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
|
||||
test_tsan_openssl:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_ubsan_openssl:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableUndefinedBehaviorSanitizer YES)
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableUndefinedBehaviorSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_tsan_openssl_release:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Release -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -configuration Release -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Release -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 .. && xcodebuild -project ixwebsocket.xcodeproj -configuration Release -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Release/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
test_tsan_mbedtls:
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_MBED_TLS=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
mkdir -p build && (cd build && cmake -GXcode -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 -DUSE_MBED_TLS=1 .. && xcodebuild -project ixwebsocket.xcodeproj -target ixwebsocket_unittest -enableThreadSanitizer YES)
|
||||
(cd build/test ; ln -sf Debug/ixwebsocket_unittest)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
build_test_openssl:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_OPEN_SSL=1 -DUSE_TEST=1 .. ; ninja install)
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPEN_SSL=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
|
||||
test_openssl: build_test_openssl
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
build_test_mbedtls:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. ; make -j 4)
|
||||
|
||||
test_mbedtls: build_test_mbedtls
|
||||
(cd test ; python2.7 run.py -r)
|
||||
@ -167,59 +159,15 @@ test_no_ssl:
|
||||
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TEST=1 .. ; make -j 4)
|
||||
(cd test ; python2.7 run.py -r)
|
||||
|
||||
luarocks:
|
||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_LUAROCKS=1 .. ; ninja)
|
||||
|
||||
ws_test: ws
|
||||
(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh)
|
||||
|
||||
autobahn_report:
|
||||
cp -rvf ~/sandbox/reports/clients/* ../bsergean.github.io/IXWebSocket/autobahn/
|
||||
|
||||
httpd:
|
||||
clang++ --std=c++14 --stdlib=libc++ -o ixhttpd httpd.cpp \
|
||||
ixwebsocket/IXSelectInterruptFactory.cpp \
|
||||
ixwebsocket/IXCancellationRequest.cpp \
|
||||
ixwebsocket/IXSocketTLSOptions.cpp \
|
||||
ixwebsocket/IXUserAgent.cpp \
|
||||
ixwebsocket/IXDNSLookup.cpp \
|
||||
ixwebsocket/IXBench.cpp \
|
||||
ixwebsocket/IXWebSocketHttpHeaders.cpp \
|
||||
ixwebsocket/IXSelectInterruptPipe.cpp \
|
||||
ixwebsocket/IXHttp.cpp \
|
||||
ixwebsocket/IXSocketConnect.cpp \
|
||||
ixwebsocket/IXSocket.cpp \
|
||||
ixwebsocket/IXSocketServer.cpp \
|
||||
ixwebsocket/IXNetSystem.cpp \
|
||||
ixwebsocket/IXHttpServer.cpp \
|
||||
ixwebsocket/IXSocketFactory.cpp \
|
||||
ixwebsocket/IXConnectionState.cpp \
|
||||
ixwebsocket/IXUrlParser.cpp \
|
||||
ixwebsocket/IXSelectInterrupt.cpp \
|
||||
ixwebsocket/IXSetThreadName.cpp \
|
||||
-lz
|
||||
|
||||
httpd_linux:
|
||||
g++ --std=c++14 -o ixhttpd httpd.cpp -Iixwebsocket \
|
||||
ixwebsocket/IXSelectInterruptFactory.cpp \
|
||||
ixwebsocket/IXCancellationRequest.cpp \
|
||||
ixwebsocket/IXSocketTLSOptions.cpp \
|
||||
ixwebsocket/IXUserAgent.cpp \
|
||||
ixwebsocket/IXDNSLookup.cpp \
|
||||
ixwebsocket/IXBench.cpp \
|
||||
ixwebsocket/IXWebSocketHttpHeaders.cpp \
|
||||
ixwebsocket/IXSelectInterruptPipe.cpp \
|
||||
ixwebsocket/IXHttp.cpp \
|
||||
ixwebsocket/IXSocketConnect.cpp \
|
||||
ixwebsocket/IXSocket.cpp \
|
||||
ixwebsocket/IXSocketServer.cpp \
|
||||
ixwebsocket/IXNetSystem.cpp \
|
||||
ixwebsocket/IXHttpServer.cpp \
|
||||
ixwebsocket/IXSocketFactory.cpp \
|
||||
ixwebsocket/IXConnectionState.cpp \
|
||||
ixwebsocket/IXUrlParser.cpp \
|
||||
ixwebsocket/IXSelectInterrupt.cpp \
|
||||
ixwebsocket/IXSetThreadName.cpp \
|
||||
-lz -lpthread
|
||||
cp -f ixhttpd /usr/local/bin
|
||||
|
||||
# For the fork that is configured with appveyor
|
||||
rebase_upstream:
|
||||
git fetch upstream
|
||||
@ -227,7 +175,6 @@ rebase_upstream:
|
||||
git reset --hard upstream/master
|
||||
git push origin master --force
|
||||
|
||||
# Legacy target
|
||||
install_cmake_for_linux:
|
||||
mkdir -p /tmp/cmake
|
||||
(cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-Linux-x86_64.tar.gz)
|
||||
@ -238,12 +185,7 @@ install_cmake_for_linux:
|
||||
doc:
|
||||
mkdocs gh-deploy
|
||||
|
||||
change: format
|
||||
vim ixwebsocket/IXWebSocketVersion.h docs/CHANGELOG.md
|
||||
|
||||
commit:
|
||||
git commit -am "`sh tools/extract_latest_change.sh`"
|
||||
|
||||
.PHONY: test
|
||||
.PHONY: build
|
||||
.PHONY: ws
|
||||
.PHONY: luarocks
|
||||
|
@ -37,11 +37,11 @@ set (SOURCES
|
||||
|
||||
test_runner.cpp
|
||||
IXTest.cpp
|
||||
IXGetFreePort.cpp
|
||||
../third_party/msgpack11/msgpack11.cpp
|
||||
|
||||
IXSocketTest.cpp
|
||||
IXSocketConnectTest.cpp
|
||||
# IXWebSocketLeakTest.cpp # commented until we have a fix for #224
|
||||
IXWebSocketServerTest.cpp
|
||||
IXWebSocketTestConnectionDisconnection.cpp
|
||||
IXUrlParserTest.cpp
|
||||
@ -55,8 +55,6 @@ set (SOURCES
|
||||
IXSentryClientTest.cpp
|
||||
IXWebSocketChatTest.cpp
|
||||
IXWebSocketBroadcastTest.cpp
|
||||
IXWebSocketPerMessageDeflateCompressorTest.cpp
|
||||
IXStreamSqlTest.cpp
|
||||
)
|
||||
|
||||
# Some unittest don't work on windows yet
|
||||
@ -93,30 +91,15 @@ if (JSONCPP_FOUND)
|
||||
target_link_libraries(ixwebsocket_unittest ${JSONCPP_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if (USE_PYTHON)
|
||||
find_package(Python COMPONENTS Development)
|
||||
if (NOT Python_FOUND)
|
||||
message(FATAL_ERROR "Python3 not found")
|
||||
endif()
|
||||
message("Python_FOUND:${Python_FOUND}")
|
||||
message("Python_VERSION:${Python_VERSION}")
|
||||
message("Python_Development_FOUND:${Python_Development_FOUND}")
|
||||
message("Python_LIBRARIES:${Python_LIBRARIES}")
|
||||
endif()
|
||||
|
||||
# library with the most dependencies come first
|
||||
target_link_libraries(ixwebsocket_unittest ixbots)
|
||||
target_link_libraries(ixwebsocket_unittest ixsnake)
|
||||
target_link_libraries(ixwebsocket_unittest ixcobra)
|
||||
target_link_libraries(ixwebsocket_unittest ixsentry)
|
||||
target_link_libraries(ixwebsocket_unittest ixredis)
|
||||
target_link_libraries(ixwebsocket_unittest ixwebsocket)
|
||||
target_link_libraries(ixwebsocket_unittest ixcrypto)
|
||||
target_link_libraries(ixwebsocket_unittest ixcore)
|
||||
|
||||
target_link_libraries(ixwebsocket_unittest spdlog)
|
||||
if (USE_PYTHON)
|
||||
target_link_libraries(ixwebsocket_unittest ${Python_LIBRARIES})
|
||||
endif()
|
||||
|
||||
install(TARGETS ixwebsocket_unittest DESTINATION bin)
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include <iostream>
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcrypto/IXUuid.h>
|
||||
#include <ixredis/IXRedisServer.h>
|
||||
#include <ixsnake/IXRedisServer.h>
|
||||
#include <ixsnake/IXSnakeServer.h>
|
||||
|
||||
using namespace ix;
|
||||
@ -125,12 +125,10 @@ namespace
|
||||
{
|
||||
std::string filter;
|
||||
std::string position("$");
|
||||
int batchSize = 1;
|
||||
|
||||
_conn.subscribe(channel,
|
||||
filter,
|
||||
position,
|
||||
batchSize,
|
||||
[this](const Json::Value& msg, const std::string& /*position*/) {
|
||||
spdlog::info("receive {}", msg.toStyledString());
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include <iostream>
|
||||
#include <ixcobra/IXCobraMetricsPublisher.h>
|
||||
#include <ixcrypto/IXUuid.h>
|
||||
#include <ixredis/IXRedisServer.h>
|
||||
#include <ixsnake/IXRedisServer.h>
|
||||
#include <ixsnake/IXSnakeServer.h>
|
||||
#include <set>
|
||||
|
||||
@ -76,12 +76,10 @@ namespace
|
||||
log("Subscriber authenticated");
|
||||
std::string filter;
|
||||
std::string position("$");
|
||||
int batchSize = 1;
|
||||
|
||||
conn.subscribe(channel,
|
||||
filter,
|
||||
position,
|
||||
batchSize,
|
||||
[](const Json::Value& msg, const std::string& /*position*/) {
|
||||
log(msg.toStyledString());
|
||||
|
||||
@ -108,7 +106,7 @@ namespace
|
||||
}
|
||||
else if (event->type == ix::CobraEventType::UnSubscribed)
|
||||
{
|
||||
TLogger() << "Subscriber: unsubscribed from channel " << event->subscriptionId;
|
||||
TLogger() << "Subscriber: ununexpected from channel " << event->subscriptionId;
|
||||
if (event->subscriptionId != channel)
|
||||
{
|
||||
TLogger() << "Subscriber: unexpected channel " << event->subscriptionId;
|
||||
|
@ -12,8 +12,8 @@
|
||||
#include <ixcobra/IXCobraConnection.h>
|
||||
#include <ixcobra/IXCobraMetricsPublisher.h>
|
||||
#include <ixcrypto/IXUuid.h>
|
||||
#include <ixredis/IXRedisServer.h>
|
||||
#include <ixsentry/IXSentryClient.h>
|
||||
#include <ixsnake/IXRedisServer.h>
|
||||
#include <ixsnake/IXSnakeServer.h>
|
||||
#include <ixwebsocket/IXHttpServer.h>
|
||||
#include <ixwebsocket/IXUserAgent.h>
|
||||
@ -95,15 +95,13 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
||||
|
||||
sentryServer.setOnConnectionCallback(
|
||||
[](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/,
|
||||
std::unique_ptr<ConnectionInfo> connectionInfo) -> HttpResponsePtr {
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << connectionInfo->remoteIp << ":" << connectionInfo->remotePort << " "
|
||||
<< request->method << " " << request->headers["User-Agent"] << " "
|
||||
ss << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri;
|
||||
|
||||
if (request->method == "POST")
|
||||
@ -140,12 +138,11 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
||||
|
||||
std::thread publisherThread(runPublisher, config, channel);
|
||||
|
||||
ix::CobraBotConfig cobraBotConfig;
|
||||
cobraBotConfig.cobraConfig = config;
|
||||
cobraBotConfig.channel = channel;
|
||||
cobraBotConfig.runtime = 3; // Only run the bot for 3 seconds
|
||||
cobraBotConfig.enableHeartbeat = false;
|
||||
std::string filter;
|
||||
std::string position("$");
|
||||
bool verbose = true;
|
||||
size_t maxQueueSize = 10;
|
||||
bool enableHeartbeat = false;
|
||||
|
||||
// FIXME: try to get this working with https instead of http
|
||||
// to regress the TLS 1.3 OpenSSL bug
|
||||
@ -160,7 +157,18 @@ TEST_CASE("Cobra_to_sentry_bot", "[cobra_bots]")
|
||||
SentryClient sentryClient(dsn);
|
||||
sentryClient.setTLSOptions(tlsOptionsClient);
|
||||
|
||||
int64_t sentCount = cobra_to_sentry_bot(cobraBotConfig, sentryClient, verbose);
|
||||
// Only run the bot for 3 seconds
|
||||
int runtime = 3;
|
||||
|
||||
int64_t sentCount = cobra_to_sentry_bot(config,
|
||||
channel,
|
||||
filter,
|
||||
position,
|
||||
sentryClient,
|
||||
verbose,
|
||||
maxQueueSize,
|
||||
enableHeartbeat,
|
||||
runtime);
|
||||
//
|
||||
// We want at least 2 messages to be sent
|
||||
//
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user