restc-cpp
restc-cpp copied to clipboard
Asio's extensible asynchronous model
Asio has an extensible asynchronous model which allows the caller their choice of completion handlers, futures, coroutines (stackful and stackless), or user defined tokens which specialize async_result
. Why did this library choose to ignore that design and instead force users to use coroutines?
That is a good question. I spent quite some thing thinking about this when I started the project, and I still think about it.
This design-choice boils down to finding the least complex way of achieving three of my primary goals when I set out: A fully asynchronous HTTP client, and a fully asynchronous deserialization of json payloads, and everything implemented (working for a specific project) in a few weeks. I have implemented similar functionality in the past using callbacks, and - well, it took time to get it right (and debugged) and a few months later the code was very hard to comprehend. Making the design-choice to implement the HTTP-client in the library using a co-routine made the implementation faster, the code simpler - and much easier to read for other people and the future me. I also sincerely believe that this is the way to go for asynchronous C++ programming in the future.
Keeping the complexity under control is paramount as this is a side-project, one of many, and I'm currently the only developer. I don't want the project to die because it require more time than I can give it.
That said, If you have some good suggestions on how to achieve the same goals using a more flexible architecture I am absolutely listening. I do like to wrap code that use the library and performs a series of related REST operations in a co-routine. I don't like to embed the example code, or very simple use, in co-routines. I also don't like to dictate the architecture of other peoples projects.
Jarle
I also don't like to dictate the architecture of other peoples projects.
That is a good design goal. The way to achieve this is to use Asio's extensible asynchronous model, described in this paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf
This means that your initiating functions are templated on CompletionToken and have a return value which uses boost::asio::async_result
. Typical declaration (this is from Beast):
template<
class AsyncReadStream,
class DynamicBuffer,
bool isRequest, class Body, class Allocator,
class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(
ReadHandler, void(error_code, std::size_t))
async_read(
AsyncReadStream& stream,
DynamicBuffer& buffer,
http::message<isRequest, Body, basic_fields<Allocator>>& msg,
ReadHandler&& handler);
This function reads an HTTP message asynchronously. ReadHandler
is a CompletionToken. This means that a user can pass a basic_yield_context
instead of a lambda and it will use the coroutine. The return value will become the std::size_t
which is the 2nd argument of the callback signature (the void(error_code, std::size_t)
. Or they can pass the special value boost::asio::use_future
, and the return value will become std::future<std::size_t>
(or something like that). Or they can pass a traditional lambda if they want to use callbacks.
The return value uses the macro BOOST_ASIO_INITFN_RESULT_TYPE
which is a helper for async_result
. The return value can change depending on what the user passes for ReadHandler
. If it is a normal completion handler the return value will be void
. If it is a coroutine the return value will be the 2nd argument of the callback signature. If it is boost::asio::use_future
it will be a future that returns the 2nd argument of the callback signature, and so on.
This is the way you make an initiating function which lets the user use the style of notification they prefer. It does require more work for the library author, but we do this gladly so that users will have a better experience.
Beast has a tutorial on writing composed operations, which you will need if you want to go this route:
http://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_io/writing_composed_operations.html
Libraries which use the extensible model will be prepared for when the Networking.TS becomes merged to the C++ standard. Users will expect that asynchronous initiating functions adhere to these specifications. Libraries which offer support for the model will see greater use.
Thank you. I'll read up on this and see if I can incorporate it in the library.