2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-23 06:02:09 +00:00

Compare commits

...

128 Commits

Author SHA1 Message Date
Marcelo
84ee2f37f1 Merge pull request #141 from boostorg/develop
Pre-Boost release of latest changes in develop
2023-08-18 23:08:05 +02:00
Marcelo
81927deda4 Merge pull request #134 from boostorg/123-automatically-select-database-after-hello
123 automatically select database after hello
2023-08-06 11:00:34 +02:00
Marcelo Zimbres
34ff1cea63 Fixes https://github.com/boostorg/redis/issues/121 2023-08-06 10:07:17 +02:00
Marcelo Zimbres
10603b7d3a Sends SELECT right after HELLO after a connection. 2023-08-06 10:07:17 +02:00
Marcelo
ad3c2914db Merge pull request #133 from boostorg/122-health-check-not-restart-after-connection-lost
Simplifies parse ops and fixes health-check on reconnection.
2023-08-05 23:04:50 +02:00
Marcelo Zimbres
91014b13bf Simplifies parse ops and fixes health-check on reconnection. 2023-08-05 22:20:39 +02:00
Marcelo
4f6f8b454d Merge pull request #132 from cbodley/wip-exec-forward
connection: async_exec forwards completion token
2023-08-02 22:06:27 +02:00
Casey Bodley
9ebcc544ae connection: async_exec forwards completion token
async operations should support move-only completion handlers. forward
the CompletionToken argument to avoid an unnecessary copy

Fixes: #131

Signed-off-by: Casey Bodley <cbodley@redhat.com>
2023-08-02 11:45:57 -04:00
Marcelo
7d16259749 Merge pull request #130 from boostorg/119-simplify-the-parser-op
Simplifications in the parser
2023-07-30 11:44:58 +02:00
Marcelo Zimbres
9dec63515e Simplifications in the parser. 2023-07-30 08:18:17 +02:00
Marcelo
46525371b9 Merge pull request #117 from cthulhu-irl/build/add-cmake-options
build: add cmake options
2023-07-01 13:03:31 +02:00
Cthulhu
b5f8348598 build: add cmake options
cmake options for install, tests, examples, and doc instead of
building them always.

options are enabled by default when building the project directly,
otherwise if add_subdirectory (directly or by FetchContent, etc),
then unnecessary options will be disabled.

