Low-level C++23 HTTP client and server library, on one of two I/O runtimes, chosen at configure time:
- ASIO, on Asio: every operation is an asynchronous operation of Asio and takes any completion token.
- COROSIO, on Corosio: coroutines only.
Warning
This repository is purely experimental. It is not meant for production use, and anything in it can change or break at any time, without notice.
It supports HTTP/1.x, HTTP/2 and HTTP/3 behind a common, type-erasing interface, hence the any in the name -- and plain QUIC streams next to HTTP/3, for protocols of your own.
None of those protocols are implemented from scratch. Instead, it is a wrapper around the following well-established libraries:
- beast (its parser and serializer)
- nghttp2, or nghttp2 version 2, its rewrite
- ngtcp2 / nghttp3
This project started as a hand-written replacement for nghttp2-asio. Once the concept was working, HTTP/1.1 was added, and both protocols were hidden behind a type-erased interface. Finally, HTTP/3 was added with the help of agentic coding.
https://pgit.github.io/anyhttp/ has the programmer's guide, which is the place to start, the API reference and how it all works: I/O runtimes, the end of a body, concurrent requests, moving to HTTP/3 and the HTTP/2 library. docs/README.md says how to make it.
A server, which echoes every request, over HTTP/1.1, HTTP/2 and HTTP/3 on one port:
Task<void> echo(server::Request request, server::Response response)
{
if (request.content_length())
response.content_length(request.content_length().value());
if (auto [ec] = co_await response.submit(200, {}); ec)
co_return;
std::array<uint8_t, 64 * 1024> buffer;
for (;;)
{
auto [ec, n] = co_await request.read_some(boost::asio::buffer(buffer));
if (ec == errors::eof)
break;
if (ec)
co_return;
if (auto [write_ec] = co_await response.write(boost::asio::buffer(buffer, n)); write_ec)
co_return;
}
co_await response.write_eof();
}A client, for which a plain GET is one operation, and anything else a few more:
Task<void> do_session(client::Client& client, boost::urls::url url)
{
auto [ec1, session] = co_await client.connect();
auto [ec2, message] = co_await session.get(url);
std::println("{}: {} bytes", message.result_int(), message.body().size());
auto [ec3, request] = co_await session.submit(url, {});
auto [ec4] = co_await request.write_eof();
auto [ec5, response] = co_await request.get_response();
}Both I/O runtimes have every operation as such a coroutine, that yields a tuple and reports errors instead of throwing. On ASIO, each is also an asynchronous operation that takes any completion token. The whole programs are hello_server.cpp and hello_client.cpp.
C++23, CMake 3.30 and Boost 1.88 or later, spdlog, nghttp2, ngtcp2 and nghttp3 -- the Dockerfiles in .devcontainer build all of them, and those are the versions that are tested.
include(FetchContent)
FetchContent_Declare(anyhttp
GIT_REPOSITORY https://github.com/pgit/anyhttp.git
GIT_TAG <commit>
SYSTEM)
FetchContent_MakeAvailable(anyhttp)
target_link_libraries(my_program PRIVATE anyhttp::anyhttp)An installed package works as well, on ASIO, see getting the library.
For now, this section contains just a set of random links collected during development.
- Beast Example using Type Erasure
- Development container -- how the build environment is put together
- Capturing traffic -- tcpdump and tshark, decrypting TLS and QUIC with a key log
- cpp-devcontainer -- the generic C++ base image it builds on
- c-ares-async -- asynchronous DNS, to resolve HTTPS resource records and start off with HTTP/3