Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions include/boost/redis/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#ifndef BOOST_REDIS_CONFIG_HPP
#define BOOST_REDIS_CONFIG_HPP

#include <boost/redis/request.hpp>

#include <chrono>
#include <limits>
#include <optional>
Expand Down Expand Up @@ -40,21 +42,45 @@ struct config {
*/
std::string unix_socket;

/** @brief Username passed to the `HELLO` command.
* If left empty `HELLO` will be sent without authentication parameters.
/** @brief Username used for authentication during connection establishment.
*
* If @ref use_setup is false (the default), during connection establishment,
* authentication is performed by sending a `HELLO` command.
* This field contains the username to employ.
*
* If the username equals the literal `"default"` (the default)
* and no password is specified, the `HELLO` command is sent
* without authentication parameters.
*/
std::string username = "default";

/** @brief Password passed to the
* `HELLO` command. If left
* empty `HELLO` will be sent without authentication parameters.
/** @brief Password used for authentication during connection establishment.
*
* If @ref use_setup is false (the default), during connection establishment,
* authentication is performed by sending a `HELLO` command.
* This field contains the password to employ.
*
* If the username equals the literal `"default"` (the default)
* and no password is specified, the `HELLO` command is sent
* without authentication parameters.
*/
std::string password;

/// Client name parameter of the `HELLO` command.
/** @brief Client name parameter to use during connection establishment.
*
* If @ref use_setup is false (the default), during connection establishment,
* a `HELLO` command is sent. If this field is not empty, the `HELLO` command
* will contain a `SETNAME` subcommand containing this value.
*/
std::string clientname = "Boost.Redis";

/// Database that will be passed to the `SELECT` command.
/** @brief Database index to pass to the `SELECT` command during connection establishment.
*
* If @ref use_setup is false (the default), and this field is set to a
* non-empty optional, and its value is different than zero,
* a `SELECT` command will be issued during connection establishment to set the logical
* database index. By default, no `SELECT` command is sent.
*/
std::optional<int> database_index = 0;

/// Message used by the health-checker in @ref boost::redis::basic_connection::async_run.
Expand Down Expand Up @@ -95,14 +121,47 @@ struct config {
*/
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)();

/** @brief read_buffer_append_size
/** @brief Grow size of the read buffer.
*
* The size by which the read buffer grows when more space is
* needed. This can help avoiding some memory allocations. Once the
* maximum size is reached no more memory allocations are made
* since the buffer is reused.
*/
std::size_t read_buffer_append_size = 4096;

/** @brief Enables using a custom requests during connection establishment.
*
* If set to true, the @ref setup member will be sent to the server immediately after
* connection establishment. Every time a reconnection happens, the setup
* request will be executed before any other request.
* It can be used to perform authentication,
* subscribe to channels or select a database index.
*
* When set to true, *the custom setup request replaces the built-in HELLO
* request generated by the library*. The @ref username, @ref password,
* @ref clientname and @ref database_index fields *will be ignored*.
*
* By default, @ref setup contains a `"HELLO 3"` command, which upgrades the
* protocol to RESP3. You might modify this request as you like,
* but you should ensure that the resulting connection uses RESP3.
*
* To prevent sending any setup request at all, set this field to true
* and @ref setup to an empty request. This can be used to interface with
* systems that don't support `HELLO`.
*
* By default, this field is false, and @ref setup will not be used.
*/
bool use_setup = false;

/** @brief Request to be executed after connection establishment.
*
* This member is only used if @ref use_setup is `true`. Please consult
* @ref use_setup docs for more info.
*
* By default, `setup` contains a `"HELLO 3"` command.
*/
request setup = detail::make_hello_request();
};

} // namespace boost::redis
Expand Down
76 changes: 42 additions & 34 deletions include/boost/redis/connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
#include <boost/redis/detail/connection_logger.hpp>
#include <boost/redis/detail/exec_fsm.hpp>
#include <boost/redis/detail/health_checker.hpp>
#include <boost/redis/detail/hello_utils.hpp>
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/detail/multiplexer.hpp>
#include <boost/redis/detail/reader_fsm.hpp>
#include <boost/redis/detail/redis_stream.hpp>
#include <boost/redis/detail/setup_request_utils.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/logger.hpp>
#include <boost/redis/operation.hpp>
Expand Down Expand Up @@ -86,8 +86,7 @@ struct connection_impl {
multiplexer mpx_;
connection_logger logger_;
read_buffer read_buffer_;
request hello_req_;
generic_response hello_resp_;
generic_response setup_resp_;

using executor_type = Executor;

Expand Down Expand Up @@ -324,25 +323,37 @@ class run_op {

using order_t = std::array<std::size_t, 5>;

static system::error_code on_hello(connection_impl<Executor>& conn, system::error_code ec)
static system::error_code on_setup_finished(
connection_impl<Executor>& conn,
system::error_code ec)
{
ec = check_hello_response(ec, conn.hello_resp_);
conn.logger_.on_hello(ec, conn.hello_resp_);
ec = check_setup_response(ec, conn.setup_resp_);
conn.logger_.on_setup(ec, conn.setup_resp_);
if (ec) {
conn.cancel(operation::run);
}
return ec;
}

template <class CompletionToken>
auto handshaker(CompletionToken&& token)
auto send_setup(CompletionToken&& token)
{
return conn_->async_exec(
conn_->hello_req_,
any_adapter(conn_->hello_resp_),
asio::deferred([&conn = *this->conn_](system::error_code hello_ec, std::size_t) {
return asio::deferred.values(on_hello(conn, hello_ec));
}))(std::forward<CompletionToken>(token));
// clang-format off
// Skip sending the setup request if it's empty
return asio::deferred_t::when(conn_->cfg_.setup.get_commands() != 0u)
.then(
conn_->async_exec(
conn_->cfg_.setup,
any_adapter(conn_->setup_resp_),
asio::deferred([&conn = *this->conn_](system::error_code ec, std::size_t) {
return asio::deferred.values(on_setup_finished(conn, ec));
})
)
)
.otherwise(asio::deferred.values(system::error_code()))
(std::forward<CompletionToken>(token))
;
// clang-format on
}

template <class CompletionToken>
Expand Down Expand Up @@ -382,7 +393,7 @@ class run_op {
system::error_code final_ec;

if (order[0] == 0 && !!ec0) {
// The hello op finished first and with an error
// The setup op finished first and with an error
final_ec = ec0;
} else if (order[0] == 2 && ec2 == error::pong_timeout) {
// The check ping timeout finished first. Use the ping error code
Expand Down Expand Up @@ -411,8 +422,8 @@ class run_op {
return;
}

// Set up the hello request, as it only depends on the config
setup_hello_request(conn_->cfg_, conn_->hello_req_);
// Compose the setup request. This only depends on the config, so it can be done just once
compose_setup_request(conn_->cfg_);

for (;;) {
// Try to connect
Expand All @@ -423,15 +434,16 @@ class run_op {
if (!ec) {
conn_->read_buffer_.clear();
conn_->mpx_.reset();
clear_response(conn_->hello_resp_);
clear_response(conn_->setup_resp_);

// Note: Order is important here because the writer might
// trigger an async_write before the async_hello thereby
// causing an authentication problem.
// trigger an async_write before the setup request is sent,
// causing other requests to be sent before the setup request,
// violating the setup request contract.
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) {
return this->handshaker(token);
return this->send_setup(token);
},
[this](auto token) {
return conn_->health_checker_.async_ping(*conn_, token);
Expand Down Expand Up @@ -526,10 +538,11 @@ class basic_connection {
executor_type ex,
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
logger lgr = {})
: impl_(std::make_unique<detail::connection_impl<Executor>>(
std::move(ex),
std::move(ctx),
std::move(lgr)))
: impl_(
std::make_unique<detail::connection_impl<Executor>>(
std::move(ex),
std::move(ctx),
std::move(lgr)))
{ }

/** @brief Constructor from an executor and a logger.
Expand Down Expand Up @@ -593,8 +606,9 @@ class basic_connection {
* connects to one of the endpoints obtained during name resolution.
* For UNIX domain socket connections, it connects to @ref boost::redis::config::unix_socket.
* @li If @ref boost::redis::config::use_ssl is `true`, performs the TLS handshake.
* @li Sends a `HELLO` command where
* each of its parameters are read from `cfg`.
* @li Executes the setup request, as defined by the passed @ref config object.
* By default, this is a `HELLO` command, but it can contain any other arbitrary
* commands. See the @ref config docs for more info.
* @li Starts a health-check operation where ping commands are sent
* at intervals specified by
* @ref boost::redis::config::health_check_interval.
Expand Down Expand Up @@ -786,10 +800,7 @@ class basic_connection {
class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_exec(request const& req, Response& resp = ignore, CompletionToken&& token = {})
{
return this->async_exec(
req,
any_adapter{resp},
std::forward<CompletionToken>(token));
return this->async_exec(req, any_adapter{resp}, std::forward<CompletionToken>(token));
}

/** @brief Executes commands on the Redis server asynchronously.
Expand Down Expand Up @@ -1093,10 +1104,7 @@ class connection {
template <class Response = ignore_t, class CompletionToken = asio::deferred_t>
auto async_exec(request const& req, Response& resp = ignore, CompletionToken&& token = {})
{
return async_exec(
req,
any_adapter{resp},
std::forward<CompletionToken>(token));
return async_exec(req, any_adapter{resp}, std::forward<CompletionToken>(token));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion include/boost/redis/detail/connection_logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class connection_logger {
void on_ssl_handshake(system::error_code const& ec);
void on_write(system::error_code const& ec, std::size_t n);
void on_fsm_resume(reader_fsm::action const& action);
void on_hello(system::error_code const& ec, generic_response const& resp);
void on_setup(system::error_code const& ec, generic_response const& resp);
void log(logger::level lvl, std::string_view msg);
void log(logger::level lvl, std::string_view op, system::error_code const& ec);
void trace(std::string_view message) { log(logger::level::debug, message); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@
* accompanying file LICENSE.txt)
*/

#ifndef BOOST_REDIS_HELLO_UTILS_HPP
#define BOOST_REDIS_HELLO_UTILS_HPP
#ifndef BOOST_REDIS_SETUP_REQUEST_UTILS_HPP
#define BOOST_REDIS_SETUP_REQUEST_UTILS_HPP

#include <boost/redis/config.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>

namespace boost::redis::detail {

void setup_hello_request(config const& cfg, request& req);
// Modifies config::setup to make a request suitable to be sent
// to the server using async_exec
void compose_setup_request(config& cfg);

// Completely clears a response
void clear_response(generic_response& res);
system::error_code check_hello_response(system::error_code io_ec, const generic_response&);

// Checks that the response to the setup request was successful
system::error_code check_setup_response(system::error_code io_ec, const generic_response&);

} // namespace boost::redis::detail

Expand Down
2 changes: 1 addition & 1 deletion include/boost/redis/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ enum class error
/// Incompatible node depth.
incompatible_node_depth,

/// Resp3 hello command error
/// The setup request sent during connection establishment failed (the name is historical).
resp3_hello,

/// The configuration specified a UNIX socket address, but UNIX sockets are not supported by the system.
Expand Down
5 changes: 3 additions & 2 deletions include/boost/redis/impl/connection_logger.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

#include <boost/redis/detail/connection_logger.hpp>
#include <boost/redis/detail/exec_fsm.hpp>
#include <boost/redis/logger.hpp>

#include <boost/asio/ip/tcp.hpp>
Expand Down Expand Up @@ -174,12 +175,12 @@ void connection_logger::on_fsm_resume(reader_fsm::action const& action)
logger_.fn(logger::level::debug, msg);
}

void connection_logger::on_hello(system::error_code const& ec, generic_response const& resp)
void connection_logger::on_setup(system::error_code const& ec, generic_response const& resp)
{
if (logger_.lvl < logger::level::info)
return;

msg_ = "hello_op: ";
msg_ = "Setup request execution: ";
if (ec) {
format_error_code(ec, msg_);
if (resp.has_error()) {
Expand Down
3 changes: 2 additions & 1 deletion include/boost/redis/impl/error.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ struct error_category_impl : system::error_category {
case error::sync_receive_push_failed:
return "Can't receive server push synchronously without blocking.";
case error::incompatible_node_depth: return "Incompatible node depth.";
case error::resp3_hello: return "RESP3 handshake error (hello command).";
case error::resp3_hello:
return "The setup request sent during connection establishment failed.";
case error::unix_sockets_unsupported:
return "The configuration specified a UNIX socket address, but UNIX sockets are not "
"supported by the system.";
Expand Down
Loading