Skip to content

Repository files navigation

Overview

Build and run tests Coverage

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

Synopsis

Server

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));

Client

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();   
}

Implementation

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()
   }
}
Loading

Links

For now, this section contains just a set of random links collected during development.

About

Low-level C++ HTTP client/server library based on ASIO

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages