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

Compare commits

...

306 Commits

Author SHA1 Message Date
Marcelo Zimbres
562075230f v1.2.0 2022-11-05 19:06:56 +01:00
Marcelo Zimbres
5dc677c6d8 Changes:
* Adds allocator support for the internal connection queue.
* Support for std::tie in aedis::adapt.
* Docs.
2022-11-05 12:07:22 +01:00
Marcelo Zimbres
395a167d48 Improvements in the coverage. 2022-11-01 14:13:33 +01:00
Marcelo Zimbres
f93f3cab58 Merge branch 'klemens-morgenstern-allocator-nonsense' 2022-10-31 22:20:54 +01:00
Marcelo Zimbres
df68fb0235 Changes:
- Ports from boost::container::pmr to std::pmr.
- Fixes clang-tidy issues.
- Adds resp3::request unit-tests.
2022-10-31 22:17:58 +01:00
Klemens Morgenstern
15e6883bc1 Added boost.container.pmr to request. 2022-10-31 07:37:03 +01:00
Marcelo Zimbres
3816d1d358 Documentation and stress test. 2022-10-30 19:48:04 +01:00
Marcelo
bb15c70723 Merge pull request #36 from klemens-morgenstern/sfinae
Added sfinae to push_range.
2022-10-30 19:47:16 +01:00
Klemens Morgenstern
297b7f15eb Added sfinae to push_range. 2022-10-30 23:11:35 +08:00
Marcelo Zimbres
ec6e99d99a Docs and example improvements. 2022-10-29 22:49:53 +02:00
Marcelo Zimbres
8dc6db069b Docs and examples. 2022-10-27 23:09:59 +02:00
Marcelo Zimbres
bac27c1770 Fixes cancellation. 2022-10-25 20:58:16 +02:00
Marcelo Zimbres
feaaedc6c0 Improvements in the cancellation support. 2022-10-23 22:32:58 +02:00
Marcelo Zimbres
000ebddf44 Fixes bug that caused unwritten request to be closed if write fails. 2022-10-22 22:34:10 +02:00
Marcelo Zimbres
268ea2c10f Improvements in the writer cancellation. 2022-10-22 21:23:33 +02:00
Marcelo Zimbres
d8b67f6e23 Improves async_exec cancellation support. 2022-10-22 20:43:37 +02:00
Marcelo Zimbres
ce1fa6a683 Implements per-op cancelation of async_exec. 2022-10-18 20:52:18 +02:00
Marcelo Zimbres
b8ede6ccb7 Fixes bug in conn.cancel(exec). 2022-10-16 22:44:44 +02:00
Marcelo Zimbres
6dce1a9226 Marks function inline. 2022-10-15 13:08:50 +02:00
Marcelo Zimbres
8566745d83 Changes the behaviour of adapt() with vector<node>. 2022-10-13 21:45:01 +02:00
Marcelo Zimbres
0b4906fcba Test improvements. 2022-10-13 20:51:35 +02:00
Marcelo Zimbres
2c8bb92071 Improvements in the docs. 2022-10-10 23:14:54 +02:00
Marcelo Zimbres
770e224917 Changes:
- CI fix.
- Renames request::fail_* to request::cancel_*.
- Adds a second parameter to async_run.
- Adds request::retry flag.
2022-10-09 22:45:42 +02:00
Marcelo Zimbres
4fb2b20954 Build fixes. 2022-10-08 22:24:20 +02:00
Marcelo Zimbres
c01a57b6cb Adds cancelation test. 2022-10-08 22:07:51 +02:00
Marcelo Zimbres
ea0b333c4d Removes the second async_run overload. 2022-10-08 17:07:28 +02:00
Marcelo Zimbres
ba82c6cd84 Progresses removing the second async_run overload. 2022-10-07 22:43:32 +02:00
Marcelo Zimbres
4c298ddc6b Adds doxygen output to the preset. 2022-10-03 14:32:36 +02:00
Marcelo Zimbres
690002b2f1 Fixes CI. 2022-10-03 13:10:56 +02:00
Marcelo Zimbres
12899da4db Back to Boost 1.79 as CI does not support 1.80. 2022-10-03 11:28:34 +02:00
Marcelo Zimbres
b2c19df113 Documentation improvements. 2022-10-02 20:24:35 +02:00
Marcelo Zimbres
61f9a29ebc Makes connection_base a private base. 2022-10-02 09:04:09 +02:00
Marcelo Zimbres
dc9b333f1e Passes timeouts as parameters to async_run. 2022-10-01 21:20:07 +02:00
Marcelo Zimbres
dac7bea54f Moves coalesce request from the connection the request. 2022-10-01 15:17:26 +02:00
Marcelo Zimbres
da84321378 Adds request::config. 2022-10-01 12:34:46 +02:00
Marcelo Zimbres
3295459700 Moves connection::config::max_read_size to adapt(). 2022-10-01 10:53:54 +02:00
Marcelo Zimbres
dff8833fe3 Removes the sync wrapper. 2022-09-29 23:15:50 +02:00
Marcelo Zimbres
3a03a43c06 Fixes some of the bugs reported in issue #28. 2022-09-25 22:43:24 +02:00
Marcelo Zimbres
b614826bb4 Improvements in the docs. 2022-09-25 20:14:05 +02:00
Marcelo Zimbres
4e30a9d53d Adds reserve member function to aedis::request. 2022-09-24 15:12:31 +02:00
Marcelo Zimbres
85ba41ae5a Adds support to request::close_on_connection_lost. 2022-09-24 15:04:33 +02:00
Marcelo Zimbres
fd82204ba9 Improvements in the documentation. 2022-09-18 23:11:13 +02:00
Marcelo Zimbres
084e95cbc7 Improvements. 2022-09-17 22:20:06 +02:00
Marcelo Zimbres
798f193f14 Progresses with code quality. 2022-09-12 21:57:57 +02:00
Marcelo Zimbres
a23a3db9ac Improvements in the readme. 2022-09-11 21:48:30 +02:00
Marcelo Zimbres
bca6511333 Progresses with TLS support and improvements in the docs. 2022-09-11 18:09:44 +02:00
Marcelo Zimbres
e2d642f34c Server role check and progresses with TLS. 2022-09-10 22:42:39 +02:00
Marcelo Zimbres
d8607af669 Summary:
- Lots of improvements.
- Changes the behaviour of the second async_run overload.
- Moves to std::optional.
2022-09-10 11:25:42 +02:00
Marcelo Zimbres
223a9aa74b Progresses with TLS support. 2022-09-04 22:19:27 +02:00
Marcelo Zimbres
75433cd028 Improves clang-tidy, adds endpoint etc. 2022-09-03 14:41:22 +02:00
Marcelo Zimbres
552c6cf6e4 Applies some clang-tidy suggestions. 2022-08-31 20:56:28 +02:00
Marcelo Zimbres
16b9347c51 Adds a cmake preset file and fixes install target. 2022-08-28 20:03:31 +02:00
Marcelo Zimbres
03558b1466 Adds more tests. 2022-08-27 23:12:44 +02:00
Marcelo Zimbres
f8165bcb6f Error fixes and test coverage improvements. 2022-08-27 19:22:10 +02:00
Marcelo Zimbres
8aad27269c Improvements in the coverage and replaces autotools with cmake. 2022-08-27 12:53:44 +02:00
Marcelo Zimbres
f11622e746 Updates the go benchmark. 2022-08-25 21:24:23 +02:00
Marcelo Zimbres
8249360a52 Progresses with coverage. 2022-08-24 22:41:17 +02:00
Marcelo Zimbres
d00f26d3da Ports code coverage build to cmake. 2022-08-23 21:49:26 +02:00
Marcelo Zimbres
480ec13119 Tries to ignore branch coverage. 2022-08-22 21:21:17 +02:00
Marcelo Zimbres
4cc3fc59a1 Improves CMakeLists.txt with packaging. 2022-08-21 21:28:21 +02:00
Marcelo Zimbres
11807c82b7 Improves documentations of the connection class. 2022-08-21 12:50:29 +02:00
Marcelo Zimbres
24a215d78b First steps with cmake support. 2022-08-20 22:12:33 +02:00
Marcelo Zimbres
b7abe20703 CI fix. 2022-08-20 12:18:59 +02:00
Marcelo Zimbres
225095944c Commit of the following:
- Adds sync class the offer a thread-safe and synchronous API.
- Fixes documentation of adapt functions.
- Removes compose.hpp header.
- Adds test to aedis::error and resp3::type.
- Simplifies some code.
2022-08-20 11:56:31 +02:00
Marcelo Zimbres
a31d797e43 Moves sync functions from experimental to connection and improves code coverage. 2022-08-18 22:17:33 +02:00
Marcelo Zimbres
cca8d5d6dc Improvements in the examples, docs, sync functions and coverage. 2022-08-17 22:30:59 +02:00
Marcelo Zimbres
6c5bee6920 Fixes bug in the context of reconnecting and events. 2022-08-16 22:18:27 +02:00
Marcelo Zimbres
c4714d0037 Splits async_receive_event in two functions. 2022-08-15 22:45:55 +02:00
Marcelo Zimbres
38bf2395af Fix coverage and ports tests to boost.test. 2022-08-14 21:46:56 +02:00
Marcelo Zimbres
7511d6b4d8 Progress porting to boost.test. 2022-08-13 22:52:42 +02:00
Marcelo Zimbres
ddc2815fe5 Progress with coverage report. 2022-08-13 17:00:18 +02:00
Marcelo Zimbres
de6f5de655 Adds coverage file. 2022-08-12 22:43:37 +02:00
Marcelo Zimbres
8d454ada0e Simplifies the cancellation of some connection async_ functions. 2022-08-12 21:53:04 +02:00
Marcelo Zimbres
ebac88f2ca Improvementes in the CI script. 2022-08-07 18:44:28 +02:00
Marcelo Zimbres
d26ecb65ca Improvements in the docs. 2022-08-07 11:32:50 +02:00
Marcelo Zimbres
c57f97b8c1 Improvements in the examples. 2022-08-06 23:12:32 +02:00
Marcelo Zimbres
37ab1e7387 Support for reconnection. 2022-08-06 18:11:12 +02:00
Marcelo Zimbres
54d448cad4 Progresses with connection events. 2022-08-06 13:06:05 +02:00
Marcelo Zimbres
97428dedb3 Progresses with reconnection. 2022-08-04 23:58:04 +02:00
Marcelo Zimbres
83802f217a Prepares for events. 2022-08-03 21:40:43 +02:00
Marcelo Zimbres
08140f9186 Fixes async_exec function. 2022-08-02 21:52:34 +02:00
Marcelo Zimbres
3ddb017edb Adds automatic AUTH and HELLO. 2022-08-01 22:46:34 +02:00
Marcelo Zimbres
20328cd423 Don't cancel the push channel when async_run exits. 2022-08-01 21:50:38 +02:00
Marcelo Zimbres
6577ddbaab First steps. 2022-07-31 22:10:49 +02:00
Marcelo Zimbres
217d2bd87b Progresses with the synchronous exec functions. 2022-07-31 11:50:23 +02:00
Marcelo Zimbres
f96dd22153 Improves executor usage in sync wrapper. 2022-07-30 23:35:28 +02:00
Marcelo Zimbres
f1fd0cfa8c Removes warnings on g++. 2022-07-30 23:18:24 +02:00
Marcelo Zimbres
8728914109 Adds changelog, fixes CI file, improvements in the docs. 2022-07-30 09:31:11 +02:00
Marcelo Zimbres
e0041ac7ae Progresses with async_failover function. 2022-07-28 22:00:42 +02:00
Marcelo Zimbres
317a185eb0 Adds in the sync_wrapper class. 2022-07-27 22:18:00 +02:00
Marcelo Zimbres
aa81200a8f Adds assert to check the response tuple is compatible with the request size. 2022-07-26 22:10:31 +02:00
Marcelo Zimbres
55fc0e861c Fixes bug on reconnection. 2022-07-26 21:47:14 +02:00
Marcelo Zimbres
04271855b0 Adds example on how to use Aedis synchronously. 2022-07-25 22:53:03 +02:00
Marcelo Zimbres
700e0c823e First steps with the CI file. 2022-07-24 21:21:02 +02:00
Marcelo Zimbres
63c6465a4a Improvements in the docs and subscriber example with reconnection. 2022-07-24 16:33:13 +02:00
Marcelo Zimbres
c86422cf50 Moves files to include directory. 2022-07-24 00:03:19 +02:00
Marcelo Zimbres
0168ed5faf Fixes build for clang++-14,13,11. 2022-07-23 14:55:01 +02:00
Marcelo Zimbres
7bffa252f4 Improvements in the documentation. 2022-07-21 22:05:26 +02:00
Marcelo Zimbres
0bb65599c4 Simplifies the char_room example. 2022-07-21 21:35:52 +02:00
Marcelo Zimbres
edd538944f Uses the correct executor in the exec timer. 2022-07-19 22:01:09 +02:00
Marcelo Zimbres
42880e788b Simplifies aedis header. 2022-07-17 18:42:24 +02:00
Marcelo Zimbres
bcc3917174 Test improvements. 2022-07-17 10:47:12 +02:00
Marcelo Zimbres
b08dd63192 Updates benchmark doc. 2022-07-16 21:25:57 +02:00
Marcelo Zimbres
76b6106caa Fixes executor usage in connection class. 2022-07-16 21:21:13 +02:00
Marcelo Zimbres
ab68e8a31d Updates to a more recent Tokio version and uses single thread. 2022-07-16 20:00:36 +02:00
Marcelo Zimbres
2673557ce5 More corrections. 2022-07-16 14:30:16 +02:00
Marcelo Zimbres
2a302dcb65 Corrections to the benchmark document. 2022-07-16 14:25:38 +02:00
Marcelo Zimbres
ffc4230368 Fixes documentation. 2022-07-16 13:50:21 +02:00
Marcelo Zimbres
59b5d35672 Small corrections. 2022-07-16 12:33:02 +02:00
Marcelo Zimbres
835a1decf4 Progresses with benchmarks. 2022-07-16 11:03:48 +02:00
Marcelo Zimbres
3fb018ccc6 Some changes in the benchmarks. 2022-07-15 23:15:08 +02:00
Marcelo Zimbres
1fe4a87287 Adds go-redis 2022-07-14 22:13:58 +02:00
Marcelo Zimbres
70cdff41e0 Fixes some bugs. 2022-07-14 21:44:50 +02:00
Marcelo Zimbres
2edd9f3d87 Some improvements in the benchmarks. 2022-07-11 00:00:09 +02:00
Marcelo Zimbres
fa4181b197 New version. 2022-07-10 20:53:44 +02:00
Marcelo Zimbres
9e2cd8855e Small documentation improvements. 2022-07-10 19:19:42 +02:00
Marcelo Zimbres
bef70870cd Test improvements. 2022-07-10 10:05:21 +02:00
Marcelo Zimbres
9885439845 Adds Petr Dannhofer to the acknowledgements section. 2022-07-09 22:55:58 +02:00
Marcelo Zimbres
b5a9162efb Uses the associated allocator to allocate memory. 2022-07-09 22:51:34 +02:00
Marcelo Zimbres
6ca0bcc945 Removes automatic sending of hello command. 2022-07-09 22:11:22 +02:00
Marcelo Zimbres
efd0a0379a Reenables some tests. 2022-07-09 17:02:34 +02:00
Marcelo Zimbres
97153abc3c Add own-ping cancelation when async_run exits. 2022-07-09 16:31:27 +02:00
Marcelo Zimbres
f4710941d3 Fixes error handling. 2022-07-09 12:50:44 +02:00
Marcelo Zimbres
f8ff3034f4 Adds AUTH to the example. 2022-07-09 10:22:12 +02:00
Marcelo Zimbres
561eb5dccb Progresses with the support for failover. 2022-07-09 09:32:33 +02:00
Marcelo Zimbres
95d609b75c Improvements in the docs. 2022-07-03 09:21:35 +02:00
Marcelo Zimbres
d5f9e702d7 Fixes missing return statement. 2022-07-03 08:16:06 +02:00
Marcelo Zimbres
5add83b73c Fix compilation on clang++-14. 2022-07-02 23:40:53 +02:00
Marcelo Zimbres
200974d9be Adds an tcp echo server from libuv. 2022-07-02 23:01:12 +02:00
Marcelo Zimbres
649c84d7d0 Code simplifications. 2022-07-02 18:11:42 +02:00
Marcelo Zimbres
240cce4b09 Fixes the tests. 2022-07-02 13:49:03 +02:00
Marcelo Zimbres
b140216f0d Fixes the connection ops for the subscriber. 2022-07-02 11:30:39 +02:00
Marcelo Zimbres
4f0d9de393 Removes command enum. 2022-06-27 22:48:47 +02:00
Marcelo Zimbres
888bb476d7 Loads missing files. 2022-06-27 21:28:33 +02:00
Marcelo Zimbres
eae37ace0b Fixes some problems with clang. 2022-06-26 23:03:36 +02:00
Marcelo Zimbres
0c3ed1afee Improves error handling. 2022-06-26 22:48:14 +02:00
Marcelo Zimbres
0f5e8e3d1f Renames from_bulk to to_bulk. 2022-06-26 21:08:05 +02:00
Marcelo Zimbres
963b228e02 Adds serialization example. 2022-06-26 18:24:36 +02:00
Marcelo Zimbres
bddf47d626 Simplifies the code. 2022-06-25 13:34:31 +02:00
Marcelo Zimbres
b3b8dfc243 Fixes the rust echo server. 2022-06-23 22:27:14 +02:00
Marcelo Zimbres
e013d846b2 Adds files to Makefile.am. 2022-06-20 21:51:06 +02:00
Marcelo Zimbres
d2ba54a7a6 Add go echo server. 2022-06-19 21:15:01 +02:00
Marcelo Zimbres
250e24d5fb Adds echo server test. 2022-06-19 20:50:00 +02:00
Marcelo Zimbres
9dcccca11e Move files around and adds rust program. 2022-06-19 14:47:56 +02:00
Marcelo Zimbres
8af1c9f19c Adds nodejs echo_server benchmark program. 2022-06-19 14:12:10 +02:00
Marcelo Zimbres
b058cc0c02 Adds echo_server_direct tool for benchmark purposes. 2022-06-19 09:17:28 +02:00
Marcelo Zimbres
df3f2b8ca5 More improvements in the docs. 2022-06-18 22:07:59 +02:00
Marcelo Zimbres
8e4928347c Improvements in the documentation. 2022-06-18 13:03:21 +02:00
Marcelo Zimbres
33461d54c8 Adds reconnect test. 2022-06-18 09:13:44 +02:00
Marcelo Zimbres
5328cdff9a Adds coalesce option. 2022-06-17 22:51:51 +02:00
Marcelo Zimbres
452589d4e7 Test improvements. 2022-06-16 15:00:35 +02:00
Marcelo Zimbres
4036df9255 Moves write operation in exec_op to its own op. 2022-06-16 11:42:16 +02:00
Marcelo Zimbres
9f2df4d052 Fix in the operations. 2022-06-14 22:43:35 +02:00
Marcelo Zimbres
1571afbd88 Simplifies read operation. 2022-06-12 21:39:18 +02:00
Marcelo Zimbres
b43e6dfb68 Simplifies the code. 2022-06-12 15:12:34 +02:00
Marcelo Zimbres
ce9cb04168 Refactoring. 2022-06-12 14:44:20 +02:00
Marcelo Zimbres
77fe3a0f5f Fixes and improves some tests. 2022-06-11 10:59:59 +02:00
Marcelo Zimbres
40dfacb0b7 Adds exec overload. 2022-06-06 22:00:43 +02:00
Marcelo Zimbres
6d859c57f8 Unifies all error codes into one. 2022-06-06 16:46:38 +02:00
Marcelo Zimbres
9e43541a5e Remove Command template parameter from request. 2022-06-06 15:22:31 +02:00
Marcelo Zimbres
97cb5b5b25 Improvements in the project structure. 2022-06-06 10:51:47 +02:00
Marcelo Zimbres
a40c9fe35f Factors async_write_with_timeout out of connection class. 2022-06-06 08:50:44 +02:00
Marcelo Zimbres
a411cc50fc Simplifies and enhances code modularity. 2022-06-05 23:01:19 +02:00
Marcelo Zimbres
5893f0913e Adds a pool of timers. 2022-06-05 10:29:04 +02:00
Marcelo Zimbres
dea7712a29 Improvements in the code. 2022-06-05 09:31:19 +02:00
Marcelo Zimbres
56479b88eb Using generic::adapter for async_read_push. 2022-06-04 22:50:48 +02:00
Marcelo Zimbres
dfeb3bbfcf Improvements in the examples. 2022-06-04 19:03:22 +02:00
Marcelo Zimbres
7464851e9e Improvements in the examples. 2022-06-04 14:41:18 +02:00
Marcelo Zimbres
226c2b228c Factors out code related with async_connect. 2022-06-04 12:39:44 +02:00
Marcelo Zimbres
fee892b6ad Updates the examples. 2022-05-29 14:06:16 +02:00
Marcelo Zimbres
74e0a6ca23 Adds support for tuple in the high level api. 2022-05-29 10:54:23 +02:00
Marcelo Zimbres
ebef2f9e23 Pass the adapter directly to async_exec. 2022-05-28 21:19:00 +02:00
Marcelo Zimbres
485bdc316b Refactors operations that consume pushes. 2022-05-28 10:54:02 +02:00
Marcelo Zimbres
36fb83e1d6 Simplifications in the read timeouts. 2022-05-28 10:08:31 +02:00
Marcelo Zimbres
3753c27dcf Changes how the reader_op works. 2022-05-27 22:32:49 +02:00
Marcelo Zimbres
091cad6ee7 Improves pipelining. 2022-05-26 21:34:29 +02:00
Marcelo Zimbres
1e98c04603 Simplifications in the examples. 2022-05-25 23:02:48 +02:00
Marcelo Zimbres
4858c078f9 Improvements with timeouts and simplifications. 2022-05-25 21:43:37 +02:00
Marcelo Zimbres
3dff0b78de Implements automatic hello. 2022-05-24 22:36:16 +02:00
Marcelo Zimbres
7300f1498b Fixes echo_server example. 2022-05-23 22:44:25 +02:00
Marcelo Zimbres
f6fc45d8ba Small improvements. 2022-05-22 22:21:58 +02:00
Marcelo Zimbres
5eb88b5042 Improvements in the examples. 2022-05-22 20:04:35 +02:00
Marcelo Zimbres
f7d2f3ab28 General improvements. 2022-05-22 17:50:22 +02:00
Marcelo Zimbres
f62ad6a8bf Ports high-level tests to new api. 2022-05-22 15:14:20 +02:00
Marcelo Zimbres
1efcf7b7d8 Fixes chat_room. 2022-05-22 08:27:22 +02:00
Marcelo Zimbres
29166a2cf0 Progresses with porting to channels. 2022-05-21 22:14:46 +02:00
Marcelo Zimbres
215fd7ea73 Renames serializer to request. 2022-05-18 22:22:17 +02:00
Marcelo Zimbres
9b8ca4dbc8 Simplifications. 2022-05-16 23:28:49 +02:00
Marcelo Zimbres
4075dc380d Commit of the following:
- Simplifications.
- Refactors read operaitons for useability.
- Better naming.
- Simplification of write operations.
- Adds a push communication channel.
2022-05-15 20:48:23 +02:00
Marcelo Zimbres
161cd848f8 Removes function. 2022-05-15 09:01:08 +02:00
Marcelo Zimbres
7c7eed4a53 Refactors the serializer class. 2022-05-14 23:16:16 +02:00
Marcelo Zimbres
e70b00e976 Renames async_receive to async_read_one. 2022-05-14 16:53:56 +02:00
Marcelo Zimbres
52d7b95cf8 Fixes one test. 2022-05-14 16:22:31 +02:00
Marcelo Zimbres
641032fa9a Fixes one more test. 2022-05-09 22:45:14 +02:00
Marcelo Zimbres
2a2a13c4dc Adopts Asio channels to deliver read events instead of callbacks. 2022-05-08 23:03:06 +02:00
Marcelo Zimbres
76741d8466 Simplifies the read operations. 2022-05-08 10:54:59 +02:00
Marcelo Zimbres
0f79214d37 Removes the on_push callback. 2022-05-08 08:58:57 +02:00
Marcelo Zimbres
de476169ae Removes the on_write callback from the receiver. 2022-05-08 08:32:19 +02:00
Marcelo Zimbres
d1bf3a91be Changes:
* Program to benchmark the high level client.
* First steps with sentinel support in the high level client.
2022-05-04 22:54:21 +02:00
Marcelo Zimbres
4be6e6cc1e Passing host and port in the config parameter. 2022-05-02 23:13:15 +02:00
Marcelo Zimbres
394bdf5b5e Increases the version. 2022-05-01 12:41:47 +02:00
Marcelo Zimbres
a4745a1a5d Better docs. 2022-05-01 12:34:21 +02:00
Marcelo Zimbres
b99da962d1 More improvements in the docs. 2022-05-01 10:48:03 +02:00
Marcelo Zimbres
172de6235c Code simplification. 2022-05-01 08:31:00 +02:00
Marcelo Zimbres
5e99a58685 Fixes the echo_server example. 2022-04-30 22:23:31 +02:00
Marcelo Zimbres
b952a2d2d8 Fixes async_op. 2022-04-30 22:21:36 +02:00
Marcelo Zimbres
16d1f8df24 Commit of
- Documentation.
- Avoids temporaries on connect.
- Removes many unnecessary instantiations of the serializer.
- Fixes ping operation.
2022-04-30 13:09:41 +02:00
Marcelo Zimbres
82b804e397 Increases the version. 2022-04-28 10:56:42 +02:00
Marcelo Zimbres
22530724c6 Improvements in the documentation. 2022-04-28 10:49:19 +02:00
Marcelo Zimbres
610aef5c5e Comparison with redis-plus-plus. 2022-04-27 23:09:27 +02:00
Marcelo Zimbres
87a03a0b6b Moves response_traits to detail. 2022-04-27 10:07:06 +02:00
Marcelo Zimbres
0e3de3688e Improvements in the docs. 2022-04-26 17:58:52 +02:00
Marcelo Zimbres
980c39084f Removes some functions and improves docs. 2022-04-26 15:51:47 +02:00
Marcelo Zimbres
abd339d0f2 Updates the copyright notice. 2022-04-26 10:43:35 +02:00
Marcelo Zimbres
a86a18c969 Using small_vector. 2022-04-26 10:38:14 +02:00
Marcelo Zimbres
f0deda42a4 Updates to boost license. 2022-04-25 22:16:05 +02:00
Marcelo Zimbres
d1e7ec6f03 Removes unused function. 2022-04-25 22:00:16 +02:00
Marcelo Zimbres
0e8e31f310 Replaces all pragma onces with guards. 2022-04-25 14:34:10 +02:00
Marcelo Zimbres
ba86c9fb05 Replaces some accurrences of pragma once with guards. 2022-04-25 11:43:49 +02:00
Marcelo Zimbres
ccef30c8ac Uses boost assert. 2022-04-25 11:28:08 +02:00
Marcelo Zimbres
ce77524d6f Simplifies client code. 2022-04-25 10:53:22 +02:00
Marcelo Zimbres
54a9fddc40 Fixes test. 2022-04-25 09:59:13 +02:00
Marcelo Zimbres
ad3127d1ca Adds function set_receiver. 2022-04-25 09:09:31 +02:00
Marcelo Zimbres
0363353154 Implements async_ping. 2022-04-24 17:37:17 +02:00
Marcelo Zimbres
b453fc63c9 Adds test for idle timeout. 2022-04-24 15:31:26 +02:00
Marcelo Zimbres
1a47dece35 Implements the idle timeout. 2022-04-24 15:10:37 +02:00
Marcelo Zimbres
048a711e51 Simplifies the code. 2022-04-24 11:42:29 +02:00
Marcelo Zimbres
a940f7f4bf Adds test to discard. 2022-04-23 16:37:53 +02:00
Marcelo Zimbres
fd0cae92ee Fixes usage of executors and adds test. 2022-04-22 15:30:36 +02:00
Marcelo Zimbres
a5376bc05f Support reconnect on the client. 2022-04-22 13:36:34 +02:00
Marcelo Zimbres
98580eb0ea Adds test and fixes bug. 2022-04-21 21:35:35 +02:00
Marcelo Zimbres
6ed2d96b07 Adds some tests to the high-level client. 2022-04-21 16:01:31 +02:00
Marcelo Zimbres
245fdb55b6 Adds resolve operation to the client. 2022-04-21 12:01:40 +02:00
Marcelo Zimbres
e6443cbe26 Fixes the documentation. 2022-04-20 16:00:43 +02:00
Marcelo Zimbres
2101def89f Fixes the makefile. 2022-04-20 14:37:51 +02:00
Marcelo Zimbres
ddfe9defc5 More improvements in the examples. 2022-04-20 12:21:10 +02:00
Marcelo Zimbres
cd28ff285f Removes receiver concept and uses callbacks. 2022-04-20 11:46:28 +02:00
Marcelo Zimbres
c4bd338e79 Fixes bug in queue managements. 2022-04-19 15:30:29 +02:00
Marcelo Zimbres
bb28f34ecc Adds adapter for double. 2022-04-17 19:40:42 +02:00
Marcelo Zimbres
aa6251b96c Improves organization of examples. 2022-04-17 19:22:32 +02:00
Marcelo Zimbres
b12140ae8a Adds generic error codes. 2022-04-17 13:09:10 +02:00
Marcelo Zimbres
d845434869 Adds config object to client class. 2022-04-16 20:27:28 +02:00
Marcelo Zimbres
5e86fb9d03 Adds the size of the read to on_read. 2022-04-16 19:59:06 +02:00
Marcelo Zimbres
dc80c04347 Adds timeout to read operations. 2022-04-16 19:51:43 +02:00
Marcelo Zimbres
20d6c67f3d Fix error handling. 2022-04-16 16:47:32 +02:00
Marcelo Zimbres
8c4816fc8d Adds a timer to the write op in the high level client. 2022-04-16 14:14:55 +02:00
Marcelo Zimbres
18aa9ff726 Adds a timeout to the connect. 2022-04-16 12:51:00 +02:00
Marcelo Zimbres
b68e413351 Simplifications. 2022-04-15 18:26:07 +02:00
Marcelo Zimbres
27b3bb89fb Some improvements in the documentation. 2022-04-10 19:24:21 +02:00
Marcelo Zimbres
70cd9b0ffd Updates link to the RESP3 spec. 2022-04-10 12:59:37 +02:00
Marcelo Zimbres
43a08e834c Adds serialization example. 2022-04-10 11:57:30 +02:00
Marcelo Zimbres
19f03e2f41 Changes version. 2022-04-08 12:45:35 +02:00
Marcelo Zimbres
0fff6496fb Improvements in the documentation. 2022-04-08 12:44:45 +02:00
Marcelo Zimbres
57ba376544 Improvements in the documentation. 2022-04-07 23:35:13 +02:00
Marcelo Zimbres
46b86c20e5 Fixes documentation and some examples. 2022-04-07 21:08:03 +02:00
Marcelo Zimbres
577c6d35fb Changes how receivers work. 2022-04-07 16:14:29 +02:00
Marcelo Zimbres
42ef8a3b06 Change signature of adapters. 2022-04-07 09:32:10 +02:00
Marcelo Zimbres
f9af8e585b Unifies the array adapters. 2022-04-07 08:45:24 +02:00
Marcelo Zimbres
350ae19936 Removes typedef. 2022-04-06 20:23:13 +02:00
Marcelo Zimbres
57f2644903 Fixes the build. 2022-04-06 17:25:24 +02:00
Marcelo Zimbres
0ac0c3cf23 Improvements in the docs. 2022-04-05 23:33:20 +02:00
Marcelo Zimbres
5c23299a8a Ports to C++14. 2022-04-05 19:47:20 +02:00
Marcelo Zimbres
379da7a340 Some code improvements. 2022-04-04 22:38:02 +02:00
Marcelo Zimbres
bcf13c03c0 Docs. 2022-04-03 21:42:01 +02:00
Marcelo Zimbres
be79f58808 Simplifies the serializer. 2022-04-03 20:54:39 +02:00
Marcelo Zimbres
2c96b07623 More fixes in the docs. 2022-04-03 12:26:28 +02:00
Marcelo Zimbres
577d32f0e2 Adds low-level adapter examplea and fixes example exe names. 2022-04-02 22:16:41 +02:00
Marcelo Zimbres
5f93257502 One more example. 2022-04-02 16:30:32 +02:00
Marcelo Zimbres
409ee61522 Make the low_level tests independent of coroutine support. 2022-04-02 16:17:17 +02:00
Marcelo Zimbres
5aa034d6a5 Some improvements. 2022-03-30 21:50:28 +02:00
Marcelo Zimbres
0ff0e537ce Improvements. 2022-03-28 23:03:22 +02:00
Marcelo Zimbres
320ee6b3cc Fixes adapter for general aggregates. 2022-03-27 21:13:03 +02:00
Marcelo Zimbres
5061e5a7a6 More improvements in the tests. 2022-03-27 11:28:33 +02:00
Marcelo Zimbres
0bda78dd9c Some refactoring and improvements in the docs. 2022-03-26 21:28:56 +01:00
Marcelo Zimbres
8704e7756f More improvements in the tests. 2022-03-20 21:18:59 +01:00
Marcelo Zimbres
8207ef7e8a Improvements in the tests. 2022-03-20 12:02:42 +01:00
Marcelo Zimbres
4712872daf Many improvements. 2022-03-19 20:55:58 +01:00
Marcelo Zimbres
06752ac664 Adds low level api example. 2022-03-13 20:56:56 +01:00
Marcelo Zimbres
b3be596f77 Moves files around. 2022-03-13 20:43:13 +01:00
Marcelo Zimbres
f92290be16 Improvements in the docs. 2022-03-13 11:59:47 +01:00
Marcelo Zimbres
331010c240 More documentation. 2022-03-12 21:58:28 +01:00
Marcelo Zimbres
11b697c572 Progresses with docs. 2022-03-12 11:08:23 +01:00
Marcelo Zimbres
1218b2ef01 Adds on_push to the receiver. 2022-03-06 21:40:57 +01:00
Marcelo Zimbres
8cc142d55b Many improvements in the docs. 2022-03-06 11:34:59 +01:00
Marcelo Zimbres
0723922ac1 Fixes many bugs in the adapters. 2022-03-04 23:21:02 +01:00
Marcelo Zimbres
54ba34c70c More progresses with examples. 2022-03-02 22:10:57 +01:00
Marcelo Zimbres
efe44fbd45 Simplifies the receiver. 2022-02-28 22:03:51 +01:00
Marcelo Zimbres
268b59afbc More progress. 2022-02-27 21:06:43 +01:00
Marcelo Zimbres
6b6272694c More improvements in the client. 2022-02-26 23:07:34 +01:00
Marcelo Zimbres
f36f3b7b5e Improvements in the code. 2022-02-26 11:26:55 +01:00
Marcelo Zimbres
b4fef73b87 More improvements in the examples. 2022-02-23 23:12:42 +01:00
Marcelo Zimbres
c27b3b6b85 Some improvements in the code. 2022-02-22 21:44:37 +01:00
Marcelo Zimbres
feb383d7e2 Fixes some examples. 2022-02-20 20:00:56 +01:00
Marcelo Zimbres
ee71ff885d Adds single async_run function. 2022-02-20 11:19:34 +01:00
Marcelo Zimbres
80a80f44ff Progresses with the high level api. 2022-02-19 23:05:49 +01:00
Marcelo Zimbres
053ce3aea9 Adds async_run function. 2022-02-19 19:48:17 +01:00
Marcelo Zimbres
db27e3cc60 Improves intro example. 2022-02-19 17:13:02 +01:00
Marcelo Zimbres
449ba0dd73 More improvements. 2022-02-19 13:35:49 +01:00
Marcelo Zimbres
8728f58981 Improvements in the client class. 2022-02-19 13:05:25 +01:00
Marcelo Zimbres
2573f39aa1 More improvements in the client. 2022-02-13 16:20:03 +01:00
Marcelo Zimbres
f460bf43a5 Using vector instead of queue. 2022-02-13 09:16:39 +01:00
Marcelo Zimbres
85d0a30dad Improvements in the client.
- Implements the writer with lightwait coroutines.
2022-02-12 19:58:31 +01:00
Marcelo Zimbres
b7f22d8c61 More improvements in the examples. 2022-02-12 16:31:36 +01:00
Marcelo Zimbres
b04cc12d64 Lots of improvements. 2022-02-10 22:05:40 +01:00
Marcelo Zimbres
317690ee91 Missing files. 2022-02-07 09:31:41 +01:00
Marcelo Zimbres
fe3cc2efb0 Fixes doc with release path. 2022-02-05 22:28:34 +01:00
124 changed files with 10606 additions and 6879 deletions

