2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-19 04:42:09 +00:00

Some refactoring.

This commit is contained in:
Marcelo Zimbres
2022-02-05 21:38:18 +01:00
parent 983e844be5
commit 1b15c2d1fe
23 changed files with 403 additions and 463 deletions

View File

@@ -15,12 +15,12 @@ check_PROGRAMS += intro
check_PROGRAMS += sets
check_PROGRAMS += hashes
check_PROGRAMS += serialization
check_PROGRAMS += nested_response
check_PROGRAMS += multipurpose_response
check_PROGRAMS += lists
check_PROGRAMS += key_expiration
check_PROGRAMS += response_adapter
check_PROGRAMS += sync
check_PROGRAMS += redis_client
check_PROGRAMS += multipurpose_client
check_PROGRAMS += test_offline
check_PROGRAMS += test_online
check_PROGRAMS += transaction
@@ -41,12 +41,12 @@ 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
nested_response_SOURCES = $(top_srcdir)/examples/nested_response.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
redis_client_SOURCES = $(top_srcdir)/examples/redis_client.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
@@ -91,7 +91,6 @@ TESTS = $(check_PROGRAMS)
EXTRA_DIST =
EXTRA_DIST += $(top_srcdir)/README.md
EXTRA_DIST += $(top_srcdir)/doc/Doxyfile
EXTRA_DIST += $(top_srcdir)/doc/DoxygenLayout.xml
EXTRA_DIST += $(top_srcdir)/doc/aedis.css
EXTRA_DIST += $(top_srcdir)/doc/htmlfooter.html
@@ -99,6 +98,6 @@ EXTRA_DIST += $(top_srcdir)/doc/htmlheader.html
.PHONY: doc
doc:
rm -rf /tmp/aedis
rm -rf ../aedis-gh-pages/*
doxygen doc/Doxyfile

View File

@@ -18,240 +18,187 @@
#include <aedis/resp3/response_traits.hpp>
#include <aedis/redis/experimental/client.hpp>
/** \mainpage
*
* \b 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/
*
* \b Using \b Aedis
*
* - \subpage installation
* - \subpage examples
*
* \b Reference
*
* - \subpage enums
* - \subpage classes
* - \subpage functions
* - \subpage operators
*/
/** \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
//---------------------------------------------------------
// Pages
In general every feature offered by the library will be
accompained by an example showing how to use it. We also focus in
a more modern asynchronous programming with coroutines.
/** \page examples Examples
*
\b Basics: Focuses on small code snippets that show basic usage of
the library, for example: how to make a request and read the
response, how to deal with keys that may not exist, etc.
\subsection Basics
- intro.cpp
Focuses on small examples that show basic usage of
the library, for example, how to make a request and read the
response, how to deal with keys that may not exist, pubsub, etc.
Some commands are sent to the Redis server and the responses are
printed to screen. A good starting point.
- intro.cpp: A good starting point. Some commands are sent to the
Redis server and the responses are printed to screen.
- transaction.cpp
- transaction.cpp: Shows how to read the responses to a trasaction
efficiently. See also https://redis.io/topics/transactions.
Shows how to read the responses to all commands inside a
trasaction efficiently. At the moment this feature supports only
transactions that contain simple types or aggregates that don't
contain aggregates themselves (as in most cases).
- multipurpose_response.cpp: Shows how to read any responses to
Redis commands, including nested aggegagtes.
- nested_response.cpp
Shows how to read responses to commands that cannot be
translated in a C++ built-in type like std::string or STL
containers, for example all commands contained in a transaction
will be nested by Redis in a single response. Users may have to
convert into their own desired format.
- subscriber.cpp: Shows how channel subscription works at a low
level. See also https://redis.io/topics/pubsub.
- subscriber.cpp
- sync.cpp: Shows hot to use the Aedis synchronous api.
Shows how channel subscription works at a low level.
- key_expiration.cpp: Shows how to use \c std::optional to deal
with keys that may have expired or do not exist.
- sync.cpp
Shows hot to use the Aedis synchronous api.
\subsection stl-containers STL Containers
- 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
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
- 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,
- 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
- sets.cpp: Shows how to read Redis sets in a \c std::set, \c
std::unordered_set and \c std::vector.
Shows how to read Redis sets in a \c std::set, \c std::unordered_set
and \c std::vector.
\subsection customization-points Customization points
\b Customization \b points: Shows how de/serialize user types
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
- serialization.cpp: Shows how to de/serialize your own
non-aggregate data-structures.
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.
- response_adapter.cpp
\subsection async-servers Asynchronous servers
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
Contains some non-trivial examples
servers that interact with users and Redis asynchronously over
long lasting connections using a higher level API.
- redis_client.cpp
- 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.
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.
- echo_server.cpp
- chat_room.cpp: Shows how to build a scalable chat room that
scales to millions of users.
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.
\section using-aedis Using Aedis
- chat_room.cpp
This section contains instructions on how to install aedis on your machine among other things.
Shows how to build a scalable chat room that scales to
millions of users.
\subsection Requirements
Aedis installation requires
- Boost 1.78 or greater
- Unix Shell
- Make
To use Aedis and build its examples and tests you will need
- C++20 with coroutine support.
- Redis server.
Some examples will also require interaction with
- Redis-client: used in on example.
- Redis Sentinel server: used in some examples.
\subsection Installation
Start by downloading and configuring the library
```
# Download the libray on github.
$ wget github-link
# 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
$ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage"\
./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.
\subsubsection Windows
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.
*/
/** \page installation Installation
* \tableofcontents
*
* \section Requirements
*
* Aedis installation requires
*
* - \b Boost \b 1.78 or greater
* - \b Unix \b Shell
* - \b Make
*
* To use Aedis and build its examples and tests you will need
*
* - \b C++20 with \b coroutine support.
* - \b Redis \b server.
*
* Some examples will also require interaction with
*
* - \b Redis-client: used in on example.
* - \b Redis \b Sentinel \b server: used in some examples.
*
* \section Installation
*
* Get the latest release and follow the steps
*
* ```
* # Download the libray on github.
* $ wget github-link
*
* # 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
* $ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage"\
* ./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
*
* ```
*
* To build the examples and test the library in your machine
*
* ```
* # Build aedis examples.
* $ make examples
*
* # Test aedis in your machine.
* $ make check
* ```
*
* \subsection Windows
*
* Windows users can use aedis by either adding the project root
* directory to their include path or manually copying to another
* location.
*
* \section using Using Aedis
*
* This library in not header-only. You have to include the following
* header
*
* ```cpp
* #include <aedis/src.hpp>
* ```
*
* in exactly one source file in your applications.
*
* \section Developers
*
* Aedis uses Autotools for its build system. 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 ...
* ```
/** \defgroup any Reference
*/
//---------------------------------------------------------
// Groups
/** \defgroup enums Enumerations
* \brief Enumerations defined by this library.
*/
/** \defgroup classes Classes
* \brief Classes defined by this library.
*/
/** \defgroup functions Free functions
* \brief All functions defined by this library.
*/
/** \defgroup operators Operators
* \brief Operators defined in Aedis
*/

