mirror of
https://github.com/boostorg/redis.git
synced 2026-01-26 06:52:09 +00:00
Compare commits
306 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
562075230f | ||
|
|
5dc677c6d8 | ||
|
|
395a167d48 | ||
|
|
f93f3cab58 | ||
|
|
df68fb0235 | ||
|
|
15e6883bc1 | ||
|
|
3816d1d358 | ||
|
|
bb15c70723 | ||
|
|
297b7f15eb | ||
|
|
ec6e99d99a | ||
|
|
8dc6db069b | ||
|
|
bac27c1770 | ||
|
|
feaaedc6c0 | ||
|
|
000ebddf44 | ||
|
|
268ea2c10f | ||
|
|
d8b67f6e23 | ||
|
|
ce1fa6a683 | ||
|
|
b8ede6ccb7 | ||
|
|
6dce1a9226 | ||
|
|
8566745d83 | ||
|
|
0b4906fcba | ||
|
|
2c8bb92071 | ||
|
|
770e224917 | ||
|
|
4fb2b20954 | ||
|
|
c01a57b6cb | ||
|
|
ea0b333c4d | ||
|
|
ba82c6cd84 | ||
|
|
4c298ddc6b | ||
|
|
690002b2f1 | ||
|
|
12899da4db | ||
|
|
b2c19df113 | ||
|
|
61f9a29ebc | ||
|
|
dc9b333f1e | ||
|
|
dac7bea54f | ||
|
|
da84321378 | ||
|
|
3295459700 | ||
|
|
dff8833fe3 | ||
|
|
3a03a43c06 | ||
|
|
b614826bb4 | ||
|
|
4e30a9d53d | ||
|
|
85ba41ae5a | ||
|
|
fd82204ba9 | ||
|
|
084e95cbc7 | ||
|
|
798f193f14 | ||
|
|
a23a3db9ac | ||
|
|
bca6511333 | ||
|
|
e2d642f34c | ||
|
|
d8607af669 | ||
|
|
223a9aa74b | ||
|
|
75433cd028 | ||
|
|
552c6cf6e4 | ||
|
|
16b9347c51 | ||
|
|
03558b1466 | ||
|
|
f8165bcb6f | ||
|
|
8aad27269c | ||
|
|
f11622e746 | ||
|
|
8249360a52 | ||
|
|
d00f26d3da | ||
|
|
480ec13119 | ||
|
|
4cc3fc59a1 | ||
|
|
11807c82b7 | ||
|
|
24a215d78b | ||
|
|
b7abe20703 | ||
|
|
225095944c | ||
|
|
a31d797e43 | ||
|
|
cca8d5d6dc | ||
|
|
6c5bee6920 | ||
|
|
c4714d0037 | ||
|
|
38bf2395af | ||
|
|
7511d6b4d8 | ||
|
|
ddc2815fe5 | ||
|
|
de6f5de655 | ||
|
|
8d454ada0e | ||
|
|
ebac88f2ca | ||
|
|
d26ecb65ca | ||
|
|
c57f97b8c1 | ||
|
|
37ab1e7387 | ||
|
|
54d448cad4 | ||
|
|
97428dedb3 | ||
|
|
83802f217a | ||
|
|
08140f9186 | ||
|
|
3ddb017edb | ||
|
|
20328cd423 | ||
|
|
6577ddbaab | ||
|
|
217d2bd87b | ||
|
|
f96dd22153 | ||
|
|
f1fd0cfa8c | ||
|
|
8728914109 | ||
|
|
e0041ac7ae | ||
|
|
317a185eb0 | ||
|
|
aa81200a8f | ||
|
|
55fc0e861c | ||
|
|
04271855b0 | ||
|
|
700e0c823e | ||
|
|
63c6465a4a | ||
|
|
c86422cf50 | ||
|
|
0168ed5faf | ||
|
|
7bffa252f4 | ||
|
|
0bb65599c4 | ||
|
|
edd538944f | ||
|
|
42880e788b | ||
|
|
bcc3917174 | ||
|
|
b08dd63192 | ||
|
|
76b6106caa | ||
|
|
ab68e8a31d | ||
|
|
2673557ce5 | ||
|
|
2a302dcb65 | ||
|
|
ffc4230368 | ||
|
|
59b5d35672 | ||
|
|
835a1decf4 | ||
|
|
3fb018ccc6 | ||
|
|
1fe4a87287 | ||
|
|
70cdff41e0 | ||
|
|
2edd9f3d87 | ||
|
|
fa4181b197 | ||
|
|
9e2cd8855e | ||
|
|
bef70870cd | ||
|
|
9885439845 | ||
|
|
b5a9162efb | ||
|
|
6ca0bcc945 | ||
|
|
efd0a0379a | ||
|
|
97153abc3c | ||
|
|
f4710941d3 | ||
|
|
f8ff3034f4 | ||
|
|
561eb5dccb | ||
|
|
95d609b75c | ||
|
|
d5f9e702d7 | ||
|
|
5add83b73c | ||
|
|
200974d9be | ||
|
|
649c84d7d0 | ||
|
|
240cce4b09 | ||
|
|
b140216f0d | ||
|
|
4f0d9de393 | ||
|
|
888bb476d7 | ||
|
|
eae37ace0b | ||
|
|
0c3ed1afee | ||
|
|
0f5e8e3d1f | ||
|
|
963b228e02 | ||
|
|
bddf47d626 | ||
|
|
b3b8dfc243 | ||
|
|
e013d846b2 | ||
|
|
d2ba54a7a6 | ||
|
|
250e24d5fb | ||
|
|
9dcccca11e | ||
|
|
8af1c9f19c | ||
|
|
b058cc0c02 | ||
|
|
df3f2b8ca5 | ||
|
|
8e4928347c | ||
|
|
33461d54c8 | ||
|
|
5328cdff9a | ||
|
|
452589d4e7 | ||
|
|
4036df9255 | ||
|
|
9f2df4d052 | ||
|
|
1571afbd88 | ||
|
|
b43e6dfb68 | ||
|
|
ce9cb04168 | ||
|
|
77fe3a0f5f | ||
|
|
40dfacb0b7 | ||
|
|
6d859c57f8 | ||
|
|
9e43541a5e | ||
|
|
97cb5b5b25 | ||
|
|
a40c9fe35f | ||
|
|
a411cc50fc | ||
|
|
5893f0913e | ||
|
|
dea7712a29 | ||
|
|
56479b88eb | ||
|
|
dfeb3bbfcf | ||
|
|
7464851e9e | ||
|
|
226c2b228c | ||
|
|
fee892b6ad | ||
|
|
74e0a6ca23 | ||
|
|
ebef2f9e23 | ||
|
|
485bdc316b | ||
|
|
36fb83e1d6 | ||
|
|
3753c27dcf | ||
|
|
091cad6ee7 | ||
|
|
1e98c04603 | ||
|
|
4858c078f9 | ||
|
|
3dff0b78de | ||
|
|
7300f1498b | ||
|
|
f6fc45d8ba | ||
|
|
5eb88b5042 | ||
|
|
f7d2f3ab28 | ||
|
|
f62ad6a8bf | ||
|
|
1efcf7b7d8 | ||
|
|
29166a2cf0 | ||
|
|
215fd7ea73 | ||
|
|
9b8ca4dbc8 | ||
|
|
4075dc380d | ||
|
|
161cd848f8 | ||
|
|
7c7eed4a53 | ||
|
|
e70b00e976 | ||
|
|
52d7b95cf8 | ||
|
|
641032fa9a | ||
|
|
2a2a13c4dc | ||
|
|
76741d8466 | ||
|
|
0f79214d37 | ||
|
|
de476169ae | ||
|
|
d1bf3a91be | ||
|
|
4be6e6cc1e | ||
|
|
394bdf5b5e | ||
|
|
a4745a1a5d | ||
|
|
b99da962d1 | ||
|
|
172de6235c | ||
|
|
5e99a58685 | ||
|
|
b952a2d2d8 | ||
|
|
16d1f8df24 | ||
|
|
82b804e397 | ||
|
|
22530724c6 | ||
|
|
610aef5c5e | ||
|
|
87a03a0b6b | ||
|
|
0e3de3688e | ||
|
|
980c39084f | ||
|
|
abd339d0f2 | ||
|
|
a86a18c969 | ||
|
|
f0deda42a4 | ||
|
|
d1e7ec6f03 | ||
|
|
0e8e31f310 | ||
|
|
ba86c9fb05 | ||
|
|
ccef30c8ac | ||
|
|
ce77524d6f | ||
|
|
54a9fddc40 | ||
|
|
ad3127d1ca | ||
|
|
0363353154 | ||
|
|
b453fc63c9 | ||
|
|
1a47dece35 | ||
|
|
048a711e51 | ||
|
|
a940f7f4bf | ||
|
|
fd0cae92ee | ||
|
|
a5376bc05f | ||
|
|
98580eb0ea | ||
|
|
6ed2d96b07 | ||
|
|
245fdb55b6 | ||
|
|
e6443cbe26 | ||
|
|
2101def89f | ||
|
|
ddfe9defc5 | ||
|
|
cd28ff285f | ||
|
|
c4bd338e79 | ||
|
|
bb28f34ecc | ||
|
|
aa6251b96c | ||
|
|
b12140ae8a | ||
|
|
d845434869 | ||
|
|
5e86fb9d03 | ||
|
|
dc80c04347 | ||
|
|
20d6c67f3d | ||
|
|
8c4816fc8d | ||
|
|
18aa9ff726 | ||
|
|
b68e413351 | ||
|
|
27b3bb89fb | ||
|
|
70cd9b0ffd | ||
|
|
43a08e834c | ||
|
|
19f03e2f41 | ||
|
|
0fff6496fb | ||
|
|
57ba376544 | ||
|
|
46b86c20e5 | ||
|
|
577c6d35fb | ||
|
|
42ef8a3b06 | ||
|
|
f9af8e585b | ||
|
|
350ae19936 | ||
|
|
57f2644903 | ||
|
|
0ac0c3cf23 | ||
|
|
5c23299a8a | ||
|
|
379da7a340 | ||
|
|
bcf13c03c0 | ||
|
|
be79f58808 | ||
|
|
2c96b07623 | ||
|
|
577d32f0e2 | ||
|
|
5f93257502 | ||
|
|
409ee61522 | ||
|
|
5aa034d6a5 | ||
|
|
0ff0e537ce | ||
|
|
320ee6b3cc | ||
|
|
5061e5a7a6 | ||
|
|
0bda78dd9c | ||
|
|
8704e7756f | ||
|
|
8207ef7e8a | ||
|
|
4712872daf | ||
|
|
06752ac664 | ||
|
|
b3be596f77 | ||
|
|
f92290be16 | ||
|
|
331010c240 | ||
|
|
11b697c572 | ||
|
|
1218b2ef01 | ||
|
|
8cc142d55b | ||
|
|
0723922ac1 | ||
|
|
54ba34c70c | ||
|
|
efe44fbd45 | ||
|
|
268b59afbc | ||
|
|
6b6272694c | ||
|
|
f36f3b7b5e | ||
|
|
b4fef73b87 | ||
|
|
c27b3b6b85 | ||
|
|
feb383d7e2 | ||
|
|
ee71ff885d | ||
|
|
80a80f44ff | ||
|
|
053ce3aea9 | ||
|
|
db27e3cc60 | ||
|
|
449ba0dd73 | ||
|
|
8728f58981 | ||
|
|
2573f39aa1 | ||
|
|
f460bf43a5 | ||
|
|
85d0a30dad | ||
|
|
b7f22d8c61 | ||
|
|
b04cc12d64 | ||
|
|
317690ee91 | ||
|
|
fe3cc2efb0 |
172
.clang-tidy
Normal file
172
.clang-tidy
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
# Enable ALL the things! Except not really
|
||||
# misc-non-private-member-variables-in-classes: the options don't do anything
|
||||
Checks: "*,\
|
||||
-readability-*,\
|
||||
-readability-identifier-length,\
|
||||
-google-readability-todo,\
|
||||
-google-readability-namespace-comments,\
|
||||
-google-readability-braces-around-statements,\
|
||||
-hicpp-braces-around-statements,\
|
||||
-hicpp-named-parameter,\
|
||||
-hicpp-avoid-goto,\
|
||||
-google-build-using-namespace,\
|
||||
-altera-*,\
|
||||
-fuchsia-*,\
|
||||
fuchsia-multiple-inheritance,\
|
||||
-llvm-namespace-comment,\
|
||||
-llvm-header-guard,\
|
||||
-llvm-include-order,\
|
||||
-llvmlibc-*,\
|
||||
-misc-non-private-member-variables-in-classes,\
|
||||
-bugprone-use-after-move,\
|
||||
-hicpp-invalid-access-moved,\
|
||||
-misc-no-recursion,\
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,\
|
||||
-cppcoreguidelines-avoid-magic-numbers,\
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,\
|
||||
-cppcoreguidelines-interfaces-global-init,\
|
||||
-cppcoreguidelines-macro-usage,\
|
||||
-cppcoreguidelines-avoid-goto,\
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes"
|
||||
WarningsAsErrors: ''
|
||||
CheckOptions:
|
||||
- key: 'bugprone-argument-comment.StrictMode'
|
||||
value: 'true'
|
||||
# Prefer using enum classes with 2 values for parameters instead of bools
|
||||
- key: 'bugprone-argument-comment.CommentBoolLiterals'
|
||||
value: 'true'
|
||||
- key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts'
|
||||
value: 'true'
|
||||
- key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression'
|
||||
value: 'true'
|
||||
- key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison'
|
||||
value: 'true'
|
||||
- key: 'readability-simplify-boolean-expr.ChainedConditionalReturn'
|
||||
value: 'true'
|
||||
- key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment'
|
||||
value: 'true'
|
||||
- key: 'readability-uniqueptr-delete-release.PreferResetCall'
|
||||
value: 'true'
|
||||
- key: 'cppcoreguidelines-init-variables.MathHeader'
|
||||
value: '<cmath>'
|
||||
- key: 'cppcoreguidelines-narrowing-conversions.PedanticMode'
|
||||
value: 'true'
|
||||
- key: 'readability-else-after-return.WarnOnUnfixable'
|
||||
value: 'true'
|
||||
- key: 'readability-else-after-return.WarnOnConditionVariables'
|
||||
value: 'true'
|
||||
- key: 'readability-inconsistent-declaration-parameter-name.Strict'
|
||||
value: 'true'
|
||||
- key: 'readability-qualified-auto.AddConstToQualified'
|
||||
value: 'true'
|
||||
- key: 'readability-redundant-access-specifiers.CheckFirstDeclaration'
|
||||
value: 'true'
|
||||
# These seem to be the most common identifier styles
|
||||
- key: 'readability-identifier-naming.AbstractClassCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantPointerParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprFunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.EnumCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.EnumConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.FunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalConstantPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalFunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.InlineNamespaceCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalConstantPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.MacroDefinitionCase'
|
||||
value: 'UPPER_CASE'
|
||||
- key: 'readability-identifier-naming.MemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.MethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.NamespaceCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ParameterPackCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PointerParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PrivateMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PrivateMemberPrefix'
|
||||
value: 'm_'
|
||||
- key: 'readability-identifier-naming.PrivateMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ProtectedMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ProtectedMemberPrefix'
|
||||
value: 'm_'
|
||||
- key: 'readability-identifier-naming.ProtectedMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PublicMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PublicMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ScopedEnumConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StaticConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StaticVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StructCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.TemplateTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.TypeAliasCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TypedefCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TypeTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.UnionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ValueTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.VariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.VirtualMethodCase'
|
||||
value: 'lower_case'
|
||||
...
|
||||
22
.codecov.yml
Normal file
22
.codecov.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
codecov:
|
||||
max_report_age: off
|
||||
require_ci_to_pass: yes
|
||||
notify:
|
||||
after_n_builds: 1
|
||||
wait_for_ci: yes
|
||||
|
||||
ignore:
|
||||
- "benchmarks/cpp/asio/*"
|
||||
- "examples/*"
|
||||
- "tests/*"
|
||||
- "/usr/*"
|
||||
- "**/boost/*"
|
||||
|
||||
parsers:
|
||||
gcov:
|
||||
branch_detection:
|
||||
conditional: no
|
||||
loop: no
|
||||
method: no
|
||||
macro: no
|
||||
|
||||
46
.github/workflows/ci.yml
vendored
Normal file
46
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
posix:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
strategy:
|
||||
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' }
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CXXFLAGS: -g -O0 -std=${{matrix.cxxstd}} -Wall -Wextra
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install CMake
|
||||
run: sudo apt-get -y install cmake
|
||||
- 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
|
||||
id: install-boost
|
||||
with:
|
||||
boost_version: 1.79.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}}"
|
||||
- name: Build
|
||||
run: make
|
||||
- name: Check
|
||||
run: ctest --output-on-failure
|
||||
47
.github/workflows/coverage.yml
vendored
Normal file
47
.github/workflows/coverage.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
posix:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CXX: g++-11
|
||||
CXXFLAGS: -g -O0 -std=c++20 --coverage -fkeep-inline-functions -fkeep-static-functions
|
||||
LDFLAGS: --coverage
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install CMake
|
||||
run: sudo apt-get -y install cmake
|
||||
- name: Install lcov
|
||||
run: sudo apt-get -y install lcov
|
||||
- name: Install compiler
|
||||
run: sudo apt-get -y install g++-11
|
||||
- name: Install Redis
|
||||
run: sudo apt-get -y install redis-server
|
||||
- name: Install boost
|
||||
uses: MarkusJx/install-boost@v2.3.0
|
||||
id: install-boost
|
||||
with:
|
||||
boost_version: 1.79.0
|
||||
platform_version: 22.04
|
||||
- name: Run CMake
|
||||
run: |
|
||||
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake --preset coverage .
|
||||
- name: Build
|
||||
run: cmake --build --preset coverage
|
||||
- name: Test
|
||||
run: ctest --preset coverage
|
||||
- name: Make the coverage file
|
||||
run: cmake --build --preset coverage --target coverage
|
||||
- name: Upload to codecov
|
||||
run: |
|
||||
bash <(curl -s https://codecov.io/bash) -f ./build/coverage/coverage.info || echo "Codecov did not collect coverage reports"
|
||||
|
||||
4
BUILD_STATUS.md
Normal file
4
BUILD_STATUS.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Branch | GH Actions | codecov.io |
|
||||
:-------------: | ---------- | ---------- |
|
||||
[`master`](https://github.com/mzimbres/aedis/tree/master) | [](https://github.com/mzimbres/aedis/actions/workflows/ci.yml) | [](https://codecov.io/gh/mzimbres/aedis/branch/master)
|
||||
|
||||
207
CMakeLists.txt
Normal file
207
CMakeLists.txt
Normal file
@@ -0,0 +1,207 @@
|
||||
# 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)
|
||||
|
||||
project(
|
||||
Aedis
|
||||
VERSION 1.2.0
|
||||
DESCRIPTION "A redis client designed for performance and scalability"
|
||||
HOMEPAGE_URL "https://mzimbres.github.io/aedis"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
add_library(aedis INTERFACE)
|
||||
target_include_directories(aedis INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
target_link_libraries(aedis
|
||||
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)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"${PROJECT_BINARY_DIR}/AedisConfigVersion.cmake"
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
|
||||
find_package(Boost 1.79 REQUIRED)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
enable_testing()
|
||||
include_directories(include)
|
||||
|
||||
# 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)
|
||||
|
||||
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)
|
||||
|
||||
target_link_libraries(intro_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
target_link_libraries(test_conn_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
|
||||
# Tests
|
||||
#=======================================================================
|
||||
|
||||
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)
|
||||
|
||||
# Install
|
||||
#=======================================================================
|
||||
|
||||
install(TARGETS aedis
|
||||
EXPORT aedis
|
||||
PUBLIC_HEADER DESTINATION include COMPONENT Development
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
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)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# Coverage
|
||||
#=======================================================================
|
||||
|
||||
set(
|
||||
COVERAGE_TRACE_COMMAND
|
||||
lcov --capture
|
||||
-output-file "${PROJECT_BINARY_DIR}/coverage.info"
|
||||
--directory "${PROJECT_BINARY_DIR}"
|
||||
--include "${PROJECT_SOURCE_DIR}/include/*"
|
||||
)
|
||||
|
||||
set(
|
||||
COVERAGE_HTML_COMMAND
|
||||
genhtml --legend -f -q
|
||||
"${PROJECT_BINARY_DIR}/coverage.info"
|
||||
--prefix "${PROJECT_SOURCE_DIR}"
|
||||
--output-directory "${PROJECT_BINARY_DIR}/coverage_html"
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
coverage
|
||||
COMMAND ${COVERAGE_TRACE_COMMAND}
|
||||
COMMAND ${COVERAGE_HTML_COMMAND}
|
||||
COMMENT "Generating coverage report"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Distribution
|
||||
#=======================================================================
|
||||
|
||||
include(CPack)
|
||||
|
||||
# TODO
|
||||
#=======================================================================
|
||||
|
||||
#.PHONY: bench
|
||||
#bench:
|
||||
# pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex
|
||||
# pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex
|
||||
# pdftoppm {input.pdf} {output.file} -png
|
||||
|
||||
86
CMakePresets.json
Normal file
86
CMakePresets.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"version": 2,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 14,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "cmake-pedantic",
|
||||
"hidden": true,
|
||||
"warnings": {
|
||||
"dev": true,
|
||||
"deprecated": true,
|
||||
"uninitialized": true,
|
||||
"unusedCli": true,
|
||||
"systemVars": false
|
||||
},
|
||||
"errors": {
|
||||
"dev": true,
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "coverage",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/coverage",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Coverage",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra",
|
||||
"CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/coverage",
|
||||
"COVERAGE_HTML_COMMAND": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dev",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/dev",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/dev",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/dev/doc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang-tidy",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["dev"],
|
||||
"binaryDir": "${sourceDir}/build/clang-tidy",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=${sourceDir}/include/*",
|
||||
"CMAKE_CXX_STANDARD": "20"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{ "name": "coverage", "configurePreset": "coverage" },
|
||||
{ "name": "dev", "configurePreset": "dev" },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy" }
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "test",
|
||||
"hidden": true,
|
||||
"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"] }
|
||||
]
|
||||
}
|
||||
373
LICENSE
373
LICENSE
@@ -1,373 +0,0 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
23
LICENSE.txt
Normal file
23
LICENSE.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
103
Makefile.am
103
Makefile.am
@@ -1,103 +0,0 @@
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
AM_COLOR_TESTS = always
|
||||
DISTCHECK_CONFIGURE_FLAGS = CPPFLAGS="$(BOOST_CPPFLAGS) $(CPPFLAGS)" LDFLAGS="$(BOOST_LDFLAGS)"
|
||||
|
||||
AM_CPPFLAGS =
|
||||
AM_CPPFLAGS += $(BOOST_CPPFLAGS)
|
||||
|
||||
AM_LDFLAGS =
|
||||
AM_LDFLAGS += -pthread
|
||||
|
||||
check_PROGRAMS =
|
||||
check_PROGRAMS += intro
|
||||
check_PROGRAMS += sets
|
||||
check_PROGRAMS += hashes
|
||||
check_PROGRAMS += serialization
|
||||
check_PROGRAMS += multipurpose_response
|
||||
check_PROGRAMS += lists
|
||||
check_PROGRAMS += key_expiration
|
||||
check_PROGRAMS += response_adapter
|
||||
check_PROGRAMS += sync
|
||||
check_PROGRAMS += multipurpose_client
|
||||
check_PROGRAMS += test_offline
|
||||
check_PROGRAMS += test_online
|
||||
check_PROGRAMS += transaction
|
||||
|
||||
EXTRA_PROGRAMS =
|
||||
EXTRA_PROGRAMS += subscriber
|
||||
EXTRA_PROGRAMS += echo_server
|
||||
EXTRA_PROGRAMS += chat_room
|
||||
EXTRA_PROGRAMS += commands
|
||||
|
||||
CLEANFILES =
|
||||
CLEANFILES += $(EXTRA_PROGRAMS)
|
||||
|
||||
.PHONY: all
|
||||
all: $(check_PROGRAMS) $(EXTRA_PROGRAMS)
|
||||
|
||||
intro_SOURCES = $(top_srcdir)/examples/intro.cpp
|
||||
sets_SOURCES = $(top_srcdir)/examples/sets.cpp
|
||||
hashes_SOURCES = $(top_srcdir)/examples/hashes.cpp
|
||||
serialization_SOURCES = $(top_srcdir)/examples/serialization.cpp
|
||||
multipurpose_response_SOURCES = $(top_srcdir)/examples/multipurpose_response.cpp
|
||||
lists_SOURCES = $(top_srcdir)/examples/lists.cpp
|
||||
key_expiration_SOURCES = $(top_srcdir)/examples/key_expiration.cpp
|
||||
response_adapter_SOURCES = $(top_srcdir)/examples/response_adapter.cpp
|
||||
sync_SOURCES = $(top_srcdir)/examples/sync.cpp
|
||||
multipurpose_client_SOURCES = $(top_srcdir)/examples/multipurpose_client.cpp
|
||||
commands_SOURCES = $(top_srcdir)/tools/commands.cpp
|
||||
test_offline_SOURCES = $(top_srcdir)/tests/offline.cpp
|
||||
test_online_SOURCES = $(top_srcdir)/tests/online.cpp
|
||||
subscriber_SOURCES = $(top_srcdir)/examples/subscriber.cpp
|
||||
echo_server_SOURCES = $(top_srcdir)/examples/echo_server.cpp
|
||||
chat_room_SOURCES = $(top_srcdir)/examples/chat_room.cpp
|
||||
transaction_SOURCES = $(top_srcdir)/examples/transaction.cpp
|
||||
|
||||
nobase_include_HEADERS =\
|
||||
$(top_srcdir)/aedis/config.hpp\
|
||||
$(top_srcdir)/aedis/src.hpp\
|
||||
$(top_srcdir)/aedis/redis/command.hpp\
|
||||
$(top_srcdir)/aedis/sentinel/command.hpp\
|
||||
$(top_srcdir)/aedis/aedis.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapter/detail/adapters.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapter/error.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapt.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/composer.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/read_ops.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/parser.hpp\
|
||||
$(top_srcdir)/aedis/resp3/serializer.hpp\
|
||||
$(top_srcdir)/aedis/resp3/response_traits.hpp\
|
||||
$(top_srcdir)/aedis/resp3/node.hpp\
|
||||
$(top_srcdir)/aedis/resp3/error.hpp\
|
||||
$(top_srcdir)/aedis/resp3/type.hpp\
|
||||
$(top_srcdir)/aedis/resp3/read.hpp\
|
||||
$(top_srcdir)/aedis/redis/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/sentinel/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/resp3/detail/impl/parser.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/type.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/node.ipp\
|
||||
$(top_srcdir)/aedis/redis/experimental/client.hpp\
|
||||
$(top_srcdir)/aedis/redis/experimental/impl/client.ipp
|
||||
|
||||
nobase_noinst_HEADERS =\
|
||||
$(top_srcdir)/examples/lib/net_utils.hpp\
|
||||
$(top_srcdir)/examples/lib/user_session.hpp\
|
||||
$(top_srcdir)/tests/check.hpp\
|
||||
$(top_srcdir)/tests/test_stream.hpp
|
||||
|
||||
TESTS = $(check_PROGRAMS)
|
||||
|
||||
EXTRA_DIST =
|
||||
EXTRA_DIST += $(top_srcdir)/README.md
|
||||
EXTRA_DIST += $(top_srcdir)/doc/DoxygenLayout.xml
|
||||
EXTRA_DIST += $(top_srcdir)/doc/aedis.css
|
||||
EXTRA_DIST += $(top_srcdir)/doc/htmlfooter.html
|
||||
EXTRA_DIST += $(top_srcdir)/doc/htmlheader.html
|
||||
|
||||
.PHONY: doc
|
||||
doc:
|
||||
rm -rf ../aedis-gh-pages/*
|
||||
doxygen doc/Doxyfile
|
||||
|
||||
183
aedis/aedis.hpp
183
aedis/aedis.hpp
@@ -1,183 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
#include <aedis/sentinel/command.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/adapt.hpp>
|
||||
#include <aedis/resp3/error.hpp>
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
#include <aedis/redis/experimental/client.hpp>
|
||||
|
||||
/** \mainpage Documentation
|
||||
\tableofcontents
|
||||
|
||||
\section Overview
|
||||
|
||||
Aedis is low-level redis client library built on top of Boost.Asio
|
||||
that implements communication with a Redis server over its native
|
||||
protocol RESP3. It has first-class support for STL containers and
|
||||
C++ built in types among other things. You will be able to
|
||||
implement your own redis client or use a general purpose provided
|
||||
by the library. For more information about Redis see
|
||||
https://redis.io/
|
||||
|
||||
\section examples Examples
|
||||
|
||||
\b Basics: Focuses on small examples that show basic usage of
|
||||
the library.
|
||||
|
||||
- intro.cpp: A good starting point. Some commands are sent to the
|
||||
Redis server and the responses are printed to screen.
|
||||
|
||||
- transaction.cpp: Shows how to read the responses to a trasaction
|
||||
efficiently. See also https://redis.io/topics/transactions.
|
||||
|
||||
- multipurpose_response.cpp: Shows how to read any responses to
|
||||
Redis commands, including nested aggegagtes.
|
||||
|
||||
- subscriber.cpp: Shows how channel subscription works at a low
|
||||
level. See also https://redis.io/topics/pubsub.
|
||||
|
||||
- sync.cpp: Shows hot to use the Aedis synchronous api.
|
||||
|
||||
- key_expiration.cpp: Shows how to use \c std::optional to deal
|
||||
with keys that may have expired or do not exist.
|
||||
|
||||
\b STL \b Containers: Many of the Redis data structures can be
|
||||
directly translated in to STL containers, below you will find some
|
||||
example code. For a list of Redis data types see
|
||||
https://redis.io/topics/data-types.
|
||||
|
||||
- hashes.cpp: Shows how to read Redis hashes in a \c std::map, \c
|
||||
std::unordered_map and \c std::vector.
|
||||
|
||||
- lists.cpp: Shows how to read Redis lists in \c std::list,
|
||||
\c std::deque, \c std::vector. It also illustrates basic serialization.
|
||||
|
||||
- sets.cpp: Shows how to read Redis sets in a \c std::set, \c
|
||||
std::unordered_set and \c std::vector.
|
||||
|
||||
\b Customization \b points: Shows how de/serialize user types
|
||||
avoiding copies. This is particularly useful for low latency
|
||||
applications that want to avoid unneeded copies, for examples when
|
||||
storing json strings in Redis keys.
|
||||
|
||||
- serialization.cpp: Shows how to de/serialize your own
|
||||
non-aggregate data-structures.
|
||||
|
||||
- response_adapter.cpp: Customization point for users that want to
|
||||
de/serialize their own data-structures like containers for example.
|
||||
|
||||
\b Asynchronous \b servers: Contains some non-trivial examples
|
||||
servers that interact with users and Redis asynchronously over
|
||||
long lasting connections using a higher level API.
|
||||
|
||||
- multipurpose_client.cpp: Shows how to use and experimental high
|
||||
level redis client that keeps a long lasting connections to a
|
||||
redis server. This is the starting point for the next examples.
|
||||
|
||||
- echo_server.cpp: Shows the basic principles behind asynchronous
|
||||
communication with a database in an asynchronous server. In this
|
||||
case, the server is a proxy between the user and Redis.
|
||||
|
||||
- chat_room.cpp: Shows how to build a scalable chat room that
|
||||
scales to millions of users.
|
||||
|
||||
\section using-aedis Using Aedis
|
||||
|
||||
To install and use Aedis you will need
|
||||
|
||||
- Boost 1.78 or greater.
|
||||
- Unix Shell and Make.
|
||||
- Compiler with C++20 coroutine support e.g. GCC 10 or greater.
|
||||
- Redis server.
|
||||
|
||||
Some examples will also require interaction with
|
||||
|
||||
- redis-cli: used in one example.
|
||||
- Redis Sentinel Server: used in some examples.
|
||||
|
||||
\subsection Installation
|
||||
|
||||
Start by downloading and configuring the library
|
||||
|
||||
```
|
||||
# Download the libray on github.
|
||||
$ wget https://github.com/mzimbres/aedis/release-path # TODO
|
||||
|
||||
# Uncompress the tarball and cd into the dir
|
||||
$ tar -xzvf aedis-1.0.0.tar.gz && cd aedis-1.0.0
|
||||
|
||||
# Run configure with appropriate C++ flags and your boost
|
||||
# installation, for example # You may also have to use
|
||||
# -Wno-subobject-linkage on gcc.
|
||||
$ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall"\
|
||||
./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0 --with-boost-libdir=/opt/boost_1_78_0/lib
|
||||
|
||||
```
|
||||
|
||||
To install the library run
|
||||
|
||||
```
|
||||
# Install Aedis in the path specified in --prefix
|
||||
$ sudo make install
|
||||
|
||||
```
|
||||
|
||||
At this point you can start using Aedis. To build the examples and
|
||||
test you can also run
|
||||
|
||||
```
|
||||
# Build aedis examples.
|
||||
$ make examples
|
||||
|
||||
# Test aedis in your machine.
|
||||
$ make check
|
||||
```
|
||||
|
||||
Finally you will have to include the following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
```
|
||||
in exactly one source file in your applications.
|
||||
|
||||
Windows users can use aedis by either adding the project root
|
||||
directory to their include path or manually copying to another
|
||||
location.
|
||||
|
||||
\subsection Developers
|
||||
|
||||
To generate the build system run
|
||||
|
||||
```
|
||||
$ autoreconf -i
|
||||
```
|
||||
|
||||
After that you will have a config in the project dir that you can
|
||||
run as explained above, for example, to use a compiler other that
|
||||
the system compiler use
|
||||
|
||||
```
|
||||
CC=/opt/gcc-10.2.0/bin/gcc-10.2.0\
|
||||
CXX=/opt/gcc-10.2.0/bin/g++-10.2.0\
|
||||
CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage -Werror" ./configure ...
|
||||
```
|
||||
\section Referece
|
||||
|
||||
See \subpage any.
|
||||
*/
|
||||
|
||||
/** \defgroup any Reference
|
||||
*/
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// TODO: Remove this.
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace net = boost::asio;
|
||||
}
|
||||
|
||||
@@ -1,471 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace redis {
|
||||
|
||||
/** \brief Redis commands.
|
||||
* \ingroup any
|
||||
*
|
||||
* For a full list of commands see https://redis.io/commands.
|
||||
*
|
||||
* \remark The list of commands below are read from Redis with the
|
||||
* help of the command \c command.
|
||||
*/
|
||||
enum class command {
|
||||
/// https://redis.io/commands/acl
|
||||
acl,
|
||||
/// https://redis.io/commands/append
|
||||
append,
|
||||
/// https://redis.io/commands/asking
|
||||
asking,
|
||||
/// https://redis.io/commands/auth
|
||||
auth,
|
||||
/// https://redis.io/commands/bgrewriteaof
|
||||
bgrewriteaof,
|
||||
/// https://redis.io/commands/bgsave
|
||||
bgsave,
|
||||
/// https://redis.io/commands/bitcount
|
||||
bitcount,
|
||||
/// https://redis.io/commands/bitfield
|
||||
bitfield,
|
||||
/// https://redis.io/commands/bitfield_ro
|
||||
bitfield_ro,
|
||||
/// https://redis.io/commands/bitop
|
||||
bitop,
|
||||
/// https://redis.io/commands/bitpos
|
||||
bitpos,
|
||||
/// https://redis.io/commands/blpop
|
||||
blpop,
|
||||
/// https://redis.io/commands/brpop
|
||||
brpop,
|
||||
/// https://redis.io/commands/brpoplpush
|
||||
brpoplpush,
|
||||
/// https://redis.io/commands/bzpopmax
|
||||
bzpopmax,
|
||||
/// https://redis.io/commands/bzpopmin
|
||||
bzpopmin,
|
||||
/// https://redis.io/commands/client
|
||||
client,
|
||||
/// https://redis.io/commands/cluster
|
||||
cluster,
|
||||
/// https://redis.io/commands/command
|
||||
command,
|
||||
/// https://redis.io/commands/config
|
||||
config,
|
||||
/// https://redis.io/commands/dbsize
|
||||
dbsize,
|
||||
/// https://redis.io/commands/debug
|
||||
debug,
|
||||
/// https://redis.io/commands/decr
|
||||
decr,
|
||||
/// https://redis.io/commands/decrby
|
||||
decrby,
|
||||
/// https://redis.io/commands/del
|
||||
del,
|
||||
/// https://redis.io/commands/discard
|
||||
discard,
|
||||
/// https://redis.io/commands/dump
|
||||
dump,
|
||||
/// https://redis.io/commands/echo
|
||||
echo,
|
||||
/// https://redis.io/commands/eval
|
||||
eval,
|
||||
/// https://redis.io/commands/evalsha
|
||||
evalsha,
|
||||
/// https://redis.io/commands/exec
|
||||
exec,
|
||||
/// https://redis.io/commands/exists
|
||||
exists,
|
||||
/// https://redis.io/commands/expire
|
||||
expire,
|
||||
/// https://redis.io/commands/expireat
|
||||
expireat,
|
||||
/// https://redis.io/commands/flushall
|
||||
flushall,
|
||||
/// https://redis.io/commands/flushdb
|
||||
flushdb,
|
||||
/// https://redis.io/commands/geoadd
|
||||
geoadd,
|
||||
/// https://redis.io/commands/geodist
|
||||
geodist,
|
||||
/// https://redis.io/commands/geohash
|
||||
geohash,
|
||||
/// https://redis.io/commands/geopos
|
||||
geopos,
|
||||
/// https://redis.io/commands/georadius
|
||||
georadius,
|
||||
/// https://redis.io/commands/georadius_ro
|
||||
georadius_ro,
|
||||
/// https://redis.io/commands/georadiusbymember
|
||||
georadiusbymember,
|
||||
/// https://redis.io/commands/georadiusbymember_ro
|
||||
georadiusbymember_ro,
|
||||
/// https://redis.io/commands/get
|
||||
get,
|
||||
/// https://redis.io/commands/getbit
|
||||
getbit,
|
||||
/// https://redis.io/commands/getrange
|
||||
getrange,
|
||||
/// https://redis.io/commands/getset
|
||||
getset,
|
||||
/// https://redis.io/commands/hdel
|
||||
hdel,
|
||||
/// https://redis.io/commands/hello
|
||||
hello,
|
||||
/// https://redis.io/commands/hexists
|
||||
hexists,
|
||||
/// https://redis.io/commands/hget
|
||||
hget,
|
||||
/// https://redis.io/commands/hgetall
|
||||
hgetall,
|
||||
/// https://redis.io/commands/hincrby
|
||||
hincrby,
|
||||
/// https://redis.io/commands/hincrbyfloat
|
||||
hincrbyfloat,
|
||||
/// https://redis.io/commands/hkeys
|
||||
hkeys,
|
||||
/// https://redis.io/commands/hlen
|
||||
hlen,
|
||||
/// https://redis.io/commands/hmget
|
||||
hmget,
|
||||
/// https://redis.io/commands/hmset
|
||||
hmset,
|
||||
/// https://redis.io/commands/hscan
|
||||
hscan,
|
||||
/// https://redis.io/commands/hset
|
||||
hset,
|
||||
/// https://redis.io/commands/hsetnx
|
||||
hsetnx,
|
||||
/// https://redis.io/commands/hstrlen
|
||||
hstrlen,
|
||||
/// https://redis.io/commands/hvals
|
||||
hvals,
|
||||
/// https://redis.io/commands/incr
|
||||
incr,
|
||||
/// https://redis.io/commands/incrby
|
||||
incrby,
|
||||
/// https://redis.io/commands/incrbyfloat
|
||||
incrbyfloat,
|
||||
/// https://redis.io/commands/info
|
||||
info,
|
||||
/// https://redis.io/commands/keys
|
||||
keys,
|
||||
/// https://redis.io/commands/lastsave
|
||||
lastsave,
|
||||
/// https://redis.io/commands/latency
|
||||
latency,
|
||||
/// https://redis.io/commands/lindex
|
||||
lindex,
|
||||
/// https://redis.io/commands/linsert
|
||||
linsert,
|
||||
/// https://redis.io/commands/llen
|
||||
llen,
|
||||
/// https://redis.io/commands/lolwut
|
||||
lolwut,
|
||||
/// https://redis.io/commands/lpop
|
||||
lpop,
|
||||
/// https://redis.io/commands/lpos
|
||||
lpos,
|
||||
/// https://redis.io/commands/lpush
|
||||
lpush,
|
||||
/// https://redis.io/commands/lpushx
|
||||
lpushx,
|
||||
/// https://redis.io/commands/lrange
|
||||
lrange,
|
||||
/// https://redis.io/commands/lrem
|
||||
lrem,
|
||||
/// https://redis.io/commands/lset
|
||||
lset,
|
||||
/// https://redis.io/commands/ltrim
|
||||
ltrim,
|
||||
/// https://redis.io/commands/memory
|
||||
memory,
|
||||
/// https://redis.io/commands/mget
|
||||
mget,
|
||||
/// https://redis.io/commands/migrate
|
||||
migrate,
|
||||
/// https://redis.io/commands/module
|
||||
module,
|
||||
/// https://redis.io/commands/monitor
|
||||
monitor,
|
||||
/// https://redis.io/commands/move
|
||||
move,
|
||||
/// https://redis.io/commands/mset
|
||||
mset,
|
||||
/// https://redis.io/commands/msetnx
|
||||
msetnx,
|
||||
/// https://redis.io/commands/multi
|
||||
multi,
|
||||
/// https://redis.io/commands/object
|
||||
object,
|
||||
/// https://redis.io/commands/persist
|
||||
persist,
|
||||
/// https://redis.io/commands/pexpire
|
||||
pexpire,
|
||||
/// https://redis.io/commands/pexpireat
|
||||
pexpireat,
|
||||
/// https://redis.io/commands/pfadd
|
||||
pfadd,
|
||||
/// https://redis.io/commands/pfcount
|
||||
pfcount,
|
||||
/// https://redis.io/commands/pfdebug
|
||||
pfdebug,
|
||||
/// https://redis.io/commands/pfmerge
|
||||
pfmerge,
|
||||
/// https://redis.io/commands/pfselftest
|
||||
pfselftest,
|
||||
/// https://redis.io/commands/ping
|
||||
ping,
|
||||
/// https://redis.io/commands/post
|
||||
post,
|
||||
/// https://redis.io/commands/psetex
|
||||
psetex,
|
||||
/// https://redis.io/commands/psubscribe
|
||||
psubscribe,
|
||||
/// https://redis.io/commands/psync
|
||||
psync,
|
||||
/// https://redis.io/commands/pttl
|
||||
pttl,
|
||||
/// https://redis.io/commands/publish
|
||||
publish,
|
||||
/// https://redis.io/commands/pubsub
|
||||
pubsub,
|
||||
/// https://redis.io/commands/punsubscribe
|
||||
punsubscribe,
|
||||
/// https://redis.io/commands/randomkey
|
||||
randomkey,
|
||||
/// https://redis.io/commands/readonly
|
||||
readonly,
|
||||
/// https://redis.io/commands/readwrite
|
||||
readwrite,
|
||||
/// https://redis.io/commands/rename
|
||||
rename,
|
||||
/// https://redis.io/commands/renamenx
|
||||
renamenx,
|
||||
/// https://redis.io/commands/replconf
|
||||
replconf,
|
||||
/// https://redis.io/commands/replicaof
|
||||
replicaof,
|
||||
/// https://redis.io/commands/restore
|
||||
restore,
|
||||
/// https://redis.io/commands/role
|
||||
role,
|
||||
/// https://redis.io/commands/rpop
|
||||
rpop,
|
||||
/// https://redis.io/commands/rpoplpush
|
||||
rpoplpush,
|
||||
/// https://redis.io/commands/rpush
|
||||
rpush,
|
||||
/// https://redis.io/commands/rpushx
|
||||
rpushx,
|
||||
/// https://redis.io/commands/sadd
|
||||
sadd,
|
||||
/// https://redis.io/commands/save
|
||||
save,
|
||||
/// https://redis.io/commands/scan
|
||||
scan,
|
||||
/// https://redis.io/commands/scard
|
||||
scard,
|
||||
/// https://redis.io/commands/script
|
||||
script,
|
||||
/// https://redis.io/commands/sdiff
|
||||
sdiff,
|
||||
/// https://redis.io/commands/sdiffstore
|
||||
sdiffstore,
|
||||
/// https://redis.io/commands/select
|
||||
select,
|
||||
/// https://redis.io/commands/set
|
||||
set,
|
||||
/// https://redis.io/commands/setbit
|
||||
setbit,
|
||||
/// https://redis.io/commands/setex
|
||||
setex,
|
||||
/// https://redis.io/commands/setnx
|
||||
setnx,
|
||||
/// https://redis.io/commands/setrange
|
||||
setrange,
|
||||
/// https://redis.io/commands/shutdown
|
||||
shutdown,
|
||||
/// https://redis.io/commands/sinter
|
||||
sinter,
|
||||
/// https://redis.io/commands/sinterstore
|
||||
sinterstore,
|
||||
/// https://redis.io/commands/sismember
|
||||
sismember,
|
||||
/// https://redis.io/commands/slaveof
|
||||
slaveof,
|
||||
/// https://redis.io/commands/slowlog
|
||||
slowlog,
|
||||
/// https://redis.io/commands/smembers
|
||||
smembers,
|
||||
/// https://redis.io/commands/smove
|
||||
smove,
|
||||
/// https://redis.io/commands/sort
|
||||
sort,
|
||||
/// https://redis.io/commands/spop
|
||||
spop,
|
||||
/// https://redis.io/commands/srandmember
|
||||
srandmember,
|
||||
/// https://redis.io/commands/srem
|
||||
srem,
|
||||
/// https://redis.io/commands/sscan
|
||||
sscan,
|
||||
/// https://redis.io/commands/stralgo
|
||||
stralgo,
|
||||
/// https://redis.io/commands/strlen
|
||||
strlen,
|
||||
/// https://redis.io/commands/subscribe
|
||||
subscribe,
|
||||
/// https://redis.io/commands/substr
|
||||
substr,
|
||||
/// https://redis.io/commands/sunion
|
||||
sunion,
|
||||
/// https://redis.io/commands/sunionstore
|
||||
sunionstore,
|
||||
/// https://redis.io/commands/swapdb
|
||||
swapdb,
|
||||
/// https://redis.io/commands/sync
|
||||
sync,
|
||||
/// https://redis.io/commands/time
|
||||
time,
|
||||
/// https://redis.io/commands/touch
|
||||
touch,
|
||||
/// https://redis.io/commands/ttl
|
||||
ttl,
|
||||
/// https://redis.io/commands/type
|
||||
type,
|
||||
/// https://redis.io/commands/unlink
|
||||
unlink,
|
||||
/// https://redis.io/commands/quit
|
||||
quit,
|
||||
/// https://redis.io/commands/unsubscribe
|
||||
unsubscribe,
|
||||
/// https://redis.io/commands/unwatch
|
||||
unwatch,
|
||||
/// https://redis.io/commands/wait
|
||||
wait,
|
||||
/// https://redis.io/commands/watch
|
||||
watch,
|
||||
/// https://redis.io/commands/xack
|
||||
xack,
|
||||
/// https://redis.io/commands/xadd
|
||||
xadd,
|
||||
/// https://redis.io/commands/xclaim
|
||||
xclaim,
|
||||
/// https://redis.io/commands/xdel
|
||||
xdel,
|
||||
/// https://redis.io/commands/xgroup
|
||||
xgroup,
|
||||
/// https://redis.io/commands/xinfo
|
||||
xinfo,
|
||||
/// https://redis.io/commands/xlen
|
||||
xlen,
|
||||
/// https://redis.io/commands/xpending
|
||||
xpending,
|
||||
/// https://redis.io/commands/xrange
|
||||
xrange,
|
||||
/// https://redis.io/commands/xread
|
||||
xread,
|
||||
/// https://redis.io/commands/xreadgroup
|
||||
xreadgroup,
|
||||
/// https://redis.io/commands/xrevrange
|
||||
xrevrange,
|
||||
/// https://redis.io/commands/xsetid
|
||||
xsetid,
|
||||
/// https://redis.io/commands/xtrim
|
||||
xtrim,
|
||||
/// https://redis.io/commands/zadd
|
||||
zadd,
|
||||
/// https://redis.io/commands/zcard
|
||||
zcard,
|
||||
/// https://redis.io/commands/zcount
|
||||
zcount,
|
||||
/// https://redis.io/commands/zincrby
|
||||
zincrby,
|
||||
/// https://redis.io/commands/zinterstore
|
||||
zinterstore,
|
||||
/// https://redis.io/commands/zlexcount
|
||||
zlexcount,
|
||||
/// https://redis.io/commands/zpopmax
|
||||
zpopmax,
|
||||
/// https://redis.io/commands/zpopmin
|
||||
zpopmin,
|
||||
/// https://redis.io/commands/zrange
|
||||
zrange,
|
||||
/// https://redis.io/commands/zrangebylex
|
||||
zrangebylex,
|
||||
/// https://redis.io/commands/zrangebyscore
|
||||
zrangebyscore,
|
||||
/// https://redis.io/commands/zrank
|
||||
zrank,
|
||||
/// https://redis.io/commands/zrem
|
||||
zrem,
|
||||
/// https://redis.io/commands/zremrangebylex
|
||||
zremrangebylex,
|
||||
/// https://redis.io/commands/zremrangebyrank
|
||||
zremrangebyrank,
|
||||
/// https://redis.io/commands/zremrangebyscore
|
||||
zremrangebyscore,
|
||||
/// https://redis.io/commands/zrevrange
|
||||
zrevrange,
|
||||
/// https://redis.io/commands/zrevrangebylex
|
||||
zrevrangebylex,
|
||||
/// https://redis.io/commands/zrevrangebyscore
|
||||
zrevrangebyscore,
|
||||
/// https://redis.io/commands/zrevrank
|
||||
zrevrank,
|
||||
/// https://redis.io/commands/zscan
|
||||
zscan,
|
||||
/// https://redis.io/commands/zscore
|
||||
zscore,
|
||||
/// https://redis.io/commands/zunionstore
|
||||
zunionstore,
|
||||
/// Unknown/invalid command.
|
||||
unknown
|
||||
};
|
||||
|
||||
/** \brief Converts a command to a string
|
||||
* \ingroup any
|
||||
*
|
||||
* \param c The command to convert.
|
||||
*/
|
||||
char const* to_string(command c);
|
||||
|
||||
/** \brief Write the text for a command name to an output stream.
|
||||
* \ingroup operators
|
||||
*
|
||||
* \param os Output stream.
|
||||
* \param c Redis command
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, command c);
|
||||
|
||||
/** \brief Returns true for commands with push response.
|
||||
* \ingroup any
|
||||
*/
|
||||
bool has_push_response(command cmd);
|
||||
|
||||
/** \brief Creates a serializer for a \c std::string.
|
||||
* \ingroup any
|
||||
* \param storage The string.
|
||||
*/
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
resp3::serializer<std::string, command>
|
||||
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
|
||||
{
|
||||
return resp3::serializer<std::basic_string<CharT, Traits, Allocator>, command>(storage);
|
||||
}
|
||||
|
||||
} // redis
|
||||
} // aedis
|
||||
@@ -1,158 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace experimental {
|
||||
|
||||
/** \brief A high level redis client.
|
||||
* \ingroup any
|
||||
*
|
||||
* This Redis client keeps a connection to the database open and
|
||||
* uses it for all communication with Redis. For examples on how to
|
||||
* use see the examples chat_room.cpp, echo_server.cpp and redis_client.cpp.
|
||||
*
|
||||
* \remarks This class reuses its internal buffers for requests and
|
||||
* for reading Redis responses. With time it will allocate less and
|
||||
* less.
|
||||
*/
|
||||
class client : public std::enable_shared_from_this<client> {
|
||||
public:
|
||||
/** \brief The extended response adapter type.
|
||||
*
|
||||
* The difference between the adapter and extended_adapter
|
||||
* concepts is that the extended get a command redis::parameter.
|
||||
*/
|
||||
using extented_adapter_type = std::function<void(redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&)>;
|
||||
|
||||
/// The type of the message callback.
|
||||
using on_message_type = std::function<void(std::error_code ec, redis::command)>;
|
||||
|
||||
/// The type of the socket used by the client.
|
||||
//using socket_type = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using socket_type = net::ip::tcp::socket;
|
||||
|
||||
private:
|
||||
struct request_info {
|
||||
// Request size in bytes.
|
||||
std::size_t size = 0;
|
||||
|
||||
// The number of commands it contains excluding commands that
|
||||
// have push types as responses, see has_push_response.
|
||||
std::size_t cmds = 0;
|
||||
};
|
||||
|
||||
// Requests payload.
|
||||
std::string requests_;
|
||||
|
||||
// The commands contained in the requests.
|
||||
std::queue<redis::command> commands_;
|
||||
|
||||
// Info about the requests.
|
||||
std::queue<request_info> req_info_;
|
||||
|
||||
// The stream.
|
||||
socket_type socket_;
|
||||
|
||||
// Timer used to inform the write coroutine that it can write the
|
||||
// next message in the output queue.
|
||||
net::steady_timer timer_;
|
||||
|
||||
// Response adapter.
|
||||
extented_adapter_type extended_adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {};
|
||||
|
||||
// Message callback.
|
||||
on_message_type on_msg_ = [](std::error_code ec, redis::command) {};
|
||||
|
||||
// Set when the writer coroutine should stop.
|
||||
bool stop_writer_ = false;
|
||||
|
||||
// A coroutine that keeps reading the socket. When a message
|
||||
// arrives it calls on_message.
|
||||
net::awaitable<void> reader();
|
||||
|
||||
// Write coroutine. It is kept suspended until there are messages
|
||||
// to be sent.
|
||||
net::awaitable<void> writer();
|
||||
|
||||
/* Prepares the back of the queue to receive further commands.
|
||||
*
|
||||
* If true is returned the request in the front of the queue can be
|
||||
* sent to the server. See async_write_some.
|
||||
*/
|
||||
bool prepare_next();
|
||||
|
||||
public:
|
||||
/** \brief Client constructor.
|
||||
*
|
||||
* Constructos the client from an executor.
|
||||
*
|
||||
* \param ex The executor.
|
||||
*/
|
||||
client(net::any_io_executor ex);
|
||||
|
||||
/// Returns the executor used for I/O with Redis.
|
||||
auto get_executor() {return socket_.get_executor();}
|
||||
|
||||
/** \brief Starts communication with Redis.
|
||||
*
|
||||
* This functions will send the hello command to Redis and spawn
|
||||
* the read and write coroutines.
|
||||
*
|
||||
* \param socket A socket that is connected to redis.
|
||||
*
|
||||
* \returns This function returns an awaitable on which users should \c
|
||||
* co_await. When the communication with Redis is lost the
|
||||
* coroutine will finally co_return.
|
||||
*/
|
||||
net::awaitable<void> engage(socket_type socket);
|
||||
|
||||
/** \brief Adds a command to the command queue.
|
||||
*
|
||||
* \sa serializer.hpp
|
||||
*/
|
||||
template <class... Ts>
|
||||
void send(redis::command cmd, Ts const&... args);
|
||||
|
||||
/// Sets an extended response adapter.
|
||||
void set_extended_adapter(extented_adapter_type adapter);
|
||||
|
||||
/// Sets the message callback;
|
||||
void set_msg_callback(on_message_type on_msg);
|
||||
};
|
||||
|
||||
template <class... Ts>
|
||||
void client::send(redis::command cmd, Ts const&... args)
|
||||
{
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
auto sr = redis::make_serializer(requests_);
|
||||
auto const before = std::size(requests_);
|
||||
sr.push(cmd, args...);
|
||||
auto const after = std::size(requests_);
|
||||
req_info_.front().size += after - before;;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.emplace(cmd);
|
||||
++req_info_.front().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
} // experimental
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,200 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/redis/experimental/client.hpp>
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace experimental {
|
||||
|
||||
client::client(net::any_io_executor ex)
|
||||
: socket_{ex}
|
||||
, timer_{ex}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
net::awaitable<void> client::reader()
|
||||
{
|
||||
// Writes and reads continuosly from the socket.
|
||||
for (std::string buffer;;) {
|
||||
// Notice this coro can get scheduled while the write operation
|
||||
// in the writer is ongoing. so we have to check.
|
||||
while (!std::empty(req_info_) && req_info_.front().size != 0) {
|
||||
assert(!std::empty(requests_));
|
||||
boost::system::error_code ec;
|
||||
co_await
|
||||
net::async_write(
|
||||
socket_,
|
||||
net::buffer(requests_.data(), req_info_.front().size),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
requests_.erase(0, req_info_.front().size);
|
||||
req_info_.front().size = 0;
|
||||
|
||||
if (req_info_.front().cmds != 0)
|
||||
break; // We must await the responses.
|
||||
|
||||
req_info_.pop();
|
||||
}
|
||||
|
||||
do { // Keeps reading while there are no messages queued waiting to be sent.
|
||||
do { // Consumes the responses to all commands in the request.
|
||||
boost::system::error_code ec;
|
||||
auto const t =
|
||||
co_await async_read_type(socket_, net::dynamic_buffer(buffer),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec) {
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
if (t == type::push) {
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
on_msg_(ec, redis::command::unknown);
|
||||
if (ec) { // TODO: Return only on non aedis errors.
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
} else {
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(commands_.front(), t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
on_msg_(ec, commands_.front());
|
||||
if (ec) { // TODO: Return only on non aedis errors.
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
commands_.pop();
|
||||
--req_info_.front().cmds;
|
||||
}
|
||||
|
||||
} while (!std::empty(req_info_) && req_info_.front().cmds != 0);
|
||||
|
||||
// We may exit the loop above either because we are done
|
||||
// with the response or because we received a server push
|
||||
// while the queue was empty so we have to check before
|
||||
// poping..
|
||||
if (!std::empty(req_info_))
|
||||
req_info_.pop();
|
||||
|
||||
} while (std::empty(req_info_));
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> client::writer()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
while (socket_.is_open()) {
|
||||
ec = {};
|
||||
co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
if (stop_writer_)
|
||||
co_return;
|
||||
|
||||
// Notice this coro can get scheduled while the write operation
|
||||
// in the reader is ongoing. so we have to check.
|
||||
while (!std::empty(req_info_) && req_info_.front().size != 0) {
|
||||
assert(!std::empty(requests_));
|
||||
ec = {};
|
||||
co_await net::async_write(
|
||||
socket_, net::buffer(requests_.data(), req_info_.front().size),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec) {
|
||||
// What should we do here exactly? Closing the socket will
|
||||
// cause the reader coroutine to return so that the engage
|
||||
// coroutine returns to the user.
|
||||
socket_.close();
|
||||
co_return;
|
||||
}
|
||||
|
||||
requests_.erase(0, req_info_.front().size);
|
||||
req_info_.front().size = 0;
|
||||
|
||||
if (req_info_.front().cmds != 0)
|
||||
break;
|
||||
|
||||
req_info_.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool client::prepare_next()
|
||||
{
|
||||
if (std::empty(req_info_)) {
|
||||
req_info_.push({});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req_info_.front().size == 0) {
|
||||
// It has already been written and we are waiting for the
|
||||
// responses.
|
||||
req_info_.push({});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void client::set_extended_adapter(extented_adapter_type adapter)
|
||||
{
|
||||
extended_adapter_ = adapter;
|
||||
}
|
||||
|
||||
void client::set_msg_callback(on_message_type on_msg)
|
||||
{
|
||||
on_msg_ = on_msg;
|
||||
}
|
||||
|
||||
net::awaitable<void> client::engage(socket_type socket)
|
||||
{
|
||||
using namespace aedis::net::experimental::awaitable_operators;
|
||||
|
||||
socket_ = std::move(socket);
|
||||
|
||||
std::string request;
|
||||
auto sr = redis::make_serializer(request);
|
||||
sr.push(redis::command::hello, 3);
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await net::async_write(socket_, net::buffer(request), net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
co_return;
|
||||
|
||||
std::string buffer;
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
co_await
|
||||
resp3::async_read(
|
||||
socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
on_msg_(ec, redis::command::hello);
|
||||
|
||||
if (ec)
|
||||
co_return;
|
||||
|
||||
co_await (reader() && writer());
|
||||
}
|
||||
|
||||
} // experimental
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace redis {
|
||||
|
||||
char const* to_string(command c)
|
||||
{
|
||||
static char const* table[] = {
|
||||
"ACL",
|
||||
"APPEND",
|
||||
"ASKING",
|
||||
"AUTH",
|
||||
"BGREWRITEAOF",
|
||||
"BGSAVE",
|
||||
"BITCOUNT",
|
||||
"BITFIELD",
|
||||
"BITFIELD_RO",
|
||||
"BITOP",
|
||||
"BITPOS",
|
||||
"BLPOP",
|
||||
"BRPOP",
|
||||
"BRPOPLPUSH",
|
||||
"BZPOPMAX",
|
||||
"BZPOPMIN",
|
||||
"CLIENT",
|
||||
"CLUSTER",
|
||||
"COMMAND",
|
||||
"CONFIG",
|
||||
"DBSIZE",
|
||||
"DEBUG",
|
||||
"DECR",
|
||||
"DECRBY",
|
||||
"DEL",
|
||||
"DISCARD",
|
||||
"DUMP",
|
||||
"ECHO",
|
||||
"EVAL",
|
||||
"EVALSHA",
|
||||
"EXEC",
|
||||
"EXISTS",
|
||||
"EXPIRE",
|
||||
"EXPIREAT",
|
||||
"FLUSHALL",
|
||||
"FLUSHDB",
|
||||
"GEOADD",
|
||||
"GEODIST",
|
||||
"GEOHASH",
|
||||
"GEOPOS",
|
||||
"GEORADIUS",
|
||||
"GEORADIUS_RO",
|
||||
"GEORADIUSBYMEMBER",
|
||||
"GEORADIUSBYMEMBER_RO",
|
||||
"GET",
|
||||
"GETBIT",
|
||||
"GETRANGE",
|
||||
"GETSET",
|
||||
"HDEL",
|
||||
"HELLO",
|
||||
"HEXISTS",
|
||||
"HGET",
|
||||
"HGETALL",
|
||||
"HINCRBY",
|
||||
"HINCRBYFLOAT",
|
||||
"HKEYS",
|
||||
"HLEN",
|
||||
"HMGET",
|
||||
"HMSET",
|
||||
"HSCAN",
|
||||
"HSET",
|
||||
"HSETNX",
|
||||
"HSTRLEN",
|
||||
"HVALS",
|
||||
"INCR",
|
||||
"INCRBY",
|
||||
"INCRBYFLOAT",
|
||||
"INFO",
|
||||
"KEYS",
|
||||
"LASTSAVE",
|
||||
"LATENCY",
|
||||
"LINDEX",
|
||||
"LINSERT",
|
||||
"LLEN",
|
||||
"LOLWUT",
|
||||
"LPOP",
|
||||
"LPOS",
|
||||
"LPUSH",
|
||||
"LPUSHX",
|
||||
"LRANGE",
|
||||
"LREM",
|
||||
"LSET",
|
||||
"LTRIM",
|
||||
"MEMORY",
|
||||
"MGET",
|
||||
"MIGRATE",
|
||||
"MODULE",
|
||||
"MONITOR",
|
||||
"MOVE",
|
||||
"MSET",
|
||||
"MSETNX",
|
||||
"MULTI",
|
||||
"OBJECT",
|
||||
"PERSIST",
|
||||
"PEXPIRE",
|
||||
"PEXPIREAT",
|
||||
"PFADD",
|
||||
"PFCOUNT",
|
||||
"PFDEBUG",
|
||||
"PFMERGE",
|
||||
"PFSELFTEST",
|
||||
"PING",
|
||||
"POST",
|
||||
"PSETEX",
|
||||
"PSUBSCRIBE",
|
||||
"PSYNC",
|
||||
"PTTL",
|
||||
"PUBLISH",
|
||||
"PUBSUB",
|
||||
"PUNSUBSCRIBE",
|
||||
"RANDOMKEY",
|
||||
"READONLY",
|
||||
"READWRITE",
|
||||
"RENAME",
|
||||
"RENAMENX",
|
||||
"REPLCONF",
|
||||
"REPLICAOF",
|
||||
"RESTORE",
|
||||
"ROLE",
|
||||
"RPOP",
|
||||
"RPOPLPUSH",
|
||||
"RPUSH",
|
||||
"RPUSHX",
|
||||
"SADD",
|
||||
"SAVE",
|
||||
"SCAN",
|
||||
"SCARD",
|
||||
"SCRIPT",
|
||||
"SDIFF",
|
||||
"SDIFFSTORE",
|
||||
"SELECT",
|
||||
"SET",
|
||||
"SETBIT",
|
||||
"SETEX",
|
||||
"SETNX",
|
||||
"SETRANGE",
|
||||
"SHUTDOWN",
|
||||
"SINTER",
|
||||
"SINTERSTORE",
|
||||
"SISMEMBER",
|
||||
"SLAVEOF",
|
||||
"SLOWLOG",
|
||||
"SMEMBERS",
|
||||
"SMOVE",
|
||||
"SORT",
|
||||
"SPOP",
|
||||
"SRANDMEMBER",
|
||||
"SREM",
|
||||
"SSCAN",
|
||||
"STRALGO",
|
||||
"STRLEN",
|
||||
"SUBSCRIBE",
|
||||
"SUBSTR",
|
||||
"SUNION",
|
||||
"SUNIONSTORE",
|
||||
"SWAPDB",
|
||||
"SYNC",
|
||||
"TIME",
|
||||
"TOUCH",
|
||||
"TTL",
|
||||
"TYPE",
|
||||
"UNLINK",
|
||||
"QUIT",
|
||||
"UNSUBSCRIBE",
|
||||
"UNWATCH",
|
||||
"WAIT",
|
||||
"WATCH",
|
||||
"XACK",
|
||||
"XADD",
|
||||
"XCLAIM",
|
||||
"XDEL",
|
||||
"XGROUP",
|
||||
"XINFO",
|
||||
"XLEN",
|
||||
"XPENDING",
|
||||
"XRANGE",
|
||||
"XREAD",
|
||||
"XREADGROUP",
|
||||
"XREVRANGE",
|
||||
"XSETID",
|
||||
"XTRIM",
|
||||
"ZADD",
|
||||
"ZCARD",
|
||||
"ZCOUNT",
|
||||
"ZINCRBY",
|
||||
"ZINTERSTORE",
|
||||
"ZLEXCOUNT",
|
||||
"ZPOPMAX",
|
||||
"ZPOPMIN",
|
||||
"ZRANGE",
|
||||
"ZRANGEBYLEX",
|
||||
"ZRANGEBYSCORE",
|
||||
"ZRANK",
|
||||
"ZREM",
|
||||
"ZREMRANGEBYLEX",
|
||||
"ZREMRANGEBYRANK",
|
||||
"ZREMRANGEBYSCORE",
|
||||
"ZREVRANGE",
|
||||
"ZREVRANGEBYLEX",
|
||||
"ZREVRANGEBYSCORE",
|
||||
"ZREVRANK",
|
||||
"ZSCAN",
|
||||
"ZSCORE",
|
||||
"ZUNIONSTORE",
|
||||
};
|
||||
|
||||
return table[static_cast<int>(c)];
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, command c)
|
||||
{
|
||||
os << to_string(c);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool has_push_response(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::subscribe:
|
||||
case command::unsubscribe:
|
||||
case command::psubscribe:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // redis
|
||||
} // aedis
|
||||
@@ -1,94 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Creates a void response adapter.
|
||||
\ingroup any
|
||||
|
||||
The adapter returned by this function ignores responses and is
|
||||
useful to avoid wasting time with responses which the user is
|
||||
insterested in.
|
||||
|
||||
Example usage:
|
||||
|
||||
@code
|
||||
co_await async_read(socket, buffer, adapt());
|
||||
@endcode
|
||||
*/
|
||||
inline
|
||||
auto adapt() noexcept
|
||||
{ return response_traits<void>::adapt(); }
|
||||
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup any
|
||||
*
|
||||
* For example
|
||||
* The following types are supported.
|
||||
*
|
||||
* - Integer data types e.g. `int`, `unsigned`, etc.
|
||||
*
|
||||
* - `std::string`
|
||||
*
|
||||
* We also support the following C++ containers
|
||||
*
|
||||
* - `std::vector<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::deque<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::list<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::set<T>`. Can be used with RESP3 set type.
|
||||
*
|
||||
* - `std::unordered_set<T>`. Can be used with RESP3 set type.
|
||||
*
|
||||
* - `std::map<T>`. Can be used with RESP3 hash type.
|
||||
*
|
||||
* - `std::unordered_map<T>`. Can be used with RESP3 hash type.
|
||||
*
|
||||
* All these types can be wrapped in an `std::optional<T>`. This
|
||||
* function also support \c std::tuple to read the response to
|
||||
* tuples. At the moment this feature supports only transactions that
|
||||
* contain simple types or aggregates that don't contain aggregates
|
||||
* themselves (as in most cases).
|
||||
*
|
||||
* 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 adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,382 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
#include <aedis/resp3/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
from_string(
|
||||
T& i,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
auto const res = std::from_chars(data, data + data_size, i);
|
||||
if (res.ec != std::errc())
|
||||
ec = std::make_error_code(res.ec);
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
void
|
||||
from_string(
|
||||
std::basic_string<CharT, Traits, Allocator>& s,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code&)
|
||||
{
|
||||
s.assign(data, data_size);
|
||||
}
|
||||
|
||||
void set_on_resp3_error(type t, std::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case type::blob_error: ec = adapter::error::blob_error; return;
|
||||
case type::null: ec = adapter::error::null; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// For optional responses.
|
||||
void set_on_resp3_error2(type t, std::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case type::blob_error: ec = adapter::error::blob_error; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapter that ignores responses.
|
||||
struct ignore {
|
||||
void
|
||||
operator()(
|
||||
type, std::size_t, std::size_t, char const*, std::size_t,
|
||||
std::error_code&) { }
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class general {
|
||||
private:
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
general(Container* c = nullptr): result_(c) {}
|
||||
|
||||
/** @brief Function called by the parser when new data has been processed.
|
||||
*
|
||||
* Users who what to customize their response types are required to derive
|
||||
* from this class and override this function, see examples.
|
||||
*
|
||||
* \param t The RESP3 type of the data.
|
||||
*
|
||||
* \param n When t is an aggregate data type this will contain its size
|
||||
* (see also element_multiplicity) for simple data types this is always 1.
|
||||
*
|
||||
* \param depth The element depth in the tree.
|
||||
*
|
||||
* \param data A pointer to the data.
|
||||
*
|
||||
* \param size The size of data.
|
||||
*/
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code&)
|
||||
{
|
||||
result_->emplace_back(t, aggregate_size, depth, std::string{data, size});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node>
|
||||
class adapter_node {
|
||||
private:
|
||||
Node* result_;
|
||||
|
||||
public:
|
||||
adapter_node(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code&)
|
||||
{
|
||||
result_->data_type = t;
|
||||
result_->aggregate_size = aggregate_size;
|
||||
result_->depth = depth;
|
||||
result_->data.assign(data, data_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Adapter for RESP3 simple data types.
|
||||
template <class T>
|
||||
class simple {
|
||||
private:
|
||||
T* result_;
|
||||
|
||||
public:
|
||||
simple(T* t = nullptr) : result_(t) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
from_string(*result_, data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class simple_optional {
|
||||
private:
|
||||
std::optional<T>* result_;
|
||||
|
||||
public:
|
||||
simple_optional(std::optional<T>* o = nullptr) : result_(o) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error2(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (depth != 0) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
if (t == type::null)
|
||||
return;
|
||||
|
||||
if (!result_->has_value())
|
||||
*result_ = T{};
|
||||
|
||||
from_string(result_->value(), data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
/* A std::vector adapter.
|
||||
*/
|
||||
template <class Container>
|
||||
class vector {
|
||||
private:
|
||||
int i_ = -1;
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
vector(Container* v = nullptr) : result_{v} {}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
if (i_ != -1) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const m = element_multiplicity(t);
|
||||
result_->resize(m * aggregate_size);
|
||||
++i_;
|
||||
} else {
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
from_string(result_->at(i_), data, data_size, ec);
|
||||
++i_;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class list {
|
||||
private:
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
list(Container* ref = nullptr): result_(ref) {}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
if (depth != 0) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (depth != 1) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
result_->push_back({});
|
||||
from_string(result_->back(), data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class set {
|
||||
private:
|
||||
Container* result_;
|
||||
typename Container::iterator hint_;
|
||||
|
||||
public:
|
||||
set(Container* c = nullptr)
|
||||
: result_(c)
|
||||
, hint_(std::end(*c))
|
||||
{}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (t == type::set) {
|
||||
assert(depth == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!is_aggregate(t));
|
||||
|
||||
assert(depth == 1);
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
typename Container::key_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
if (hint_ == std::end(*result_)) {
|
||||
auto const ret = result_->insert(std::move(obj));
|
||||
hint_ = ret.first;
|
||||
} else {
|
||||
hint_ = result_->insert(hint_, std::move(obj));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class map {
|
||||
private:
|
||||
Container* result_;
|
||||
typename Container::iterator current_;
|
||||
bool on_key_ = true;
|
||||
|
||||
public:
|
||||
map(Container* c = nullptr)
|
||||
: result_(c)
|
||||
, current_(std::end(*c))
|
||||
{}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (t == type::map) {
|
||||
assert(depth == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!is_aggregate(t));
|
||||
assert(depth == 1);
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (on_key_) {
|
||||
typename Container::key_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
current_ = result_->insert(current_, {std::move(obj), {}});
|
||||
} else {
|
||||
typename Container::mapped_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
current_->second = std::move(obj);
|
||||
}
|
||||
|
||||
on_key_ = !on_key_;
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
# include <system_error>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Errors that may occurr when reading a response.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Expects a simple RESP3 type but got an aggregate.
|
||||
expects_simple_type = 1,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_unsupported,
|
||||
|
||||
/// Got RESP3 simple error.
|
||||
simple_error,
|
||||
|
||||
/// Got RESP3 blob_error.
|
||||
blob_error,
|
||||
|
||||
/// The tuple used as response has incompatible size.
|
||||
incompatible_tuple_size,
|
||||
|
||||
/// Got RESP3 null type.
|
||||
null
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : std::error_category {
|
||||
|
||||
/// \todo Fix string lifetime.
|
||||
char const* name() const noexcept override
|
||||
{ return "aedis.response_adapter"; }
|
||||
|
||||
// TODO: Move to .ipp
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::expects_simple_type: return "Expects a simple RESP3 type";
|
||||
case error::nested_unsupported: return "Nested responses unsupported.";
|
||||
case error::simple_error: return "Got RESP3 simple-error.";
|
||||
case error::blob_error: return "Got RESP3 blob-error.";
|
||||
case error::incompatible_tuple_size: return "The tuple used as response has incompatible size.";
|
||||
case error::null: return "Got RESP3 null.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
std::error_category const& adapter_category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
inline
|
||||
std::error_code make_error_code(error e)
|
||||
{
|
||||
static detail::error_category_impl const eci{};
|
||||
return std::error_code{static_cast<int>(e), detail::adapter_category()};
|
||||
}
|
||||
|
||||
inline
|
||||
std::error_condition make_error_condition(error e)
|
||||
{
|
||||
return std::error_condition(static_cast<int>(e), detail::adapter_category());
|
||||
}
|
||||
|
||||
} // adapter
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::resp3::adapter::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
@@ -1,87 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
template <class>
|
||||
struct needs_to_string : std::true_type {};
|
||||
|
||||
template <> struct needs_to_string<std::string> : std::false_type {};
|
||||
template <> struct needs_to_string<std::string_view> : std::false_type {};
|
||||
template <> struct needs_to_string<char const*> : std::false_type {};
|
||||
template <> struct needs_to_string<char*> : std::false_type {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct needs_to_string<char[N]> : std::false_type {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct needs_to_string<char const[N]> : std::false_type {};
|
||||
|
||||
template <class Storage>
|
||||
void add_header(Storage& to, int size)
|
||||
{
|
||||
// std::string does not support allocators.
|
||||
using std::to_string;
|
||||
auto const str = to_string(size);
|
||||
|
||||
to += "*";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Storage>
|
||||
void add_bulk(Storage& to, std::string_view data)
|
||||
{
|
||||
// std::string does not support allocators.
|
||||
using std::to_string;
|
||||
auto const str = to_string(std::size(data));
|
||||
|
||||
to += "$";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
to += data;
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Storage, class T>
|
||||
void add_bulk(Storage& to, T const& data, typename std::enable_if<needs_to_string<T>::value, bool>::type = false)
|
||||
{
|
||||
using std::to_string;
|
||||
auto const s = to_string(data);
|
||||
add_bulk(to, s);
|
||||
}
|
||||
|
||||
// Overload for pairs.
|
||||
// TODO: Overload for tuples.
|
||||
template <class Storage, class T1, class T2>
|
||||
void add_bulk(Storage& to, std::pair<T1, T2> const& pair)
|
||||
{
|
||||
add_bulk(to, pair.first);
|
||||
add_bulk(to, pair.second);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct value_type_size {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct value_type_size<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,40 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
type to_type(char c)
|
||||
{
|
||||
switch (c) {
|
||||
case '!': return type::blob_error;
|
||||
case '=': return type::verbatim_string;
|
||||
case '$': return type::blob_string;
|
||||
case ';': return type::streamed_string_part;
|
||||
case '-': return type::simple_error;
|
||||
case ':': return type::number;
|
||||
case ',': return type::doublean;
|
||||
case '#': return type::boolean;
|
||||
case '(': return type::big_number;
|
||||
case '+': return type::simple_string;
|
||||
case '_': return type::null;
|
||||
case '>': return type::push;
|
||||
case '~': return type::set;
|
||||
case '*': return type::array;
|
||||
case '|': return type::attribute;
|
||||
case '%': return type::map;
|
||||
default: return type::invalid;
|
||||
}
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,137 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
// TODO: Use asio::coroutine.
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter>
|
||||
class parse_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
parser<ResponseAdapter> parser_;
|
||||
std::size_t consumed_;
|
||||
int start_;
|
||||
|
||||
public:
|
||||
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
|
||||
: stream_ {stream}
|
||||
, buf_ {buf}
|
||||
, parser_ {adapter}
|
||||
, consumed_{0}
|
||||
, start_{1}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
switch (start_) {
|
||||
for (;;) {
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
case 1:
|
||||
start_ = 0;
|
||||
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 (std::size(buf_) < (parser_.bulk_length() + 2)) {
|
||||
start_ = 0;
|
||||
auto const s = std::size(buf_);
|
||||
auto const l = parser_.bulk_length();
|
||||
auto const to_read = l + 2 - s;
|
||||
buf_.grow(to_read);
|
||||
net::async_read(stream_, buf_.data(s, to_read), net::transfer_all(), std::move(self));
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
n = parser_.advance((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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Use asio::coroutine.
|
||||
template <class AsyncReadStream, class DynamicBuffer>
|
||||
class type_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
|
||||
public:
|
||||
type_op(AsyncReadStream& stream, DynamicBuffer buf)
|
||||
: stream_ {stream}
|
||||
, buf_ {buf}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, type::invalid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::size(buf_) == 0) {
|
||||
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
return;
|
||||
}
|
||||
|
||||
auto const* data = (char const*)buf_.data(0, n).data();
|
||||
auto const type = to_type(*data);
|
||||
self.complete(ec, type);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
# include <system_error>
|
||||
|
||||
/// \file error.hpp
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief RESP3 parsing errors.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Invalid RESP3 type.
|
||||
invalid_type = 1,
|
||||
|
||||
/// Can't parse the string as an integer.
|
||||
not_a_number,
|
||||
|
||||
/// Received less bytes than expected.
|
||||
unexpected_read_size,
|
||||
|
||||
/// The maximum depth of a nested response was exceeded.
|
||||
exceeeds_max_nested_depth
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : std::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{ return "aedis.resp3"; }
|
||||
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::invalid_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::unexpected_read_size: return "Unexpected read size.";
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
std::error_category const& category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
/** \brief Converts an error in an std::error_code object.
|
||||
* \ingroup any
|
||||
*/
|
||||
inline
|
||||
std::error_code make_error_code(error e)
|
||||
{
|
||||
static detail::error_category_impl const eci{};
|
||||
return std::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
inline
|
||||
std::error_condition make_error_condition(error e)
|
||||
{
|
||||
return std::error_condition(static_cast<int>(e), detail::category());
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::resp3::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
@@ -1,67 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
std::string to_string(node 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 += in.data;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool operator==(node const& a, node const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
&& a.data_type == b.data_type
|
||||
&& a.data == b.data;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, node const& o)
|
||||
{
|
||||
os << to_string(o);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r)
|
||||
{
|
||||
os << to_string(r);
|
||||
return os;
|
||||
}
|
||||
|
||||
// TODO: Output like in redis-cli.
|
||||
std::string to_string(std::vector<node> const& vec)
|
||||
{
|
||||
if (std::empty(vec))
|
||||
return {};
|
||||
|
||||
auto begin = std::cbegin(vec);
|
||||
std::string res;
|
||||
for (; begin != std::prev(std::cend(vec)); ++begin) {
|
||||
res += to_string(*begin);
|
||||
res += '\n';
|
||||
}
|
||||
|
||||
res += to_string(*begin);
|
||||
return res;
|
||||
}
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
char const* to_string(type t)
|
||||
{
|
||||
static char const* table[] =
|
||||
{ "array"
|
||||
, "push"
|
||||
, "set"
|
||||
, "map"
|
||||
, "attribute"
|
||||
, "simple_string"
|
||||
, "simple_error"
|
||||
, "number"
|
||||
, "doublean"
|
||||
, "boolean"
|
||||
, "big_number"
|
||||
, "null"
|
||||
, "blob_error"
|
||||
, "verbatim_string"
|
||||
, "blob_string"
|
||||
, "streamed_string_part"
|
||||
, "invalid"
|
||||
};
|
||||
|
||||
return table[static_cast<int>(t)];
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, type t)
|
||||
{
|
||||
os << to_string(t);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool is_aggregate(type t)
|
||||
{
|
||||
switch (t) {
|
||||
case type::array:
|
||||
case type::push:
|
||||
case type::set:
|
||||
case type::map:
|
||||
case type::attribute: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t element_multiplicity(type t)
|
||||
{
|
||||
switch (t) {
|
||||
case type::map:
|
||||
case type::attribute: return 2ULL;
|
||||
default: return 1ULL;
|
||||
}
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief A node in the response tree.
|
||||
* \ingroup any
|
||||
*
|
||||
* Redis responses are the pre-order view of the response tree (see
|
||||
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
*/
|
||||
struct node {
|
||||
/// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
/// 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 data types this is always empty.
|
||||
std::string data;
|
||||
};
|
||||
|
||||
/** \brief Converts the node to a string.
|
||||
* \ingroup any
|
||||
*
|
||||
* \param obj The node object.
|
||||
*/
|
||||
std::string to_string(node const& obj);
|
||||
|
||||
/** \brief Compares a node for equality.
|
||||
* \ingroup any
|
||||
*/
|
||||
bool operator==(node const& a, node const& b);
|
||||
|
||||
/** \brief Writes the node to the stream.
|
||||
* \ingroup any
|
||||
*
|
||||
* NOTE: Binary data is not converted to text.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, node const& o);
|
||||
|
||||
/** \brief Writes the response to the output stream
|
||||
* \ingroup any
|
||||
*/
|
||||
std::string to_string(std::vector<node> const& vec);
|
||||
|
||||
/** \brief Writes the response to the output stream
|
||||
* \ingroup any
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r);
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,197 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/adapt.hpp>
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/detail/read_ops.hpp>
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Read the response to a command sychronously.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buf Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \param ec Error if any.
|
||||
* \returns The number of bytes that have been consumed from the
|
||||
* auxiliary buffer.
|
||||
*/
|
||||
template <
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter
|
||||
>
|
||||
std::size_t
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
detail::parser p {adapter};
|
||||
std::size_t n = 0;
|
||||
std::size_t consumed = 0;
|
||||
do {
|
||||
if (p.bulk() == type::invalid) {
|
||||
n = net::read_until(stream, buf, "\r\n", ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (n < 3) {
|
||||
ec = error::unexpected_read_size;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
auto const s = std::size(buf);
|
||||
auto const l = p.bulk_length();
|
||||
if (s < (l + 2)) {
|
||||
auto const to_read = l + 2 - s;
|
||||
buf.grow(to_read);
|
||||
n = net::read(stream, buf.data(s, to_read), ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (n < to_read) {
|
||||
ec = error::unexpected_read_size;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
auto const* data = (char const*) buf.data(0, n).data();
|
||||
n = p.advance(data, n, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
buf.consume(n);
|
||||
consumed += n;
|
||||
} while (!p.done());
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/** \brief Reads the reponse to a command.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buf Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \returns The number of bytes that have been consumed from the
|
||||
* auxiliary buffer.
|
||||
*/
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = response_traits<void>::adapter_type>
|
||||
std::size_t
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter = adapt())
|
||||
{
|
||||
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 the response to a Redis command asynchronously.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
*
|
||||
* The completion handler must have the following signature.
|
||||
*
|
||||
* @code
|
||||
* void(boost::system::error_code, std::size_t)
|
||||
* @endcode
|
||||
*
|
||||
* The second argumet to the completion handler is the number of
|
||||
* bytes that have been consumed in the read operation.
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buffer Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \param token The completion token.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = response_traits<void>::adapter_type,
|
||||
class CompletionToken = net::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
ResponseAdapter adapter = adapt(),
|
||||
CompletionToken&& token =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return net::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
|
||||
token,
|
||||
stream);
|
||||
}
|
||||
|
||||
/** \brief Reads the RESP3 type of the next incomming.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function won't consume any data from the buffer. The
|
||||
* completion handler must have the following signature.
|
||||
*
|
||||
* @code
|
||||
void(boost::system::error_code, type)
|
||||
* @endcode
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buffer Auxiliary read buffer, usually a `std::string`.
|
||||
* \param token The completion token.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read_type(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
CompletionToken&& token =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return net::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, type)
|
||||
>(detail::type_op<AsyncReadStream, DynamicBuffer> {stream, buffer}, token, stream);
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
@@ -1,229 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/adapter/detail/adapters.hpp>
|
||||
#include <aedis/resp3/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Traits class for response objects.
|
||||
* \ingroup any
|
||||
*/
|
||||
template <class T>
|
||||
struct response_traits
|
||||
{
|
||||
/// The response type.
|
||||
using response_type = T;
|
||||
|
||||
/// The adapter type.
|
||||
using adapter_type = adapter::detail::simple<response_type>;
|
||||
|
||||
/// Returns an adapter for the reponse r
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
/// Template typedef for response_traits.
|
||||
template <class T>
|
||||
using response_traits_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
template <class T>
|
||||
struct response_traits<std::optional<T>>
|
||||
{
|
||||
using response_type = std::optional<T>;
|
||||
using adapter_type = adapter::detail::simple_optional<typename response_type::value_type>;
|
||||
static auto adapt(response_type& i) noexcept { return adapter_type{&i}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::vector<T, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<T, Allocator>;
|
||||
using adapter_type = adapter::detail::vector<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<node>
|
||||
{
|
||||
using response_type = node;
|
||||
using adapter_type = adapter::detail::adapter_node<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
struct response_traits<std::vector<node, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<node, Allocator>;
|
||||
using adapter_type = adapter::detail::general<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::list<T, Allocator>>
|
||||
{
|
||||
using response_type = std::list<T, Allocator>;
|
||||
using adapter_type = adapter::detail::list<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::deque<T, Allocator>>
|
||||
{
|
||||
using response_type = std::deque<T, Allocator>;
|
||||
using adapter_type = adapter::detail::list<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct response_traits<std::set<Key, Compare, Allocator>>
|
||||
{
|
||||
using response_type = std::set<Key, Compare, Allocator>;
|
||||
using adapter_type = adapter::detail::set<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct response_traits<std::unordered_set<Key, Hash, KeyEqual, Allocator>>
|
||||
{
|
||||
using response_type = std::unordered_set<Key, Hash, KeyEqual, Allocator>;
|
||||
using adapter_type = adapter::detail::set<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct response_traits<std::map<Key, T, Compare, Allocator>>
|
||||
{
|
||||
using response_type = std::map<Key, T, Compare, Allocator>;
|
||||
using adapter_type = adapter::detail::map<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct response_traits<std::unordered_map<Key, Hash, KeyEqual, Allocator>>
|
||||
{
|
||||
using response_type = std::unordered_map<Key, Hash, KeyEqual, Allocator>;
|
||||
using adapter_type = adapter::detail::map<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<void>
|
||||
{
|
||||
using response_type = void;
|
||||
using adapter_type = adapter::detail::ignore;
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
|
||||
assigner<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[0] = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
class flat_transaction_adapter {
|
||||
private:
|
||||
using variant_type =
|
||||
boost::mp11::mp_rename<boost::mp11::mp_transform<response_traits_t, Tuple>, std::variant>;
|
||||
|
||||
std::size_t i_ = 0;
|
||||
std::size_t aggregate_size_ = 0;
|
||||
std::array<variant_type, std::tuple_size<Tuple>::value> adapters_;
|
||||
|
||||
public:
|
||||
flat_transaction_adapter(Tuple* r)
|
||||
{ assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r); }
|
||||
|
||||
void count(type t, std::size_t aggregate_size, std::size_t depth)
|
||||
{
|
||||
if (depth == 1) {
|
||||
if (is_aggregate(t))
|
||||
aggregate_size_ = aggregate_size;
|
||||
else
|
||||
++i_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (--aggregate_size_ == 0)
|
||||
++i_;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
if (depth == 0) {
|
||||
if (aggregate_size != std::tuple_size<Tuple>::value) {
|
||||
ec = adapter::error::incompatible_tuple_size;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::visit([&](auto& arg){arg(t, aggregate_size, depth, data, size, ec);}, adapters_[i_]);
|
||||
count(t, aggregate_size, depth);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
|
||||
// The adapter of responses to transactions, move it to its own header?
|
||||
template <class... Ts>
|
||||
struct response_traits<std::tuple<Ts...>>
|
||||
{
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = adapter::detail::flat_transaction_adapter<response_type>;
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,157 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/detail/composer.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** @brief Creates a Redis request from user data.
|
||||
* \ingroup any
|
||||
*
|
||||
* 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
|
||||
* std::string request;
|
||||
* auto sr = make_serializer(request);
|
||||
* sr.push(command::hello, 3);
|
||||
* sr.push(command::flushall);
|
||||
* sr.push(command::ping);
|
||||
* sr.push(command::incr, "key");
|
||||
* sr.push(command::quit);
|
||||
* co_await async_write(socket, buffer(request));
|
||||
* @endcode
|
||||
*
|
||||
* \tparam Storage The storage type. This is currently a \c std::string.
|
||||
* \tparam Command The command to serialize.
|
||||
*
|
||||
* \remarks Non-string types will be converted to string by using \c
|
||||
* to_string, which must be made available over ADL.
|
||||
*/
|
||||
|
||||
// NOTE: Consider adding an overload for containers.
|
||||
//
|
||||
// TODO: Should we detect any std::pair or tuple 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.
|
||||
template <class Storage, class Command>
|
||||
class serializer {
|
||||
private:
|
||||
Storage* request_;
|
||||
|
||||
public:
|
||||
/** \brief Constructor
|
||||
*
|
||||
* \param storage Object where the serialized request will be
|
||||
* stored.
|
||||
*/
|
||||
serializer(Storage& storage) : request_(&storage) {}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* \code
|
||||
* std::string request;
|
||||
* auto sr = make_serializer<command>(request);
|
||||
* sr.push(command::set, "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the \c set command with payload "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The redis command.
|
||||
* \param args The arguments of the Redis command.
|
||||
*
|
||||
*/
|
||||
template <class... Ts>
|
||||
void push(Command cmd, Ts const&... args)
|
||||
{
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
detail::add_header(*request_, 1 + pack_size);
|
||||
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
(detail::add_bulk(*request_, args), ...);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that require a key. For example
|
||||
*
|
||||
* \code{.cpp}
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
* , {"key3", "value3"}
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param key The key the Redis command refers to.
|
||||
* \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(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
auto constexpr size = detail::value_type_size<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(*request_, 2 + size * distance);
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
detail::add_bulk(*request_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(*request_, *begin);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that don't have a key. For
|
||||
* example
|
||||
*
|
||||
* \code
|
||||
* std::set<std::string> channels
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push(command::subscribe, std::cbegin(channels), std::cedn(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(Command cmd, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
auto constexpr size = detail::value_type_size<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(*request_, 1 + size * distance);
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(*request_, *begin);
|
||||
}
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,88 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief RESP3 types
|
||||
\ingroup any
|
||||
|
||||
The RESP3 full specification can be found at https://github.com/antirez/RESP3/blob/74adea588783e463c7e84793b325b088fe6edd1c/spec.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 Returns the string representation of the type.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
char const* to_string(type t);
|
||||
|
||||
/** \brief Writes the type to the output stream.
|
||||
* \ingroup operators
|
||||
* \param os Output stream.
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, type t);
|
||||
|
||||
/** \brief Returns true if the data type is an aggregate.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
bool is_aggregate(type t);
|
||||
|
||||
/** @brief Returns the element multilicity.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
*
|
||||
* For type map and attribute this value is 2, all other types have
|
||||
* 1.
|
||||
*/
|
||||
std::size_t element_multiplicity(type t);
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,79 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace aedis {
|
||||
namespace sentinel {
|
||||
|
||||
/** \brief Sentinel commands.
|
||||
* \ingroup any
|
||||
*
|
||||
* For a full list of commands see https://redis.io/topics/sentinel
|
||||
*
|
||||
* \remark The list of commands below are read from Redis with the
|
||||
* help of the command \c command.
|
||||
*/
|
||||
enum class command {
|
||||
/// https://redis.io/commands/acl
|
||||
acl,
|
||||
/// https://redis.io/commands/auth
|
||||
auth,
|
||||
/// https://redis.io/commands/client
|
||||
client,
|
||||
/// https://redis.io/commands/command
|
||||
command,
|
||||
/// https://redis.io/commands/hello
|
||||
hello,
|
||||
/// https://redis.io/commands/info
|
||||
info,
|
||||
/// https://redis.io/commands/ping
|
||||
ping,
|
||||
/// https://redis.io/commands/psubscribe
|
||||
psubscribe,
|
||||
/// https://redis.io/commands/publish
|
||||
publish,
|
||||
/// https://redis.io/commands/punsubscribe
|
||||
punsubscribe,
|
||||
/// https://redis.io/commands/role
|
||||
role,
|
||||
/// https://redis.io/topics/sentinel
|
||||
sentinel,
|
||||
/// https://redis.io/commands/shutdown
|
||||
shutdown,
|
||||
/// https://redis.io/commands/subscribe
|
||||
subscribe,
|
||||
/// https://redis.io/commands/unsubscribe
|
||||
unsubscribe,
|
||||
/// Unknown/invalid command.
|
||||
unknown
|
||||
};
|
||||
|
||||
/** \brief Converts a sentinel command to a string
|
||||
* \ingroup any
|
||||
*
|
||||
* \param c The command to convert.
|
||||
*/
|
||||
char const* to_string(command c);
|
||||
|
||||
/** \brief Write the text for a sentinel command name to an output stream.
|
||||
* \ingroup operators
|
||||
*
|
||||
* \param os Output stream.
|
||||
* \param c Sentinel command
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, command c);
|
||||
|
||||
/** \brief Returns true for sentinel commands with push response.
|
||||
* \ingroup any
|
||||
*/
|
||||
bool has_push_response(command cmd);
|
||||
|
||||
} // sentinel
|
||||
} // aedis
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/sentinel/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace sentinel {
|
||||
|
||||
char const* to_string(command c)
|
||||
{
|
||||
static char const* table[] = {
|
||||
"ACL",
|
||||
"AUTH",
|
||||
"CLIENT",
|
||||
"COMMAND",
|
||||
"HELLO",
|
||||
"INFO",
|
||||
"PING",
|
||||
"PSUBSCRIBE",
|
||||
"PUBLISH",
|
||||
"PUNSUBSCRIBE",
|
||||
"ROLE",
|
||||
"SENTINEL",
|
||||
"SHUTDOWN",
|
||||
"SUBSCRIBE",
|
||||
"UNSUBSCRIBE",
|
||||
};
|
||||
|
||||
return table[static_cast<int>(c)];
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, command c)
|
||||
{
|
||||
os << to_string(c);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool has_push_response(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::subscribe:
|
||||
case command::unsubscribe:
|
||||
case command::psubscribe:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // sentinel
|
||||
} // aedis
|
||||
@@ -1,15 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// Include this file in no more than one source file in your application.
|
||||
|
||||
#include <aedis/redis/impl/command.ipp>
|
||||
#include <aedis/sentinel/impl/command.ipp>
|
||||
#include <aedis/resp3/impl/type.ipp>
|
||||
#include <aedis/resp3/impl/node.ipp>
|
||||
#include <aedis/redis/experimental/impl/client.ipp>
|
||||
#include <aedis/resp3/detail/impl/parser.ipp>
|
||||
71
benchmarks/benchmarks.tex
Normal file
71
benchmarks/benchmarks.tex
Normal file
@@ -0,0 +1,71 @@
|
||||
\documentclass{article}
|
||||
\usepackage{pgfplots}
|
||||
\pgfrealjobname{echo}
|
||||
\pgfplotsset{compat=newest}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\beginpgfgraphicnamed{echo-f0}
|
||||
% time ./echo_server_client 1000 5000
|
||||
\begin{tikzpicture}[scale=1.0]
|
||||
\begin{axis}[
|
||||
y dir=reverse,
|
||||
%xbar stacked,
|
||||
xbar, xmin=0,
|
||||
%hide x axis,
|
||||
bar shift=0pt,
|
||||
width=15cm, height=6cm, enlarge y limits=0.5,
|
||||
title={TCP Echo Server Performance},
|
||||
xlabel={Seconds},
|
||||
symbolic y coords={Asio,Tokio,Go,Libuv,Nodejs},
|
||||
ytick=data,
|
||||
%bar width=1cm,
|
||||
nodes near coords,
|
||||
nodes near coords align={horizontal},
|
||||
]
|
||||
\addplot coordinates {
|
||||
(29.5,Asio)
|
||||
(30.7,Tokio)
|
||||
(35.6,Go)
|
||||
(43.6,Libuv)
|
||||
(74.2,Nodejs)
|
||||
};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\endpgfgraphicnamed
|
||||
|
||||
\beginpgfgraphicnamed{echo-f1}
|
||||
%$ time ./echo_server_client 1000 1000
|
||||
\begin{tikzpicture}[scale=1.0]
|
||||
\begin{axis}[
|
||||
y dir=reverse,
|
||||
%xbar stacked,
|
||||
xbar, xmin=0,
|
||||
%hide x axis,
|
||||
bar shift=0pt,
|
||||
width=12cm, height=6cm, enlarge y limits=0.5,
|
||||
title={TCP Echo Server Performance (over Redis)},
|
||||
xlabel={Seconds},
|
||||
symbolic y coords={Aedis,Rust-rs,Libuv,Node-redis,Go-redis},
|
||||
ytick=data,
|
||||
%bar width=1cm,
|
||||
nodes near coords,
|
||||
nodes near coords align={horizontal},
|
||||
]
|
||||
\addplot coordinates {
|
||||
(12.6,Aedis)
|
||||
(28.8,Node-redis)
|
||||
(352.4,Go-redis)
|
||||
};
|
||||
%\addplot coordinates {
|
||||
% (30.0,Asio)
|
||||
% (90.6,Rust-rs)
|
||||
% (0.0,Libuv)
|
||||
% (68.9,Nodejs)
|
||||
% (0.0,Go)
|
||||
%};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\endpgfgraphicnamed
|
||||
|
||||
\end{document}
|
||||
7
benchmarks/c/libuv/README.md
Normal file
7
benchmarks/c/libuv/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
This example was taken from
|
||||
|
||||
https://github.com/libuv/libuv/tree/v1.x/docs/code/tcp-echo-server
|
||||
|
||||
To build it run, for example
|
||||
|
||||
$ gcc echo_server_direct.c -luv -O2 -o echo_server_direct
|
||||
87
benchmarks/c/libuv/echo_server_direct.c
Normal file
87
benchmarks/c/libuv/echo_server_direct.c
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <uv.h>
|
||||
|
||||
#define DEFAULT_PORT 55555
|
||||
#define DEFAULT_BACKLOG 1024
|
||||
|
||||
uv_loop_t *loop;
|
||||
struct sockaddr_in addr;
|
||||
|
||||
typedef struct {
|
||||
uv_write_t req;
|
||||
uv_buf_t buf;
|
||||
} write_req_t;
|
||||
|
||||
void free_write_req(uv_write_t *req) {
|
||||
write_req_t *wr = (write_req_t*) req;
|
||||
free(wr->buf.base);
|
||||
free(wr);
|
||||
}
|
||||
|
||||
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
||||
buf->base = (char*) malloc(suggested_size);
|
||||
buf->len = suggested_size;
|
||||
}
|
||||
|
||||
void on_close(uv_handle_t* handle) {
|
||||
free(handle);
|
||||
}
|
||||
|
||||
void echo_write(uv_write_t *req, int status) {
|
||||
if (status) {
|
||||
fprintf(stderr, "Write error %s\n", uv_strerror(status));
|
||||
}
|
||||
free_write_req(req);
|
||||
}
|
||||
|
||||
void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {
|
||||
if (nread > 0) {
|
||||
write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t));
|
||||
req->buf = uv_buf_init(buf->base, nread);
|
||||
uv_write((uv_write_t*) req, client, &req->buf, 1, echo_write);
|
||||
return;
|
||||
}
|
||||
if (nread < 0) {
|
||||
if (nread != UV_EOF)
|
||||
fprintf(stderr, "Read error %s\n", uv_err_name(nread));
|
||||
uv_close((uv_handle_t*) client, on_close);
|
||||
}
|
||||
|
||||
free(buf->base);
|
||||
}
|
||||
|
||||
void on_new_connection(uv_stream_t *server, int status) {
|
||||
if (status < 0) {
|
||||
fprintf(stderr, "New connection error %s\n", uv_strerror(status));
|
||||
// error!
|
||||
return;
|
||||
}
|
||||
|
||||
uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
|
||||
uv_tcp_init(loop, client);
|
||||
if (uv_accept(server, (uv_stream_t*) client) == 0) {
|
||||
uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
|
||||
}
|
||||
else {
|
||||
uv_close((uv_handle_t*) client, on_close);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
loop = uv_default_loop();
|
||||
|
||||
uv_tcp_t server;
|
||||
uv_tcp_init(loop, &server);
|
||||
|
||||
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);
|
||||
|
||||
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
|
||||
int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);
|
||||
if (r) {
|
||||
fprintf(stderr, "Listen error %s\n", uv_strerror(r));
|
||||
return 1;
|
||||
}
|
||||
return uv_run(loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
68
benchmarks/cpp/asio/echo_server_client.cpp
Normal file
68
benchmarks/cpp/asio/echo_server_client.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/* 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 <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
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)
|
||||
{
|
||||
try {
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
tcp_socket socket{ex};
|
||||
co_await socket.async_connect(ep);
|
||||
|
||||
std::string buffer;
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
co_await net::async_write(socket, net::buffer(msg));
|
||||
auto n = co_await net::async_read_until(socket, dbuffer, "\n");
|
||||
//std::printf("> %s", buffer.data());
|
||||
dbuffer.consume(n);
|
||||
}
|
||||
|
||||
//std::printf("Ok: %s", msg.data());
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try {
|
||||
int sessions = 1;
|
||||
int msgs = 1;
|
||||
|
||||
if (argc == 3) {
|
||||
sessions = std::stoi(argv[1]);
|
||||
msgs = std::stoi(argv[2]);
|
||||
}
|
||||
|
||||
net::io_context ioc;
|
||||
|
||||
tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve("127.0.0.1", "55555");
|
||||
auto ep = *std::begin(res);
|
||||
|
||||
for (int i = 0; i < sessions; ++i)
|
||||
net::co_spawn(ioc, example(ep, "Some message\n", msgs), 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 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
63
benchmarks/cpp/asio/echo_server_direct.cpp
Normal file
63
benchmarks/cpp/asio/echo_server_direct.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// echo_server.cpp
|
||||
// ~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace this_coro = net::this_coro;
|
||||
using net::ip::tcp;
|
||||
using net::detached;
|
||||
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>;
|
||||
constexpr net::use_awaitable_t<executor_type> use_awaitable;
|
||||
|
||||
awaitable_type echo(tcp_socket socket)
|
||||
{
|
||||
try {
|
||||
char data[1024];
|
||||
for (;;) {
|
||||
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) {
|
||||
//std::printf("echo Exception: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
awaitable_type listener()
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
tcp_acceptor acceptor(ex, {tcp::v4(), 55555});
|
||||
for (;;) {
|
||||
tcp_socket socket = co_await acceptor.async_accept(use_awaitable);
|
||||
co_spawn(ex, echo(std::move(socket)), detached);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context io_context{BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO};
|
||||
co_spawn(io_context, listener(), detached);
|
||||
io_context.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::printf("Exception: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
43
benchmarks/go/echo_server_direct.go
Normal file
43
benchmarks/go/echo_server_direct.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func echo(conn net.Conn) {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
break;
|
||||
}
|
||||
|
||||
conn.Write(buf[:n])
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
l, err := net.Listen("tcp", "0.0.0.0:55555")
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
continue
|
||||
}
|
||||
go echo(conn)
|
||||
}
|
||||
}
|
||||
54
benchmarks/go/echo_server_over_redis.go
Normal file
54
benchmarks/go/echo_server_over_redis.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
var rdb = redis.NewClient(&redis.Options{Addr: "db.occase.de:6379", Password: "", DB: 0,})
|
||||
|
||||
func echo(conn net.Conn) {
|
||||
r := bufio.NewReader(conn)
|
||||
for {
|
||||
line, err := r.ReadBytes(byte('\n'))
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
case io.EOF:
|
||||
default:
|
||||
fmt.Println("ERROR", err)
|
||||
}
|
||||
|
||||
err2 := rdb.Ping(ctx).Err()
|
||||
if err2 != nil {
|
||||
fmt.Println("ERROR", err2)
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
conn.Write(line)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
l, err := net.Listen("tcp", "0.0.0.0:55555")
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go echo(conn)
|
||||
}
|
||||
}
|
||||
105
benchmarks/java/echo_server_direct/TcpEchoServer.java
Normal file
105
benchmarks/java/echo_server_direct/TcpEchoServer.java
Normal file
@@ -0,0 +1,105 @@
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TcpEchoServer {
|
||||
|
||||
public static int DEFAULT_PORT = 55555;
|
||||
|
||||
public static void main(String[] args) {
|
||||
int port;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
}
|
||||
|
||||
catch (Exception ex) {
|
||||
port = DEFAULT_PORT;
|
||||
}
|
||||
|
||||
//System.out.println("Listening for connections on port " + port);
|
||||
ServerSocketChannel serverChannel;
|
||||
Selector selector;
|
||||
try {
|
||||
serverChannel = ServerSocketChannel.open( );
|
||||
ServerSocket ss = serverChannel.socket( );
|
||||
InetSocketAddress address = new InetSocketAddress(port);
|
||||
ss.bind(address);
|
||||
serverChannel.configureBlocking(false);
|
||||
selector = Selector.open( );
|
||||
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
ex.printStackTrace( );
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
selector.select( );
|
||||
}
|
||||
catch (IOException ex) {
|
||||
ex.printStackTrace( );
|
||||
break;
|
||||
}
|
||||
Set readyKeys = selector.selectedKeys( );
|
||||
Iterator iterator = readyKeys.iterator( );
|
||||
while (iterator.hasNext( )) {
|
||||
SelectionKey key = (SelectionKey) iterator.next( );
|
||||
iterator.remove( );
|
||||
try {
|
||||
if (key.isAcceptable( )) {
|
||||
ServerSocketChannel server = (ServerSocketChannel ) key.channel( );
|
||||
SocketChannel client = server.accept( );
|
||||
//System.out.println("Accepted connection from " + client);
|
||||
client.configureBlocking(false);
|
||||
SelectionKey clientKey = client.register(
|
||||
selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(100);
|
||||
clientKey.attach(buffer);
|
||||
//System.out.println(buffer.toString());
|
||||
|
||||
}
|
||||
|
||||
if (key.isReadable( )) {
|
||||
SocketChannel client = (SocketChannel) key.channel( );
|
||||
ByteBuffer output = (ByteBuffer) key.attachment( );
|
||||
client.read(output);
|
||||
}
|
||||
|
||||
if (key.isWritable( )) {
|
||||
SocketChannel client = (SocketChannel) key.channel( );
|
||||
ByteBuffer output = (ByteBuffer) key.attachment( );
|
||||
output.flip( );
|
||||
client.write(output);
|
||||
output.compact( );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
catch (IOException ex) {
|
||||
key.cancel( );
|
||||
try {
|
||||
key.channel().close();
|
||||
}
|
||||
|
||||
catch (IOException cex) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
var net = require('net');
|
||||
|
||||
net.createServer(function(socket){
|
||||
socket.on('data', function(data){
|
||||
socket.write(data.toString())
|
||||
});
|
||||
}).listen(55555);
|
||||
2
benchmarks/nodejs/echo_server_direct/package.json
Normal file
2
benchmarks/nodejs/echo_server_direct/package.json
Normal file
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createClient } from 'redis';
|
||||
import * as net from 'net';
|
||||
|
||||
const client = createClient({url: 'redis://db.occase.de:6379' });
|
||||
client.on('error', (err) => console.log('Redis Client Error', err));
|
||||
await client.connect();
|
||||
|
||||
net.createServer(function(socket){
|
||||
socket.on('data', async function(data) {
|
||||
const value = await client.ping(data.toString());
|
||||
socket.write(data)
|
||||
});
|
||||
}).listen(55555);
|
||||
169
benchmarks/nodejs/echo_server_over_redis/package-lock.json
generated
Normal file
169
benchmarks/nodejs/echo_server_over_redis/package-lock.json
generated
Normal file
@@ -0,0 +1,169 @@
|
||||
{
|
||||
"name": "echo_server_over_redis",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"redis": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz",
|
||||
"integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz",
|
||||
"integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz",
|
||||
"integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==",
|
||||
"dependencies": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.2.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.0.6",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/client": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz",
|
||||
"integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==",
|
||||
"requires": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
}
|
||||
},
|
||||
"@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/search": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz",
|
||||
"integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"requires": {}
|
||||
},
|
||||
"cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg=="
|
||||
},
|
||||
"redis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz",
|
||||
"integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==",
|
||||
"requires": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.2.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.0.6",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
6
benchmarks/nodejs/echo_server_over_redis/package.json
Normal file
6
benchmarks/nodejs/echo_server_over_redis/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"redis": "^4.2.0"
|
||||
}
|
||||
}
|
||||
309
benchmarks/rust/echo_server_direct/Cargo.lock
generated
Normal file
309
benchmarks/rust/echo_server_direct/Cargo.lock
generated
Normal file
@@ -0,0 +1,309 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "echo_server_direct"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f53dc8cf16a769a6f677e09e7ff2cd4be1ea0f48754aac39520536962011de0d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
9
benchmarks/rust/echo_server_direct/Cargo.toml
Normal file
9
benchmarks/rust/echo_server_direct/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "echo_server_direct"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
35
benchmarks/rust/echo_server_direct/src/main.rs
Normal file
35
benchmarks/rust/echo_server_direct/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:55555").await?;
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
// In a loop, read data from the socket and write the data back.
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
// socket closed
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Write the data back
|
||||
if let Err(e) = socket.write_all(&buf[0..n]).await {
|
||||
eprintln!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
11
benchmarks/rust/echo_server_over_redis/Cargo.toml
Normal file
11
benchmarks/rust/echo_server_over_redis/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "echo_server_over_redis"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
redis = { version = "0.21.5", features = ["tokio-comp"] }
|
||||
futures = "0.3"
|
||||
44
benchmarks/rust/echo_server_over_redis/src/main.rs
Normal file
44
benchmarks/rust/echo_server_over_redis/src/main.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::Mutex;
|
||||
use std::sync::{Arc};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:55555").await?;
|
||||
let client = redis::Client::open("redis://db.occase.de/").unwrap();
|
||||
let con = Arc::new(Mutex::new(client.get_async_connection().await?));
|
||||
|
||||
loop {
|
||||
let conn = Arc::clone(&con);
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 1024];
|
||||
|
||||
loop {
|
||||
let n = match socket.read(&mut buf).await {
|
||||
Ok(n) if n == 0 => return,
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
eprintln!("failed to read from socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut local_conn = conn.lock().await;
|
||||
|
||||
let result =
|
||||
redis::cmd("PING")
|
||||
.arg(&buf[0..n])
|
||||
.query_async::<redis::aio::Connection, String>(&mut local_conn).await.unwrap();
|
||||
|
||||
if let Err(e) = socket.write_all(result.as_bytes()).await {
|
||||
eprintln!("failed to write to socket; err = {:?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
4
cmake/AedisConfig.cmake.in
Normal file
4
cmake/AedisConfig.cmake.in
Normal file
@@ -0,0 +1,4 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Aedis.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
22
configure.ac
22
configure.ac
@@ -1,22 +0,0 @@
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([Aedis], [0.0.1], [mzimbres@gmail.com])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
#AC_CONFIG_SRCDIR([src/aedis.cpp])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AM_INIT_AUTOMAKE([-Wall foreign])
|
||||
|
||||
# Checks for programs.
|
||||
AC_PROG_CXX
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_LN_S
|
||||
AC_PROG_RANLIB
|
||||
AM_PROG_AR
|
||||
|
||||
AX_BOOST_BASE([1.78],, AC_MSG_ERROR[Boost not found])
|
||||
|
||||
AC_CHECK_HEADER_STDBOOL
|
||||
AC_TYPE_UINT64_T
|
||||
AC_CHECK_TYPES([ptrdiff_t])
|
||||
|
||||
AC_CONFIG_FILES([Makefile doc/Doxyfile])
|
||||
AC_OUTPUT
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,9 +24,7 @@ div.contents {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/*
|
||||
code
|
||||
{
|
||||
background-color:#EFD25E;
|
||||
background-color:#fffbeb;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,123 +1,106 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "unistd.h"
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#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>
|
||||
|
||||
#include "lib/user_session.hpp"
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::resp3::adapt;
|
||||
using aedis::resp3::experimental::client;
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::resp3::type;
|
||||
using aedis::user_session;
|
||||
using aedis::user_session_base;
|
||||
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>;
|
||||
|
||||
// TODO: Delete sessions that have expired.
|
||||
class receiver : public std::enable_shared_from_this<receiver> {
|
||||
public:
|
||||
private:
|
||||
std::vector<node> resps_;
|
||||
std::vector<std::weak_ptr<user_session_base>> sessions_;
|
||||
// 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.
|
||||
|
||||
public:
|
||||
void on_message(std::error_code ec, command cmd)
|
||||
{
|
||||
if (ec) {
|
||||
std::cerr << "Error: " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case command::incr:
|
||||
{
|
||||
std::cout << "Message so far: " << resps_.front().data << std::endl;
|
||||
} break;
|
||||
case command::unknown: // Push
|
||||
{
|
||||
for (auto& weak: sessions_) {
|
||||
if (auto session = weak.lock()) {
|
||||
session->deliver(resps_.at(3).data);
|
||||
} else {
|
||||
std::cout << "Session expired." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
default: { /* Ignore */ }
|
||||
}
|
||||
|
||||
resps_.clear();
|
||||
}
|
||||
|
||||
auto get_extended_adapter()
|
||||
{
|
||||
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
|
||||
{ return adapter(t, aggregate_size, depth, data, size, ec); };
|
||||
}
|
||||
|
||||
auto add(std::shared_ptr<user_session_base> session)
|
||||
{ sessions_.push_back(session); }
|
||||
};
|
||||
|
||||
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
|
||||
// Receives messages from other users.
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> conn)
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
co_await db->engage(std::move(socket));
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> listener()
|
||||
// Subscribes to the channels when a new connection is stablished.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
|
||||
|
||||
auto recv = std::make_shared<receiver>();
|
||||
auto on_db_msg = [recv](std::error_code ec, command cmd)
|
||||
{ recv->on_message(ec, cmd); };
|
||||
|
||||
auto db = std::make_shared<client>(ex);
|
||||
db->set_extended_adapter(recv->get_extended_adapter());
|
||||
db->set_msg_callback(on_db_msg);
|
||||
net::co_spawn(ex, connection_manager(db), net::detached);
|
||||
db->send(command::subscribe, "channel");
|
||||
|
||||
auto on_user_msg = [db](std::string const& msg)
|
||||
{
|
||||
db->send(command::publish, "channel", msg);
|
||||
db->send(command::incr, "message-counter");
|
||||
};
|
||||
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 (;;) {
|
||||
auto socket = co_await acceptor.async_accept(net::use_awaitable);
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
recv->add(session);
|
||||
session->start(on_user_msg);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
// 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(); });
|
||||
co_spawn(ioc, listener(), net::detached);
|
||||
|
||||
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)
|
||||
|
||||
125
examples/containers.cpp
Normal file
125
examples/containers.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
/* 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)
|
||||
@@ -1,121 +1,87 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#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>
|
||||
|
||||
#include "lib/user_session.hpp"
|
||||
#include "lib/net_utils.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>;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::user_session;
|
||||
using aedis::user_session_base;
|
||||
using aedis::resp3::node;
|
||||
using aedis::resp3::adapt;
|
||||
using aedis::resp3::experimental::client;
|
||||
using aedis::resp3::type;
|
||||
|
||||
class receiver : public std::enable_shared_from_this<receiver> {
|
||||
private:
|
||||
std::vector<node> resps_;
|
||||
std::queue<std::weak_ptr<user_session_base>> sessions_;
|
||||
|
||||
public:
|
||||
void on_message(std::error_code ec, command cmd)
|
||||
{
|
||||
if (ec) {
|
||||
std::cerr << "Error: " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case command::ping:
|
||||
{
|
||||
if (auto session = sessions_.front().lock()) {
|
||||
session->deliver(resps_.front().data);
|
||||
} else {
|
||||
std::cout << "Session expired." << std::endl;
|
||||
}
|
||||
|
||||
sessions_.pop();
|
||||
} break;
|
||||
case command::incr:
|
||||
{
|
||||
std::cout << "Echos so far: " << resps_.front().data << std::endl;
|
||||
} break;
|
||||
default: { /* Ignore */; }
|
||||
}
|
||||
|
||||
resps_.clear();
|
||||
}
|
||||
|
||||
auto get_extended_adapter()
|
||||
{
|
||||
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
|
||||
{ return adapter(t, aggregate_size, depth, data, size, ec); };
|
||||
}
|
||||
|
||||
void add_user_session(std::shared_ptr<user_session_base> session)
|
||||
{ sessions_.push(session); }
|
||||
};
|
||||
|
||||
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
|
||||
awaitable_type echo_server_session(tcp_socket socket, std::shared_ptr<connection> db)
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
co_await db->engage(std::move(socket));
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> listener()
|
||||
awaitable_type listener(std::shared_ptr<connection> db)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
|
||||
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);
|
||||
}
|
||||
|
||||
auto recv = std::make_shared<receiver>();
|
||||
auto on_db_msg = [recv](std::error_code ec, command cmd)
|
||||
{ recv->on_message(ec, cmd); };
|
||||
|
||||
auto db = std::make_shared<client>(ex);
|
||||
db->set_extended_adapter(recv->get_extended_adapter());
|
||||
db->set_msg_callback(on_db_msg);
|
||||
net::co_spawn(ex, connection_manager(db), net::detached);
|
||||
|
||||
for (;;) {
|
||||
auto socket = co_await acceptor.async_accept(net::use_awaitable);
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
|
||||
auto on_user_msg = [db, recv, session](std::string const& msg)
|
||||
{
|
||||
db->send(command::ping, msg);
|
||||
db->send(command::incr, "echo-counter");
|
||||
recv->add_user_session(session);
|
||||
};
|
||||
|
||||
session->start(on_user_msg);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
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(), net::detached);
|
||||
signals.async_wait([&](auto, auto) {
|
||||
ioc.stop();
|
||||
});
|
||||
|
||||
co_spawn(ioc, listener(db), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> containers()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
std::map<std::string, std::string> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// The expected responses
|
||||
int hset;
|
||||
std::vector<std::string> hgetall1;
|
||||
std::map<std::string, std::string> hgetall2;
|
||||
std::unordered_map<std::string, std::string> hgetall3;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hset));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall1));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall2));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall3));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "hset: " << hset;
|
||||
std::cout << "\nhgetall (as vector): ";
|
||||
for (auto const& e: hgetall1) std::cout << e << ", ";
|
||||
std::cout << "\nhgetall (as map): ";
|
||||
for (auto const& e: hgetall2) std::cout << e.first << " ==> " << e.second << "; ";
|
||||
std::cout << "\nhgetall (as unordered_map): ";
|
||||
for (auto const& e: hgetall3) std::cout << e.first << " ==> " << e.second << "; ";
|
||||
std::cout << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, containers(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,68 +1,43 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
auto const logger = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> ping()
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect(); // See lib/net_utils.hpp
|
||||
boost::asio::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::ping);
|
||||
sr.push(command::incr, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
// Responses we are interested in.
|
||||
int incr;
|
||||
std::string ping;
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
conn.async_exec(req, adapt(resp), logger);
|
||||
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
|
||||
|
||||
// Reads the responses to ping and incr, ignore the others.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(ping));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(incr));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
ioc.run();
|
||||
|
||||
// Print the responses.
|
||||
std::cout
|
||||
<< "ping: " << ping << "\n"
|
||||
<< "incr: " << incr << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
std::cout << std::get<0>(resp) << std::endl;
|
||||
} catch (...) {
|
||||
std::cerr << "Error" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, ping(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
62
examples/intro_sync.cpp
Normal file
62
examples/intro_sync.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
57
examples/intro_tls.cpp
Normal file
57
examples/intro_tls.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/ssl/connection.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::ssl::connection<net::ssl::stream<net::ip::tcp::socket>>;
|
||||
|
||||
auto const logger = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
|
||||
net::ssl::context ctx{net::ssl::context::sslv23};
|
||||
|
||||
connection conn{ioc, ctx};
|
||||
conn.next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn.next_layer().set_verify_callback(verify_certificate);
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
conn.async_exec(req, adapt(resp), logger);
|
||||
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
|
||||
|
||||
ioc.run();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp) << std::endl;
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include <aedis/src.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using resp3::node;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> key_expiration()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
// Creates and sends the first request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::set, "key", "Some payload", "EX", "2");
|
||||
sr.push(command::get, "key");
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Will hold the response to get.
|
||||
std::optional<std::string> get;
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
|
||||
|
||||
std::cout
|
||||
<< "Before expiration: " << get.has_value() << ", "
|
||||
<< *get << std::endl;
|
||||
|
||||
// Waits some seconds for the key to expire.
|
||||
timer tm{socket.get_executor(), std::chrono::seconds{3}};
|
||||
co_await tm.async_wait();
|
||||
|
||||
// Creates a request to get after expiration.
|
||||
get.reset(); request.clear();
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the response to the second request.
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
|
||||
|
||||
// Reading without an optional will result in an error.
|
||||
std::string str;
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer),
|
||||
adapt(str), net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
// Quit
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer));
|
||||
|
||||
std::cout << "After expiration (optional): " << get.has_value() << "\n";
|
||||
std::cout << "After expiration (non-optional): " << ec.message() << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, key_expiration(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
|
||||
using tcp_socket = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::socket>;
|
||||
using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
|
||||
using timer = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::steady_timer>;
|
||||
|
||||
aedis::net::awaitable<tcp_socket>
|
||||
connect(
|
||||
std::string host = "127.0.0.1",
|
||||
std::string port = "6379")
|
||||
{
|
||||
auto ex = co_await aedis::net::this_coro::executor;
|
||||
tcp_resolver resolver{ex};
|
||||
auto const res = co_await resolver.async_resolve(host, port);
|
||||
tcp_socket socket{ex};
|
||||
co_await aedis::net::async_connect(socket, res);
|
||||
co_return std::move(socket);
|
||||
}
|
||||
|
||||
//net::awaitable<void>
|
||||
//client::connection_manager()
|
||||
//{
|
||||
// using namespace aedis::net::experimental::awaitable_operators;
|
||||
// using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
|
||||
//
|
||||
// for (;;) {
|
||||
// tcp_resolver resolver{socket_.get_executor()};
|
||||
// auto const res = co_await resolver.async_resolve("127.0.0.1", "6379");
|
||||
// co_await net::async_connect(socket_, res);
|
||||
//
|
||||
// co_await say_hello();
|
||||
//
|
||||
// timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
// co_await (reader() && writer());
|
||||
//
|
||||
// socket_.close();
|
||||
// timer_.cancel();
|
||||
//
|
||||
// timer_.expires_after(std::chrono::seconds{1});
|
||||
// boost::system::error_code ec;
|
||||
// co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
|
||||
// An example user session.
|
||||
|
||||
namespace aedis
|
||||
{
|
||||
|
||||
// Base class for user sessions.
|
||||
struct user_session_base {
|
||||
virtual ~user_session_base() {}
|
||||
virtual void deliver(std::string const& msg) = 0;
|
||||
};
|
||||
|
||||
class user_session:
|
||||
public user_session_base,
|
||||
public std::enable_shared_from_this<user_session> {
|
||||
public:
|
||||
user_session(net::ip::tcp::socket socket)
|
||||
: socket_(std::move(socket))
|
||||
, timer_(socket_.get_executor())
|
||||
{ timer_.expires_at(std::chrono::steady_clock::time_point::max()); }
|
||||
|
||||
void start(std::function<void(std::string const&)> on_msg)
|
||||
{
|
||||
co_spawn(socket_.get_executor(),
|
||||
[self = shared_from_this(), on_msg]{ return self->reader(on_msg); },
|
||||
net::detached);
|
||||
|
||||
co_spawn(socket_.get_executor(),
|
||||
[self = shared_from_this()]{ return self->writer(); },
|
||||
net::detached);
|
||||
}
|
||||
|
||||
void deliver(std::string const& msg)
|
||||
{
|
||||
write_msgs_.push_back(msg);
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
private:
|
||||
net::awaitable<void>
|
||||
reader(std::function<void(std::string const&)> on_msg)
|
||||
{
|
||||
try {
|
||||
for (std::string msg;;) {
|
||||
auto const n = co_await net::async_read_until(socket_, net::dynamic_buffer(msg, 1024), "\n", net::use_awaitable);
|
||||
on_msg(msg);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
} catch (std::exception&) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> writer()
|
||||
{
|
||||
try {
|
||||
while (socket_.is_open()) {
|
||||
if (write_msgs_.empty()) {
|
||||
boost::system::error_code ec;
|
||||
co_await timer_.async_wait(redirect_error(net::use_awaitable, ec));
|
||||
} else {
|
||||
co_await net::async_write(socket_, net::buffer(write_msgs_.front()), net::use_awaitable);
|
||||
write_msgs_.pop_front();
|
||||
}
|
||||
}
|
||||
} catch (std::exception&) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
socket_.close();
|
||||
timer_.cancel();
|
||||
}
|
||||
|
||||
net::ip::tcp::socket socket_;
|
||||
net::steady_timer timer_;
|
||||
std::deque<std::string> write_msgs_;
|
||||
};
|
||||
|
||||
} // aedis
|
||||
@@ -1,89 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> ping()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
// Creates and sends the request.
|
||||
auto vec = {1, 2, 3, 4, 5, 6};
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::rpush, "key", std::cbegin(vec), std::cend(vec));
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
int rpush;
|
||||
std::vector<std::string> svec;
|
||||
std::list<std::string> slist;
|
||||
std::deque<std::string> sdeq;
|
||||
std::vector<int> ivec;
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(rpush)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(svec));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(slist));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(sdeq));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(ivec));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "rpush: " << rpush;
|
||||
std::cout << "\nlrange (as vector): ";
|
||||
for (auto e: svec) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as list): ";
|
||||
for (auto e: slist) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as deque): ";
|
||||
for (auto e: sdeq) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as vector<int>): ";
|
||||
for (auto e: ivec) std::cout << e << " ";
|
||||
std::cout << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, ping(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
53
examples/low_level_sync.cpp
Normal file
53
examples/low_level_sync.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <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);
|
||||
}
|
||||
}
|
||||
65
examples/print.hpp
Normal file
65
examples/print.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
// Some functions to make the examples less repetitive.
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
|
||||
void print_aggr(std::vector<aedis::resp3::node<std::string>>& v)
|
||||
{
|
||||
if (std::empty(v))
|
||||
return;
|
||||
|
||||
auto const m = aedis::resp3::element_multiplicity(v.front().data_type);
|
||||
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
|
||||
std::cout << v[i + 1].value << " ";
|
||||
std::cout << "\n";
|
||||
v.clear();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void print(std::vector<T> const& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void print(std::set<T> const& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << "\n";
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
void print(std::map<T, U> const& cont)
|
||||
{
|
||||
for (auto const& e: cont)
|
||||
std::cout << e.first << ": " << e.second << "\n";
|
||||
}
|
||||
|
||||
void print(std::string const& e)
|
||||
{
|
||||
std::cout << e << std::endl;
|
||||
}
|
||||
|
||||
void print_push(std::vector<aedis::resp3::node<std::string>>& resp)
|
||||
{
|
||||
std::cout
|
||||
<< "Push type: " << resp.at(1).value << "\n"
|
||||
<< "Channel: " << resp.at(2).value << "\n"
|
||||
<< "Message: " << resp.at(3).value << "\n"
|
||||
<< std::endl;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using resp3::type;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
// An adapter that prints the data it receives in the screen.
|
||||
struct myadapter {
|
||||
void operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code&)
|
||||
{
|
||||
std::cout
|
||||
<< "Type: " << t << "\n"
|
||||
<< "Aggregate_size: " << aggregate_size << "\n"
|
||||
<< "Depth: " << depth << "\n"
|
||||
<< "Data: " << std::string_view(data, size) << "\n"
|
||||
<< "----------------------" << "\n";
|
||||
}
|
||||
};
|
||||
|
||||
net::awaitable<void> adapter_example()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
auto list = {"one", "two", "three"};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::rpush, "key", std::cbegin(list), std::cend(list));
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), myadapter{}); // lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, adapter_example(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,109 +1,117 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <charconv>
|
||||
#include <set>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/src.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
using namespace boost::json;
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::make_serializer;
|
||||
using aedis::redis::command;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
// Define the to_string and from_string functions for your own data
|
||||
// types.
|
||||
|
||||
// The struct we would like to store in redis using our own
|
||||
// serialization.
|
||||
struct mydata {
|
||||
int a;
|
||||
int b;
|
||||
struct user {
|
||||
std::string name;
|
||||
std::string age;
|
||||
std::string country;
|
||||
};
|
||||
|
||||
// Serializes to Tab Separated Value (TSV).
|
||||
std::string to_string(mydata const& obj)
|
||||
void tag_invoke(value_from_tag, value& jv, user const& u)
|
||||
{
|
||||
return std::to_string(obj.a) + '\t' + std::to_string(obj.b);
|
||||
jv =
|
||||
{ {"name", u.name}
|
||||
, {"age", u.age}
|
||||
, {"country", u.country}
|
||||
};
|
||||
}
|
||||
|
||||
// Deserializes TSV.
|
||||
void
|
||||
from_string(
|
||||
mydata& obj,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code& ec)
|
||||
template<class T>
|
||||
void extract(object const& obj, T& t, boost::string_view key)
|
||||
{
|
||||
auto const* end = data + size;
|
||||
auto const* pos = std::find(data, end, '\t');
|
||||
assert(pos != end); // Or use your own error code.
|
||||
|
||||
auto const res1 = std::from_chars(data, pos, obj.a);
|
||||
if (res1.ec != std::errc()) {
|
||||
ec = std::make_error_code(res1.ec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const res2 = std::from_chars(pos + 1, end, obj.b);
|
||||
if (res2.ec != std::errc()) {
|
||||
ec = std::make_error_code(res2.ec);
|
||||
return;
|
||||
}
|
||||
t = value_to<T>(obj.at(key));
|
||||
}
|
||||
|
||||
net::awaitable<void> serialization()
|
||||
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 {
|
||||
auto socket = co_await connect();
|
||||
net::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
mydata obj{21, 22};
|
||||
std::set<user> users
|
||||
{{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}};
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::set, "key", obj);
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
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");
|
||||
|
||||
// The response.
|
||||
mydata get;
|
||||
std::tuple<aedis::ignore, int, std::set<user>, std::string> resp;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(get));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Print the responses.
|
||||
std::cout << "get: a = " << get.a << ", b = " << get.b << "\n";
|
||||
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 << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, serialization(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> containers()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
std::set<std::string> set
|
||||
{"one", "two", "three", "four"};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::sadd, "key", std::cbegin(set), std::cend(set));
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
int sadd;
|
||||
std::vector<std::string> smembers1;
|
||||
std::set<std::string> smembers2;
|
||||
std::unordered_set<std::string> smembers3;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(sadd));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers1));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers2));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers3));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "sadd: " << sadd;
|
||||
std::cout << "\nsmembers (as vector): ";
|
||||
for (auto const& e: smembers1) std::cout << e << " ";
|
||||
std::cout << "\nsmembers (as set): ";
|
||||
for (auto const& e: smembers2) std::cout << e << " ";
|
||||
std::cout << "\nsmembers (as unordered_set): ";
|
||||
for (auto const& e: smembers3) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, containers(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,86 +1,103 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <tuple>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#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>
|
||||
|
||||
#include "lib/net_utils.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>;
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using resp3::node;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
/* In this example we send a subscription to a channel and start
|
||||
reading server side messages indefinitely.
|
||||
|
||||
Notice we store the id of the connection (attributed by the redis
|
||||
server) to be able to identify it (in the logs for example).
|
||||
|
||||
After starting the example you can test it by sending messages with
|
||||
redis-cli like this
|
||||
|
||||
$ redis-cli -3
|
||||
127.0.0.1:6379> PUBLISH channel1 some-message
|
||||
(integer) 3
|
||||
127.0.0.1:6379>
|
||||
|
||||
The messages will then appear on the terminal you are running the
|
||||
example.
|
||||
/* 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
|
||||
*/
|
||||
net::awaitable<void> subscriber()
|
||||
|
||||
// Receives pushes.
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> conn)
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, "3");
|
||||
sr.push(command::subscribe, "channel1", "channel2");
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
std::vector<node> resp;
|
||||
|
||||
// Reads the response to the hello command.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
|
||||
// Saves the id of this connection.
|
||||
auto const id = resp.at(8).data;
|
||||
|
||||
// Loops to receive server pushes.
|
||||
for (;;) {
|
||||
resp.clear();
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
|
||||
std::cout
|
||||
<< "Subscriber " << id << ":\n"
|
||||
<< resp << std::endl;
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
// See
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
ioc.run();
|
||||
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)
|
||||
|
||||
139
examples/subscriber_sentinel.cpp
Normal file
139
examples/subscriber_sentinel.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/* 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)
|
||||
@@ -1,55 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using aedis::resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::dynamic_buffer;
|
||||
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};
|
||||
connect(socket, res);
|
||||
|
||||
// Creates and sends the request to redis.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::ping);
|
||||
sr.push(command::quit);
|
||||
net::write(socket, net::buffer(request));
|
||||
|
||||
// Will store the response to ping.
|
||||
std::string resp;
|
||||
|
||||
// Reads the responses to all commands in the request.
|
||||
std::string buffer;
|
||||
resp3::read(socket, dynamic_buffer(buffer)); // hello (ignored)
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
resp3::read(socket, dynamic_buffer(buffer)); // quit (ignored)
|
||||
|
||||
std::cout << "Ping: " << resp << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> transaction()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
auto list = {"one", "two", "three"};
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::multi); // Starts a transaction
|
||||
sr.push(command::ping, "Some message");
|
||||
sr.push(command::incr, "incr1-key");
|
||||
sr.push_range(command::rpush, "list-key", std::cbegin(list), std::cend(list));
|
||||
sr.push(command::lrange, "list-key", 0, -1);
|
||||
sr.push(command::incr, "incr2-key");
|
||||
sr.push(command::exec); // Ends the transaction.
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
|
||||
// Reads the response.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // multi
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // ping
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Prints the response to the transaction.
|
||||
std::cout << "ping: " << std::get<0>(execs) << "\n";
|
||||
std::cout << "incr1: " << std::get<1>(execs) << "\n";
|
||||
std::cout << "rpush: " << std::get<2>(execs) << "\n";
|
||||
std::cout << "lrange: ";
|
||||
for (auto const& e: std::get<3>(execs)) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
std::cout << "incr2: " << std::get<4>(execs) << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, transaction(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
25
include/aedis.hpp
Normal file
25
include/aedis.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/* 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_HPP
|
||||
#define AEDIS_HPP
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/adapt.hpp>
|
||||
#include <aedis/connection.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
/** @defgroup high-level-api Reference
|
||||
*
|
||||
* This page contains the documentation of the Aedis high-level API.
|
||||
*/
|
||||
|
||||
/** @defgroup low-level-api Reference
|
||||
*
|
||||
* This page contains the documentation of the Aedis low-level API.
|
||||
*/
|
||||
|
||||
#endif // AEDIS_HPP
|
||||
227
include/aedis/adapt.hpp
Normal file
227
include/aedis/adapt.hpp
Normal file
@@ -0,0 +1,227 @@
|
||||
/* 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
|
||||
80
include/aedis/adapter/adapt.hpp
Normal file
80
include/aedis/adapter/adapt.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef 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
|
||||
427
include/aedis/adapter/detail/adapters.hpp
Normal file
427
include/aedis/adapter/detail/adapters.hpp
Normal file
@@ -0,0 +1,427 @@
|
||||
/* 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_ADAPTERS_HPP
|
||||
#define AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include <forward_list>
|
||||
#include <system_error>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#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
|
||||
{
|
||||
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
|
||||
{
|
||||
i = resp3::detail::parse_uint(sv.data(), sv.size(), ec);
|
||||
}
|
||||
|
||||
inline
|
||||
void from_bulk(
|
||||
bool& t,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code&)
|
||||
{
|
||||
t = *sv.data() == 't';
|
||||
}
|
||||
|
||||
inline
|
||||
void from_bulk(
|
||||
double& d,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
d = parse_double(sv.data(), sv.size(), ec);
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
void
|
||||
from_bulk(
|
||||
std::basic_string<CharT, Traits, Allocator>& s,
|
||||
boost::string_view sv,
|
||||
boost::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:
|
||||
Result* result_;
|
||||
|
||||
public:
|
||||
explicit general_aggregate(Result* c = nullptr): result_(c) {}
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
|
||||
{
|
||||
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node>
|
||||
class general_simple {
|
||||
private:
|
||||
Node* result_;
|
||||
|
||||
public:
|
||||
explicit general_simple(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code& ec)
|
||||
{
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class simple_impl {
|
||||
public:
|
||||
void on_value_available(Result&) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& n,
|
||||
boost::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;
|
||||
return;
|
||||
}
|
||||
|
||||
from_bulk(result, n.value, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class set_impl {
|
||||
private:
|
||||
typename Result::iterator hint_;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& result)
|
||||
{ hint_ = std::end(result); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::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;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = error::expects_resp3_set;
|
||||
return;
|
||||
}
|
||||
|
||||
typename Result::key_type obj;
|
||||
from_bulk(obj, nd.value, ec);
|
||||
hint_ = result.insert(hint_, std::move(obj));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class map_impl {
|
||||
private:
|
||||
typename Result::iterator current_;
|
||||
bool on_key_ = true;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& result)
|
||||
{ current_ = std::end(result); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::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;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = error::expects_resp3_map;
|
||||
return;
|
||||
}
|
||||
|
||||
if (on_key_) {
|
||||
typename Result::key_type obj;
|
||||
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);
|
||||
current_->second = std::move(obj);
|
||||
}
|
||||
|
||||
on_key_ = !on_key_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class vector_impl {
|
||||
public:
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class array_impl {
|
||||
private:
|
||||
int i_ = -1;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::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;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
|
||||
ec = error::incompatible_size;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (i_ == -1) {
|
||||
ec = error::expects_resp3_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
from_bulk(result.at(i_), nd.value, ec);
|
||||
}
|
||||
|
||||
++i_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
struct list_impl {
|
||||
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::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;
|
||||
return;
|
||||
}
|
||||
|
||||
result.push_back({});
|
||||
from_bulk(result.back(), nd.value, ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//---------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
struct impl_map { using type = simple_impl<T>; };
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct impl_map<std::set<Key, Compare, Allocator>> { using type = set_impl<std::set<Key, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct impl_map<std::multiset<Key, Compare, Allocator>> { using type = set_impl<std::multiset<Key, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct impl_map<std::map<Key, T, Compare, Allocator>> { using type = map_impl<std::map<Key, T, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct impl_map<std::multimap<Key, T, Compare, Allocator>> { using type = map_impl<std::multimap<Key, T, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::vector<T, Allocator>> { using type = vector_impl<std::vector<T, Allocator>>; };
|
||||
|
||||
template <class T, std::size_t N>
|
||||
struct impl_map<std::array<T, N>> { using type = array_impl<std::array<T, N>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::list<T, Allocator>> { using type = list_impl<std::list<T, Allocator>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T, Allocator>>; };
|
||||
|
||||
//---------------------------------------------------
|
||||
|
||||
template <class Result>
|
||||
class wrapper {
|
||||
private:
|
||||
Result* 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)
|
||||
{
|
||||
BOOST_ASSERT(result_);
|
||||
impl_(*result_, nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
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) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
if (nd.data_type == resp3::type::null)
|
||||
return;
|
||||
|
||||
if (!result_->has_value()) {
|
||||
*result_ = T{};
|
||||
impl_.on_value_available(result_->value());
|
||||
}
|
||||
|
||||
impl_(result_->value(), nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::adapter:.detail
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
157
include/aedis/adapter/detail/response_traits.hpp
Normal file
157
include/aedis/adapter/detail/response_traits.hpp
Normal file
@@ -0,0 +1,157 @@
|
||||
/* 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_RESPONSE_TRAITS_HPP
|
||||
#define AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#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)>;
|
||||
|
||||
/* 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 <>
|
||||
struct response_traits<ignore> {
|
||||
using response_type = ignore;
|
||||
using adapter_type = resp3::detail::ignore_response;
|
||||
static auto adapt(response_type) noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct response_traits<resp3::node<T>> {
|
||||
using response_type = resp3::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>;
|
||||
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;
|
||||
|
||||
// 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); }
|
||||
|
||||
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));
|
||||
assigner<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[0] = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
class static_aggregate_adapter {
|
||||
private:
|
||||
using adapters_array_type =
|
||||
std::array<
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
boost::variant2::variant>,
|
||||
std::tuple_size<Tuple>::value>;
|
||||
|
||||
std::size_t i_ = 0;
|
||||
std::size_t aggregate_size_ = 0;
|
||||
adapters_array_type adapters_;
|
||||
|
||||
public:
|
||||
explicit static_aggregate_adapter(Tuple* r = nullptr)
|
||||
{
|
||||
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r);
|
||||
}
|
||||
|
||||
void count(resp3::node<boost::string_view> const& nd)
|
||||
{
|
||||
if (nd.depth == 1) {
|
||||
if (is_aggregate(nd.data_type))
|
||||
aggregate_size_ = element_multiplicity(nd.data_type) * nd.aggregate_size;
|
||||
else
|
||||
++i_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (--aggregate_size_ == 0)
|
||||
++i_;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
using boost::variant2::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;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
visit([&](auto& arg){arg(nd, ec);}, adapters_[i_]);
|
||||
count(nd);
|
||||
}
|
||||
};
|
||||
|
||||
template <class... Ts>
|
||||
struct response_traits<std::tuple<Ts...>>
|
||||
{
|
||||
using response_type = 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
|
||||
|
||||
#endif // AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
276
include/aedis/connection.hpp
Normal file
276
include/aedis/connection.hpp
Normal file
@@ -0,0 +1,276 @@
|
||||
/* 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
|
||||
480
include/aedis/detail/connection_base.hpp
Normal file
480
include/aedis/detail/connection_base.hpp
Normal file
@@ -0,0 +1,480 @@
|
||||
/* 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
|
||||
570
include/aedis/detail/connection_ops.hpp
Normal file
570
include/aedis/detail/connection_ops.hpp
Normal file
@@ -0,0 +1,570 @@
|
||||
/* 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
|
||||
205
include/aedis/detail/net.hpp
Normal file
205
include/aedis/detail/net.hpp
Normal file
@@ -0,0 +1,205 @@
|
||||
/* 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
|
||||
38
include/aedis/endpoint.hpp
Normal file
38
include/aedis/endpoint.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
/* 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
|
||||
105
include/aedis/error.hpp
Normal file
105
include/aedis/error.hpp
Normal file
@@ -0,0 +1,105 @@
|
||||
/* 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_ERROR_HPP
|
||||
#define AEDIS_ERROR_HPP
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** \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,
|
||||
|
||||
/// Can't parse the string as a number.
|
||||
not_a_number,
|
||||
|
||||
/// The maximum depth of a nested response was exceeded.
|
||||
exceeeds_max_nested_depth,
|
||||
|
||||
/// Got non boolean value.
|
||||
unexpected_bool_value,
|
||||
|
||||
/// Expected field value is empty.
|
||||
empty_field,
|
||||
|
||||
/// Expects a simple RESP3 type but got an aggregate.
|
||||
expects_resp3_simple_type,
|
||||
|
||||
/// Expects aggregate.
|
||||
expects_resp3_aggregate,
|
||||
|
||||
/// Expects a map but got other aggregate.
|
||||
expects_resp3_map,
|
||||
|
||||
/// Expects a set aggregate but got something else.
|
||||
expects_resp3_set,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_aggregate_not_supported,
|
||||
|
||||
/// Got RESP3 simple error.
|
||||
resp3_simple_error,
|
||||
|
||||
/// Got RESP3 blob_error.
|
||||
resp3_blob_error,
|
||||
|
||||
/// Aggregate container has incompatible size.
|
||||
incompatible_size,
|
||||
|
||||
/// Not a double
|
||||
not_a_double,
|
||||
|
||||
/// 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,
|
||||
};
|
||||
|
||||
/** \internal
|
||||
* \brief Creates a error_code object from an error.
|
||||
* \param e Error code.
|
||||
* \ingroup any
|
||||
*/
|
||||
auto make_error_code(error e) -> boost::system::error_code;
|
||||
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
|
||||
#endif // AEDIS_ERROR_HPP
|
||||
18
include/aedis/impl/endpoint.ipp
Normal file
18
include/aedis/impl/endpoint.ipp
Normal file
@@ -0,0 +1,18 @@
|
||||
/* 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
|
||||
65
include/aedis/impl/error.ipp
Normal file
65
include/aedis/impl/error.ipp
Normal file
@@ -0,0 +1,65 @@
|
||||
/* 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/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : boost::system::error_category {
|
||||
|
||||
virtual ~error_category_impl() = default;
|
||||
|
||||
auto name() const noexcept -> char const* override
|
||||
{
|
||||
return "aedis";
|
||||
}
|
||||
|
||||
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::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.";
|
||||
case error::expects_resp3_simple_type: return "Expects a resp3 simple type.";
|
||||
case error::expects_resp3_aggregate: return "Expects resp3 aggregate.";
|
||||
case error::expects_resp3_map: return "Expects resp3 map.";
|
||||
case error::expects_resp3_set: return "Expects resp3 set.";
|
||||
case error::nested_aggregate_not_supported: return "Nested aggregate not_supported.";
|
||||
case error::resp3_simple_error: return "Got RESP3 simple-error.";
|
||||
case error::resp3_blob_error: return "Got RESP3 blob-error.";
|
||||
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.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto category() -> boost::system::error_category const&
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
auto make_error_code(error e) -> boost::system::error_code
|
||||
{
|
||||
return boost::system::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
} // aedis
|
||||
29
include/aedis/operation.hpp
Normal file
29
include/aedis/operation.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
/* 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
|
||||
173
include/aedis/resp3/detail/exec.hpp
Normal file
173
include/aedis/resp3/detail/exec.hpp
Normal file
@@ -0,0 +1,173 @@
|
||||
/* 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
|
||||
25
include/aedis/resp3/detail/impl/parser.ipp
Normal file
25
include/aedis/resp3/detail/impl/parser.ipp
Normal file
@@ -0,0 +1,25 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
auto parse_uint(char const* data, std::size_t size, boost::system::error_code& ec) -> std::size_t
|
||||
{
|
||||
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
|
||||
std::size_t ret = 0;
|
||||
if (!parse(data, data + size, p, ret))
|
||||
ec = error::not_a_number;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // aedis::resp3::detail
|
||||
@@ -1,29 +1,30 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_RESP3_PARSER_HPP
|
||||
#define AEDIS_RESP3_PARSER_HPP
|
||||
|
||||
#include <string_view>
|
||||
#include <charconv>
|
||||
#include <system_error>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <system_error>
|
||||
|
||||
#include <aedis/resp3/error.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
type to_type(char c);
|
||||
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_;
|
||||
@@ -36,7 +37,7 @@ private:
|
||||
// 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::size_t sizes_[max_embedded_depth + 1] = {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)();
|
||||
@@ -46,32 +47,29 @@ private:
|
||||
type bulk_ = type::invalid;
|
||||
|
||||
public:
|
||||
parser(ResponseAdapter adapter)
|
||||
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.
|
||||
std::size_t
|
||||
advance(char const* data, std::size_t n, std::error_code& ec)
|
||||
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:
|
||||
{
|
||||
if (bulk_length_ == 0) {
|
||||
sizes_[depth_] = 1;
|
||||
} else {
|
||||
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
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);
|
||||
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
@@ -83,46 +81,75 @@ public:
|
||||
} else if (sizes_[depth_] != 0) {
|
||||
auto const t = to_type(*data);
|
||||
switch (t) {
|
||||
case type::blob_error:
|
||||
case type::verbatim_string:
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
auto const r =
|
||||
std::from_chars(data + 1, data + n - 2, bulk_length_);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
bulk_length_ = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bulk_ = t;
|
||||
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) == '?') {
|
||||
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 lenght
|
||||
// the server is supposed to send a part with length
|
||||
// 0.
|
||||
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
|
||||
} else {
|
||||
auto const r =
|
||||
std::from_chars(data + 1, data + n - 2, bulk_length_);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
bulk_length_ = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
bulk_ = type::blob_string;
|
||||
bulk_ = t;
|
||||
}
|
||||
} break;
|
||||
case type::simple_error:
|
||||
case type::number:
|
||||
case type::doublean:
|
||||
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);
|
||||
adapter_({t, 1, depth_, {&data[1], n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -130,7 +157,7 @@ public:
|
||||
} break;
|
||||
case type::null:
|
||||
{
|
||||
adapter_(type::null, 1, depth_, nullptr, 0, ec);
|
||||
adapter_({type::null, 1, depth_, {}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -142,14 +169,11 @@ public:
|
||||
case type::attribute:
|
||||
case type::map:
|
||||
{
|
||||
std::size_t l;
|
||||
auto const r = std::from_chars(data + 1, data + n - 2, l);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
auto const l = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
adapter_(t, l, depth_, nullptr, 0, ec);
|
||||
adapter_({t, l, depth_, {}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -168,7 +192,7 @@ public:
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
ec = error::invalid_type;
|
||||
ec = error::invalid_data_type;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -183,17 +207,17 @@ public:
|
||||
}
|
||||
|
||||
// Returns true when the parser is done with the current message.
|
||||
auto done() const noexcept
|
||||
[[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.
|
||||
auto bulk() const noexcept { return bulk_; }
|
||||
[[nodiscard]] auto bulk() const noexcept { return bulk_; }
|
||||
|
||||
// The length expected in the the next bulk.
|
||||
auto bulk_length() const noexcept { return bulk_length_; }
|
||||
[[nodiscard]] auto bulk_length() const noexcept { return bulk_length_; }
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
} // detail::resp3::aedis
|
||||
|
||||
#endif // AEDIS_RESP3_PARSER_HPP
|
||||
130
include/aedis/resp3/detail/read_ops.hpp
Normal file
130
include/aedis/resp3/detail/read_ops.hpp
Normal file
@@ -0,0 +1,130 @@
|
||||
/* 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
|
||||
19
include/aedis/resp3/impl/request.ipp
Normal file
19
include/aedis/resp3/impl/request.ipp
Normal file
@@ -0,0 +1,19 @@
|
||||
/* 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/resp3/request.hpp>
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
auto has_push_response(boost::string_view cmd) -> bool
|
||||
{
|
||||
if (cmd == "SUBSCRIBE") return true;
|
||||
if (cmd == "PSUBSCRIBE") return true;
|
||||
if (cmd == "UNSUBSCRIBE") return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // aedis::resp3::detail
|
||||
109
include/aedis/resp3/impl/type.ipp
Normal file
109
include/aedis/resp3/impl/type.ipp
Normal file
@@ -0,0 +1,109 @@
|
||||
/* 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/assert.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis::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";
|
||||
}
|
||||
}
|
||||
|
||||
auto operator<<(std::ostream& os, type t) -> std::ostream&
|
||||
{
|
||||
os << to_string(t);
|
||||
return os;
|
||||
}
|
||||
|
||||
auto is_aggregate(type t) -> bool
|
||||
{
|
||||
switch (t) {
|
||||
case type::array:
|
||||
case type::push:
|
||||
case type::set:
|
||||
case type::map:
|
||||
case type::attribute: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto element_multiplicity(type t) -> std::size_t
|
||||
{
|
||||
switch (t) {
|
||||
case type::map:
|
||||
case type::attribute: return 2ULL;
|
||||
default: return 1ULL;
|
||||
}
|
||||
}
|
||||
|
||||
auto to_code(type t) -> char
|
||||
{
|
||||
switch (t) {
|
||||
case type::blob_error: return '!';
|
||||
case type::verbatim_string: return '=';
|
||||
case type::blob_string: return '$';
|
||||
case type::streamed_string_part: return ';';
|
||||
case type::simple_error: return '-';
|
||||
case type::number: return ':';
|
||||
case type::doublean: return ',';
|
||||
case type::boolean: return '#';
|
||||
case type::big_number: return '(';
|
||||
case type::simple_string: return '+';
|
||||
case type::null: return '_';
|
||||
case type::push: return '>';
|
||||
case type::set: return '~';
|
||||
case type::array: return '*';
|
||||
case type::attribute: return '|';
|
||||
case type::map: return '%';
|
||||
|
||||
default: BOOST_ASSERT(false); return ' ';
|
||||
}
|
||||
}
|
||||
|
||||
auto to_type(char c) -> type
|
||||
{
|
||||
switch (c) {
|
||||
case '!': return type::blob_error;
|
||||
case '=': return type::verbatim_string;
|
||||
case '$': return type::blob_string;
|
||||
case ';': return type::streamed_string_part;
|
||||
case '-': return type::simple_error;
|
||||
case ':': return type::number;
|
||||
case ',': return type::doublean;
|
||||
case '#': return type::boolean;
|
||||
case '(': return type::big_number;
|
||||
case '+': return type::simple_string;
|
||||
case '_': return type::null;
|
||||
case '>': return type::push;
|
||||
case '~': return type::set;
|
||||
case '*': return type::array;
|
||||
case '|': return type::attribute;
|
||||
case '%': return type::map;
|
||||
default: return type::invalid;
|
||||
}
|
||||
}
|
||||
|
||||
} // aedis::resp3
|
||||
94
include/aedis/resp3/node.hpp
Normal file
94
include/aedis/resp3/node.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
/* 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
|
||||
180
include/aedis/resp3/read.hpp
Normal file
180
include/aedis/resp3/read.hpp
Normal file
@@ -0,0 +1,180 @@
|
||||
/* 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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user