172
.clang-tidy Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,4 @@
Branch | GH Actions | codecov.io |
:-------------: | ---------- | ---------- |
[`master`](https://github.com/mzimbres/aedis/tree/master) | [![CI](https://github.com/mzimbres/aedis/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/mzimbres/aedis/actions/workflows/ci.yml) | [![codecov](https://codecov.io/gh/mzimbres/aedis/branch/master/graph/badge.svg)](https://codecov.io/gh/mzimbres/aedis/branch/master)

207
CMakeLists.txt Normal file
View 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
View 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"] }
]
}

View File

@@ -1 +0,0 @@
See file:///tmp/aedis/html/installation.html

373
LICENSE
View File

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

View File

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

1037
README.md

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

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

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

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

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

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

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

View File

@@ -0,0 +1,7 @@
var net = require('net');
net.createServer(function(socket){
socket.on('data', function(data){
socket.write(data.toString())
});
}).listen(55555);

View File

@@ -0,0 +1,2 @@
{
}

View File

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

View 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=="
}
}
}

View File

@@ -0,0 +1,6 @@
{
"type": "module",
"dependencies": {
"redis": "^4.2.0"
}
}

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

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

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

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

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

View File

@@ -0,0 +1,4 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/Aedis.cmake")
check_required_components("@PROJECT_NAME@")

