mirror of
https://github.com/boostorg/redis.git
synced 2026-01-23 06:02:09 +00:00
Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84ee2f37f1 | ||
|
|
81927deda4 | ||
|
|
34ff1cea63 | ||
|
|
10603b7d3a | ||
|
|
ad3c2914db | ||
|
|
91014b13bf | ||
|
|
4f6f8b454d | ||
|
|
9ebcc544ae | ||
|
|
7d16259749 | ||
|
|
9dec63515e | ||
|
|
46525371b9 | ||
|
|
b5f8348598 | ||
|
|
69d12421e2 | ||
|
|
a715c251bf | ||
|
|
d29a057fa6 | ||
|
|
82430afc8b | ||
|
|
607946f00e | ||
|
|
c99790ab5c | ||
|
|
635b3608ad | ||
|
|
a8a78c38c6 | ||
|
|
e09a53ff08 | ||
|
|
ec8a1c7286 | ||
|
|
3c02a7662b | ||
|
|
538ab8f35f | ||
|
|
f5f57e370b | ||
|
|
7abfc5fd8d | ||
|
|
11eebcf771 | ||
|
|
c21f70bc07 | ||
|
|
22bacbd52c | ||
|
|
2982f831f6 | ||
|
|
663e9ac671 | ||
|
|
c0aa4356ea | ||
|
|
6f9fd5b2fb | ||
|
|
30a6e34e4e | ||
|
|
1f9b3e8008 | ||
|
|
3808fec0e3 | ||
|
|
607a9e9dd6 | ||
|
|
2d53bb748e | ||
|
|
a6cb4ca323 | ||
|
|
5ac4f7e8ad | ||
|
|
7a08588808 | ||
|
|
e7ff1cedf3 | ||
|
|
0bcb7dcf16 | ||
|
|
c28969674b | ||
|
|
c7f49c6677 | ||
|
|
90bcd621fb | ||
|
|
fd967204df | ||
|
|
cd00047a49 | ||
|
|
728b35cfe0 | ||
|
|
52e62ba78c | ||
|
|
bb18ff4891 | ||
|
|
6ce793e413 | ||
|
|
a83c0e7803 | ||
|
|
64820bd25b | ||
|
|
16b5c8d1ba | ||
|
|
8ef4d3cf0b | ||
|
|
d01a9acf3b | ||
|
|
ac7e425d47 | ||
|
|
d620cdee59 | ||
|
|
5f07b730f7 | ||
|
|
6d3a112f94 | ||
|
|
1f3ef6b486 | ||
|
|
a850a6ed63 | ||
|
|
c8b73c2fe8 | ||
|
|
8b02268182 | ||
|
|
1b60eeb352 | ||
|
|
b93f36163d | ||
|
|
071f9a93aa | ||
|
|
5a6ca14a67 | ||
|
|
a5c86107f8 | ||
|
|
3a4445022e | ||
|
|
bfb26f2602 | ||
|
|
7e70cb4ad7 | ||
|
|
886561409a | ||
|
|
0c5ff09685 | ||
|
|
4b07b6d516 | ||
|
|
c1ce8358c7 | ||
|
|
13e16b7a60 | ||
|
|
e11502e0df | ||
|
|
b2344384cf | ||
|
|
56c0b28003 | ||
|
|
c88fcfb9ed | ||
|
|
a56bf982ab | ||
|
|
5d0ed0e986 | ||
|
|
15deaa637d | ||
|
|
bb8ff90351 | ||
|
|
7d4902369a | ||
|
|
607ca17a89 | ||
|
|
3849ba42fd | ||
|
|
56bcdb7914 | ||
|
|
73ad66eb93 | ||
|
|
9cf00d6a23 | ||
|
|
a00c9e7439 | ||
|
|
0520791100 | ||
|
|
14b376e36e | ||
|
|
4f9dcc7dc5 | ||
|
|
ad5dd8c30b | ||
|
|
842f864689 | ||
|
|
63f9b74502 | ||
|
|
801f60a026 | ||
|
|
c37fcb641c | ||
|
|
48c3f37168 | ||
|
|
3c63911802 | ||
|
|
1645881a44 | ||
|
|
730e06c38d | ||
|
|
cf3a79737d | ||
|
|
edb384c843 | ||
|
|
f745faddf8 | ||
|
|
927117568e | ||
|
|
1e7c176f92 | ||
|
|
449b5f7e7c | ||
|
|
75f91f3b11 | ||
|
|
b9a23568e3 | ||
|
|
4ac2509afa | ||
|
|
e9dab97992 | ||
|
|
2e8cad858d | ||
|
|
5a6e426028 | ||
|
|
c55978a379 | ||
|
|
6f51397e49 | ||
|
|
6b9ba6b2d9 | ||
|
|
d29c03cb38 | ||
|
|
34cfbaa22f | ||
|
|
c9354fe320 | ||
|
|
bb555cb509 | ||
|
|
5b209afa1d | ||
|
|
3f5491654d | ||
|
|
2bdc25752f | ||
|
|
faafce1c64 |
112
.github/workflows/ci.yml
vendored
112
.github/workflows/ci.yml
vendored
@@ -3,6 +3,92 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
name: "${{matrix.generator}} ${{matrix.toolset}} Boost ${{matrix.boost_version}} ${{matrix.build_type}} ${{matrix.name_args}}"
|
||||
runs-on: ${{matrix.os}}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
boost_version: ["1.81.0"]
|
||||
os: [windows-2019, windows-2022]
|
||||
toolset: [v142, v143]
|
||||
build_type: [Release]
|
||||
generator: ["Visual Studio 16 2019", "Visual Studio 17 2022"]
|
||||
config_args: [""]
|
||||
build_args: [""]
|
||||
name_args: [""]
|
||||
exclude:
|
||||
- { os: windows-2019, toolset: v143 }
|
||||
- { os: windows-2019, generator: "Visual Studio 17 2022" }
|
||||
- { os: windows-2022, generator: "Visual Studio 16 2019" }
|
||||
# The following combinations are not available through install-boost
|
||||
- { boost_version: "1.81.0", toolset: v143 }
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Add boost toolset to environment
|
||||
if: contains(fromJson('["1.81.0"]'), matrix.boost_version)
|
||||
run: echo BOOST_TOOLSET=$(echo "msvc") >> $GITHUB_ENV
|
||||
|
||||
# The platform_version passed to boost-install determines the msvc toolset version for which static libs are installed.
|
||||
- name: Add boost platform version to environment
|
||||
run: |
|
||||
declare -A toolset_to_platform_version=( [v142]="2019" [v143]="2022" )
|
||||
key=$(echo "${{matrix.toolset}}")
|
||||
echo BOOST_PLATFORM_VERSION="${toolset_to_platform_version[$key]}" >> $GITHUB_ENV
|
||||
|
||||
- name: Add boost install path to environment
|
||||
run: echo BOOST_INSTALL_PATH="${GITHUB_WORKSPACE}/boost-${{matrix.boost_version}}${BOOST_TOOLSET}${BOOST_PLATFORM_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Add build type configuration to environment
|
||||
run: echo BUILD_CONFIG_ARG="--config ${{matrix.build_type}}" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache Boost installation
|
||||
id: cache-boost
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{env.BOOST_INSTALL_PATH}}
|
||||
key: ${{matrix.boost_version}}${{env.BOOST_TOOLSET}}${{env.BOOST_PLATFORM_VERSION}}
|
||||
|
||||
- name: Install Boost
|
||||
if: steps.cache-boost.outputs.cache-hit != 'true'
|
||||
uses: MarkusJx/install-boost@v2.4.1
|
||||
with:
|
||||
boost_version: ${{matrix.boost_version}}
|
||||
toolset: ${{env.BOOST_TOOLSET}}
|
||||
boost_install_dir: ${{env.BOOST_INSTALL_PATH}}
|
||||
platform_version: ${{env.BOOST_PLATFORM_VERSION}}
|
||||
arch: null
|
||||
|
||||
- name: Install openssl
|
||||
run: choco install openssl
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir build
|
||||
|
||||
- name: Configure
|
||||
working-directory: build
|
||||
run: |
|
||||
cmake -T "${{matrix.toolset}}" \
|
||||
-G "${{matrix.generator}}" \
|
||||
${{matrix.config_args}} \
|
||||
${BOOST_COMPILER_ARG}\
|
||||
"${GITHUB_WORKSPACE}"
|
||||
env:
|
||||
BOOST_ROOT: ${{env.BOOST_INSTALL_PATH}}/boost
|
||||
|
||||
- name: Build
|
||||
working-directory: build
|
||||
run: |
|
||||
cmake --build . ${BUILD_CONFIG_ARG} ${{matrix.build_args}}
|
||||
|
||||
posix:
|
||||
defaults:
|
||||
run:
|
||||
@@ -12,34 +98,38 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { toolset: gcc, compiler: g++-10, install: g++-10, os: ubuntu-22.04, cxxstd: 'c++17' }
|
||||
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxstd: 'c++17' }
|
||||
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxstd: 'c++20' }
|
||||
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxstd: 'c++17' }
|
||||
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxstd: 'c++20' }
|
||||
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxstd: 'c++17' }
|
||||
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxstd: 'c++20' }
|
||||
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
|
||||
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxflags: '-std=c++17 -stdlib=libc++', ldflags: '-lc++' }
|
||||
- { toolset: clang, compiler: clang++-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxflags: '-std=c++20 -stdlib=libc++', ldflags: '-lc++' }
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CXXFLAGS: -g -O0 -std=${{matrix.cxxstd}} -Wall -Wextra
|
||||
CXXFLAGS: -g -O0 ${{matrix.cxxflags}} -Wall -Wextra
|
||||
LDFLAGS: ${{matrix.ldflags}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install CMake
|
||||
run: sudo apt-get -y install cmake
|
||||
- name: Install protobuf
|
||||
run: sudo apt-get -y install protobuf-compiler
|
||||
- name: Install compiler
|
||||
run: sudo apt-get install -y ${{ matrix.install }}
|
||||
- name: Install Redis
|
||||
run: sudo apt-get install -y redis-server
|
||||
- name: Install boost
|
||||
uses: MarkusJx/install-boost@v2.3.0
|
||||
uses: MarkusJx/install-boost@v2.4.1
|
||||
id: install-boost
|
||||
with:
|
||||
boost_version: 1.79.0
|
||||
boost_version: 1.81.0
|
||||
platform_version: 22.04
|
||||
- name: Run CMake
|
||||
run: |
|
||||
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake -DCMAKE_CXX_COMPILER="${{matrix.compiler}}" -DCMAKE_CXX_FLAGS="${{env.CXXFLAGS}}"
|
||||
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake -DCMAKE_CXX_COMPILER="${{matrix.compiler}}" -DCMAKE_CXX_FLAGS="${{env.CXXFLAGS}}" -DCMAKE_EXE_LINKER_FLAGS="${{env.LDFLAGS}}"
|
||||
- name: Build
|
||||
run: make
|
||||
- name: Check
|
||||
|
||||
6
.github/workflows/coverage.yml
vendored
6
.github/workflows/coverage.yml
vendored
@@ -3,7 +3,7 @@ name: Coverage
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
jobs:
|
||||
posix:
|
||||
defaults:
|
||||
@@ -27,10 +27,10 @@ jobs:
|
||||
- name: Install Redis
|
||||
run: sudo apt-get -y install redis-server
|
||||
- name: Install boost
|
||||
uses: MarkusJx/install-boost@v2.3.0
|
||||
uses: MarkusJx/install-boost@v2.4.1
|
||||
id: install-boost
|
||||
with:
|
||||
boost_version: 1.79.0
|
||||
boost_version: 1.81.0
|
||||
platform_version: 22.04
|
||||
- name: Run CMake
|
||||
run: |
|
||||
|
||||
309
CMakeLists.txt
309
CMakeLists.txt
@@ -1,168 +1,230 @@
|
||||
# At the moment the official build system is still autotools and this
|
||||
# file is meant to support Aedis on windows.
|
||||
|
||||
# BOOST_ROOT=/opt/boost_1_79/ cmake -DCMAKE_CXX_FLAGS="-g -O0
|
||||
# -std=c++20 -Wall -Wextra --coverage -fkeep-inline-functions
|
||||
# -fkeep-static-functions" -DCMAKE_EXE_LINKER_FLAGS="--coverage"
|
||||
# ~/my/aedis
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
#set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
|
||||
|
||||
# determine whether it's main/root project
|
||||
# or being built under another project.
|
||||
if (NOT DEFINED BOOST_REDIS_MAIN_PROJECT)
|
||||
set(BOOST_REDIS_MAIN_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(BOOST_REDIS_MAIN_PROJECT ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
project(
|
||||
Aedis
|
||||
VERSION 1.2.0
|
||||
DESCRIPTION "A redis client designed for performance and scalability"
|
||||
HOMEPAGE_URL "https://mzimbres.github.io/aedis"
|
||||
boost_redis
|
||||
VERSION 1.4.1
|
||||
DESCRIPTION "A redis client library"
|
||||
HOMEPAGE_URL "https://boostorg.github.io/redis/"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
add_library(aedis INTERFACE)
|
||||
target_include_directories(aedis INTERFACE
|
||||
option(BOOST_REDIS_INSTALL "Generate install targets." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
option(BOOST_REDIS_TESTS "Build tests." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
option(BOOST_REDIS_EXAMPLES "Build examples." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
option(BOOST_REDIS_BENCHMARKS "Build benchmarks." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
option(BOOST_REDIS_DOC "Generate documentations." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
|
||||
add_library(boost_redis INTERFACE)
|
||||
add_library(Boost::redis ALIAS boost_redis)
|
||||
target_include_directories(boost_redis INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
target_link_libraries(aedis
|
||||
|
||||
target_link_libraries(
|
||||
boost_redis
|
||||
INTERFACE
|
||||
Boost::asio
|
||||
Boost::assert
|
||||
Boost::config
|
||||
Boost::core
|
||||
Boost::mp11
|
||||
Boost::optional
|
||||
Boost::system
|
||||
Boost::utility
|
||||
Boost::winapi
|
||||
)
|
||||
|
||||
target_compile_features(aedis INTERFACE cxx_std_17)
|
||||
target_compile_features(boost_redis INTERFACE cxx_std_17)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"${PROJECT_BINARY_DIR}/AedisConfigVersion.cmake"
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
# Asio bases C++ feature detection on __cplusplus. Make MSVC
|
||||
# define it correctly
|
||||
if (MSVC)
|
||||
target_compile_options(boost_redis INTERFACE /Zc:__cplusplus)
|
||||
endif()
|
||||
|
||||
find_package(Boost 1.80 REQUIRED)
|
||||
|
||||
find_package(Boost 1.79 REQUIRED)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
enable_testing()
|
||||
include_directories(include)
|
||||
|
||||
# Common
|
||||
#=======================================================================
|
||||
|
||||
add_library(boost_redis_project_options INTERFACE)
|
||||
target_link_libraries(boost_redis_project_options INTERFACE OpenSSL::Crypto OpenSSL::SSL)
|
||||
if (MSVC)
|
||||
target_compile_options(boost_redis_project_options INTERFACE /bigobj)
|
||||
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601)
|
||||
endif()
|
||||
|
||||
add_library(boost_redis_src STATIC examples/boost_redis.cpp)
|
||||
target_compile_features(boost_redis_src PRIVATE cxx_std_17)
|
||||
target_link_libraries(boost_redis_src PRIVATE boost_redis_project_options)
|
||||
|
||||
# Executables
|
||||
#=======================================================================
|
||||
|
||||
#add_executable(intro_sync examples/intro_sync.cpp) // Uncomment after update to Boost 1.80
|
||||
add_executable(chat_room examples/chat_room.cpp)
|
||||
add_executable(containers examples/containers.cpp)
|
||||
add_executable(echo_server examples/echo_server.cpp)
|
||||
add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
|
||||
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
|
||||
add_executable(intro examples/intro.cpp)
|
||||
add_executable(intro_tls examples/intro_tls.cpp)
|
||||
add_executable(low_level_sync examples/low_level_sync.cpp)
|
||||
add_executable(serialization examples/serialization.cpp)
|
||||
add_executable(subscriber examples/subscriber.cpp)
|
||||
add_executable(subscriber_sentinel examples/subscriber_sentinel.cpp)
|
||||
add_executable(test_conn_connect tests/conn_connect.cpp)
|
||||
add_executable(test_conn_exec tests/conn_exec.cpp)
|
||||
add_executable(test_conn_push tests/conn_push.cpp)
|
||||
add_executable(test_conn_quit tests/conn_quit.cpp)
|
||||
add_executable(test_conn_quit_coalesce tests/conn_quit_coalesce.cpp)
|
||||
add_executable(test_conn_reconnect tests/conn_reconnect.cpp)
|
||||
add_executable(test_conn_tls tests/conn_tls.cpp)
|
||||
add_executable(test_low_level tests/low_level.cpp)
|
||||
add_executable(test_conn_run_cancel tests/conn_run_cancel.cpp)
|
||||
add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp)
|
||||
add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp)
|
||||
add_executable(test_request tests/request.cpp)
|
||||
if (BOOST_REDIS_BENCHMARKS)
|
||||
add_library(benchmarks_options INTERFACE)
|
||||
target_link_libraries(benchmarks_options INTERFACE boost_redis_src)
|
||||
target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options)
|
||||
target_compile_features(benchmarks_options INTERFACE cxx_std_20)
|
||||
|
||||
target_compile_features(chat_room PUBLIC cxx_std_20)
|
||||
target_compile_features(containers PUBLIC cxx_std_20)
|
||||
target_compile_features(echo_server PUBLIC cxx_std_20)
|
||||
target_compile_features(echo_server_client PUBLIC cxx_std_20)
|
||||
target_compile_features(echo_server_direct PUBLIC cxx_std_20)
|
||||
target_compile_features(intro PUBLIC cxx_std_17)
|
||||
target_compile_features(intro_tls PUBLIC cxx_std_17)
|
||||
target_compile_features(low_level_sync PUBLIC cxx_std_17)
|
||||
target_compile_features(serialization PUBLIC cxx_std_17)
|
||||
target_compile_features(subscriber PUBLIC cxx_std_20)
|
||||
target_compile_features(subscriber_sentinel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_connect PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_exec PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_push PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_quit PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_quit_coalesce PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_reconnect PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_tls PUBLIC cxx_std_17)
|
||||
target_compile_features(test_low_level PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_run_cancel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20)
|
||||
target_compile_features(test_request PUBLIC cxx_std_17)
|
||||
add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
|
||||
target_link_libraries(echo_server_client PRIVATE benchmarks_options)
|
||||
|
||||
target_link_libraries(intro_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
target_link_libraries(test_conn_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
|
||||
target_link_libraries(echo_server_direct PRIVATE benchmarks_options)
|
||||
endif()
|
||||
|
||||
# Tests
|
||||
#=======================================================================
|
||||
if (BOOST_REDIS_EXAMPLES)
|
||||
add_library(examples_main STATIC examples/main.cpp)
|
||||
target_compile_features(examples_main PRIVATE cxx_std_20)
|
||||
target_link_libraries(examples_main PRIVATE boost_redis_project_options)
|
||||
|
||||
add_test(containers containers)
|
||||
add_test(intro intro)
|
||||
add_test(intro_tls intro_tls)
|
||||
#add_test(intro_sync intro_sync)
|
||||
add_test(serialization serialization)
|
||||
add_test(low_level_sync low_level_sync)
|
||||
add_test(test_low_level test_low_level)
|
||||
add_test(test_conn_exec test_conn_exec)
|
||||
add_test(test_conn_connect test_conn_connect)
|
||||
add_test(test_conn_push test_conn_push)
|
||||
add_test(test_conn_quit test_conn_quit)
|
||||
add_test(test_conn_quit_coalesce test_conn_quit_coalesce)
|
||||
add_test(test_conn_reconnect test_conn_reconnect)
|
||||
add_test(test_conn_tls test_conn_tls)
|
||||
add_test(test_conn_run_cancel test_conn_run_cancel)
|
||||
add_test(test_conn_exec_cancel test_conn_exec_cancel)
|
||||
add_test(test_conn_echo_stress test_conn_echo_stress)
|
||||
add_test(test_request test_request)
|
||||
macro(make_example EXAMPLE_NAME STANDARD)
|
||||
add_executable(${EXAMPLE_NAME} examples/${EXAMPLE_NAME}.cpp)
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src)
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options)
|
||||
target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD})
|
||||
if (${STANDARD} STREQUAL "20")
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(make_testable_example EXAMPLE_NAME STANDARD)
|
||||
make_example(${EXAMPLE_NAME} ${STANDARD})
|
||||
add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME})
|
||||
endmacro()
|
||||
|
||||
make_testable_example(cpp17_intro 17)
|
||||
make_testable_example(cpp17_intro_sync 17)
|
||||
|
||||
make_testable_example(cpp20_intro 20)
|
||||
make_testable_example(cpp20_containers 20)
|
||||
make_testable_example(cpp20_json 20)
|
||||
make_testable_example(cpp20_intro_tls 20)
|
||||
|
||||
make_example(cpp20_subscriber 20)
|
||||
make_example(cpp20_streams 20)
|
||||
make_example(cpp20_echo_server 20)
|
||||
make_example(cpp20_resolve_with_sentinel 20)
|
||||
|
||||
# We test the protobuf example only on gcc.
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
find_package(Protobuf)
|
||||
if (Protobuf_FOUND)
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto)
|
||||
make_testable_example(cpp20_protobuf 20)
|
||||
target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES})
|
||||
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT MSVC)
|
||||
make_example(cpp20_chat_room 20)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (BOOST_REDIS_TESTS)
|
||||
enable_testing()
|
||||
|
||||
add_library(tests_common STATIC tests/common.cpp)
|
||||
target_compile_features(tests_common PRIVATE cxx_std_17)
|
||||
target_link_libraries(tests_common PRIVATE boost_redis_project_options)
|
||||
|
||||
macro(make_test TEST_NAME STANDARD)
|
||||
add_executable(${TEST_NAME} tests/${TEST_NAME}.cpp)
|
||||
target_link_libraries(${TEST_NAME} PRIVATE boost_redis_src tests_common)
|
||||
target_link_libraries(${TEST_NAME} PRIVATE boost_redis_project_options)
|
||||
target_compile_features(${TEST_NAME} PRIVATE cxx_std_${STANDARD})
|
||||
add_test(${TEST_NAME} ${TEST_NAME})
|
||||
endmacro()
|
||||
|
||||
make_test(test_conn_quit 17)
|
||||
make_test(test_conn_tls 17)
|
||||
make_test(test_low_level 17)
|
||||
make_test(test_conn_exec_retry 17)
|
||||
make_test(test_conn_exec_error 17)
|
||||
make_test(test_request 17)
|
||||
make_test(test_run 17)
|
||||
make_test(test_low_level_sync 17)
|
||||
make_test(test_low_level_sync_sans_io 17)
|
||||
make_test(test_conn_check_health 17)
|
||||
|
||||
make_test(test_conn_exec 20)
|
||||
make_test(test_conn_push 20)
|
||||
make_test(test_conn_reconnect 20)
|
||||
make_test(test_conn_exec_cancel 20)
|
||||
make_test(test_conn_exec_cancel2 20)
|
||||
make_test(test_conn_echo_stress 20)
|
||||
make_test(test_low_level_async 20)
|
||||
make_test(test_conn_run_cancel 20)
|
||||
make_test(test_issue_50 20)
|
||||
endif()
|
||||
|
||||
# Install
|
||||
#=======================================================================
|
||||
|
||||
install(TARGETS aedis
|
||||
EXPORT aedis
|
||||
PUBLIC_HEADER DESTINATION include COMPONENT Development
|
||||
)
|
||||
if (BOOST_REDIS_INSTALL)
|
||||
install(TARGETS boost_redis
|
||||
EXPORT boost_redis
|
||||
PUBLIC_HEADER DESTINATION include COMPONENT Development
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
configure_package_config_file(
|
||||
"${PROJECT_SOURCE_DIR}/cmake/BoostRedisConfig.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake"
|
||||
INSTALL_DESTINATION lib/cmake/boost/redis
|
||||
)
|
||||
|
||||
install(EXPORT boost_redis DESTINATION lib/cmake/boost/redis)
|
||||
install(FILES "${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake"
|
||||
"${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake"
|
||||
DESTINATION lib/cmake/boost/redis)
|
||||
|
||||
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake"
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
|
||||
configure_package_config_file(
|
||||
"${PROJECT_SOURCE_DIR}/cmake/AedisConfig.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/AedisConfig.cmake"
|
||||
INSTALL_DESTINATION lib/cmake/aedis
|
||||
)
|
||||
|
||||
install(EXPORT aedis DESTINATION lib/cmake/aedis)
|
||||
install(FILES "${PROJECT_BINARY_DIR}/AedisConfigVersion.cmake"
|
||||
"${PROJECT_BINARY_DIR}/AedisConfig.cmake"
|
||||
DESTINATION lib/cmake/aedis)
|
||||
|
||||
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
|
||||
include(CPack)
|
||||
endif()
|
||||
|
||||
# Doxygen
|
||||
#=======================================================================
|
||||
|
||||
set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doc")
|
||||
configure_file(doc/Doxyfile.in doc/Doxyfile @ONLY)
|
||||
|
||||
add_custom_target(
|
||||
doc
|
||||
COMMAND doxygen "${PROJECT_BINARY_DIR}/doc/Doxyfile"
|
||||
COMMENT "Building documentation using Doxygen"
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
VERBATIM
|
||||
)
|
||||
if (BOOST_REDIS_DOC)
|
||||
set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doc")
|
||||
configure_file(doc/Doxyfile.in doc/Doxyfile @ONLY)
|
||||
|
||||
add_custom_target(
|
||||
doc
|
||||
COMMAND doxygen "${PROJECT_BINARY_DIR}/doc/Doxyfile"
|
||||
COMMENT "Building documentation using Doxygen"
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
# Coverage
|
||||
#=======================================================================
|
||||
@@ -191,11 +253,6 @@ add_custom_target(
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Distribution
|
||||
#=======================================================================
|
||||
|
||||
include(CPack)
|
||||
|
||||
# TODO
|
||||
#=======================================================================
|
||||
|
||||
|
||||
@@ -40,26 +40,97 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dev",
|
||||
"name": "g++-11",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/dev",
|
||||
"binaryDir": "${sourceDir}/build/g++-11",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_CXX_COMPILER": "g++-11",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/dev",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/dev/doc/"
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11/doc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "g++-11-release",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/g++-11-release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra",
|
||||
"CMAKE_CXX_COMPILER": "g++-11",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-release",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11-release/doc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang++-13",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/clang++-13",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_CXX_COMPILER": "clang++-13",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/clang++-13/doc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libc++-14-cpp17",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/libc++-14-cpp17",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -stdlib=libc++ -std=c++17",
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-lc++",
|
||||
"CMAKE_CXX_COMPILER": "clang++-14",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp17",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/libc++-14-cpp17/doc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libc++-14-cpp20",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/libc++-14-cpp20",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -stdlib=libc++ -std=c++17",
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-lc++",
|
||||
"CMAKE_CXX_COMPILER": "clang++-14",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp20",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/libc++-14-cpp20/doc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang-tidy",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["dev"],
|
||||
"inherits": ["g++-11"],
|
||||
"binaryDir": "${sourceDir}/build/clang-tidy",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=${sourceDir}/include/*",
|
||||
@@ -69,7 +140,11 @@
|
||||
],
|
||||
"buildPresets": [
|
||||
{ "name": "coverage", "configurePreset": "coverage" },
|
||||
{ "name": "dev", "configurePreset": "dev" },
|
||||
{ "name": "g++-11", "configurePreset": "g++-11" },
|
||||
{ "name": "g++-11-release", "configurePreset": "g++-11-release" },
|
||||
{ "name": "clang++-13", "configurePreset": "clang++-13" },
|
||||
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17" },
|
||||
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20" },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy" }
|
||||
],
|
||||
"testPresets": [
|
||||
@@ -79,8 +154,12 @@
|
||||
"output": {"outputOnFailure": true},
|
||||
"execution": {"noTestsAction": "error", "stopOnFailure": true}
|
||||
},
|
||||
{ "name": "coverage", "configurePreset": "coverage", "inherits": ["test"] },
|
||||
{ "name": "dev", "configurePreset": "dev", "inherits": ["test"] },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy", "inherits": ["test"] }
|
||||
{ "name": "coverage", "configurePreset": "coverage", "inherits": ["test"] },
|
||||
{ "name": "g++-11", "configurePreset": "g++-11", "inherits": ["test"] },
|
||||
{ "name": "g++-11-release", "configurePreset": "g++-11-release", "inherits": ["test"] },
|
||||
{ "name": "clang++-13", "configurePreset": "clang++-13", "inherits": ["test"] },
|
||||
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17", "inherits": ["test"] },
|
||||
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20", "inherits": ["test"] },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy", "inherits": ["test"] }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ using net::ip::tcp;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using timer_type = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
|
||||
net::awaitable<void>
|
||||
example(boost::asio::ip::tcp::endpoint ep, std::string msg, int n)
|
||||
auto example(boost::asio::ip::tcp::endpoint ep, std::string msg, int n) -> net::awaitable<void>
|
||||
{
|
||||
try {
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
@@ -33,7 +33,7 @@ awaitable_type echo(tcp_socket socket)
|
||||
std::size_t n = co_await socket.async_read_some(net::buffer(data), use_awaitable);
|
||||
co_await async_write(socket, net::buffer(data, n), use_awaitable);
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
} catch (std::exception const&) {
|
||||
//std::printf("echo Exception: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
4
benchmarks/nodejs/README.txt
Normal file
4
benchmarks/nodejs/README.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
$ npm install
|
||||
|
||||
$ node echo_server_over_redis.js
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createClient } from 'redis';
|
||||
import * as net from 'net';
|
||||
|
||||
const client = createClient({url: 'redis://db.occase.de:6379' });
|
||||
const client = createClient({url: 'redis://aedis.occase.de:63799' });
|
||||
client.on('error', (err) => console.log('Redis Client Error', err));
|
||||
await client.connect();
|
||||
|
||||
|
||||
@@ -1250,7 +1250,7 @@ HTML_FILE_EXTENSION = .html
|
||||
# of the possible markers and block names see the documentation.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_HEADER = doc/htmlheader.html
|
||||
HTML_HEADER =
|
||||
|
||||
# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
|
||||
# generated HTML page. If the tag is left blank doxygen will generate a standard
|
||||
@@ -1260,7 +1260,7 @@ HTML_HEADER = doc/htmlheader.html
|
||||
# that doxygen normally uses.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_FOOTER = doc/htmlfooter.html
|
||||
HTML_FOOTER =
|
||||
|
||||
# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
|
||||
# sheet that is used by each HTML page. It can be used to fine-tune the look of
|
||||
@@ -1285,7 +1285,7 @@ HTML_STYLESHEET =
|
||||
# list). For an example see the documentation.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_EXTRA_STYLESHEET = doc/aedis.css
|
||||
HTML_EXTRA_STYLESHEET = doc/doxygen-awesome.css doc/doxygen-awesome-sidebar-only.css
|
||||
|
||||
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
|
||||
# other source files which should be copied to the HTML output directory. Note
|
||||
@@ -1578,7 +1578,7 @@ ECLIPSE_DOC_ID = org.doxygen.Project
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
DISABLE_INDEX = YES
|
||||
DISABLE_INDEX = NO
|
||||
|
||||
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
|
||||
# structure should be generated to display hierarchical information. If the tag
|
||||
@@ -1595,7 +1595,7 @@ DISABLE_INDEX = YES
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
GENERATE_TREEVIEW = NO
|
||||
GENERATE_TREEVIEW = YES
|
||||
|
||||
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
|
||||
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Navigation index tabs for HTML output -->
|
||||
<navindex>
|
||||
<tab type="mainpage" visible="yes" title="Contents"/>
|
||||
<tab type="pages" visible="no" title="" intro=""/>
|
||||
<tab type="pages" visible="yes" title="" intro=""/>
|
||||
<tab type="modules" visible="no" title="Reference" intro=""/>
|
||||
<tab type="namespaces" visible="no" title="">
|
||||
<tab type="namespacelist" visible="yes" title="" intro=""/>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/* Doxygen HTML_EXTRA_STYLESHEET */
|
||||
|
||||
div.contents {
|
||||
max-width: 100em;
|
||||
margin-right: 5em;
|
||||
margin-left: 5em;
|
||||
}
|
||||
|
||||
.ui-resizable-e {
|
||||
background-image:url("splitbar.png");
|
||||
background-size:100%;
|
||||
background-repeat:repeat-y;
|
||||
background-attachment: scroll;
|
||||
cursor:ew-resize;
|
||||
height:100%;
|
||||
right:0;
|
||||
top:0;
|
||||
width:1px;
|
||||
}
|
||||
|
||||
.pyrootbox {
|
||||
border: 1px solid #879ecb;
|
||||
background-color: #f9fafc;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
code
|
||||
{
|
||||
background-color:#fffbeb;
|
||||
}
|
||||
115
doc/doxygen-awesome-sidebar-only.css
Normal file
115
doc/doxygen-awesome-sidebar-only.css
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 jothepro
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 335px;
|
||||
--menu-display: none;
|
||||
|
||||
--top-height: 120px;
|
||||
--toc-sticky-top: -25px;
|
||||
--toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px);
|
||||
}
|
||||
|
||||
#projectname {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
html {
|
||||
--searchbar-background: var(--page-background-color);
|
||||
}
|
||||
|
||||
#side-nav {
|
||||
min-width: var(--side-nav-fixed-width);
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
top: var(--top-height);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#nav-tree, #side-nav {
|
||||
height: calc(100vh - var(--top-height)) !important;
|
||||
}
|
||||
|
||||
#nav-tree {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#top {
|
||||
display: block;
|
||||
border-bottom: none;
|
||||
height: var(--top-height);
|
||||
margin-bottom: calc(0px - var(--top-height));
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
overflow: hidden;
|
||||
background: var(--side-nav-background);
|
||||
}
|
||||
#main-nav {
|
||||
float: left;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ui-resizable-handle {
|
||||
cursor: default;
|
||||
width: 1px !important;
|
||||
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
|
||||
}
|
||||
|
||||
#nav-path {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: var(--side-nav-fixed-width);
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#doc-content {
|
||||
height: calc(100vh - 31px) !important;
|
||||
padding-bottom: calc(3 * var(--spacing-large));
|
||||
padding-top: calc(var(--top-height) - 80px);
|
||||
box-sizing: border-box;
|
||||
margin-left: var(--side-nav-fixed-width) !important;
|
||||
}
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
|
||||
}
|
||||
|
||||
#MSearchResultsWindow {
|
||||
left: var(--spacing-medium) !important;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
2405
doc/doxygen-awesome.css
Normal file
2405
doc/doxygen-awesome.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
<!-- HTML footer for doxygen 1.8.14-->
|
||||
<!-- start footer part -->
|
||||
<!--BEGIN GENERATE_TREEVIEW-->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
$navpath
|
||||
<li class="footer">
|
||||
Aedis 1.0.0 - Reference Guide generated on $datetime using Doxygen $doxygenversion   
|
||||
<img class="footer" src="rootlogo_s.gif" alt="root"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--END GENERATE_TREEVIEW-->
|
||||
<!--BEGIN !GENERATE_TREEVIEW-->
|
||||
<hr class="footer"/><address class="footer">
|
||||
Author: Marcelo Zimbres Silva.
|
||||
</address>
|
||||
<!--END !GENERATE_TREEVIEW-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,34 +0,0 @@
|
||||
<!-- HTML header for doxygen 1.8.14-->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen $doxygenversion"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
$search
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
$extrastylesheet
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table bgcolor="#346295" cellspacing="0" cellpadding="6">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="middle" style="color: #FFFFFF" nowrap="nowrap"><font size="6">$projectname $projectnumber</font>   <br> $projectbrief </td>
|
||||
<td style="width:100%"> $searchbox </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
7
examples/boost_redis.cpp
Normal file
7
examples/boost_redis.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/src.hpp>
|
||||
@@ -1,106 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "unistd.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using tcp_acceptor = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::acceptor>;
|
||||
using stream_descriptor = net::use_awaitable_t<>::as_default_on_t<net::posix::stream_descriptor>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
|
||||
// Chat over redis pubsub. To test, run this program from different
|
||||
// terminals and type messages to stdin. Use
|
||||
//
|
||||
// $ redis-cli monitor
|
||||
//
|
||||
// to monitor the message traffic.
|
||||
|
||||
// Receives messages from other users.
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribes to the channels when a new connection is stablished.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SUBSCRIBE", "chat-channel");
|
||||
|
||||
stimer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (;;) {
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn->async_exec(req, adapt(), net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
std::clog << "async_run: " << ec1.message() << "\n"
|
||||
<< "async_exec: " << ec2.message() << std::endl;
|
||||
conn->reset_stream();
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Publishes messages to other users.
|
||||
net::awaitable<void> publisher(stream_descriptor& in, std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (std::string msg;;) {
|
||||
auto n = co_await net::async_read_until(in, net::dynamic_buffer(msg, 1024), "\n");
|
||||
request req;
|
||||
req.push("PUBLISH", "chat-channel", msg);
|
||||
co_await conn->async_exec(req);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
stream_descriptor in{ioc, ::dup(STDIN_FILENO)};
|
||||
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
co_spawn(ioc, publisher(in, conn), net::detached);
|
||||
co_spawn(ioc, push_receiver(conn), net::detached);
|
||||
co_spawn(ioc, reconnect(conn), net::detached);
|
||||
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
@@ -1,125 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
// To avoid verbosity.
|
||||
auto redir(boost::system::error_code& ec)
|
||||
{
|
||||
return net::redirect_error(net::use_awaitable, ec);
|
||||
}
|
||||
|
||||
// Sends some containers.
|
||||
net::awaitable<void> send(endpoint ep)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::map<std::string, std::string> map
|
||||
{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push_range("RPUSH", "rpush-key", vec); // Sends
|
||||
req.push_range("HSET", "hset-key", map); // Sends
|
||||
req.push("QUIT");
|
||||
|
||||
connection conn{ex};
|
||||
co_await (conn.async_run(ep) || conn.async_exec(req));
|
||||
}
|
||||
|
||||
// Retrieves a Redis hash as an std::map.
|
||||
net::awaitable<std::map<std::string, std::string>> retrieve_hashes(endpoint ep)
|
||||
{
|
||||
connection conn{co_await net::this_coro::executor};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HGETALL", "hset-key");
|
||||
req.push("QUIT");
|
||||
|
||||
std::map<std::string, std::string> ret;
|
||||
auto resp = std::tie(ret, std::ignore);
|
||||
co_await (conn.async_run(ep) || conn.async_exec(req, adapt(resp)));
|
||||
|
||||
co_return std::move(ret);
|
||||
}
|
||||
|
||||
// Retrieves as a data structure.
|
||||
net::awaitable<void> transaction(endpoint ep)
|
||||
{
|
||||
connection conn{co_await net::this_coro::executor};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("MULTI");
|
||||
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
|
||||
req.push("HGETALL", "hset-key"); // Retrieves
|
||||
req.push("EXEC");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<
|
||||
aedis::ignore, // multi
|
||||
aedis::ignore, // lrange
|
||||
aedis::ignore, // hgetall
|
||||
std::tuple<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>>, // exec
|
||||
aedis::ignore // quit
|
||||
> resp;
|
||||
|
||||
co_await (conn.async_run(ep) || conn.async_exec(req, adapt(resp)));
|
||||
|
||||
print(std::get<0>(std::get<3>(resp)).value());
|
||||
print(std::get<1>(std::get<3>(resp)).value());
|
||||
}
|
||||
|
||||
net::awaitable<void> async_main()
|
||||
{
|
||||
try {
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
co_await send(ep);
|
||||
co_await transaction(ep);
|
||||
auto const hashes = co_await retrieve_hashes(ep);
|
||||
print(hashes);
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_main(), net::detached);
|
||||
ioc.run();
|
||||
} catch (...) {
|
||||
std::cerr << "Error." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
50
examples/cpp17_intro.cpp
Normal file
50
examples/cpp17_intro.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
|
||||
auto main(int argc, char * argv[]) -> int
|
||||
{
|
||||
try {
|
||||
config cfg;
|
||||
|
||||
if (argc == 3) {
|
||||
cfg.addr.host = argv[1];
|
||||
cfg.addr.port = argv[2];
|
||||
}
|
||||
|
||||
request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
conn.async_run(cfg, {}, net::detached);
|
||||
|
||||
conn.async_exec(req, resp, [&](auto ec, auto) {
|
||||
if (!ec)
|
||||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
|
||||
conn.cancel();
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
44
examples/cpp17_intro_sync.cpp
Normal file
44
examples/cpp17_intro_sync.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include "sync_connection.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::sync_connection;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
|
||||
auto main(int argc, char * argv[]) -> int
|
||||
{
|
||||
try {
|
||||
config cfg;
|
||||
|
||||
if (argc == 3) {
|
||||
cfg.addr.host = argv[1];
|
||||
cfg.addr.port = argv[2];
|
||||
}
|
||||
|
||||
sync_connection conn;
|
||||
conn.run(cfg);
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
conn.exec(req, resp);
|
||||
conn.stop();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
99
examples/cpp20_chat_room.cpp
Normal file
99
examples/cpp20_chat_room.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using stream_descriptor = net::deferred_t::as_default_on_t<net::posix::stream_descriptor>;
|
||||
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
|
||||
using boost::redis::request;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::ignore;
|
||||
using net::redirect_error;
|
||||
using net::use_awaitable;
|
||||
using boost::system::error_code;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// Chat over Redis pubsub. To test, run this program from multiple
|
||||
// terminals and type messages to stdin.
|
||||
|
||||
auto
|
||||
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
while (conn->will_reconnect()) {
|
||||
|
||||
// Subscribe to channels.
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
|
||||
// Loop reading Redis push messages.
|
||||
for (generic_response resp;;) {
|
||||
error_code ec;
|
||||
co_await conn->async_receive(resp, redirect_error(use_awaitable, ec));
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
std::cout
|
||||
<< resp.value().at(1).value
|
||||
<< " " << resp.value().at(2).value
|
||||
<< " " << resp.value().at(3).value
|
||||
<< std::endl;
|
||||
resp.value().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Publishes stdin messages to a Redis channel.
|
||||
auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
for (std::string msg;;) {
|
||||
auto n = co_await net::async_read_until(*in, net::dynamic_buffer(msg, 1024), "\n");
|
||||
request req;
|
||||
req.push("PUBLISH", "channel", msg);
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
auto stream = std::make_shared<stream_descriptor>(ex, ::dup(STDIN_FILENO));
|
||||
|
||||
net::co_spawn(ex, receiver(conn), net::detached);
|
||||
net::co_spawn(ex, publisher(stream, conn), net::detached);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
signal_set sig_set{ex, SIGINT, SIGTERM};
|
||||
co_await sig_set.async_wait();
|
||||
conn->cancel();
|
||||
stream->cancel();
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
auto co_main(config const&) -> net::awaitable<void>
|
||||
{
|
||||
std::cout << "Requires support for posix streams." << std::endl;
|
||||
co_return;
|
||||
}
|
||||
#endif // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
102
examples/cpp20_containers.cpp
Normal file
102
examples/cpp20_containers.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
void print(std::map<std::string, std::string> const& cont)
|
||||
{
|
||||
for (auto const& e: cont)
|
||||
std::cout << e.first << ": " << e.second << "\n";
|
||||
}
|
||||
|
||||
void print(std::vector<int> const& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
// Stores the content of some STL containers in Redis.
|
||||
auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::map<std::string, std::string> map
|
||||
{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
|
||||
|
||||
request req;
|
||||
req.push_range("RPUSH", "rpush-key", vec);
|
||||
req.push_range("HSET", "hset-key", map);
|
||||
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
}
|
||||
|
||||
auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
// A request contains multiple commands.
|
||||
request req;
|
||||
req.push("HGETALL", "hset-key");
|
||||
|
||||
// Responses as tuple elements.
|
||||
response<std::map<std::string, std::string>> resp;
|
||||
|
||||
// Executes the request and reads the response.
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
|
||||
print(std::get<0>(resp).value());
|
||||
}
|
||||
|
||||
// Retrieves in a transaction.
|
||||
auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("MULTI");
|
||||
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
|
||||
req.push("HGETALL", "hset-key"); // Retrieves
|
||||
req.push("EXEC");
|
||||
|
||||
response<
|
||||
ignore_t, // multi
|
||||
ignore_t, // lrange
|
||||
ignore_t, // hgetall
|
||||
response<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>> // exec
|
||||
> resp;
|
||||
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
|
||||
print(std::get<0>(std::get<3>(resp).value()).value().value());
|
||||
print(std::get<1>(std::get<3>(resp).value()).value().value());
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
net::awaitable<void> co_main(config cfg)
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
co_await store(conn);
|
||||
co_await transaction(conn);
|
||||
co_await hgetall(conn);
|
||||
conn->cancel();
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
70
examples/cpp20_echo_server.cpp
Normal file
70
examples/cpp20_echo_server.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using tcp_socket = net::deferred_t::as_default_on_t<net::ip::tcp::socket>;
|
||||
using tcp_acceptor = net::deferred_t::as_default_on_t<net::ip::tcp::acceptor>;
|
||||
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::system::error_code;
|
||||
using boost::redis::connection;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
response<std::string> resp;
|
||||
|
||||
for (std::string buffer;;) {
|
||||
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
|
||||
req.push("PING", buffer);
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
co_await net::async_write(socket, net::buffer(std::get<0>(resp).value()));
|
||||
std::get<0>(resp).value().clear();
|
||||
req.clear();
|
||||
buffer.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
// Listens for tcp connections.
|
||||
auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
try {
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555});
|
||||
for (;;)
|
||||
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached);
|
||||
} catch (std::exception const& e) {
|
||||
std::clog << "Listener: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
net::co_spawn(ex, listener(conn), net::detached);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
conn->cancel();
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
42
examples/cpp20_intro.cpp
Normal file
42
examples/cpp20_intro.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
// A request containing only a ping command.
|
||||
request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
// Response where the PONG response will be stored.
|
||||
response<std::string> resp;
|
||||
|
||||
// Executes the request.
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
54
examples/cpp20_intro_tls.cpp
Normal file
54
examples/cpp20_intro_tls.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
cfg.use_ssl = true;
|
||||
cfg.username = "aedis";
|
||||
cfg.password = "aedis";
|
||||
cfg.addr.host = "db.occase.de";
|
||||
cfg.addr.port = "6380";
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
conn->next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn->next_layer().set_verify_callback(verify_certificate);
|
||||
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
77
examples/cpp20_json.cpp
Normal file
77
examples/cpp20_json.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/describe.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
#define BOOST_JSON_NO_LIB
|
||||
#define BOOST_CONTAINER_NO_LIB
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value_from.hpp>
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
#include <boost/json/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace boost::describe;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
// Struct that will be stored in Redis using json serialization.
|
||||
struct user {
|
||||
std::string name;
|
||||
std::string age;
|
||||
std::string country;
|
||||
};
|
||||
|
||||
// The type must be described for serialization to work.
|
||||
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
|
||||
|
||||
// Boost.Redis customization points (examples/json.hpp)
|
||||
void boost_redis_to_bulk(std::string& to, user const& u)
|
||||
{ boost::redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u))); }
|
||||
|
||||
void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code&)
|
||||
{ u = boost::json::value_to<user>(boost::json::parse(sv)); }
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
// user object that will be stored in Redis in json format.
|
||||
user const u{"Joao", "58", "Brazil"};
|
||||
|
||||
// Stores and retrieves in the same request.
|
||||
request req;
|
||||
req.push("SET", "json-key", u); // Stores in Redis.
|
||||
req.push("GET", "json-key"); // Retrieves from Redis.
|
||||
|
||||
response<ignore_t, user> resp;
|
||||
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
conn->cancel();
|
||||
|
||||
// Prints the first ping
|
||||
std::cout
|
||||
<< "Name: " << std::get<1>(resp).value().name << "\n"
|
||||
<< "Age: " << std::get<1>(resp).value().age << "\n"
|
||||
<< "Country: " << std::get<1>(resp).value().country << "\n";
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
88
examples/cpp20_protobuf.cpp
Normal file
88
examples/cpp20_protobuf.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <iostream>
|
||||
|
||||
// See the definition in person.proto. This header is automatically
|
||||
// generated by CMakeLists.txt.
|
||||
#include "person.pb.h"
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
// The protobuf type described in examples/person.proto
|
||||
using tutorial::person;
|
||||
|
||||
// Boost.Redis customization points (examples/protobuf.hpp)
|
||||
namespace tutorial
|
||||
{
|
||||
|
||||
// Below I am using a Boost.Redis to indicate a protobuf error, this
|
||||
// is ok for an example, users however might want to define their own
|
||||
// error codes.
|
||||
void boost_redis_to_bulk(std::string& to, person const& u)
|
||||
{
|
||||
std::string tmp;
|
||||
if (!u.SerializeToString(&tmp))
|
||||
throw boost::system::system_error(boost::redis::error::invalid_data_type);
|
||||
|
||||
boost::redis::resp3::boost_redis_to_bulk(to, tmp);
|
||||
}
|
||||
|
||||
void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_code& ec)
|
||||
{
|
||||
std::string const tmp {sv};
|
||||
if (!u.ParseFromString(tmp))
|
||||
ec = boost::redis::error::invalid_data_type;
|
||||
}
|
||||
|
||||
} // tutorial
|
||||
|
||||
using tutorial::boost_redis_to_bulk;
|
||||
using tutorial::boost_redis_from_bulk;
|
||||
|
||||
net::awaitable<void> co_main(config cfg)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
person p;
|
||||
p.set_name("Louis");
|
||||
p.set_id(3);
|
||||
p.set_email("No email yet.");
|
||||
|
||||
request req;
|
||||
req.push("SET", "protobuf-key", p);
|
||||
req.push("GET", "protobuf-key");
|
||||
|
||||
response<ignore_t, person> resp;
|
||||
|
||||
// Sends the request and receives the response.
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
conn->cancel();
|
||||
|
||||
std::cout
|
||||
<< "Name: " << std::get<1>(resp).value().name() << "\n"
|
||||
<< "Age: " << std::get<1>(resp).value().id() << "\n"
|
||||
<< "Email: " << std::get<1>(resp).value().email() << "\n";
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
75
examples/cpp20_resolve_with_sentinel.cpp
Normal file
75
examples/cpp20_resolve_with_sentinel.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using endpoints = net::ip::tcp::resolver::results_type;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::config;
|
||||
using boost::redis::address;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto redir(boost::system::error_code& ec)
|
||||
{ return net::redirect_error(net::use_awaitable, ec); }
|
||||
|
||||
// For more info see
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
auto resolve_master_address(std::vector<address> const& addresses) -> net::awaitable<address>
|
||||
{
|
||||
request req;
|
||||
req.push("SENTINEL", "get-master-addr-by-name", "mymaster");
|
||||
req.push("QUIT");
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
|
||||
response<std::optional<std::array<std::string, 2>>, ignore_t> resp;
|
||||
for (auto addr : addresses) {
|
||||
boost::system::error_code ec;
|
||||
config cfg;
|
||||
cfg.addr = addr;
|
||||
// TODO: async_run and async_exec should be lauched in
|
||||
// parallel here so we can wait for async_run completion
|
||||
// before eventually calling it again.
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
co_await conn->async_exec(req, resp, redir(ec));
|
||||
conn->cancel();
|
||||
conn->reset_stream();
|
||||
if (!ec && std::get<0>(resp))
|
||||
co_return address{std::get<0>(resp).value().value().at(0), std::get<0>(resp).value().value().at(1)};
|
||||
}
|
||||
|
||||
co_return address{};
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
// A list of sentinel addresses from which only one is responsive.
|
||||
// This simulates sentinels that are down.
|
||||
std::vector<address> const addresses
|
||||
{ address{"foo", "26379"}
|
||||
, address{"bar", "26379"}
|
||||
, cfg.addr
|
||||
};
|
||||
|
||||
auto const ep = co_await resolve_master_address(addresses);
|
||||
|
||||
std::clog
|
||||
<< "Host: " << ep.host << "\n"
|
||||
<< "Port: " << ep.port << "\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
98
examples/cpp20_streams.cpp
Normal file
98
examples/cpp20_streams.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::config;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::request;
|
||||
using boost::redis::connection;
|
||||
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
|
||||
|
||||
auto stream_reader(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
std::string redisStreamKey_;
|
||||
request req;
|
||||
generic_response resp;
|
||||
|
||||
std::string stream_id{"$"};
|
||||
std::string const field = "myfield";
|
||||
|
||||
for (;;) {
|
||||
req.push("XREAD", "BLOCK", "0", "STREAMS", "test-topic", stream_id);
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
|
||||
//std::cout << "Response: ";
|
||||
//for (auto i = 0UL; i < resp->size(); ++i) {
|
||||
// std::cout << resp->at(i).value << ", ";
|
||||
//}
|
||||
//std::cout << std::endl;
|
||||
|
||||
// The following approach was taken in order to be able to
|
||||
// deal with the responses, as generated by redis in the case
|
||||
// that there are multiple stream 'records' within a single
|
||||
// generic_response. The nesting and number of values in
|
||||
// resp.value() are different, depending on the contents
|
||||
// of the stream in redis. Uncomment the above commented-out
|
||||
// code for examples while running the XADD command.
|
||||
|
||||
std::size_t item_index = 0;
|
||||
while (item_index < std::size(resp.value())) {
|
||||
auto const& val = resp.value().at(item_index).value;
|
||||
|
||||
if (field.compare(val) == 0) {
|
||||
// We've hit a myfield field.
|
||||
// The streamId is located at item_index - 2
|
||||
// The payload is located at item_index + 1
|
||||
stream_id = resp.value().at(item_index - 2).value;
|
||||
std::cout
|
||||
<< "StreamId: " << stream_id << ", "
|
||||
<< "MyField: " << resp.value().at(item_index + 1).value
|
||||
<< std::endl;
|
||||
++item_index; // We can increase so we don't read this again
|
||||
}
|
||||
|
||||
++item_index;
|
||||
}
|
||||
|
||||
req.clear();
|
||||
resp.value().clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Run this in another terminal:
|
||||
// redis-cli -r 100000 -i 0.0001 XADD "test-topic" "*" "myfield" "myfieldvalue1"
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
net::co_spawn(ex, stream_reader(conn), net::detached);
|
||||
|
||||
// Disable health checks.
|
||||
cfg.health_check_interval = std::chrono::seconds::zero();
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
conn->cancel();
|
||||
}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
90
examples/cpp20_subscriber.cpp
Normal file
90
examples/cpp20_subscriber.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace std::chrono_literals;
|
||||
using boost::redis::request;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::config;
|
||||
using boost::redis::ignore;
|
||||
using boost::system::error_code;
|
||||
using boost::redis::connection;
|
||||
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
|
||||
|
||||
/* This example will subscribe and read pushes indefinitely.
|
||||
*
|
||||
* To test send messages with redis-cli
|
||||
*
|
||||
* $ redis-cli -3
|
||||
* 127.0.0.1:6379> PUBLISH channel some-message
|
||||
* (integer) 3
|
||||
* 127.0.0.1:6379>
|
||||
*
|
||||
* To test reconnection try, for example, to close all clients currently
|
||||
* connected to the Redis instance
|
||||
*
|
||||
* $ redis-cli
|
||||
* > CLIENT kill TYPE pubsub
|
||||
*/
|
||||
|
||||
// Receives server pushes.
|
||||
auto
|
||||
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
// Loop while reconnection is enabled
|
||||
while (conn->will_reconnect()) {
|
||||
|
||||
// Reconnect to channels.
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
|
||||
// Loop reading Redis pushs messages.
|
||||
for (generic_response resp;;) {
|
||||
error_code ec;
|
||||
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
std::cout
|
||||
<< resp.value().at(1).value
|
||||
<< " " << resp.value().at(2).value
|
||||
<< " " << resp.value().at(3).value
|
||||
<< std::endl;
|
||||
resp.value().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
net::co_spawn(ex, receiver(conn), net::detached);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
|
||||
conn->cancel();
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
@@ -1,87 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <aedis.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using executor_type = net::io_context::executor_type;
|
||||
using socket_type = net::basic_stream_socket<net::ip::tcp, executor_type>;
|
||||
using tcp_socket = net::use_awaitable_t<executor_type>::as_default_on_t<socket_type>;
|
||||
using acceptor_type = net::basic_socket_acceptor<net::ip::tcp, executor_type>;
|
||||
using tcp_acceptor = net::use_awaitable_t<executor_type>::as_default_on_t<acceptor_type>;
|
||||
using awaitable_type = net::awaitable<void, executor_type>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
awaitable_type echo_server_session(tcp_socket socket, std::shared_ptr<connection> db)
|
||||
{
|
||||
request req;
|
||||
std::tuple<std::string> response;
|
||||
|
||||
for (std::string buffer;;) {
|
||||
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
|
||||
req.push("PING", buffer);
|
||||
co_await db->async_exec(req, adapt(response));
|
||||
co_await net::async_write(socket, net::buffer(std::get<0>(response)));
|
||||
std::get<0>(response).clear();
|
||||
req.clear();
|
||||
buffer.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
awaitable_type listener(std::shared_ptr<connection> db)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555});
|
||||
for (;;)
|
||||
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), db), net::detached);
|
||||
}
|
||||
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
net::steady_timer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (boost::system::error_code ec1;;) {
|
||||
co_await conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1));
|
||||
std::clog << "async_run: " << ec1.message() << std::endl;
|
||||
conn->reset_stream();
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait(net::use_awaitable);
|
||||
}
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
co_spawn(ioc, reconnect(db), net::detached);
|
||||
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto) {
|
||||
ioc.stop();
|
||||
});
|
||||
|
||||
co_spawn(ioc, listener(db), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
@@ -1,43 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
|
||||
auto const logger = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
boost::asio::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
conn.async_exec(req, adapt(resp), logger);
|
||||
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
|
||||
|
||||
ioc.run();
|
||||
|
||||
std::cout << std::get<0>(resp) << std::endl;
|
||||
} catch (...) {
|
||||
std::cerr << "Error" << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
|
||||
template <class Adapter>
|
||||
auto exec(connection& conn, request const& req, Adapter adapter, boost::system::error_code& ec)
|
||||
{
|
||||
net::dispatch(
|
||||
conn.get_executor(),
|
||||
net::deferred([&]() { return conn.async_exec(req, adapter, net::deferred); }))
|
||||
(net::redirect_error(net::use_future, ec)).get();
|
||||
}
|
||||
|
||||
auto logger = [](auto const& ec)
|
||||
{ std::clog << "Run: " << ec.message() << std::endl; };
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
|
||||
connection conn{ioc};
|
||||
std::thread t{[&]() {
|
||||
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
|
||||
ioc.run();
|
||||
}};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
boost::system::error_code ec;
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
exec(conn, req, adapt(resp), ec);
|
||||
|
||||
std::cout
|
||||
<< "Exec: " << ec.message() << "\n"
|
||||
<< "Response: " << std::get<0>(resp) << std::endl;
|
||||
|
||||
t.join();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/ssl/connection.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::ssl::connection<net::ssl::stream<net::ip::tcp::socket>>;
|
||||
|
||||
auto const logger = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
|
||||
net::ssl::context ctx{net::ssl::context::sslv23};
|
||||
|
||||
connection conn{ioc, ctx};
|
||||
conn.next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn.next_layer().set_verify_callback(verify_certificate);
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
conn.async_exec(req, adapt(resp), logger);
|
||||
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
|
||||
|
||||
ioc.run();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp) << std::endl;
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapter::adapt2;
|
||||
using net::ip::tcp;
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp::socket socket{ioc};
|
||||
net::connect(socket, res);
|
||||
|
||||
// Creates the request and writes to the socket.
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
resp3::write(socket, req);
|
||||
|
||||
// Responses
|
||||
std::string buffer, resp;
|
||||
|
||||
// Reads the responses to all commands in the request.
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
resp3::read(socket, dbuffer);
|
||||
resp3::read(socket, dbuffer, adapt2(resp));
|
||||
resp3::read(socket, dbuffer);
|
||||
|
||||
std::cout << "Ping: " << resp << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
53
examples/main.cpp
Normal file
53
examples/main.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::config;
|
||||
using boost::redis::logger;
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
extern net::awaitable<void> co_main(config);
|
||||
|
||||
auto main(int argc, char * argv[]) -> int
|
||||
{
|
||||
try {
|
||||
config cfg;
|
||||
|
||||
if (argc == 3) {
|
||||
cfg.addr.host = argv[1];
|
||||
cfg.addr.port = argv[2];
|
||||
}
|
||||
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, std::move(co_main(cfg)), [](std::exception_ptr p) {
|
||||
if (p)
|
||||
std::rethrow_exception(p);
|
||||
});
|
||||
ioc.run();
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "(main) " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
std::cout << "Requires coroutine support." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
9
examples/person.proto
Normal file
9
examples/person.proto
Normal file
@@ -0,0 +1,9 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package tutorial;
|
||||
|
||||
message person {
|
||||
optional string name = 1;
|
||||
optional int32 id = 2;
|
||||
optional string email = 3;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
// Some functions to make the examples less repetitive.
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
|
||||
void print_aggr(std::vector<aedis::resp3::node<std::string>>& v)
|
||||
{
|
||||
if (std::empty(v))
|
||||
return;
|
||||
|
||||
auto const m = aedis::resp3::element_multiplicity(v.front().data_type);
|
||||
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
|
||||
std::cout << v[i + 1].value << " ";
|
||||
std::cout << "\n";
|
||||
v.clear();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void print(std::vector<T> const& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void print(std::set<T> const& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << "\n";
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
void print(std::map<T, U> const& cont)
|
||||
{
|
||||
for (auto const& e: cont)
|
||||
std::cout << e.first << ": " << e.second << "\n";
|
||||
}
|
||||
|
||||
void print(std::string const& e)
|
||||
{
|
||||
std::cout << e << std::endl;
|
||||
}
|
||||
|
||||
void print_push(std::vector<aedis::resp3::node<std::string>>& resp)
|
||||
{
|
||||
std::cout
|
||||
<< "Push type: " << resp.at(1).value << "\n"
|
||||
<< "Channel: " << resp.at(2).value << "\n"
|
||||
<< "Message: " << resp.at(3).value << "\n"
|
||||
<< std::endl;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/src.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
using namespace boost::json;
|
||||
|
||||
struct user {
|
||||
std::string name;
|
||||
std::string age;
|
||||
std::string country;
|
||||
};
|
||||
|
||||
void tag_invoke(value_from_tag, value& jv, user const& u)
|
||||
{
|
||||
jv =
|
||||
{ {"name", u.name}
|
||||
, {"age", u.age}
|
||||
, {"country", u.country}
|
||||
};
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void extract(object const& obj, T& t, boost::string_view key)
|
||||
{
|
||||
t = value_to<T>(obj.at(key));
|
||||
}
|
||||
|
||||
auto tag_invoke(value_to_tag<user>, value const& jv)
|
||||
{
|
||||
user u;
|
||||
object const& obj = jv.as_object();
|
||||
extract(obj, u.name, "name");
|
||||
extract(obj, u.age, "age");
|
||||
extract(obj, u.country, "country");
|
||||
return u;
|
||||
}
|
||||
|
||||
// Serializes
|
||||
void to_bulk(std::pmr::string& to, user const& u)
|
||||
{
|
||||
aedis::resp3::to_bulk(to, serialize(value_from(u)));
|
||||
}
|
||||
|
||||
// Deserializes
|
||||
void from_bulk(user& u, boost::string_view sv, boost::system::error_code&)
|
||||
{
|
||||
value jv = parse(sv);
|
||||
u = value_to<user>(jv);
|
||||
}
|
||||
|
||||
auto operator<<(std::ostream& os, user const& u) -> std::ostream&
|
||||
{
|
||||
os << "Name: " << u.name << "\n"
|
||||
<< "Age: " << u.age << "\n"
|
||||
<< "Country: " << u.country;
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
auto operator<(user const& a, user const& b)
|
||||
{
|
||||
return std::tie(a.name, a.age, a.country) < std::tie(b.name, b.age, b.country);
|
||||
}
|
||||
|
||||
auto const logger = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
std::set<user> users
|
||||
{{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("SADD", "sadd-key", users); // Sends
|
||||
req.push("SMEMBERS", "sadd-key"); // Retrieves
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<aedis::ignore, int, std::set<user>, std::string> resp;
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
conn.async_exec(req, adapt(resp),logger);
|
||||
conn.async_run(ep, {}, logger);
|
||||
ioc.run();
|
||||
|
||||
// Print
|
||||
print(std::get<2>(resp));
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
/* This example will subscribe and read pushes indefinitely.
|
||||
*
|
||||
* To test send messages with redis-cli
|
||||
*
|
||||
* $ redis-cli -3
|
||||
* 127.0.0.1:6379> PUBLISH channel some-message
|
||||
* (integer) 3
|
||||
* 127.0.0.1:6379>
|
||||
*
|
||||
* To test reconnection try, for example, to close all clients currently
|
||||
* connected to the Redis instance
|
||||
*
|
||||
* $ redis-cli
|
||||
* > CLIENT kill TYPE pubsub
|
||||
*/
|
||||
|
||||
// Receives pushes.
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// See
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
request req;
|
||||
req.get_config().cancel_if_not_connected = false;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
stimer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (;;) {
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn->async_exec(req, adapt(), net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
std::clog << "async_run: " << ec1.message() << "\n"
|
||||
<< "async_exec: " << ec2.message() << std::endl;
|
||||
conn->reset_stream();
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
net::co_spawn(ioc, push_receiver(conn), net::detached);
|
||||
net::co_spawn(ioc, reconnect(conn), net::detached);
|
||||
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
@@ -1,139 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
auto is_valid(endpoint const& ep) noexcept -> bool
|
||||
{
|
||||
return !std::empty(ep.host) && !std::empty(ep.port);
|
||||
}
|
||||
|
||||
// Connects to a Redis instance over sentinel and performs failover in
|
||||
// case of disconnection, see
|
||||
// https://redis.io/docs/reference/sentinel-clients. This example
|
||||
// assumes a sentinel and a redis server running on localhost.
|
||||
|
||||
net::awaitable<void> receive_pushes(std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<endpoint> resolve()
|
||||
{
|
||||
// A list of sentinel addresses from which only one is responsive
|
||||
// to simulate sentinels that are down.
|
||||
std::vector<endpoint> const endpoints
|
||||
{ {"foo", "26379"}
|
||||
, {"bar", "26379"}
|
||||
, {"127.0.0.1", "26379"}
|
||||
};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SENTINEL", "get-master-addr-by-name", "mymaster");
|
||||
req.push("QUIT");
|
||||
|
||||
connection conn{co_await net::this_coro::executor};
|
||||
|
||||
std::tuple<std::optional<std::array<std::string, 2>>, aedis::ignore> addr;
|
||||
for (auto ep : endpoints) {
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn.async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn.async_exec(req, adapt(addr), net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
std::clog << "async_run: " << ec1.message() << "\n"
|
||||
<< "async_exec: " << ec2.message() << std::endl;
|
||||
|
||||
conn.reset_stream();
|
||||
if (std::get<0>(addr))
|
||||
break;
|
||||
}
|
||||
|
||||
endpoint ep;
|
||||
if (std::get<0>(addr)) {
|
||||
ep.host = std::get<0>(addr).value().at(0);
|
||||
ep.port = std::get<0>(addr).value().at(1);
|
||||
}
|
||||
|
||||
co_return ep;
|
||||
}
|
||||
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
stimer timer{ex};
|
||||
for (;;) {
|
||||
auto ep = co_await net::co_spawn(ex, resolve(), net::use_awaitable);
|
||||
if (!is_valid(ep)) {
|
||||
std::clog << "Can't resolve master name" << std::endl;
|
||||
co_return;
|
||||
}
|
||||
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn->async_exec(req, adapt(), net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
std::clog << "async_run: " << ec1.message() << "\n"
|
||||
<< "async_exec: " << ec2.message() << "\n"
|
||||
<< "Starting the failover." << std::endl;
|
||||
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, receive_pushes(conn), net::detached);
|
||||
net::co_spawn(ioc, reconnect(conn), net::detached);
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
int main() {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
63
examples/sync_connection.hpp
Normal file
63
examples/sync_connection.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/request.hpp>
|
||||
#include <boost/asio/deferred.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/use_future.hpp>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace boost::redis
|
||||
{
|
||||
|
||||
class sync_connection {
|
||||
public:
|
||||
sync_connection()
|
||||
: ioc_{1}
|
||||
, conn_{std::make_shared<connection>(ioc_)}
|
||||
{ }
|
||||
|
||||
~sync_connection()
|
||||
{
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
void run(config cfg)
|
||||
{
|
||||
// Starts a thread that will can io_context::run on which the
|
||||
// connection will run.
|
||||
thread_ = std::thread{[this, cfg]() {
|
||||
conn_->async_run(cfg, {}, asio::detached);
|
||||
ioc_.run();
|
||||
}};
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
asio::dispatch(ioc_, [this]() { conn_->cancel(); });
|
||||
}
|
||||
|
||||
template <class Response>
|
||||
auto exec(request const& req, Response& resp)
|
||||
{
|
||||
asio::dispatch(
|
||||
conn_->get_executor(),
|
||||
asio::deferred([this, &req, &resp]() { return conn_->async_exec(req, resp, asio::deferred); }))
|
||||
(asio::use_future).get();
|
||||
}
|
||||
|
||||
private:
|
||||
asio::io_context ioc_{1};
|
||||
std::shared_ptr<connection> conn_;
|
||||
std::thread thread_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_ADAPT_HPP
|
||||
#define AEDIS_ADAPT_HPP
|
||||
|
||||
#include <tuple>
|
||||
#include <limits>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
#include <boost/variant2.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <boost/system.hpp>
|
||||
|
||||
#include <aedis/resp3/node.hpp>
|
||||
#include <aedis/adapter/adapt.hpp>
|
||||
#include <aedis/adapter/detail/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** @brief Tag used to ignore responses.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* @code
|
||||
* std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
|
||||
* @endcode
|
||||
*
|
||||
* will cause only the second tuple type to be parsed, the others
|
||||
* will be ignored.
|
||||
*/
|
||||
using ignore = adapter::detail::ignore;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
class ignore_adapter {
|
||||
public:
|
||||
explicit ignore_adapter(std::size_t max_read_size) : max_read_size_{max_read_size} {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t, resp3::node<boost::string_view> const&, boost::system::error_code&) { }
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return max_read_size_;}
|
||||
|
||||
private:
|
||||
std::size_t max_read_size_;
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
class static_adapter {
|
||||
private:
|
||||
static constexpr auto size = std::tuple_size<Tuple>::value;
|
||||
using adapter_tuple = boost::mp11::mp_transform<adapter::adapter_t, Tuple>;
|
||||
using variant_type = boost::mp11::mp_rename<adapter_tuple, boost::variant2::variant>;
|
||||
using adapters_array_type = std::array<variant_type, size>;
|
||||
|
||||
adapters_array_type adapters_;
|
||||
std::size_t max_read_size_;
|
||||
|
||||
public:
|
||||
explicit static_adapter(Tuple& r, std::size_t max_read_size)
|
||||
: max_read_size_{max_read_size}
|
||||
{
|
||||
adapter::detail::assigner<size - 1>::assign(adapters_, r);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return size;}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return max_read_size_;}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t i,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
using boost::variant2::visit;
|
||||
// I am usure whether this should be an error or an assertion.
|
||||
BOOST_ASSERT(i < adapters_.size());
|
||||
visit([&](auto& arg){arg(nd, ec);}, adapters_.at(i));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Vector>
|
||||
class vector_adapter {
|
||||
private:
|
||||
using adapter_type = typename adapter::detail::response_traits<Vector>::adapter_type;
|
||||
adapter_type adapter_;
|
||||
std::size_t max_read_size_;
|
||||
|
||||
public:
|
||||
explicit vector_adapter(Vector& v, std::size_t max_read_size)
|
||||
: adapter_{adapter::adapt2(v)}
|
||||
, max_read_size_{max_read_size}
|
||||
{ }
|
||||
|
||||
[[nodiscard]]
|
||||
auto
|
||||
get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return max_read_size_;}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class>
|
||||
struct response_traits;
|
||||
|
||||
template <>
|
||||
struct response_traits<void> {
|
||||
using response_type = void;
|
||||
using adapter_type = detail::ignore_adapter;
|
||||
|
||||
static auto adapt(std::size_t max_read_size) noexcept
|
||||
{ return detail::ignore_adapter{max_read_size}; }
|
||||
};
|
||||
|
||||
template <class String, class Allocator>
|
||||
struct response_traits<std::vector<resp3::node<String>, Allocator>> {
|
||||
using response_type = std::vector<resp3::node<String>, Allocator>;
|
||||
using adapter_type = vector_adapter<response_type>;
|
||||
|
||||
static auto adapt(response_type& v, std::size_t max_read_size) noexcept
|
||||
{ return adapter_type{v, max_read_size}; }
|
||||
};
|
||||
|
||||
template <class ...Ts>
|
||||
struct response_traits<std::tuple<Ts...>> {
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = static_adapter<response_type>;
|
||||
|
||||
static auto adapt(response_type& r, std::size_t max_read_size) noexcept
|
||||
{ return adapter_type{r, max_read_size}; }
|
||||
};
|
||||
|
||||
template <class Adapter>
|
||||
class wrapper {
|
||||
public:
|
||||
explicit wrapper(Adapter adapter) : adapter_{adapter} {}
|
||||
|
||||
void operator()(resp3::node<boost::string_view> const& node, boost::system::error_code& ec)
|
||||
{ return adapter_(0, node, ec); }
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return adapter_.get_supported_response_size();}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return adapter_.get_max_read_size(0); }
|
||||
|
||||
private:
|
||||
Adapter adapter_;
|
||||
};
|
||||
|
||||
template <class Adapter>
|
||||
auto make_adapter_wrapper(Adapter adapter)
|
||||
{
|
||||
return wrapper{adapter};
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
/** @brief Creates an adapter that ignores responses.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* This function can be used to create adapters that ignores
|
||||
* responses.
|
||||
*
|
||||
* @param max_read_size Specifies the maximum size of the read
|
||||
* buffer.
|
||||
*/
|
||||
inline auto adapt(std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)()) noexcept
|
||||
{
|
||||
return detail::response_traits<void>::adapt(max_read_size);
|
||||
}
|
||||
|
||||
/** @brief Adapts a type to be used as a response.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* The type T must be either
|
||||
*
|
||||
* 1. a std::tuple<T1, T2, T3, ...> or
|
||||
* 2. std::vector<node<String>>
|
||||
*
|
||||
* The types T1, T2, etc can be any STL container, any integer type
|
||||
* and \c std::string
|
||||
*
|
||||
* @param t Tuple containing the responses.
|
||||
* @param max_read_size Specifies the maximum size of the read
|
||||
* buffer.
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t, std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)()) noexcept
|
||||
{
|
||||
return detail::response_traits<T>::adapt(t, max_read_size);
|
||||
}
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPT_HPP
|
||||
@@ -1,80 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_ADAPTER_ADAPT_HPP
|
||||
#define AEDIS_ADAPTER_ADAPT_HPP
|
||||
|
||||
#include <aedis/adapter/detail/response_traits.hpp>
|
||||
|
||||
namespace aedis::adapter {
|
||||
|
||||
template <class T>
|
||||
using adapter_t = typename detail::adapter_t<T>;
|
||||
|
||||
/** \brief Creates a dummy response adapter.
|
||||
\ingroup low-level-api
|
||||
|
||||
The adapter returned by this function ignores responses. It is
|
||||
useful to avoid wasting time with responses which are not needed.
|
||||
|
||||
Example:
|
||||
|
||||
@code
|
||||
// Pushes and writes some commands to the server.
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::ping);
|
||||
sr.push(command::quit);
|
||||
net::write(socket, net::buffer(request));
|
||||
|
||||
// Ignores all responses except for the response to ping.
|
||||
std::string buffer;
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt()); // hello
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt(resp)); // ping
|
||||
resp3::read(socket, dynamic_buffer(buffer, adapt())); // quit
|
||||
@endcode
|
||||
*/
|
||||
inline
|
||||
auto adapt2() noexcept
|
||||
{ return detail::response_traits<void>::adapt(); }
|
||||
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* STL containers, \c std::tuple and built-in types are supported and
|
||||
* can be used in conjunction with \c std::optional<T>.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code
|
||||
* std::unordered_map<std::string, std::string> cont;
|
||||
* co_await async_read(socket, buffer, adapt(cont));
|
||||
* @endcode
|
||||
*
|
||||
* For a transaction
|
||||
*
|
||||
* @code
|
||||
* sr.push(command::multi);
|
||||
* sr.push(command::ping, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push_range(command::rpush, ...);
|
||||
* sr.push(command::lrange, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push(command::exec);
|
||||
*
|
||||
* co_await async_write(socket, buffer(request));
|
||||
*
|
||||
* // Reads the response to a transaction
|
||||
* std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
* @endcode
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt2(T& t) noexcept
|
||||
{ return detail::response_traits<T>::adapt(t); }
|
||||
|
||||
} // aedis::adapter
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPT_HPP
|
||||
@@ -1,276 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_CONNECTION_HPP
|
||||
#define AEDIS_CONNECTION_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <aedis/detail/connection_base.hpp>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** @brief A connection to the Redis server.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* This class keeps a healthy connection to the Redis instance where
|
||||
* commands can be sent at any time. For more details, please see the
|
||||
* documentation of each individual function.
|
||||
*
|
||||
* @tparam AsyncReadWriteStream A stream that supports reading and
|
||||
* writing.
|
||||
*/
|
||||
template <class AsyncReadWriteStream = boost::asio::ip::tcp::socket>
|
||||
class connection :
|
||||
private detail::connection_base<
|
||||
typename AsyncReadWriteStream::executor_type,
|
||||
connection<AsyncReadWriteStream>> {
|
||||
public:
|
||||
/// Executor type.
|
||||
using executor_type = typename AsyncReadWriteStream::executor_type;
|
||||
|
||||
/// Type of the next layer
|
||||
using next_layer_type = AsyncReadWriteStream;
|
||||
using base_type = detail::connection_base<executor_type, connection<AsyncReadWriteStream>>;
|
||||
|
||||
/** \brief Connection configuration parameters.
|
||||
*/
|
||||
struct timeouts {
|
||||
/// Timeout of the resolve operation.
|
||||
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the connect operation.
|
||||
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the resp3-handshake operation.
|
||||
std::chrono::steady_clock::duration resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
|
||||
/// Time interval with which PING commands are sent to Redis.
|
||||
std::chrono::steady_clock::duration ping_interval = std::chrono::seconds{1};
|
||||
};
|
||||
|
||||
/// Constructor
|
||||
explicit
|
||||
connection(
|
||||
executor_type ex,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: base_type{ex, resource}
|
||||
, stream_{ex}
|
||||
{}
|
||||
|
||||
explicit
|
||||
connection(
|
||||
boost::asio::io_context& ioc,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: connection(ioc.get_executor(), resource)
|
||||
{ }
|
||||
|
||||
/// Returns the associated executor.
|
||||
auto get_executor() {return stream_.get_executor();}
|
||||
|
||||
/// Resets the underlying stream.
|
||||
void reset_stream()
|
||||
{
|
||||
if (stream_.is_open()) {
|
||||
boost::system::error_code ignore;
|
||||
stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore);
|
||||
stream_.close(ignore);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the next layer.
|
||||
auto next_layer() noexcept -> auto& { return stream_; }
|
||||
|
||||
/// Returns a const reference to the next layer.
|
||||
auto next_layer() const noexcept -> auto const& { return stream_; }
|
||||
|
||||
/** @brief Establishes a connection with the Redis server asynchronously.
|
||||
*
|
||||
* This function performs the following steps
|
||||
*
|
||||
* @li Resolves the Redis host as of `async_resolve` with the
|
||||
* timeout passed in the base class `connection::timeouts::resolve_timeout`.
|
||||
*
|
||||
* @li Connects to one of the endpoints returned by the resolve
|
||||
* operation with the timeout passed in the base class
|
||||
* `connection::timeouts::connect_timeout`.
|
||||
*
|
||||
* @li Performs a RESP3 handshake by sending a
|
||||
* [HELLO](https://redis.io/commands/hello/) command with protocol
|
||||
* version 3 and the credentials contained in the
|
||||
* `aedis::endpoint` object. The timeout used is the one specified
|
||||
* in `connection::timeouts::resp3_handshake_timeout`.
|
||||
*
|
||||
* @li Erases any password that may be contained in
|
||||
* `endpoint::password`.
|
||||
*
|
||||
* @li Checks whether the server role corresponds to the one
|
||||
* specified in the `endpoint`. If `endpoint::role` is left empty,
|
||||
* no check is performed. If the role is different than the
|
||||
* expected `async_run` will complete with
|
||||
* `error::unexpected_server_role`.
|
||||
*
|
||||
* @li Starts healthy checks with a timeout twice the value of
|
||||
* `connection::timeouts::ping_interval`. If no data is received during that
|
||||
* time interval `connection::async_run` completes with
|
||||
* `error::idle_timeout`.
|
||||
*
|
||||
* @li Starts the healthy check operation that sends the
|
||||
* [PING](https://redis.io/commands/ping/) to Redis with a
|
||||
* frequency equal to `connection::timeouts::ping_interval`.
|
||||
*
|
||||
* @li Starts reading from the socket and executes all requests
|
||||
* that have been started prior to this function call.
|
||||
*
|
||||
* @param ep Redis endpoint.
|
||||
* @param ts Timeouts used by the operations.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code);
|
||||
* @endcode
|
||||
*
|
||||
* This function will complete when the connection is lost as
|
||||
* follows. If the error is boost::asio::error::eof this function
|
||||
* will complete without error.
|
||||
*/
|
||||
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto
|
||||
async_run(
|
||||
endpoint ep,
|
||||
timeouts ts = timeouts{},
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_run(ep, ts, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Executes a command on the Redis server asynchronously.
|
||||
*
|
||||
* This function will send a request to the Redis server and
|
||||
* complete when the response arrives. If the request contains
|
||||
* only commands that don't expect a response, the completion
|
||||
* occurs after it has been written to the underlying stream.
|
||||
* Multiple concurrent calls to this function will be
|
||||
* automatically queued by the implementation.
|
||||
*
|
||||
* @param req Request object.
|
||||
* @param adapter Response adapter.
|
||||
* @param token Asio completion token.
|
||||
*
|
||||
* For an example see echo_server.cpp. The completion token must
|
||||
* have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the response in
|
||||
* bytes.
|
||||
*/
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_exec(
|
||||
resp3::request const& req,
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_exec(req, adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Receives server side pushes asynchronously.
|
||||
*
|
||||
* Users that expect server pushes should call this function in a
|
||||
* loop. If a push arrives and there is no reader, the connection
|
||||
* will hang and eventually timeout.
|
||||
*
|
||||
* @param adapter The response adapter.
|
||||
* @param token The Asio completion token.
|
||||
*
|
||||
* For an example see subscriber.cpp. The completion token must
|
||||
* have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the push in
|
||||
* bytes.
|
||||
*/
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive(
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_receive(adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* @li `operation::exec`: Cancels operations started with
|
||||
* `async_exec`. Affects only requests that haven't been written
|
||||
* yet.
|
||||
* @li operation::run: Cancels the `async_run` operation. Notice
|
||||
* that the preferred way to close a connection is to send a
|
||||
* [QUIT](https://redis.io/commands/quit/) command to the server.
|
||||
* An unresponsive Redis server will also cause the idle-checks to
|
||||
* timeout and lead to `connection::async_run` completing with
|
||||
* `error::idle_timeout`. Calling `cancel(operation::run)`
|
||||
* directly should be seen as the last option.
|
||||
* @li operation::receive: Cancels any ongoing callto
|
||||
* `async_receive`.
|
||||
*
|
||||
* @param op: The operation to be cancelled.
|
||||
* @returns The number of operations that have been canceled.
|
||||
*/
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{ return base_type::cancel(op); }
|
||||
|
||||
private:
|
||||
using this_type = connection<next_layer_type>;
|
||||
|
||||
template <class, class> friend class detail::connection_base;
|
||||
template <class, class> friend struct detail::exec_read_op;
|
||||
template <class, class> friend struct detail::exec_op;
|
||||
template <class, class> friend struct detail::receive_op;
|
||||
template <class> friend struct detail::check_idle_op;
|
||||
template <class> friend struct detail::reader_op;
|
||||
template <class> friend struct detail::writer_op;
|
||||
template <class, class> friend struct detail::connect_with_timeout_op;
|
||||
template <class, class> friend struct detail::run_op;
|
||||
template <class> friend struct detail::ping_op;
|
||||
|
||||
template <class Timer, class CompletionToken>
|
||||
auto
|
||||
async_connect(
|
||||
boost::asio::ip::tcp::resolver::results_type const& endpoints,
|
||||
timeouts ts,
|
||||
Timer& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::connect_with_timeout_op<this_type, Timer>{this, &endpoints, ts, &timer},
|
||||
token, stream_);
|
||||
}
|
||||
|
||||
void close() { stream_.close(); }
|
||||
auto is_open() const noexcept { return stream_.is_open(); }
|
||||
auto lowest_layer() noexcept -> auto& { return stream_.lowest_layer(); }
|
||||
|
||||
AsyncReadWriteStream stream_;
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_CONNECTION_HPP
|
||||
@@ -1,480 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_CONNECTION_BASE_HPP
|
||||
#define AEDIS_CONNECTION_BASE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <limits>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <memory_resource>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/experimental/channel.hpp>
|
||||
|
||||
#include <aedis/adapt.hpp>
|
||||
#include <aedis/operation.hpp>
|
||||
#include <aedis/endpoint.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
#include <aedis/detail/connection_ops.hpp>
|
||||
|
||||
namespace aedis::detail {
|
||||
|
||||
/** Base class for high level Redis asynchronous connections.
|
||||
*
|
||||
* This class is not meant to be instantiated directly but as base
|
||||
* class in the CRTP.
|
||||
*
|
||||
* @tparam Executor The executor type.
|
||||
* @tparam Derived The derived class type.
|
||||
*
|
||||
*/
|
||||
template <class Executor, class Derived>
|
||||
class connection_base {
|
||||
public:
|
||||
using executor_type = Executor;
|
||||
using this_type = connection_base<Executor, Derived>;
|
||||
|
||||
explicit
|
||||
connection_base(executor_type ex, std::pmr::memory_resource* resource)
|
||||
: resv_{ex}
|
||||
, ping_timer_{ex}
|
||||
, check_idle_timer_{ex}
|
||||
, writer_timer_{ex}
|
||||
, read_timer_{ex}
|
||||
, push_channel_{ex}
|
||||
, reqs_{resource}
|
||||
, last_data_{std::chrono::time_point<std::chrono::steady_clock>::min()}
|
||||
{
|
||||
req_.get_config().cancel_if_not_connected = true;
|
||||
req_.get_config().cancel_on_connection_lost = true;
|
||||
writer_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
read_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
auto get_executor() {return resv_.get_executor();}
|
||||
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{
|
||||
switch (op) {
|
||||
case operation::exec:
|
||||
{
|
||||
return cancel_unwritten_requests();
|
||||
}
|
||||
case operation::run:
|
||||
{
|
||||
resv_.cancel();
|
||||
derived().close();
|
||||
|
||||
read_timer_.cancel();
|
||||
check_idle_timer_.cancel();
|
||||
writer_timer_.cancel();
|
||||
ping_timer_.cancel();
|
||||
cancel_on_conn_lost();
|
||||
|
||||
return 1U;
|
||||
}
|
||||
case operation::receive:
|
||||
{
|
||||
push_channel_.cancel();
|
||||
return 1U;
|
||||
}
|
||||
default: BOOST_ASSERT(false); return 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto cancel_unwritten_requests() -> std::size_t
|
||||
{
|
||||
auto f = [](auto const& ptr)
|
||||
{
|
||||
BOOST_ASSERT(ptr != nullptr);
|
||||
return ptr->is_written();
|
||||
};
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), f);
|
||||
|
||||
auto const ret = std::distance(point, std::end(reqs_));
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto cancel_on_conn_lost() -> std::size_t
|
||||
{
|
||||
auto cond = [](auto const& ptr)
|
||||
{
|
||||
BOOST_ASSERT(ptr != nullptr);
|
||||
|
||||
if (ptr->get_request().get_config().cancel_on_connection_lost)
|
||||
return false;
|
||||
|
||||
return !(!ptr->get_request().get_config().retry && ptr->is_written());
|
||||
};
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), cond);
|
||||
|
||||
auto const ret = std::distance(point, std::end(reqs_));
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return ptr->reset_status();
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_exec(
|
||||
resp3::request const& req,
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
BOOST_ASSERT_MSG(req.size() <= adapter.get_supported_response_size(), "Request and adapter have incompatible sizes.");
|
||||
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_op<Derived, Adapter>{&derived(), &req, adapter}, token, resv_);
|
||||
}
|
||||
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive(
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
auto f = detail::make_adapter_wrapper(adapter);
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::receive_op<Derived, decltype(f)>{&derived(), f}, token, resv_);
|
||||
}
|
||||
|
||||
template <class Timeouts, class CompletionToken>
|
||||
auto
|
||||
async_run(endpoint ep, Timeouts ts, CompletionToken token)
|
||||
{
|
||||
ep_ = std::move(ep);
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::run_op<Derived, Timeouts>{&derived(), ts}, token, resv_);
|
||||
}
|
||||
|
||||
private:
|
||||
using clock_type = std::chrono::steady_clock;
|
||||
using clock_traits_type = boost::asio::wait_traits<clock_type>;
|
||||
using timer_type = boost::asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;
|
||||
using resolver_type = boost::asio::ip::basic_resolver<boost::asio::ip::tcp, executor_type>;
|
||||
using push_channel_type = boost::asio::experimental::channel<executor_type, void(boost::system::error_code, std::size_t)>;
|
||||
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
auto derived() -> Derived& { return static_cast<Derived&>(*this); }
|
||||
|
||||
void on_write()
|
||||
{
|
||||
// We have to clear the payload right after writing it to use it
|
||||
// as a flag that informs there is no ongoing write.
|
||||
write_buffer_.clear();
|
||||
|
||||
// Notice this must come before the for-each below.
|
||||
cancel_push_requests();
|
||||
|
||||
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
if (ptr->is_staged())
|
||||
ptr->mark_written();
|
||||
});
|
||||
}
|
||||
|
||||
struct req_info {
|
||||
public:
|
||||
enum class action
|
||||
{
|
||||
stop,
|
||||
proceed,
|
||||
none,
|
||||
};
|
||||
|
||||
explicit req_info(resp3::request const& req, executor_type ex)
|
||||
: timer_{ex}
|
||||
, action_{action::none}
|
||||
, req_{&req}
|
||||
, cmds_{std::size(req)}
|
||||
, status_{status::none}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
auto proceed()
|
||||
{
|
||||
timer_.cancel();
|
||||
action_ = action::proceed;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
timer_.cancel();
|
||||
action_ = action::stop;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_written() const noexcept
|
||||
{ return status_ == status::written; }
|
||||
|
||||
[[nodiscard]] auto is_staged() const noexcept
|
||||
{ return status_ == status::staged; }
|
||||
|
||||
void mark_written() noexcept
|
||||
{ status_ = status::written; }
|
||||
|
||||
void mark_staged() noexcept
|
||||
{ status_ = status::staged; }
|
||||
|
||||
void reset_status() noexcept
|
||||
{ status_ = status::none; }
|
||||
|
||||
[[nodiscard]] auto get_number_of_commands() const noexcept
|
||||
{ return cmds_; }
|
||||
|
||||
[[nodiscard]] auto get_request() const noexcept -> auto const&
|
||||
{ return *req_; }
|
||||
|
||||
[[nodiscard]] auto get_action() const noexcept
|
||||
{ return action_;}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_wait(CompletionToken token)
|
||||
{
|
||||
return timer_.async_wait(std::move(token));
|
||||
}
|
||||
|
||||
private:
|
||||
enum class status
|
||||
{ none
|
||||
, staged
|
||||
, written
|
||||
};
|
||||
|
||||
timer_type timer_;
|
||||
action action_;
|
||||
resp3::request const* req_;
|
||||
std::size_t cmds_;
|
||||
status status_;
|
||||
};
|
||||
|
||||
void remove_request(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.erase(std::remove(std::begin(reqs_), std::end(reqs_), info));
|
||||
}
|
||||
|
||||
using reqs_type = std::pmr::deque<std::shared_ptr<req_info>>;
|
||||
|
||||
template <class, class> friend struct detail::receive_op;
|
||||
template <class> friend struct detail::reader_op;
|
||||
template <class> friend struct detail::writer_op;
|
||||
template <class> friend struct detail::ping_op;
|
||||
template <class, class> friend struct detail::run_op;
|
||||
template <class, class> friend struct detail::exec_op;
|
||||
template <class, class> friend struct detail::exec_read_op;
|
||||
template <class> friend struct detail::resolve_with_timeout_op;
|
||||
template <class> friend struct detail::check_idle_op;
|
||||
template <class, class> friend struct detail::start_op;
|
||||
template <class> friend struct detail::send_receive_op;
|
||||
|
||||
void cancel_push_requests()
|
||||
{
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return !(ptr->is_staged() && ptr->get_request().size() == 0);
|
||||
});
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->proceed();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
}
|
||||
|
||||
void add_request_info(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.push_back(info);
|
||||
if (derived().is_open() && cmds_ == 0 && write_buffer_.empty())
|
||||
writer_timer_.cancel();
|
||||
}
|
||||
|
||||
auto make_dynamic_buffer(std::size_t max_read_size = 512)
|
||||
{ return boost::asio::dynamic_buffer(read_buffer_, max_read_size); }
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_resolve_with_timeout(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::resolve_with_timeout_op<this_type>{this, d},
|
||||
token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto reader(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::reader_op<Derived>{&derived()}, token, resv_.get_executor());
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto writer(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::writer_op<Derived>{&derived()}, token, resv_.get_executor());
|
||||
}
|
||||
|
||||
template <
|
||||
class Timeouts,
|
||||
class CompletionToken>
|
||||
auto async_start(Timeouts ts, CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::start_op<this_type, Timeouts>{this, ts}, token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_ping(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::ping_op<Derived>{&derived(), d}, token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_check_idle(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::check_idle_op<Derived>{&derived(), d}, token, check_idle_timer_);
|
||||
}
|
||||
|
||||
template <class Adapter, class CompletionToken>
|
||||
auto async_exec_read(Adapter adapter, std::size_t cmds, CompletionToken token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_read_op<Derived, Adapter>{&derived(), adapter, cmds}, token, resv_);
|
||||
}
|
||||
|
||||
void stage_request(req_info& ri)
|
||||
{
|
||||
write_buffer_ += ri.get_request().payload();
|
||||
cmds_ += ri.get_request().size();
|
||||
ri.mark_staged();
|
||||
}
|
||||
|
||||
void coalesce_requests()
|
||||
{
|
||||
// Coalesce the requests and marks them staged. After a
|
||||
// successful write staged requests will be marked as written.
|
||||
BOOST_ASSERT(write_buffer_.empty());
|
||||
BOOST_ASSERT(!reqs_.empty());
|
||||
|
||||
stage_request(*reqs_.at(0));
|
||||
|
||||
for (std::size_t i = 1; i < std::size(reqs_); ++i) {
|
||||
if (!reqs_.at(i - 1)->get_request().get_config().coalesce ||
|
||||
!reqs_.at(i - 0)->get_request().get_config().coalesce) {
|
||||
break;
|
||||
}
|
||||
stage_request(*reqs_.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
void prepare_hello(endpoint const& ep)
|
||||
{
|
||||
req_.clear();
|
||||
if (requires_auth(ep)) {
|
||||
req_.push("HELLO", "3", "AUTH", ep.username, ep.password);
|
||||
} else {
|
||||
req_.push("HELLO", "3");
|
||||
}
|
||||
}
|
||||
|
||||
auto expect_role(std::string const& expected) -> bool
|
||||
{
|
||||
if (std::empty(expected))
|
||||
return true;
|
||||
|
||||
resp3::node<std::string> role_node;
|
||||
role_node.data_type = resp3::type::blob_string;
|
||||
role_node.aggregate_size = 1;
|
||||
role_node.depth = 1;
|
||||
role_node.value = "role";
|
||||
|
||||
auto iter = std::find(std::cbegin(response_), std::cend(response_), role_node);
|
||||
if (iter == std::end(response_))
|
||||
return false;
|
||||
|
||||
++iter;
|
||||
BOOST_ASSERT(iter != std::cend(response_));
|
||||
return iter->value == expected;
|
||||
}
|
||||
|
||||
// IO objects
|
||||
resolver_type resv_;
|
||||
timer_type ping_timer_;
|
||||
timer_type check_idle_timer_;
|
||||
timer_type writer_timer_;
|
||||
timer_type read_timer_;
|
||||
push_channel_type push_channel_;
|
||||
|
||||
std::string read_buffer_;
|
||||
std::string write_buffer_;
|
||||
std::size_t cmds_ = 0;
|
||||
reqs_type reqs_;
|
||||
|
||||
// Last time we received data.
|
||||
time_point_type last_data_;
|
||||
|
||||
resp3::request req_;
|
||||
std::vector<resp3::node<std::string>> response_;
|
||||
endpoint ep_;
|
||||
// The result of async_resolve.
|
||||
boost::asio::ip::tcp::resolver::results_type endpoints_;
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_CONNECTION_BASE_HPP
|
||||
@@ -1,570 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_CONNECTION_OPS_HPP
|
||||
#define AEDIS_CONNECTION_OPS_HPP
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <aedis/adapt.hpp>
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/detail/net.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/detail/exec.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/write.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::detail {
|
||||
|
||||
template <class Conn, class Timer>
|
||||
struct connect_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
boost::asio::ip::tcp::resolver::results_type const* endpoints = nullptr;
|
||||
typename Conn::timeouts ts;
|
||||
Timer* timer = nullptr;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::endpoint const& = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
timer->expires_after(ts.connect_timeout);
|
||||
yield detail::async_connect(conn->next_layer(), *timer, *endpoints, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct resolve_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
std::chrono::steady_clock::duration resolve_timeout{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::resolver::results_type const& res = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
conn->ping_timer_.expires_after(resolve_timeout);
|
||||
yield
|
||||
aedis::detail::async_resolve(
|
||||
conn->resv_, conn->ping_timer_,
|
||||
conn->ep_.host, conn->ep_.port, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
conn->endpoints_ = res;
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct receive_op {
|
||||
Conn* conn = nullptr;
|
||||
Adapter adapter;
|
||||
std::size_t read_size = 0;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield conn->push_channel_.async_receive(std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
yield
|
||||
resp3::async_read(
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(adapter.get_max_read_size(0)),
|
||||
adapter, std::move(self));
|
||||
|
||||
// cancel(receive) is needed to cancel the channel, otherwise
|
||||
// the read operation will be blocked forever see
|
||||
// test_push_adapter.
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run); conn->cancel(operation::receive));
|
||||
|
||||
read_size = n;
|
||||
|
||||
yield conn->push_channel_.async_send({}, 0, std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
self.complete({}, read_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct exec_read_op {
|
||||
Conn* conn;
|
||||
Adapter adapter;
|
||||
std::size_t cmds = 0;
|
||||
std::size_t read_size = 0;
|
||||
std::size_t index = 0;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
// Loop reading the responses to this request.
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
while (cmds != 0) {
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
|
||||
//-----------------------------------
|
||||
// If we detect a push in the middle of a request we have
|
||||
// to hand it to the push consumer. To do that we need
|
||||
// some data in the read bufer.
|
||||
if (conn->read_buffer_.empty()) {
|
||||
yield
|
||||
boost::asio::async_read_until(
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(),
|
||||
"\r\n", std::move(self));
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run));
|
||||
}
|
||||
|
||||
// If the next request is a push we have to handle it to
|
||||
// the receive_op wait for it to be done and continue.
|
||||
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push) {
|
||||
yield
|
||||
async_send_receive(conn->push_channel_, std::move(self));
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run));
|
||||
continue;
|
||||
}
|
||||
//-----------------------------------
|
||||
|
||||
yield
|
||||
resp3::async_read(
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(adapter.get_max_read_size(index)),
|
||||
[i = index, adpt = adapter] (resp3::node<boost::string_view> const& nd, boost::system::error_code& ec) mutable { adpt(i, nd, ec); },
|
||||
std::move(self));
|
||||
|
||||
++index;
|
||||
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run));
|
||||
|
||||
read_size += n;
|
||||
|
||||
BOOST_ASSERT(cmds != 0);
|
||||
--cmds;
|
||||
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
--conn->cmds_;
|
||||
}
|
||||
|
||||
self.complete({}, read_size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct exec_op {
|
||||
using req_info_type = typename Conn::req_info;
|
||||
|
||||
Conn* conn = nullptr;
|
||||
resp3::request const* req = nullptr;
|
||||
Adapter adapter{};
|
||||
std::shared_ptr<req_info_type> info = nullptr;
|
||||
std::size_t read_size = 0;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
// Check whether the user wants to wait for the connection to
|
||||
// be stablished.
|
||||
// TODO: is_open below reflects only whether a TCP connection
|
||||
// has been stablished. We need a variable that informs
|
||||
// whether HELLO was successfull and we are connected with
|
||||
// Redis.
|
||||
if (req->get_config().cancel_if_not_connected && !conn->is_open())
|
||||
return self.complete(error::not_connected, 0);
|
||||
|
||||
info = std::allocate_shared<req_info_type>(boost::asio::get_associated_allocator(self), *req, conn->resv_.get_executor());
|
||||
|
||||
conn->add_request_info(info);
|
||||
EXEC_OP_WAIT:
|
||||
yield info->async_wait(std::move(self));
|
||||
BOOST_ASSERT(ec == boost::asio::error::operation_aborted);
|
||||
|
||||
if (info->get_action() == Conn::req_info::action::stop) {
|
||||
return self.complete(ec, 0);
|
||||
}
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
if (info->is_written()) {
|
||||
self.get_cancellation_state().clear();
|
||||
goto EXEC_OP_WAIT; // Too late, can't cancel.
|
||||
} else {
|
||||
conn->remove_request(info);
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_ASSERT(conn->is_open());
|
||||
|
||||
if (req->size() == 0)
|
||||
return self.complete({}, 0);
|
||||
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
BOOST_ASSERT(conn->reqs_.front() != nullptr);
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
yield
|
||||
conn->async_exec_read(adapter, conn->reqs_.front()->get_number_of_commands(), std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
read_size = n;
|
||||
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.pop_front();
|
||||
|
||||
if (conn->cmds_ == 0) {
|
||||
conn->read_timer_.cancel_one();
|
||||
if (!conn->reqs_.empty())
|
||||
conn->writer_timer_.cancel_one();
|
||||
} else {
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.front()->proceed();
|
||||
}
|
||||
|
||||
self.complete({}, read_size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct ping_op {
|
||||
Conn* conn{};
|
||||
std::chrono::steady_clock::duration ping_interval{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t = 0)
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
conn->ping_timer_.expires_after(ping_interval);
|
||||
yield conn->ping_timer_.async_wait(std::move(self));
|
||||
if (!conn->is_open() || ec || is_cancelled(self)) {
|
||||
// Checking for is_open is necessary becuse the timer can
|
||||
// complete with success although cancel has been called.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
conn->req_.clear();
|
||||
conn->req_.push("PING");
|
||||
yield conn->async_exec(conn->req_, adapt(), std::move(self));
|
||||
if (!conn->is_open() || is_cancelled(self)) {
|
||||
// Checking for is_open is necessary to avoid
|
||||
// looping back on the timer although cancel has been
|
||||
// called.
|
||||
return self.complete({});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct check_idle_op {
|
||||
Conn* conn{};
|
||||
std::chrono::steady_clock::duration ping_interval{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
conn->check_idle_timer_.expires_after(2 * ping_interval);
|
||||
yield conn->check_idle_timer_.async_wait(std::move(self));
|
||||
if (!conn->is_open() || ec || is_cancelled(self)) {
|
||||
// Checking for is_open is necessary becuse the timer can
|
||||
// complete with success although cancel has been called.
|
||||
return self.complete({});
|
||||
}
|
||||
|
||||
auto const now = std::chrono::steady_clock::now();
|
||||
if (conn->last_data_ + (2 * ping_interval) < now) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(error::idle_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->last_data_ = now;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Timeouts>
|
||||
struct start_op {
|
||||
Conn* conn;
|
||||
Timeouts ts;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 4> order = {}
|
||||
, boost::system::error_code ec0 = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
, boost::system::error_code ec3 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return conn->reader(token);},
|
||||
[this](auto token) { return conn->writer(token);},
|
||||
[this](auto token) { return conn->async_check_idle(ts.ping_interval, token);},
|
||||
[this](auto token) { return conn->async_ping(ts.ping_interval, token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec0); break;
|
||||
case 1: self.complete(ec1); break;
|
||||
case 2: self.complete(ec2); break;
|
||||
case 3: self.complete(ec3); break;
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
auto check_resp3_handshake_failed(std::vector<resp3::node<std::string>> const& resp) -> bool
|
||||
{
|
||||
return std::size(resp) == 1 &&
|
||||
(resp.front().data_type == resp3::type::simple_error ||
|
||||
resp.front().data_type == resp3::type::blob_error ||
|
||||
resp.front().data_type == resp3::type::null);
|
||||
}
|
||||
|
||||
template <class Conn, class Timeouts>
|
||||
struct run_op {
|
||||
Conn* conn = nullptr;
|
||||
Timeouts ts;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(
|
||||
Self& self,
|
||||
boost::system::error_code ec = {},
|
||||
std::size_t = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield conn->async_resolve_with_timeout(ts.resolve_timeout, std::move(self));
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
yield conn->derived().async_connect(conn->endpoints_, ts, conn->ping_timer_, std::move(self));
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
conn->prepare_hello(conn->ep_);
|
||||
conn->ping_timer_.expires_after(ts.resp3_handshake_timeout);
|
||||
conn->response_.clear();
|
||||
|
||||
yield
|
||||
resp3::detail::async_exec(
|
||||
conn->next_layer(),
|
||||
conn->ping_timer_,
|
||||
conn->req_,
|
||||
adapter::adapt2(conn->response_),
|
||||
conn->make_dynamic_buffer(),
|
||||
std::move(self)
|
||||
);
|
||||
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
if (check_resp3_handshake_failed(conn->response_)) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(error::resp3_handshake_error);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->ep_.password.clear();
|
||||
|
||||
if (!conn->expect_role(conn->ep_.role)) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(error::unexpected_server_role);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->write_buffer_.clear();
|
||||
conn->cmds_ = 0;
|
||||
|
||||
yield conn->async_start(ts, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct writer_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
while (!conn->reqs_.empty() && conn->cmds_ == 0 && conn->write_buffer_.empty()) {
|
||||
conn->coalesce_requests();
|
||||
yield
|
||||
boost::asio::async_write(conn->next_layer(), boost::asio::buffer(conn->write_buffer_), std::move(self));
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
conn->on_write();
|
||||
|
||||
// A socket.close() may have been called while a
|
||||
// successful write might had already been queued, so we
|
||||
// have to check here before proceeding.
|
||||
if (!conn->is_open()) {
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield conn->writer_timer_.async_wait(std::move(self));
|
||||
if (!conn->is_open() || is_cancelled(self)) {
|
||||
// Notice this is not an error of the op, stoping was
|
||||
// requested from the outside, so we complete with
|
||||
// success.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct reader_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
yield
|
||||
boost::asio::async_read_until(
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(),
|
||||
"\r\n", std::move(self));
|
||||
|
||||
if (ec == boost::asio::error::eof) {
|
||||
conn->cancel(operation::run);
|
||||
return self.complete({}); // EOFINAE: EOF is not an error.
|
||||
}
|
||||
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
conn->last_data_ = std::chrono::steady_clock::now();
|
||||
|
||||
// We handle unsolicited events in the following way
|
||||
//
|
||||
// 1. Its resp3 type is a push.
|
||||
//
|
||||
// 2. A non-push type is received with an empty requests
|
||||
// queue. I have noticed this is possible (e.g. -MISCONF).
|
||||
// I expect them to have type push so we can distinguish
|
||||
// them from responses to commands, but it is a
|
||||
// simple-error. If we are lucky enough to receive them
|
||||
// when the command queue is empty we can treat them as
|
||||
// server pushes, otherwise it is impossible to handle
|
||||
// them properly
|
||||
//
|
||||
// 3. The request does not expect any response but we got
|
||||
// one. This may happen if for example, subscribe with
|
||||
// wrong syntax.
|
||||
//
|
||||
BOOST_ASSERT(!conn->read_buffer_.empty());
|
||||
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push
|
||||
|| conn->reqs_.empty()
|
||||
|| (!conn->reqs_.empty() && conn->reqs_.front()->get_number_of_commands() == 0)) {
|
||||
yield async_send_receive(conn->push_channel_, std::move(self));
|
||||
if (!conn->is_open() || ec || is_cancelled(self)) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(boost::asio::error::basic_errors::operation_aborted);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
BOOST_ASSERT(conn->reqs_.front()->get_number_of_commands() != 0);
|
||||
conn->reqs_.front()->proceed();
|
||||
yield conn->read_timer_.async_wait(std::move(self));
|
||||
if (!conn->is_open() || is_cancelled(self)) {
|
||||
// Added this cancel here to make sure any outstanding
|
||||
// ping is cancelled.
|
||||
conn->cancel(operation::run);
|
||||
self.complete(boost::asio::error::basic_errors::operation_aborted);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_CONNECTION_OPS_HPP
|
||||
@@ -1,205 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_NET_HPP
|
||||
#define AEDIS_NET_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::detail {
|
||||
|
||||
template <class Executor>
|
||||
using conn_timer_t = boost::asio::basic_waitable_timer<std::chrono::steady_clock, boost::asio::wait_traits<std::chrono::steady_clock>, Executor>;
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
class EndpointSequence
|
||||
>
|
||||
struct connect_op {
|
||||
Stream* socket;
|
||||
conn_timer_t<typename Stream::executor_type>* timer;
|
||||
EndpointSequence* endpoints;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, typename Stream::protocol_type::endpoint const& ep = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token)
|
||||
{
|
||||
auto f = [](boost::system::error_code const&, auto const&) { return true; };
|
||||
return boost::asio::async_connect(*socket, *endpoints, f, token);
|
||||
},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted, {});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, ep); return;
|
||||
case 1:
|
||||
{
|
||||
if (ec2) {
|
||||
self.complete(ec2, {});
|
||||
} else {
|
||||
self.complete(error::connect_timeout, ep);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Resolver, class Timer>
|
||||
struct resolve_op {
|
||||
Resolver* resv;
|
||||
Timer* timer;
|
||||
boost::string_view host;
|
||||
boost::string_view port;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::asio::ip::tcp::resolver::results_type res = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return resv->async_resolve(host.data(), port.data(), token);},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted, {});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, res); return;
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (ec2) {
|
||||
self.complete(ec2, {});
|
||||
} else {
|
||||
self.complete(error::resolve_timeout, {});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Channel>
|
||||
struct send_receive_op {
|
||||
Channel* channel;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
channel->async_send(boost::system::error_code{}, 0, std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
yield
|
||||
channel->async_receive(std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
self.complete({}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
class EndpointSequence,
|
||||
class CompletionToken
|
||||
>
|
||||
auto async_connect(
|
||||
Stream& socket,
|
||||
conn_timer_t<typename Stream::executor_type>& timer,
|
||||
EndpointSequence ep,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, typename Stream::protocol_type::endpoint const&)
|
||||
>(connect_op<Stream, EndpointSequence>
|
||||
{&socket, &timer, &ep}, token, socket, timer);
|
||||
}
|
||||
|
||||
template <
|
||||
class Resolver,
|
||||
class Timer,
|
||||
class CompletionToken =
|
||||
boost::asio::default_completion_token_t<typename Resolver::executor_type>
|
||||
>
|
||||
auto async_resolve(
|
||||
Resolver& resv,
|
||||
Timer& timer,
|
||||
boost::string_view host,
|
||||
boost::string_view port,
|
||||
CompletionToken&& token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, boost::asio::ip::tcp::resolver::results_type)
|
||||
>(resolve_op<Resolver, Timer>{&resv, &timer, host, port}, token, resv, timer);
|
||||
}
|
||||
|
||||
template <
|
||||
class Channel,
|
||||
class CompletionToken =
|
||||
boost::asio::default_completion_token_t<typename Channel::executor_type>
|
||||
>
|
||||
auto async_send_receive(Channel& channel, CompletionToken&& token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(send_receive_op<Channel>{&channel}, token, channel);
|
||||
}
|
||||
} // aedis::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_NET_HPP
|
||||
@@ -1,38 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_ENDPOINT_HPP
|
||||
#define AEDIS_ENDPOINT_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** \brief A Redis endpoint.
|
||||
* \ingroup high-level-api
|
||||
*/
|
||||
struct endpoint {
|
||||
/// Redis server address.
|
||||
std::string host;
|
||||
|
||||
/// Redis server port.
|
||||
std::string port;
|
||||
|
||||
/// Expected role if any.
|
||||
std::string role{};
|
||||
|
||||
/// Username if authentication is required.
|
||||
std::string username{};
|
||||
|
||||
/// Password if authentication is required.
|
||||
std::string password{};
|
||||
};
|
||||
|
||||
auto requires_auth(endpoint const& ep) noexcept -> bool;
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ENDPOINT_HPP
|
||||
@@ -1,18 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <aedis/endpoint.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
auto requires_auth(endpoint const& ep) noexcept -> bool
|
||||
{
|
||||
return !std::empty(ep.username) && !std::empty(ep.password);
|
||||
}
|
||||
|
||||
} // aedis
|
||||
@@ -1,29 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_OPERATION_HPP
|
||||
#define AEDIS_OPERATION_HPP
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** \brief Connection operations that can be cancelled.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* The operations listed below can be passed to the
|
||||
* `aedis::connection::cancel` member function.
|
||||
*/
|
||||
enum class operation {
|
||||
/// Refers to `connection::async_exec` operations.
|
||||
exec,
|
||||
/// Refers to `connection::async_run` operations.
|
||||
run,
|
||||
/// Refers to `connection::async_receive` operations.
|
||||
receive,
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_OPERATION_HPP
|
||||
@@ -1,173 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_EXEC_HPP
|
||||
#define AEDIS_RESP3_EXEC_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Adapter,
|
||||
class DynamicBuffer
|
||||
>
|
||||
struct exec_op {
|
||||
AsyncStream* socket = nullptr;
|
||||
request const* req = nullptr;
|
||||
Adapter adapter;
|
||||
DynamicBuffer dbuf{};
|
||||
std::size_t n_cmds = 0;
|
||||
std::size_t size = 0;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
if (req) {
|
||||
yield
|
||||
boost::asio::async_write(
|
||||
*socket,
|
||||
boost::asio::buffer(req->payload()),
|
||||
std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
if (n_cmds == 0) {
|
||||
return self.complete({}, n);
|
||||
}
|
||||
|
||||
req = nullptr;
|
||||
}
|
||||
|
||||
yield resp3::async_read(*socket, dbuf, adapter, std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
size += n;
|
||||
if (--n_cmds == 0) {
|
||||
return self.complete(ec, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Adapter,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncStream::executor_type>
|
||||
>
|
||||
auto async_exec(
|
||||
AsyncStream& socket,
|
||||
request const& req,
|
||||
Adapter adapter,
|
||||
DynamicBuffer dbuf,
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_op<AsyncStream, Adapter, DynamicBuffer>
|
||||
{&socket, &req, adapter, dbuf, req.size()}, token, socket);
|
||||
}
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Timer,
|
||||
class Adapter,
|
||||
class DynamicBuffer
|
||||
>
|
||||
struct exec_with_timeout_op {
|
||||
AsyncStream* socket = nullptr;
|
||||
Timer* timer = nullptr;
|
||||
request const* req = nullptr;
|
||||
Adapter adapter;
|
||||
DynamicBuffer dbuf{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, std::size_t n = 0
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return detail::async_exec(*socket, *req, adapter, dbuf, token);},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, n); break;
|
||||
case 1:
|
||||
{
|
||||
if (ec2) {
|
||||
self.complete(ec2, 0);
|
||||
} else {
|
||||
self.complete(aedis::error::exec_timeout, 0);
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Timer,
|
||||
class Adapter,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncStream::executor_type>
|
||||
>
|
||||
auto async_exec(
|
||||
AsyncStream& socket,
|
||||
Timer& timer,
|
||||
request const& req,
|
||||
Adapter adapter,
|
||||
DynamicBuffer dbuf,
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_with_timeout_op<AsyncStream, Timer, Adapter, DynamicBuffer>
|
||||
{&socket, &timer, &req, adapter, dbuf}, token, socket, timer);
|
||||
}
|
||||
|
||||
} // aedis::resp3::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_RESP3_EXEC_HPP
|
||||
@@ -1,25 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
auto parse_uint(char const* data, std::size_t size, boost::system::error_code& ec) -> std::size_t
|
||||
{
|
||||
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
|
||||
std::size_t ret = 0;
|
||||
if (!parse(data, data + size, p, ret))
|
||||
ec = error::not_a_number;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // aedis::resp3::detail
|
||||
@@ -1,223 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_PARSER_HPP
|
||||
#define AEDIS_RESP3_PARSER_HPP
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <system_error>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
auto parse_uint(char const* data, std::size_t size, boost::system::error_code& ec) -> std::size_t;
|
||||
|
||||
template <class ResponseAdapter>
|
||||
class parser {
|
||||
private:
|
||||
using node_type = node<boost::string_view>;
|
||||
static constexpr std::size_t max_embedded_depth = 5;
|
||||
|
||||
ResponseAdapter adapter_;
|
||||
|
||||
// The current depth. Simple data types will have depth 0, whereas
|
||||
// the elements of aggregates will have depth 1. Embedded types
|
||||
// will have increasing depth.
|
||||
std::size_t depth_ = 0;
|
||||
|
||||
// The parser supports up to 5 levels of nested structures. The
|
||||
// first element in the sizes stack is a sentinel and must be
|
||||
// different from 1.
|
||||
std::array<std::size_t, max_embedded_depth + 1> sizes_ = {{1}};
|
||||
|
||||
// Contains the length expected in the next bulk read.
|
||||
std::size_t bulk_length_ = (std::numeric_limits<std::size_t>::max)();
|
||||
|
||||
// The type of the next bulk. Contains type::invalid if no bulk is
|
||||
// expected.
|
||||
type bulk_ = type::invalid;
|
||||
|
||||
public:
|
||||
explicit parser(ResponseAdapter adapter)
|
||||
: adapter_{adapter}
|
||||
{
|
||||
sizes_[0] = 2; // The sentinel must be more than 1.
|
||||
}
|
||||
|
||||
// Returns the number of bytes that have been consumed.
|
||||
auto
|
||||
consume(char const* data, std::size_t n, boost::system::error_code& ec) -> std::size_t
|
||||
{
|
||||
if (bulk_ != type::invalid) {
|
||||
n = bulk_length_ + 2;
|
||||
switch (bulk_) {
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
BOOST_ASSERT(bulk_length_ != 0);
|
||||
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bulk_ = type::invalid;
|
||||
--sizes_[depth_];
|
||||
|
||||
} else if (sizes_[depth_] != 0) {
|
||||
auto const t = to_type(*data);
|
||||
switch (t) {
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
bulk_length_ = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (bulk_length_ == 0) {
|
||||
adapter_({type::streamed_string_part, 1, depth_, {}}, ec);
|
||||
sizes_[depth_] = 0; // We are done.
|
||||
} else {
|
||||
bulk_ = type::streamed_string_part;
|
||||
}
|
||||
} break;
|
||||
case type::blob_error:
|
||||
case type::verbatim_string:
|
||||
case type::blob_string:
|
||||
{
|
||||
if (data[1] == '?') {
|
||||
// NOTE: This can only be triggered with blob_string.
|
||||
// Trick: A streamed string is read as an aggregate
|
||||
// of infinite lenght. When the streaming is done
|
||||
// the server is supposed to send a part with length
|
||||
// 0.
|
||||
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
|
||||
} else {
|
||||
bulk_length_ = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
bulk_ = t;
|
||||
}
|
||||
} break;
|
||||
case type::boolean:
|
||||
{
|
||||
if (n == 3) {
|
||||
ec = error::empty_field;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (data[1] != 'f' && data[1] != 't') {
|
||||
ec = error::unexpected_bool_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::doublean:
|
||||
case type::big_number:
|
||||
case type::number:
|
||||
{
|
||||
if (n == 3) {
|
||||
ec = error::empty_field;
|
||||
return 0;
|
||||
}
|
||||
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::simple_error:
|
||||
case type::simple_string:
|
||||
{
|
||||
adapter_({t, 1, depth_, {&data[1], n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::null:
|
||||
{
|
||||
adapter_({type::null, 1, depth_, {}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::push:
|
||||
case type::set:
|
||||
case type::array:
|
||||
case type::attribute:
|
||||
case type::map:
|
||||
{
|
||||
auto const l = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
adapter_({t, l, depth_, {}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (l == 0) {
|
||||
--sizes_[depth_];
|
||||
} else {
|
||||
if (depth_ == max_embedded_depth) {
|
||||
ec = error::exceeeds_max_nested_depth;
|
||||
return 0;
|
||||
}
|
||||
|
||||
++depth_;
|
||||
|
||||
sizes_[depth_] = l * element_multiplicity(t);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
ec = error::invalid_data_type;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (sizes_[depth_] == 0) {
|
||||
--depth_;
|
||||
--sizes_[depth_];
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// Returns true when the parser is done with the current message.
|
||||
[[nodiscard]] auto done() const noexcept
|
||||
{ return depth_ == 0 && bulk_ == type::invalid; }
|
||||
|
||||
// The bulk type expected in the next read. If none is expected returns
|
||||
// type::invalid.
|
||||
[[nodiscard]] auto bulk() const noexcept { return bulk_; }
|
||||
|
||||
// The length expected in the the next bulk.
|
||||
[[nodiscard]] auto bulk_length() const noexcept { return bulk_length_; }
|
||||
};
|
||||
|
||||
} // detail::resp3::aedis
|
||||
|
||||
#endif // AEDIS_RESP3_PARSER_HPP
|
||||
@@ -1,130 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_READ_OPS_HPP
|
||||
#define AEDIS_RESP3_READ_OPS_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/read_until.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::detail
|
||||
{
|
||||
template <class T>
|
||||
auto is_cancelled(T const& self)
|
||||
{
|
||||
return self.get_cancellation_state().cancelled() != boost::asio::cancellation_type_t::none;
|
||||
}
|
||||
}
|
||||
|
||||
#define AEDIS_CHECK_OP0(X)\
|
||||
if (ec || aedis::detail::is_cancelled(self)) {\
|
||||
X;\
|
||||
self.complete(!!ec ? ec : boost::asio::error::operation_aborted);\
|
||||
return;\
|
||||
}
|
||||
|
||||
#define AEDIS_CHECK_OP1(X)\
|
||||
if (ec || aedis::detail::is_cancelled(self)) {\
|
||||
X;\
|
||||
self.complete(!!ec ? ec : boost::asio::error::operation_aborted, {});\
|
||||
return;\
|
||||
}
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
struct ignore_response {
|
||||
void operator()(node<boost::string_view> nd, boost::system::error_code& ec)
|
||||
{
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::simple_error: ec = error::resp3_simple_error; return;
|
||||
case resp3::type::blob_error: ec = error::resp3_blob_error; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter>
|
||||
class parse_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
parser<ResponseAdapter> parser_;
|
||||
std::size_t consumed_ = 0;
|
||||
std::size_t buffer_size_ = 0;
|
||||
boost::asio::coroutine coro_{};
|
||||
|
||||
public:
|
||||
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
|
||||
: stream_ {stream}
|
||||
, buf_ {std::move(buf)}
|
||||
, parser_ {std::move(adapter)}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro_) for (;;) {
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
yield
|
||||
boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
} else {
|
||||
// On a bulk read we can't read until delimiter since the
|
||||
// payload may contain the delimiter itself so we have to
|
||||
// read the whole chunk. However if the bulk blob is small
|
||||
// enough it may be already on the buffer (from the last
|
||||
// read), in which case there is no need of initiating
|
||||
// another async op, otherwise we have to read the missing
|
||||
// bytes.
|
||||
if (buf_.size() < (parser_.bulk_length() + 2)) {
|
||||
buffer_size_ = buf_.size();
|
||||
buf_.grow(parser_.bulk_length() + 2 - buffer_size_);
|
||||
|
||||
yield
|
||||
boost::asio::async_read(
|
||||
stream_,
|
||||
buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_),
|
||||
boost::asio::transfer_all(),
|
||||
std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
}
|
||||
|
||||
n = parser_.bulk_length() + 2;
|
||||
BOOST_ASSERT(buf_.size() >= n);
|
||||
}
|
||||
|
||||
n = parser_.consume(static_cast<char const*>(buf_.data(0, n).data()), n, ec);
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
buf_.consume(n);
|
||||
consumed_ += n;
|
||||
if (parser_.done()) {
|
||||
self.complete({}, consumed_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::resp3::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_RESP3_READ_OPS_HPP
|
||||
@@ -1,94 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_NODE_HPP
|
||||
#define AEDIS_RESP3_NODE_HPP
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aedis::resp3 {
|
||||
|
||||
/** \brief A node in the response tree.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* Redis responses are the pre-order view of the response tree (see
|
||||
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
*
|
||||
* \remark Any Redis response can be received in an array of nodes,
|
||||
* for example \c std::vector<node<std::string>>.
|
||||
*/
|
||||
template <class String>
|
||||
struct node {
|
||||
/// The RESP3 type of the data in this node.
|
||||
type data_type = type::invalid;
|
||||
|
||||
/// The number of elements of an aggregate.
|
||||
std::size_t aggregate_size{};
|
||||
|
||||
/// The depth of this node in the response tree.
|
||||
std::size_t depth{};
|
||||
|
||||
/// The actual data. For aggregate types this is usually empty.
|
||||
String value{};
|
||||
};
|
||||
|
||||
/** @brief Converts the node to a string.
|
||||
* @relates node
|
||||
*
|
||||
* @param in The node object.
|
||||
*/
|
||||
template <class String>
|
||||
auto to_string(node<String> const& in)
|
||||
{
|
||||
std::string out;
|
||||
out += std::to_string(in.depth);
|
||||
out += '\t';
|
||||
out += to_string(in.data_type);
|
||||
out += '\t';
|
||||
out += std::to_string(in.aggregate_size);
|
||||
out += '\t';
|
||||
if (!is_aggregate(in.data_type))
|
||||
out.append(in.value.data(), in.value.size());
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/** @brief Compares a node for equality.
|
||||
* @relates node
|
||||
*
|
||||
* @param a Left hand side node object.
|
||||
* @param b Right hand side node object.
|
||||
*/
|
||||
template <class String>
|
||||
auto operator==(node<String> const& a, node<String> const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
&& a.data_type == b.data_type
|
||||
&& a.value == b.value;
|
||||
};
|
||||
|
||||
/** @brief Writes the node string to the stream.
|
||||
* @relates node
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param node Node object.
|
||||
*
|
||||
* \remark Binary data is not converted to text.
|
||||
*/
|
||||
template <class String>
|
||||
auto operator<<(std::ostream& os, node<String> const& node) -> std::ostream&
|
||||
{
|
||||
os << to_string(node);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_NODE_HPP
|
||||
@@ -1,180 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_READ_HPP
|
||||
#define AEDIS_RESP3_READ_HPP
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/detail/read_ops.hpp>
|
||||
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
|
||||
namespace aedis::resp3 {
|
||||
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* This function reads a complete response to a command or a
|
||||
* server push synchronously. For example
|
||||
*
|
||||
* @code
|
||||
* int resp;
|
||||
* std::string buffer;
|
||||
* resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
* @endcode
|
||||
*
|
||||
* For a complete example see examples/intro_sync.cpp. This function
|
||||
* is implemented in terms of one or more calls to @c
|
||||
* asio::read_until and @c asio::read functions, and is known as a @a
|
||||
* composed @a operation. Furthermore, the implementation may read
|
||||
* additional bytes from the stream that lie past the end of the
|
||||
* message being read. These additional bytes are stored in the
|
||||
* dynamic buffer, which must be preserved for subsequent reads.
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buf Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter.
|
||||
* \param ec If an error occurs, it will be assigned to this paramter.
|
||||
* \returns The number of bytes that have been consumed from the dynamic buffer.
|
||||
*
|
||||
* \remark This function calls buf.consume() in each chunk of data
|
||||
* after it has been passed to the adapter. Users must not consume
|
||||
* the bytes after it returns.
|
||||
*/
|
||||
template <
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter
|
||||
>
|
||||
auto
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter,
|
||||
boost::system::error_code& ec) -> std::size_t
|
||||
{
|
||||
detail::parser<ResponseAdapter> p {adapter};
|
||||
std::size_t n = 0;
|
||||
std::size_t consumed = 0;
|
||||
do {
|
||||
if (p.bulk() == type::invalid) {
|
||||
n = boost::asio::read_until(stream, buf, "\r\n", ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
auto const s = buf.size();
|
||||
auto const l = p.bulk_length();
|
||||
if (s < (l + 2)) {
|
||||
auto const to_read = l + 2 - s;
|
||||
buf.grow(to_read);
|
||||
n = boost::asio::read(stream, buf.data(s, to_read), ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto const* data = static_cast<char const*>(buf.data(0, n).data());
|
||||
n = p.consume(data, n, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
buf.consume(n);
|
||||
consumed += n;
|
||||
} while (!p.done());
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* Same as the error_code overload but throws on error.
|
||||
*/
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = detail::ignore_response>
|
||||
auto
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter = ResponseAdapter{})
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
auto const n = resp3::read(stream, buf, adapter, ec);
|
||||
|
||||
if (ec)
|
||||
BOOST_THROW_EXCEPTION(boost::system::system_error{ec});
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/** \brief Reads a complete response to a Redis command asynchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* This function reads a complete response to a command or a
|
||||
* server push asynchronously. For example
|
||||
*
|
||||
* @code
|
||||
* std::string buffer;
|
||||
* std::set<std::string> resp;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
* @endcode
|
||||
*
|
||||
* For a complete example see examples/transaction.cpp. This function
|
||||
* is implemented in terms of one or more calls to @c
|
||||
* asio::async_read_until and @c asio::async_read functions, and is
|
||||
* known as a @a composed @a operation. Furthermore, the
|
||||
* implementation may read additional bytes from the stream that lie
|
||||
* past the end of the message being read. These additional bytes are
|
||||
* stored in the dynamic buffer, which must be preserved for
|
||||
* subsequent reads.
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buffer Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter.
|
||||
* \param token The completion token.
|
||||
*
|
||||
* The completion handler will receive as a parameter the total
|
||||
* number of bytes transferred from the stream and must have the
|
||||
* following signature
|
||||
*
|
||||
* @code
|
||||
* void(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* \remark This function calls buf.consume() in each chunk of data
|
||||
* after it has been passed to the adapter. Users must not consume
|
||||
* the bytes after it returns.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = detail::ignore_response,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
ResponseAdapter adapter = ResponseAdapter{},
|
||||
CompletionToken&& token =
|
||||
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
|
||||
token,
|
||||
stream);
|
||||
}
|
||||
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_READ_HPP
|
||||
@@ -1,395 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_REQUEST_HPP
|
||||
#define AEDIS_RESP3_REQUEST_HPP
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <memory_resource>
|
||||
|
||||
#include <boost/hana.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
// NOTE: Consider detecting tuples in the type in the parameter pack
|
||||
// to calculate the header size correctly.
|
||||
//
|
||||
// NOTE: For some commands like hset it would be a good idea to assert
|
||||
// the value type is a pair.
|
||||
|
||||
namespace aedis::resp3 {
|
||||
|
||||
constexpr char const* separator = "\r\n";
|
||||
|
||||
/** @brief Adds a bulk to the request.
|
||||
* @relates request
|
||||
*
|
||||
* This function is useful in serialization of your own data
|
||||
* structures in a request. For example
|
||||
*
|
||||
* @code
|
||||
* void to_bulk(std::string& to, mystruct const& obj)
|
||||
* {
|
||||
* auto const str = // Convert obj to a string.
|
||||
* resp3::to_bulk(to, str);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param to Storage on which data will be copied into.
|
||||
* @param data Data that will be serialized and stored in @c to.
|
||||
*
|
||||
* See more in @ref serialization.
|
||||
*/
|
||||
template <class Request>
|
||||
void to_bulk(Request& to, boost::string_view data)
|
||||
{
|
||||
auto const str = std::to_string(data.size());
|
||||
|
||||
to += to_code(type::blob_string);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += separator;
|
||||
to.append(std::cbegin(data), std::cend(data));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request, class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
|
||||
void to_bulk(Request& to, T n)
|
||||
{
|
||||
auto const s = std::to_string(n);
|
||||
to_bulk(to, boost::string_view{s});
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
auto has_push_response(boost::string_view cmd) -> bool;
|
||||
|
||||
template <class T>
|
||||
struct add_bulk_impl {
|
||||
template <class Request>
|
||||
static void add(Request& to, T const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <class U, class V>
|
||||
struct add_bulk_impl<std::pair<U, V>> {
|
||||
template <class Request>
|
||||
static void add(Request& to, std::pair<U, V> const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from.first);
|
||||
to_bulk(to, from.second);
|
||||
}
|
||||
};
|
||||
|
||||
template <class ...Ts>
|
||||
struct add_bulk_impl<boost::hana::tuple<Ts...>> {
|
||||
template <class Request>
|
||||
static void add(Request& to, boost::hana::tuple<Ts...> const& from)
|
||||
{
|
||||
using boost::hana::for_each;
|
||||
|
||||
// Fold expressions is C++17 so we use hana.
|
||||
//(detail::add_bulk(*request_, args), ...);
|
||||
|
||||
for_each(from, [&](auto const& e) {
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, e);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Request>
|
||||
void add_header(Request& to, type t, std::size_t size)
|
||||
{
|
||||
auto const str = std::to_string(size);
|
||||
|
||||
to += to_code(t);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request, class T>
|
||||
void add_bulk(Request& to, T const& data)
|
||||
{
|
||||
detail::add_bulk_impl<T>::add(to, data);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct bulk_counter;
|
||||
|
||||
template <class>
|
||||
struct bulk_counter {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct bulk_counter<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
template <class Request>
|
||||
void add_blob(Request& to, boost::string_view blob)
|
||||
{
|
||||
to.append(std::cbegin(blob), std::cend(blob));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request>
|
||||
void add_separator(Request& to)
|
||||
{
|
||||
to += separator;
|
||||
}
|
||||
} // detail
|
||||
|
||||
/** \brief Creates Redis requests.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* A request is composed of one or more Redis commands and is
|
||||
* referred to in the redis documentation as a pipeline, see
|
||||
* https://redis.io/topics/pipelining. For example
|
||||
*
|
||||
* @code
|
||||
* request r;
|
||||
* r.push("HELLO", 3);
|
||||
* r.push("FLUSHALL");
|
||||
* r.push("PING");
|
||||
* r.push("PING", "key");
|
||||
* r.push("QUIT");
|
||||
* @endcode
|
||||
*
|
||||
* \remarks
|
||||
*
|
||||
* \li Non-string types will be converted to string by using \c
|
||||
* to_bulk, which must be made available over ADL.
|
||||
* \li Uses std::string as internal storage.
|
||||
*/
|
||||
class request {
|
||||
public:
|
||||
/// Request configuration options.
|
||||
struct config {
|
||||
/** \brief If set to true, requests started with
|
||||
* `aedis::connection::async_exec` will fail if the connection is
|
||||
* lost while the request is pending. The default
|
||||
* behaviour is not to close requests.
|
||||
*/
|
||||
bool cancel_on_connection_lost = false;
|
||||
|
||||
/** \brief If true this request will be coalesced with other requests,
|
||||
* see https://redis.io/topics/pipelining. If false, this
|
||||
* request will be sent individually.
|
||||
*/
|
||||
bool coalesce = true;
|
||||
|
||||
/** \brief If set to true, requests started with
|
||||
* `aedis::connection::async_exec` will fail if the call happens
|
||||
* before the connection with Redis was stablished.
|
||||
*/
|
||||
bool cancel_if_not_connected = false;
|
||||
|
||||
/** \brief If true, the implementation will resend this
|
||||
* request if it remained unresponded when
|
||||
* `aedis::connection::async_run` completed. Has effect only if
|
||||
* cancel_on_connection_lost is true.
|
||||
*/
|
||||
bool retry = true;
|
||||
};
|
||||
|
||||
/** \brief Constructor
|
||||
*
|
||||
* \param cfg Configuration options.
|
||||
* \param resource Memory resource.
|
||||
*/
|
||||
explicit
|
||||
request(config cfg = config{false, true, false, true},
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: payload_(resource), cfg_{cfg}
|
||||
{}
|
||||
|
||||
|
||||
//// Returns the number of commands contained in this request.
|
||||
[[nodiscard]] auto size() const noexcept -> std::size_t { return commands_;};
|
||||
|
||||
// Returns the request payload.
|
||||
[[nodiscard]] auto payload() const noexcept -> auto const& { return payload_;}
|
||||
|
||||
/// Clears the request preserving allocated memory.
|
||||
void clear()
|
||||
{
|
||||
payload_.clear();
|
||||
commands_ = 0;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* \code
|
||||
* request req;
|
||||
* req.push("SET", "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the \c set command with value "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The command e.g redis or sentinel command.
|
||||
* \param args Command arguments.
|
||||
*/
|
||||
template <class... Ts>
|
||||
void push(boost::string_view cmd, Ts const&... args)
|
||||
{
|
||||
using boost::hana::for_each;
|
||||
using boost::hana::make_tuple;
|
||||
using resp3::type;
|
||||
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
detail::add_header(payload_, type::array, 1 + pack_size);
|
||||
detail::add_bulk(payload_, cmd);
|
||||
detail::add_bulk(payload_, make_tuple(args...));
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a key and have a
|
||||
* dynamic range of arguments. For example
|
||||
*
|
||||
* @code
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
* , {"key3", "value3"}
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range("HSET", "key", std::cbegin(map), std::cend(map));
|
||||
* @endcode
|
||||
*
|
||||
* \param cmd The command e.g. Redis or Sentinel command.
|
||||
* \param key The command key.
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class Key, class ForwardIterator>
|
||||
void push_range(boost::string_view cmd, Key const& key, ForwardIterator begin, ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = detail::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(payload_, type::array, 2 + size * distance);
|
||||
detail::add_bulk(payload_, cmd);
|
||||
detail::add_bulk(payload_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(payload_, *begin);
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a dynamic number
|
||||
* of arguments and don't have a key. For example
|
||||
*
|
||||
* \code
|
||||
* std::set<std::string> channels
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push("SUBSCRIBE", std::cbegin(channels), std::cend(channels));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range(boost::string_view cmd, ForwardIterator begin, ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = detail::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(payload_, type::array, 1 + size * distance);
|
||||
detail::add_bulk(payload_, cmd);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(payload_, *begin);
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param key Redis key.
|
||||
* \param range Range to send e.g. and \c std::map.
|
||||
*/
|
||||
template <class Key, class Range>
|
||||
void push_range(boost::string_view cmd, Key const& key, Range const& range,
|
||||
decltype(std::begin(range)) * = nullptr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param range Range to send e.g. and \c std::map.
|
||||
*/
|
||||
template <class Range>
|
||||
void push_range(boost::string_view cmd, Range const& range,
|
||||
decltype(std::begin(range)) * = nullptr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range(cmd, begin(range), end(range));
|
||||
}
|
||||
|
||||
/// Calls std::string::reserve on the internal storage.
|
||||
void reserve(std::size_t new_cap = 0)
|
||||
{ payload_.reserve(new_cap); }
|
||||
|
||||
/// Returns a const reference to the config object.
|
||||
[[nodiscard]] auto get_config() const noexcept -> auto const& {return cfg_; }
|
||||
|
||||
/// Returns a reference to the config object.
|
||||
[[nodiscard]] auto get_config() noexcept -> auto& {return cfg_; }
|
||||
|
||||
private:
|
||||
std::pmr::string payload_;
|
||||
std::size_t commands_ = 0;
|
||||
config cfg_;
|
||||
};
|
||||
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_SERIALIZER_HPP
|
||||
@@ -1,87 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_TYPE_HPP
|
||||
#define AEDIS_RESP3_TYPE_HPP
|
||||
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace aedis::resp3 {
|
||||
|
||||
/** \brief RESP3 data types.
|
||||
\ingroup high-level-api
|
||||
|
||||
The RESP3 specification can be found at https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md.
|
||||
*/
|
||||
enum class type
|
||||
{ /// Aggregate
|
||||
array,
|
||||
/// Aaggregate
|
||||
push,
|
||||
/// Aggregate
|
||||
set,
|
||||
/// Aggregate
|
||||
map,
|
||||
/// Aggregate
|
||||
attribute,
|
||||
/// Simple
|
||||
simple_string,
|
||||
/// Simple
|
||||
simple_error,
|
||||
/// Simple
|
||||
number,
|
||||
/// Simple
|
||||
doublean,
|
||||
/// Simple
|
||||
boolean,
|
||||
/// Simple
|
||||
big_number,
|
||||
/// Simple
|
||||
null,
|
||||
/// Simple
|
||||
blob_error,
|
||||
/// Simple
|
||||
verbatim_string,
|
||||
/// Simple
|
||||
blob_string,
|
||||
/// Simple
|
||||
streamed_string_part,
|
||||
/// Invalid
|
||||
invalid
|
||||
};
|
||||
|
||||
/** \brief Converts the data type to a string.
|
||||
* \ingroup high-level-api
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
auto to_string(type t) -> char const*;
|
||||
|
||||
/** \brief Writes the type to the output stream.
|
||||
* \ingroup high-level-api
|
||||
* \param os Output stream.
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
auto operator<<(std::ostream& os, type t) -> std::ostream&;
|
||||
|
||||
/* Checks whether the data type is an aggregate.
|
||||
*/
|
||||
auto is_aggregate(type t) -> bool;
|
||||
|
||||
// For map and attribute data types this function returns 2. All
|
||||
// other types have value 1.
|
||||
auto element_multiplicity(type t) -> std::size_t;
|
||||
|
||||
// Returns the wire code of a given type.
|
||||
auto to_code(type t) -> char;
|
||||
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
auto to_type(char c) -> type;
|
||||
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_TYPE_HPP
|
||||
@@ -1,64 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_WRITE_HPP
|
||||
#define AEDIS_RESP3_WRITE_HPP
|
||||
|
||||
#include <boost/asio/write.hpp>
|
||||
|
||||
namespace aedis::resp3 {
|
||||
|
||||
/** \brief Writes a request synchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
*/
|
||||
template<
|
||||
class SyncWriteStream,
|
||||
class Request
|
||||
>
|
||||
auto write(SyncWriteStream& stream, Request const& req)
|
||||
{
|
||||
return boost::asio::write(stream, boost::asio::buffer(req.payload()));
|
||||
}
|
||||
|
||||
template<
|
||||
class SyncWriteStream,
|
||||
class Request
|
||||
>
|
||||
auto write(
|
||||
SyncWriteStream& stream,
|
||||
Request const& req,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
return boost::asio::write(stream, boost::asio::buffer(req.payload()), ec);
|
||||
}
|
||||
|
||||
/** \brief Writes a request asynchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
* \param token Asio completion token.
|
||||
*/
|
||||
template<
|
||||
class AsyncWriteStream,
|
||||
class Request,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncWriteStream::executor_type>
|
||||
>
|
||||
auto async_write(
|
||||
AsyncWriteStream& stream,
|
||||
Request const& req,
|
||||
CompletionToken&& token =
|
||||
boost::asio::default_completion_token_t<typename AsyncWriteStream::executor_type>{})
|
||||
{
|
||||
return boost::asio::async_write(stream, boost::asio::buffer(req.payload()), token);
|
||||
}
|
||||
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_WRITE_HPP
|
||||
@@ -1,11 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <aedis/impl/error.ipp>
|
||||
#include <aedis/impl/endpoint.ipp>
|
||||
#include <aedis/resp3/impl/request.ipp>
|
||||
#include <aedis/resp3/impl/type.ipp>
|
||||
#include <aedis/resp3/detail/impl/parser.ipp>
|
||||
@@ -1,186 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_SSL_CONNECTION_HPP
|
||||
#define AEDIS_SSL_CONNECTION_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <aedis/detail/connection_base.hpp>
|
||||
#include <aedis/ssl/detail/connection_ops.hpp>
|
||||
|
||||
namespace aedis::ssl {
|
||||
|
||||
template <class>
|
||||
class connection;
|
||||
|
||||
/** \brief A SSL connection to the Redis server.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* This class keeps a healthy connection to the Redis instance where
|
||||
* commands can be sent at any time. For more details, please see the
|
||||
* documentation of each individual function.
|
||||
*
|
||||
* @tparam AsyncReadWriteStream A stream that supports reading and
|
||||
* writing.
|
||||
*
|
||||
*/
|
||||
template <class AsyncReadWriteStream>
|
||||
class connection<boost::asio::ssl::stream<AsyncReadWriteStream>> :
|
||||
private aedis::detail::connection_base<
|
||||
typename boost::asio::ssl::stream<AsyncReadWriteStream>::executor_type,
|
||||
connection<boost::asio::ssl::stream<AsyncReadWriteStream>>> {
|
||||
public:
|
||||
/// Type of the next layer
|
||||
using next_layer_type = boost::asio::ssl::stream<AsyncReadWriteStream>;
|
||||
|
||||
/// Executor type.
|
||||
using executor_type = typename next_layer_type::executor_type;
|
||||
using base_type = aedis::detail::connection_base<executor_type, connection<boost::asio::ssl::stream<AsyncReadWriteStream>>>;
|
||||
|
||||
/** \brief Connection configuration parameters.
|
||||
*/
|
||||
struct timeouts {
|
||||
/// Timeout of the resolve operation.
|
||||
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the connect operation.
|
||||
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the ssl handshake operation.
|
||||
std::chrono::steady_clock::duration handshake_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the resp3 handshake operation.
|
||||
std::chrono::steady_clock::duration resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
|
||||
/// Time interval of ping operations.
|
||||
std::chrono::steady_clock::duration ping_interval = std::chrono::seconds{1};
|
||||
};
|
||||
|
||||
/// Constructor
|
||||
explicit
|
||||
connection(
|
||||
executor_type ex,
|
||||
boost::asio::ssl::context& ctx,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: base_type{ex, resource}
|
||||
, stream_{ex, ctx}
|
||||
{
|
||||
}
|
||||
|
||||
/// Constructor
|
||||
explicit
|
||||
connection(
|
||||
boost::asio::io_context& ioc,
|
||||
boost::asio::ssl::context& ctx,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: connection(ioc.get_executor(), ctx, resource)
|
||||
{ }
|
||||
|
||||
/// Returns the associated executor.
|
||||
auto get_executor() {return stream_.get_executor();}
|
||||
|
||||
/// Reset the underlying stream.
|
||||
void reset_stream(boost::asio::ssl::context& ctx)
|
||||
{
|
||||
stream_ = next_layer_type{stream_.get_executor(), ctx};
|
||||
}
|
||||
|
||||
/// Returns a reference to the next layer.
|
||||
auto& next_layer() noexcept { return stream_; }
|
||||
|
||||
/// Returns a const reference to the next layer.
|
||||
auto const& next_layer() const noexcept { return stream_; }
|
||||
|
||||
/** @brief Establishes a connection with the Redis server asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_run for more information.
|
||||
*/
|
||||
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto
|
||||
async_run(
|
||||
endpoint ep,
|
||||
timeouts ts = timeouts{},
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_run(ep, ts, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Executes a command on the Redis server asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_exec for more information.
|
||||
*/
|
||||
template <
|
||||
class Adapter = aedis::detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_exec(
|
||||
resp3::request const& req,
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_exec(req, adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Receives server side pushes asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_receive for detailed information.
|
||||
*/
|
||||
template <
|
||||
class Adapter = aedis::detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive(
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_receive(adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* See aedis::connection::cancel for more information.
|
||||
*/
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{ return base_type::cancel(op); }
|
||||
|
||||
private:
|
||||
using this_type = connection<next_layer_type>;
|
||||
|
||||
template <class, class> friend class aedis::detail::connection_base;
|
||||
template <class, class> friend struct aedis::detail::exec_op;
|
||||
template <class, class> friend struct detail::ssl_connect_with_timeout_op;
|
||||
template <class, class> friend struct aedis::detail::run_op;
|
||||
template <class> friend struct aedis::detail::writer_op;
|
||||
template <class> friend struct aedis::detail::check_idle_op;
|
||||
template <class> friend struct aedis::detail::reader_op;
|
||||
template <class, class> friend struct aedis::detail::exec_read_op;
|
||||
template <class> friend struct aedis::detail::ping_op;
|
||||
|
||||
auto& lowest_layer() noexcept { return stream_.lowest_layer(); }
|
||||
auto is_open() const noexcept { return stream_.next_layer().is_open(); }
|
||||
void close() { stream_.next_layer().close(); }
|
||||
|
||||
template <class Timer, class CompletionToken>
|
||||
auto
|
||||
async_connect(
|
||||
boost::asio::ip::tcp::resolver::results_type const& endpoints,
|
||||
timeouts ts,
|
||||
Timer& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::ssl_connect_with_timeout_op<this_type, Timer>{this, &endpoints, ts, &timer}, token, stream_);
|
||||
}
|
||||
|
||||
next_layer_type stream_;
|
||||
};
|
||||
|
||||
} // aedis::ssl
|
||||
|
||||
#endif // AEDIS_SSL_CONNECTION_HPP
|
||||
@@ -1,113 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
#define AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::ssl::detail
|
||||
{
|
||||
|
||||
template <class Stream>
|
||||
struct handshake_op {
|
||||
Stream* stream;
|
||||
aedis::detail::conn_timer_t<typename Stream::executor_type>* timer;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token)
|
||||
{
|
||||
return stream->async_handshake(boost::asio::ssl::stream_base::client, token);
|
||||
},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1); return;
|
||||
case 1:
|
||||
{
|
||||
BOOST_ASSERT_MSG(!ec2, "handshake_op: Incompatible state.");
|
||||
self.complete(error::ssl_handshake_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
class CompletionToken
|
||||
>
|
||||
auto async_handshake(
|
||||
Stream& stream,
|
||||
aedis::detail::conn_timer_t<typename Stream::executor_type>& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(handshake_op<Stream>{&stream, &timer}, token, stream, timer);
|
||||
}
|
||||
|
||||
template <class Conn, class Timer>
|
||||
struct ssl_connect_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
boost::asio::ip::tcp::resolver::results_type const* endpoints = nullptr;
|
||||
typename Conn::timeouts ts;
|
||||
Timer* timer = nullptr;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::endpoint const& = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
timer->expires_after(ts.connect_timeout);
|
||||
yield
|
||||
aedis::detail::async_connect(
|
||||
conn->lowest_layer(), *timer, *endpoints, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
|
||||
timer->expires_after(ts.handshake_timeout);
|
||||
yield
|
||||
async_handshake(conn->next_layer(), *timer, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::ssl::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
@@ -4,13 +4,16 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_HPP
|
||||
#define AEDIS_HPP
|
||||
#ifndef BOOST_REDIS_HPP
|
||||
#define BOOST_REDIS_HPP
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/adapt.hpp>
|
||||
#include <aedis/connection.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/request.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/redis/ignore.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
|
||||
/** @defgroup high-level-api Reference
|
||||
*
|
||||
@@ -22,4 +25,4 @@
|
||||
* This page contains the documentation of the Aedis low-level API.
|
||||
*/
|
||||
|
||||
#endif // AEDIS_HPP
|
||||
#endif // BOOST_REDIS_HPP
|
||||
80
include/boost/redis/adapter/adapt.hpp
Normal file
80
include/boost/redis/adapter/adapt.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_ADAPTER_ADAPT_HPP
|
||||
#define BOOST_REDIS_ADAPTER_ADAPT_HPP
|
||||
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/redis/adapter/detail/result_traits.hpp>
|
||||
#include <boost/redis/adapter/detail/response_traits.hpp>
|
||||
#include <boost/mp11.hpp>
|
||||
#include <boost/system.hpp>
|
||||
|
||||
#include <tuple>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
namespace boost::redis::adapter
|
||||
{
|
||||
|
||||
/** @brief Adapts a type to be used as a response.
|
||||
*
|
||||
* The type T must be either
|
||||
*
|
||||
* 1. a response<T1, T2, T3, ...> or
|
||||
* 2. std::vector<node<String>>
|
||||
*
|
||||
* The types T1, T2, etc can be any STL container, any integer type
|
||||
* and `std::string`.
|
||||
*
|
||||
* @param t Tuple containing the responses.
|
||||
*/
|
||||
template<class T>
|
||||
auto boost_redis_adapt(T& t) noexcept
|
||||
{
|
||||
return detail::response_traits<T>::adapt(t);
|
||||
}
|
||||
|
||||
/** @brief Adapts user data to read operations.
|
||||
* @ingroup low-level-api
|
||||
*
|
||||
* STL containers, \c resp3::response and built-in types are supported and
|
||||
* can be used in conjunction with \c std::optional<T>.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code
|
||||
* std::unordered_map<std::string, std::string> cont;
|
||||
* co_await async_read(socket, buffer, adapt(cont));
|
||||
* @endcode
|
||||
*
|
||||
* For a transaction
|
||||
*
|
||||
* @code
|
||||
* sr.push(command::multi);
|
||||
* sr.push(command::ping, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push_range(command::rpush, ...);
|
||||
* sr.push(command::lrange, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push(command::exec);
|
||||
*
|
||||
* co_await async_write(socket, buffer(request));
|
||||
*
|
||||
* // Reads the response to a transaction
|
||||
* resp3::response<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
* @endcode
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt2(T& t = redis::ignore) noexcept
|
||||
{ return detail::result_traits<T>::adapt(t); }
|
||||
|
||||
} // boost::redis::adapter
|
||||
|
||||
#endif // BOOST_REDIS_ADAPTER_ADAPT_HPP
|
||||
@@ -4,8 +4,15 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
#define AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
#ifndef BOOST_REDIS_ADAPTER_ADAPTERS_HPP
|
||||
#define BOOST_REDIS_ADAPTER_ADAPTERS_HPP
|
||||
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/redis/adapter/result.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <set>
|
||||
#include <optional>
|
||||
@@ -18,86 +25,66 @@
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <charconv>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
// See https://stackoverflow.com/a/31658120/1077832
|
||||
#include<ciso646>
|
||||
#ifdef _LIBCPP_VERSION
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis::adapter::detail {
|
||||
|
||||
inline
|
||||
auto parse_double(
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
boost::system::error_code& ec) -> double
|
||||
namespace boost::redis::adapter::detail
|
||||
{
|
||||
static constexpr boost::spirit::x3::real_parser<double> p{};
|
||||
double ret = 0;
|
||||
if (!parse(data, data + size, p, ret))
|
||||
ec = error::not_a_double;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Serialization.
|
||||
|
||||
template <class T>
|
||||
auto from_bulk(
|
||||
T& i,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec) -> typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
auto boost_redis_from_bulk(T& i, std::string_view sv, system::error_code& ec) -> typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
{
|
||||
i = resp3::detail::parse_uint(sv.data(), sv.size(), ec);
|
||||
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), i);
|
||||
if (res.ec != std::errc())
|
||||
ec = redis::error::not_a_number;
|
||||
}
|
||||
|
||||
inline
|
||||
void from_bulk(
|
||||
bool& t,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code&)
|
||||
void boost_redis_from_bulk(bool& t, std::string_view sv, system::error_code&)
|
||||
{
|
||||
t = *sv.data() == 't';
|
||||
}
|
||||
|
||||
inline
|
||||
void from_bulk(
|
||||
double& d,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
void boost_redis_from_bulk(double& d, std::string_view sv, system::error_code& ec)
|
||||
{
|
||||
d = parse_double(sv.data(), sv.size(), ec);
|
||||
#ifdef _LIBCPP_VERSION
|
||||
// The string in sv is not null terminated and we also don't know
|
||||
// if there is enough space at the end for a null char. The easiest
|
||||
// thing to do is to create a temporary.
|
||||
std::string const tmp{sv.data(), sv.data() + std::size(sv)};
|
||||
char* end{};
|
||||
d = std::strtod(tmp.data(), &end);
|
||||
if (d == HUGE_VAL || d == 0)
|
||||
ec = redis::error::not_a_double;
|
||||
#else
|
||||
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), d);
|
||||
if (res.ec != std::errc())
|
||||
ec = redis::error::not_a_double;
|
||||
#endif // _LIBCPP_VERSION
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
void
|
||||
from_bulk(
|
||||
boost_redis_from_bulk(
|
||||
std::basic_string<CharT, Traits, Allocator>& s,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code&)
|
||||
std::string_view sv,
|
||||
system::error_code&)
|
||||
{
|
||||
s.append(sv.data(), sv.size());
|
||||
}
|
||||
|
||||
//================================================
|
||||
|
||||
inline
|
||||
void set_on_resp3_error(resp3::type t, boost::system::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case resp3::type::simple_error: ec = error::resp3_simple_error; return;
|
||||
case resp3::type::blob_error: ec = error::resp3_blob_error; return;
|
||||
case resp3::type::null: ec = error::resp3_null; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
class general_aggregate {
|
||||
private:
|
||||
@@ -105,9 +92,17 @@ private:
|
||||
|
||||
public:
|
||||
explicit general_aggregate(Result* c = nullptr): result_(c) {}
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code&)
|
||||
{
|
||||
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
|
||||
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::blob_error:
|
||||
case resp3::type::simple_error:
|
||||
*result_ = error{nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)}};
|
||||
break;
|
||||
default:
|
||||
result_->value().push_back({nd.data_type, nd.aggregate_size, nd.depth, std::string{std::cbegin(nd.value), std::cend(nd.value)}});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,13 +114,20 @@ private:
|
||||
public:
|
||||
explicit general_simple(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code& ec)
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code&)
|
||||
{
|
||||
result_->data_type = n.data_type;
|
||||
result_->aggregate_size = n.aggregate_size;
|
||||
result_->depth = n.depth;
|
||||
result_->value.assign(n.value.data(), n.value.size());
|
||||
set_on_resp3_error(n.data_type, ec);
|
||||
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::blob_error:
|
||||
case resp3::type::simple_error:
|
||||
*result_ = error{nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)}};
|
||||
break;
|
||||
default:
|
||||
result_->value().data_type = nd.data_type;
|
||||
result_->value().aggregate_size = nd.aggregate_size;
|
||||
result_->value().depth = nd.depth;
|
||||
result_->value().value.assign(nd.value.data(), nd.value.size());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,19 +139,15 @@ public:
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& n,
|
||||
boost::system::error_code& ec)
|
||||
resp3::basic_node<std::string_view> const& n,
|
||||
system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(n.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(n.data_type)) {
|
||||
ec = error::expects_resp3_simple_type;
|
||||
ec = redis::error::expects_resp3_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
from_bulk(result, n.value, ec);
|
||||
boost_redis_from_bulk(result, n.value, ec);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,28 +163,24 @@ public:
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (nd.data_type != resp3::type::set)
|
||||
ec = error::expects_resp3_set;
|
||||
ec = redis::error::expects_resp3_set;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = error::expects_resp3_set;
|
||||
ec = redis::error::expects_resp3_set;
|
||||
return;
|
||||
}
|
||||
|
||||
typename Result::key_type obj;
|
||||
from_bulk(obj, nd.value, ec);
|
||||
boost_redis_from_bulk(obj, nd.value, ec);
|
||||
hint_ = result.insert(hint_, std::move(obj));
|
||||
}
|
||||
};
|
||||
@@ -204,33 +198,29 @@ public:
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (element_multiplicity(nd.data_type) != 2)
|
||||
ec = error::expects_resp3_map;
|
||||
ec = redis::error::expects_resp3_map;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = error::expects_resp3_map;
|
||||
ec = redis::error::expects_resp3_map;
|
||||
return;
|
||||
}
|
||||
|
||||
if (on_key_) {
|
||||
typename Result::key_type obj;
|
||||
from_bulk(obj, nd.value, ec);
|
||||
boost_redis_from_bulk(obj, nd.value, ec);
|
||||
current_ = result.insert(current_, {std::move(obj), {}});
|
||||
} else {
|
||||
typename Result::mapped_type obj;
|
||||
from_bulk(obj, nd.value, ec);
|
||||
boost_redis_from_bulk(obj, nd.value, ec);
|
||||
current_->second = std::move(obj);
|
||||
}
|
||||
|
||||
@@ -246,19 +236,15 @@ public:
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
auto const m = element_multiplicity(nd.data_type);
|
||||
result.reserve(result.size() + m * nd.aggregate_size);
|
||||
} else {
|
||||
result.push_back({});
|
||||
from_bulk(result.back(), nd.value, ec);
|
||||
boost_redis_from_bulk(result.back(), nd.value, ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -274,31 +260,27 @@ public:
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (i_ != -1) {
|
||||
ec = error::nested_aggregate_not_supported;
|
||||
ec = redis::error::nested_aggregate_not_supported;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
|
||||
ec = error::incompatible_size;
|
||||
ec = redis::error::incompatible_size;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (i_ == -1) {
|
||||
ec = error::expects_resp3_aggregate;
|
||||
ec = redis::error::expects_resp3_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
from_bulk(result.at(i_), nd.value, ec);
|
||||
boost_redis_from_bulk(result.at(i_), nd.value, ec);
|
||||
}
|
||||
|
||||
++i_;
|
||||
@@ -313,22 +295,18 @@ struct list_impl {
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (!is_aggregate(nd.data_type)) {
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
if (nd.depth < 1) {
|
||||
ec = error::expects_resp3_aggregate;
|
||||
ec = redis::error::expects_resp3_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
result.push_back({});
|
||||
from_bulk(result.back(), nd.value, ec);
|
||||
boost_redis_from_bulk(result.back(), nd.value, ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -376,52 +354,106 @@ struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T,
|
||||
|
||||
//---------------------------------------------------
|
||||
|
||||
template <class>
|
||||
class wrapper;
|
||||
|
||||
template <class Result>
|
||||
class wrapper {
|
||||
class wrapper<result<Result>> {
|
||||
public:
|
||||
using response_type = result<Result>;
|
||||
private:
|
||||
Result* result_;
|
||||
response_type* result_;
|
||||
typename impl_map<Result>::type impl_;
|
||||
|
||||
public:
|
||||
explicit wrapper(Result* t = nullptr) : result_(t)
|
||||
{ impl_.on_value_available(*result_); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
bool set_if_resp3_error(resp3::basic_node<std::string_view> const& nd) noexcept
|
||||
{
|
||||
BOOST_ASSERT(result_);
|
||||
impl_(*result_, nd, ec);
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::null:
|
||||
case resp3::type::simple_error:
|
||||
case resp3::type::blob_error:
|
||||
*result_ = error{nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)}};
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class wrapper<std::optional<T>> {
|
||||
private:
|
||||
std::optional<T>* result_;
|
||||
typename impl_map<T>::type impl_{};
|
||||
|
||||
public:
|
||||
explicit wrapper(std::optional<T>* o = nullptr) : result_(o) {}
|
||||
explicit wrapper(response_type* t = nullptr) : result_(t)
|
||||
{
|
||||
if (result_) {
|
||||
result_->value() = Result{};
|
||||
impl_.on_value_available(result_->value());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
if (nd.data_type == resp3::type::null)
|
||||
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
|
||||
|
||||
if (result_->has_error())
|
||||
return;
|
||||
|
||||
if (!result_->has_value()) {
|
||||
*result_ = T{};
|
||||
impl_.on_value_available(result_->value());
|
||||
}
|
||||
if (set_if_resp3_error(nd))
|
||||
return;
|
||||
|
||||
BOOST_ASSERT(result_);
|
||||
impl_(result_->value(), nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::adapter:.detail
|
||||
template <class T>
|
||||
class wrapper<result<std::optional<T>>> {
|
||||
public:
|
||||
using response_type = result<std::optional<T>>;
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
private:
|
||||
response_type* result_;
|
||||
typename impl_map<T>::type impl_{};
|
||||
|
||||
bool set_if_resp3_error(resp3::basic_node<std::string_view> const& nd) noexcept
|
||||
{
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::blob_error:
|
||||
case resp3::type::simple_error:
|
||||
*result_ = error{nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)}};
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit wrapper(response_type* o = nullptr) : result_(o) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
|
||||
|
||||
if (result_->has_error())
|
||||
return;
|
||||
|
||||
if (set_if_resp3_error(nd))
|
||||
return;
|
||||
|
||||
if (nd.data_type == resp3::type::null)
|
||||
return;
|
||||
|
||||
if (!result_->value().has_value()) {
|
||||
result_->value() = T{};
|
||||
impl_.on_value_available(result_->value().value());
|
||||
}
|
||||
|
||||
impl_(result_->value().value(), nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // boost::redis::adapter::detail
|
||||
|
||||
#endif // BOOST_REDIS_ADAPTER_ADAPTERS_HPP
|
||||
164
include/boost/redis/adapter/detail/response_traits.hpp
Normal file
164
include/boost/redis/adapter/detail/response_traits.hpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_ADAPTER_DETAIL_RESPONSE_TRAITS_HPP
|
||||
#define BOOST_REDIS_ADAPTER_DETAIL_RESPONSE_TRAITS_HPP
|
||||
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/redis/adapter/detail/result_traits.hpp>
|
||||
#include <boost/mp11.hpp>
|
||||
#include <boost/system.hpp>
|
||||
|
||||
#include <tuple>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
namespace boost::redis::adapter::detail
|
||||
{
|
||||
|
||||
class ignore_adapter {
|
||||
public:
|
||||
void
|
||||
operator()(std::size_t, resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
|
||||
{
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::simple_error: ec = redis::error::resp3_simple_error; break;
|
||||
case resp3::type::blob_error: ec = redis::error::resp3_blob_error; break;
|
||||
case resp3::type::null: ec = redis::error::resp3_null; break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
};
|
||||
|
||||
template <class Response>
|
||||
class static_adapter {
|
||||
private:
|
||||
static constexpr auto size = std::tuple_size<Response>::value;
|
||||
using adapter_tuple = mp11::mp_transform<adapter_t, Response>;
|
||||
using variant_type = mp11::mp_rename<adapter_tuple, std::variant>;
|
||||
using adapters_array_type = std::array<variant_type, size>;
|
||||
|
||||
adapters_array_type adapters_;
|
||||
|
||||
public:
|
||||
explicit static_adapter(Response& r)
|
||||
{
|
||||
assigner<size - 1>::assign(adapters_, r);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return size;}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t i,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
using std::visit;
|
||||
// I am usure whether this should be an error or an assertion.
|
||||
BOOST_ASSERT(i < adapters_.size());
|
||||
visit([&](auto& arg){arg(nd, ec);}, adapters_.at(i));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Vector>
|
||||
class vector_adapter {
|
||||
private:
|
||||
using adapter_type = typename result_traits<Vector>::adapter_type;
|
||||
adapter_type adapter_;
|
||||
|
||||
public:
|
||||
explicit vector_adapter(Vector& v)
|
||||
: adapter_{internal_adapt(v)}
|
||||
{ }
|
||||
|
||||
[[nodiscard]]
|
||||
auto
|
||||
get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class>
|
||||
struct response_traits;
|
||||
|
||||
template <>
|
||||
struct response_traits<ignore_t> {
|
||||
using response_type = ignore_t;
|
||||
using adapter_type = detail::ignore_adapter;
|
||||
|
||||
static auto adapt(response_type&) noexcept
|
||||
{ return detail::ignore_adapter{}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<result<ignore_t>> {
|
||||
using response_type = result<ignore_t>;
|
||||
using adapter_type = detail::ignore_adapter;
|
||||
|
||||
static auto adapt(response_type&) noexcept
|
||||
{ return detail::ignore_adapter{}; }
|
||||
};
|
||||
|
||||
template <class String, class Allocator>
|
||||
struct response_traits<result<std::vector<resp3::basic_node<String>, Allocator>>> {
|
||||
using response_type = result<std::vector<resp3::basic_node<String>, Allocator>>;
|
||||
using adapter_type = vector_adapter<response_type>;
|
||||
|
||||
static auto adapt(response_type& v) noexcept
|
||||
{ return adapter_type{v}; }
|
||||
};
|
||||
|
||||
template <class ...Ts>
|
||||
struct response_traits<response<Ts...>> {
|
||||
using response_type = response<Ts...>;
|
||||
using adapter_type = static_adapter<response_type>;
|
||||
|
||||
static auto adapt(response_type& r) noexcept
|
||||
{ return adapter_type{r}; }
|
||||
};
|
||||
|
||||
template <class Adapter>
|
||||
class wrapper {
|
||||
public:
|
||||
explicit wrapper(Adapter adapter) : adapter_{adapter} {}
|
||||
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
|
||||
{ return adapter_(0, nd, ec); }
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return adapter_.get_supported_response_size();}
|
||||
|
||||
private:
|
||||
Adapter adapter_;
|
||||
};
|
||||
|
||||
template <class Adapter>
|
||||
auto make_adapter_wrapper(Adapter adapter)
|
||||
{
|
||||
return wrapper{adapter};
|
||||
}
|
||||
|
||||
} // boost::redis::adapter::detail
|
||||
|
||||
#endif // BOOST_REDIS_ADAPTER_DETAIL_RESPONSE_TRAITS_HPP
|
||||
@@ -4,77 +4,77 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
#define AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
#ifndef BOOST_REDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
#define BOOST_REDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/redis/ignore.hpp>
|
||||
#include <boost/redis/adapter/detail/adapters.hpp>
|
||||
#include <boost/redis/adapter/result.hpp>
|
||||
#include <boost/redis/adapter/ignore.hpp>
|
||||
#include <boost/mp11.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
#include <boost/variant2.hpp>
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/adapter/detail/adapters.hpp>
|
||||
|
||||
namespace aedis::adapter::detail {
|
||||
|
||||
using ignore = std::decay_t<decltype(std::ignore)>;
|
||||
namespace boost::redis::adapter::detail
|
||||
{
|
||||
|
||||
/* Traits class for response objects.
|
||||
*
|
||||
* Provides traits for all supported response types i.e. all STL
|
||||
* containers and C++ buil-in types.
|
||||
*/
|
||||
template <class ResponseType>
|
||||
struct response_traits {
|
||||
using adapter_type = adapter::detail::wrapper<typename std::decay<ResponseType>::type>;
|
||||
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
|
||||
template <class Result>
|
||||
struct result_traits {
|
||||
using adapter_type = adapter::detail::wrapper<typename std::decay<Result>::type>;
|
||||
static auto adapt(Result& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<ignore> {
|
||||
using response_type = ignore;
|
||||
using adapter_type = resp3::detail::ignore_response;
|
||||
struct result_traits<result<ignore_t>> {
|
||||
using response_type = result<ignore_t>;
|
||||
using adapter_type = ignore;
|
||||
static auto adapt(response_type) noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct result_traits<ignore_t> {
|
||||
using response_type = ignore_t;
|
||||
using adapter_type = ignore;
|
||||
static auto adapt(response_type) noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct response_traits<resp3::node<T>> {
|
||||
using response_type = resp3::node<T>;
|
||||
struct result_traits<result<resp3::basic_node<T>>> {
|
||||
using response_type = result<resp3::basic_node<T>>;
|
||||
using adapter_type = adapter::detail::general_simple<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class String, class Allocator>
|
||||
struct response_traits<std::vector<resp3::node<String>, Allocator>> {
|
||||
using response_type = std::vector<resp3::node<String>, Allocator>;
|
||||
struct result_traits<result<std::vector<resp3::basic_node<String>, Allocator>>> {
|
||||
using response_type = result<std::vector<resp3::basic_node<String>, Allocator>>;
|
||||
using adapter_type = adapter::detail::general_aggregate<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<void> {
|
||||
using response_type = void;
|
||||
using adapter_type = resp3::detail::ignore_response;
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using adapter_t = typename response_traits<std::decay_t<T>>::adapter_type;
|
||||
using adapter_t = typename result_traits<std::decay_t<T>>::adapter_type;
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<std::decay_t<T>>::adapt(t); }
|
||||
{ return result_traits<std::decay_t<T>>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[N] = internal_adapt(std::get<N>(from));
|
||||
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
|
||||
assigner<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
@@ -84,32 +84,39 @@ struct assigner<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[0] = internal_adapt(std::get<0>(from));
|
||||
dest[0].template emplace<0>(internal_adapt(std::get<0>(from)));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
class static_aggregate_adapter {
|
||||
class static_aggregate_adapter;
|
||||
|
||||
template <class Tuple>
|
||||
class static_aggregate_adapter<result<Tuple>> {
|
||||
private:
|
||||
using adapters_array_type =
|
||||
std::array<
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
mp11::mp_rename<
|
||||
mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
boost::variant2::variant>,
|
||||
std::variant>,
|
||||
std::tuple_size<Tuple>::value>;
|
||||
|
||||
std::size_t i_ = 0;
|
||||
std::size_t aggregate_size_ = 0;
|
||||
adapters_array_type adapters_;
|
||||
result<Tuple>* res_ = nullptr;
|
||||
|
||||
public:
|
||||
explicit static_aggregate_adapter(Tuple* r = nullptr)
|
||||
explicit static_aggregate_adapter(result<Tuple>* r = nullptr)
|
||||
{
|
||||
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r);
|
||||
if (r) {
|
||||
res_ = r;
|
||||
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, r->value());
|
||||
}
|
||||
}
|
||||
|
||||
void count(resp3::node<boost::string_view> const& nd)
|
||||
void count(resp3::basic_node<std::string_view> const& nd)
|
||||
{
|
||||
if (nd.depth == 1) {
|
||||
if (is_aggregate(nd.data_type))
|
||||
@@ -124,17 +131,14 @@ public:
|
||||
++i_;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
|
||||
{
|
||||
using boost::variant2::visit;
|
||||
using std::visit;
|
||||
|
||||
if (nd.depth == 0) {
|
||||
auto const real_aggr_size = nd.aggregate_size * element_multiplicity(nd.data_type);
|
||||
if (real_aggr_size != std::tuple_size<Tuple>::value)
|
||||
ec = error::incompatible_size;
|
||||
ec = redis::error::incompatible_size;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -145,13 +149,13 @@ public:
|
||||
};
|
||||
|
||||
template <class... Ts>
|
||||
struct response_traits<std::tuple<Ts...>>
|
||||
struct result_traits<result<std::tuple<Ts...>>>
|
||||
{
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using response_type = result<std::tuple<Ts...>>;
|
||||
using adapter_type = static_aggregate_adapter<response_type>;
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // aedis::adapter::detail
|
||||
} // boost::redis::adapter::detail
|
||||
|
||||
#endif // AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
#endif // BOOST_REDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
37
include/boost/redis/adapter/ignore.hpp
Normal file
37
include/boost/redis/adapter/ignore.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_ADAPTER_IGNORE_HPP
|
||||
#define BOOST_REDIS_ADAPTER_IGNORE_HPP
|
||||
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace boost::redis::adapter
|
||||
{
|
||||
|
||||
/** @brief An adapter that ignores responses
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* RESP3 errors won't be ignored.
|
||||
*/
|
||||
struct ignore {
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
|
||||
{
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::simple_error: ec = redis::error::resp3_simple_error; break;
|
||||
case resp3::type::blob_error: ec = redis::error::resp3_blob_error; break;
|
||||
case resp3::type::null: ec = redis::error::resp3_null; break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // boost::redis::adapter
|
||||
|
||||
#endif // BOOST_REDIS_ADAPTER_IGNORE_HPP
|
||||
81
include/boost/redis/adapter/result.hpp
Normal file
81
include/boost/redis/adapter/result.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_ADAPTER_RESULT_HPP
|
||||
#define BOOST_REDIS_ADAPTER_RESULT_HPP
|
||||
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/system/result.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace boost::redis::adapter
|
||||
{
|
||||
|
||||
/** @brief Stores any resp3 error
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
struct error {
|
||||
/// RESP3 error data type.
|
||||
resp3::type data_type = resp3::type::invalid;
|
||||
|
||||
/// Diagnostic error message sent by Redis.
|
||||
std::string diagnostic;
|
||||
};
|
||||
|
||||
/** @brief Compares two error objects for equality
|
||||
* @relates error
|
||||
*
|
||||
* @param a Left hand side error object.
|
||||
* @param b Right hand side error object.
|
||||
*/
|
||||
inline bool operator==(error const& a, error const& b)
|
||||
{
|
||||
return a.data_type == b.data_type && a.diagnostic == b.diagnostic;
|
||||
}
|
||||
|
||||
/** @brief Compares two error objects for difference
|
||||
* @relates error
|
||||
*
|
||||
* @param a Left hand side error object.
|
||||
* @param b Right hand side error object.
|
||||
*/
|
||||
inline bool operator!=(error const& a, error const& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/** @brief Stores response to individual Redis commands
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
template <class Value>
|
||||
using result = system::result<Value, error>;
|
||||
|
||||
BOOST_NORETURN inline void
|
||||
throw_exception_from_error(error const & e, boost::source_location const &)
|
||||
{
|
||||
system::error_code ec;
|
||||
switch (e.data_type) {
|
||||
case resp3::type::simple_error:
|
||||
ec = redis::error::resp3_simple_error;
|
||||
break;
|
||||
case resp3::type::blob_error:
|
||||
ec = redis::error::resp3_blob_error;
|
||||
break;
|
||||
case resp3::type::null:
|
||||
ec = redis::error::resp3_null;
|
||||
break;
|
||||
default:
|
||||
BOOST_ASSERT_MSG(false, "Unexpected data type.");
|
||||
}
|
||||
|
||||
throw system::system_error(ec, e.diagnostic);
|
||||
}
|
||||
|
||||
} // boost::redis::adapter
|
||||
|
||||
#endif // BOOST_REDIS_ADAPTER_RESULT_HPP
|
||||
85
include/boost/redis/config.hpp
Normal file
85
include/boost/redis/config.hpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_CONFIG_HPP
|
||||
#define BOOST_REDIS_CONFIG_HPP
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
namespace boost::redis
|
||||
{
|
||||
|
||||
/** @brief Address of a Redis server
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
struct address {
|
||||
/// Redis host.
|
||||
std::string host = "127.0.0.1";
|
||||
/// Redis port.
|
||||
std::string port = "6379";
|
||||
};
|
||||
|
||||
/** @brief Configure parameters used by the connection classes
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
struct config {
|
||||
/// Uses SSL instead of a plain connection.
|
||||
bool use_ssl = false;
|
||||
|
||||
/// Address of the Redis server.
|
||||
address addr = address{"127.0.0.1", "6379"};
|
||||
|
||||
/** @brief Username passed to the
|
||||
* [HELLO](https://redis.io/commands/hello/) command. If left
|
||||
* empty `HELLO` will be sent without authentication parameters.
|
||||
*/
|
||||
std::string username;
|
||||
|
||||
/** @brief Password passed to the
|
||||
* [HELLO](https://redis.io/commands/hello/) command. If left
|
||||
* empty `HELLO` will be sent without authentication parameters.
|
||||
*/
|
||||
std::string password;
|
||||
|
||||
/// Client name parameter of the [HELLO](https://redis.io/commands/hello/) command.
|
||||
std::string clientname = "Boost.Redis";
|
||||
|
||||
/// Database that will be passed to the [SELECT](https://redis.io/commands/hello/) command.
|
||||
std::optional<int> database_index = 0;
|
||||
|
||||
/// Message used by the health-checker in `boost::redis::connection::async_run`.
|
||||
std::string health_check_id = "Boost.Redis";
|
||||
|
||||
/// Logger prefix, see `boost::redis::logger`.
|
||||
std::string log_prefix = "(Boost.Redis) ";
|
||||
|
||||
/// Time the resolve operation is allowed to last.
|
||||
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Time the connect operation is allowed to last.
|
||||
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Time the SSL handshake operation is allowed to last.
|
||||
std::chrono::steady_clock::duration ssl_handshake_timeout = std::chrono::seconds{10};
|
||||
|
||||
/** Health checks interval.
|
||||
*
|
||||
* To disable health-checks pass zero as duration.
|
||||
*/
|
||||
std::chrono::steady_clock::duration health_check_interval = std::chrono::seconds{2};
|
||||
|
||||
/** @brief Time waited before trying a reconnection.
|
||||
*
|
||||
* To disable reconnection pass zero as duration.
|
||||
*/
|
||||
std::chrono::steady_clock::duration reconnect_wait_interval = std::chrono::seconds{1};
|
||||
};
|
||||
|
||||
} // boost::redis
|
||||
|
||||
#endif // BOOST_REDIS_CONFIG_HPP
|
||||
388
include/boost/redis/connection.hpp
Normal file
388
include/boost/redis/connection.hpp
Normal file
@@ -0,0 +1,388 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_CONNECTION_HPP
|
||||
#define BOOST_REDIS_CONNECTION_HPP
|
||||
|
||||
#include <boost/redis/detail/connection_base.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/any_completion_handler.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
|
||||
namespace boost::redis {
|
||||
namespace detail
|
||||
{
|
||||
template <class Connection, class Logger>
|
||||
struct reconnection_op {
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, system::error_code ec = {})
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->impl_.async_run(conn_->cfg_, logger_, std::move(self));
|
||||
conn_->cancel(operation::receive);
|
||||
logger_.on_connection_lost(ec);
|
||||
if (!conn_->will_reconnect() || is_cancelled(self)) {
|
||||
conn_->cancel(operation::reconnection);
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
conn_->timer_.expires_after(conn_->cfg_.reconnect_wait_interval);
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->timer_.async_wait(std::move(self));
|
||||
BOOST_REDIS_CHECK_OP0(;)
|
||||
if (!conn_->will_reconnect()) {
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
conn_->reset_stream();
|
||||
}
|
||||
}
|
||||
};
|
||||
} // detail
|
||||
|
||||
/** @brief A SSL connection to the Redis server.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* This class keeps a healthy connection to the Redis instance where
|
||||
* commands can be sent at any time. For more details, please see the
|
||||
* documentation of each individual function.
|
||||
*
|
||||
* @tparam Socket The socket type e.g. asio::ip::tcp::socket.
|
||||
*
|
||||
*/
|
||||
template <class Executor>
|
||||
class basic_connection {
|
||||
public:
|
||||
/// Executor type.
|
||||
using executor_type = Executor;
|
||||
|
||||
/// Returns the underlying executor.
|
||||
executor_type get_executor() noexcept
|
||||
{ return impl_.get_executor(); }
|
||||
|
||||
/// Rebinds the socket type to another executor.
|
||||
template <class Executor1>
|
||||
struct rebind_executor
|
||||
{
|
||||
/// The connection type when rebound to the specified executor.
|
||||
using other = basic_connection<Executor1>;
|
||||
};
|
||||
|
||||
/// Contructs from an executor.
|
||||
explicit
|
||||
basic_connection(
|
||||
executor_type ex,
|
||||
asio::ssl::context::method method = asio::ssl::context::tls_client,
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)())
|
||||
: impl_{ex, method, max_read_size}
|
||||
, timer_{ex}
|
||||
{ }
|
||||
|
||||
/// Contructs from a context.
|
||||
explicit
|
||||
basic_connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context::method method = asio::ssl::context::tls_client,
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)())
|
||||
: basic_connection(ioc.get_executor(), method, max_read_size)
|
||||
{ }
|
||||
|
||||
/** @brief Starts underlying connection operations.
|
||||
*
|
||||
* This member function provides the following functionality
|
||||
*
|
||||
* 1. Resolve the address passed on `boost::redis::config::addr`.
|
||||
* 2. Connect to one of the results obtained in the resolve operation.
|
||||
* 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`.
|
||||
* 4. Start a health-check operation where ping commands are sent
|
||||
* at intervals specified in
|
||||
* `boost::redis::config::health_check_interval`. The message passed to
|
||||
* `PING` will be `boost::redis::config::health_check_id`. Passing a
|
||||
* timeout with value zero will disable health-checks. If the Redis
|
||||
* server does not respond to a health-check within two times the value
|
||||
* specified here, it will be considered unresponsive and the connection
|
||||
* will be closed and a new connection will be stablished.
|
||||
* 5. Starts read and write operations with the Redis
|
||||
* server. More specifically it will trigger the write of all
|
||||
* requests i.e. calls to `async_exec` that happened prior to this
|
||||
* call.
|
||||
*
|
||||
* When a connection is lost for any reason, a new one is
|
||||
* stablished automatically. To disable reconnection call
|
||||
* `boost::redis::connection::cancel(operation::reconnection)`.
|
||||
*
|
||||
* @param cfg Configuration paramters.
|
||||
* @param l Logger object. The interface expected is specified in the class `boost::redis::logger`.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(system::error_code);
|
||||
* @endcode
|
||||
*
|
||||
* For example on how to call this function refer to
|
||||
* cpp20_intro.cpp or any other example.
|
||||
*/
|
||||
template <
|
||||
class Logger = logger,
|
||||
class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
auto
|
||||
async_run(
|
||||
config const& cfg = {},
|
||||
Logger l = Logger{},
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
using this_type = basic_connection<executor_type>;
|
||||
|
||||
cfg_ = cfg;
|
||||
l.set_prefix(cfg_.log_prefix);
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(detail::reconnection_op<this_type, Logger>{this, l}, token, timer_);
|
||||
}
|
||||
|
||||
/** @brief Receives server side pushes asynchronously.
|
||||
*
|
||||
* When pushes arrive and there is no `async_receive` operation in
|
||||
* progress, pushed data, requests, and responses will be paused
|
||||
* until `async_receive` is called again. Apps will usually want
|
||||
* to call `async_receive` in a loop.
|
||||
*
|
||||
* To cancel an ongoing receive operation apps should call
|
||||
* `connection::cancel(operation::receive)`.
|
||||
*
|
||||
* @param response Response object.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* For an example see cpp20_subscriber.cpp. The completion token must
|
||||
* have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the push received in
|
||||
* bytes.
|
||||
*/
|
||||
template <
|
||||
class Response = ignore_t,
|
||||
class CompletionToken = asio::default_completion_token_t<executor_type>
|
||||
>
|
||||
auto
|
||||
async_receive(
|
||||
Response& response,
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return impl_.async_receive(response, token);
|
||||
}
|
||||
|
||||
/** @brief Executes commands on the Redis server asynchronously.
|
||||
*
|
||||
* This function sends a request to the Redis server and waits for
|
||||
* the responses to each individual command in the request. If the
|
||||
* request contains only commands that don't expect a response,
|
||||
* the completion occurs after it has been written to the
|
||||
* underlying stream. Multiple concurrent calls to this function
|
||||
* will be automatically queued by the implementation.
|
||||
*
|
||||
* @param req Request.
|
||||
* @param resp Response.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* For an example see cpp20_echo_server.cpp. The completion token must
|
||||
* have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the response received
|
||||
* in bytes.
|
||||
*/
|
||||
template <
|
||||
class Response = ignore_t,
|
||||
class CompletionToken = asio::default_completion_token_t<executor_type>
|
||||
>
|
||||
auto
|
||||
async_exec(
|
||||
request const& req,
|
||||
Response& resp = ignore,
|
||||
CompletionToken&& token = CompletionToken{})
|
||||
{
|
||||
return impl_.async_exec(req, resp, std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* @li `operation::exec`: Cancels operations started with
|
||||
* `async_exec`. Affects only requests that haven't been written
|
||||
* yet.
|
||||
* @li operation::run: Cancels the `async_run` operation.
|
||||
* @li operation::receive: Cancels any ongoing calls to `async_receive`.
|
||||
* @li operation::all: Cancels all operations listed above.
|
||||
*
|
||||
* @param op: The operation to be cancelled.
|
||||
* @returns The number of operations that have been canceled.
|
||||
*/
|
||||
void cancel(operation op = operation::all)
|
||||
{
|
||||
switch (op) {
|
||||
case operation::reconnection:
|
||||
case operation::all:
|
||||
cfg_.reconnect_wait_interval = std::chrono::seconds::zero();
|
||||
timer_.cancel();
|
||||
break;
|
||||
default: /* ignore */;
|
||||
}
|
||||
|
||||
impl_.cancel(op);
|
||||
}
|
||||
|
||||
/// Returns true if the connection was canceled.
|
||||
bool will_reconnect() const noexcept
|
||||
{ return cfg_.reconnect_wait_interval != std::chrono::seconds::zero();}
|
||||
|
||||
/// Returns the ssl context.
|
||||
auto const& get_ssl_context() const noexcept
|
||||
{ return impl_.get_ssl_context();}
|
||||
|
||||
/// Returns the ssl context.
|
||||
auto& get_ssl_context() noexcept
|
||||
{ return impl_.get_ssl_context();}
|
||||
|
||||
/// Resets the underlying stream.
|
||||
void reset_stream()
|
||||
{ impl_.reset_stream(); }
|
||||
|
||||
/// Returns a reference to the next layer.
|
||||
auto& next_layer() noexcept
|
||||
{ return impl_.next_layer(); }
|
||||
|
||||
/// Returns a const reference to the next layer.
|
||||
auto const& next_layer() const noexcept
|
||||
{ return impl_.next_layer(); }
|
||||
|
||||
private:
|
||||
using timer_type =
|
||||
asio::basic_waitable_timer<
|
||||
std::chrono::steady_clock,
|
||||
asio::wait_traits<std::chrono::steady_clock>,
|
||||
Executor>;
|
||||
|
||||
template <class, class> friend struct detail::reconnection_op;
|
||||
|
||||
config cfg_;
|
||||
detail::connection_base<executor_type> impl_;
|
||||
timer_type timer_;
|
||||
};
|
||||
|
||||
/** \brief A basic_connection that type erases the executor.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* This connection type uses the asio::any_io_executor and
|
||||
* asio::any_completion_token to reduce compilation times.
|
||||
*
|
||||
* For documentaiton of each member function see
|
||||
* `boost::redis::basic_connection`.
|
||||
*/
|
||||
class connection {
|
||||
public:
|
||||
/// Executor type.
|
||||
using executor_type = asio::any_io_executor;
|
||||
|
||||
/// Contructs from an executor.
|
||||
explicit
|
||||
connection(
|
||||
executor_type ex,
|
||||
asio::ssl::context::method method = asio::ssl::context::tls_client,
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)());
|
||||
|
||||
/// Contructs from a context.
|
||||
explicit
|
||||
connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context::method method = asio::ssl::context::tls_client,
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)());
|
||||
|
||||
/// Returns the underlying executor.
|
||||
executor_type get_executor() noexcept
|
||||
{ return impl_.get_executor(); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_run`.
|
||||
template <class CompletionToken>
|
||||
auto async_run(config const& cfg, logger l, CompletionToken token)
|
||||
{
|
||||
return asio::async_initiate<
|
||||
CompletionToken, void(boost::system::error_code)>(
|
||||
[](auto handler, connection* self, config const* cfg, logger l)
|
||||
{
|
||||
self->async_run_impl(*cfg, l, std::move(handler));
|
||||
}, token, this, &cfg, l);
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_receive`.
|
||||
template <class Response, class CompletionToken>
|
||||
auto async_receive(Response& response, CompletionToken token)
|
||||
{
|
||||
return impl_.async_receive(response, std::move(token));
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_exec`.
|
||||
template <class Response, class CompletionToken>
|
||||
auto async_exec(request const& req, Response& resp, CompletionToken token)
|
||||
{
|
||||
return impl_.async_exec(req, resp, std::move(token));
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::cancel`.
|
||||
void cancel(operation op = operation::all);
|
||||
|
||||
/// Calls `boost::redis::basic_connection::will_reconnect`.
|
||||
bool will_reconnect() const noexcept
|
||||
{ return impl_.will_reconnect();}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::next_layer`.
|
||||
auto& next_layer() noexcept
|
||||
{ return impl_.next_layer(); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::next_layer`.
|
||||
auto const& next_layer() const noexcept
|
||||
{ return impl_.next_layer(); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::reset_stream`.
|
||||
void reset_stream()
|
||||
{ impl_.reset_stream();}
|
||||
|
||||
private:
|
||||
void
|
||||
async_run_impl(
|
||||
config const& cfg,
|
||||
logger l,
|
||||
asio::any_completion_handler<void(boost::system::error_code)> token);
|
||||
|
||||
basic_connection<executor_type> impl_;
|
||||
};
|
||||
|
||||
} // boost::redis
|
||||
|
||||
#endif // BOOST_REDIS_CONNECTION_HPP
|
||||
920
include/boost/redis/detail/connection_base.hpp
Normal file
920
include/boost/redis/detail/connection_base.hpp
Normal file
@@ -0,0 +1,920 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_CONNECTION_BASE_HPP
|
||||
#define BOOST_REDIS_CONNECTION_BASE_HPP
|
||||
|
||||
#include <boost/redis/adapter/adapt.hpp>
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
#include <boost/redis/detail/read.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/redis/operation.hpp>
|
||||
#include <boost/redis/request.hpp>
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/detail/runner.hpp>
|
||||
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/basic_stream_socket.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <boost/asio/read_until.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
template <class Conn>
|
||||
struct wait_receive_op {
|
||||
Conn* conn_;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()(Self& self , system::error_code ec = {})
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro)
|
||||
{
|
||||
conn_->read_op_timer_.cancel();
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->read_op_timer_.async_wait(std::move(self));
|
||||
if (!conn_->is_open() || is_cancelled(self)) {
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
class read_next_op {
|
||||
public:
|
||||
using req_info_type = typename Conn::req_info;
|
||||
using req_info_ptr = typename std::shared_ptr<req_info_type>;
|
||||
|
||||
private:
|
||||
Conn* conn_;
|
||||
req_info_ptr info_;
|
||||
Adapter adapter_;
|
||||
std::size_t cmds_ = 0;
|
||||
std::size_t read_size_ = 0;
|
||||
std::size_t index_ = 0;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
public:
|
||||
read_next_op(Conn& conn, Adapter adapter, req_info_ptr info)
|
||||
: conn_{&conn}
|
||||
, info_{info}
|
||||
, adapter_{adapter}
|
||||
, cmds_{info->get_number_of_commands()}
|
||||
{}
|
||||
|
||||
auto make_adapter() noexcept
|
||||
{
|
||||
return [i = index_, adpt = adapter_] (resp3::basic_node<std::string_view> const& nd, system::error_code& ec) mutable { adpt(i, nd, ec); };
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
// Loop reading the responses to this request.
|
||||
while (cmds_ != 0) {
|
||||
if (info_->stop_requested()) {
|
||||
self.complete(asio::error::operation_aborted, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
//-----------------------------------
|
||||
// If we detect a push in the middle of a request we have
|
||||
// to hand it to the push consumer. To do that we need
|
||||
// some data in the read bufer.
|
||||
if (conn_->read_buffer_.empty()) {
|
||||
|
||||
if (conn_->use_ssl()) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::async_read_until(conn_->next_layer(), conn_->dbuf_, resp3::parser::sep, std::move(self));
|
||||
} else {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::async_read_until(conn_->next_layer().next_layer(), conn_->dbuf_, resp3::parser::sep, std::move(self));
|
||||
}
|
||||
|
||||
BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run););
|
||||
if (info_->stop_requested()) {
|
||||
self.complete(asio::error::operation_aborted, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the next request is a push we have to handle it to
|
||||
// the receive_op wait for it to be done and continue.
|
||||
if (resp3::to_type(conn_->read_buffer_.front()) == resp3::type::push) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->async_wait_receive(std::move(self));
|
||||
BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run););
|
||||
continue;
|
||||
}
|
||||
//-----------------------------------
|
||||
|
||||
if (conn_->use_ssl()) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
redis::detail::async_read(conn_->next_layer(), conn_->dbuf_, make_adapter(), std::move(self));
|
||||
} else {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
redis::detail::async_read(conn_->next_layer().next_layer(), conn_->dbuf_, make_adapter(), std::move(self));
|
||||
}
|
||||
|
||||
++index_;
|
||||
|
||||
if (ec || redis::detail::is_cancelled(self)) {
|
||||
conn_->cancel(operation::run);
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted, {});
|
||||
return;
|
||||
}
|
||||
|
||||
conn_->dbuf_.consume(n);
|
||||
read_size_ += n;
|
||||
|
||||
BOOST_ASSERT(cmds_ != 0);
|
||||
--cmds_;
|
||||
}
|
||||
|
||||
self.complete({}, read_size_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct receive_op {
|
||||
Conn* conn_;
|
||||
Adapter adapter;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro)
|
||||
{
|
||||
if (!conn_->is_next_push()) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->read_op_timer_.async_wait(std::move(self));
|
||||
if (!conn_->is_open() || is_cancelled(self)) {
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (conn_->use_ssl()) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
redis::detail::async_read(conn_->next_layer(), conn_->dbuf_, adapter, std::move(self));
|
||||
} else {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
redis::detail::async_read(conn_->next_layer().next_layer(), conn_->dbuf_, adapter, std::move(self));
|
||||
}
|
||||
|
||||
if (ec || is_cancelled(self)) {
|
||||
conn_->cancel(operation::run);
|
||||
conn_->cancel(operation::receive);
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted, {});
|
||||
return;
|
||||
}
|
||||
|
||||
conn_->dbuf_.consume(n);
|
||||
|
||||
if (!conn_->is_next_push()) {
|
||||
conn_->read_op_timer_.cancel();
|
||||
}
|
||||
|
||||
self.complete({}, n);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct exec_op {
|
||||
using req_info_type = typename Conn::req_info;
|
||||
|
||||
Conn* conn = nullptr;
|
||||
request const* req = nullptr;
|
||||
Adapter adapter{};
|
||||
std::shared_ptr<req_info_type> info = nullptr;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro)
|
||||
{
|
||||
// Check whether the user wants to wait for the connection to
|
||||
// be stablished.
|
||||
if (req->get_config().cancel_if_not_connected && !conn->is_open()) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::post(std::move(self));
|
||||
return self.complete(error::not_connected, 0);
|
||||
}
|
||||
|
||||
info = std::allocate_shared<req_info_type>(asio::get_associated_allocator(self), *req, conn->get_executor());
|
||||
|
||||
conn->add_request_info(info);
|
||||
EXEC_OP_WAIT:
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
info->async_wait(std::move(self));
|
||||
BOOST_ASSERT(ec == asio::error::operation_aborted);
|
||||
|
||||
if (info->stop_requested()) {
|
||||
// Don't have to call remove_request as it has already
|
||||
// been by cancel(exec).
|
||||
return self.complete(ec, 0);
|
||||
}
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
if (info->is_written()) {
|
||||
using c_t = asio::cancellation_type;
|
||||
auto const c = self.get_cancellation_state().cancelled();
|
||||
if ((c & c_t::terminal) != c_t::none) {
|
||||
// Cancellation requires closing the connection
|
||||
// otherwise it stays in inconsistent state.
|
||||
conn->cancel(operation::run);
|
||||
return self.complete(ec, 0);
|
||||
} else {
|
||||
// Can't implement other cancelation types, ignoring.
|
||||
self.get_cancellation_state().clear();
|
||||
goto EXEC_OP_WAIT;
|
||||
}
|
||||
} else {
|
||||
// Cancelation can be honored.
|
||||
conn->remove_request(info);
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_ASSERT(conn->is_open());
|
||||
|
||||
if (req->size() == 0) {
|
||||
// Don't have to call remove_request as it has already
|
||||
// been removed.
|
||||
return self.complete({}, 0);
|
||||
}
|
||||
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
BOOST_ASSERT(conn->reqs_.front() != nullptr);
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn->async_read_next(adapter, std::move(self));
|
||||
BOOST_REDIS_CHECK_OP1(;);
|
||||
|
||||
if (info->stop_requested()) {
|
||||
// Don't have to call remove_request as it has already
|
||||
// been by cancel(exec).
|
||||
return self.complete(ec, 0);
|
||||
}
|
||||
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.pop_front();
|
||||
|
||||
if (conn->is_waiting_response()) {
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.front()->proceed();
|
||||
} else {
|
||||
conn->read_timer_.cancel_one();
|
||||
}
|
||||
|
||||
self.complete({}, n);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Logger>
|
||||
struct run_op {
|
||||
Conn* conn = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, system::error_code ec0 = {}
|
||||
, system::error_code ec1 = {})
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro)
|
||||
{
|
||||
conn->write_buffer_.clear();
|
||||
conn->read_buffer_.clear();
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return conn->reader(token);},
|
||||
[this](auto token) { return conn->writer(logger_, token);}
|
||||
).async_wait(
|
||||
asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec0); break;
|
||||
case 1: self.complete(ec1); break;
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Logger>
|
||||
struct writer_op {
|
||||
Conn* conn_;
|
||||
Logger logger_;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
ignore_unused(n);
|
||||
|
||||
BOOST_ASIO_CORO_REENTER (coro) for (;;)
|
||||
{
|
||||
while (conn_->coalesce_requests()) {
|
||||
if (conn_->use_ssl())
|
||||
BOOST_ASIO_CORO_YIELD asio::async_write(conn_->next_layer(), asio::buffer(conn_->write_buffer_), std::move(self));
|
||||
else
|
||||
BOOST_ASIO_CORO_YIELD asio::async_write(conn_->next_layer().next_layer(), asio::buffer(conn_->write_buffer_), std::move(self));
|
||||
|
||||
logger_.on_write(ec, conn_->write_buffer_);
|
||||
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run););
|
||||
|
||||
conn_->on_write();
|
||||
|
||||
// A socket.close() may have been called while a
|
||||
// successful write might had already been queued, so we
|
||||
// have to check here before proceeding.
|
||||
if (!conn_->is_open()) {
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->writer_timer_.async_wait(std::move(self));
|
||||
if (!conn_->is_open() || is_cancelled(self)) {
|
||||
// Notice this is not an error of the op, stoping was
|
||||
// requested from the outside, so we complete with
|
||||
// success.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct reader_op {
|
||||
Conn* conn;
|
||||
asio::coroutine coro{};
|
||||
|
||||
bool as_push() const
|
||||
{
|
||||
return
|
||||
(resp3::to_type(conn->read_buffer_.front()) == resp3::type::push)
|
||||
|| conn->reqs_.empty()
|
||||
|| (!conn->reqs_.empty() && conn->reqs_.front()->get_number_of_commands() == 0)
|
||||
|| !conn->is_waiting_response(); // Added to deal with MONITOR.
|
||||
}
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
ignore_unused(n);
|
||||
|
||||
BOOST_ASIO_CORO_REENTER (coro) for (;;)
|
||||
{
|
||||
if (conn->use_ssl())
|
||||
BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer(), conn->dbuf_, "\r\n", std::move(self));
|
||||
else
|
||||
BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer().next_layer(), conn->dbuf_, "\r\n", std::move(self));
|
||||
|
||||
if (ec == asio::error::eof) {
|
||||
conn->cancel(operation::run);
|
||||
return self.complete({}); // EOFINAE: EOF is not an error.
|
||||
}
|
||||
|
||||
BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run););
|
||||
|
||||
// We handle unsolicited events in the following way
|
||||
//
|
||||
// 1. Its resp3 type is a push.
|
||||
//
|
||||
// 2. A non-push type is received with an empty requests
|
||||
// queue. I have noticed this is possible (e.g. -MISCONF).
|
||||
// I expect them to have type push so we can distinguish
|
||||
// them from responses to commands, but it is a
|
||||
// simple-error. If we are lucky enough to receive them
|
||||
// when the command queue is empty we can treat them as
|
||||
// server pushes, otherwise it is impossible to handle
|
||||
// them properly
|
||||
//
|
||||
// 3. The request does not expect any response but we got
|
||||
// one. This may happen if for example, subscribe with
|
||||
// wrong syntax.
|
||||
//
|
||||
// Useful links:
|
||||
//
|
||||
// - https://github.com/redis/redis/issues/11784
|
||||
// - https://github.com/redis/redis/issues/6426
|
||||
//
|
||||
BOOST_ASSERT(!conn->read_buffer_.empty());
|
||||
if (as_push()) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn->async_wait_receive(std::move(self));
|
||||
} else {
|
||||
BOOST_ASSERT_MSG(conn->is_waiting_response(), "Not waiting for a response (using MONITOR command perhaps?)");
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
BOOST_ASSERT(conn->reqs_.front()->get_number_of_commands() != 0);
|
||||
conn->reqs_.front()->proceed();
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn->read_timer_.async_wait(std::move(self));
|
||||
ec = {};
|
||||
}
|
||||
|
||||
if (!conn->is_open() || ec || is_cancelled(self)) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(asio::error::basic_errors::operation_aborted);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @brief Base class for high level Redis asynchronous connections.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @tparam Executor The executor type.
|
||||
*
|
||||
*/
|
||||
template <class Executor>
|
||||
class connection_base {
|
||||
public:
|
||||
/// Executor type
|
||||
using executor_type = Executor;
|
||||
|
||||
/// Type of the next layer
|
||||
using next_layer_type = asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp, Executor>>;
|
||||
|
||||
using this_type = connection_base<Executor>;
|
||||
|
||||
/// Constructs from an executor.
|
||||
connection_base(
|
||||
executor_type ex,
|
||||
asio::ssl::context::method method,
|
||||
std::size_t max_read_size)
|
||||
: ctx_{method}
|
||||
, stream_{std::make_unique<next_layer_type>(ex, ctx_)}
|
||||
, writer_timer_{ex}
|
||||
, read_timer_{ex}
|
||||
, read_op_timer_{ex}
|
||||
, runner_{ex, {}}
|
||||
, dbuf_{read_buffer_, max_read_size}
|
||||
{
|
||||
writer_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
read_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
read_op_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
/// Returns the ssl context.
|
||||
auto const& get_ssl_context() const noexcept
|
||||
{ return ctx_;}
|
||||
|
||||
/// Returns the ssl context.
|
||||
auto& get_ssl_context() noexcept
|
||||
{ return ctx_;}
|
||||
|
||||
/// Resets the underlying stream.
|
||||
void reset_stream()
|
||||
{
|
||||
stream_ = std::make_unique<next_layer_type>(writer_timer_.get_executor(), ctx_);
|
||||
}
|
||||
|
||||
/// Returns a reference to the next layer.
|
||||
auto& next_layer() noexcept { return *stream_; }
|
||||
|
||||
/// Returns a const reference to the next layer.
|
||||
auto const& next_layer() const noexcept { return *stream_; }
|
||||
|
||||
/// Returns the associated executor.
|
||||
auto get_executor() {return writer_timer_.get_executor();}
|
||||
|
||||
/// Cancels specific operations.
|
||||
virtual void cancel(operation op)
|
||||
{
|
||||
runner_.cancel(op);
|
||||
if (op == operation::all) {
|
||||
cancel_impl(operation::run);
|
||||
cancel_impl(operation::receive);
|
||||
cancel_impl(operation::exec);
|
||||
return;
|
||||
}
|
||||
|
||||
cancel_impl(op);
|
||||
}
|
||||
|
||||
template <class Response, class CompletionToken>
|
||||
auto async_exec(request const& req, Response& resp, CompletionToken token)
|
||||
{
|
||||
using namespace boost::redis::adapter;
|
||||
auto f = boost_redis_adapt(resp);
|
||||
BOOST_ASSERT_MSG(req.size() <= f.get_supported_response_size(), "Request and response have incompatible sizes.");
|
||||
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code, std::size_t)
|
||||
>(redis::detail::exec_op<this_type, decltype(f)>{this, &req, f}, token, writer_timer_);
|
||||
}
|
||||
|
||||
template <class Response, class CompletionToken>
|
||||
auto async_receive(Response& response, CompletionToken token)
|
||||
{
|
||||
using namespace boost::redis::adapter;
|
||||
auto g = boost_redis_adapt(response);
|
||||
auto f = adapter::detail::make_adapter_wrapper(g);
|
||||
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code, std::size_t)
|
||||
>(redis::detail::receive_op<this_type, decltype(f)>{this, f}, token, read_op_timer_);
|
||||
}
|
||||
|
||||
template <class Logger, class CompletionToken>
|
||||
auto async_run(config const& cfg, Logger l, CompletionToken token)
|
||||
{
|
||||
runner_.set_config(cfg);
|
||||
l.set_prefix(runner_.get_config().log_prefix);
|
||||
return runner_.async_run(*this, l, std::move(token));
|
||||
}
|
||||
|
||||
private:
|
||||
using clock_type = std::chrono::steady_clock;
|
||||
using clock_traits_type = asio::wait_traits<clock_type>;
|
||||
using timer_type = asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;
|
||||
using runner_type = redis::detail::runner<executor_type>;
|
||||
|
||||
auto use_ssl() const noexcept
|
||||
{ return runner_.get_config().use_ssl;}
|
||||
|
||||
auto cancel_on_conn_lost() -> std::size_t
|
||||
{
|
||||
// Must return false if the request should be removed.
|
||||
auto cond = [](auto const& ptr)
|
||||
{
|
||||
BOOST_ASSERT(ptr != nullptr);
|
||||
|
||||
if (ptr->is_written()) {
|
||||
return !ptr->get_request().get_config().cancel_if_unresponded;
|
||||
} else {
|
||||
return !ptr->get_request().get_config().cancel_on_connection_lost;
|
||||
}
|
||||
};
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), cond);
|
||||
|
||||
auto const ret = std::distance(point, std::end(reqs_));
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return ptr->reset_status();
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto cancel_unwritten_requests() -> std::size_t
|
||||
{
|
||||
auto f = [](auto const& ptr)
|
||||
{
|
||||
BOOST_ASSERT(ptr != nullptr);
|
||||
return ptr->is_written();
|
||||
};
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), f);
|
||||
|
||||
auto const ret = std::distance(point, std::end(reqs_));
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void cancel_impl(operation op)
|
||||
{
|
||||
switch (op) {
|
||||
case operation::exec:
|
||||
{
|
||||
cancel_unwritten_requests();
|
||||
} break;
|
||||
case operation::run:
|
||||
{
|
||||
close();
|
||||
read_timer_.cancel();
|
||||
writer_timer_.cancel();
|
||||
cancel_on_conn_lost();
|
||||
} break;
|
||||
case operation::receive:
|
||||
{
|
||||
read_op_timer_.cancel();
|
||||
} break;
|
||||
default: /* ignore */;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write()
|
||||
{
|
||||
// We have to clear the payload right after writing it to use it
|
||||
// as a flag that informs there is no ongoing write.
|
||||
write_buffer_.clear();
|
||||
|
||||
// Notice this must come before the for-each below.
|
||||
cancel_push_requests();
|
||||
|
||||
// There is small optimization possible here: traverse only the
|
||||
// partition of unwritten requests instead of them all.
|
||||
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
BOOST_ASSERT_MSG(ptr != nullptr, "Expects non-null pointer.");
|
||||
if (ptr->is_staged())
|
||||
ptr->mark_written();
|
||||
});
|
||||
}
|
||||
|
||||
struct req_info {
|
||||
public:
|
||||
enum class action
|
||||
{
|
||||
stop,
|
||||
proceed,
|
||||
none,
|
||||
};
|
||||
|
||||
explicit req_info(request const& req, executor_type ex)
|
||||
: timer_{ex}
|
||||
, action_{action::none}
|
||||
, req_{&req}
|
||||
, cmds_{std::size(req)}
|
||||
, status_{status::none}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
auto proceed()
|
||||
{
|
||||
timer_.cancel();
|
||||
action_ = action::proceed;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
timer_.cancel();
|
||||
action_ = action::stop;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_waiting_write() const noexcept
|
||||
{ return !is_written() && !is_staged(); }
|
||||
|
||||
[[nodiscard]] auto is_written() const noexcept
|
||||
{ return status_ == status::written; }
|
||||
|
||||
[[nodiscard]] auto is_staged() const noexcept
|
||||
{ return status_ == status::staged; }
|
||||
|
||||
void mark_written() noexcept
|
||||
{ status_ = status::written; }
|
||||
|
||||
void mark_staged() noexcept
|
||||
{ status_ = status::staged; }
|
||||
|
||||
void reset_status() noexcept
|
||||
{ status_ = status::none; }
|
||||
|
||||
[[nodiscard]] auto get_number_of_commands() const noexcept
|
||||
{ return cmds_; }
|
||||
|
||||
[[nodiscard]] auto get_request() const noexcept -> auto const&
|
||||
{ return *req_; }
|
||||
|
||||
[[nodiscard]] auto stop_requested() const noexcept
|
||||
{ return action_ == action::stop;}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_wait(CompletionToken token)
|
||||
{
|
||||
return timer_.async_wait(std::move(token));
|
||||
}
|
||||
|
||||
private:
|
||||
enum class status
|
||||
{ none
|
||||
, staged
|
||||
, written
|
||||
};
|
||||
|
||||
timer_type timer_;
|
||||
action action_;
|
||||
request const* req_;
|
||||
std::size_t cmds_;
|
||||
status status_;
|
||||
};
|
||||
|
||||
void remove_request(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.erase(std::remove(std::begin(reqs_), std::end(reqs_), info));
|
||||
}
|
||||
|
||||
using reqs_type = std::deque<std::shared_ptr<req_info>>;
|
||||
|
||||
template <class> friend struct redis::detail::reader_op;
|
||||
template <class, class> friend struct redis::detail::writer_op;
|
||||
template <class, class> friend struct redis::detail::run_op;
|
||||
template <class, class> friend struct redis::detail::exec_op;
|
||||
template <class, class> friend class redis::detail::read_next_op;
|
||||
template <class, class> friend struct redis::detail::receive_op;
|
||||
template <class> friend struct redis::detail::wait_receive_op;
|
||||
template <class, class, class> friend struct redis::detail::run_all_op;
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_wait_receive(CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(redis::detail::wait_receive_op<this_type>{this}, token, read_op_timer_);
|
||||
}
|
||||
|
||||
void cancel_push_requests()
|
||||
{
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return !(ptr->is_staged() && ptr->get_request().size() == 0);
|
||||
});
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->proceed();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_writing() const noexcept
|
||||
{
|
||||
return !write_buffer_.empty();
|
||||
}
|
||||
|
||||
void add_request_info(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.push_back(info);
|
||||
|
||||
if (info->get_request().has_hello_priority()) {
|
||||
auto rend = std::partition_point(std::rbegin(reqs_), std::rend(reqs_), [](auto const& e) {
|
||||
return e->is_waiting_write();
|
||||
});
|
||||
|
||||
std::rotate(std::rbegin(reqs_), std::rbegin(reqs_) + 1, rend);
|
||||
}
|
||||
|
||||
if (is_open() && !is_writing())
|
||||
writer_timer_.cancel();
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto reader(CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(redis::detail::reader_op<this_type>{this}, token, writer_timer_);
|
||||
}
|
||||
|
||||
template <class CompletionToken, class Logger>
|
||||
auto writer(Logger l, CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(redis::detail::writer_op<this_type, Logger>{this, l}, token, writer_timer_);
|
||||
}
|
||||
|
||||
template <class Adapter, class CompletionToken>
|
||||
auto async_read_next(Adapter adapter, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code, std::size_t)
|
||||
>(redis::detail::read_next_op<this_type, Adapter>{*this, adapter, reqs_.front()}, token, writer_timer_);
|
||||
}
|
||||
|
||||
template <class Logger, class CompletionToken>
|
||||
auto async_run_lean(config const& cfg, Logger l, CompletionToken token)
|
||||
{
|
||||
runner_.set_config(cfg);
|
||||
l.set_prefix(runner_.get_config().log_prefix);
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(redis::detail::run_op<this_type, Logger>{this, l}, token, writer_timer_);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool coalesce_requests()
|
||||
{
|
||||
// Coalesces the requests and marks them staged. After a
|
||||
// successful write staged requests will be marked as written.
|
||||
auto const point = std::partition_point(std::cbegin(reqs_), std::cend(reqs_), [](auto const& ri) {
|
||||
return !ri->is_waiting_write();
|
||||
});
|
||||
|
||||
std::for_each(point, std::cend(reqs_), [this](auto const& ri) {
|
||||
// Stage the request.
|
||||
write_buffer_ += ri->get_request().payload();
|
||||
ri->mark_staged();
|
||||
});
|
||||
|
||||
return point != std::cend(reqs_);
|
||||
}
|
||||
|
||||
bool is_waiting_response() const noexcept
|
||||
{
|
||||
return !std::empty(reqs_) && reqs_.front()->is_written();
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if (stream_->next_layer().is_open())
|
||||
stream_->next_layer().close();
|
||||
}
|
||||
|
||||
bool is_next_push() const noexcept
|
||||
{
|
||||
return !read_buffer_.empty() && (resp3::to_type(read_buffer_.front()) == resp3::type::push);
|
||||
}
|
||||
|
||||
auto is_open() const noexcept { return stream_->next_layer().is_open(); }
|
||||
auto& lowest_layer() noexcept { return stream_->lowest_layer(); }
|
||||
|
||||
asio::ssl::context ctx_;
|
||||
std::unique_ptr<next_layer_type> stream_;
|
||||
|
||||
// Notice we use a timer to simulate a condition-variable. It is
|
||||
// also more suitable than a channel and the notify operation does
|
||||
// not suspend.
|
||||
timer_type writer_timer_;
|
||||
timer_type read_timer_;
|
||||
timer_type read_op_timer_;
|
||||
runner_type runner_;
|
||||
|
||||
using dyn_buffer_type = asio::dynamic_string_buffer<char, std::char_traits<char>, std::allocator<char>>;
|
||||
|
||||
std::string read_buffer_;
|
||||
dyn_buffer_type dbuf_;
|
||||
std::string write_buffer_;
|
||||
reqs_type reqs_;
|
||||
};
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_CONNECTION_BASE_HPP
|
||||
133
include/boost/redis/detail/connector.hpp
Normal file
133
include/boost/redis/detail/connector.hpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_CONNECTOR_HPP
|
||||
#define BOOST_REDIS_CONNECTOR_HPP
|
||||
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
namespace boost::redis::detail
|
||||
{
|
||||
|
||||
template <class Connector, class Stream>
|
||||
struct connect_op {
|
||||
Connector* ctor_ = nullptr;
|
||||
Stream* stream = nullptr;
|
||||
asio::ip::tcp::resolver::results_type const* res_ = nullptr;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> const& order = {}
|
||||
, system::error_code const& ec1 = {}
|
||||
, asio::ip::tcp::endpoint const& ep= {}
|
||||
, system::error_code const& ec2 = {})
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro)
|
||||
{
|
||||
ctor_->timer_.expires_after(ctor_->timeout_);
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token)
|
||||
{
|
||||
auto f = [](system::error_code const&, auto const&) { return true; };
|
||||
return asio::async_connect(*stream, *res_, f, token);
|
||||
},
|
||||
[this](auto token) { return ctor_->timer_.async_wait(token);}
|
||||
).async_wait(
|
||||
asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: {
|
||||
ctor_->endpoint_ = ep;
|
||||
self.complete(ec1);
|
||||
} break;
|
||||
case 1:
|
||||
{
|
||||
if (ec2) {
|
||||
self.complete(ec2);
|
||||
} else {
|
||||
self.complete(error::connect_timeout);
|
||||
}
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Executor>
|
||||
class connector {
|
||||
public:
|
||||
using timer_type =
|
||||
asio::basic_waitable_timer<
|
||||
std::chrono::steady_clock,
|
||||
asio::wait_traits<std::chrono::steady_clock>,
|
||||
Executor>;
|
||||
|
||||
connector(Executor ex)
|
||||
: timer_{ex}
|
||||
{}
|
||||
|
||||
void set_config(config const& cfg)
|
||||
{ timeout_ = cfg.connect_timeout; }
|
||||
|
||||
template <class Stream, class CompletionToken>
|
||||
auto
|
||||
async_connect(
|
||||
Stream& stream,
|
||||
asio::ip::tcp::resolver::results_type const& res,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(connect_op<connector, Stream>{this, &stream, &res}, token, timer_);
|
||||
}
|
||||
|
||||
std::size_t cancel(operation op)
|
||||
{
|
||||
switch (op) {
|
||||
case operation::connect:
|
||||
case operation::all:
|
||||
timer_.cancel();
|
||||
break;
|
||||
default: /* ignore */;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto const& endpoint() const noexcept { return endpoint_;}
|
||||
|
||||
private:
|
||||
template <class, class> friend struct connect_op;
|
||||
|
||||
timer_type timer_;
|
||||
std::chrono::steady_clock::duration timeout_ = std::chrono::seconds{2};
|
||||
asio::ip::tcp::endpoint endpoint_;
|
||||
};
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_CONNECTOR_HPP
|
||||
124
include/boost/redis/detail/handshaker.hpp
Normal file
124
include/boost/redis/detail/handshaker.hpp
Normal file
@@ -0,0 +1,124 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_SSL_CONNECTOR_HPP
|
||||
#define BOOST_REDIS_SSL_CONNECTOR_HPP
|
||||
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
namespace boost::redis::detail
|
||||
{
|
||||
|
||||
template <class Handshaker, class Stream>
|
||||
struct handshake_op {
|
||||
Handshaker* hsher_ = nullptr;
|
||||
Stream* stream_ = nullptr;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> const& order = {}
|
||||
, system::error_code const& ec1 = {}
|
||||
, system::error_code const& ec2 = {})
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro)
|
||||
{
|
||||
hsher_->timer_.expires_after(hsher_->timeout_);
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return stream_->async_handshake(asio::ssl::stream_base::client, token); },
|
||||
[this](auto token) { return hsher_->timer_.async_wait(token);}
|
||||
).async_wait(
|
||||
asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: {
|
||||
self.complete(ec1);
|
||||
} break;
|
||||
case 1:
|
||||
{
|
||||
if (ec2) {
|
||||
self.complete(ec2);
|
||||
} else {
|
||||
self.complete(error::ssl_handshake_timeout);
|
||||
}
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Executor>
|
||||
class handshaker {
|
||||
public:
|
||||
using timer_type =
|
||||
asio::basic_waitable_timer<
|
||||
std::chrono::steady_clock,
|
||||
asio::wait_traits<std::chrono::steady_clock>,
|
||||
Executor>;
|
||||
|
||||
handshaker(Executor ex)
|
||||
: timer_{ex}
|
||||
{}
|
||||
|
||||
template <class Stream, class CompletionToken>
|
||||
auto
|
||||
async_handshake(Stream& stream, CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(handshake_op<handshaker, Stream>{this, &stream}, token, timer_);
|
||||
}
|
||||
|
||||
std::size_t cancel(operation op)
|
||||
{
|
||||
switch (op) {
|
||||
case operation::ssl_handshake:
|
||||
case operation::all:
|
||||
timer_.cancel();
|
||||
break;
|
||||
default: /* ignore */;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr bool is_dummy() const noexcept
|
||||
{return false;}
|
||||
|
||||
void set_config(config const& cfg)
|
||||
{ timeout_ = cfg.ssl_handshake_timeout; }
|
||||
|
||||
private:
|
||||
template <class, class> friend struct handshake_op;
|
||||
|
||||
timer_type timer_;
|
||||
std::chrono::steady_clock::duration timeout_;
|
||||
};
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_SSL_CONNECTOR_HPP
|
||||
224
include/boost/redis/detail/health_checker.hpp
Normal file
224
include/boost/redis/detail/health_checker.hpp
Normal file
@@ -0,0 +1,224 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_HEALTH_CHECKER_HPP
|
||||
#define BOOST_REDIS_HEALTH_CHECKER_HPP
|
||||
|
||||
// Has to included before promise.hpp to build on msvc.
|
||||
#include <boost/redis/request.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/redis/operation.hpp>
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
template <class HealthChecker, class Connection>
|
||||
class ping_op {
|
||||
public:
|
||||
HealthChecker* checker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
|
||||
{
|
||||
if (checker_->checker_has_exited_) {
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->async_exec(checker_->req_, checker_->resp_, std::move(self));
|
||||
BOOST_REDIS_CHECK_OP0(checker_->wait_timer_.cancel();)
|
||||
|
||||
// Wait before pinging again.
|
||||
checker_->ping_timer_.expires_after(checker_->ping_interval_);
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
checker_->ping_timer_.async_wait(std::move(self));
|
||||
BOOST_REDIS_CHECK_OP0(;)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class HealthChecker, class Connection>
|
||||
class check_timeout_op {
|
||||
public:
|
||||
HealthChecker* checker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, system::error_code ec = {})
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
|
||||
{
|
||||
checker_->wait_timer_.expires_after(2 * checker_->ping_interval_);
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
checker_->wait_timer_.async_wait(std::move(self));
|
||||
BOOST_REDIS_CHECK_OP0(;)
|
||||
|
||||
if (checker_->resp_.has_error()) {
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
if (checker_->resp_.value().empty()) {
|
||||
checker_->ping_timer_.cancel();
|
||||
conn_->cancel(operation::run);
|
||||
checker_->checker_has_exited_ = true;
|
||||
self.complete(error::pong_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checker_->resp_.has_value()) {
|
||||
checker_->resp_.value().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class HealthChecker, class Connection>
|
||||
class check_health_op {
|
||||
public:
|
||||
HealthChecker* checker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()(
|
||||
Self& self,
|
||||
std::array<std::size_t, 2> order = {},
|
||||
system::error_code ec1 = {},
|
||||
system::error_code ec2 = {})
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
if (checker_->ping_interval_ == std::chrono::seconds::zero()) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::post(std::move(self));
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return checker_->async_ping(*conn_, token); },
|
||||
[this](auto token) { return checker_->async_check_timeout(*conn_, token);}
|
||||
).async_wait(
|
||||
asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1); return;
|
||||
case 1: self.complete(ec2); return;
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Executor>
|
||||
class health_checker {
|
||||
private:
|
||||
using timer_type =
|
||||
asio::basic_waitable_timer<
|
||||
std::chrono::steady_clock,
|
||||
asio::wait_traits<std::chrono::steady_clock>,
|
||||
Executor>;
|
||||
|
||||
public:
|
||||
health_checker(Executor ex)
|
||||
: ping_timer_{ex}
|
||||
, wait_timer_{ex}
|
||||
{
|
||||
req_.push("PING", "Boost.Redis");
|
||||
}
|
||||
|
||||
void set_config(config const& cfg)
|
||||
{
|
||||
req_.clear();
|
||||
req_.push("PING", cfg.health_check_id);
|
||||
ping_interval_ = cfg.health_check_interval;
|
||||
}
|
||||
|
||||
template <
|
||||
class Connection,
|
||||
class CompletionToken = asio::default_completion_token_t<Executor>
|
||||
>
|
||||
auto async_check_health(Connection& conn, CompletionToken token = CompletionToken{})
|
||||
{
|
||||
checker_has_exited_ = false;
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(check_health_op<health_checker, Connection>{this, &conn}, token, conn);
|
||||
}
|
||||
|
||||
std::size_t cancel(operation op)
|
||||
{
|
||||
switch (op) {
|
||||
case operation::health_check:
|
||||
case operation::all:
|
||||
ping_timer_.cancel();
|
||||
wait_timer_.cancel();
|
||||
break;
|
||||
default: /* ignore */;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class Connection, class CompletionToken>
|
||||
auto async_ping(Connection& conn, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(ping_op<health_checker, Connection>{this, &conn}, token, conn, ping_timer_);
|
||||
}
|
||||
|
||||
template <class Connection, class CompletionToken>
|
||||
auto async_check_timeout(Connection& conn, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(check_timeout_op<health_checker, Connection>{this, &conn}, token, conn, wait_timer_);
|
||||
}
|
||||
|
||||
template <class, class> friend class ping_op;
|
||||
template <class, class> friend class check_timeout_op;
|
||||
template <class, class> friend class check_health_op;
|
||||
|
||||
timer_type ping_timer_;
|
||||
timer_type wait_timer_;
|
||||
redis::request req_;
|
||||
redis::generic_response resp_;
|
||||
std::chrono::steady_clock::duration ping_interval_ = std::chrono::seconds{5};
|
||||
bool checker_has_exited_ = false;
|
||||
};
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_HEALTH_CHECKER_HPP
|
||||
37
include/boost/redis/detail/helper.hpp
Normal file
37
include/boost/redis/detail/helper.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_HELPER_HPP
|
||||
#define BOOST_REDIS_HELPER_HPP
|
||||
|
||||
#include <boost/asio/cancellation_type.hpp>
|
||||
|
||||
namespace boost::redis::detail
|
||||
{
|
||||
|
||||
template <class T>
|
||||
auto is_cancelled(T const& self)
|
||||
{
|
||||
return self.get_cancellation_state().cancelled() != asio::cancellation_type_t::none;
|
||||
}
|
||||
|
||||
#define BOOST_REDIS_CHECK_OP0(X)\
|
||||
if (ec || redis::detail::is_cancelled(self)) {\
|
||||
X\
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted);\
|
||||
return;\
|
||||
}
|
||||
|
||||
#define BOOST_REDIS_CHECK_OP1(X)\
|
||||
if (ec || redis::detail::is_cancelled(self)) {\
|
||||
X\
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted, {});\
|
||||
return;\
|
||||
}
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_HELPER_HPP
|
||||
291
include/boost/redis/detail/read.hpp
Normal file
291
include/boost/redis/detail/read.hpp
Normal file
@@ -0,0 +1,291 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_READ_HPP
|
||||
#define BOOST_REDIS_READ_HPP
|
||||
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/redis/resp3/parser.hpp>
|
||||
#include <boost/redis/adapter/ignore.hpp>
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <limits>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
template <class DynamicBuffer>
|
||||
std::string_view buffer_view(DynamicBuffer buf) noexcept
|
||||
{
|
||||
char const* start = static_cast<char const*>(buf.data(0, buf.size()).data());
|
||||
return std::string_view{start, std::size(buf)};
|
||||
}
|
||||
|
||||
template <class AsyncReadStream, class DynamicBuffer>
|
||||
class append_some_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
std::size_t size_ = 0;
|
||||
std::size_t tmp_ = 0;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
public:
|
||||
append_some_op(AsyncReadStream& stream, DynamicBuffer buf, std::size_t size)
|
||||
: stream_ {stream}
|
||||
, buf_ {std::move(buf)}
|
||||
, size_{size}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
tmp_ = buf_.size();
|
||||
buf_.grow(size_);
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
stream_.async_read_some(buf_.data(tmp_, size_), std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
buf_.shrink(buf_.size() - tmp_ - n);
|
||||
self.complete({}, n);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class AsyncReadStream, class DynamicBuffer, class CompletionToken>
|
||||
auto
|
||||
async_append_some(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
std::size_t size,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code, std::size_t)
|
||||
>(append_some_op<AsyncReadStream, DynamicBuffer> {stream, buffer, size}, token, stream);
|
||||
}
|
||||
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter>
|
||||
class parse_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
resp3::parser parser_;
|
||||
ResponseAdapter adapter_;
|
||||
bool needs_rescheduling_ = true;
|
||||
system::error_code ec_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
static std::size_t const growth = 1024;
|
||||
|
||||
public:
|
||||
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
|
||||
: stream_ {stream}
|
||||
, buf_ {std::move(buf)}
|
||||
, adapter_ {std::move(adapter)}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
while (!resp3::parse(parser_, buffer_view(buf_), adapter_, ec)) {
|
||||
needs_rescheduling_ = false;
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
async_append_some(
|
||||
stream_, buf_, parser_.get_suggested_buffer_growth(growth),
|
||||
std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ec_ = ec;
|
||||
if (needs_rescheduling_) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::post(std::move(self));
|
||||
}
|
||||
|
||||
self.complete(ec_, parser_.get_consumed());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
*
|
||||
* This function reads a complete response to a command or a
|
||||
* server push synchronously. For example
|
||||
*
|
||||
* @code
|
||||
* int resp;
|
||||
* std::string buffer;
|
||||
* resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
* @endcode
|
||||
*
|
||||
* For a complete example see examples/intro_sync.cpp. This function
|
||||
* is implemented in terms of one or more calls to @c
|
||||
* asio::read_until and @c asio::read functions, and is known as a @a
|
||||
* composed @a operation. Furthermore, the implementation may read
|
||||
* additional bytes from the stream that lie past the end of the
|
||||
* message being read. These additional bytes are stored in the
|
||||
* dynamic buffer, which must be preserved for subsequent reads.
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buf Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter.
|
||||
* \param ec If an error occurs, it will be assigned to this paramter.
|
||||
* \returns The number of bytes that have been consumed from the dynamic buffer.
|
||||
*
|
||||
* \remark This function calls buf.consume() in each chunk of data
|
||||
* after it has been passed to the adapter. Users must not consume
|
||||
* the bytes after it returns.
|
||||
*/
|
||||
template <
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter
|
||||
>
|
||||
auto
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter,
|
||||
system::error_code& ec) -> std::size_t
|
||||
{
|
||||
static std::size_t const growth = 1024;
|
||||
|
||||
resp3::parser parser;
|
||||
while (!parser.done()) {
|
||||
auto const res = parser.consume(detail::buffer_view(buf), ec);
|
||||
if (ec)
|
||||
return 0UL;
|
||||
|
||||
if (!res.has_value()) {
|
||||
auto const size_before = buf.size();
|
||||
buf.grow(parser.get_suggested_buffer_growth(growth));
|
||||
auto const n =
|
||||
stream.read_some(
|
||||
buf.data(size_before, parser.get_suggested_buffer_growth(growth)),
|
||||
ec);
|
||||
if (ec)
|
||||
return 0UL;
|
||||
|
||||
buf.shrink(buf.size() - size_before - n);
|
||||
continue;
|
||||
}
|
||||
|
||||
adapter(res.value(), ec);
|
||||
if (ec)
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
return parser.get_consumed();
|
||||
}
|
||||
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
*
|
||||
* Same as the error_code overload but throws on error.
|
||||
*/
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = adapter::ignore>
|
||||
auto
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter = ResponseAdapter{})
|
||||
{
|
||||
system::error_code ec;
|
||||
auto const n = redis::detail::read(stream, buf, adapter, ec);
|
||||
|
||||
if (ec)
|
||||
BOOST_THROW_EXCEPTION(system::system_error{ec});
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/** \brief Reads a complete response to a Redis command asynchronously.
|
||||
*
|
||||
* This function reads a complete response to a command or a
|
||||
* server push asynchronously. For example
|
||||
*
|
||||
* @code
|
||||
* std::string buffer;
|
||||
* std::set<std::string> resp;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
* @endcode
|
||||
*
|
||||
* For a complete example see examples/transaction.cpp. This function
|
||||
* is implemented in terms of one or more calls to @c
|
||||
* asio::async_read_until and @c asio::async_read functions, and is
|
||||
* known as a @a composed @a operation. Furthermore, the
|
||||
* implementation may read additional bytes from the stream that lie
|
||||
* past the end of the message being read. These additional bytes are
|
||||
* stored in the dynamic buffer, which must be preserved for
|
||||
* subsequent reads.
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buffer Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter.
|
||||
* \param token The completion token.
|
||||
*
|
||||
* The completion handler will receive as a parameter the total
|
||||
* number of bytes transferred from the stream and must have the
|
||||
* following signature
|
||||
*
|
||||
* @code
|
||||
* void(system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* \remark This function calls buf.consume() in each chunk of data
|
||||
* after it has been passed to the adapter. Users must not consume
|
||||
* the bytes after it returns.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = adapter::ignore,
|
||||
class CompletionToken = asio::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
ResponseAdapter adapter = ResponseAdapter{},
|
||||
CompletionToken&& token =
|
||||
asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code, std::size_t)
|
||||
>(parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
|
||||
token,
|
||||
stream);
|
||||
}
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_READ_HPP
|
||||
137
include/boost/redis/detail/resolver.hpp
Normal file
137
include/boost/redis/detail/resolver.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_RESOLVER_HPP
|
||||
#define BOOST_REDIS_RESOLVER_HPP
|
||||
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
namespace boost::redis::detail
|
||||
{
|
||||
|
||||
template <class Resolver>
|
||||
struct resolve_op {
|
||||
Resolver* resv_ = nullptr;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, system::error_code ec1 = {}
|
||||
, asio::ip::tcp::resolver::results_type res = {}
|
||||
, system::error_code ec2 = {})
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro)
|
||||
{
|
||||
resv_->timer_.expires_after(resv_->timeout_);
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token)
|
||||
{
|
||||
return resv_->resv_.async_resolve(resv_->addr_.host, resv_->addr_.port, token);
|
||||
},
|
||||
[this](auto token) { return resv_->timer_.async_wait(token);}
|
||||
).async_wait(
|
||||
asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: {
|
||||
// Resolver completed first.
|
||||
resv_->results_ = res;
|
||||
self.complete(ec1);
|
||||
} break;
|
||||
|
||||
case 1: {
|
||||
if (ec2) {
|
||||
// Timer completed first with error, perhaps a
|
||||
// cancellation going on.
|
||||
self.complete(ec2);
|
||||
} else {
|
||||
// Timer completed first without an error, this is a
|
||||
// resolve timeout.
|
||||
self.complete(error::resolve_timeout);
|
||||
}
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Executor>
|
||||
class resolver {
|
||||
public:
|
||||
using timer_type =
|
||||
asio::basic_waitable_timer<
|
||||
std::chrono::steady_clock,
|
||||
asio::wait_traits<std::chrono::steady_clock>,
|
||||
Executor>;
|
||||
|
||||
resolver(Executor ex) : resv_{ex} , timer_{ex} {}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_resolve(CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(resolve_op<resolver>{this}, token, resv_);
|
||||
}
|
||||
|
||||
std::size_t cancel(operation op)
|
||||
{
|
||||
switch (op) {
|
||||
case operation::resolve:
|
||||
case operation::all:
|
||||
resv_.cancel();
|
||||
timer_.cancel();
|
||||
break;
|
||||
default: /* ignore */;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto const& results() const noexcept
|
||||
{ return results_;}
|
||||
|
||||
void set_config(config const& cfg)
|
||||
{
|
||||
addr_ = cfg.addr;
|
||||
timeout_ = cfg.resolve_timeout;
|
||||
}
|
||||
|
||||
private:
|
||||
using resolver_type = asio::ip::basic_resolver<asio::ip::tcp, Executor>;
|
||||
template <class> friend struct resolve_op;
|
||||
|
||||
resolver_type resv_;
|
||||
timer_type timer_;
|
||||
address addr_;
|
||||
std::chrono::steady_clock::duration timeout_;
|
||||
asio::ip::tcp::resolver::results_type results_;
|
||||
};
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_RESOLVER_HPP
|
||||
250
include/boost/redis/detail/runner.hpp
Normal file
250
include/boost/redis/detail/runner.hpp
Normal file
@@ -0,0 +1,250 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_RUNNER_HPP
|
||||
#define BOOST_REDIS_RUNNER_HPP
|
||||
|
||||
#include <boost/redis/detail/health_checker.hpp>
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
#include <boost/redis/operation.hpp>
|
||||
#include <boost/redis/detail/connector.hpp>
|
||||
#include <boost/redis/detail/resolver.hpp>
|
||||
#include <boost/redis/detail/handshaker.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
namespace boost::redis::detail
|
||||
{
|
||||
|
||||
template <class Runner, class Connection, class Logger>
|
||||
struct hello_op {
|
||||
Runner* runner_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
runner_->hello_req_.clear();
|
||||
if (runner_->hello_resp_.has_value())
|
||||
runner_->hello_resp_.value().clear();
|
||||
runner_->add_hello();
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->async_exec(runner_->hello_req_, runner_->hello_resp_, std::move(self));
|
||||
logger_.on_hello(ec, runner_->hello_resp_);
|
||||
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Runner, class Connection, class Logger>
|
||||
class runner_op {
|
||||
private:
|
||||
Runner* runner_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
public:
|
||||
runner_op(Runner* runner, Connection* conn, Logger l)
|
||||
: runner_{runner}
|
||||
, conn_{conn}
|
||||
, logger_{l}
|
||||
{}
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 3> order = {}
|
||||
, system::error_code ec0 = {}
|
||||
, system::error_code ec1 = {}
|
||||
, system::error_code ec2 = {}
|
||||
, std::size_t = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return runner_->async_run_all(*conn_, logger_, token); },
|
||||
[this](auto token) { return runner_->health_checker_.async_check_health(*conn_, token); },
|
||||
[this](auto token) { return runner_->async_hello(*conn_, logger_, token); }
|
||||
).async_wait(
|
||||
asio::experimental::wait_for_all(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec0 == error::connect_timeout || ec0 == error::resolve_timeout) {
|
||||
self.complete(ec0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (order[0] == 2 && !!ec2) {
|
||||
self.complete(ec2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (order[0] == 1 && ec1 == error::pong_timeout) {
|
||||
self.complete(ec1);
|
||||
return;
|
||||
}
|
||||
|
||||
self.complete(ec0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Runner, class Connection, class Logger>
|
||||
struct run_all_op {
|
||||
Runner* runner_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
runner_->resv_.async_resolve(std::move(self));
|
||||
logger_.on_resolve(ec, runner_->resv_.results());
|
||||
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
runner_->ctor_.async_connect(conn_->next_layer().next_layer(), runner_->resv_.results(), std::move(self));
|
||||
logger_.on_connect(ec, runner_->ctor_.endpoint());
|
||||
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
|
||||
|
||||
if (conn_->use_ssl()) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
runner_->hsher_.async_handshake(conn_->next_layer(), std::move(self));
|
||||
logger_.on_ssl_handshake(ec);
|
||||
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
|
||||
}
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->async_run_lean(runner_->cfg_, logger_, std::move(self));
|
||||
BOOST_REDIS_CHECK_OP0(;)
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Executor>
|
||||
class runner {
|
||||
public:
|
||||
runner(Executor ex, config cfg)
|
||||
: resv_{ex}
|
||||
, ctor_{ex}
|
||||
, hsher_{ex}
|
||||
, health_checker_{ex}
|
||||
, cfg_{cfg}
|
||||
{ }
|
||||
|
||||
std::size_t cancel(operation op)
|
||||
{
|
||||
resv_.cancel(op);
|
||||
ctor_.cancel(op);
|
||||
hsher_.cancel(op);
|
||||
health_checker_.cancel(op);
|
||||
return 0U;
|
||||
}
|
||||
|
||||
void set_config(config const& cfg)
|
||||
{
|
||||
cfg_ = cfg;
|
||||
resv_.set_config(cfg);
|
||||
ctor_.set_config(cfg);
|
||||
hsher_.set_config(cfg);
|
||||
health_checker_.set_config(cfg);
|
||||
}
|
||||
|
||||
template <class Connection, class Logger, class CompletionToken>
|
||||
auto async_run(Connection& conn, Logger l, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(runner_op<runner, Connection, Logger>{this, &conn, l}, token, conn);
|
||||
}
|
||||
|
||||
config const& get_config() const noexcept {return cfg_;}
|
||||
|
||||
private:
|
||||
using resolver_type = resolver<Executor>;
|
||||
using connector_type = connector<Executor>;
|
||||
using handshaker_type = detail::handshaker<Executor>;
|
||||
using health_checker_type = health_checker<Executor>;
|
||||
using timer_type = typename connector_type::timer_type;
|
||||
|
||||
template <class, class, class> friend struct run_all_op;
|
||||
template <class, class, class> friend class runner_op;
|
||||
template <class, class, class> friend struct hello_op;
|
||||
|
||||
template <class Connection, class Logger, class CompletionToken>
|
||||
auto async_run_all(Connection& conn, Logger l, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(run_all_op<runner, Connection, Logger>{this, &conn, l}, token, conn);
|
||||
}
|
||||
|
||||
template <class Connection, class Logger, class CompletionToken>
|
||||
auto async_hello(Connection& conn, Logger l, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(hello_op<runner, Connection, Logger>{this, &conn, l}, token, conn);
|
||||
}
|
||||
|
||||
void add_hello()
|
||||
{
|
||||
if (!cfg_.username.empty() && !cfg_.password.empty() && !cfg_.clientname.empty())
|
||||
hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password, "SETNAME", cfg_.clientname);
|
||||
else if (cfg_.username.empty() && cfg_.password.empty() && cfg_.clientname.empty())
|
||||
hello_req_.push("HELLO", "3");
|
||||
else if (cfg_.clientname.empty())
|
||||
hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password);
|
||||
else
|
||||
hello_req_.push("HELLO", "3", "SETNAME", cfg_.clientname);
|
||||
|
||||
if (cfg_.database_index)
|
||||
hello_req_.push("SELECT", cfg_.database_index.value());
|
||||
}
|
||||
|
||||
resolver_type resv_;
|
||||
connector_type ctor_;
|
||||
handshaker_type hsher_;
|
||||
health_checker_type health_checker_;
|
||||
request hello_req_;
|
||||
generic_response hello_resp_;
|
||||
config cfg_;
|
||||
};
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_RUNNER_HPP
|
||||
55
include/boost/redis/detail/write.hpp
Normal file
55
include/boost/redis/detail/write.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_WRITE_HPP
|
||||
#define BOOST_REDIS_WRITE_HPP
|
||||
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/redis/request.hpp>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
/** \brief Writes a request synchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
*/
|
||||
template<class SyncWriteStream>
|
||||
auto write(SyncWriteStream& stream, request const& req)
|
||||
{
|
||||
return asio::write(stream, asio::buffer(req.payload()));
|
||||
}
|
||||
|
||||
template<class SyncWriteStream>
|
||||
auto write(SyncWriteStream& stream, request const& req, system::error_code& ec)
|
||||
{
|
||||
return asio::write(stream, asio::buffer(req.payload()), ec);
|
||||
}
|
||||
|
||||
/** \brief Writes a request asynchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
* \param token Asio completion token.
|
||||
*/
|
||||
template<
|
||||
class AsyncWriteStream,
|
||||
class CompletionToken = asio::default_completion_token_t<typename AsyncWriteStream::executor_type>
|
||||
>
|
||||
auto async_write(
|
||||
AsyncWriteStream& stream,
|
||||
request const& req,
|
||||
CompletionToken&& token =
|
||||
asio::default_completion_token_t<typename AsyncWriteStream::executor_type>{})
|
||||
{
|
||||
return asio::async_write(stream, asio::buffer(req.payload()), token);
|
||||
}
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_WRITE_HPP
|
||||
@@ -4,32 +4,20 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_ERROR_HPP
|
||||
#define AEDIS_ERROR_HPP
|
||||
#ifndef BOOST_REDIS_ERROR_HPP
|
||||
#define BOOST_REDIS_ERROR_HPP
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace boost::redis {
|
||||
|
||||
/** \brief Generic errors.
|
||||
* \ingroup high-level-api
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Resolve timeout.
|
||||
resolve_timeout = 1,
|
||||
|
||||
/// Connect timeout.
|
||||
connect_timeout,
|
||||
|
||||
/// Idle timeout.
|
||||
idle_timeout,
|
||||
|
||||
/// Exec timeout.
|
||||
exec_timeout,
|
||||
|
||||
/// Invalid RESP3 type.
|
||||
invalid_data_type,
|
||||
invalid_data_type = 1,
|
||||
|
||||
/// Can't parse the string as a number.
|
||||
not_a_number,
|
||||
@@ -73,17 +61,20 @@ enum class error
|
||||
/// Got RESP3 null.
|
||||
resp3_null,
|
||||
|
||||
/// Unexpected server role.
|
||||
unexpected_server_role,
|
||||
|
||||
/// SSL handshake timeout.
|
||||
ssl_handshake_timeout,
|
||||
|
||||
/// There is no stablished connection.
|
||||
not_connected,
|
||||
|
||||
/// RESP3 handshake error (HELLO command).
|
||||
resp3_handshake_error,
|
||||
/// Resolve timeout
|
||||
resolve_timeout,
|
||||
|
||||
/// Connect timeout
|
||||
connect_timeout,
|
||||
|
||||
/// Connect timeout
|
||||
pong_timeout,
|
||||
|
||||
/// SSL handshake timeout
|
||||
ssl_handshake_timeout,
|
||||
};
|
||||
|
||||
/** \internal
|
||||
@@ -91,15 +82,15 @@ enum class error
|
||||
* \param e Error code.
|
||||
* \ingroup any
|
||||
*/
|
||||
auto make_error_code(error e) -> boost::system::error_code;
|
||||
auto make_error_code(error e) -> system::error_code;
|
||||
|
||||
} // aedis
|
||||
} // boost::redis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::error> : std::true_type {};
|
||||
struct is_error_code_enum<::boost::redis::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
|
||||
#endif // AEDIS_ERROR_HPP
|
||||
#endif // BOOST_REDIS_ERROR_HPP
|
||||
49
include/boost/redis/ignore.hpp
Normal file
49
include/boost/redis/ignore.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_IGNORE_HPP
|
||||
#define BOOST_REDIS_IGNORE_HPP
|
||||
|
||||
#include <boost/system/result.hpp>
|
||||
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost::redis
|
||||
{
|
||||
|
||||
/** @brief Type used to ignore responses.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* @code
|
||||
* response<ignore_t, std::string, ignore_t> resp;
|
||||
* @endcode
|
||||
*
|
||||
* will ignore the first and third responses. RESP3 errors won't be
|
||||
* ignore but will cause `async_exec` to complete with an error.
|
||||
*/
|
||||
using ignore_t = std::decay_t<decltype(std::ignore)>;
|
||||
|
||||
/** @brief Global ignore object.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* Can be used to ignore responses to a request
|
||||
*
|
||||
* @code
|
||||
* conn->async_exec(req, ignore, ...);
|
||||
* @endcode
|
||||
*
|
||||
* RESP3 errors won't be ignore but will cause `async_exec` to
|
||||
* complete with an error.
|
||||
*/
|
||||
extern ignore_t ignore;
|
||||
|
||||
} // boost::redis
|
||||
|
||||
#endif // BOOST_REDIS_IGNORE_HPP
|
||||
39
include/boost/redis/impl/connection.ipp
Normal file
39
include/boost/redis/impl/connection.ipp
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
connection::connection(
|
||||
executor_type ex,
|
||||
asio::ssl::context::method method,
|
||||
std::size_t max_read_size)
|
||||
: impl_{ex, method, max_read_size}
|
||||
{ }
|
||||
|
||||
connection::connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context::method method,
|
||||
std::size_t max_read_size)
|
||||
: impl_{ioc.get_executor(), method, max_read_size}
|
||||
{ }
|
||||
|
||||
void
|
||||
connection::async_run_impl(
|
||||
config const& cfg,
|
||||
logger l,
|
||||
asio::any_completion_handler<void(boost::system::error_code)> token)
|
||||
{
|
||||
impl_.async_run(cfg, l, std::move(token));
|
||||
}
|
||||
|
||||
void connection::cancel(operation op)
|
||||
{
|
||||
impl_.cancel(op);
|
||||
}
|
||||
|
||||
} // namespace boost::redis
|
||||
@@ -4,29 +4,26 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace boost::redis {
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : boost::system::error_category {
|
||||
struct error_category_impl : system::error_category {
|
||||
|
||||
virtual ~error_category_impl() = default;
|
||||
|
||||
auto name() const noexcept -> char const* override
|
||||
{
|
||||
return "aedis";
|
||||
return "boost.redis";
|
||||
}
|
||||
|
||||
auto message(int ev) const -> std::string override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::resolve_timeout: return "Resolve operation timeout.";
|
||||
case error::connect_timeout: return "Connect operation timeout.";
|
||||
case error::idle_timeout: return "Idle timeout.";
|
||||
case error::exec_timeout: return "Exec timeout.";
|
||||
case error::invalid_data_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::not_a_number: return "Can't convert string to number (maybe forgot to upgrade to RESP3?).";
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
case error::unexpected_bool_value: return "Unexpected bool value.";
|
||||
case error::empty_field: return "Expected field value is empty.";
|
||||
@@ -40,16 +37,16 @@ struct error_category_impl : boost::system::error_category {
|
||||
case error::incompatible_size: return "Aggregate container has incompatible size.";
|
||||
case error::not_a_double: return "Not a double.";
|
||||
case error::resp3_null: return "Got RESP3 null.";
|
||||
case error::unexpected_server_role: return "Unexpected server role.";
|
||||
case error::ssl_handshake_timeout: return "SSL handshake timeout.";
|
||||
case error::not_connected: return "Not connected.";
|
||||
case error::resp3_handshake_error: return "RESP3 handshake error (HELLO command).";
|
||||
default: BOOST_ASSERT(false); return "Aedis error.";
|
||||
case error::resolve_timeout: return "Resolve timeout.";
|
||||
case error::connect_timeout: return "Connect timeout.";
|
||||
case error::pong_timeout: return "Pong timeout.";
|
||||
default: BOOST_ASSERT(false); return "Boost.Redis error.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto category() -> boost::system::error_category const&
|
||||
auto category() -> system::error_category const&
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
@@ -57,9 +54,9 @@ auto category() -> boost::system::error_category const&
|
||||
|
||||
} // detail
|
||||
|
||||
auto make_error_code(error e) -> boost::system::error_code
|
||||
auto make_error_code(error e) -> system::error_code
|
||||
{
|
||||
return boost::system::error_code{static_cast<int>(e), detail::category()};
|
||||
return system::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
} // aedis
|
||||
} // boost::redis::detail
|
||||
12
include/boost/redis/impl/ignore.ipp
Normal file
12
include/boost/redis/impl/ignore.ipp
Normal file
@@ -0,0 +1,12 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/ignore.hpp>
|
||||
|
||||
namespace boost::redis
|
||||
{
|
||||
ignore_t ignore;
|
||||
}
|
||||
128
include/boost/redis/impl/logger.ipp
Normal file
128
include/boost/redis/impl/logger.ipp
Normal file
@@ -0,0 +1,128 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/logger.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
|
||||
namespace boost::redis
|
||||
{
|
||||
|
||||
void logger::write_prefix()
|
||||
{
|
||||
if (!std::empty(prefix_))
|
||||
std::clog << prefix_;
|
||||
}
|
||||
|
||||
void logger::on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "Resolve results: ";
|
||||
|
||||
if (ec) {
|
||||
std::clog << ec.message() << std::endl;
|
||||
} else {
|
||||
auto begin = std::cbegin(res);
|
||||
auto end = std::cend(res);
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
std::clog << begin->endpoint();
|
||||
for (auto iter = std::next(begin); iter != end; ++iter)
|
||||
std::clog << ", " << iter->endpoint();
|
||||
}
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "Connected to endpoint: ";
|
||||
|
||||
if (ec)
|
||||
std::clog << ec.message() << std::endl;
|
||||
else
|
||||
std::clog << ep;
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_ssl_handshake(system::error_code const& ec)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "SSL handshake: " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_connection_lost(system::error_code const& ec)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
if (ec)
|
||||
std::clog << "Connection lost: " << ec.message();
|
||||
else
|
||||
std::clog << "Connection lost.";
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
logger::on_write(
|
||||
system::error_code const& ec,
|
||||
std::string const& payload)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
if (ec)
|
||||
std::clog << "Write: " << ec.message();
|
||||
else
|
||||
std::clog << "Bytes written: " << std::size(payload);
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
logger::on_hello(
|
||||
system::error_code const& ec,
|
||||
generic_response const& resp)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
if (ec) {
|
||||
std::clog << "Hello: " << ec.message();
|
||||
if (resp.has_error())
|
||||
std::clog << " (" << resp.error().diagnostic << ")";
|
||||
} else {
|
||||
std::clog << "Hello: Success";
|
||||
}
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
} // boost::redis
|
||||
@@ -4,11 +4,13 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <aedis/resp3/request.hpp>
|
||||
#include <boost/redis/request.hpp>
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
#include <string_view>
|
||||
|
||||
auto has_push_response(boost::string_view cmd) -> bool
|
||||
namespace boost::redis::detail {
|
||||
|
||||
auto has_response(std::string_view cmd) -> bool
|
||||
{
|
||||
if (cmd == "SUBSCRIBE") return true;
|
||||
if (cmd == "PSUBSCRIBE") return true;
|
||||
@@ -16,4 +18,4 @@ auto has_push_response(boost::string_view cmd) -> bool
|
||||
return false;
|
||||
}
|
||||
|
||||
} // aedis::resp3::detail
|
||||
} // boost:redis::detail
|
||||
127
include/boost/redis/logger.hpp
Normal file
127
include/boost/redis/logger.hpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_LOGGER_HPP
|
||||
#define BOOST_REDIS_LOGGER_HPP
|
||||
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace boost::system {class error_code;}
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Logger class
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* The class can be passed to the connection objects to log to `std::clog`
|
||||
*/
|
||||
class logger {
|
||||
public:
|
||||
/** @brief Syslog-like log levels
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
enum class level
|
||||
{ /// Emergency
|
||||
emerg,
|
||||
|
||||
/// Alert
|
||||
alert,
|
||||
|
||||
/// Critical
|
||||
crit,
|
||||
|
||||
/// Error
|
||||
err,
|
||||
|
||||
/// Warning
|
||||
warning,
|
||||
|
||||
/// Notice
|
||||
notice,
|
||||
|
||||
/// Info
|
||||
info,
|
||||
|
||||
/// Debug
|
||||
debug
|
||||
};
|
||||
|
||||
/** @brief Constructor
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param l Log level.
|
||||
*/
|
||||
logger(level l = level::info)
|
||||
: level_{l}
|
||||
{}
|
||||
|
||||
/** @brief Called when the resolve operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error returned by the resolve operation.
|
||||
* @param res Resolve results.
|
||||
*/
|
||||
void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res);
|
||||
|
||||
/** @brief Called when the connect operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error returned by the connect operation.
|
||||
* @param ep Endpoint to which the connection connected.
|
||||
*/
|
||||
void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep);
|
||||
|
||||
/** @brief Called when the ssl handshake operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error returned by the handshake operation.
|
||||
*/
|
||||
void on_ssl_handshake(system::error_code const& ec);
|
||||
|
||||
/** @brief Called when the connection is lost.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error returned when the connection is lost.
|
||||
*/
|
||||
void on_connection_lost(system::error_code const& ec);
|
||||
|
||||
/** @brief Called when the write operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error code returned by the write operation.
|
||||
* @param payload The payload written to the socket.
|
||||
*/
|
||||
void on_write(system::error_code const& ec, std::string const& payload);
|
||||
|
||||
/** @brief Called when the `HELLO` request completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error code returned by the async_exec operation.
|
||||
* @param resp Response sent by the Redis server.
|
||||
*/
|
||||
void on_hello(system::error_code const& ec, generic_response const& resp);
|
||||
|
||||
/** @brief Sets a prefix to every log message
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param prefix The prefix.
|
||||
*/
|
||||
void set_prefix(std::string_view prefix)
|
||||
{
|
||||
prefix_ = prefix;
|
||||
}
|
||||
|
||||
private:
|
||||
void write_prefix();
|
||||
level level_;
|
||||
std::string_view prefix_;
|
||||
};
|
||||
|
||||
} // boost::redis
|
||||
|
||||
#endif // BOOST_REDIS_LOGGER_HPP
|
||||
41
include/boost/redis/operation.hpp
Normal file
41
include/boost/redis/operation.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_OPERATION_HPP
|
||||
#define BOOST_REDIS_OPERATION_HPP
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Connection operations that can be cancelled.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* The operations listed below can be passed to the
|
||||
* `boost::redis::connection::cancel` member function.
|
||||
*/
|
||||
enum class operation {
|
||||
/// Resolve operation.
|
||||
resolve,
|
||||
/// Connect operation.
|
||||
connect,
|
||||
/// SSL handshake operation.
|
||||
ssl_handshake,
|
||||
/// Refers to `connection::async_exec` operations.
|
||||
exec,
|
||||
/// Refers to `connection::async_run` operations.
|
||||
run,
|
||||
/// Refers to `connection::async_receive` operations.
|
||||
receive,
|
||||
/// Cancels reconnection.
|
||||
reconnection,
|
||||
/// Health check operation.
|
||||
health_check,
|
||||
/// Refers to all operations.
|
||||
all,
|
||||
};
|
||||
|
||||
} // boost::redis
|
||||
|
||||
#endif // BOOST_REDIS_OPERATION_HPP
|
||||
321
include/boost/redis/request.hpp
Normal file
321
include/boost/redis/request.hpp
Normal file
@@ -0,0 +1,321 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_REQUEST_HPP
|
||||
#define BOOST_REDIS_REQUEST_HPP
|
||||
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <algorithm>
|
||||
|
||||
// NOTE: For some commands like hset it would be a good idea to assert
|
||||
// the value type is a pair.
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
namespace detail{
|
||||
auto has_response(std::string_view cmd) -> bool;
|
||||
}
|
||||
|
||||
/** \brief Creates Redis requests.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* A request is composed of one or more Redis commands and is
|
||||
* referred to in the redis documentation as a pipeline, see
|
||||
* https://redis.io/topics/pipelining. For example
|
||||
*
|
||||
* @code
|
||||
* request r;
|
||||
* r.push("HELLO", 3);
|
||||
* r.push("FLUSHALL");
|
||||
* r.push("PING");
|
||||
* r.push("PING", "key");
|
||||
* r.push("QUIT");
|
||||
* @endcode
|
||||
*
|
||||
* \remarks
|
||||
*
|
||||
* Uses a std::string for internal storage.
|
||||
*/
|
||||
class request {
|
||||
public:
|
||||
/// Request configuration options.
|
||||
struct config {
|
||||
/** \brief If `true`
|
||||
* `boost::redis::connection::async_exec` will complete with error if the
|
||||
* connection is lost. Affects only requests that haven't been
|
||||
* sent yet.
|
||||
*/
|
||||
bool cancel_on_connection_lost = true;
|
||||
|
||||
/** \brief If `true` the request will complete with
|
||||
* boost::redis::error::not_connected if `async_exec` is called before
|
||||
* the connection with Redis was established.
|
||||
*/
|
||||
bool cancel_if_not_connected = false;
|
||||
|
||||
/** \brief If `false` `boost::redis::connection::async_exec` will not
|
||||
* automatically cancel this request if the connection is lost.
|
||||
* Affects only requests that have been written to the socket
|
||||
* but remained unresponded when `boost::redis::connection::async_run`
|
||||
* completed.
|
||||
*/
|
||||
bool cancel_if_unresponded = true;
|
||||
|
||||
/** \brief If this request has a `HELLO` command and this flag is
|
||||
* `true`, the `boost::redis::connection` will move it to the front of
|
||||
* the queue of awaiting requests. This makes it possible to
|
||||
* send `HELLO` and authenticate before other commands are sent.
|
||||
*/
|
||||
bool hello_with_priority = true;
|
||||
};
|
||||
|
||||
/** \brief Constructor
|
||||
*
|
||||
* \param cfg Configuration options.
|
||||
*/
|
||||
explicit
|
||||
request(config cfg = config{true, false, true, true})
|
||||
: cfg_{cfg} {}
|
||||
|
||||
//// Returns the number of commands contained in this request.
|
||||
[[nodiscard]] auto size() const noexcept -> std::size_t
|
||||
{ return commands_;};
|
||||
|
||||
[[nodiscard]] auto payload() const noexcept -> std::string_view
|
||||
{ return payload_;}
|
||||
|
||||
[[nodiscard]] auto has_hello_priority() const noexcept -> auto const&
|
||||
{ return has_hello_priority_;}
|
||||
|
||||
/// Clears the request preserving allocated memory.
|
||||
void clear()
|
||||
{
|
||||
payload_.clear();
|
||||
commands_ = 0;
|
||||
has_hello_priority_ = false;
|
||||
}
|
||||
|
||||
/// Calls std::string::reserve on the internal storage.
|
||||
void reserve(std::size_t new_cap = 0)
|
||||
{ payload_.reserve(new_cap); }
|
||||
|
||||
/// Returns a const reference to the config object.
|
||||
[[nodiscard]] auto get_config() const noexcept -> auto const& {return cfg_; }
|
||||
|
||||
/// Returns a reference to the config object.
|
||||
[[nodiscard]] auto get_config() noexcept -> auto& {return cfg_; }
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* \code
|
||||
* request req;
|
||||
* req.push("SET", "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the `set` command with value "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The command e.g redis or sentinel command.
|
||||
* \param args Command arguments.
|
||||
* \tparam Ts Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument. This function must be made available over ADL and must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void boost_redis_to_bulk(std::string& to, T const& t);
|
||||
* {
|
||||
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* See cpp20_serialization.cpp
|
||||
*/
|
||||
template <class... Ts>
|
||||
void push(std::string_view cmd, Ts const&... args)
|
||||
{
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
resp3::add_header(payload_, resp3::type::array, 1 + pack_size);
|
||||
resp3::add_bulk(payload_, cmd);
|
||||
resp3::add_bulk(payload_, std::tie(std::forward<Ts const&>(args)...));
|
||||
|
||||
check_cmd(cmd);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a key and have a
|
||||
* dynamic range of arguments. For example
|
||||
*
|
||||
* @code
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
* , {"key3", "value3"}
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range("HSET", "key", std::cbegin(map), std::cend(map));
|
||||
* @endcode
|
||||
*
|
||||
* \param cmd The command e.g. Redis or Sentinel command.
|
||||
* \param key The command key.
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
* \tparam Ts Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument. This function must be made available over ADL and must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void boost_redis_to_bulk(std::string& to, T const& t);
|
||||
* {
|
||||
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* See cpp20_serialization.cpp
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void
|
||||
push_range(
|
||||
std::string_view const& cmd,
|
||||
std::string_view const& key,
|
||||
ForwardIterator begin,
|
||||
ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = resp3::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
resp3::add_header(payload_, resp3::type::array, 2 + size * distance);
|
||||
resp3::add_bulk(payload_, cmd);
|
||||
resp3::add_bulk(payload_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
resp3::add_bulk(payload_, *begin);
|
||||
|
||||
check_cmd(cmd);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a dynamic number
|
||||
* of arguments and don't have a key. For example
|
||||
*
|
||||
* \code
|
||||
* std::set<std::string> channels
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push("SUBSCRIBE", std::cbegin(channels), std::cend(channels));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
* \tparam ForwardIterator If the value type is not a std::string it will be converted to a string by calling `boost_redis_to_bulk`. This function must be made available over ADL and must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void boost_redis_to_bulk(std::string& to, T const& t);
|
||||
* {
|
||||
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* See cpp20_serialization.cpp
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void
|
||||
push_range(
|
||||
std::string_view const& cmd,
|
||||
ForwardIterator begin,
|
||||
ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = resp3::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
resp3::add_header(payload_, resp3::type::array, 1 + size * distance);
|
||||
resp3::add_bulk(payload_, cmd);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
resp3::add_bulk(payload_, *begin);
|
||||
|
||||
check_cmd(cmd);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range of begin and end
|
||||
* iterators.
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param key Redis key.
|
||||
* \param range Range to send e.g. `std::map`.
|
||||
* \tparam A type that can be passed to `std::cbegin()` and `std::cend()`.
|
||||
*/
|
||||
template <class Range>
|
||||
void
|
||||
push_range(
|
||||
std::string_view const& cmd,
|
||||
std::string_view const& key,
|
||||
Range const& range,
|
||||
decltype(std::begin(range)) * = nullptr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range of begin and end
|
||||
* iterators.
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param range Range to send e.g. `std::map`.
|
||||
* \tparam A type that can be passed to `std::cbegin()` and `std::cend()`.
|
||||
*/
|
||||
template <class Range>
|
||||
void
|
||||
push_range(
|
||||
std::string_view cmd,
|
||||
Range const& range,
|
||||
decltype(std::cbegin(range)) * = nullptr)
|
||||
{
|
||||
using std::cbegin;
|
||||
using std::cend;
|
||||
push_range(cmd, cbegin(range), cend(range));
|
||||
}
|
||||
|
||||
private:
|
||||
void check_cmd(std::string_view cmd)
|
||||
{
|
||||
if (!detail::has_response(cmd))
|
||||
++commands_;
|
||||
|
||||
if (cmd == "HELLO")
|
||||
has_hello_priority_ = cfg_.hello_with_priority;
|
||||
}
|
||||
|
||||
config cfg_;
|
||||
std::string payload_;
|
||||
std::size_t commands_ = 0;
|
||||
bool has_hello_priority_ = false;
|
||||
};
|
||||
|
||||
} // boost::redis::resp3
|
||||
|
||||
#endif // BOOST_REDIS_REQUEST_HPP
|
||||
209
include/boost/redis/resp3/impl/parser.ipp
Normal file
209
include/boost/redis/resp3/impl/parser.ipp
Normal file
@@ -0,0 +1,209 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/resp3/parser.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <charconv>
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
void to_int(int_type& i, std::string_view sv, system::error_code& ec)
|
||||
{
|
||||
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), i);
|
||||
if (res.ec != std::errc())
|
||||
ec = error::not_a_number;
|
||||
}
|
||||
|
||||
parser::parser()
|
||||
{
|
||||
sizes_[0] = 2; // The sentinel must be more than 1.
|
||||
}
|
||||
|
||||
std::size_t
|
||||
parser::get_suggested_buffer_growth(std::size_t hint) const noexcept
|
||||
{
|
||||
if (!bulk_expected())
|
||||
return hint;
|
||||
|
||||
if (hint < bulk_length_ + 2)
|
||||
return bulk_length_ + 2;
|
||||
|
||||
return hint;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
parser::get_consumed() const noexcept
|
||||
{
|
||||
return consumed_;
|
||||
}
|
||||
|
||||
bool
|
||||
parser::done() const noexcept
|
||||
{
|
||||
return depth_ == 0 && bulk_ == type::invalid && consumed_ != 0;
|
||||
}
|
||||
|
||||
void
|
||||
parser::commit_elem() noexcept
|
||||
{
|
||||
--sizes_[depth_];
|
||||
while (sizes_[depth_] == 0) {
|
||||
--depth_;
|
||||
--sizes_[depth_];
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
parser::consume(std::string_view view, system::error_code& ec) noexcept -> parser::result
|
||||
{
|
||||
switch (bulk_) {
|
||||
case type::invalid:
|
||||
{
|
||||
auto const pos = view.find(sep, consumed_);
|
||||
if (pos == std::string::npos)
|
||||
return {}; // Needs more data to proceeed.
|
||||
|
||||
auto const t = to_type(view.at(consumed_));
|
||||
auto const content = view.substr(consumed_ + 1, pos - 1 - consumed_);
|
||||
auto const ret = consume_impl(t, content, ec);
|
||||
if (ec)
|
||||
return {};
|
||||
|
||||
consumed_ = pos + 2;
|
||||
if (!bulk_expected())
|
||||
return ret;
|
||||
|
||||
} [[fallthrough]];
|
||||
|
||||
default: // Handles bulk.
|
||||
{
|
||||
auto const span = bulk_length_ + 2;
|
||||
if ((std::size(view) - consumed_) < span)
|
||||
return {}; // Needs more data to proceeed.
|
||||
|
||||
auto const bulk_view = view.substr(consumed_, bulk_length_);
|
||||
node_type const ret = {bulk_, 1, depth_, bulk_view};
|
||||
bulk_ = type::invalid;
|
||||
commit_elem();
|
||||
|
||||
consumed_ += span;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto
|
||||
parser::consume_impl(
|
||||
type t,
|
||||
std::string_view elem,
|
||||
system::error_code& ec) -> parser::node_type
|
||||
{
|
||||
BOOST_ASSERT(!bulk_expected());
|
||||
|
||||
node_type ret;
|
||||
switch (t) {
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
to_int(bulk_length_ , elem, ec);
|
||||
if (ec)
|
||||
return {};
|
||||
|
||||
if (bulk_length_ == 0) {
|
||||
ret = {type::streamed_string_part, 1, depth_, {}};
|
||||
sizes_[depth_] = 1; // We are done.
|
||||
bulk_ = type::invalid;
|
||||
commit_elem();
|
||||
} else {
|
||||
bulk_ = type::streamed_string_part;
|
||||
}
|
||||
} break;
|
||||
case type::blob_error:
|
||||
case type::verbatim_string:
|
||||
case type::blob_string:
|
||||
{
|
||||
if (elem.at(0) == '?') {
|
||||
// NOTE: This can only be triggered with blob_string.
|
||||
// Trick: A streamed string is read as an aggregate of
|
||||
// infinite length. When the streaming is done the server
|
||||
// is supposed to send a part with length 0.
|
||||
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
|
||||
ret = {type::streamed_string, 0, depth_, {}};
|
||||
} else {
|
||||
to_int(bulk_length_ , elem , ec);
|
||||
if (ec)
|
||||
return {};
|
||||
|
||||
bulk_ = t;
|
||||
}
|
||||
} break;
|
||||
case type::boolean:
|
||||
{
|
||||
if (std::empty(elem)) {
|
||||
ec = error::empty_field;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (elem.at(0) != 'f' && elem.at(0) != 't') {
|
||||
ec = error::unexpected_bool_value;
|
||||
return {};
|
||||
}
|
||||
|
||||
ret = {t, 1, depth_, elem};
|
||||
commit_elem();
|
||||
} break;
|
||||
case type::doublean:
|
||||
case type::big_number:
|
||||
case type::number:
|
||||
{
|
||||
if (std::empty(elem)) {
|
||||
ec = error::empty_field;
|
||||
return {};
|
||||
}
|
||||
} [[fallthrough]];
|
||||
case type::simple_error:
|
||||
case type::simple_string:
|
||||
case type::null:
|
||||
{
|
||||
ret = {t, 1, depth_, elem};
|
||||
commit_elem();
|
||||
} break;
|
||||
case type::push:
|
||||
case type::set:
|
||||
case type::array:
|
||||
case type::attribute:
|
||||
case type::map:
|
||||
{
|
||||
int_type l = -1;
|
||||
to_int(l, elem, ec);
|
||||
if (ec)
|
||||
return {};
|
||||
|
||||
ret = {t, l, depth_, {}};
|
||||
if (l == 0) {
|
||||
commit_elem();
|
||||
} else {
|
||||
if (depth_ == max_embedded_depth) {
|
||||
ec = error::exceeeds_max_nested_depth;
|
||||
return {};
|
||||
}
|
||||
|
||||
++depth_;
|
||||
|
||||
sizes_[depth_] = l * element_multiplicity(t);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
ec = error::invalid_data_type;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} // boost::redis::resp3
|
||||
42
include/boost/redis/resp3/impl/serialization.ipp
Normal file
42
include/boost/redis/resp3/impl/serialization.ipp
Normal file
@@ -0,0 +1,42 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
#include <boost/redis/resp3/parser.hpp>
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
void boost_redis_to_bulk(std::string& payload, std::string_view data)
|
||||
{
|
||||
auto const str = std::to_string(data.size());
|
||||
|
||||
payload += to_code(type::blob_string);
|
||||
payload.append(std::cbegin(str), std::cend(str));
|
||||
payload += parser::sep;
|
||||
payload.append(std::cbegin(data), std::cend(data));
|
||||
payload += parser::sep;
|
||||
}
|
||||
|
||||
void add_header(std::string& payload, type t, std::size_t size)
|
||||
{
|
||||
auto const str = std::to_string(size);
|
||||
|
||||
payload += to_code(t);
|
||||
payload.append(std::cbegin(str), std::cend(str));
|
||||
payload += parser::sep;
|
||||
}
|
||||
|
||||
void add_blob(std::string& payload, std::string_view blob)
|
||||
{
|
||||
payload.append(std::cbegin(blob), std::cend(blob));
|
||||
payload += parser::sep;
|
||||
}
|
||||
|
||||
void add_separator(std::string& payload)
|
||||
{
|
||||
payload += parser::sep;
|
||||
}
|
||||
} // boost::redis::resp3
|
||||
42
include/boost/redis/resp3/impl/type.ipp
Normal file
42
include/boost/redis/resp3/impl/type.ipp
Normal file
@@ -0,0 +1,42 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
auto to_string(type t) noexcept -> char const*
|
||||
{
|
||||
switch (t) {
|
||||
case type::array: return "array";
|
||||
case type::push: return "push";
|
||||
case type::set: return "set";
|
||||
case type::map: return "map";
|
||||
case type::attribute: return "attribute";
|
||||
case type::simple_string: return "simple_string";
|
||||
case type::simple_error: return "simple_error";
|
||||
case type::number: return "number";
|
||||
case type::doublean: return "doublean";
|
||||
case type::boolean: return "boolean";
|
||||
case type::big_number: return "big_number";
|
||||
case type::null: return "null";
|
||||
case type::blob_error: return "blob_error";
|
||||
case type::verbatim_string: return "verbatim_string";
|
||||
case type::blob_string: return "blob_string";
|
||||
case type::streamed_string: return "streamed_string";
|
||||
case type::streamed_string_part: return "streamed_string_part";
|
||||
default: return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
auto operator<<(std::ostream& os, type t) -> std::ostream&
|
||||
{
|
||||
os << to_string(t);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // boost::redis::resp3
|
||||
64
include/boost/redis/resp3/node.hpp
Normal file
64
include/boost/redis/resp3/node.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_RESP3_NODE_HPP
|
||||
#define BOOST_REDIS_RESP3_NODE_HPP
|
||||
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
/** \brief A node in the response tree.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* RESP3 can contain recursive data structures: A map of sets of
|
||||
* vector of etc. As it is parsed each element is passed to user
|
||||
* callbacks (push parser). The signature of this
|
||||
* callback is `f(resp3::node<std::string_view)`. This class is called a node
|
||||
* because it can be seen as the element of the response tree. It
|
||||
* is a template so that users can use it with owing strings e.g.
|
||||
* `std::string` or `boost::static_string` etc.
|
||||
*
|
||||
* @tparam String A `std::string`-like type.
|
||||
*/
|
||||
template <class String>
|
||||
struct basic_node {
|
||||
/// The RESP3 type of the data in this node.
|
||||
type data_type = type::invalid;
|
||||
|
||||
/// The number of elements of an aggregate.
|
||||
std::size_t aggregate_size{};
|
||||
|
||||
/// The depth of this node in the response tree.
|
||||
std::size_t depth{};
|
||||
|
||||
/// The actual data. For aggregate types this is usually empty.
|
||||
String value{};
|
||||
};
|
||||
|
||||
/** @brief Compares a node for equality.
|
||||
* @relates basic_node
|
||||
*
|
||||
* @param a Left hand side node object.
|
||||
* @param b Right hand side node object.
|
||||
*/
|
||||
template <class String>
|
||||
auto operator==(basic_node<String> const& a, basic_node<String> const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
&& a.data_type == b.data_type
|
||||
&& a.value == b.value;
|
||||
};
|
||||
|
||||
/** @brief A node in the response tree.
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
using node = basic_node<std::string>;
|
||||
|
||||
} // boost::redis::resp3
|
||||
|
||||
#endif // BOOST_REDIS_RESP3_NODE_HPP
|
||||
102
include/boost/redis/resp3/parser.hpp
Normal file
102
include/boost/redis/resp3/parser.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_RESP3_PARSER_HPP
|
||||
#define BOOST_REDIS_RESP3_PARSER_HPP
|
||||
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
using int_type = std::uint64_t;
|
||||
|
||||
class parser {
|
||||
public:
|
||||
using node_type = basic_node<std::string_view>;
|
||||
using result = std::optional<node_type>;
|
||||
|
||||
static constexpr std::size_t max_embedded_depth = 5;
|
||||
static constexpr std::string_view sep = "\r\n";
|
||||
|
||||
private:
|
||||
// The current depth. Simple data types will have depth 0, whereas
|
||||
// the elements of aggregates will have depth 1. Embedded types
|
||||
// will have increasing depth.
|
||||
std::size_t depth_ = 0;
|
||||
|
||||
// The parser supports up to 5 levels of nested structures. The
|
||||
// first element in the sizes stack is a sentinel and must be
|
||||
// different from 1.
|
||||
std::array<std::size_t, max_embedded_depth + 1> sizes_ = {{1}};
|
||||
|
||||
// Contains the length expected in the next bulk read.
|
||||
int_type bulk_length_ = (std::numeric_limits<unsigned long>::max)();
|
||||
|
||||
// The type of the next bulk. Contains type::invalid if no bulk is
|
||||
// expected.
|
||||
type bulk_ = type::invalid;
|
||||
|
||||
// The number of bytes consumed from the buffer.
|
||||
std::size_t consumed_ = 0;
|
||||
|
||||
// Returns the number of bytes that have been consumed.
|
||||
auto consume_impl(type t, std::string_view elem, system::error_code& ec) -> node_type;
|
||||
|
||||
void commit_elem() noexcept;
|
||||
|
||||
// The bulk type expected in the next read. If none is expected
|
||||
// returns type::invalid.
|
||||
[[nodiscard]]
|
||||
auto bulk_expected() const noexcept -> bool
|
||||
{ return bulk_ != type::invalid; }
|
||||
|
||||
public:
|
||||
parser();
|
||||
|
||||
// Returns true when the parser is done with the current message.
|
||||
[[nodiscard]]
|
||||
auto done() const noexcept -> bool;
|
||||
|
||||
auto get_suggested_buffer_growth(std::size_t hint) const noexcept -> std::size_t;
|
||||
|
||||
auto get_consumed() const noexcept -> std::size_t;
|
||||
|
||||
auto consume(std::string_view view, system::error_code& ec) noexcept -> result;
|
||||
};
|
||||
|
||||
template <class Adapter>
|
||||
bool
|
||||
parse(
|
||||
resp3::parser& p,
|
||||
std::string_view const& msg,
|
||||
Adapter& adapter,
|
||||
system::error_code& ec)
|
||||
{
|
||||
while (!p.done()) {
|
||||
auto const res = p.consume(msg, ec);
|
||||
if (ec)
|
||||
return true;
|
||||
|
||||
if (!res)
|
||||
return false;
|
||||
|
||||
adapter(res.value(), ec);
|
||||
if (ec)
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // boost::redis::resp3
|
||||
|
||||
#endif // BOOST_REDIS_RESP3_PARSER_HPP
|
||||
144
include/boost/redis/resp3/serialization.hpp
Normal file
144
include/boost/redis/resp3/serialization.hpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_RESP3_SERIALIZATION_HPP
|
||||
#define BOOST_REDIS_RESP3_SERIALIZATION_HPP
|
||||
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <boost/redis/resp3/parser.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
// NOTE: Consider detecting tuples in the type in the parameter pack
|
||||
// to calculate the header size correctly.
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
/** @brief Adds a bulk to the request.
|
||||
* @relates boost::redis::request
|
||||
*
|
||||
* This function is useful in serialization of your own data
|
||||
* structures in a request. For example
|
||||
*
|
||||
* @code
|
||||
* void boost_redis_to_bulk(std::string& payload, mystruct const& obj)
|
||||
* {
|
||||
* auto const str = // Convert obj to a string.
|
||||
* boost_redis_to_bulk(payload, str);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param payload Storage on which data will be copied into.
|
||||
* @param data Data that will be serialized and stored in `payload`.
|
||||
*
|
||||
* See more in @ref serialization.
|
||||
*/
|
||||
void boost_redis_to_bulk(std::string& payload, std::string_view data);
|
||||
|
||||
template <class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
|
||||
void boost_redis_to_bulk(std::string& payload, T n)
|
||||
{
|
||||
auto const s = std::to_string(n);
|
||||
boost::redis::resp3::boost_redis_to_bulk(payload, std::string_view{s});
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct add_bulk_impl {
|
||||
static void add(std::string& payload, T const& from)
|
||||
{
|
||||
using namespace boost::redis::resp3;
|
||||
boost_redis_to_bulk(payload, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <class ...Ts>
|
||||
struct add_bulk_impl<std::tuple<Ts...>> {
|
||||
static void add(std::string& payload, std::tuple<Ts...> const& t)
|
||||
{
|
||||
auto f = [&](auto const&... vs)
|
||||
{
|
||||
using namespace boost::redis::resp3;
|
||||
(boost_redis_to_bulk(payload, vs), ...);
|
||||
};
|
||||
|
||||
std::apply(f, t);
|
||||
}
|
||||
};
|
||||
|
||||
template <class U, class V>
|
||||
struct add_bulk_impl<std::pair<U, V>> {
|
||||
static void add(std::string& payload, std::pair<U, V> const& from)
|
||||
{
|
||||
using namespace boost::redis::resp3;
|
||||
boost_redis_to_bulk(payload, from.first);
|
||||
boost_redis_to_bulk(payload, from.second);
|
||||
}
|
||||
};
|
||||
|
||||
void add_header(std::string& payload, type t, std::size_t size);
|
||||
|
||||
template <class T>
|
||||
void add_bulk(std::string& payload, T const& data)
|
||||
{
|
||||
add_bulk_impl<T>::add(payload, data);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct bulk_counter;
|
||||
|
||||
template <class>
|
||||
struct bulk_counter {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct bulk_counter<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
void add_blob(std::string& payload, std::string_view blob);
|
||||
void add_separator(std::string& payload);
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template <class Adapter>
|
||||
void deserialize(std::string_view const& data, Adapter adapter, system::error_code& ec)
|
||||
{
|
||||
parser parser;
|
||||
while (!parser.done()) {
|
||||
auto const res = parser.consume(data, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
BOOST_ASSERT(res.has_value());
|
||||
|
||||
adapter(res.value(), ec);
|
||||
if (ec)
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(parser.get_consumed() == std::size(data));
|
||||
}
|
||||
|
||||
template <class Adapter>
|
||||
void deserialize(std::string_view const& data, Adapter adapter)
|
||||
{
|
||||
system::error_code ec;
|
||||
deserialize(data, adapter, ec);
|
||||
|
||||
if (ec)
|
||||
BOOST_THROW_EXCEPTION(system::system_error{ec});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // boost::redis::resp3
|
||||
|
||||
#endif // BOOST_REDIS_RESP3_SERIALIZATION_HPP
|
||||
@@ -4,41 +4,76 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_RESP3_TYPE_HPP
|
||||
#define BOOST_REDIS_RESP3_TYPE_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace aedis::resp3 {
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
auto to_string(type t) -> char const*
|
||||
{
|
||||
switch (t) {
|
||||
case type::array: return "array";
|
||||
case type::push: return "push";
|
||||
case type::set: return "set";
|
||||
case type::map: return "map";
|
||||
case type::attribute: return "attribute";
|
||||
case type::simple_string: return "simple_string";
|
||||
case type::simple_error: return "simple_error";
|
||||
case type::number: return "number";
|
||||
case type::doublean: return "doublean";
|
||||
case type::boolean: return "boolean";
|
||||
case type::big_number: return "big_number";
|
||||
case type::null: return "null";
|
||||
case type::blob_error: return "blob_error";
|
||||
case type::verbatim_string: return "verbatim_string";
|
||||
case type::blob_string: return "blob_string";
|
||||
case type::streamed_string_part: return "streamed_string_part";
|
||||
default: return "invalid";
|
||||
}
|
||||
}
|
||||
/** \brief RESP3 data types.
|
||||
\ingroup high-level-api
|
||||
|
||||
The RESP3 specification can be found at https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md.
|
||||
*/
|
||||
enum class type
|
||||
{ /// Aggregate
|
||||
array,
|
||||
/// Aaggregate
|
||||
push,
|
||||
/// Aggregate
|
||||
set,
|
||||
/// Aggregate
|
||||
map,
|
||||
/// Aggregate
|
||||
attribute,
|
||||
/// Simple
|
||||
simple_string,
|
||||
/// Simple
|
||||
simple_error,
|
||||
/// Simple
|
||||
number,
|
||||
/// Simple
|
||||
doublean,
|
||||
/// Simple
|
||||
boolean,
|
||||
/// Simple
|
||||
big_number,
|
||||
/// Simple
|
||||
null,
|
||||
/// Simple
|
||||
blob_error,
|
||||
/// Simple
|
||||
verbatim_string,
|
||||
/// Simple
|
||||
blob_string,
|
||||
/// Simple
|
||||
streamed_string,
|
||||
/// Simple
|
||||
streamed_string_part,
|
||||
/// Invalid
|
||||
invalid
|
||||
};
|
||||
|
||||
auto operator<<(std::ostream& os, type t) -> std::ostream&
|
||||
{
|
||||
os << to_string(t);
|
||||
return os;
|
||||
}
|
||||
/** \brief Converts the data type to a string.
|
||||
* \ingroup high-level-api
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
auto to_string(type t) noexcept -> char const*;
|
||||
|
||||
auto is_aggregate(type t) -> bool
|
||||
/** \brief Writes the type to the output stream.
|
||||
* \ingroup high-level-api
|
||||
* \param os Output stream.
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
auto operator<<(std::ostream& os, type t) -> std::ostream&;
|
||||
|
||||
/* Checks whether the data type is an aggregate.
|
||||
*/
|
||||
constexpr auto is_aggregate(type t) noexcept -> bool
|
||||
{
|
||||
switch (t) {
|
||||
case type::array:
|
||||
@@ -50,7 +85,9 @@ auto is_aggregate(type t) -> bool
|
||||
}
|
||||
}
|
||||
|
||||
auto element_multiplicity(type t) -> std::size_t
|
||||
// For map and attribute data types this function returns 2. All
|
||||
// other types have value 1.
|
||||
constexpr auto element_multiplicity(type t) noexcept -> std::size_t
|
||||
{
|
||||
switch (t) {
|
||||
case type::map:
|
||||
@@ -59,7 +96,8 @@ auto element_multiplicity(type t) -> std::size_t
|
||||
}
|
||||
}
|
||||
|
||||
auto to_code(type t) -> char
|
||||
// Returns the wire code of a given type.
|
||||
constexpr auto to_code(type t) noexcept -> char
|
||||
{
|
||||
switch (t) {
|
||||
case type::blob_error: return '!';
|
||||
@@ -83,7 +121,8 @@ auto to_code(type t) -> char
|
||||
}
|
||||
}
|
||||
|
||||
auto to_type(char c) -> type
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
constexpr auto to_type(char c) noexcept -> type
|
||||
{
|
||||
switch (c) {
|
||||
case '!': return type::blob_error;
|
||||
@@ -106,4 +145,6 @@ auto to_type(char c) -> type
|
||||
}
|
||||
}
|
||||
|
||||
} // aedis::resp3
|
||||
} // boost::redis::resp3
|
||||
|
||||
#endif // BOOST_REDIS_RESP3_TYPE_HPP
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user