issue #115
2023-06-25 19:26:27 +03:30
Marcelo
69d12421e2 Merge pull request #116 from boostorg/113-create-an-experimental-connection-class-that-has-fast-compilation-times
113 create an experimental connection class that has fast compilation times
2023-06-24 09:52:48 +02:00
Marcelo Zimbres
a715c251bf Improvements in the docs. 2023-06-23 22:24:34 +02:00
Marcelo Zimbres
d29a057fa6 Uses composition instead of inheritance in the connection class. 2023-06-20 23:01:17 +02:00
Marcelo Zimbres
82430afc8b Make the connection non-generic on the executor type. 2023-06-18 09:34:40 +02:00
Marcelo
607946f00e Merge pull request #112 from boostorg/111-simplify-the-serialization-examples
111 simplify the serialization examples
2023-06-11 11:12:06 +02:00
Marcelo Zimbres
c99790ab5c Uses choco instead of cinst. 2023-06-11 09:28:12 +02:00
Marcelo Zimbres
635b3608ad Removes unnecessary files. 2023-06-08 22:00:07 +02:00
Marcelo
a8a78c38c6 Merge pull request #109 from boostorg/103-cant-read-response-of-hello-+-ping-+-hello
Removes payload rotation from request.
2023-06-04 16:50:12 +02:00
Marcelo Zimbres
e09a53ff08 Removes payload rotation from request.
The user can simply call HELLO before other commands. Altering the order
of requests makes it impossible to declare responses.
2023-06-04 16:18:35 +02:00
Marcelo
ec8a1c7286 Merge pull request #105 from boostorg/95-improve-the-performance-of-connectionasync_receive
95 improve the performance of connectionasync receive
2023-05-28 11:14:14 +02:00
Marcelo Zimbres
3c02a7662b Replaces connection channel with a timer. 2023-05-27 23:20:15 +02:00
Marcelo Zimbres
538ab8f35f Reduces the number of rescheduling needed to process a server sent push.
Performance improved by close to 10%.
2023-05-21 21:17:13 +02:00
Marcelo Zimbres
f5f57e370b Improvements in the redis-push stress test. 2023-05-21 21:17:13 +02:00
Marcelo
7abfc5fd8d Merge pull request #101 from boostorg/100-runhpp-no-longer-present-but-still-referred-to-from-redishpp
Fixes redis.hpp and slightly improves compilation times.
2023-05-20 14:16:19 +02:00
Marcelo Zimbres
11eebcf771 Fixes redis.hpp and slightly improves compilation times. 2023-05-20 13:19:31 +02:00
Marcelo
c21f70bc07 Merge pull request #99 from boostorg/93-use-cmake-foreach-to-simplify-cmakeliststxt
Simplifies the CMakeLists.txt.
2023-05-14 13:55:46 +02:00
Marcelo Zimbres
22bacbd52c Simplifies the CMakeLists.txt. 2023-05-14 10:42:16 +02:00
Marcelo
2982f831f6 Merge pull request #98 from boostorg/94-unify-redisconnection-and-redissslconnection
94 unify redisconnection and redissslconnection
2023-05-13 18:22:53 +02:00
Marcelo Zimbres
663e9ac671 Simplifications. 2023-05-13 10:22:11 +02:00
Marcelo Zimbres
c0aa4356ea The ssl::context is now owned by the connection. 2023-05-10 23:25:09 +02:00
Marcelo Zimbres
6f9fd5b2fb Unifies ssl and plain connections. 2023-05-09 23:12:16 +02:00
Marcelo
30a6e34e4e Merge pull request #97 from boostorg/85-added-example-cpp20_streams-which-reproduces-an-assertion
85 added example cpp20 streams which reproduces an assertion
2023-05-06 21:26:09 +02:00
Marcelo Zimbres
1f9b3e8008 Rebase the branch on develop. 2023-05-06 20:48:32 +02:00
bram
3808fec0e3 Cleaned up a bit
Removed unused stuff
Using request and response as shared_ptrs.
Removed (unnecessary?) calls to net::post.
2023-05-06 17:14:10 +02:00
bram
607a9e9dd6 Added example cpp20_streams, which reproduces an assertion. 2023-05-06 16:55:15 +02:00
Marcelo
2d53bb748e Merge pull request #92 from boostorg/90-add-support-for-reconnection
90 add support for reconnection
2023-05-06 15:39:29 +02:00
Marcelo Zimbres
a6cb4ca323 Adds high-level functionality to connection::async_run. 2023-05-02 23:15:08 +02:00
Marcelo Zimbres
5ac4f7e8ad Removes dependency on asio::promise as it does not compile on windows. 2023-03-31 04:00:20 +02:00
Marcelo Zimbres
7a08588808 Progresses with the subscriber. 2023-03-31 04:00:19 +02:00
Marcelo
e7ff1cedf3 Merge pull request #89 from boostorg/88-simplify-async_check_health-with-asioconsign
Uses consign to simplify the check-health operation.
2023-03-20 01:53:40 +01:00
Marcelo Zimbres
0bcb7dcf16 Uses consign to simplify the check-health operation. 2023-03-20 01:26:50 +01:00
Marcelo
c28969674b Merge pull request #86 from boostorg/83-fix-reconnect-loop-in-the-subscriber-example
83 fix reconnect loop in the subscriber example
2023-03-16 18:05:40 +01:00
Marcelo Zimbres
c7f49c6677 Adds address struct. 2023-03-16 17:01:49 +01:00
Marcelo Zimbres
90bcd621fb Including only necessary headers. 2023-03-14 20:11:22 +01:00
Marcelo Zimbres
fd967204df Implements non-member async_run for plain connections.
This function will resolve and connect before calling member async_run.
2023-03-13 21:37:25 +01:00
Marcelo
cd00047a49 Merge pull request #82 from boostorg/81-add-support-for-protobuf
First steps with protobuf support.
2023-03-11 13:10:39 +01:00
Marcelo Zimbres
728b35cfe0 Adds protobuf example. 2023-03-11 12:24:35 +01:00
Marcelo
52e62ba78c Merge pull request #80 from boostorg/79-missing-header-in-resulthpp
Adds missing include header.
2023-03-08 12:10:37 +01:00
Marcelo Zimbres
bb18ff4891 Adds missing include header. 2023-03-07 20:24:53 +01:00
Marcelo
6ce793e413 Merge pull request #78 from boostorg/69-move-health-check-functionality-from-examples-to-the-library
Implements a function that checks Redis health.
2023-03-05 21:18:12 +01:00
Marcelo Zimbres
a83c0e7803 Trying to fix build on MSVC by including tuple before asio. 2023-03-05 20:54:02 +01:00
Marcelo Zimbres
64820bd25b Implements a function that checks Redis health. 2023-03-04 15:54:23 +01:00
Marcelo
16b5c8d1ba Merge pull request #77 from boostorg/72-cannot-build-on-macos
Adds support for libc++.
2023-03-02 08:12:36 +01:00
Marcelo Zimbres
8ef4d3cf0b Adds support for libc++. 2023-03-02 07:46:21 +01:00
Marcelo
d01a9acf3b Merge pull request #75 from boostorg/51-more-suggestions-for-documentation-improvements
Addresses issue #45, #47, #51 and #74.
2023-02-26 20:58:14 +01:00
Marcelo Zimbres
ac7e425d47 Addresses issue #45, #47, #51 and #74. 2023-02-26 20:34:43 +01:00
Marcelo
d620cdee59 Merge pull request #71 from boostorg/70-rename-node-to-basic_node-and-add-typedef-for-nodestdstring
70 rename node to basic node and add typedef for nodestdstring
2023-02-25 15:30:04 +01:00
Marcelo Zimbres
5f07b730f7 Upgrades to Boost 1.81. 2023-02-25 10:26:07 +01:00
Marcelo Zimbres
6d3a112f94 Removes json serialization boilerplate. 2023-02-25 10:14:28 +01:00
Marcelo Zimbres
1f3ef6b486 Renames node to basic_node. 2023-02-25 10:14:28 +01:00
Marcelo
a850a6ed63 Merge pull request #68 from boostorg/67-make-the-connection-full-duplex
67 make the connection full duplex
2023-02-18 21:39:13 +01:00
Marcelo Zimbres
c8b73c2fe8 Removes coalesce property of the requests.
It doesn't make any sense after the implementation of full-duplex
communication.
2023-02-18 20:08:55 +01:00
Marcelo Zimbres
8b02268182 Uses boost.describe to simplify json serialization. 2023-02-16 21:44:45 +01:00
Marcelo Zimbres
1b60eeb352 Makes the connection full-duplex. 2023-02-12 19:14:07 +01:00
Marcelo
b93f36163d Merge pull request #66 from boostorg/65-move-redisresp3async_read-to-redis-namespace
Moves read functions from resp3:: to redis::.
2023-02-12 07:44:33 +01:00
Marcelo Zimbres
071f9a93aa Moves read functions from resp3:: to redis::. 2023-02-11 19:17:05 +01:00
Marcelo
5a6ca14a67 Merge pull request #64 from boostorg/40-improve-support-to-redis-error-messages
Uses system::result to implement per request error handling.
2023-02-11 14:56:12 +01:00
Marcelo Zimbres
a5c86107f8 Uses system::result to implement per request error handling. 2023-02-11 11:53:44 +01:00
Marcelo
3a4445022e Merge pull request #63 from boostorg/62-let-the-implementation-call-adaptresp-automatically
async_exec accepts response now instead of adapter.
2023-02-05 10:08:05 +01:00
Marcelo Zimbres
bfb26f2602 async_exec accepts response now instead of adapter. 2023-02-04 20:43:36 +01:00
Marcelo
7e70cb4ad7 Merge pull request #61 from boostorg/60-we-need-response-typedefs
Adds response typedefs.
2023-02-04 10:29:22 +01:00
Marcelo Zimbres
886561409a Adds response typedefs. 2023-02-04 10:03:08 +01:00
Marcelo
0c5ff09685 Merge pull request #59 from boostorg/42-the-names-from_bulk-and-to_bulk-are-too-generic-for-adl-customization-points
Prefix to_ and from_bulk with boost_redis_ (boost review).
2023-02-02 08:33:44 +01:00
Marcelo Zimbres
4b07b6d516 Prefix to_ and from_bulk with boost_redis_ (boost review). 2023-02-01 22:48:33 +01:00
Marcelo
c1ce8358c7 Merge pull request #57 from boostorg/56-remove-memory_resource-usage
Removes memory_resource.
2023-01-29 21:14:37 +01:00
Marcelo Zimbres
13e16b7a60 Removes memory_resource. 2023-01-29 20:21:30 +01:00
Marcelo
e11502e0df Merge pull request #55 from boostorg/54-rename-aedis-to-boost-redis
Renames Aedis to Boost.Redis.
2023-01-28 21:43:57 +01:00
Marcelo Zimbres
b2344384cf Renames Aedis to Boost.Redis. 2023-01-28 17:57:35 +01:00
Marcelo Zimbres
56c0b28003 Fixes issue 50 and 44. 2023-01-28 09:35:36 +01:00
Marcelo Zimbres
c88fcfb9ed Adds more doc to node class. 2023-01-08 21:51:41 +01:00
Marcelo
a56bf982ab Merge pull request #41 from Hailios/remove_duplicate_cmake
remove duplicate line in cmake
2023-01-07 20:11:50 +01:00
Jakob Lövhall
5d0ed0e986 remove duplicate line in cmake 2023-01-07 16:58:50 +01:00
Marcelo Zimbres
15deaa637d Doc improvements. 2023-01-07 00:14:29 +01:00
Marcelo Zimbres
bb8ff90351 Fixes issue 39. 2023-01-06 17:38:10 +01:00
Marcelo Zimbres
7d4902369a Doc improvements and replaces async_main to co_main. 2023-01-05 23:37:55 +01:00
Marcelo Zimbres
607ca17a89 Improvements in the documentation. 2023-01-04 22:51:53 +01:00
Marcelo Zimbres
3849ba42fd Changes:
- Fix include header order.
- Removes default completion token where it is not needed.
- Replaces yield with BOOST_ macros.
2023-01-02 23:51:50 +01:00
Marcelo Zimbres
56bcdb7914 Improvements in the docs. 2022-12-31 15:58:31 +01:00
Marcelo Zimbres
73ad66eb93 Adds example that does not user awaitable ops. 2022-12-30 18:13:09 +01:00
Marcelo Zimbres
9cf00d6a23 Adds cpp17 async example. 2022-12-30 00:04:41 +01:00
Marcelo Zimbres
a00c9e7439 Doc improvements. 2022-12-27 21:21:43 +01:00
Marcelo Zimbres
0520791100 Renames request flag. 2022-12-27 18:46:27 +01:00
Marcelo Zimbres
14b376e36e Fixes cancelation of async_exec (2). 2022-12-26 11:02:13 +01:00
Marcelo Zimbres
4f9dcc7dc5 Fixes async_exec terminal cancellation. 2022-12-25 20:01:35 +01:00
Marcelo Zimbres
ad5dd8c30b Refactors the parser so it is not header-only. 2022-12-22 21:42:41 +01:00
Marcelo Zimbres
842f864689 Using doxygen-awesome css. 2022-12-19 21:40:44 +01:00
Marcelo Zimbres
63f9b74502 Improves the stress test. 2022-12-18 09:20:29 +01:00
Marcelo Zimbres
801f60a026 Readme improvements. 2022-12-17 22:59:44 +01:00
Marcelo Zimbres
c37fcb641c Documentation improvements. 2022-12-17 16:59:06 +01:00
Marcelo Zimbres
48c3f37168 Test improvements and bugfix in send-retry. 2022-12-11 22:19:37 +01:00
Marcelo Zimbres
3c63911802 Removes some boost dependencies. 2022-12-10 19:42:51 +01:00
Marcelo Zimbres
1645881a44 Doc improvements and add guarded_op class. 2022-12-07 22:32:49 +01:00
Marcelo Zimbres
730e06c38d Adds bigobj and other vs flags. 2022-12-04 20:36:17 +01:00
Marcelo Zimbres
cf3a79737d Removes mingw from windows builds. 2022-12-04 16:15:12 +01:00
Marcelo Zimbres
edb384c843 Build fix on windows. 2022-12-04 15:13:14 +01:00
Marcelo Zimbres
f745faddf8 Replaces boost::string_view with std::string_view. 2022-12-04 13:53:44 +01:00
Marcelo Zimbres
927117568e Build fix. 2022-12-04 13:53:44 +01:00
Marcelo Zimbres
1e7c176f92 Removes dependency on Boost.Hana. 2022-12-03 22:29:04 +01:00
Marcelo Zimbres
449b5f7e7c First steps with windows CI. 2022-12-03 18:14:10 +01:00
Marcelo Zimbres
75f91f3b11 v1.3.1 and build fixes. 2022-12-03 14:34:15 +01:00
Marcelo Zimbres
b9a23568e3 Many improvements in the examples. 2022-12-02 22:58:39 +01:00
Marcelo Zimbres
4ac2509afa Improvements in the docs and examples. 2022-11-27 21:59:02 +01:00
Marcelo Zimbres
e9dab97992 v1.3.0 2022-11-26 22:22:56 +01:00
Marcelo Zimbres
2e8cad858d Improvements in the examples. 2022-11-26 19:42:39 +01:00
Marcelo Zimbres
5a6e426028 Build fix and improvements in the examples. 2022-11-22 22:57:33 +01:00
Marcelo Zimbres
c55978a379 CI fix and improvements in the examples. 2022-11-21 23:41:41 +01:00
Marcelo Zimbres
6f51397e49 Build fix. 2022-11-20 14:06:07 +01:00
Marcelo Zimbres
6b9ba6b2d9 Adds connection typedef and improves docs. 2022-11-19 23:53:26 +01:00
Marcelo Zimbres
d29c03cb38 Changes:
* Uses pmr::string for the connection read and write buffer.
* Improvements in the examples.
2022-11-18 23:15:47 +01:00
Marcelo Zimbres
34cfbaa22f Removes healthy checks from the connection class. 2022-11-13 21:22:50 +01:00
Marcelo Zimbres
c9354fe320 Test improvements. 2022-11-13 18:39:28 +01:00
Marcelo Zimbres
bb555cb509 Remove built-in resolve and connect operation in async_run. 2022-11-13 00:10:26 +01:00
Marcelo Zimbres
5b209afa1d Removes endpoint class. 2022-11-09 23:05:52 +01:00
Marcelo Zimbres
3f5491654d Removes built-in HELLO from the connection. 2022-11-08 00:04:52 +01:00
Marcelo Zimbres
2bdc25752f Simplifications in the low-level tests. 2022-11-06 22:40:00 +01:00
Marcelo Zimbres
faafce1c64 Adds tls test. 2022-11-06 19:12:36 +01:00
134 changed files with 12005 additions and 7822 deletions

View File

@@ -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

View File

@@ -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: |

View File

@@ -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
#=======================================================================

View File

@@ -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"] }
]
}

915
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -0,0 +1,4 @@
$ npm install
$ node echo_server_over_redis.js

View File

@@ -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();

View File

@@ -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

View File

@@ -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=""/>

View File

@@ -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;
}

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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 &#160;&#160;
<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>

View File

@@ -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> &#160; <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
View 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>

View File

@@ -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)

View File

@@ -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
View 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;
}
}

View 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;
}
}

View 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)

View 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)

View 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
View 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)

View 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
View 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)

View 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)

View 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)

View 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)

View 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)

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
View 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
View File

@@ -0,0 +1,9 @@
syntax = "proto2";
package tutorial;
message person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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)

View 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_;
};
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -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

View 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;
}

View 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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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