View File

@@ -7,6 +7,7 @@
#pragma once
// TODO: Remove this.
#include <boost/asio.hpp>
namespace aedis {

View File

@@ -16,7 +16,7 @@ namespace aedis {
namespace redis {
/** \brief Redis commands.
* \ingroup enums
* \ingroup any
*
* For a full list of commands see https://redis.io/commands.
*
@@ -437,7 +437,7 @@ enum class command {
};
/** \brief Converts a command to a string
* \ingroup functions
* \ingroup any
*
* \param c The command to convert.
*/
@@ -452,12 +452,12 @@ char const* to_string(command c);
std::ostream& operator<<(std::ostream& os, command c);
/** \brief Returns true for commands with push response.
* \ingroup functions
* \ingroup any
*/
bool has_push_response(command cmd);
/** \brief Creates a serializer for a \c std::string.
* \ingroup functions
* \ingroup any
* \param storage The string.
*/
template <class CharT, class Traits, class Allocator>

View File

@@ -18,22 +18,33 @@ namespace resp3 {
namespace experimental {
/** \brief A high level redis client.
* \ingroup classes
* \ingroup any
*
* This Redis client keeps a connection to the database open and
* manage reconnections.
* 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:
/// The response adapter type.
using adapter_type = std::function<void(redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&)>;
/** \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)>;
private:
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
/// 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;
@@ -53,18 +64,21 @@ private:
std::queue<request_info> req_info_;
// The stream.
tcp_socket socket_;
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.
adapter_type adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {};
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();
@@ -73,12 +87,6 @@ private:
// to be sent.
net::awaitable<void> writer();
net::awaitable<void> say_hello();
// The connection manager. It keeps trying the reconnect to the
// server when the connection is lost.
net::awaitable<void> connection_manager();
/* Prepares the back of the queue to receive further commands.
*
* If true is returned the request in the front of the queue can be
@@ -95,9 +103,21 @@ public:
*/
client(net::any_io_executor ex);
/** \brief Prepares the client for execution.
/// 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.
*/
void prepare();
net::awaitable<void> engage(socket_type socket);
/** \brief Adds a command to the command queue.
*
@@ -106,8 +126,8 @@ public:
template <class... Ts>
void send(redis::command cmd, Ts const&... args);
/// Sets the response adapter.
void set_adapter(adapter_type adapter);
/// 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);

View File

@@ -18,16 +18,27 @@ 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;;) {
while (!std::empty(req_info_)) {
co_await net::async_write(socket_, net::buffer(requests_.data(), req_info_.front().size));
// 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.
@@ -37,21 +48,41 @@ net::awaitable<void> client::reader()
do { // Keeps reading while there are no messages queued waiting to be sent.
do { // Consumes the responses to all commands in the request.
auto const t = co_await async_read_type(socket_, net::dynamic_buffer(buffer));
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)
{adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);};
{extended_adapter_(redis::command::unknown, 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, 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)
{adapter_(commands_.front(), t, aggregate_size, depth, data, size, 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;
}
@@ -71,14 +102,33 @@ net::awaitable<void> client::reader()
net::awaitable<void> client::writer()
{
boost::system::error_code ec;
while (socket_.is_open()) {
boost::system::error_code ec;
ec = {};
co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
while (!std::empty(req_info_)) {
co_await net::async_write(socket_, net::buffer(requests_.data(), req_info_.front().size));
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)
if (req_info_.front().cmds != 0)
break;
req_info_.pop();
@@ -86,44 +136,6 @@ net::awaitable<void> client::writer()
}
}
net::awaitable<void> client::say_hello()
{
std::string request;
auto sr = redis::make_serializer(request);
sr.push(redis::command::hello, 3);
co_await net::async_write(socket_, net::buffer(request));
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)
{adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);};
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter);
}
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));
}
}
bool client::prepare_next()
{
if (std::empty(req_info_)) {
@@ -131,7 +143,9 @@ bool client::prepare_next()
return true;
}
if (std::size(req_info_) == 1) {
if (req_info_.front().size == 0) {
// It has already been written and we are waiting for the
// responses.
req_info_.push({});
return false;
}
@@ -139,16 +153,9 @@ bool client::prepare_next()
return false;
}
void client::prepare()
void client::set_extended_adapter(extented_adapter_type adapter)
{
net::co_spawn(socket_.get_executor(),
[self = this->shared_from_this()]{ return self->connection_manager(); },
net::detached);
}
void client::set_adapter(adapter_type adapter)
{
adapter_ = adapter;
extended_adapter_ = adapter;
}
void client::set_msg_callback(on_message_type on_msg)
@@ -156,6 +163,37 @@ 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

@@ -13,7 +13,7 @@ namespace aedis {
namespace resp3 {
/** \brief Creates a void response adapter.
\ingroup functions
\ingroup any
The adapter returned by this function ignores responses and is
useful to avoid wasting time with responses which the user is
@@ -30,32 +30,36 @@ auto adapt() noexcept
{ return response_traits<void>::adapt(); }
/** \brief Adapts user data to read operations.
* \ingroup functions
* \ingroup any
*
* For example
* The following types are supported.
*
* 1. Integer data types e.g. `int`, `unsigned`, etc.
* - Integer data types e.g. `int`, `unsigned`, etc.
*
* 1. `std::string`
* - `std::string`
*
* We also support the following C++ containers
*
* 1. `std::vector<T>`. Can be used with any RESP3 aggregate type.
* - `std::vector<T>`. Can be used with any RESP3 aggregate type.
*
* 1. `std::deque<T>`. Can be used with any RESP3 aggregate type.
* - `std::deque<T>`. Can be used with any RESP3 aggregate type.
*
* 1. `std::list<T>`. Can be used with any RESP3 aggregate type.
* - `std::list<T>`. Can be used with any RESP3 aggregate type.
*
* 1. `std::set<T>`. Can be used with RESP3 set type.
* - `std::set<T>`. Can be used with RESP3 set type.
*
* 1. `std::unordered_set<T>`. Can be used with RESP3 set type.
* - `std::unordered_set<T>`. Can be used with RESP3 set type.
*
* 1. `std::map<T>`. Can be used with RESP3 hash type.
* - `std::map<T>`. Can be used with RESP3 hash type.
*
* 1. `std::unordered_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>`.
* 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:
*
@@ -63,6 +67,24 @@ auto adapt() noexcept
* 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

View File

@@ -7,7 +7,7 @@ namespace resp3 {
namespace adapter {
/** \brief Errors that may occurr when reading a response.
* \ingroup enums
* \ingroup any
*/
enum class error
{

View File

@@ -8,7 +8,7 @@ namespace aedis {
namespace resp3 {
/** \brief RESP3 parsing errors.
* \ingroup enums
* \ingroup any
*/
enum class error
{
@@ -54,7 +54,7 @@ std::error_category const& category()
} // detail
/** \brief Converts an error in an std::error_code object.
* \ingroup functions
* \ingroup any
*/
inline
std::error_code make_error_code(error e)

View File

@@ -16,7 +16,7 @@ namespace aedis {
namespace resp3 {
/** \brief A node in the response tree.
* \ingroup classes
* \ingroup any
*
* Redis responses are the pre-order view of the response tree (see
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
@@ -36,31 +36,31 @@ struct node {
};
/** \brief Converts the node to a string.
* \ingroup functions
* \ingroup any
*
* \param obj The node object.
*/
std::string to_string(node const& obj);
/** \brief Compares a node for equality.
* \ingroup operators
* \ingroup any
*/
bool operator==(node const& a, node const& b);
/** \brief Writes the node to the stream.
* \ingroup operators
* \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 functions
* \ingroup any
*/
std::string to_string(std::vector<node> const& vec);
/** \brief Writes the response to the output stream
* \ingroup operators
* \ingroup any
*/
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r);

View File

@@ -27,7 +27,7 @@ namespace aedis {
namespace resp3 {
/** \brief Traits class for response objects.
* \ingroup classes
* \ingroup any
*/
template <class T>
struct response_traits

View File

@@ -13,7 +13,7 @@ namespace aedis {
namespace resp3 {
/** @brief Creates a Redis request from user data.
* \ingroup classes
* \ingroup any
*
* A request is composed of one or more redis commands and is
* referred to in the redis documentation as a pipeline, see

View File

@@ -15,7 +15,7 @@ namespace aedis {
namespace resp3 {
/** \brief RESP3 types
\ingroup enums
\ingroup any
The RESP3 full specification can be found at https://github.com/antirez/RESP3/blob/74adea588783e463c7e84793b325b088fe6edd1c/spec.md
*/
@@ -57,7 +57,7 @@ enum class type
};
/** \brief Returns the string representation of the type.
* \ingroup functions
* \ingroup any
* \param t RESP3 type.
*/
char const* to_string(type t);
@@ -70,13 +70,13 @@ char const* to_string(type t);
std::ostream& operator<<(std::ostream& os, type t);
/** \brief Returns true if the data type is an aggregate.
* \ingroup functions
* \ingroup any
* \param t RESP3 type.
*/
bool is_aggregate(type t);
/** @brief Returns the element multilicity.
* \ingroup functions
* \ingroup any
* \param t RESP3 type.
*
* For type map and attribute this value is 2, all other types have

View File

@@ -13,7 +13,7 @@ namespace aedis {
namespace sentinel {
/** \brief Sentinel commands.
* \ingroup enums
* \ingroup any
*
* For a full list of commands see https://redis.io/topics/sentinel
*
@@ -56,7 +56,7 @@ enum class command {
};
/** \brief Converts a sentinel command to a string
* \ingroup functions
* \ingroup any
*
* \param c The command to convert.
*/
@@ -71,7 +71,7 @@ char const* to_string(command c);
std::ostream& operator<<(std::ostream& os, command c);
/** \brief Returns true for sentinel commands with push response.
* \ingroup functions
* \ingroup any
*/
bool has_push_response(command cmd);

View File

@@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([aedis], [1.0.0], [mzimbres@gmail.com])
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])
@@ -18,5 +18,5 @@ AC_CHECK_HEADER_STDBOOL
AC_TYPE_UINT64_T
AC_CHECK_TYPES([ptrdiff_t])
AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([Makefile doc/Doxyfile])
AC_OUTPUT

View File

@@ -38,13 +38,13 @@ PROJECT_NAME = "Aedis"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 1.0.0
PROJECT_NUMBER = "0.0.1"
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = "A low level Redis client library"
PROJECT_BRIEF = "Low level Redis client library"
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
@@ -58,7 +58,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY = /home/marcelo/my/aedis-gh-pages
OUTPUT_DIRECTORY = ../aedis-gh-pages
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and

View File

@@ -24,7 +24,7 @@ $extrastylesheet
<table bgcolor="#346295" cellspacing="0" cellpadding="6">
<tbody>
<tr>
<td valign="middle" style="color: #FFFFFF" nowrap="nowrap"><font size="6">$projectname</font> &#160; <br> $projectbrief </td>
<td valign="middle" style="color: #FFFFFF" nowrap="nowrap"><font size="6">$projectname $projectnumber</font> &#160; <br> $projectbrief </td>
<td style="width:100%"> $searchbox </td>
</tr>
</tbody>

View File

@@ -12,6 +12,7 @@
#include <aedis/src.hpp>
#include "lib/user_session.hpp"
#include "lib/net_utils.hpp"
namespace net = aedis::net;
using aedis::redis::command;
@@ -59,7 +60,7 @@ public:
resps_.clear();
}
auto get_adapter()
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); };
@@ -69,6 +70,16 @@ public:
{ sessions_.push_back(session); }
};
net::awaitable<void> connection_manager(std::shared_ptr<client> 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;
}
}
net::awaitable<void> listener()
{
auto ex = co_await net::this_coro::executor;
@@ -79,10 +90,10 @@ net::awaitable<void> listener()
{ recv->on_message(ec, cmd); };
auto db = std::make_shared<client>(ex);
db->set_adapter(recv->get_adapter());
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");
db->prepare();
auto on_user_msg = [db](std::string const& msg)
{

View File

@@ -13,6 +13,7 @@
#include <aedis/src.hpp>
#include "lib/user_session.hpp"
#include "lib/net_utils.hpp"
namespace net = aedis::net;
using aedis::redis::command;
@@ -51,13 +52,13 @@ public:
{
std::cout << "Echos so far: " << resps_.front().data << std::endl;
} break;
default: { assert(false); }
default: { /* Ignore */; }
}
resps_.clear();
}
auto get_adapter()
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); };
@@ -67,6 +68,16 @@ public:
{ sessions_.push(session); }
};
net::awaitable<void> connection_manager(std::shared_ptr<client> 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;
}
}
net::awaitable<void> listener()
{
auto ex = co_await net::this_coro::executor;
@@ -77,9 +88,9 @@ net::awaitable<void> listener()
{ recv->on_message(ec, cmd); };
auto db = std::make_shared<client>(ex);
db->set_adapter(recv->get_adapter());
db->set_extended_adapter(recv->get_extended_adapter());
db->set_msg_callback(on_db_msg);
db->prepare();
net::co_spawn(ex, connection_manager(db), net::detached);
for (;;) {
auto socket = co_await acceptor.async_accept(net::use_awaitable);

View File

@@ -51,8 +51,8 @@ net::awaitable<void> ping()
// Print the responses.
std::cout
<< "ping: " << ping << "\n"
<< "incr: " << incr << "\n";
<< "ping: " << ping << "\n"
<< "incr: " << incr << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;

View File

@@ -24,3 +24,28 @@ connect(
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,79 +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;
using resp3::node;
namespace net = aedis::net;
using net::async_write;
using net::buffer;
using net::dynamic_buffer;
net::awaitable<void> nested_response()
{
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);
sr.push(command::ping, "Some message");
sr.push(command::incr, "incr-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::exec);
sr.push(command::quit);
co_await async_write(socket, buffer(request));
// Expected responses.
std::vector<node> exec;
// 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), adapt(exec));
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
// Prints the response.
std::cout << "General format:\n";
for (auto const& e: exec) std::cout << e << "\n";
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
int main()
{
net::io_context ioc;
co_spawn(ioc, nested_response(), net::detached);
ioc.run();
}

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 <aedis/aedis.hpp>
#include <aedis/src.hpp>
namespace net = aedis::net;
using aedis::redis::command;
using aedis::resp3::experimental::client;
using aedis::resp3::node;
using aedis::resp3::type;
int main()
{
try {
std::vector<node> resps;
auto on_msg = [&resps](std::error_code ec, command cmd)
{
if (ec) {
std::cerr << "Error: " << ec.message() << std::endl;
return;
}
std::cout << cmd << ": " << resps.front().data << std::endl;
resps.clear();
};
net::io_context ioc{1};
// This adapter uses the general response that is suitable for
// all commands, so the command parameter will be ignored.
auto adapter = [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 db = std::make_shared<client>(ioc.get_executor());
db->set_adapter(adapter);
db->set_msg_callback(on_msg);
db->send(command::ping, "O rato roeu a roupa do rei de Roma");
db->send(command::incr, "redis-client-counter");
db->send(command::quit);
db->prepare();
ioc.run();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}