mirror of
https://github.com/boostorg/redis.git
synced 2026-01-19 04:42:09 +00:00
Migrates the documentation to Asciidoc/MrDocs (#276)
Fixes some typos close #247
This commit is contained in:
committed by
GitHub
parent
963ae8d145
commit
adf17f2b3b
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@@ -334,3 +334,24 @@ jobs:
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--variant debug,release
|
||||
|
||||
# Checks that we don't have any errors in docs
|
||||
check-docs:
|
||||
name: Check docs
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Boost
|
||||
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
cd ~/boost-root
|
||||
./b2 libs/redis/doc
|
||||
[ -f ~/boost-root/libs/redis/doc/html/index.html ]
|
||||
|
||||
26
doc/CMakeLists.txt
Normal file
26
doc/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
#
|
||||
|
||||
# Root CMakeLists.txt so MrDocs knows how to build our code
|
||||
cmake_minimum_required(VERSION 3.8...3.22)
|
||||
|
||||
# Project
|
||||
project(boost_redis_mrdocs LANGUAGES CXX)
|
||||
|
||||
# MrDocs forces CMAKE_EXPORT_COMPILE_COMMANDS=ON, incorrectly
|
||||
# causing all targets to be dumped to the compilation database.
|
||||
# Disable this setting so we can set EXPORT_COMPILE_COMMANDS
|
||||
# only to our target of interest
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS OFF)
|
||||
|
||||
# Add Boost
|
||||
add_subdirectory($ENV{BOOST_SRC_DIR} deps/boost)
|
||||
|
||||
# Add the target for mrdocs to analyze
|
||||
add_executable(mrdocs mrdocs.cpp)
|
||||
target_link_libraries(mrdocs PRIVATE Boost::redis)
|
||||
set_target_properties(mrdocs PROPERTIES EXPORT_COMPILE_COMMANDS ON)
|
||||
@@ -1,226 +0,0 @@
|
||||
<doxygenlayout version="1.0">
|
||||
<!-- Generated by doxygen 1.9.1 -->
|
||||
<!-- Navigation index tabs for HTML output -->
|
||||
<navindex>
|
||||
<tab type="mainpage" visible="yes" title="Contents"/>
|
||||
<tab type="pages" visible="yes" title="" intro=""/>
|
||||
<tab type="modules" visible="no" title="Reference" intro=""/>
|
||||
<tab type="namespaces" visible="no" title="">
|
||||
<tab type="namespacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="namespacemembers" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="interfaces" visible="no" title="">
|
||||
<tab type="interfacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="classes" visible="no" title="">
|
||||
<tab type="classlist" visible="no" title="" intro=""/>
|
||||
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="hierarchy" visible="no" title="" intro=""/>
|
||||
<tab type="classmembers" visible="no" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="structs" visible="no" title="">
|
||||
<tab type="structlist" visible="no" title="" intro=""/>
|
||||
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
</tab>
|
||||
<tab type="exceptions" visible="no" title="">
|
||||
<tab type="exceptionlist" visible="no" title="" intro=""/>
|
||||
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="files" visible="no" title="">
|
||||
<tab type="filelist" visible="no" title="" intro=""/>
|
||||
<tab type="globals" visible="no" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="examples" visible="yes" title="" intro=""/>
|
||||
</navindex>
|
||||
|
||||
<!-- Layout definition for a class page -->
|
||||
<class>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<inheritancegraph visible="$CLASS_GRAPH"/>
|
||||
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
|
||||
<memberdecl>
|
||||
<nestedclasses visible="yes" title=""/>
|
||||
<publictypes title=""/>
|
||||
<services title=""/>
|
||||
<interfaces title=""/>
|
||||
<publicslots title=""/>
|
||||
<signals title=""/>
|
||||
<publicmethods title=""/>
|
||||
<publicstaticmethods title=""/>
|
||||
<publicattributes title=""/>
|
||||
<publicstaticattributes title=""/>
|
||||
<protectedtypes title=""/>
|
||||
<protectedslots title=""/>
|
||||
<protectedmethods title=""/>
|
||||
<protectedstaticmethods title=""/>
|
||||
<protectedattributes title=""/>
|
||||
<protectedstaticattributes title=""/>
|
||||
<packagetypes title=""/>
|
||||
<packagemethods title=""/>
|
||||
<packagestaticmethods title=""/>
|
||||
<packageattributes title=""/>
|
||||
<packagestaticattributes title=""/>
|
||||
<properties title=""/>
|
||||
<events title=""/>
|
||||
<privatetypes title=""/>
|
||||
<privateslots title=""/>
|
||||
<privatemethods title=""/>
|
||||
<privatestaticmethods title=""/>
|
||||
<privateattributes title=""/>
|
||||
<privatestaticattributes title=""/>
|
||||
<friends title=""/>
|
||||
<related title="" subtitle=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<services title=""/>
|
||||
<interfaces title=""/>
|
||||
<constructors title=""/>
|
||||
<functions title=""/>
|
||||
<related title=""/>
|
||||
<variables title=""/>
|
||||
<properties title=""/>
|
||||
<events title=""/>
|
||||
</memberdef>
|
||||
<allmemberslink visible="yes"/>
|
||||
<usedfiles visible="$SHOW_USED_FILES"/>
|
||||
<authorsection visible="yes"/>
|
||||
</class>
|
||||
|
||||
<!-- Layout definition for a namespace page -->
|
||||
<namespace>
|
||||
<briefdescription visible="yes"/>
|
||||
<memberdecl>
|
||||
<nestednamespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<structs visible="yes" title=""/>
|
||||
<exceptions visible="yes" title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</namespace>
|
||||
|
||||
<!-- Layout definition for a file page -->
|
||||
<file>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<includegraph visible="$INCLUDE_GRAPH"/>
|
||||
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
|
||||
<sourcelink visible="yes"/>
|
||||
<memberdecl>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<structs visible="yes" title=""/>
|
||||
<exceptions visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
</memberdef>
|
||||
<authorsection/>
|
||||
</file>
|
||||
|
||||
<!-- Layout definition for a group page -->
|
||||
<group>
|
||||
<briefdescription visible="yes"/>
|
||||
<groupgraph visible="$GROUP_GRAPHS"/>
|
||||
<memberdecl>
|
||||
<nestedgroups visible="yes" title=""/>
|
||||
<dirs visible="yes" title=""/>
|
||||
<files visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<enumvalues title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<signals title=""/>
|
||||
<publicslots title=""/>
|
||||
<protectedslots title=""/>
|
||||
<privateslots title=""/>
|
||||
<events title=""/>
|
||||
<properties title=""/>
|
||||
<friends title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<pagedocs/>
|
||||
<inlineclasses title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<enumvalues title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<signals title=""/>
|
||||
<publicslots title=""/>
|
||||
<protectedslots title=""/>
|
||||
<privateslots title=""/>
|
||||
<events title=""/>
|
||||
<properties title=""/>
|
||||
<friends title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</group>
|
||||
|
||||
<!-- Layout definition for a directory page -->
|
||||
<directory>
|
||||
<briefdescription visible="yes"/>
|
||||
<directorygraph visible="yes"/>
|
||||
<memberdecl>
|
||||
<dirs visible="yes"/>
|
||||
<files visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
</directory>
|
||||
</doxygenlayout>
|
||||
92
doc/Jamfile
92
doc/Jamfile
@@ -1,85 +1,17 @@
|
||||
project redis/doc ;
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
#
|
||||
|
||||
import doxygen ;
|
||||
import path ;
|
||||
import sequence ;
|
||||
# Adapted from Boost.Unordered
|
||||
make html/index.html : build_antora.sh : @run-script ;
|
||||
|
||||
# All paths must be absolute to work well with the Doxygen rules.
|
||||
path-constant this_dir : . ;
|
||||
path-constant target_dir : html ;
|
||||
path-constant redis_root_dir : .. ;
|
||||
path-constant include_dir : ../include ;
|
||||
path-constant examples_dir : ../example ;
|
||||
path-constant readme : ../README.md ;
|
||||
path-constant layout_file : DoxygenLayout.xml ;
|
||||
path-constant header : header.html ;
|
||||
path-constant footer : footer.html ;
|
||||
|
||||
local stylesheet_files = [ path.glob $(this_dir) : *.css ] ;
|
||||
local includes = [ path.glob-tree $(include_dir) : *.hpp *.cpp ] ;
|
||||
local examples = [ path.glob-tree $(examples_dir) : *.hpp *.cpp ] ;
|
||||
|
||||
# If passed directly, several HTML_EXTRA_STYLESHEET tags are generated,
|
||||
# which is not correct.
|
||||
local stylesheet_arg = [ sequence.join "\"$(stylesheet_files)\"" : " " ] ;
|
||||
|
||||
# The doxygen rule requires the target name to end in .html to generate HTML files
|
||||
doxygen doc.html
|
||||
:
|
||||
$(includes) $(examples) $(readme)
|
||||
:
|
||||
<doxygen:param>"PROJECT_NAME=Boost.Redis"
|
||||
<doxygen:param>PROJECT_NUMBER="1.84.0"
|
||||
<doxygen:param>PROJECT_BRIEF="A redis client library"
|
||||
<doxygen:param>"STRIP_FROM_PATH=\"$(redis_root_dir)\""
|
||||
<doxygen:param>"STRIP_FROM_INC_PATH=\"$(include_dir)\""
|
||||
<doxygen:param>BUILTIN_STL_SUPPORT=YES
|
||||
<doxygen:param>INLINE_SIMPLE_STRUCTS=YES
|
||||
<doxygen:param>HIDE_UNDOC_MEMBERS=YES
|
||||
<doxygen:param>HIDE_UNDOC_CLASSES=YES
|
||||
<doxygen:param>SHOW_HEADERFILE=YES
|
||||
<doxygen:param>SORT_BRIEF_DOCS=YES
|
||||
<doxygen:param>SORT_MEMBERS_CTORS_1ST=YES
|
||||
<doxygen:param>SHOW_FILES=NO
|
||||
<doxygen:param>SHOW_NAMESPACES=NO
|
||||
<doxygen:param>"LAYOUT_FILE=\"$(layout_file)\""
|
||||
<doxygen:param>WARN_IF_INCOMPLETE_DOC=YES
|
||||
<doxygen:param>FILE_PATTERNS="*.hpp *.cpp"
|
||||
<doxygen:param>EXCLUDE_SYMBOLS=std
|
||||
<doxygen:param>"USE_MDFILE_AS_MAINPAGE=\"$(readme)\""
|
||||
<doxygen:param>SOURCE_BROWSER=YES
|
||||
<doxygen:param>"HTML_HEADER=\"$(header)\""
|
||||
<doxygen:param>"HTML_FOOTER=\"$(footer)\""
|
||||
<doxygen:param>"HTML_EXTRA_STYLESHEET=$(stylesheet_arg)"
|
||||
<doxygen:param>HTML_TIMESTAMP=YES
|
||||
<doxygen:param>GENERATE_TREEVIEW=YES
|
||||
<doxygen:param>FULL_SIDEBAR=YES
|
||||
<doxygen:param>DISABLE_INDEX=YES
|
||||
<doxygen:param>ENUM_VALUES_PER_LINE=0
|
||||
<doxygen:param>OBFUSCATE_EMAILS=YES
|
||||
<doxygen:param>USE_MATHJAX=YES
|
||||
<doxygen:param>MATHJAX_VERSION=MathJax_2
|
||||
<doxygen:param>MATHJAX_RELPATH="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/"
|
||||
<doxygen:param>MACRO_EXPANSION=YES
|
||||
<doxygen:param>HAVE_DOT=NO
|
||||
<doxygen:param>CLASS_GRAPH=NO
|
||||
<doxygen:param>DIRECTORY_GRAPH=NO
|
||||
;
|
||||
|
||||
explicit doc.html ;
|
||||
|
||||
# The doxygen rule only informs b2 about the main HTML file, and not about
|
||||
# all the doc directory that gets generated. Using the install rule copies
|
||||
# only a single file, which is incorrect. This is a workaround to copy
|
||||
# the generated docs to the doc/html directory, where they should be.
|
||||
make copyhtml.tag : doc.html : @copy_html_dir ;
|
||||
explicit copyhtml.tag ;
|
||||
actions copy_html_dir
|
||||
# Runs the Antora script
|
||||
actions run-script
|
||||
{
|
||||
rm -rf $(target_dir)
|
||||
mkdir -p $(target_dir)
|
||||
cp -r $(<:D)/html/doc/* $(target_dir)/
|
||||
echo "Stamped" > "$(<)"
|
||||
bash -x $(>)
|
||||
}
|
||||
|
||||
# These are used to inform the build system of the
|
||||
@@ -88,5 +20,5 @@ actions copy_html_dir
|
||||
alias boostdoc ;
|
||||
explicit boostdoc ;
|
||||
|
||||
alias boostrelease : copyhtml.tag ;
|
||||
alias boostrelease : html/index.html ;
|
||||
explicit boostrelease ;
|
||||
|
||||
15
doc/antora.yml
Normal file
15
doc/antora.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
#
|
||||
|
||||
name: redis
|
||||
title: Boost.Redis
|
||||
version: ~
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
ext:
|
||||
cpp-reference:
|
||||
config: doc/mrdocs.yml
|
||||
19
doc/build_antora.sh
Executable file
19
doc/build_antora.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Required by our CMake.
|
||||
# Prevents Antora from cloning Boost again
|
||||
export BOOST_SRC_DIR=$(realpath $SCRIPT_DIR/../../..)
|
||||
|
||||
npm ci
|
||||
npx antora --log-format=pretty redis-playbook.yml
|
||||
@@ -1,145 +0,0 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 335px;
|
||||
}
|
||||
|
||||
#projectname {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#page-wrapper {
|
||||
height: calc(100vh - 100px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#doc-content {
|
||||
overflow-y: scroll;
|
||||
flex: 1;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
html {
|
||||
--searchbar-background: var(--page-background-color);
|
||||
}
|
||||
|
||||
#sidebar-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: var(--side-nav-fixed-width);
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
background-color: var(--side-nav-background);
|
||||
border-right: 1px solid rgb(222, 222, 222);
|
||||
}
|
||||
|
||||
#search-box-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
#MSearchBox {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
|
||||
#MSearchBox .left {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: static;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#MSearchBox .right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#MSearchSelect {
|
||||
padding-left: 0.75em;
|
||||
left: auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
flex: 1;
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#nav-tree {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
#nav-sync {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#top {
|
||||
display: block;
|
||||
border-bottom: none;
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
background: var(--side-nav-background);
|
||||
}
|
||||
|
||||
.ui-resizable-handle {
|
||||
cursor: default;
|
||||
width: 1px !important;
|
||||
}
|
||||
|
||||
#MSearchResultsWindow {
|
||||
left: var(--spacing-medium) !important;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#sidebar-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
<!-- HTML footer for doxygen 1.9.1-->
|
||||
<!-- start footer part -->
|
||||
</div> <!-- close #content-wrapper -->
|
||||
<!--BEGIN GENERATE_TREEVIEW-->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
$navpath
|
||||
<li class="footer">$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion </li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--END GENERATE_TREEVIEW-->
|
||||
<!--BEGIN !GENERATE_TREEVIEW-->
|
||||
<hr class="footer"/><address class="footer"><small>
|
||||
$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion
|
||||
</small></address>
|
||||
<!--END !GENERATE_TREEVIEW-->
|
||||
</div> <!-- #page-wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,61 +0,0 @@
|
||||
<!-- HTML header for doxygen 1.9.1-->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen $doxygenversion"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
$treeview
|
||||
$search
|
||||
$mathjax
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
$extrastylesheet
|
||||
</head>
|
||||
<body>
|
||||
<div id="page-wrapper">
|
||||
<div id="content-wrapper">
|
||||
<div id="sidebar-wrapper">
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<!--BEGIN PROJECT_LOGO-->
|
||||
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
|
||||
<!--END PROJECT_LOGO-->
|
||||
<!--BEGIN PROJECT_NAME-->
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">$projectname
|
||||
<!--BEGIN PROJECT_NUMBER--> <span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
|
||||
</div>
|
||||
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
|
||||
</td>
|
||||
<!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME-->
|
||||
<!--BEGIN PROJECT_BRIEF-->
|
||||
<td style="padding-left: 0.5em;">
|
||||
<div id="projectbrief">$projectbrief</div>
|
||||
</td>
|
||||
<!--END PROJECT_BRIEF-->
|
||||
<!--END !PROJECT_NAME-->
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<div id="search-box-wrapper">
|
||||
$searchbox
|
||||
</div>
|
||||
<!--END SEARCHENGINE-->
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
10
doc/modules/ROOT/nav.adoc
Normal file
10
doc/modules/ROOT/nav.adoc
Normal file
@@ -0,0 +1,10 @@
|
||||
* xref:index.adoc[Introduction]
|
||||
* xref:requests_responses.adoc[]
|
||||
* xref:serialization.adoc[]
|
||||
* xref:logging.adoc[]
|
||||
* xref:benchmarks.adoc[]
|
||||
* xref:comparison.adoc[]
|
||||
* xref:examples.adoc[]
|
||||
* xref:reference.adoc[Reference]
|
||||
* xref:acknowledgements.adoc[]
|
||||
* xref:changelog.adoc[]
|
||||
37
doc/modules/ROOT/pages/acknowledgements.adoc
Normal file
37
doc/modules/ROOT/pages/acknowledgements.adoc
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.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)
|
||||
//
|
||||
|
||||
= Acknowledgements
|
||||
|
||||
Acknowledgement to people that helped shape Boost.Redis
|
||||
|
||||
* Richard Hodges (https://github.com/madmongo1[madmongo1]): For very helpful support with Asio, the design of asynchronous programs, etc.
|
||||
* Vinícius dos Santos Oliveira (https://github.com/vinipsmaker[vinipsmaker]): For useful discussion about how Boost.Redis consumes buffers in the read operation.
|
||||
* Petr Dannhofer (https://github.com/Eddie-cz[Eddie-cz]): For helping me understand how the `AUTH` and `HELLO` command can influence each other.
|
||||
* Mohammad Nejati (https://github.com/ashtum[ashtum]): For pointing out scenarios where calls to `async_exec` should fail when the connection is lost.
|
||||
* Klemens Morgenstern (https://github.com/klemens-morgenstern[klemens-morgenstern]): For useful discussion about timeouts, cancellation, synchronous interfaces and general help with Asio.
|
||||
* Vinnie Falco (https://github.com/vinniefalco[vinniefalco]): For general suggestions about how to improve the code and the documentation.
|
||||
* Bram Veldhoen (https://github.com/bveldhoen[bveldhoen]): For contributing a Redis-streams example.
|
||||
|
||||
Also many thanks to all individuals that participated in the Boost
|
||||
review
|
||||
|
||||
* Zach Laine: https://lists.boost.org/Archives/boost/2023/01/253883.php
|
||||
* Vinnie Falco: https://lists.boost.org/Archives/boost/2023/01/253886.php
|
||||
* Christian Mazakas: https://lists.boost.org/Archives/boost/2023/01/253900.php
|
||||
* Ruben Perez: https://lists.boost.org/Archives/boost/2023/01/253915.php
|
||||
* Dmitry Arkhipov: https://lists.boost.org/Archives/boost/2023/01/253925.php
|
||||
* Alan de Freitas: https://lists.boost.org/Archives/boost/2023/01/253927.php
|
||||
* Mohammad Nejati: https://lists.boost.org/Archives/boost/2023/01/253929.php
|
||||
* Sam Hartsfield: https://lists.boost.org/Archives/boost/2023/01/253931.php
|
||||
* Miguel Portilla: https://lists.boost.org/Archives/boost/2023/01/253935.php
|
||||
* Robert A.H. Leahy: https://lists.boost.org/Archives/boost/2023/01/253928.php
|
||||
|
||||
The Reviews can be found at:
|
||||
https://lists.boost.org/Archives/boost/2023/01/date.php. The thread
|
||||
with the ACCEPT from the review manager can be found here:
|
||||
https://lists.boost.org/Archives/boost/2023/01/253944.php.
|
||||
95
doc/modules/ROOT/pages/benchmarks.adoc
Normal file
95
doc/modules/ROOT/pages/benchmarks.adoc
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.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)
|
||||
//
|
||||
|
||||
= Echo server benchmark
|
||||
|
||||
This document benchmarks the performance of TCP echo servers I
|
||||
implemented in different languages using different Redis clients. The
|
||||
main motivations for choosing an echo server are
|
||||
|
||||
* Simple to implement and does not require expertise level in most languages.
|
||||
* I/O bound: Echo servers have very low CPU consumption in general
|
||||
and therefore are excellent to measure how a program handles concurrent requests.
|
||||
* It simulates very well a typical backend in regard to concurrency.
|
||||
|
||||
I also imposed some constraints on the implementations
|
||||
|
||||
* It should be simple enough and not require writing too much code.
|
||||
* Favor the use standard idioms and avoid optimizations that require expert level.
|
||||
* Avoid the use of complex things like connection and thread pool.
|
||||
|
||||
To reproduce these results run one of the echo-server programs in one
|
||||
terminal and the
|
||||
https://github.com/boostorg/redis/blob/42880e788bec6020dd018194075a211ad9f339e8/benchmarks/cpp/asio/echo_server_client.cpp[echo-server-client] in another.
|
||||
|
||||
== Without Redis
|
||||
|
||||
First I tested a pure TCP echo server, i.e. one that sends the messages
|
||||
directly to the client without interacting with Redis. The result can
|
||||
be seen below
|
||||
|
||||
image::https://boostorg.github.io/redis/tcp-echo-direct.png[]
|
||||
|
||||
The tests were performed with a 1000 concurrent TCP connections on the
|
||||
localhost where latency is 0.07ms on average on my machine. On higher
|
||||
latency networks the difference among libraries is expected to
|
||||
decrease.
|
||||
|
||||
* I expected Libuv to have similar performance to Asio and Tokio.
|
||||
* I did expect nodejs to come a little behind given it is is
|
||||
javascript code. Otherwise I did expect it to have similar
|
||||
performance to libuv since it is the framework behind it.
|
||||
* Go did surprise me: faster than nodejs and libuv!
|
||||
|
||||
The code used in the benchmarks can be found at
|
||||
|
||||
* https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/cpp/asio/echo_server_direct.cpp[Asio]: A variation of https://github.com/chriskohlhoff/asio/blob/4915cfd8a1653c157a1480162ae5601318553eb8/asio/src/examples/cpp20/coroutines/echo_server.cpp[this Asio example].
|
||||
* https://github.com/boostorg/redis/tree/835a1decf477b09317f391eddd0727213cdbe12b/benchmarks/c/libuv[Libuv]: Taken from https://github.com/libuv/libuv/blob/06948c6ee502862524f233af4e2c3e4ca876f5f6/docs/code/tcp-echo-server/main.c[this Libuv example].
|
||||
* https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/rust/echo_server_direct[Tokio]: Taken from https://docs.rs/tokio/latest/tokio/[here].
|
||||
* https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/nodejs/echo_server_direct[Nodejs]
|
||||
* https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/go/echo_server_direct.go[Go]
|
||||
|
||||
== With Redis
|
||||
|
||||
This is similar to the echo server described above but messages are
|
||||
echoed by Redis and not by the echo-server itself, which acts
|
||||
as a proxy between the client and the Redis server. The results
|
||||
can be seen below
|
||||
|
||||
image::https://boostorg.github.io/redis/tcp-echo-over-redis.png[]
|
||||
|
||||
The tests were performed on a network where latency is 35ms on
|
||||
average, otherwise it uses the same number of TCP connections
|
||||
as the previous example.
|
||||
|
||||
As the reader can see, the Libuv and the Rust test are not depicted
|
||||
in the graph, the reasons are
|
||||
|
||||
* https://github.com/redis-rs/redis-rs[redis-rs]: This client
|
||||
comes so far behind that it can't even be represented together
|
||||
with the other benchmarks without making them look insignificant.
|
||||
I don't know for sure why it is so slow, I suppose it has
|
||||
something to do with its lack of automatic
|
||||
https://redis.io/docs/manual/pipelining/[pipelining] support.
|
||||
In fact, the more TCP connections I launch the worse its
|
||||
performance gets.
|
||||
|
||||
* Libuv: I left it out because it would require me writing to much
|
||||
c code. More specifically, I would have to use hiredis and
|
||||
implement support for pipelines manually.
|
||||
|
||||
The code used in the benchmarks can be found at
|
||||
|
||||
* https://github.com/boostorg/redis[Boost.Redis]: https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/examples/echo_server.cpp[code]
|
||||
* https://github.com/redis/node-redis[node-redis]: https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/nodejs/echo_server_over_redis[code]
|
||||
* https://github.com/go-redis/redis[go-redis]: https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/go/echo_server_over_redis.go[code]
|
||||
|
||||
|
||||
== Conclusion
|
||||
|
||||
Redis clients have to support automatic pipelining to have competitive performance. For updates to this document follow https://github.com/boostorg/redis[].
|
||||
|
||||
397
doc/modules/ROOT/pages/changelog.adoc
Normal file
397
doc/modules/ROOT/pages/changelog.adoc
Normal file
@@ -0,0 +1,397 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.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)
|
||||
//
|
||||
|
||||
= Changelog
|
||||
|
||||
== Boost 1.88
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/233[233])
|
||||
To deal with keys that might not exits in the Redis server, the
|
||||
library supports `std::optional`, for example
|
||||
`response<std::optional<std::vector<std::string>>>`. In some cases
|
||||
however, such as the https://redis.io/docs/latest/commands/mget/[MGET] command,
|
||||
each element in the vector might be non exiting, now it is possible
|
||||
to specify a response as `response<std::optional<std::vector<std::optional<std::string>>>>`.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/225[225])
|
||||
Use `deferred` as the connection default completion token.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/128[128])
|
||||
Adds a new `async_exec` overload that allows passing response
|
||||
adapters. This makes it possible to receive Redis responses directly
|
||||
in custom data structures thereby avoiding unnecessary data copying.
|
||||
Thanks to Ruben Perez (@anarthal) for implementing this feature.
|
||||
|
||||
* There are also other multiple small improvements in this release,
|
||||
users can refer to the git history for more details.
|
||||
|
||||
== Boost 1.87
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/205[205])
|
||||
Improves reaction time to disconnection by using `wait_for_one_error`
|
||||
instead of `wait_for_all`. The function `connection::async_run` was
|
||||
also changed to return EOF to the user when that error is received
|
||||
from the server. That is a breaking change.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/210[210])
|
||||
Fixes the adapter of empty nested responses.
|
||||
|
||||
* (Issues https://github.com/boostorg/redis/issues/211[211] and https://github.com/boostorg/redis/issues/212[212])
|
||||
Fixes the reconnect loop that would hang under certain conditions,
|
||||
see the linked issues for more details.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/219[219])
|
||||
Changes the default log level from `disabled` to `debug`.
|
||||
|
||||
== Boost 1.85
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/170[170])
|
||||
Under load and on low-latency networks it is possible to start
|
||||
receiving responses before the write operation completed and while
|
||||
the request is still marked as staged and not written. This messes
|
||||
up with the heuristics that classifies responses as unsolicited or
|
||||
not.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/168[168]).
|
||||
Provides a way of passing a custom SSL context to the connection.
|
||||
The design here differs from that of Boost.Beast and Boost.MySql
|
||||
since in Boost.Redis the connection owns the context instead of only
|
||||
storing a reference to a user provided one. This is ok so because
|
||||
apps need only one connection for their entire application, which
|
||||
makes the overhead of one ssl-context per connection negligible.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/181[181]).
|
||||
See a detailed description of this bug in
|
||||
https://github.com/boostorg/redis/issues/181#issuecomment-1913346983[this comment].
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/182[182]).
|
||||
Sets `"default"` as the default value of `config::username`. This
|
||||
makes it simpler to use the `requirepass` configuration in Redis.
|
||||
|
||||
* (Issue https://github.com/boostorg/redis/issues/189[189]).
|
||||
Fixes narrowing conversion by using `std::size_t` instead of
|
||||
`std::uint64_t` for the sizes of bulks and aggregates. The code
|
||||
relies now on `std::from_chars` returning an error if a value
|
||||
greater than 32 is received on platforms on which the size
|
||||
of `std::size_t` is 32.
|
||||
|
||||
|
||||
== Boost 1.84 (First release in Boost)
|
||||
|
||||
* Deprecates the `async_receive` overload that takes a response. Users
|
||||
should now first call `set_receive_response` to avoid constantly and
|
||||
unnecessarily setting the same response.
|
||||
|
||||
* Uses `std::function` to type erase the response adapter. This change
|
||||
should not influence users in any way but allowed important
|
||||
simplification in the connections internals. This resulted in
|
||||
massive performance improvement.
|
||||
|
||||
* The connection has a new member `get_usage()` that returns the
|
||||
connection usage information, such as number of bytes written,
|
||||
received etc.
|
||||
|
||||
* There are massive performance improvements in the consuming of
|
||||
server pushes which are now communicated with an `asio::channel` and
|
||||
therefore can be buffered which avoids blocking the socket read-loop.
|
||||
Batch reads are also supported by means of `channel.try_send` and
|
||||
buffered messages can be consumed synchronously with
|
||||
`connection::receive`. The function `boost::redis::cancel_one` has
|
||||
been added to simplify processing multiple server pushes contained
|
||||
in the same `generic_response`. *IMPORTANT*: These changes may
|
||||
result in more than one push in the response when
|
||||
`connection::async_receive` resumes. The user must therefore be
|
||||
careful when calling `resp.clear()`: either ensure that all message
|
||||
have been processed or just use `consume_one`.
|
||||
|
||||
== v1.4.2 (incorporates changes to conform the boost review and more)
|
||||
|
||||
* Adds `boost::redis::config::database_index` to make it possible to
|
||||
choose a database before starting running commands e.g. after an
|
||||
automatic reconnection.
|
||||
|
||||
* Massive performance improvement. One of my tests went from
|
||||
140k req/s to 390k/s. This was possible after a parser
|
||||
simplification that reduced the number of reschedules and buffer
|
||||
rotations.
|
||||
|
||||
* Adds Redis stream example.
|
||||
|
||||
* Renames the project to Boost.Redis and moves the code into namespace
|
||||
`boost::redis`.
|
||||
|
||||
* As pointed out in the reviews the `to_bulk` and `from_bulk` names were too
|
||||
generic for ADL customization points. They gained the prefix `boost_redis_`.
|
||||
|
||||
* Moves `boost::redis::resp3::request` to `boost::redis::request`.
|
||||
|
||||
* Adds new typedef `boost::redis::response` that should be used instead of
|
||||
`std::tuple`.
|
||||
|
||||
* Adds new typedef `boost::redis::generic_response` that should be used instead
|
||||
of `std::vector<resp3::node<std::string>>`.
|
||||
|
||||
* Renames `redis::ignore` to `redis::ignore_t`.
|
||||
|
||||
* Changes `async_exec` to receive a `redis::response` instead of an adapter,
|
||||
namely, instead of passing `adapt(resp)` users should pass `resp` directly.
|
||||
|
||||
* Introduces `boost::redis::adapter::result` to store responses to commands
|
||||
including possible resp3 errors without losing the error diagnostic part. To
|
||||
access values now use `std::get<N>(resp).value()` instead of
|
||||
`std::get<N>(resp)`.
|
||||
|
||||
* Implements full-duplex communication. Before these changes the connection
|
||||
would wait for a response to arrive before sending the next one. Now requests
|
||||
are continuously coalesced and written to the socket. `request::coalesce`
|
||||
became unnecessary and was removed. I could measure significative performance
|
||||
gains with these changes.
|
||||
|
||||
* Improves serialization examples using Boost.Describe to serialize to JSON and protobuf. See
|
||||
cpp20_json.cpp and cpp20_protobuf.cpp for more details.
|
||||
|
||||
* Upgrades to Boost 1.81.0.
|
||||
|
||||
* Fixes build with pass:[libc++].
|
||||
|
||||
* Adds high-level functionality to the connection classes. For
|
||||
example, `boost::redis::connection::async_run` will automatically
|
||||
resolve, connect, reconnect and perform health checks.
|
||||
|
||||
== v1.4.0-1
|
||||
|
||||
* Renames `retry_on_connection_lost` to `cancel_if_unresponded`. (v1.4.1)
|
||||
* Removes dependency on Boost.Hana, `boost::string_view`, Boost.Variant2 and Boost.Spirit.
|
||||
* Fixes build and setup CI on windows.
|
||||
|
||||
== v1.3.0-1
|
||||
|
||||
* Upgrades to Boost 1.80.0
|
||||
|
||||
* Removes automatic sending of the `HELLO` command. This can't be
|
||||
implemented properly without bloating the connection class. It is
|
||||
now a user responsibility to send HELLO. Requests that contain it have
|
||||
priority over other requests and will be moved to the front of the
|
||||
queue, see `aedis::request::config`
|
||||
|
||||
* Automatic name resolving and connecting have been removed from
|
||||
`aedis::connection::async_run`. Users have to do this step manually
|
||||
now. The reason for this change is that having them built-in doesn't
|
||||
offer enough flexibility that is need for boost users.
|
||||
|
||||
* Removes healthy checks and idle timeout. This functionality must now
|
||||
be implemented by users, see the examples. This is
|
||||
part of making Aedis useful to a larger audience and suitable for
|
||||
the Boost review process.
|
||||
|
||||
* The `aedis::connection` is now using a typeddef to a
|
||||
`net::ip::tcp::socket` and `aedis::ssl::connection` to
|
||||
`net::ssl::stream<net::ip::tcp::socket>`. Users that need to use
|
||||
other stream type must now specialize `aedis::basic_connection`.
|
||||
|
||||
* Adds a low level example of async code.
|
||||
|
||||
== v1.2.0
|
||||
|
||||
* `aedis::adapt` supports now tuples created with `std::tie`.
|
||||
`aedis::ignore` is now an alias to the type of `std::ignore`.
|
||||
|
||||
* Provides allocator support for the internal queue used in the
|
||||
`aedis::connection` class.
|
||||
|
||||
* Changes the behaviour of `async_run` to complete with success if
|
||||
asio::error::eof is received. This makes it easier to write
|
||||
composed operations with awaitable operators.
|
||||
|
||||
* Adds allocator support in the `aedis::request` (a
|
||||
contribution from Klemens Morgenstern).
|
||||
|
||||
* Renames `aedis::request::push_range2` to `push_range`. The
|
||||
suffix 2 was used for disambiguation. Klemens fixed it with SFINAE.
|
||||
|
||||
* Renames `fail_on_connection_lost` to
|
||||
`aedis::request::config::cancel_on_connection_lost`. Now, it will
|
||||
only cause connections to be canceled when `async_run` completes.
|
||||
|
||||
* Introduces `aedis::request::config::cancel_if_not_connected` which will
|
||||
cause a request to be canceled if `async_exec` is called before a
|
||||
connection has been established.
|
||||
|
||||
* Introduces new request flag `aedis::request::config::retry` that if
|
||||
set to true will cause the request to not be canceled when it was
|
||||
sent to Redis but remained unresponded after `async_run` completed.
|
||||
It provides a way to avoid executing commands twice.
|
||||
|
||||
* Removes the `aedis::connection::async_run` overload that takes
|
||||
request and adapter as parameters.
|
||||
|
||||
* Changes the way `aedis::adapt()` behaves with
|
||||
`std::vector<aedis::resp3::node<T>>`. Receiving RESP3 simple errors,
|
||||
blob errors or null won't causes an error but will be treated as
|
||||
normal response. It is the user responsibility to check the content
|
||||
in the vector.
|
||||
|
||||
* Fixes a bug in `connection::cancel(operation::exec)`. Now this
|
||||
call will only cancel non-written requests.
|
||||
|
||||
* Implements per-operation implicit cancellation support for
|
||||
`aedis::connection::async_exec`. The following call will `co_await (conn.async_exec(...) || timer.async_wait(...))`
|
||||
will cancel the request as long as it has not been written.
|
||||
|
||||
* Changes `aedis::connection::async_run` completion signature to
|
||||
`f(error_code)`. This is how is was in the past, the second
|
||||
parameter was not helpful.
|
||||
|
||||
* Renames `operation::receive_push` to `aedis::operation::receive`.
|
||||
|
||||
== v1.1.0-1
|
||||
|
||||
* Removes `coalesce_requests` from the `aedis::connection::config`, it
|
||||
became a request property now, see `aedis::request::config::coalesce`.
|
||||
|
||||
* Removes `max_read_size` from the `aedis::connection::config`. The maximum
|
||||
read size can be specified now as a parameter of the
|
||||
`aedis::adapt()` function.
|
||||
|
||||
* Removes `aedis::sync` class, see intro_sync.cpp for how to perform
|
||||
synchronous and thread safe calls. This is possible in Boost. 1.80
|
||||
only as it requires `boost::asio::deferred`.
|
||||
|
||||
* Moves from `boost::optional` to `std::optional`. This is part of
|
||||
moving to pass:[C++17].
|
||||
|
||||
* Changes the behaviour of the second `aedis::connection::async_run` overload
|
||||
so that it always returns an error when the connection is lost.
|
||||
|
||||
* Adds TLS support, see intro_tls.cpp.
|
||||
|
||||
* Adds an example that shows how to resolve addresses over sentinels,
|
||||
see subscriber_sentinel.cpp.
|
||||
|
||||
* Adds a `aedis::connection::timeouts::resp3_handshake_timeout`. This is
|
||||
timeout used to send the `HELLO` command.
|
||||
|
||||
* Adds `aedis::endpoint` where in addition to host and port, users can
|
||||
optionally provide username, password and the expected server role
|
||||
(see `aedis::error::unexpected_server_role`).
|
||||
|
||||
* `aedis::connection::async_run` checks whether the server role received in
|
||||
the hello command is equal to the expected server role specified in
|
||||
`aedis::endpoint`. To skip this check let the role variable empty.
|
||||
|
||||
* Removes reconnect functionality from `aedis::connection`. It
|
||||
is possible in simple reconnection strategies but bloats the class
|
||||
in more complex scenarios, for example, with sentinel,
|
||||
authentication and TLS. This is trivial to implement in a separate
|
||||
coroutine. As a result the `enum event` and `async_receive_event`
|
||||
have been removed from the class too.
|
||||
|
||||
* Fixes a bug in `connection::async_receive_push` that prevented
|
||||
passing any response adapter other that `adapt(std::vector<node>)`.
|
||||
|
||||
* Changes the behaviour of `aedis::adapt()` that caused RESP3 errors
|
||||
to be ignored. One consequence of it is that `connection::async_run`
|
||||
would not exit with failure in servers that required authentication.
|
||||
|
||||
* Changes the behaviour of `connection::async_run` that would cause it
|
||||
to complete with success when an error in the
|
||||
`connection::async_exec` occurred.
|
||||
|
||||
* Ports the buildsystem from autotools to CMake.
|
||||
|
||||
== v1.0.0
|
||||
|
||||
* Adds experimental cmake support for windows users.
|
||||
|
||||
* Adds new class `aedis::sync` that wraps an `aedis::connection` in
|
||||
a thread-safe and synchronous API. All free functions from the
|
||||
`sync.hpp` are now member functions of `aedis::sync`.
|
||||
|
||||
* Split `aedis::connection::async_receive_event` in two functions, one
|
||||
to receive events and another for server side pushes, see
|
||||
`aedis::connection::async_receive_push`.
|
||||
|
||||
* Removes collision between `aedis::adapter::adapt` and
|
||||
`aedis::adapt`.
|
||||
|
||||
* Adds `connection::operation` enum to replace `cancel_*` member
|
||||
functions with a single cancel function that gets the operations
|
||||
that should be cancelled as argument.
|
||||
|
||||
* Bugfix: a bug on reconnect from a state where the `connection` object
|
||||
had unsent commands. It could cause `async_exec` to never
|
||||
complete under certain conditions.
|
||||
|
||||
* Bugfix: Documentation of `adapt()` functions were missing from
|
||||
Doxygen.
|
||||
|
||||
== v0.3.0
|
||||
|
||||
* Adds `experimental::exec` and `receive_event` functions to offer a
|
||||
thread safe and synchronous way of executing requests across
|
||||
threads. See `intro_sync.cpp` and `subscriber_sync.cpp` for
|
||||
examples.
|
||||
|
||||
* `connection::async_read_push` was renamed to `async_receive_event`.
|
||||
|
||||
* `connection::async_receive_event` is now being used to communicate
|
||||
internal events to the user, such as resolve, connect, push etc. For
|
||||
examples see cpp20_subscriber.cpp and `connection::event`.
|
||||
|
||||
* The `aedis` directory has been moved to `include` to look more
|
||||
similar to Boost libraries. Users should now replace `-I/aedis-path`
|
||||
with `-I/aedis-path/include` in the compiler flags.
|
||||
|
||||
* The `AUTH` and `HELLO` commands are now sent automatically. This change was
|
||||
necessary to implement reconnection. The username and password
|
||||
used in `AUTH` should be provided by the user on
|
||||
`connection::config`.
|
||||
|
||||
* Adds support for reconnection. See `connection::enable_reconnect`.
|
||||
|
||||
* Fixes a bug in the `connection::async_run(host, port)` overload
|
||||
that was causing crashes on reconnection.
|
||||
|
||||
* Fixes the executor usage in the connection class. Before these
|
||||
changes it was imposing `any_io_executor` on users.
|
||||
|
||||
* `connection::async_receiver_event` is not cancelled anymore when
|
||||
`connection::async_run` exits. This change makes user code simpler.
|
||||
|
||||
* `connection::async_exec` with host and port overload has been
|
||||
removed. Use the other `connection::async_run` overload.
|
||||
|
||||
* The host and port parameters from `connection::async_run` have been
|
||||
move to `connection::config` to better support authentication and
|
||||
failover.
|
||||
|
||||
* Many simplifications in the `chat_room` example.
|
||||
|
||||
* Fixes build in clang the compilers and makes some improvements in
|
||||
the documentation.
|
||||
|
||||
== v0.2.0-1
|
||||
|
||||
* Fixes a bug that happens on very high load. (v0.2.1)
|
||||
* Major rewrite of the high-level API. There is no more need to use the low-level API anymore.
|
||||
* No more callbacks: Sending requests follows the ASIO asynchronous model.
|
||||
* Support for reconnection: Pending requests are not canceled when a connection is lost and are re-sent when a new one is established.
|
||||
* The library is not sending HELLO-3 on user behalf anymore. This is important to support AUTH properly.
|
||||
|
||||
== v0.1.0-2
|
||||
|
||||
* Adds reconnect coroutine in the `echo_server` example. (v0.1.2)
|
||||
* Corrects `client::async_wait_for_data` with `make_parallel_group` to launch operation. (v0.1.2)
|
||||
* Improvements in the documentation. (v0.1.2)
|
||||
* Avoids dynamic memory allocation in the client class after reconnection. (v0.1.2)
|
||||
* Improves the documentation and adds some features to the high-level client. (v.0.1.1)
|
||||
* Improvements in the design and documentation.
|
||||
|
||||
== v0.0.1
|
||||
|
||||
* First release to collect design feedback.
|
||||
|
||||
121
doc/modules/ROOT/pages/comparison.adoc
Normal file
121
doc/modules/ROOT/pages/comparison.adoc
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.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)
|
||||
//
|
||||
|
||||
= Comparison with other Redis clients
|
||||
|
||||
## Comparison
|
||||
|
||||
The main reason for why I started writing Boost.Redis was to have a client
|
||||
compatible with the Asio asynchronous model. As I made progresses I could
|
||||
also address what I considered weaknesses in other libraries. Due to
|
||||
time constraints I won't be able to give a detailed comparison with
|
||||
each client listed in the
|
||||
https://redis.io/docs/clients/#cpp[official list].
|
||||
Instead, I will focus on the most popular pass:[C++] client on github in number of stars, namely:
|
||||
|
||||
https://github.com/sewenew/redis-plus-plus[]
|
||||
|
||||
### Boost.Redis vs Redis-plus-plus
|
||||
|
||||
Before we start it is important to mention some of the things
|
||||
redis-plus-plus does not support
|
||||
|
||||
* The latest version of the communication protocol RESP3. Without that it is impossible to support some important Redis features like client side caching, among other things.
|
||||
* Coroutines.
|
||||
* Reading responses directly in user data structures to avoid creating temporaries.
|
||||
* Error handling with support for error-code.
|
||||
* Cancellation.
|
||||
|
||||
The remaining points will be addressed individually. Let us first
|
||||
have a look at what sending a command a pipeline and a transaction
|
||||
look like
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
auto redis = Redis("tcp://127.0.0.1:6379");
|
||||
|
||||
// Send commands
|
||||
redis.set("key", "val");
|
||||
auto val = redis.get("key"); // val is of type OptionalString.
|
||||
if (val)
|
||||
std::cout << *val << std::endl;
|
||||
|
||||
// Sending pipelines
|
||||
auto pipe = redis.pipeline();
|
||||
auto pipe_replies = pipe.set("key", "value")
|
||||
.get("key")
|
||||
.rename("key", "new-key")
|
||||
.rpush("list", {"a", "b", "c"})
|
||||
.lrange("list", 0, -1)
|
||||
.exec();
|
||||
|
||||
// Parse reply with reply type and index.
|
||||
auto set_cmd_result = pipe_replies.get<bool>(0);
|
||||
// ...
|
||||
|
||||
// Sending a transaction
|
||||
auto tx = redis.transaction();
|
||||
auto tx_replies = tx.incr("num0")
|
||||
.incr("num1")
|
||||
.mget({"num0", "num1"})
|
||||
.exec();
|
||||
|
||||
auto incr_result0 = tx_replies.get<long long>(0);
|
||||
// ...
|
||||
----
|
||||
|
||||
Some of the problems with this API are
|
||||
|
||||
* Heterogeneous treatment of commands, pipelines and transaction. This makes auto-pipelining impossible.
|
||||
* Any Api that sends individual commands has a very restricted scope of usability and should be avoided for performance reasons.
|
||||
* The API imposes exceptions on users, no error-code overload is provided.
|
||||
* No way to reuse the buffer for new calls to e.g. redis.get in order to avoid further dynamic memory allocations.
|
||||
* Error handling of resolve and connection not clear.
|
||||
|
||||
According to the documentation, pipelines in redis-plus-plus have
|
||||
the following characteristics
|
||||
|
||||
> +NOTE+: By default, creating a Pipeline object is NOT cheap, since
|
||||
> it creates a new connection.
|
||||
|
||||
This is clearly a downside in the API as pipelines should be the
|
||||
default way of communicating and not an exception, paying such a
|
||||
high price for each pipeline imposes a severe cost in performance.
|
||||
Transactions also suffer from the very same problem:
|
||||
|
||||
> +NOTE+: Creating a Transaction object is NOT cheap, since it
|
||||
> creates a new connection.
|
||||
|
||||
In Boost.Redis there is no difference between sending one command, a
|
||||
pipeline or a transaction because requests are decoupled
|
||||
from the I/O objects.
|
||||
|
||||
> redis-plus-plus also supports async interface, however, async
|
||||
> support for Transaction and Subscriber is still on the way.
|
||||
>
|
||||
> The async interface depends on third-party event library, and so
|
||||
> far, only libuv is supported.
|
||||
|
||||
Async code in redis-plus-plus looks like the following
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
auto async_redis = AsyncRedis(opts, pool_opts);
|
||||
|
||||
Future<string> ping_res = async_redis.ping();
|
||||
|
||||
cout << ping_res.get() << endl;
|
||||
----
|
||||
|
||||
As the reader can see, the async interface is based on futures
|
||||
which is also known to have a bad performance. The biggest
|
||||
problem however with this async design is that it makes it
|
||||
impossible to write asynchronous programs correctly since it
|
||||
starts an async operation on every command sent instead of
|
||||
enqueueing a message and triggering a write when it can be sent.
|
||||
It is also not clear how are pipelines realised with this design
|
||||
(if at all).
|
||||
27
doc/modules/ROOT/pages/examples.adoc
Normal file
27
doc/modules/ROOT/pages/examples.adoc
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.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)
|
||||
//
|
||||
|
||||
= Examples
|
||||
|
||||
The examples below show how to use the features discussed throughout this documentation:
|
||||
|
||||
* {site-url}/example/cpp20_intro.cpp[cpp20_intro.cpp]: Does not use awaitable operators.
|
||||
* {site-url}/example/cpp20_intro_tls.cpp[cpp20_intro_tls.cpp]: Communicates over TLS.
|
||||
* {site-url}/example/cpp20_unix_sockets.cpp[cpp20_unix_sockets.cpp]: Communicates over UNIX domain sockets.
|
||||
* {site-url}/example/cpp20_containers.cpp[cpp20_containers.cpp]: Shows how to send and receive STL containers and how to use transactions.
|
||||
* {site-url}/example/cpp20_json.cpp[cpp20_json.cpp]: Shows how to serialize types using Boost.Json.
|
||||
* {site-url}/example/cpp20_protobuf.cpp[cpp20_protobuf.cpp]: Shows how to serialize types using protobuf.
|
||||
* {site-url}/example/cpp20_resolve_with_sentinel.cpp[cpp20_resolve_with_sentinel.cpp]: Shows how to resolve a master address using sentinels.
|
||||
* {site-url}/example/cpp20_subscriber.cpp[cpp20_subscriber.cpp]: Shows how to implement pubsub with reconnection re-subscription.
|
||||
* {site-url}/example/cpp20_echo_server.cpp[cpp20_echo_server.cpp]: A simple TCP echo server.
|
||||
* {site-url}/example/cpp20_chat_room.cpp[cpp20_chat_room.cpp]: A command line chat built on Redis pubsub.
|
||||
* {site-url}/example/cpp17_intro.cpp[cpp17_intro.cpp]: Uses callbacks and requires pass:[C++17].
|
||||
* {site-url}/example/cpp17_intro_sync.cpp[cpp17_intro_sync.cpp]: Runs `async_run` in a separate thread and performs synchronous calls to `async_exec`.
|
||||
* {site-url}/example/cpp17_spdlog.cpp[cpp17_spdlog.cpp]: Shows how to use third-party logging libraries like `spdlog` with Boost.Redis.
|
||||
|
||||
The main function used in some async examples has been factored out in
|
||||
the {site-url}/example/main.cpp[main.cpp] file.
|
||||
143
doc/modules/ROOT/pages/index.adoc
Normal file
143
doc/modules/ROOT/pages/index.adoc
Normal file
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.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)
|
||||
//
|
||||
|
||||
[#intro]
|
||||
= Introduction
|
||||
|
||||
Boost.Redis is a high-level https://redis.io/[Redis] client library built on top of
|
||||
https://www.boost.org/doc/libs/latest/doc/html/boost_asio.html[Boost.Asio]
|
||||
that implements the Redis protocol
|
||||
https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md[RESP3].
|
||||
|
||||
== Requirements
|
||||
|
||||
The requirements for using Boost.Redis are:
|
||||
|
||||
* Boost 1.84 or higher. Boost.Redis is included in Boost installations since Boost 1.84.
|
||||
* pass:[C++17] or higher. Supported compilers include gcc 11 and later, clang 11 and later, and Visual Studio 16 (2019) and later.
|
||||
* Redis 6 or higher (must support RESP3).
|
||||
* OpenSSL.
|
||||
|
||||
The documentation assumes basic-level knowledge about https://redis.io/docs/[Redis] and https://www.boost.org/doc/libs/latest/doc/html/boost_asio.html[Boost.Asio].
|
||||
|
||||
== Building the library
|
||||
|
||||
To use the library it is necessary to include the following:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
#include <boost/redis/src.hpp>
|
||||
----
|
||||
|
||||
in exactly one source file in your applications. Otherwise, the library is header-only.
|
||||
|
||||
Boost.Redis unconditionally requires OpenSSL. Targets using Boost.Redis need to link
|
||||
to the OpenSSL libraries.
|
||||
|
||||
== Tutorial
|
||||
|
||||
The code below uses a short-lived connection to
|
||||
https://redis.io/commands/ping/[ping] a Redis server:
|
||||
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto co_main(config const& cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
|
||||
// A request containing only a ping command.
|
||||
request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
// Response object.
|
||||
response<std::string> resp;
|
||||
|
||||
// Executes the request.
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
|
||||
}
|
||||
----
|
||||
|
||||
The roles played by the `async_run` and `async_exec` functions are:
|
||||
|
||||
* xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`connection::async_exec`]: executes the commands contained in the
|
||||
request and stores the individual responses in the response object. Can
|
||||
be called from multiple places in your code concurrently.
|
||||
* xref:reference:boost/redis/basic_connection/async_run-04.adoc[`connection::async_run`]: keeps the connection healthy. It takes care of hostname resolution, session establishment, health-checks, reconnection and coordination of low-level read and write operations. It should be called only once per connection, regardless of the number of requests to execute.
|
||||
|
||||
== Server pushes
|
||||
|
||||
Redis servers can also send a variety of pushes to the client. Some of
|
||||
them are:
|
||||
|
||||
* https://redis.io/docs/manual/pubsub/[Pubsub messages].
|
||||
* https://redis.io/docs/manual/keyspace-notifications/[Keyspace notifications].
|
||||
* https://redis.io/docs/manual/client-side-caching/[Client-side caching].
|
||||
|
||||
The connection class supports server pushes by means of the
|
||||
xref:reference:boost/redis/basic_connection/async_receive.adoc[`connection::async_receive`] function, which can be
|
||||
called in the same connection that is being used to execute commands.
|
||||
The coroutine below shows how to use it:
|
||||
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
auto
|
||||
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
generic_response resp;
|
||||
conn->set_receive_response(resp);
|
||||
|
||||
// Loop while reconnection is enabled
|
||||
while (conn->will_reconnect()) {
|
||||
|
||||
// Reconnect to channels.
|
||||
co_await conn->async_exec(req, ignore);
|
||||
|
||||
// Loop reading Redis pushes.
|
||||
for (;;) {
|
||||
error_code ec;
|
||||
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
|
||||
// Use the response resp in some way and then clear it.
|
||||
...
|
||||
|
||||
consume_one(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Further reading
|
||||
|
||||
Here is a list of topics that you might be interested in:
|
||||
|
||||
* xref:requests_responses.adoc[More on requests and responses].
|
||||
* xref:serialization.adoc[Serializing and parsing into custom types].
|
||||
* xref:logging.adoc[Configuring logging].
|
||||
* xref:examples.adoc[Examples].
|
||||
45
doc/modules/ROOT/pages/logging.adoc
Normal file
45
doc/modules/ROOT/pages/logging.adoc
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
//
|
||||
|
||||
= Logging
|
||||
|
||||
xref:reference:boost/redis/basic_connection/async_run-04.adoc[`connection::async_run`]
|
||||
is a complex algorithm, with features like built-in reconnection.
|
||||
This can make configuration problems, like a misconfigured hostname, difficult to debug -
|
||||
Boost.Redis will keep retrying to connect to the same hostname over and over.
|
||||
For this reason, Boost.Redis incorporates a lightweight logging solution, and
|
||||
*will log some status messages to stderr by default*.
|
||||
|
||||
Logging can be customized by passing a
|
||||
xref:reference:boost/redis/logger.adoc[`logger`] object to the connection's constructor. For example, logging can be disabled by writing:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
asio::io_context ioc;
|
||||
connection conn {ioc, logger{logger::level::disabled}};
|
||||
----
|
||||
|
||||
Every message logged by the library is attached a
|
||||
https://en.wikipedia.org/wiki/Syslog#Severity_level[syslog-like severity]
|
||||
tag (a xref:reference:boost/redis/logger/level.adoc[`logger::level`]).
|
||||
You can filter messages by severity by creating a `logger` with a specific level:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
asio::io_context ioc;
|
||||
|
||||
// Logs to stderr messages with severity >= level::error.
|
||||
// This will hide all informational output.
|
||||
connection conn {ioc, logger{logger::level::error}};
|
||||
----
|
||||
|
||||
The `logger` constructor accepts a `std::function<void(logger::level, std::string_view)>`
|
||||
as second argument. If supplied, Boost.Redis will call this function when logging
|
||||
instead of printing to stderr. This can be used to integrate third-party logging
|
||||
libraries. See our {site-url}/example/cpp17_spdlog.cpp[spdlog integration example]
|
||||
for sample code.
|
||||
90
doc/modules/ROOT/pages/reference.adoc
Normal file
90
doc/modules/ROOT/pages/reference.adoc
Normal file
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
//
|
||||
|
||||
[#reference]
|
||||
= Reference
|
||||
|
||||
[width=100%,cols="5*"]
|
||||
|===
|
||||
|
||||
| *Connections*
|
||||
| *Requests and responses*
|
||||
| *Adapters*
|
||||
| *RESP3 protocol*
|
||||
| *Unstable low-level APIs*
|
||||
|
||||
|
|
||||
xref:reference:boost/redis/connection.adoc[`connection`]
|
||||
|
||||
xref:reference:boost/redis/basic_connection.adoc[`basic_connection`]
|
||||
|
||||
xref:reference:boost/redis/address.adoc[`address`]
|
||||
|
||||
xref:reference:boost/redis/config.adoc[`config`]
|
||||
|
||||
xref:reference:boost/redis/error.adoc[`error`]
|
||||
|
||||
xref:reference:boost/redis/logger.adoc[`logger`]
|
||||
|
||||
xref:reference:boost/redis/logger/level.adoc[`logger::level`]
|
||||
|
||||
xref:reference:boost/redis/operation.adoc[`operation`]
|
||||
|
||||
xref:reference:boost/redis/usage.adoc[`usage`]
|
||||
|
||||
|
||||
|
|
||||
xref:reference:boost/redis/ignore_t.adoc[`ignore_t`]
|
||||
|
||||
xref:reference:boost/redis/ignore.adoc[`ignore`]
|
||||
|
||||
xref:reference:boost/redis/request.adoc[`request`]
|
||||
|
||||
xref:reference:boost/redis/request/config.adoc[`request::config`]
|
||||
|
||||
xref:reference:boost/redis/response.adoc[`response`]
|
||||
|
||||
xref:reference:boost/redis/generic_response.adoc[`generic_response`]
|
||||
|
||||
xref:reference:boost/redis/consume_one-08.adoc[`consume_one`]
|
||||
|
||||
|
||||
|
|
||||
xref:reference:boost/redis/adapter/boost_redis_adapt.adoc[`boost_redis_adapt`]
|
||||
|
||||
xref:reference:boost/redis/adapter/ignore.adoc[`adapter::ignore`]
|
||||
|
||||
xref:reference:boost/redis/adapter/error.adoc[`adapter::error`]
|
||||
|
||||
xref:reference:boost/redis/adapter/result.adoc[`adapter::result`]
|
||||
|
||||
xref:reference:boost/redis/any_adapter.adoc[`any_adapter`]
|
||||
|
||||
|
|
||||
xref:reference:boost/redis/resp3/basic_node.adoc[`basic_node`]
|
||||
|
||||
xref:reference:boost/redis/resp3/node.adoc[`node`]
|
||||
|
||||
xref:reference:boost/redis/resp3/node_view.adoc[`node_view`]
|
||||
|
||||
xref:reference:boost/redis/resp3/boost_redis_to_bulk-08.adoc[`boost_redis_to_bulk`]
|
||||
|
||||
xref:reference:boost/redis/resp3/type.adoc[`type`]
|
||||
|
||||
xref:reference:boost/redis/resp3/is_aggregate.adoc[`is_aggregate`]
|
||||
|
||||
|
||||
|
|
||||
|
||||
xref:reference:boost/redis/adapter/adapt2.adoc[`adapter::adapt2`]
|
||||
|
||||
xref:reference:boost/redis/resp3/parser.adoc[`parser`]
|
||||
|
||||
xref:reference:boost/redis/resp3/parse.adoc[`parse`]
|
||||
|
||||
|===
|
||||
302
doc/modules/ROOT/pages/requests_responses.adoc
Normal file
302
doc/modules/ROOT/pages/requests_responses.adoc
Normal file
@@ -0,0 +1,302 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.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)
|
||||
//
|
||||
|
||||
= Requests and responses
|
||||
|
||||
== Requests
|
||||
|
||||
Redis requests are composed of one or more commands. In the
|
||||
Redis documentation, requests are called
|
||||
https://redis.io/topics/pipelining[pipelines]. For example:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Some example containers.
|
||||
std::list<std::string> list {...};
|
||||
std::map<std::string, mystruct> map { ...};
|
||||
|
||||
// The request can contain multiple commands.
|
||||
request req;
|
||||
|
||||
// Command with variable length of arguments.
|
||||
req.push("SET", "key", "some value", "EX", "2");
|
||||
|
||||
// Pushes a list.
|
||||
req.push_range("SUBSCRIBE", list);
|
||||
|
||||
// Same as above but as an iterator range.
|
||||
req.push_range("SUBSCRIBE", std::cbegin(list), std::cend(list));
|
||||
|
||||
// Pushes a map.
|
||||
req.push_range("HSET", "key", map);
|
||||
----
|
||||
|
||||
Sending a request to Redis is performed by
|
||||
xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`connection::async_exec`]
|
||||
as already stated. Requests accept a xref:reference:boost/redis/request/config[`boost::redis::request::config`]
|
||||
object when constructed that dictates how requests are handled in situations like
|
||||
reconnection. The reader is advised to read it carefully.
|
||||
|
||||
## Responses
|
||||
|
||||
Boost.Redis uses the following strategy to deal with Redis responses:
|
||||
|
||||
* xref:reference:boost/redis/response.adoc[`boost::redis::response`] should be used
|
||||
when the request's number of commands is known at compile-time.
|
||||
* xref:reference:boost/redis/generic_response.adoc[`boost::redis::generic_response`] should be
|
||||
used when the number of commands is dynamic.
|
||||
|
||||
For example, the request below has three commands:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("INCR", "key");
|
||||
req.push("QUIT");
|
||||
----
|
||||
|
||||
Therefore, its response will also contain three elements.
|
||||
The following response object can be used:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
response<std::string, int, std::string>
|
||||
----
|
||||
|
||||
The response behaves as a `std::tuple` and must
|
||||
have as many elements as the request has commands (exceptions below).
|
||||
It is also necessary that each tuple element is capable of storing the
|
||||
response to the command it refers to, otherwise an error will occur.
|
||||
|
||||
To ignore responses to individual commands in the request use the tag
|
||||
xref:reference:boost/redis/ignore_t.adoc[`boost::redis::ignore_t`]. For example:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Ignore the second and last responses.
|
||||
response<std::string, ignore_t, std::string, ignore_t>
|
||||
----
|
||||
|
||||
The following table provides the RESP3-types returned by some Redis
|
||||
commands:
|
||||
|
||||
[cols="3*"]
|
||||
|===
|
||||
|
||||
| *Command* | *RESP3 type* | *Documentation*
|
||||
|
||||
| `lpush` | Number | https://redis.io/commands/lpush[]
|
||||
| `lrange` | Array | https://redis.io/commands/lrange[]
|
||||
| `set` | Simple-string, null or blob-string | https://redis.io/commands/set[]
|
||||
| `get` | Blob-string | https://redis.io/commands/get[]
|
||||
| `smembers` | Set | https://redis.io/commands/smembers[]
|
||||
| `hgetall` | Map | https://redis.io/commands/hgetall[]
|
||||
|
||||
|===
|
||||
|
||||
To map these RESP3 types into a pass:[C++] data structure use the table below:
|
||||
|
||||
[cols="3*"]
|
||||
|===
|
||||
|
||||
| *RESP3 type* | *Possible pass:[C++] type* | *Type*
|
||||
|
||||
| Simple-string | `std::string` | Simple
|
||||
| Simple-error | `std::string` | Simple
|
||||
| Blob-string | `std::string`, `std::vector` | Simple
|
||||
| Blob-error | `std::string`, `std::vector` | Simple
|
||||
| Number | `long long`, `int`, `std::size_t`, `std::string` | Simple
|
||||
| Double | `double`, `std::string` | Simple
|
||||
| Null | `std::optional<T>` | Simple
|
||||
| Array | `std::vector`, `std::list`, `std::array`, `std::deque` | Aggregate
|
||||
| Map | `std::vector`, `std::map`, `std::unordered_map` | Aggregate
|
||||
| Set | `std::vector`, `std::set`, `std::unordered_set` | Aggregate
|
||||
| Push | `std::vector`, `std::map`, `std::unordered_map` | Aggregate
|
||||
|
||||
|===
|
||||
|
||||
For example, the response to the request
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("RPUSH", "key1", vec);
|
||||
req.push_range("HSET", "key2", map);
|
||||
req.push("LRANGE", "key3", 0, -1);
|
||||
req.push("HGETALL", "key4");
|
||||
req.push("QUIT");
|
||||
----
|
||||
|
||||
Can be read in the following response object:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
response<
|
||||
redis::ignore_t, // hello
|
||||
int, // rpush
|
||||
int, // hset
|
||||
std::vector<T>, // lrange
|
||||
std::map<U, V>, // hgetall
|
||||
std::string // quit
|
||||
> resp;
|
||||
----
|
||||
|
||||
To execute the request and read the response use
|
||||
xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`async_exec`]:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
co_await conn->async_exec(req, resp);
|
||||
----
|
||||
|
||||
If the intention is to ignore responses altogether, use
|
||||
xref:reference:boost/redis/ignore.adoc[`ignore`]:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Ignores the response
|
||||
co_await conn->async_exec(req, ignore);
|
||||
----
|
||||
|
||||
Responses that contain nested aggregates or heterogeneous data
|
||||
types will be given special treatment later in xref:#the-general-case[the general case]. As
|
||||
of this writing, not all RESP3 types are used by the Redis server,
|
||||
which means in practice users will be concerned with a reduced
|
||||
subset of the RESP3 specification.
|
||||
|
||||
### Pushes
|
||||
|
||||
Commands that have no response, like
|
||||
|
||||
* `"SUBSCRIBE"`
|
||||
* `"PSUBSCRIBE"`
|
||||
* `"UNSUBSCRIBE"`
|
||||
|
||||
must **NOT** be included in the response tuple. For example, the following request
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("QUIT");
|
||||
----
|
||||
|
||||
must be read in the response object `response<std::string, std::string>`.
|
||||
|
||||
### Null
|
||||
|
||||
It is not uncommon for apps to access keys that do not exist or that
|
||||
have already expired in the Redis server. To deal with these use cases,
|
||||
wrap the type within a `std::optional`:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
response<
|
||||
std::optional<A>,
|
||||
std::optional<B>,
|
||||
...
|
||||
> resp;
|
||||
----
|
||||
|
||||
Everything else stays the same.
|
||||
|
||||
### Transactions
|
||||
|
||||
To read responses to transactions we must first observe that Redis
|
||||
will queue the transaction commands and send their individual
|
||||
responses as elements of an array. The array itself is the response to
|
||||
the `EXEC` command. For example, to read the response to this request
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
req.push("MULTI");
|
||||
req.push("GET", "key1");
|
||||
req.push("LRANGE", "key2", 0, -1);
|
||||
req.push("HGETALL", "key3");
|
||||
req.push("EXEC");
|
||||
----
|
||||
|
||||
Use the following response type:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
response<
|
||||
ignore_t, // multi
|
||||
ignore_t, // QUEUED
|
||||
ignore_t, // QUEUED
|
||||
ignore_t, // QUEUED
|
||||
response<
|
||||
std::optional<std::string>, // get
|
||||
std::optional<std::vector<std::string>>, // lrange
|
||||
std::optional<std::map<std::string, std::string>> // hgetall
|
||||
> // exec
|
||||
> resp;
|
||||
----
|
||||
|
||||
For a complete example, see {site-url}/example/cpp20_containers.cpp[cpp20_containers.cpp].
|
||||
|
||||
[#the-general-case]
|
||||
### The general case
|
||||
|
||||
There are cases where responses to Redis
|
||||
commands won't fit in the model presented above. Some examples are:
|
||||
|
||||
* Commands (like `set`) whose responses don't have a fixed
|
||||
RESP3 type. Expecting an `int` and receiving a blob-string
|
||||
results in an error.
|
||||
* RESP3 aggregates that contain nested aggregates can't be read in STL containers.
|
||||
* Transactions with a dynamic number of commands can't be read in a `response`.
|
||||
|
||||
To deal with these cases Boost.Redis provides the xref:reference:boost/redis/resp3/node.adoc[`boost::redis::resp3::node`] type
|
||||
abstraction, that is the most general form of an element in a
|
||||
response, be it a simple RESP3 type or the element of an aggregate. It
|
||||
is defined like:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
template <class String>
|
||||
struct basic_node {
|
||||
// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
// The number of elements of an aggregate (or 1 for simple data).
|
||||
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 always empty.
|
||||
String value;
|
||||
};
|
||||
----
|
||||
|
||||
Any response to a Redis command can be parsed into a
|
||||
xref:reference:boost/redis/generic_response.adoc[boost::redis::generic_response].
|
||||
The vector can be seen as a pre-order view of the response tree.
|
||||
Using it is not different than using other types:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Receives any RESP3 simple or aggregate data type.
|
||||
boost::redis::generic_response resp;
|
||||
co_await conn->async_exec(req, resp);
|
||||
----
|
||||
|
||||
For example, suppose we want to retrieve a hash data structure
|
||||
from Redis with `HGETALL`, some of the options are
|
||||
|
||||
* `boost::redis::generic_response`: always works.
|
||||
* `std::vector<std::string>`: efficient and flat, all elements as string.
|
||||
* `std::map<std::string, std::string>`: efficient if you need the data as a `std::map`.
|
||||
* `std::map<U, V>`: efficient if you are storing serialized data. Avoids temporaries and requires `boost_redis_from_bulk` for `U` and `V`.
|
||||
|
||||
In addition to the above users can also use unordered versions of the
|
||||
containers. The same reasoning applies to sets e.g. `SMEMBERS`
|
||||
and other data structures in general.
|
||||
26
doc/modules/ROOT/pages/serialization.adoc
Normal file
26
doc/modules/ROOT/pages/serialization.adoc
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.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)
|
||||
//
|
||||
|
||||
= Serializing and parsing into custom types
|
||||
|
||||
Boost.Redis supports serialization of user defined types by means of
|
||||
the following customization points
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
// Serialize
|
||||
void boost_redis_to_bulk(std::string& to, mystruct const& obj);
|
||||
|
||||
// Deserialize
|
||||
void boost_redis_from_bulk(mystruct& u, node_view const& node, boost::system::error_code&);
|
||||
----
|
||||
|
||||
These functions are accessed over ADL and therefore they must be
|
||||
imported in the global namespace by the user. The following examples might be of interest:
|
||||
|
||||
* {site-url}/example/cpp20_json.cpp[cpp20_json.cpp]: serializes and parses JSON objects.
|
||||
* {site-url}/example/cpp20_protobuf.cpp[cpp20_protobuf.cpp]: serializes and parses https://protobuf.dev/[protobuf] objects.
|
||||
10
doc/mrdocs.cpp
Normal file
10
doc/mrdocs.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
//
|
||||
|
||||
#define BOOST_ALLOW_DEPRECATED // avoid mrdocs errors with the BOOST_DEPRECATED macro
|
||||
|
||||
#include <boost/redis.hpp>
|
||||
31
doc/mrdocs.yml
Normal file
31
doc/mrdocs.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
#
|
||||
|
||||
source-root: ../include
|
||||
compilation-database: ./CMakeLists.txt
|
||||
include-symbols:
|
||||
- "boost::redis::**"
|
||||
exclude-symbols:
|
||||
- "boost::redis::detail::**"
|
||||
- "boost::redis::adapter::detail::**"
|
||||
- "boost::redis::resp3::detail::**"
|
||||
- "boost::redis::basic_connection::run_is_canceled"
|
||||
- "boost::redis::basic_connection::this_type"
|
||||
- "boost::redis::any_adapter::impl_t"
|
||||
- "boost::redis::any_adapter::fn_type"
|
||||
- "boost::redis::any_adapter::create_impl"
|
||||
- "boost::redis::any_adapter::impl_"
|
||||
- "boost::redis::request::payload"
|
||||
- "boost::redis::request::has_hello_priority"
|
||||
see-below:
|
||||
- "boost::redis::adapter::ignore"
|
||||
sort-members: false
|
||||
base-url: https://github.com/boostorg/redis/blob/master/include/
|
||||
use-system-libc: true
|
||||
warn-as-error: true
|
||||
warn-if-undocumented: false
|
||||
warn-no-paramdoc: false
|
||||
1955
doc/package-lock.json
generated
Normal file
1955
doc/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
doc/package.json
Normal file
6
doc/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@cppalliance/antora-cpp-reference-extension": "^0.0.6",
|
||||
"antora": "^3.1.10"
|
||||
}
|
||||
}
|
||||
45
doc/redis-playbook.yml
Normal file
45
doc/redis-playbook.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail 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)
|
||||
#
|
||||
|
||||
site:
|
||||
url: https://github.com/boostorg/redis/blob/master
|
||||
title: Boost.Redis
|
||||
robots: allow
|
||||
start_page: redis::index.adoc
|
||||
|
||||
antora:
|
||||
extensions:
|
||||
- require: '@cppalliance/antora-cpp-reference-extension'
|
||||
dependencies:
|
||||
- name: 'boost'
|
||||
repo: 'https://github.com/boostorg/boost.git'
|
||||
tag: 'develop'
|
||||
variable: 'BOOST_SRC_DIR'
|
||||
system-env: 'BOOST_SRC_DIR'
|
||||
|
||||
asciidoc:
|
||||
attributes:
|
||||
# Scrolling problems appear without this
|
||||
page-pagination: ''
|
||||
|
||||
content:
|
||||
sources:
|
||||
- url: ..
|
||||
start_path: doc
|
||||
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/boostorg/website-v2-docs/releases/download/ui-master/ui-bundle.zip
|
||||
snapshot: true
|
||||
output_dir: _
|
||||
|
||||
output:
|
||||
dir: html
|
||||
|
||||
runtime:
|
||||
log:
|
||||
failure_level: error
|
||||
@@ -9,16 +9,7 @@
|
||||
|
||||
#include <boost/redis/adapter/detail/response_traits.hpp>
|
||||
#include <boost/redis/adapter/detail/result_traits.hpp>
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
#include <boost/system.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
#include <boost/redis/ignore.hpp>
|
||||
|
||||
namespace boost::redis::adapter {
|
||||
|
||||
@@ -26,13 +17,13 @@ namespace boost::redis::adapter {
|
||||
*
|
||||
* The type T must be either
|
||||
*
|
||||
* 1. a response<T1, T2, T3, ...> or
|
||||
* 2. std::vector<node<String>>
|
||||
* @li a `response<T1, T2, T3, ...>`
|
||||
* @li `std::vector<node<String>>`
|
||||
*
|
||||
* The types T1, T2, etc can be any STL container, any integer type
|
||||
* and `std::string`.
|
||||
*
|
||||
* @param t Tuple containing the responses.
|
||||
* @tparam T The response type.
|
||||
*/
|
||||
template <class T>
|
||||
auto boost_redis_adapt(T& t) noexcept
|
||||
@@ -40,36 +31,9 @@ auto boost_redis_adapt(T& t) noexcept
|
||||
return detail::response_traits<T>::adapt(t);
|
||||
}
|
||||
|
||||
/** @brief Adapts user data to read operations.
|
||||
* @ingroup low-level-api
|
||||
/** @brief Adapts a type to be used as the response to an individual command.
|
||||
*
|
||||
* STL containers, \c resp3::response and built-in types are supported and
|
||||
* can be used in conjunction with \c std::optional<T>.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code
|
||||
* std::unordered_map<std::string, std::string> cont;
|
||||
* co_await async_read(socket, buffer, adapt(cont));
|
||||
* @endcode
|
||||
*
|
||||
* For a transaction
|
||||
*
|
||||
* @code
|
||||
* sr.push(command::multi);
|
||||
* sr.push(command::ping, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push_range(command::rpush, ...);
|
||||
* sr.push(command::lrange, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push(command::exec);
|
||||
*
|
||||
* co_await async_write(socket, buffer(request));
|
||||
*
|
||||
* // Reads the response to a transaction
|
||||
* resp3::response<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
* @endcode
|
||||
* It can be used with low-level APIs, like @ref boost::redis::resp3::parser.
|
||||
*/
|
||||
template <class T>
|
||||
auto adapt2(T& t = redis::ignore) noexcept
|
||||
|
||||
@@ -20,17 +20,17 @@
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief A type-erased reference to a response.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* A type-erased response adapter. It can be executed using @ref connection::async_exec.
|
||||
* Using this type instead of raw response references enables separate compilation.
|
||||
*
|
||||
* Given a response object `resp` that can be passed to `async_exec`, the following two
|
||||
* statements have the same effect:
|
||||
* ```
|
||||
*
|
||||
* @code
|
||||
* co_await conn.async_exec(req, resp);
|
||||
* co_await conn.async_exec(req, any_response(resp));
|
||||
* ```
|
||||
* @endcode
|
||||
*/
|
||||
class any_adapter {
|
||||
public:
|
||||
|
||||
@@ -12,12 +12,9 @@
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace boost::redis::adapter {
|
||||
|
||||
/** @brief An adapter that ignores responses
|
||||
* @ingroup high-level-api
|
||||
/** @brief An adapter that ignores responses.
|
||||
*
|
||||
* RESP3 errors won't be ignored.
|
||||
*/
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
|
||||
namespace boost::redis::adapter {
|
||||
|
||||
/** @brief Stores any resp3 error
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
/// Stores any resp3 error.
|
||||
struct error {
|
||||
/// RESP3 error data type.
|
||||
resp3::type data_type = resp3::type::invalid;
|
||||
@@ -47,12 +45,15 @@ inline bool operator==(error const& a, error const& b)
|
||||
*/
|
||||
inline bool operator!=(error const& a, error const& b) { return !(a == b); }
|
||||
|
||||
/** @brief Stores response to individual Redis commands
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
/// Stores response to individual Redis commands.
|
||||
template <class Value>
|
||||
using result = system::result<Value, error>;
|
||||
|
||||
/**
|
||||
* @brief Allows using @ref error with `boost::system::result`.
|
||||
* @param e The error to throw.
|
||||
* @relates error
|
||||
*/
|
||||
BOOST_NORETURN inline void throw_exception_from_error(error const& e, boost::source_location const&)
|
||||
{
|
||||
system::error_code ec;
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Address of a Redis server
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
/// Address of a Redis server.
|
||||
struct address {
|
||||
/// Redis host.
|
||||
std::string host = "127.0.0.1";
|
||||
@@ -24,40 +22,42 @@ struct address {
|
||||
std::string port = "6379";
|
||||
};
|
||||
|
||||
/** @brief Configure parameters used by the connection classes
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
/// Configure parameters used by the connection classes.
|
||||
struct config {
|
||||
/// Uses SSL instead of a plain connection.
|
||||
bool use_ssl = false;
|
||||
|
||||
/// Address of the Redis server.
|
||||
/// For TCP connections, hostname and port of the Redis server.
|
||||
address addr = address{"127.0.0.1", "6379"};
|
||||
|
||||
/// The UNIX domain socket path where the server is listening. If non-empty,
|
||||
/// communication with the server will happen using UNIX domain sockets, and addr will be ignored.
|
||||
/// UNIX domain sockets can't be used with SSL: if `unix_socket` is non-empty, `use_ssl` must be false.
|
||||
/**
|
||||
* @brief The UNIX domain socket path where the server is listening.
|
||||
*
|
||||
* If non-empty, communication with the server will happen using
|
||||
* UNIX domain sockets, and @ref addr will be ignored.
|
||||
* UNIX domain sockets can't be used with SSL: if `unix_socket` is non-empty,
|
||||
* @ref use_ssl must be `false`.
|
||||
*/
|
||||
std::string unix_socket;
|
||||
|
||||
/** @brief Username passed to the
|
||||
* [HELLO](https://redis.io/commands/hello/) command. If left
|
||||
* empty `HELLO` will be sent without authentication parameters.
|
||||
/** @brief Username passed to the `HELLO` command.
|
||||
* If left empty `HELLO` will be sent without authentication parameters.
|
||||
*/
|
||||
std::string username = "default";
|
||||
|
||||
/** @brief Password passed to the
|
||||
* [HELLO](https://redis.io/commands/hello/) command. If left
|
||||
* `HELLO` command. If left
|
||||
* empty `HELLO` will be sent without authentication parameters.
|
||||
*/
|
||||
std::string password;
|
||||
|
||||
/// Client name parameter of the [HELLO](https://redis.io/commands/hello/) command.
|
||||
/// Client name parameter of the `HELLO` command.
|
||||
std::string clientname = "Boost.Redis";
|
||||
|
||||
/// Database that will be passed to the [SELECT](https://redis.io/commands/hello/) command.
|
||||
/// Database that will be passed to the `SELECT` command.
|
||||
std::optional<int> database_index = 0;
|
||||
|
||||
/// Message used by the health-checker in `boost::redis::connection::async_run`.
|
||||
/// Message used by the health-checker in @ref boost::redis::basic_connection::async_run.
|
||||
std::string health_check_id = "Boost.Redis";
|
||||
|
||||
/**
|
||||
@@ -69,28 +69,26 @@ struct config {
|
||||
*/
|
||||
std::string log_prefix = "(Boost.Redis) ";
|
||||
|
||||
/// Time the resolve operation is allowed to last.
|
||||
/// Time span that the resolve operation is allowed to elapse.
|
||||
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Time the connect operation is allowed to last.
|
||||
/// Time span that the connect operation is allowed to elapse.
|
||||
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Time the SSL handshake operation is allowed to last.
|
||||
/// Time span that the SSL handshake operation is allowed to elapse.
|
||||
std::chrono::steady_clock::duration ssl_handshake_timeout = std::chrono::seconds{10};
|
||||
|
||||
/** Health checks interval.
|
||||
*
|
||||
* To disable health-checks pass zero as duration.
|
||||
/** @brief Time span between successive health checks.
|
||||
* Set to zero to disable health-checks pass zero as duration.
|
||||
*/
|
||||
std::chrono::steady_clock::duration health_check_interval = std::chrono::seconds{2};
|
||||
|
||||
/** @brief Time waited before trying a reconnection.
|
||||
*
|
||||
* To disable reconnection pass zero as duration.
|
||||
/** @brief Time span to wait between successive connection retries.
|
||||
* Set to zero to disable reconnection.
|
||||
*/
|
||||
std::chrono::steady_clock::duration reconnect_wait_interval = std::chrono::seconds{1};
|
||||
|
||||
/** @brief Maximum size of a socket read.
|
||||
/** @brief Maximum size of a socket read, in bytes.
|
||||
*
|
||||
* Sets a limit on how much data is allowed to be read into the
|
||||
* read buffer. It can be used to prevent DDOS.
|
||||
|
||||
@@ -407,30 +407,25 @@ logger make_stderr_logger(logger::level lvl, std::string prefix);
|
||||
} // namespace detail
|
||||
|
||||
/** @brief A SSL connection to the Redis server.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* This class keeps a healthy connection to the Redis instance where
|
||||
* commands can be sent at any time. For more details, please see the
|
||||
* documentation of each individual function.
|
||||
*
|
||||
* @tparam Socket The socket type e.g. asio::ip::tcp::socket.
|
||||
*
|
||||
* @tparam Executor The executor type used to create any required I/O objects.
|
||||
*/
|
||||
template <class Executor>
|
||||
class basic_connection {
|
||||
public:
|
||||
using this_type = basic_connection<Executor>;
|
||||
|
||||
/// Type of the next layer
|
||||
/// (Deprecated) Type of the next layer
|
||||
BOOST_DEPRECATED("This typedef is deprecated, and will be removed with next_layer().")
|
||||
typedef asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp, Executor>> next_layer_type;
|
||||
|
||||
/// Executor type
|
||||
/// The type of the executor associated to this object.
|
||||
using executor_type = Executor;
|
||||
|
||||
/// Returns the associated executor.
|
||||
executor_type get_executor() noexcept { return writer_timer_.get_executor(); }
|
||||
|
||||
/// Rebinds the socket type to another executor.
|
||||
template <class Executor1>
|
||||
struct rebind_executor {
|
||||
@@ -438,9 +433,9 @@ public:
|
||||
using other = basic_connection<Executor1>;
|
||||
};
|
||||
|
||||
/** @brief Constructor
|
||||
/** @brief Constructor from an executor.
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param ex Executor used to create all internal I/O objects.
|
||||
* @param ctx SSL context.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
@@ -461,9 +456,9 @@ public:
|
||||
writer_timer_.expires_at((std::chrono::steady_clock::time_point::max)());
|
||||
}
|
||||
|
||||
/** @brief Constructor
|
||||
/** @brief Constructor from an executor and a logger.
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param ex Executor used to create all internal I/O objects.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
@@ -477,7 +472,15 @@ public:
|
||||
std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Constructs from a context.
|
||||
/**
|
||||
* @brief Constructor from an `io_context`.
|
||||
*
|
||||
* @param ioc I/O context used to create all internal I/O objects.
|
||||
* @param ctx SSL context.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
*/
|
||||
explicit basic_connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
@@ -485,40 +488,52 @@ public:
|
||||
: basic_connection(ioc.get_executor(), std::move(ctx), std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Constructs from a context.
|
||||
basic_connection(asio::io_context& ctx, logger lgr)
|
||||
/**
|
||||
* @brief Constructor from an `io_context` and a logger.
|
||||
*
|
||||
* @param ioc I/O context used to create all internal I/O objects.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
*/
|
||||
basic_connection(asio::io_context& ioc, logger lgr)
|
||||
: basic_connection(
|
||||
ctx.get_executor(),
|
||||
ioc.get_executor(),
|
||||
asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::move(lgr))
|
||||
{ }
|
||||
|
||||
/** @brief Starts underlying connection operations.
|
||||
/// Returns the associated executor.
|
||||
executor_type get_executor() noexcept { return writer_timer_.get_executor(); }
|
||||
|
||||
/** @brief Starts the underlying connection operations.
|
||||
*
|
||||
* This member function provides the following functionality
|
||||
* This function establishes a connection to the Redis server and keeps
|
||||
* it healthy by performing the following operations:
|
||||
*
|
||||
* 1. Resolve the address passed on `boost::redis::config::addr`.
|
||||
* 2. Connect to one of the results obtained in the resolve operation.
|
||||
* 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`.
|
||||
* 4. Start a health-check operation where ping commands are sent
|
||||
* at intervals specified in
|
||||
* `boost::redis::config::health_check_interval`. The message passed to
|
||||
* `PING` will be `boost::redis::config::health_check_id`. Passing a
|
||||
* timeout with value zero will disable health-checks. If the Redis
|
||||
* server does not respond to a health-check within two times the value
|
||||
* specified here, it will be considered unresponsive and the connection
|
||||
* will be closed and a new connection will be stablished.
|
||||
* 5. Starts read and write operations with the Redis
|
||||
* server. More specifically it will trigger the write of all
|
||||
* requests i.e. calls to `async_exec` that happened prior to this
|
||||
* call.
|
||||
* @li For TCP connections, resolves the server hostname passed in
|
||||
* @ref boost::redis::config::addr.
|
||||
* @li Establishes a physical connection to the server. For TCP connections,
|
||||
* 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 Starts a health-check operation where ping commands are sent
|
||||
* at intervals specified by
|
||||
* @ref boost::redis::config::health_check_interval.
|
||||
* The message passed to `PING` will be @ref boost::redis::config::health_check_id.
|
||||
* Passing an interval with a zero value will disable health-checks. If the Redis
|
||||
* server does not respond to a health-check within two times the value
|
||||
* specified here, it will be considered unresponsive and the connection
|
||||
* will be closed.
|
||||
* @li Starts read and write operations. Requests issued using @ref async_exec
|
||||
* before `async_run` is called will be written to the server immediately.
|
||||
*
|
||||
* When a connection is lost for any reason, a new one is
|
||||
* stablished automatically. To disable reconnection call
|
||||
* `boost::redis::connection::cancel(operation::reconnection)`.
|
||||
*
|
||||
* @param cfg Configuration paramters.
|
||||
* @param token Completion token.
|
||||
* established automatically. To disable reconnection call
|
||||
* `boost::redis::connection::cancel(operation::reconnection)`
|
||||
* or set @ref boost::redis::config::reconnect_wait_interval to zero.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
*
|
||||
@@ -528,6 +543,9 @@ public:
|
||||
*
|
||||
* For example on how to call this function refer to
|
||||
* cpp20_intro.cpp or any other example.
|
||||
*
|
||||
* @param cfg Configuration parameters.
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
auto async_run(config const& cfg, CompletionToken&& token = {})
|
||||
@@ -543,7 +561,8 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc async_run
|
||||
* @brief (Deprecated) Starts the underlying connection operations.
|
||||
* @copydetail async_run
|
||||
*
|
||||
* This function accepts an extra logger parameter. The passed `logger::lvl`
|
||||
* will be used, but `logger::fn` will be ignored. Instead, a function
|
||||
@@ -555,6 +574,10 @@ public:
|
||||
* The logger should be passed to the connection's constructor instead of using this
|
||||
* function. Use the overload without a logger parameter, instead. This function is
|
||||
* deprecated and will be removed in subsequent releases.
|
||||
*
|
||||
* @param cfg Configuration parameters.
|
||||
* @param l Logger.
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
BOOST_DEPRECATED(
|
||||
@@ -568,13 +591,16 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc async_run
|
||||
* @brief (Deprecated) Starts the underlying connection operations.
|
||||
* @copydetail async_run
|
||||
*
|
||||
* Uses a default-constructed config object to run the connection.
|
||||
*
|
||||
* @par Deprecated
|
||||
* This function is deprecated and will be removed in subsequent releases.
|
||||
* Use the overload taking an explicit config object, instead.
|
||||
*
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
BOOST_DEPRECATED(
|
||||
@@ -589,13 +615,11 @@ public:
|
||||
*
|
||||
* When pushes arrive and there is no `async_receive` operation in
|
||||
* progress, pushed data, requests, and responses will be paused
|
||||
* until `async_receive` is called again. Apps will usually want
|
||||
* until `async_receive` is called again. Apps will usually want
|
||||
* to call `async_receive` in a loop.
|
||||
*
|
||||
* To cancel an ongoing receive operation apps should call
|
||||
* `connection::cancel(operation::receive)`.
|
||||
*
|
||||
* @param token Completion token.
|
||||
* `basic_connection::cancel(operation::receive)`.
|
||||
*
|
||||
* For an example see cpp20_subscriber.cpp. The completion token must
|
||||
* have the following signature
|
||||
@@ -606,6 +630,8 @@ public:
|
||||
*
|
||||
* Where the second parameter is the size of the push received in
|
||||
* bytes.
|
||||
*
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive(CompletionToken&& token = {})
|
||||
@@ -618,10 +644,9 @@ public:
|
||||
* Receives a server push synchronously by calling `try_receive` on
|
||||
* the underlying channel. If the operation fails because
|
||||
* `try_receive` returns `false`, `ec` will be set to
|
||||
* `boost::redis::error::sync_receive_push_failed`.
|
||||
* @ref boost::redis::error::sync_receive_push_failed.
|
||||
*
|
||||
* @param ec Contains the error if any occurred.
|
||||
*
|
||||
* @returns The number of bytes read from the socket.
|
||||
*/
|
||||
std::size_t receive(system::error_code& ec)
|
||||
@@ -652,12 +677,9 @@ public:
|
||||
* underlying stream. Multiple concurrent calls to this function
|
||||
* will be automatically queued by the implementation.
|
||||
*
|
||||
* @param req Request.
|
||||
* @param resp Response.
|
||||
* @param token Completion token.
|
||||
* For an example see cpp20_echo_server.cpp.
|
||||
*
|
||||
* For an example see cpp20_echo_server.cpp. The completion token must
|
||||
* have the following signature
|
||||
* The completion token must have the following signature:
|
||||
*
|
||||
* @code
|
||||
* void f(system::error_code, std::size_t);
|
||||
@@ -670,11 +692,18 @@ public:
|
||||
* This operation supports per-operation cancellation. The following cancellation types
|
||||
* are supported:
|
||||
*
|
||||
* - `asio::cancellation_type_t::terminal`. Always supported. May cause the current
|
||||
* `async_run` operation to be cancelled.
|
||||
* - `asio::cancellation_type_t::partial` and `asio::cancellation_type_t::total`.
|
||||
* Supported only if the request hasn't been written to the network yet.
|
||||
* @li `asio::cancellation_type_t::terminal`. Always supported. May cause the current
|
||||
* `async_run` operation to be cancelled.
|
||||
* @li `asio::cancellation_type_t::partial` and `asio::cancellation_type_t::total`.
|
||||
* Supported only if the request hasn't been written to the network yet.
|
||||
*
|
||||
* @par Object lifetimes
|
||||
* Both `req` and `res` should be kept alive until the operation completes.
|
||||
* No copies of the request object are made.
|
||||
*
|
||||
* @param req The request to be executed.
|
||||
* @param resp The response object to parse data into.
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <
|
||||
class Response = ignore_t,
|
||||
@@ -684,10 +713,43 @@ public:
|
||||
return this->async_exec(req, any_adapter(resp), std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/** @copydoc async_exec
|
||||
/** @brief Executes commands on the Redis server asynchronously.
|
||||
*
|
||||
* @details This function uses the type-erased @ref any_adapter class, which
|
||||
* encapsulates a reference to a response object.
|
||||
* This function sends a request to the Redis server and waits for
|
||||
* the responses to each individual command in the request. If the
|
||||
* request contains only commands that don't expect a response,
|
||||
* the completion occurs after it has been written to the
|
||||
* underlying stream. Multiple concurrent calls to this function
|
||||
* will be automatically queued by the implementation.
|
||||
*
|
||||
* For an example see cpp20_echo_server.cpp.
|
||||
*
|
||||
* The completion token must have the following signature:
|
||||
*
|
||||
* @code
|
||||
* void f(system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the response received
|
||||
* in bytes.
|
||||
*
|
||||
* @par Per-operation cancellation
|
||||
* This operation supports per-operation cancellation. The following cancellation types
|
||||
* are supported:
|
||||
*
|
||||
* @li `asio::cancellation_type_t::terminal`. Always supported. May cause the current
|
||||
* `async_run` operation to be cancelled.
|
||||
* @li `asio::cancellation_type_t::partial` and `asio::cancellation_type_t::total`.
|
||||
* Supported only if the request hasn't been written to the network yet.
|
||||
*
|
||||
* @par Object lifetimes
|
||||
* Both `req` and any response object referenced by `adapter`
|
||||
* should be kept alive until the operation completes.
|
||||
* No copies of the request object are made.
|
||||
*
|
||||
* @param req The request to be executed.
|
||||
* @param adapter An adapter object referencing a response to place data into.
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
auto async_exec(request const& req, any_adapter adapter, CompletionToken&& token = {})
|
||||
@@ -714,14 +776,14 @@ public:
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* @li `operation::exec`: Cancels operations started with
|
||||
* @li `operation::exec`: cancels operations started with
|
||||
* `async_exec`. Affects only requests that haven't been written
|
||||
* yet.
|
||||
* @li operation::run: Cancels the `async_run` operation.
|
||||
* @li operation::receive: Cancels any ongoing calls to `async_receive`.
|
||||
* @li operation::all: Cancels all operations listed above.
|
||||
* @li `operation::run`: cancels the `async_run` operation.
|
||||
* @li `operation::receive`: cancels any ongoing calls to `async_receive`.
|
||||
* @li `operation::all`: cancels all operations listed above.
|
||||
*
|
||||
* @param op: The operation to be cancelled.
|
||||
* @param op The operation to be cancelled.
|
||||
*/
|
||||
void cancel(operation op = operation::all)
|
||||
{
|
||||
@@ -748,37 +810,69 @@ public:
|
||||
|
||||
auto run_is_canceled() const noexcept { return mpx_.get_cancel_run_state(); }
|
||||
|
||||
/// Returns true if the connection was canceled.
|
||||
/// Returns true if the connection will try to reconnect if an error is encountered.
|
||||
bool will_reconnect() const noexcept
|
||||
{
|
||||
return cfg_.reconnect_wait_interval != std::chrono::seconds::zero();
|
||||
}
|
||||
|
||||
/// Returns the ssl context.
|
||||
/**
|
||||
* @brief (Deprecated) Returns the ssl context.
|
||||
*
|
||||
* `ssl::context` has no const methods, so this function should not be called.
|
||||
* Any TLS configuration should be set up by passing an `ssl::context`
|
||||
* to the connection's constructor.
|
||||
*
|
||||
* @returns The SSL context.
|
||||
*/
|
||||
BOOST_DEPRECATED(
|
||||
"ssl::context has no const methods, so this function should not be called. Set up any "
|
||||
"required TLS configuration before passing the ssl::context to the connection's constructor.")
|
||||
auto const& get_ssl_context() const noexcept { return stream_.get_ssl_context(); }
|
||||
asio::ssl::context const& get_ssl_context() const noexcept { return stream_.get_ssl_context(); }
|
||||
|
||||
/// Resets the underlying stream.
|
||||
/**
|
||||
* @brief (Deprecated) Resets the underlying stream.
|
||||
*
|
||||
* This function is no longer necessary and is currently a no-op.
|
||||
*/
|
||||
BOOST_DEPRECATED(
|
||||
"This function is no longer necessary and is currently a no-op. connection resets the stream "
|
||||
"internally as required. This function will be removed in subsequent releases")
|
||||
void reset_stream() { }
|
||||
|
||||
/// Returns a reference to the next layer.
|
||||
/**
|
||||
* @brief (Deprecated) Returns a reference to the next layer.
|
||||
*
|
||||
* This function returns a dummy object for connections using UNIX domain sockets.
|
||||
*
|
||||
* @par Deprecated
|
||||
* Accessing the underlying stream is deprecated and will be removed in the next release.
|
||||
* Use the other member functions to interact with the connection.
|
||||
*
|
||||
* @returns A reference to the underlying SSL stream object.
|
||||
*/
|
||||
BOOST_DEPRECATED(
|
||||
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
|
||||
"the other member functions to interact with the connection.")
|
||||
auto& next_layer() noexcept { return stream_.next_layer(); }
|
||||
|
||||
/// Returns a const reference to the next layer.
|
||||
/**
|
||||
* @brief (Deprecated) Returns a reference to the next layer.
|
||||
*
|
||||
* This function returns a dummy object for connections using UNIX domain sockets.
|
||||
*
|
||||
* @par Deprecated
|
||||
* Accessing the underlying stream is deprecated and will be removed in the next release.
|
||||
* Use the other member functions to interact with the connection.
|
||||
*
|
||||
* @returns A reference to the underlying SSL stream object.
|
||||
*/
|
||||
BOOST_DEPRECATED(
|
||||
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
|
||||
"the other member functions to interact with the connection.")
|
||||
auto const& next_layer() const noexcept { return stream_.next_layer(); }
|
||||
|
||||
/// Sets the response object of `async_receive` operations.
|
||||
/// Sets the response object of @ref async_receive operations.
|
||||
template <class Response>
|
||||
void set_receive_response(Response& response)
|
||||
{
|
||||
@@ -860,23 +954,22 @@ private:
|
||||
detail::connection_logger logger_;
|
||||
};
|
||||
|
||||
/** \brief A basic_connection that type erases the executor.
|
||||
* \ingroup high-level-api
|
||||
/** @brief A basic_connection that type erases the executor.
|
||||
*
|
||||
* This connection type uses the asio::any_io_executor and
|
||||
* asio::any_completion_token to reduce compilation times.
|
||||
* This connection type uses `asio::any_io_executor` and
|
||||
* `asio::any_completion_token` to reduce compilation times.
|
||||
*
|
||||
* For documentation of each member function see
|
||||
* `boost::redis::basic_connection`.
|
||||
* @ref boost::redis::basic_connection.
|
||||
*/
|
||||
class connection {
|
||||
public:
|
||||
/// Executor type.
|
||||
using executor_type = asio::any_io_executor;
|
||||
|
||||
/** @brief Constructor
|
||||
/** @brief Constructor from an executor.
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param ex Executor used to create all internal I/O objects.
|
||||
* @param ctx SSL context.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
@@ -887,9 +980,9 @@ public:
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
logger lgr = {});
|
||||
|
||||
/** @brief Constructor
|
||||
/** @brief Constructor from an executor and a logger.
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param ex Executor used to create all internal I/O objects.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
@@ -903,7 +996,15 @@ public:
|
||||
std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Constructs from a context.
|
||||
/**
|
||||
* @brief Constructor from an `io_context`.
|
||||
*
|
||||
* @param ioc I/O context used to create all internal I/O objects.
|
||||
* @param ctx SSL context.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
*/
|
||||
explicit connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
@@ -911,7 +1012,14 @@ public:
|
||||
: connection(ioc.get_executor(), std::move(ctx), std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Constructs from a context.
|
||||
/**
|
||||
* @brief Constructor from an `io_context` and a logger.
|
||||
*
|
||||
* @param ioc I/O context used to create all internal I/O objects.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
*/
|
||||
connection(asio::io_context& ioc, logger lgr)
|
||||
: connection(
|
||||
ioc.get_executor(),
|
||||
@@ -923,7 +1031,25 @@ public:
|
||||
executor_type get_executor() noexcept { return impl_.get_executor(); }
|
||||
|
||||
/**
|
||||
* @brief Calls `boost::redis::basic_connection::async_run`.
|
||||
* @brief Calls @ref boost::redis::basic_connection::async_run.
|
||||
*
|
||||
* @param cfg Configuration parameters.
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <class CompletionToken = asio::deferred_t>
|
||||
auto async_run(config const& cfg, CompletionToken&& token = {})
|
||||
{
|
||||
return asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
|
||||
[](auto handler, connection* self, config const* cfg) {
|
||||
self->async_run_impl(*cfg, std::move(handler));
|
||||
},
|
||||
token,
|
||||
this,
|
||||
&cfg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief (Deprecated) Calls @ref boost::redis::basic_connection::async_run.
|
||||
*
|
||||
* This function accepts an extra logger parameter. The passed logger
|
||||
* will be used by the connection, overwriting any logger passed to the connection's
|
||||
@@ -933,6 +1059,10 @@ public:
|
||||
* The logger should be passed to the connection's constructor instead of using this
|
||||
* function. Use the overload without a logger parameter, instead. This function is
|
||||
* deprecated and will be removed in subsequent releases.
|
||||
*
|
||||
* @param cfg Configuration parameters.
|
||||
* @param l Logger.
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <class CompletionToken = asio::deferred_t>
|
||||
BOOST_DEPRECATED(
|
||||
@@ -951,37 +1081,36 @@ public:
|
||||
std::move(l));
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_run`.
|
||||
template <class CompletionToken = asio::deferred_t>
|
||||
auto async_run(config const& cfg, CompletionToken&& token = {})
|
||||
{
|
||||
return asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
|
||||
[](auto handler, connection* self, config const* cfg) {
|
||||
self->async_run_impl(*cfg, std::move(handler));
|
||||
},
|
||||
token,
|
||||
this,
|
||||
&cfg);
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_receive`.
|
||||
/// @copydoc basic_connection::async_receive
|
||||
template <class CompletionToken = asio::deferred_t>
|
||||
auto async_receive(CompletionToken&& token = {})
|
||||
{
|
||||
return impl_.async_receive(std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::receive`.
|
||||
/// @copydoc basic_connection::receive
|
||||
std::size_t receive(system::error_code& ec) { return impl_.receive(ec); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_exec`.
|
||||
/**
|
||||
* @brief Calls @ref boost::redis::basic_connection::async_exec.
|
||||
*
|
||||
* @param req The request to be executed.
|
||||
* @param resp The response object to parse data into.
|
||||
* @param token Completion token.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_exec`.
|
||||
/**
|
||||
* @brief Calls @ref boost::redis::basic_connection::async_exec.
|
||||
*
|
||||
* @param req The request to be executed.
|
||||
* @param adapter An adapter object referencing a response to place data into.
|
||||
* @param token Completion token.
|
||||
*/
|
||||
template <class CompletionToken = asio::deferred_t>
|
||||
auto async_exec(request const& req, any_adapter adapter, CompletionToken&& token = {})
|
||||
{
|
||||
@@ -995,45 +1124,54 @@ public:
|
||||
std::move(adapter));
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::cancel`.
|
||||
/// @copydoc basic_connection::cancel
|
||||
void cancel(operation op = operation::all);
|
||||
|
||||
/// Calls `boost::redis::basic_connection::will_reconnect`.
|
||||
/// @copydoc basic_connection::will_reconnect
|
||||
bool will_reconnect() const noexcept { return impl_.will_reconnect(); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::next_layer`.
|
||||
/// (Deprecated) Calls @ref boost::redis::basic_connection::next_layer.
|
||||
BOOST_DEPRECATED(
|
||||
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
|
||||
"the other member functions to interact with the connection.")
|
||||
auto& next_layer() noexcept { return impl_.stream_.next_layer(); }
|
||||
asio::ssl::stream<asio::ip::tcp::socket>& next_layer() noexcept
|
||||
{
|
||||
return impl_.stream_.next_layer();
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::next_layer`.
|
||||
/// (Deprecated) Calls @ref boost::redis::basic_connection::next_layer.
|
||||
BOOST_DEPRECATED(
|
||||
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
|
||||
"the other member functions to interact with the connection.")
|
||||
auto const& next_layer() const noexcept { return impl_.stream_.next_layer(); }
|
||||
asio::ssl::stream<asio::ip::tcp::socket> const& next_layer() const noexcept
|
||||
{
|
||||
return impl_.stream_.next_layer();
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::reset_stream`.
|
||||
/// @copydoc basic_connection::reset_stream
|
||||
BOOST_DEPRECATED(
|
||||
"This function is no longer necessary and is currently a no-op. connection resets the stream "
|
||||
"internally as required. This function will be removed in subsequent releases")
|
||||
void reset_stream() { }
|
||||
|
||||
/// Sets the response object of `async_receive` operations.
|
||||
/// @copydoc basic_connection::set_receive_response
|
||||
template <class Response>
|
||||
void set_receive_response(Response& response)
|
||||
{
|
||||
impl_.set_receive_response(response);
|
||||
}
|
||||
|
||||
/// Returns connection usage information.
|
||||
/// @copydoc basic_connection::get_usage
|
||||
usage get_usage() const noexcept { return impl_.get_usage(); }
|
||||
|
||||
/// Returns the ssl context.
|
||||
/// @copydoc basic_connection::get_ssl_context
|
||||
BOOST_DEPRECATED(
|
||||
"ssl::context has no const methods, so this function should not be called. Set up any "
|
||||
"required TLS configuration before passing the ssl::context to the connection's constructor.")
|
||||
auto const& get_ssl_context() const noexcept { return impl_.stream_.get_ssl_context(); }
|
||||
asio::ssl::context const& get_ssl_context() const noexcept
|
||||
{
|
||||
return impl_.stream_.get_ssl_context();
|
||||
}
|
||||
|
||||
private:
|
||||
void async_run_impl(
|
||||
|
||||
@@ -13,11 +13,10 @@
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
/** \brief Writes a request synchronously.
|
||||
* \ingroup low-level-api
|
||||
/** @brief Writes a request synchronously.
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
* @param stream Stream to write the request to.
|
||||
* @param req Request to write.
|
||||
*/
|
||||
template <class SyncWriteStream>
|
||||
auto write(SyncWriteStream& stream, request const& req)
|
||||
@@ -31,12 +30,11 @@ auto write(SyncWriteStream& stream, request const& req, system::error_code& ec)
|
||||
return asio::write(stream, asio::buffer(req.payload()), ec);
|
||||
}
|
||||
|
||||
/** \brief Writes a request asynchronously.
|
||||
* \ingroup low-level-api
|
||||
/** @brief Writes a request asynchronously.
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
* \param token Asio completion token.
|
||||
* @param stream Stream to write the request to.
|
||||
* @param req Request to write.
|
||||
* @param token Asio completion token.
|
||||
*/
|
||||
template <
|
||||
class AsyncWriteStream,
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
/** \brief Generic errors.
|
||||
* \ingroup high-level-api
|
||||
*/
|
||||
/// Generic errors.
|
||||
enum class error
|
||||
{
|
||||
/// Invalid RESP3 type.
|
||||
@@ -92,10 +90,10 @@ enum class error
|
||||
unix_sockets_ssl_unsupported,
|
||||
};
|
||||
|
||||
/** \internal
|
||||
* \brief Creates a error_code object from an error.
|
||||
* \param e Error code.
|
||||
* \ingroup any
|
||||
/**
|
||||
* @brief Creates a error_code object from an error.
|
||||
*
|
||||
* @param e Error code.
|
||||
*/
|
||||
auto make_error_code(error e) -> system::error_code;
|
||||
|
||||
|
||||
@@ -16,26 +16,24 @@
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Type used to ignore responses.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* For example
|
||||
* For example:
|
||||
*
|
||||
* @code
|
||||
* response<ignore_t, std::string, ignore_t> resp;
|
||||
* @endcode
|
||||
*
|
||||
* will ignore the first and third responses. RESP3 errors won't be
|
||||
* This will ignore the first and third responses. RESP3 errors won't be
|
||||
* ignore but will cause `async_exec` to complete with an error.
|
||||
*/
|
||||
using ignore_t = std::decay_t<decltype(std::ignore)>;
|
||||
|
||||
/** @brief Global ignore object.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* Can be used to ignore responses to a request
|
||||
* Can be used to ignore responses to a request. For example:
|
||||
*
|
||||
* @code
|
||||
* conn->async_exec(req, ignore, ...);
|
||||
* co_await conn.async_exec(req, ignore);
|
||||
* @endcode
|
||||
*
|
||||
* RESP3 errors won't be ignore but will cause `async_exec` to
|
||||
|
||||
@@ -12,15 +12,12 @@
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Defines logging configuration
|
||||
* @ingroup high-level-api
|
||||
/** @brief Defines logging configuration.
|
||||
*
|
||||
* See the member descriptions for more info.
|
||||
*/
|
||||
struct logger {
|
||||
/** @brief Syslog-like log levels
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
/// Syslog-like log levels.
|
||||
enum class level
|
||||
{
|
||||
/// Disabled
|
||||
@@ -54,7 +51,7 @@ struct logger {
|
||||
/** @brief Constructor from a level.
|
||||
*
|
||||
* Constructs a logger with the specified level
|
||||
* and a logging function that prints messages to stderr.
|
||||
* and a logging function that prints messages to `stderr`.
|
||||
*
|
||||
* @param l The value to set @ref lvl to.
|
||||
*
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Connection operations that can be cancelled.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* The operations listed below can be passed to the
|
||||
* `boost::redis::connection::cancel` member function.
|
||||
* @ref boost::redis::connection::cancel member function.
|
||||
*/
|
||||
enum class operation
|
||||
{
|
||||
|
||||
@@ -23,12 +23,13 @@ namespace detail {
|
||||
auto has_response(std::string_view cmd) -> bool;
|
||||
}
|
||||
|
||||
/** \brief Creates Redis requests.
|
||||
* \ingroup high-level-api
|
||||
/** @brief Represents a Redis request.
|
||||
*
|
||||
* 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
|
||||
* referred to in the redis documentation as a pipeline. See
|
||||
* <a href="https://redis.io/topics/pipelining"></a> for more info.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* @code
|
||||
* request r;
|
||||
@@ -39,58 +40,56 @@ auto has_response(std::string_view cmd) -> bool;
|
||||
* r.push("QUIT");
|
||||
* @endcode
|
||||
*
|
||||
* \remarks
|
||||
*
|
||||
* Uses a std::string for internal storage.
|
||||
* Uses a `std::string` for internal storage.
|
||||
*/
|
||||
class request {
|
||||
public:
|
||||
/// Request configuration options.
|
||||
struct config {
|
||||
/** \brief If `true` calls to `connection::async_exec` will
|
||||
/** @brief If `true`, calls to @ref basic_connection::async_exec will
|
||||
* complete with error if the connection is lost while the
|
||||
* request hasn't been sent yet.
|
||||
*/
|
||||
bool cancel_on_connection_lost = true;
|
||||
|
||||
/** \brief If `true` `connection::async_exec` will complete with
|
||||
* `boost::redis::error::not_connected` if the call happens
|
||||
/** @brief If `true`, @ref basic_connection::async_exec will complete with
|
||||
* @ref boost::redis::error::not_connected if the call happens
|
||||
* before the connection with Redis was established.
|
||||
*/
|
||||
bool cancel_if_not_connected = false;
|
||||
|
||||
/** \brief If `false` `connection::async_exec` will not
|
||||
/** @brief If `false`, @ref basic_connection::async_exec will not
|
||||
* automatically cancel this request if the connection is lost.
|
||||
* Affects only requests that have been written to the socket
|
||||
* but remained unresponded when
|
||||
* `boost::redis::connection::async_run` completed.
|
||||
* but have not been responded when
|
||||
* @ref boost::redis::connection::async_run completes.
|
||||
*/
|
||||
bool cancel_if_unresponded = true;
|
||||
|
||||
/** \brief If this request has a `HELLO` command and this flag
|
||||
* is `true`, the `boost::redis::connection` will move it to the
|
||||
/** @brief If this request has a `HELLO` command and this flag
|
||||
* is `true`, it will be moved to the
|
||||
* front of the queue of awaiting requests. This makes it
|
||||
* possible to send `HELLO` and authenticate before other
|
||||
* possible to send `HELLO` commands and authenticate before other
|
||||
* commands are sent.
|
||||
*/
|
||||
bool hello_with_priority = true;
|
||||
};
|
||||
|
||||
/** \brief Constructor
|
||||
/** @brief Constructor
|
||||
*
|
||||
* \param cfg Configuration options.
|
||||
* @param cfg Configuration options.
|
||||
*/
|
||||
explicit request(config cfg = config{true, false, true, true})
|
||||
: cfg_{cfg}
|
||||
{ }
|
||||
|
||||
//// Returns the number of responses expected for this request.
|
||||
/// Returns the number of responses expected for this request.
|
||||
[[nodiscard]] auto get_expected_responses() const noexcept -> std::size_t
|
||||
{
|
||||
return expected_responses_;
|
||||
};
|
||||
|
||||
//// Returns the number of commands contained in this request.
|
||||
/// Returns the number of commands contained in this request.
|
||||
[[nodiscard]] auto get_commands() const noexcept -> std::size_t { return commands_; };
|
||||
|
||||
[[nodiscard]] auto payload() const noexcept -> std::string_view { return payload_; }
|
||||
@@ -109,39 +108,43 @@ public:
|
||||
has_hello_priority_ = false;
|
||||
}
|
||||
|
||||
/// Calls std::string::reserve on the internal storage.
|
||||
/// Calls `std::string::reserve` on the internal storage.
|
||||
void reserve(std::size_t new_cap = 0) { payload_.reserve(new_cap); }
|
||||
|
||||
/// Returns a const reference to the config object.
|
||||
[[nodiscard]] auto get_config() const noexcept -> auto const& { return cfg_; }
|
||||
/// Returns a reference to the config object.
|
||||
[[nodiscard]] auto get_config() const noexcept -> config const& { return cfg_; }
|
||||
|
||||
/// Returns a reference to the config object.
|
||||
[[nodiscard]] auto get_config() noexcept -> auto& { return cfg_; }
|
||||
[[nodiscard]] auto get_config() noexcept -> config& { return cfg_; }
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
* For example:
|
||||
*
|
||||
* \code
|
||||
* @code
|
||||
* request req;
|
||||
* req.push("SET", "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
* @endcode
|
||||
*
|
||||
* will add the `set` command with value "some string" and an
|
||||
* This will add a `SET` command with value `"some string"` and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The command e.g redis or sentinel command.
|
||||
* \param args Command arguments.
|
||||
* \tparam Ts Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument. This function must be made available over ADL and must have the following signature
|
||||
* Command arguments should either be convertible to `std::string_view`
|
||||
* or support the `boost_redis_to_bulk` function.
|
||||
* This function is a customization point that must be made available
|
||||
* using ADL and must have the following signature:
|
||||
*
|
||||
* @code
|
||||
* void boost_redis_to_bulk(std::string& to, T const& t);
|
||||
* {
|
||||
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
*
|
||||
* See cpp20_serialization.cpp
|
||||
*
|
||||
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
|
||||
* @param args Command arguments. Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument.
|
||||
* @tparam Ts Types of the command arguments.
|
||||
*
|
||||
*/
|
||||
template <class... Ts>
|
||||
void push(std::string_view cmd, Ts const&... args)
|
||||
@@ -157,7 +160,7 @@ public:
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a key and have a
|
||||
* dynamic range of arguments. For example
|
||||
* dynamic range of arguments. For example:
|
||||
*
|
||||
* @code
|
||||
* std::map<std::string, std::string> map
|
||||
@@ -167,28 +170,31 @@ public:
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range("HSET", "key", std::cbegin(map), std::cend(map));
|
||||
* req.push_range("HSET", "key", map.cbegin(), map.cend());
|
||||
* @endcode
|
||||
*
|
||||
* \param cmd The command e.g. Redis or Sentinel command.
|
||||
* \param key The command key.
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
* \tparam Ts Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument. This function must be made available over ADL and must have the following signature
|
||||
* Command arguments should either be convertible to `std::string_view`
|
||||
* or support the `boost_redis_to_bulk` function.
|
||||
* This function is a customization point that must be made available
|
||||
* using ADL and must have the following signature:
|
||||
*
|
||||
* @code
|
||||
* void boost_redis_to_bulk(std::string& to, T const& t);
|
||||
* {
|
||||
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
|
||||
* @param key The command key. It will be added as the first argument to the command.
|
||||
* @param begin Iterator to the begin of the range.
|
||||
* @param end Iterator to the end of the range.
|
||||
* @tparam ForwardIterator A forward iterator with an element type that is convertible to `std::string_view`
|
||||
* or supports `boost_redis_to_bulk`.
|
||||
*
|
||||
* See cpp20_serialization.cpp
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range(
|
||||
std::string_view const& cmd,
|
||||
std::string_view const& key,
|
||||
std::string_view cmd,
|
||||
std::string_view key,
|
||||
ForwardIterator begin,
|
||||
ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type* = nullptr)
|
||||
@@ -213,33 +219,36 @@ public:
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a dynamic number
|
||||
* of arguments and don't have a key. For example
|
||||
* of arguments and don't have a key. For example:
|
||||
*
|
||||
* \code
|
||||
* @code
|
||||
* std::set<std::string> channels
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
* { "channel1" , "channel2" , "channel3" };
|
||||
*
|
||||
* request req;
|
||||
* req.push("SUBSCRIBE", std::cbegin(channels), std::cend(channels));
|
||||
* \endcode
|
||||
* @endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
* \tparam ForwardIterator If the value type is not a std::string it will be converted to a string by calling `boost_redis_to_bulk`. This function must be made available over ADL and must have the following signature
|
||||
* Command arguments should either be convertible to `std::string_view`
|
||||
* or support the `boost_redis_to_bulk` function.
|
||||
* This function is a customization point that must be made available
|
||||
* using ADL and must have the following signature:
|
||||
*
|
||||
* @code
|
||||
* void boost_redis_to_bulk(std::string& to, T const& t);
|
||||
* {
|
||||
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
|
||||
* @param begin Iterator to the begin of the range.
|
||||
* @param end Iterator to the end of the range.
|
||||
* @tparam ForwardIterator A forward iterator with an element type that is convertible to `std::string_view`
|
||||
* or supports `boost_redis_to_bulk`.
|
||||
*
|
||||
* See cpp20_serialization.cpp
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range(
|
||||
std::string_view const& cmd,
|
||||
std::string_view cmd,
|
||||
ForwardIterator begin,
|
||||
ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type* = nullptr)
|
||||
@@ -265,15 +274,17 @@ public:
|
||||
* Equivalent to the overload taking a range of begin and end
|
||||
* iterators.
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param key Redis key.
|
||||
* \param range Range to send e.g. `std::map`.
|
||||
* \tparam A type that can be passed to `std::cbegin()` and `std::cend()`.
|
||||
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
|
||||
* @param key The command key. It will be added as the first argument to the command.
|
||||
* @param range Range containing the command arguments.
|
||||
* @tparam Range A type that can be passed to `std::begin()` and `std::end()` to obtain
|
||||
* iterators. The range elements should be convertible to `std::string_view`
|
||||
* or support `boost_redis_to_bulk`.
|
||||
*/
|
||||
template <class Range>
|
||||
void push_range(
|
||||
std::string_view const& cmd,
|
||||
std::string_view const& key,
|
||||
std::string_view cmd,
|
||||
std::string_view key,
|
||||
Range const& range,
|
||||
decltype(std::begin(range))* = nullptr)
|
||||
{
|
||||
@@ -287,9 +298,11 @@ public:
|
||||
* Equivalent to the overload taking a range of begin and end
|
||||
* iterators.
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param range Range to send e.g. `std::map`.
|
||||
* \tparam A type that can be passed to `std::cbegin()` and `std::cend()`.
|
||||
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
|
||||
* @param range Range containing the command arguments.
|
||||
* @tparam Range A type that can be passed to `std::begin()` and `std::end()` to obtain
|
||||
* iterators. The range elements should be convertible to `std::string_view`
|
||||
* or support `boost_redis_to_bulk`.
|
||||
*/
|
||||
template <class Range>
|
||||
void push_range(
|
||||
|
||||
@@ -11,16 +11,13 @@
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
/** \brief A node in the response tree.
|
||||
* \ingroup high-level-api
|
||||
/** @brief A node in the response tree.
|
||||
*
|
||||
* RESP3 can contain recursive data structures: A map of sets of
|
||||
* vector of etc. As it is parsed each element is passed to user
|
||||
* callbacks (push parser). The signature of this
|
||||
* callback is `f(resp3::node<std::string_view)`. This class is called a node
|
||||
* RESP3 can contain recursive data structures, like a map of sets of
|
||||
* vectors. This class is called a node
|
||||
* because it can be seen as the element of the response tree. It
|
||||
* is a template so that users can use it with owing strings e.g.
|
||||
* `std::string` or `boost::static_string` etc.
|
||||
* is a template so that users can use it with any string type, like
|
||||
* `std::string` or `boost::static_string`.
|
||||
*
|
||||
* @tparam String A `std::string`-like type.
|
||||
*/
|
||||
@@ -56,14 +53,10 @@ auto operator==(basic_node<String> const& a, basic_node<String> const& b)
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
/** @brief A node in the response tree that owns its data
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
/// A node in the response tree that owns its data.
|
||||
using node = basic_node<std::string>;
|
||||
|
||||
/** @brief A node view in the response tree
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
/// A node in the response tree that does not own its data.
|
||||
using node_view = basic_node<std::string_view>;
|
||||
|
||||
} // namespace boost::redis::resp3
|
||||
|
||||
@@ -37,8 +37,6 @@ namespace boost::redis::resp3 {
|
||||
*
|
||||
* @param payload Storage on which data will be copied into.
|
||||
* @param data Data that will be serialized and stored in `payload`.
|
||||
*
|
||||
* See more in @ref serialization.
|
||||
*/
|
||||
void boost_redis_to_bulk(std::string& payload, std::string_view data);
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
/** \brief RESP3 data types.
|
||||
\ingroup high-level-api
|
||||
/** @brief RESP3 data types.
|
||||
|
||||
The RESP3 specification can be found at https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md.
|
||||
The RESP3 specification can be found at
|
||||
<a href="https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md"></a>
|
||||
*/
|
||||
enum class type
|
||||
{ /// Aggregate
|
||||
@@ -59,20 +59,26 @@ enum class type
|
||||
invalid
|
||||
};
|
||||
|
||||
/** \brief Converts the data type to a string.
|
||||
* \ingroup high-level-api
|
||||
* \param t RESP3 type.
|
||||
/** @brief Converts the data type to a string.
|
||||
*
|
||||
* @relates type
|
||||
* @param t The type to convert.
|
||||
*/
|
||||
auto to_string(type t) noexcept -> char const*;
|
||||
|
||||
/** \brief Writes the type to the output stream.
|
||||
* \ingroup high-level-api
|
||||
* \param os Output stream.
|
||||
* \param t RESP3 type.
|
||||
/** @brief Writes the type to the output stream.
|
||||
*
|
||||
* @relates type
|
||||
* @param os Output stream.
|
||||
* @param t The type to stream.
|
||||
*/
|
||||
auto operator<<(std::ostream& os, type t) -> std::ostream&;
|
||||
|
||||
/* Checks whether the data type is an aggregate.
|
||||
/** @brief Checks whether the data type is an aggregate.
|
||||
*
|
||||
* @relates type
|
||||
* @param t The type to check.
|
||||
* @returns True if the given type is an aggregate.
|
||||
*/
|
||||
constexpr auto is_aggregate(type t) noexcept -> bool
|
||||
{
|
||||
|
||||
@@ -18,14 +18,11 @@
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Response with compile-time size.
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
/// Response with compile-time size.
|
||||
template <class... Ts>
|
||||
using response = std::tuple<adapter::result<Ts>...>;
|
||||
|
||||
/** @brief A generic response to a request
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* This response type can store any type of RESP3 data structure. It
|
||||
* contains the
|
||||
@@ -47,7 +44,7 @@ using generic_response = adapter::result<std::vector<resp3::node>>;
|
||||
* req.push("PING", "three");
|
||||
*
|
||||
* generic_response resp;
|
||||
* co_await conn->async_exec(req, resp, asio::deferred);
|
||||
* co_await conn.async_exec(req, resp);
|
||||
*
|
||||
* std::cout << "PING: " << resp.value().front().value << std::endl;
|
||||
* consume_one(resp);
|
||||
@@ -56,7 +53,7 @@ using generic_response = adapter::result<std::vector<resp3::node>>;
|
||||
* std::cout << "PING: " << resp.value().front().value << std::endl;
|
||||
* @endcode
|
||||
*
|
||||
* is
|
||||
* Is:
|
||||
*
|
||||
* @code
|
||||
* PING: one
|
||||
@@ -69,10 +66,17 @@ using generic_response = adapter::result<std::vector<resp3::node>>;
|
||||
* introduced mainly to deal with buffers server pushes as shown in
|
||||
* the cpp20_subscriber.cpp example. In the future queue-like
|
||||
* responses might be introduced to consume in O(1) operations.
|
||||
*
|
||||
* @param r The response to modify.
|
||||
* @param ec Will be populated in case of error.
|
||||
*/
|
||||
void consume_one(generic_response& r, system::error_code& ec);
|
||||
|
||||
/// Throwing overload of `consume_one`.
|
||||
/**
|
||||
* @brief Throwing overload of `consume_one`.
|
||||
*
|
||||
* @param r The response to modify.
|
||||
*/
|
||||
void consume_one(generic_response& r);
|
||||
|
||||
} // namespace boost::redis
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
#ifndef BOOST_REDIS_USAGE_HPP
|
||||
#define BOOST_REDIS_USAGE_HPP
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Connection usage information.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @note: To simplify the implementation, the commands_sent and
|
||||
* bytes_sent in the struct below are computed just before writing to
|
||||
* the socket, which means on error they might not represent exactly
|
||||
* @note To simplify the implementation, @ref commands_sent and
|
||||
* @ref bytes_sent are computed just before writing to
|
||||
* the socket. On error, they might not represent exactly
|
||||
* what has been received by the Redis server.
|
||||
*/
|
||||
struct usage {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<body>
|
||||
Automatic redirection failed, please go to
|
||||
<a href="./doc/html/index.html">./doc/html/index.html</a>
|
||||
<a href="./doc/html/redis/index.html">./doc/html/redis/index.html</a>
|
||||
<hr>
|
||||
<tt>
|
||||
Boost.Redis<br>
|
||||
|
||||
@@ -109,7 +109,7 @@ def _setup_boost(
|
||||
copytree(
|
||||
str(source_dir),
|
||||
str(lib_dir),
|
||||
ignore=ignore_patterns('__build*__', '.git')
|
||||
ignore=ignore_patterns('__build*__')
|
||||
)
|
||||
|
||||
# Install Boost dependencies
|
||||
|
||||
Reference in New Issue
Block a user