Low-level C++ HTTP client and server library, based on ASIO and its asynchronous model.
** THIS REPOSITORY IS PURELY EXPERIMENTAL **
It supports HTTP/1.x, HTTP/2 and HTTP/3 behind a common, type-erasing interface, hence the any int the name.
None of those protocols are implemented from scratch. Instead, it is a wrapper around the following well-established libraries:
- Boost Beast
- nghttp2
- ngtcp2/nghttp3
awaitable<void> echo(server::Request request, server::Response response)
{
if (request.content_length())
response.content_length(request.content_length().value());
co_await response.async_submit(200, {});
std::array<uint8_t, 64 * 1024> buffer;
for (;;)
{
auto [ec, n] = co_await request.async_read_some(asio::buffer(buffer), as_tuple);
if (ec == asio::error::eof)
break;
if (ec)
throw boost::system::system_error(ec);
co_await response.async_write(asio::buffer(buffer, n));
}
co_await response.async_write_eof();
}The end of an incoming body is reported the way ASIO reports it everywhere else: asio::error::eof
with zero bytes. A body cut short -- a reset stream, a connection that went away mid-message --
completes with http::error::partial_message instead, so the two stay distinguishable.
The end of an outgoing body is stated explicitly, with async_write_eof(). It takes a buffer of
its own, so the last of the body and the end of it go out together -- one DATA frame with
END_STREAM, one QUIC STREAM frame with FIN, one last chunk -- instead of costing a second, empty
write:
co_await response.async_submit(200, fields({{"Content-Length", body.size()}}));
co_await response.async_write_eof(asio::buffer(body));awaitable<void> do_session(Client& client, boost::urls::url url)
{
auto session = co_await client.async_connect();
auto request = co_await session.async_submit(url, {});
auto response = co_await request.async_get_response();
}The asynchronous operations exposed by server and client are ASIO asynchronous operations. As such, they support a range of completion tokens like use_awaitable or plain callbacks.
The implementation is hidden behind any_completion_handler so that it can be compiled separately.
This work is partly inspired by asio-grpc, which takes the idea even one step further and also supports the upcoming sender/receiver model of execution.
classDiagram
Response --|> Reader
Request_Impl --|> Writer
namespace client {
class Response {
async_read_some(buffer)
}
class Request {
async_get_response()
async_write(buffer)
async_write_eof(buffer)
}
class Client {
async_connect()
}
class Request_Impl {
}
}
namespace impl {
class Reader {
get_executor()
content_length()
async_read_some(buffer)
detach()
destroy()
}
class Writer {
get_executor()
content_length(optional<size_t>)
async_write(buffer, eof)
detach()
destroy()
}
class Client {
get_executor()
}
}
For now, this section contains just a set of random links collected during development.