Why Coroutines Matter
As I build more components in modern C++, I find myself relying ever more on C++20 coroutines. Whenever my code needs to “wait for something” – an HTTP request, a timer, a file I/O – coroutines let me express asynchronous flows as straight-line code. But to make that work, you need libraries that expose awaitable interfaces.
Until now, RESTinCurl has provided a simple, header-only wrapper around libcurl for both synchronous and callback‐based asynchronous HTTP calls. Today I’m pleased to announce that RESTinCurl’s RequestBuilder also offers native C++20 coroutine support, so you can write:
auto r = co_await client. -> . .;... and have that work whether you’re talking HTTP/1.1 or HTTP/2, without pulling in heavyweight dependencies.
My Motivation
The backend for NextApp, my productivity app, needs push support to mobile devices. For Google and Android, I could have hacked something in Boost.Beast (if I had the time), or just used rest-cpp. Google’s API works fine with HTTP/1.1. Apple, on the other hand, requires HTTP/2 for their push notification API for iPhones.
RESTinCurl, on the other hand, a lightweight C++ wrapper over libcurl that I wrote when I implemented a cross-platform C++ library for a crypto wallet, would be perfect. The only "problem" was its async callback interface. I don’t do callbacks anymore (if I can avoid it).
So, I decided to add C++20 coroutine support. Now it’s perfect for the job. My server already uses Boost.Asio for its coroutines, so I added support for that as well as standard, generic C++20 coroutines. For some reason, Boost.Asio doesn’t support the generic coroutine interface.
For the push notifications to iPhones, I can write something like:
auto sendPush = -> unifex:: const auto url = ; const auto r = co_await client. -> . . . .; if std::cerr << "Push failed: " << r.body << "\n"; co_return false; } co_return true;};...if I use the Unifex coroutine library. For Boost, the code would be almost the same (see below).
Lightweight, Header-Only, No Boost Required
RESTinCurl was designed for minimal dependencies (just libcurl) and small binary size. Adding coroutine support didn’t change that philosophy:
- Header-only: you only need to include restincurl.h and enable RESTINCURL_ENABLE_ASYNC.
- Single worker thread: a dedicated thread services all in-flight requests.
- No Boost. Unless you really want it: if you already use Boost.Asio, you can integrate via a second awaitable (see below), but coroutines work out of the box without Asio.
Two Flavors of Awaitable Execution
1. CoExecute() – Plain C++20 Awaitable
This member returns a custom awaiter that:
- Suspends the coroutine.
- Enqueues the request on RESTinCurl’s worker thread.
- Resumes when the HTTP response arrives.
- Returns the completed restincurl::Result from await_resume().
unifex::task<void> No templates beyond what you’re already using, and no extra files to compile.
2. AsioAsyncExecute() – Optional Boost.Asio Integration
If your project already uses Boost.Asio, you can seamlessly slot RESTinCurl into your io_context. Just enable RESTINCURL_ENABLE_ASIO and write:
boost::asio::io_context ctx;auto f = ;ctx.;f.;AsioAsyncExecute(use_future) integrates directly into Asio’s completion/coroutine machinery.
Note that the worker-thread used to drive libcurl is still an internal thread managed by RESTinCurl.
Conclusion
With RESTinCurl’s new coroutine APIs you get:
- A lightweight wrapper over libcurl
- Header-only, single thread per Client instance.
- Native C++20 coroutines for any async HTTP/1.1 & 2 request
- Optional seamless Boost.Asio support
If your next C++20 component needs to “wait” for anything – REST calls, timers, or I/O – having coroutine-capable libraries is essential. Give the new coroutine APIs in RESTinCurl a try today.
Happy coding!
- Link to RESTinCurl