{"id":20756482,"url":"https://github.com/basiliscos/cpp-bredis","last_synced_at":"2025-08-22T05:32:21.492Z","repository":{"id":65039799,"uuid":"87557956","full_name":"basiliscos/cpp-bredis","owner":"basiliscos","description":"Boost::ASIO low-level redis client (connector)","archived":false,"fork":false,"pushed_at":"2022-12-22T13:19:21.000Z","size":415,"stargazers_count":146,"open_issues_count":8,"forks_count":36,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-12-13T03:41:56.242Z","etag":null,"topics":["asio","boost","cpp11","header-only","network","no-dependencies","redis","redis-client"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/basiliscos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-07T15:00:18.000Z","updated_at":"2024-11-21T13:02:27.000Z","dependencies_parsed_at":"2022-12-25T19:15:08.817Z","dependency_job_id":null,"html_url":"https://github.com/basiliscos/cpp-bredis","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basiliscos%2Fcpp-bredis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basiliscos%2Fcpp-bredis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basiliscos%2Fcpp-bredis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basiliscos%2Fcpp-bredis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/basiliscos","download_url":"https://codeload.github.com/basiliscos/cpp-bredis/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230561014,"owners_count":18245324,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asio","boost","cpp11","header-only","network","no-dependencies","redis","redis-client"],"created_at":"2024-11-17T09:32:41.634Z","updated_at":"2024-12-20T09:07:26.058Z","avatar_url":"https://github.com/basiliscos.png","language":"C++","readme":"# bredis\n\nBoost::ASIO low-level redis client (connector),\n [github](https://github.com/basiliscos/cpp-bredis)\n [gitee](https://gitee.com/basiliscos/cpp-bredis)\n\n\n[![Travis](https://img.shields.io/travis/basiliscos/cpp-bredis.svg)](https://travis-ci.org/basiliscos/cpp-bredis)\n[![Build status](https://ci.appveyor.com/api/projects/status/a302juc7hrcdhhoc?svg=true)](https://ci.appveyor.com/project/basiliscos/cpp-bredis)\n[![license](https://img.shields.io/github/license/basiliscos/cpp-bredis.svg)](https://github.com/basiliscos/cpp-bredis/blob/master/LICENSE)\n[![codecov](https://codecov.io/gh/basiliscos/cpp-bredis/badge.svg)](https://codecov.io/gh/basiliscos/cpp-bredis)\n\n## Features\n\n- header only\n- zero-copy (currently only for received replies from Redis)\n- low-level controls (i.e. you can cancel, or do manual DNS-resolving before a connection)\n- unix domain sockets support\n- works on linux (clang, gcc) and windows (msvc)\n- synchronous \u0026 asynchronous interface\n- inspired by [beast](https://github.com/vinniefalco/Beast)\n- requirements: boost `v1.77` minimum\n\n## Changelog\n\n### 0.12\n- [feature, breakging] modernize API to use completion token instead of\nusing completion handler, which makes it possible to use `bredis` with\ncoroutines. Thanks to [Usevalad Sauta](https://github.com/VsevolodSauta).\nSuccessfully tested with boost `v1.77`, lower versions might not work\n\n### 0.11\n- [feature, possible breakage] add `BOOST_ASIO_NO_DEPRECATED` definition\nfor better support boost `v1.74` and modernize boost API usage\n\n### 0.10\n- [bugfix] avoid access to protected destructor (c++17 compatibility)\n\n### 0.09\n- [bugfix] critical bug in protcol serialization on empty values\n\n### 0.08\n- relaxed c++ compiler requirements: c++11 can be used instead of c++14\n\n### 0.07\n- minor parsing speed improvements (upto 10% in synthetic tests)\n- fix compilation issues on boost::asio 1.70\n- make it possible to use `DynamicBuffer_v2` (dynamic_string_buffer, dynamic_vector_buffer)\n    from boost::asio 1.70 in addition to `streambuf`. `DynamicBuffer_v1` was actually never\n    supported by `bredis`\n- [API breakage] `boos::asio::use_future` cannot be used with `bredis` and `boost::asio`\n    prior `v1.70` (see [issue](https://github.com/boostorg/asio/issues/226)). If you need\n    `use_future` then either upgrade boost::asio or use previous `bredis` version.\n\n### 0.06\n- the `parsing_policy::drop_result` was documented and made applicable in client code\n- updated preformance results\n- fixed compliation warnings (`-Wall -Wextra -pedantic -Werror`)\n- added shortcut header `include/bredis.hpp`\n- added redis-streams usage example\n- added multi-thread example\n\n### 0.05\n- fixed level 4 warning in MSVC\n- fixed compilation issues on archlinux\n- improved documentation (numerous typos etc.)\n\n### 0.04\n - [bugfix] removed unneeded `tx_buff.commit()` on `async_write` which corrupted buffer\n\n### 0.03\n- improved protocol parser (no memory allocations during input stream validity check)\n- more detailed information in `protocol_error_t`\n- added async `incr` speed test example\n- [small API breakage] `positive_parse_result_t` was enriched with parcing policy;\nnow instead of `positive_parse_result_t\u003cIterator\u003e` should be written:\n\n```cpp\nusing Policy = r::parsing_policy::keep_result;\nusing result_t = r::parse_result_mapper_t\u003cIterator, Policy\u003e;\n```\n- [small API breakage] `protocol_error_t` instead of `std::string what` member\nnow contains `boost::system::error_code code`\n\n### 0.02\n- added windows support\n- added coroutines \u0026 futures support\n- generalised (templated) buffer support\n- changed return type: instead of result of parsing just result markers are returned, extraction of result can be done as separate step\n- dropped queing support (queuing policy should be implemented at more higher levels)\n- dropped subscription support (can be implemented at higher levels)\n- dropped internal buffers (can be implemented at higher levels)\n- dropped explicit cancellation (socket reference can be passed to connector, and cancellation\ncan be done on the socket object outside of the connector)\n\n### 0.01\n- initial version\n\n## Performance\n\nResults achieved with `examples/speed_test_async_multi.cpp` for 1 thread, Intel Core i7-8550U, void-linux, gcc 8.3.0\n\n | bredis (commands/s) | bredis(*) (commands/s) | redox (commands/s)|\n |---------------------|------------------------|-------------------|\n |      1.80845e+06    |      2.503e+06         |    0.999375+06    |\n\nThese results are not completely fair, because of the usage of different semantics in the\nAPIs; however they are still interesting, as they are using different\nunderlying event libraries ([Boost::ASIO](http://www.boost.org/doc/libs/release/libs/asio/) vs [libev](http://software.schmorp.de/pkg/libev.html)) as well as redis protocol\nparsing libraries (written from scratch vs [hiredis](https://github.com/redis/hiredis))\n\n`(*)` bredis with drop_result policy, i.e. replies from redis server are\nscanned only for formal correctness and never delivered to the caller.\n\n\n## Work with the result\n\nThe general idea is that the result of trying to parse a redis reply can be either: not enough data, protocol error (in an extreme case) or some positive parse result. The last one is just **markers** of the result, which is actually stored in the *receive buffer* (i.e. outside of markers, and outside of the bredis-connection).\n\nThe further work with markers depends on your needs: it is possible to either **scan** the result for the expected results (e.g. for a `PONG` reply on a `PING` command, or for `OK`/`QUEUED` replies on `MULTI`/`EXEC` commands) or to **extract** the results (the common redis types: `nil`, `string`, `error`, `int` or a (recursive) array of them).\n\nWhen the data in the receive buffer is no longer required, it should be consumed.\n\nScan example:\n\n```cpp\n#include \"bredis/MarkerHelpers.hpp\"\n...\nnamespace r = bredis;\n...\nusing Buffer = boost::asio::streambuf;\n...\nBuffer rx_buff;\nauto result_markers = c.read(rx_buff);\n/* check for the response */\nauto eq_pong = r::marker_helpers::equality\u003cIterator\u003e(\"PONG\");\n/* print true or false */\nstd::cout \u003c\u003c boost::apply_visitor(eq_pong, result_markers.result) \u003c\u003c \"\\n\";\n/* consume the buffers, after finishing work with the markers */\nrx_buff.consume(result_markers.consumed);\n```\n\nFor *extraction* of results it is possible to use either one of the shipped extractors or to write a custom one. Shipped extractors detach (copy / convert) the extraction results from the receive buffer.\n\n```cpp\n#include \"bredis/Extract.hpp\"\n...\nauto result_markers = c.read(rx_buff);\nauto extract = boost::apply_visitor(r::extractor\u003cIterator\u003e(), result_markers.result);\n/* safe to consume buffers now */\nrx_buff.consume(result_markers.consumed);\n/* we know what the type is, safe to unpack to string */\nauto \u0026reply_str = boost::get\u003cr::extracts::string_t\u003e(extract);\n/* print \"PONG\" */\nstd::cout \u003c\u003c reply_str.str \u003c\u003c \"\\n\";\n```\n\nCustom extractors (visitors) might be useful for performance-sensitive cases, e.g. when JSON is re-constructed in-place by using string reply markers **without** re-allocating the whole JSON-string reply.\n\nThe underlying reason for the decision to retrieve the final results in two steps (get markers and then scan/extract results) is that the *receive buffer* might be scattered (fragmented). In such cases scan and extraction can be performed without gathering receive buffers (i.e. without flattening / linearizing it) if they are separate steps.\n\nIn other words, *markers* have **reference semantics** (they refer to memory regions in the buffer, but do not own it), while *extracted results* have **value semantics** (they take ownership).\n\n## Synchronous TCP-connection example\n\n```cpp\n#include \"bredis/Connection.hpp\"\n#include \"bredis/MarkerHelpers.hpp\"\n\n#include \u003cboost/variant.hpp\u003e\n...\nnamespace r = bredis;\nnamespace asio = boost::asio;\n...\n/* define used types */\nusing socket_t = asio::ip::tcp::socket;\nusing Buffer = boost::asio::streambuf;\nusing Iterator = typename r::to_iterator\u003cBuffer\u003e::iterator_t;\n...\n/* establishing connection to redis is outside of bredis */\nasio::ip::tcp::endpoint end_point(\n    asio::ip::make_address(\"127.0.0.1\"), port);\nsocket_t socket(io_service, end_point.protocol());\nsocket.connect(end_point);\n\n/* wrap socket to bredis connection */\nr::Connection\u003csocket_t\u003e c(std::move(socket));\n\n/* synchronously write command */\nc.write(\"ping\");\n\n/* buffer is allocated outside of bredis connection*/\nBuffer rx_buff;\n/* get the result markers */\nauto result_markers = c.read(rx_buff);\n/* check for the response */\nauto eq_pong = r::marker_helpers::equality\u003cIterator\u003e(\"PONG\");\n/* print true */\nstd::cout \u003c\u003c boost::apply_visitor(eq_pong, result_markers.result) \u003c\u003c \"\\n\";\n/* consume the buffers, after finishing work with the markers */\nrx_buff.consume(result_markers.consumed);\n```\n\nIn the ping example above the `PONG` reply string from redis is not (re)allocated, but directly scanned from the `rx_buff` using a result markers. This can be useful for performance-sensitive cases, e.g. when JSON is re-constructed in-place by using string reply markers **without** re-allocating the whole JSON-string reply.\n\nIn cases where you need to **extract** the reply (i.e. to detach it from `rx_buff`), the following can be done:\n\n```cpp\n#include \"bredis/Extract.hpp\"\n...\nauto result_markers = c.read(rx_buff);\n/* extract the results */\nauto extract = boost::apply_visitor(r::extractor\u003cIterator\u003e(), result_markers.result);\n/* safe to consume buffers now */\nrx_buff.consume(result_markers.consumed);\n/* we know what the type is, safe to unpack to string */\nauto \u0026reply_str = boost::get\u003cr::extracts::string_t\u003e(extract);\n/* print \"PONG\" */\nstd::cout \u003c\u003c reply_str.str \u003c\u003c \"\\n\";\n```\n\nThe examples above throw an exception in case of I/O or protocol errors. Another way to use the API is\n\n```cpp\nboost::system::error_code ec;\nc.write(\"ping\", ec);\n...\nparse_result = c.read(rx_buff, ec);\n```\n\nin case you don't want the throw-exception behaviour.\n\n## Asynchronous TCP-connection example\n```cpp\n#include \"bredis/Connection.hpp\"\n#include \"bredis/MarkerHelpers.hpp\"\n...\nnamespace r = bredis;\nnamespace asio = boost::asio;\nnamespace sys = boost::system;\n...\nusing socket_t = asio::ip::tcp::socket;\nusing Buffer = boost::asio::streambuf;\nusing Iterator = typename r::to_iterator\u003cBuffer\u003e::iterator_t;\nusing Policy = r::parsing_policy::keep_result;\nusing result_t = r::parse_result_mapper_t\u003cIterator, Policy\u003e;\n\n...\n/* establishing the connection to redis is outside of bredis */\nasio::ip::tcp::endpoint end_point(\n    asio::ip::make_address(\"127.0.0.1\"), port);\nsocket_t socket(io_service, end_point.protocol());\nsocket.connect(end_point);\n...\nBuffer tx_buff, rx_buff;\nc.async_write(\n    tx_buff, r::single_command_t{\"llen\", \"my-queue\"}, [\u0026](const sys::error_code \u0026ec, std::size_t bytes_transferred) {\n        /* tx_buff must be consumed when it is no longer needed */\n        tx_buff.consume(bytes_transferred);\n        c.async_read(rx_buff, [\u0026](const sys::error_code \u0026ec, result_t \u0026\u0026r) {\n            /* see above how to work with the result */\n            auto extract = boost::apply_visitor(r::extractor\u003cIterator\u003e(), r.result);\n            auto \u0026queue_size = boost::get\u003cr::extracts::int_t\u003e(extract);\n            std::cout \u003c\u003c \"queue size: \" \u003c\u003c queue_size \u003c\u003c \"\\n\";\n            ...\n            /* consume rx_buff when it is no longer needed */\n            rx_buff.consume(r.consumed);\n        });\n    });\n\n```\n\nIn the example above separate receive and transfer buffers are used. In theory you can use only one buffer for both operations, but you must ensure that it will not be used simultaneously for reading and writing, in other words you cannot use the [pipelining](https://redis.io/topics/pipelining) redis feature.\n\n\n## Asynchronous unix domain socket connections\n\nThe same as above, except the underlying socket type must be changed:\n\n```cpp\nusing socket_t = asio::local::stream_protocol::socket;\n```\n\n## Subscriptions\n\nThere is no specific support for subscriptions, but you can easily build your own like\n\n### synchronous subscription\n\n```cpp\nr::single_command_t subscribe_cmd{\"subscribe\", \"some-channel1\", \"some-channel2\"};\nc.write(subscribe_cmd);\nBuffer rx_buff;\n\n/* get the 2 confirmations, as we subscribed to 2 channels */\nr::marker_helpers::check_subscription\u003cIterator\u003e check_subscription{std::move(subscribe_cmd)};\nfor (auto i = 0; i \u003c 2; ++i){\n  auto result_markers = c.read(rx_buff);\n  bool confirmed =  boost::apply_visitor(check_subscription, result_markers.result);\n  if (!confirmed) {\n    // do something!\n    ...;\n  }\n  rx_buff.consume(result_markers.consumed);\n}\n\nwhile(true) {\n  auto result_markers = c.read(rx_buff);\n  auto extract = boost::apply_visitor(r::extractor\u003cIterator\u003e(), result_markers.result);\n  rx_buff.consume(result_markers.consumed);\n\n  /* process the result  */\n  auto\u0026 array_reply = boost::get\u003cr::extracts::array_holder_t\u003e(extract);\n  auto* type_reply = boost::get\u003cr::extracts::string_t\u003e(\u0026array_reply.elements[0]);\n  if (type_reply \u0026\u0026 type_reply-\u003estr == \"message\") {\n      auto\u0026 channel = boost::get\u003cr::extracts::string_t\u003e(array_reply.elements[1]);\n      auto\u0026 payload = boost::get\u003cr::extracts::string_t\u003e(array_reply.elements[2]);\n      ...\n  }\n}\n```\n\nSee `examples/synch-subscription.cpp` for the full example.\n\n### asynchronous subscription\n\nThese work similarly to the synchronous approach. However you have to provide a read callback initially and again after each successfull read\n```cpp\nusing Policy = r::parsing_policy::keep_result;\nusing ParseResult = r::parse_result_mapper_t\u003cIterator, Policy\u003e;\nusing read_callback_t = std::function\u003cvoid(const boost::system::error_code \u0026error_code, ParseResult \u0026\u0026r)\u003e;\nusing Extractor = r::extractor\u003cIterator\u003e;\n...\n/* we can execute the subscription command synchronously, as it is easier */\nc.command(\"subscribe\", \"channel-1\", \"channel-2\");\n...\nBuffer rx_buff;\nread_callback_t notification_callback = [\u0026](const boost::system::error_code,\n                                            ParseResult \u0026\u0026r) {\n    auto extract = boost::apply_visitor(Extractor(), r.result);\n    rx_buff.consume(r.consumed);\n    /* process the result, see above */\n    ...\n    /* re-trigger new message processing */\n    c.async_read(rx_buff, notification_callback);\n};\n\n/* initialise listening subscriptions */\nc.async_read(rx_buff, notification_callback);\n```\n\nSee `examples/stream-parse.cpp` for the full example.\n\n## Transactions\n\nThere is no specific support for transactions in bredis, but you can easily build your own for your needs.\n\nFirst, wrap your commands into a transaction:\n\n```cpp\n\nr::command_container_t tx_commands = {\n    r::single_command_t(\"MULTI\"),\n    r::single_command_t(\"INCR\", \"foo\"),\n    r::single_command_t(\"GET\", \"bar\"),\n    r::single_command_t(\"EXEC\"),\n};\nr::command_wrapper_t cmd(tx_commands);\nc.write(cmd);\n```\n\nThen, as above there were **4** redis commands, there we should receive **4** redis\nreplies: `OK`, `QUEUED`, `QUEUED` followed by the array of results of the execution of the commands\nin the transaction (i.e. results for `INCR` and `GET` above)\n\n```cpp\nBuffer rx_buff;\nc.async_read(rx_buff, [\u0026](const sys::error_code \u0026ec, result_t\u0026\u0026 r){\n    auto \u0026replies = boost::get\u003cr::markers::array_holder_t\u003cIterator\u003e\u003e(r.result);\n    /* scan stream for OK, QUEUED, QUEUED */\n    ...\n    assert(replies.elements.size() == 4);\n    auto eq_OK = r::marker_helpers::equality\u003cIterator\u003e(\"OK\");\n    auto eq_QUEUED = r::marker_helpers::equality\u003cIterator\u003e(\"QUEUED\");\n    assert(boost::apply_visitor(eq_OK, replies.elements[0]));\n    assert(boost::apply_visitor(eq_QUEUED, replies.elements[1]));\n    assert(boost::apply_visitor(eq_QUEUED, replies.elements[2]));\n\n    /* get tx replies */\n    auto \u0026tx_replies = boost::get\u003cr::markers::array_holder_t\u003cIterator\u003e\u003e(replies.elements[3]);\n    ...;\n    rx_buff.consume(r.consumed);\n},\n4); /* pay attention here */\n\n```\n\n## Futures \u0026 Coroutines\n\nDone in a similiar way as in `Boost::ASIO` (special thanks to Vinnie Falko for the suggestion)\n\n### Futures\n\n```cpp\n#include \u003cboost/asio/use_future.hpp\u003e\n...\nBuffer rx_buff, tx_buff;\nauto f_tx_consumed = c.async_write(tx_buff, \"ping\", asio::use_future);\nauto f_result_markers = c.async_read(rx_buff, asio::use_future);\n...\ntx_buff.consume(f_tx_consumed.get());\nauto result_markers = f_result_markers.get();\n/* scan/extract result, and consume rx_buff as usual */\n```\n\n### Coroutines\n\n```cpp\n#include \u003cboost/asio/spawn.hpp\u003e\nBuffer rx_buff, tx_buff;\n\nboost::asio::spawn(\n    io_service, [\u0026](boost::asio::yield_context yield) mutable {\n        boost::system::error_code error_code;\n        auto consumed = c.async_write(tx_buff, \"ping\", yield[error_code]);\n        tx_buff.consume(consumed);\n        ...\n        auto parse_result = c.async_read(rx_buff, yield[error_code], 1);\n        /* scan/extract result */\n        rx_buff.consume(parse_result.consumed);\n    });\n```\n\n## Steams\n\nThere is no specific support for streams (appeared in redis 5.0) in bredis,\nthey are just usual `XADD`, `XRANGE` etc. commands and corresponding replies.\n\n```cpp\n...\nBuffer rx_buff;\nc.write(r::single_command_t{ \"XADD\", \"mystream\", \"*\", \"cpu-temp\", \"23.4\", \"load\", \"2.3\" });\nauto parse_result1 = c.read(rx_buff);\nauto extract1 = boost::apply_visitor(Extractor(), parse_result1.result);\nauto id1 = boost::get\u003cr::extracts::string_t\u003e(extract1);\n\nc.write(r::single_command_t{ \"XADD\", \"mystream\", \"*\", \"cpu-temp\", \"23.2\", \"load\", \"2.1\" });\nauto parse_result2 = c.read(rx_buff);\nauto extract2 = boost::apply_visitor(Extractor(), parse_result2.result);\nauto id2 = boost::get\u003cr::extracts::string_t\u003e(extract2);\nrx_buff.consume(parse_result2.consumed);\n\nc.write(r::single_command_t{ \"XRANGE\" , \"mystream\",  id1.str, id2.str});\nauto parse_result3 = c.read(rx_buff);\nauto extract3 = boost::apply_visitor(Extractor(), parse_result3.result);\nrx_buff.consume(parse_result3.consumed);\n\nauto\u0026 outer_arr = boost::get\u003cr::extracts::array_holder_t\u003e(extract3);\nauto\u0026 inner_arr1 = boost::get\u003cr::extracts::array_holder_t\u003e(outer_arr.elements[0]);\nauto\u0026 inner_arr2 = boost::get\u003cr::extracts::array_holder_t\u003e(outer_arr.elements[1]);\n...\n\n```\n\n\n## Inspecting network traffic\n\nSee `t/SocketWithLogging.hpp` for an example. The main idea is quite simple:\nInstead of providing a real socket implementation supplied by `Boost::ASIO`,\nprovide a wrapper (proxy) which will **spy** on the traffic before\ndelegating it to/from a `Boost::ASIO` socket.\n\n## Cancellation \u0026 other socket operations\n\nThere is nothing specific to this in bredis. If you need low-level socket\noperations, instead of moving *socket* into bredis connection, you can\nsimply move a *reference* to it and keep (own) the socket somewhere\noutside of the bredis connection.\n\n```cpp\nusing socket_t = asio::ip::tcp::socket;\nusing next_layer_t = socket_t \u0026;\n...\nasio::ip::tcp::endpoint end_point(asio::ip::make_address(\"127.0.0.1\"), port);\nsocket_t socket(io_service, end_point.protocol());\nsocket.connect(end_point);\nr::Connection\u003cnext_layer_t\u003e c(socket);\n...\nsocket.cancel();\n```\n\n## Thread-safety\n\n`bredis` itself is thread-agnostic, however the underlying socket (`next_layer_t`)\nand used buffers are usually not thread-safe. To handle that in multi-thead\nenvironment the access to those objects should be sequenced via\n`asio::io_context::strand` . See the `examples/multi-threads-1.cpp`.\n\n\n## parsing_policy::drop_result\nThe performance still can be boosted if it is known beforehand that the response from\nredis server is not needed at all. For example, the only possible response to `PING`\ncommand is `PONG` reply, usually there is no sense it validating that `PONG` reply,\nas soon as it is known, that redis-server alredy delivered us **some** reply\n(in practice it is `PONG`). Another example is `SET` command, when redis-server\n**usually** replies with `OK`.\n\nWith `parsing_policy::drop_result` the reply result is just verified with formal\ncompliance to redis protocol, and then it is discarded.\n\nIt should be noted, that redis can reply back with error, which aslo correct\nreply, but the caller side isn't able to see it when `parsing_policy::drop_result`\nis applied. So, it should be used with care, when you know what your are doing. You have\nbeen warned.\n\nIt is safe, however, to mix different parsing policies on the same connection,\ni.e. write `SET` command and read it's reply with `parsing_policy::drop_result` and\nthen write `GET` command and read it's reply with `parsing_policy::keep_result`.\nSee the `examples/speed_test_async_multi.cpp`.\n\n## API\n\nThere's a convenience header include/bredis.hpp, doing `#include \"bredis.hpp\"` will include\nevery header under include/bredis/ .\n\n### `Iterator` template\n\nThe underlying iterator type used for the dynamic buffer type (e.g. `boost::asio::streambuf`)\n\n### `redis_result_t\u003cIterator\u003e`\n\nHeader: `include/bredis/Markers.hpp`\n\nNamespace: `bredis::markers`\n\n\n`boost::variant` for the basic types in the redis protocol [](https://redis.io/topics/protocol),\ni.e. the following marker types :\n- `nil_t\u003cIterator\u003e`\n- `int_t\u003cIterator\u003e`\n- `string_t\u003cIterator\u003e` (simple string and bulk strings)\n- `error_t\u003cIterator\u003e`\n- `array_holder_t\u003cIterator\u003e`\n\nThe basic type is `string_t\u003cIterator\u003e`, which contains `from` and `to` members (`Iterator`)\nto where the string is held. String does not contain the special redis-protocol symbols or any other\nmetadata, i.e. it can be used to extract/flatten the whole string.\n\n`nil_t\u003cIterator\u003e`, `int_t\u003cIterator\u003e`, `error_t\u003cIterator\u003e` just have a `string` member\nto point to the underlying string in the redis protocol.\n\n`array_holder_t` is recursive wrapper for the `redis_result_t\u003cIterator\u003e`, it contains a\n`elements` member of `std::array` of `redis_result_t\u003cIterator\u003e` type.\n\n### `parse_result_t\u003cIterator, Policy\u003e`\n\nHeader: `include/bredis/Result.hpp`\n\nNamespace: `bredis`\n\nRepresents the results of a parse attempt. It is a `boost::variant` of the following types:\n- `not_enough_data_t`\n- `protocol_error_t`\n- `positive_parse_result_t\u003cIterator, Policy\u003e`\n\n`not_enough_data_t` is a empty struct. It means that buffer just does not contain enough\ninformation to completely parse it.\n\n`protocol_error_t` has a `boost::system::error_code code` member. It describes the error\nin the protocol (e.g. when the type in the stream is specified as an integer, but it cannot be\nconverted to an integer). This error should never occur in production code, meaning\nthat no (logical) errors are expected in the redis-server nor in the bredis parser. The\nerror might occur if the buffer is corrupted.\n\n`Policy` (namespace `bredis::parsing_policy`) specifies what to do with the result:\nEither drop it (`bredis::parsing_policy::drop_result`) or keep it\n(`bredis::parsing_policy::keep_result`). The helper\n`parse_result_mapper_t\u003cIterator, Policy\u003e` helps to get the proper\n`positive_parse_result_t\u003cIterator, Policy\u003e` type.\n\n`positive_parse_result_t\u003cIterator, Policy\u003e` contains members:\n- `markers::redis_result_t\u003cIterator\u003e result` - the result of mark-up buffer; can be used\neither for scanning for particular results or for extraction of results. Valid only\nfor `keep_result` policy.\n- `size_t consumed` - how many bytes of receive buffer must be consumed after\nusing the `result` field.\n\n### marker helpers\n\nHeader: `include/bredis/MarkerHelpers.hpp`\n\nNamespace: `bredis::marker_helpers`\n\n#### `stringizer\u003cIterator\u003e`\n\nApply this `boost::static_visitor\u003cstd::string\u003e`s to\nstringize the result (can be useful for debugging).\n\n#### `equality\u003cIterator\u003e`\n\nApply this `boost::static_visitor\u003cbool\u003e` to find a *string* in the\nparsed results (the markup can point to integer types, but as it\nis transferred as a string anyway, it still can be founded as string\ntoo).\n\nConstructor: `equality\u003cIterator\u003e(std::string str)`\n\n#### `check_subscription\u003cIterator\u003e`\n\nThis `boost::static_visitor\u003cbool\u003e` helper is used to check\nwhether the redis reply confirms one of the requested channels. Hence,\nthe constructor is `check_subscription(single_command_t)`.\n\nUsually, the redis subscription reply is in the form:\n```\n[array] {\n    [string] \"subcribe\",\n    [string] channel_name,\n    [int] reference\n}\n```\n\nSo it checks that:\n1. The redis reply is a 3-element array\n2. The 1st reply element is a string, and it *case-insensitively*\nmatches the command, i.e. it is assumed, that\ncommand will be `subscribe` or `psubscribe` depending on the original command\n3. That the 3rd reply element is a reference, and it is present\namong the command arguments.\n\nIt is possible to reuse the same `check_subscription\u003cIterator\u003e`\non *multiple* redis replies to a single subsription command for multiple channels.\n\nExample:\n\n```cpp\nbredis::single_command_t subscribe_cmd{\n    \"subscribe\", \"channel-1\", \"channel-2\"\n};\n...\n// write the command, so the subscribe_cmd\n// will be no longer be required\n...;\nbredis::marker_helpers::check_subscription\u003cIterator\u003e\n    check_subscription{std::move(subscribe_cmd)};\n...;\n// get the 1st reply\nauto parse_result = ...;\nbool channel_1_ok = boost::apply_visitor(check_subscription, parse_result.result);\n...;\n// get the 2nd reply\nparse_result = ...;\nbool channel_2_ok = boost::apply_visitor(check_subscription, parse_result.result);\n```\n\n### `command_wrapper_t`\n\nHeader: `include/bredis/Command.hpp`\n\nNamespace: `bredis`\n\n`boost::variant` for the basic commands:\n- `single_command_t`\n- `command_container_t`\n\n`single_command_t` represents a single redis command with all its arguments, e.g.:\n\n```cpp\n// compile-time version\nr::single_command_t cmd_ping {\"ping\"};\nr::single_command_t cmd_get {\"get\", \"queu-name\"};\n...\n// or runtime-version\nstd::vector\u003cstd::string\u003e subscription_items { \"subscribe\", \"channel-a\", \"channel-b\"};\nr::single_command_t cmd_subscribe {\n    subscription_items.cbegin(),\n    subscription_items.cend()\n};\n```\n\nThe arguments must be conversible to `boost::string_ref`.\n\n`command_container_t` is a `std::vector` of `single_command_t`. It is useful for transactions\nor bulk message creation.\n\n### `Connection\u003cNextLayer\u003e`\n\nHeader: `include/bredis/Connection.hpp`\n\nNamespace: `bredis`\n\nA thin wrapper around `NextLayer`; represents a connection to redis. `NextLayer` can\nbe either `asio::ip::tcp::socket` or `asio::ip::tcp::socket\u0026` or a custom wrapper, which\nfollows the specification of `asio::ip::tcp::socket`.\n\nThe constructor `template \u003ctypename... Args\u003e Connection(Args \u0026\u0026... args)` is used for the\nconstruction of NextLayer (stream interface).\n\nStream interface accessors:\n- `NextLayer \u0026next_layer()`\n- `const NextLayer \u0026next_layer() const`\n\nreturn the underlying stream object.\n\n#### Synchronous interface\n\nPerforms a synchonous write of a redis command:\n\n- `void write(const command_wrapper_t \u0026command)`\n- `void write(const command_wrapper_t \u0026command, boost::system::error_code \u0026ec)`\n\nPerforms a synchronous read of a redis result until the buffer is parsed or\nsome error (protocol or I/O) occurs:\n\n- `template \u003ctypename DynamicBuffer\u003e positive_parse_result_t\u003cIterator, Policy = bredis::parsing_policy::keep_result\u003e read(DynamicBuffer \u0026rx_buff)`\n- `template \u003ctypename DynamicBuffer\u003e positive_parse_result_t\u003cIterator, Policy = bredis::parsing_policy::keep_result\u003e read(DynamicBuffer \u0026rx_buff, boost::system::error_code \u0026ec);`\n\n`DynamicBuffer` must conform to the `boost::asio::streambuf` interface.\n\n#### Asynchronous interface\n\n##### async_write\n\nThe `WriteCallback` template should be a callable object with the signature:\n\n`void (const boost::system::error_code\u0026, std::size_t bytes_transferred)`\n\nThe asynchronous write has the following signature:\n\n```cpp\nvoid-or-deduced\nasync_write(DynamicBuffer \u0026tx_buff, const command_wrapper_t \u0026command,\n                WriteCallback write_callback)\n```\n\nIt writes the redis command (or commands) into a *transfer buffer*, sends them\nto the *next_layer* stream and invokes `write_callback` after completion.\n\n`tx_buff` must consume `bytes_transferred` upon `write_callback` invocation.\n\nThe client must guarantee that `async_write` is not invoked until the previous\ninvocation is finished.\n\n##### async_read\n\n`ReadCallback` template should be a callable object with the signature:\n\n`void(boost::system::error_code, r::positive_parse_result_t\u003cIterator, Policy = bredis::parsing_policy::keep_result\u003e\u0026\u0026 result)`\n\nThe asynchronous read has the following signature:\n\n```cpp\nvoid-or-deduced\nasync_read(DynamicBuffer \u0026rx_buff, ReadCallback read_callback,\n               std::size_t replies_count = 1, Policy = bredis::parsing_policy::keep_result{});\n```\n\nIt reads `replies_count` replies from the *next_layer* stream, which will be\nstored in `rx_buff`, or until an error (I/O or protocol) is encountered; then\n`read_callback` will be invoked.\n\nIf `replies_count` is greater than `1`, the result type will always be\n`bredis::array_wrapper_t`; if the `replies_count` is `1` then the result type\ndepends on redis answer type.\n\nOn `read_callback` invocation with a successful parse result it is expected,\nthat `rx_buff` will consume the amount of bytes specified in the `result`.\n\nThe client must guarantee that `async_read` is not invoked until the previous\ninvocation is finished. If you invoke `async_read` from `read_callback`\ndon't forget to **consume** `rx_buff` first, otherwise it leads to\nsubtle bugs.\n\n# License\n\nMIT\n\n# Contributors\n\n- [Derek Colley](https://github.com/dcolley)\n- [Stefan Hacker](https://github.com/hacst)\n- [nkochakian](https://github.com/nkochakian)\n- [Yuval Hager](https://github.com/yhager)\n- [Vinnie Falco](https://github.com/vinniefalco)\n- [Stephen Coleman](https://github.com/omegacoleman)\n- [maxtorm miximtor](https://github.com/miximtor)\n- [Ronny Nowak](https://github.com/dargun)\n- [Stephen Chisholm](https://github.com/sbchisholm)\n- [amensel](https://github.com/amensel)\n- [Usevalad Sauta](https://github.com/VsevolodSauta)\n\n## See also\n- https://github.com/Cylix/cpp_redis\n- https://github.com/hmartiro/redox\n- https://github.com/nekipelov/redisclient\n","funding_links":[],"categories":["C++"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbasiliscos%2Fcpp-bredis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbasiliscos%2Fcpp-bredis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbasiliscos%2Fcpp-bredis/lists"}