View File

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

View File

@@ -24,9 +24,7 @@ div.contents {
padding: 15px;
}
/*
code
{
background-color:#EFD25E;
background-color:#fffbeb;
}
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <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
View 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;
}

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -0,0 +1,80 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_ADAPTER_ADAPT_HPP
#define AEDIS_ADAPTER_ADAPT_HPP
#include <aedis/adapter/detail/response_traits.hpp>
namespace aedis::adapter {
template <class T>
using adapter_t = typename detail::adapter_t<T>;
/** \brief Creates a dummy response adapter.
\ingroup low-level-api
The adapter returned by this function ignores responses. It is
useful to avoid wasting time with responses which are not needed.
Example:
@code
// Pushes and writes some commands to the server.
sr.push(command::hello, 3);
sr.push(command::ping);
sr.push(command::quit);
net::write(socket, net::buffer(request));
// Ignores all responses except for the response to ping.
std::string buffer;
resp3::read(socket, dynamic_buffer(buffer), adapt()); // hello
resp3::read(socket, dynamic_buffer(buffer), adapt(resp)); // ping
resp3::read(socket, dynamic_buffer(buffer, adapt())); // quit
@endcode
*/
inline
auto adapt2() noexcept
{ return detail::response_traits<void>::adapt(); }
/** \brief Adapts user data to read operations.
* \ingroup low-level-api
*
* STL containers, \c std::tuple and built-in types are supported and
* can be used in conjunction with \c std::optional<T>.
*
* Example usage:
*
* @code
* std::unordered_map<std::string, std::string> cont;
* co_await async_read(socket, buffer, adapt(cont));
* @endcode
*
* For a transaction
*
* @code
* sr.push(command::multi);
* sr.push(command::ping, ...);
* sr.push(command::incr, ...);
* sr.push_range(command::rpush, ...);
* sr.push(command::lrange, ...);
* sr.push(command::incr, ...);
* sr.push(command::exec);
*
* co_await async_write(socket, buffer(request));
*
* // Reads the response to a transaction
* std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
* @endcode
*/
template<class T>
auto adapt2(T& t) noexcept
{ return detail::response_traits<T>::adapt(t); }
} // aedis::adapter
#endif // AEDIS_ADAPTER_ADAPT_HPP

View File

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

View 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

View 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

View 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

View 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

View 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

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

View 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

View 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

View 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

View 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

View 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

View File

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

View 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

View 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

View 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

View 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

View 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