API reference

Made from the comments in the public headers. Programmer’s guide is the place to start, and says how these go together.

Everything is in the namespace anyhttp, with what is for one side in anyhttp::client, anyhttp::server and anyhttp::quic. This shows the headers of an ASIO build: on COROSIO, there are no operations with completion tokens, and the types are the other column of the table.

Client

#include <anyhttp/client.hpp>
class Client

Makes connections to the one server of its Config, each of them a Session.

It can be moved but not copied.

Public Types

using executor_type = Executor

The executor type, for what looks for one.

Public Functions

Client(Executor executor, Config config)

A client on executor, which does nothing until it is told to connect.

Parameters:
  • executor – What the client and its sessions run on.

  • config – What it connects to, and how.

executor_type get_executor() const noexcept
Returns:

The executor the client runs on.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_connect(CompletionToken &&token = CompletionToken())

Connect to configured peer and establish a new session.

This operation supports ‘terminal’ cancellation. When cancellation is requested, it may take some time to be executed. This is because the async resolver eventually calls getaddrinfo(), which is a blocking system call. This is done from a separate thread, so the users executor is not blocked, but this still means that the operation cannot be interrupted.

Parameters:

token – The completion token, for a handler of the signature Connect.

Returns:

What token makes of the operation.

IoTask<Session> connect()

async_connect() as a coroutine: auto [ec, session] = co_await client.connect();

Returns:

The error and the session.

struct Config

What a Client is made with.

Public Members

boost::urls::url url = {"localhost:8080"}

What this client is for: the server’s certificate has to be valid for the host of this URL.

Unless endpoints says otherwise, it also is where the client connects to.

std::vector<TcpEndpoint> endpoints = {}

Where to connect, instead of resolving the host of url, for a caller that knows better: it uses a resolver of its own, or follows a DNS HTTPS record (RFC 9460).

They are tried in this order until one of them can be connected to, and with these, url does not have to resolve at all.

Only where the connection goes changes. The certificate still has to be valid for the host of url, which also goes out as SNI. And what a request says it is for is the URL it is submitted with, as ever. An HTTP/3 alternative service is not followed, see follow_alt_svc: the caller has made that choice.

With HTTP/3, an endpoint that does not answer at all takes the idle timeout of 30 seconds before the next one is tried – limit that by cancelling async_connect().

Protocol protocol = {Protocol::h2}

What to speak.

An “https” url means TLS, and the server has a say in it: h1 offers HTTP/1.1 only, while h2 offers HTTP/2 and HTTP/1.1 and takes what the server picks, so the session may turn out to speak HTTP/1.1, see Session::protocol(). A server that does not use ALPN at all gets HTTP/1.1, too.

Any other scheme means cleartext, and exactly the protocol given: HTTP/2 is then “h2c” by prior knowledge, which a server has to be ready for. HTTP/3 is TLS over QUIC with any scheme.

size_t max_header_size = default_max_header_size

The largest header section of a response the client accepts, in bytes, counted as for server::Config::max_header_size.

For a response with more, async_get_response() fails with errors::header_limit and the stream is reset – with HTTP/1.1, which has no streams, the connection can not be used any more.

int socket_send_buffer_size = 0

The kernel’s send and receive buffer, in bytes, for the TCP connection; zero (the default) leaves the kernel’s autotuning alone.

Set once the connection is up – the socket is not open before that – so the window scale of the handshake is the kernel’s own, and only the window advertised from then on follows this. TCP only – HTTP/3 goes over UDP.

As with server::Config::socket_send_buffer_size, this is a testing aid: it bounds how much async_write() can hand to the kernel before it has to wait for the peer, which over HTTP/1.1 is the only thing that makes a write block at all. Both ends have to be pinned to bound the pipeline – the bytes in flight are the client’s send buffer plus the server’s receive buffer, and whatever the server echoes back fills the other direction too.

int socket_receive_buffer_size = 0

See socket_send_buffer_size.

bool follow_alt_svc = false

Whether to take up an HTTP/3 alternative service the server advertises (RFC 7838), as an “Alt-Svc” header field on a response or, over HTTP/2, an ALTSVC frame.

QUIC has no in-band upgrade, so this is what an “upgrade” to HTTP/3 comes down to: the connection that learns about the alternative keeps speaking what it speaks, and the next async_connect() goes to the advertised endpoint over HTTP/3 instead of using protocol.

The alternative is remembered for as long as its “ma” parameter says, which is 24 hours unless the server gives one, and only for as long as the Client itself lives – there is no cache on disk, so a fresh process starts over with protocol. It does not apply to a client that has been given endpoints.

std::string tls_ca_file

The CA certificates to verify the server against over TLS, as a PEM file.

Empty uses the default trust store of the TLS library. The server’s certificate must be valid for the host of url – also when connecting to an alternative service, as RFC 7838 requires – or async_connect() fails. Read on every connect, so a replaced file takes effect with the next connection.

class Request : public anyhttp::Writer

A request that has been submitted, as a handle: a Writer for its body, and the way to its response.

It can be moved but not copied.

The body is ended with Writer::async_write_eof, which does no harm where there is none. What releasing a request before that means, or before asking for its response, is on the page on concurrent requests.

Public Types

using GetResponse = void(error_code, Response)

The completion signature of async_get_response: the error and the response.

Public Functions

Request()

A handle without a request, as the one that comes with an error.

void reset() noexcept

Releases the request, as the destructor does.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_get_response(CompletionToken &&token = CompletionToken())

Waits for the response to this request, until its header has been received.

With HTTP/1.1, responses arrive in the order the requests were sent, one after the other. Getting the response to a request whose predecessors’ responses have not been read to their end does not wait for that to happen, but fails immediately with errors::would_block. After a response could not be read – it was released before its end, or its request was released without asking for it – getting any later response fails with errors::connection_aborted. See the page on concurrent requests.

Parameters:

token – The completion token, for a handler of the signature GetResponse.

Returns:

What token makes of the operation.

IoTask<Response> get_response()

async_get_response() as a coroutine: auto [ec, response] = co_await request.get_response();

Returns:

The error and the response.

class Response : public anyhttp::Reader

The response to a request, as a handle: its status and header fields, and a Reader for its body.

It can be moved but not copied.

Releasing one before the end of the body resets its stream with HTTP/2 and HTTP/3, which leaves the session usable. Over HTTP/1.1, it is the end of the connection for further responses.

Public Functions

Response()

A handle without a response, as the one that comes with an error.

void reset() noexcept

Releases the response, as the destructor does.

int status_code() const noexcept
Returns:

The status code of the response.

const Fields &fields() const
Returns:

The response header fields, without HTTP/2 and HTTP/3 pseudo-headers.

using anyhttp::client::Message = boost::beast::http::response<boost::beast::http::string_body>

A response received in one piece: status, header fields and the whole body as a string.

This is a plain Beast message, with nothing of anyhttp left in it – what Session::async_get() hands back. Its version is 11 whatever the protocol was: HTTP/2 and HTTP/3 have no version on the wire, and a Beast message has nowhere else to put one.

Session

#include <anyhttp/session.hpp>
class Session

One connection of a client, as a handle: what client::Client::async_connect gives.

Requests are submitted on it, as many at a time as the protocol and the peer allow.

It can be moved but not copied, and destroying it ends the connection the abrupt way, see async_shutdown. It is to be used on its executor only. The requests and responses it has given out may outlive it, and fail from then on.

Public Functions

Session() = default

A handle without a connection.

void reset() noexcept

Ends the connection the abrupt way, as the destructor does: nothing asynchronous, and the peer sees a connection that was cut.

inline explicit constexpr operator bool() const noexcept
Returns:

Whether this handle has a connection.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_submit(boost::urls::url target, const Fields &headers = {}, CompletionToken &&token = CompletionToken())

Submits a new request.

Submitting a request means initiating the sending of the request method, path and any headers. It does not mean that all or even any of those are actually transmitted immediately. The same is true if you start an asynchronous write operation on the request object. The request may just be queued for later transmission.

Use client::Request::async_get_response on the request to wait for the response.

A session may limit the number of requests in progress. When that limit is reached, this does not wait for a request to finish – the caller might be the one who has to finish it – but fails immediately with errors::would_block. HTTP/1.1 is such a protocol, with a limit of one: a request is in progress until it is complete, that is, until all of it, header and body, has been written. See the page on concurrent requests.

After a request could not be completed, an HTTP/1.1 session can’t send any more requests, and this fails with errors::connection_aborted.

Without a method, this is a POST. There is an overload for any other, see below.

Parameters:
  • target – The URL of the request.

  • headers – The header fields of the request.

  • token – The completion token, for a handler of the signature Submit.

Returns:

What token makes of the operation.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_submit(std::string_view method, boost::urls::url target, const Fields &headers = {}, CompletionToken &&token = CompletionToken())

Submits a new request with the given method.

All else is as with the overload above, and ends the same way: client::Request::async_write_eof() ends the request body, even if there is none, and client::Request::async_get_response() waits for the response.

GET and HEAD go out with “Content-Length: 0” unless headers already frames a body, so that HTTP/1.1 does not have to make them chunked, which some servers refuse. With any other method, a request without framing has a chunked body over HTTP/1.1.

This is the way to have the body of a response without keeping all of it, as async_get() does, when a peer may send more than the caller wants to read. What it gets to send is bounded by flow control, and over HTTP/1.1 by the socket buffers. Releasing the session ends the rest of it, see the programmer’s guide.

Parameters:
  • method – The method of the request.

  • target – The URL of the request.

  • headers – The header fields of the request.

  • token – The completion token, for a handler of the signature Submit.

Returns:

What token makes of the operation.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_get(boost::urls::url url, const Fields &headers = {}, CompletionToken &&token = CompletionToken())

Performs a whole GET request in one operation, and hands back the whole response.

This is the short way through what async_submit() spreads over four steps: it submits the request, ends its (empty) body, waits for the response and reads all of it into a client::Message – status, header fields and body as a std::string:

auto message = co_await session.async_get(url);
EXPECT_EQ(message.result_int(), 200);
EXPECT_EQ(message.body(), "Hello, World!");

The convenience is paid for with memory: the body is buffered in full, however large it turns out to be, as there is no way to look at it before it is complete. Anything that needs the body while it arrives, or to stop reading it at some point, a request body of its own, or a method other than GET still wants async_submit().

The request goes out with “Content-Length: 0” unless headers already frames a body, so that HTTP/1.1 does not have to make it chunked.

Any error along the way completes this operation: the ones async_submit() describes, errors::header_limit for a response header section over client::Config::max_header_size, and errors::partial_message for a body cut short. The message that comes with an error is empty, and says status::unknown rather than the 200 a default-constructed Beast response would claim. A response that says 404, on the other hand, is not an error – it is a response, and arrives as one.

Parameters:
  • url – The URL of the request.

  • headers – The header fields of the request.

  • token – The completion token, for a handler of the signature Get.

Returns:

What token makes of the operation.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_shutdown(CompletionToken &&token = CompletionToken())

Ends the connection in an orderly way, and completes once it is closed.

Unlike destroying the session, which is over as fast as possible, this tells the peer that nothing has been lost:

  • HTTP/2 sends a GOAWAY, and TLS over TCP sends its “close_notify” and waits for the peer’s. Then comes the FIN, and the socket is closed once the peer has sent its own. Each of those waits is limited to two seconds, so a peer that does not cooperate can only delay this. How the peer has ended its side is not reported: there is nothing to be done about it, and not every TLS library can tell.

  • HTTP/3 sends a CONNECTION_CLOSE, which has no answer to wait for.

It does not wait for anything in progress: a request or response that is not complete yet fails, right away, and so does anything else tried with this session afterwards.

The one error is errors::connection_aborted, for an end that could not be orderly: a write has been interrupted, leaving part of a TLS record behind, for which a “close_notify” would come too late. The connection is closed in that case as well.

Calling this again does nothing, and neither does destroying the session afterwards.

Parameters:

token – The completion token, for a handler of the signature SessionShutdown.

Returns:

What token makes of the operation.

IoTask<client::Request> submit(boost::urls::url target, Fields headers = {})

async_submit() as a coroutine: auto [ec, request] = co_await session.submit(url, headers);

Parameters:
  • target – The URL of the request.

  • headers – The header fields of the request.

Returns:

The error and the request.

IoTask<client::Request> submit(std::string_view method, boost::urls::url target, Fields headers = {})

async_submit() with a method, as a coroutine: auto [ec, request] = co_await session.submit("GET", url, headers);

Parameters:
  • method – The method of the request.

  • target – The URL of the request.

  • headers – The header fields of the request.

Returns:

The error and the request.

IoTask<client::Message> get(boost::urls::url url, Fields headers = {})

async_get() as a coroutine: auto [ec, message] = co_await session.get(url);

Parameters:
  • url – The URL of the request.

  • headers – The header fields of the request.

Returns:

The error and the whole response.

IoTask shutdown()

async_shutdown() as a coroutine: auto [ec] = co_await session.shutdown();

Returns:

The error.

Protocol protocol() const noexcept

The protocol this session speaks, which over TLS the server has a say in: see client::Config::protocol.

It tells how many requests the session can take at a time, as HTTP/1.1 takes only one, see async_submit().

Returns:

What it speaks.

Executor get_executor() const noexcept
Returns:

The executor the session runs on, and is to be used on.

Server

#include <anyhttp/server.hpp>
class Server

Listens on a port, TCP and UDP both, and serves every protocol on it: HTTP/1.1 and HTTP/2, with or without TLS, told apart by what the client sends first, and HTTP/3 over UDP.

It starts listening when it is made, and stops when it is destroyed, which also ends every connection it has. It can be moved but not copied.

Public Types

using executor_type = Executor

The executor type, for what looks for one.

Public Functions

Server(Executor executor, Config config)

Starts listening.

Parameters:
  • executor – What the server and its connections run on.

  • config – Where it listens, and how it serves.

Throws:

std::exception – for a certificate that can’t be read or a port that is taken.

executor_type get_executor() const noexcept
Returns:

The executor the server runs on.

void on_request(RequestHandler &&handler)

Sets what serves the requests from now on, each in a coroutine of its own on the executor of its connection.

Set it before the first request comes in, as requests go unserved without.

Parameters:

handler – What serves a request.

void on_quic(quic::Config config, quic::ConnectionHandler handler)

Serves plain QUIC streams (see quic::Stream) to clients that negotiate config.alpn, on the UDP port HTTP/3 is on: each such connection is handed to handler, on its own executor, and closed when the handler returns.

Registering an ALPN again replaces what was registered for it, from the next connection on.

Of config, the server uses the flow control limits, the error codes and the write mode; its idle timeout is Config::idle_timeout.

Parameters:
  • config – The protocol, and how to serve it.

  • handler – What each connection is handed to.

TcpEndpoint local_endpoint() const
Returns:

Where the server listens, with the actual port if Config::port was zero.

struct Config

What a Server is made with.

Public Members

std::string listen_address = "::"

The address to listen on, for TCP and for HTTP/3 alike.

uint16_t port = 8080

The port to listen on, for TCP and for HTTP/3 alike.

Zero has the kernel pick one, see Server::local_endpoint.

bool use_strand = false

Give every connection a strand of its own, for a server whose context runs on several threads.

The handler of a request then runs on the strand of its connection.

bool reuse_port = false

Bind the TCP acceptor and the HTTP/3 UDP socket with SO_REUSEPORT, so that several servers, each on an executor of its own, can listen on the same port and the kernel spreads the load over them: new TCP connections, and UDP datagrams by their address and port.

A QUIC connection whose client address changes (migration, NAT rebinding) lands on another server then, which does not know it. All of them have to be bound before traffic arrives – each one that joins reshuffles where the datagrams go.

int socket_send_buffer_size = 0

The kernel’s send and receive buffer, in bytes, for every TCP connection the server accepts; zero (the default) leaves the kernel’s autotuning alone, which grows them into the megabytes.

TCP only – HTTP/3 has its own flow control, and the UDP socket is shared by every connection on it.

This is a testing aid, not a tuning knob: pinning them small bounds how much a peer can write before it has to wait, which is the only way to make a write block at a predictable point over HTTP/1.1, where there is no flow control of its own. Small buffers cost throughput – 8 KiB of receive buffer makes the ‘PostRange’ tests very slow – so leave them at zero unless a test needs the back pressure.

int socket_receive_buffer_size = 0

See socket_send_buffer_size.

std::string tls_certificate_chain = "pki/out/server-chain.pem"

The server’s certificate chain, and with tls_private_key its key, as PEM files, for TLS over TCP and for HTTP/3.

The chain starts with the server certificate, followed by its intermediates. Both files are read when the Server is constructed, which fails if they can’t be, and not again: replacing them on disk takes effect with the next Server. Relative paths are resolved against the working directory.

The defaults are the test PKI generated by the ‘pki’ target (see cmake/pki.cmake) – good for development and the tests, and for nothing else.

std::string tls_private_key = "pki/out/server-key.pem"

The private key that goes with tls_certificate_chain, read at the same time.

size_t max_header_size = default_max_header_size

The largest header section of a request the server accepts, in bytes.

A request with more is answered with 431 (Request Header Fields Too Large) and never reaches the request handler. HTTP/1.1 counts the request line and header lines as received, and closes the connection after the 431 response. HTTP/2 and HTTP/3 count each field as its name and value plus 32 bytes (pseudo-headers included) and announce the limit to the client in their SETTINGS.

Fields beyond the limit are not stored, so this bounds the memory a request can take up with its headers. A single field is also limited by the protocol libraries: 64 KiB for HTTP/2 and HTTP/3, where a larger one fails the whole connection. So does an HTTP/2 header block that takes up more CONTINUATION frames than a header section of this size needs (but at least 8).

uint64_t max_concurrent_streams = 100

How many requests a client may have in progress at once on one connection, announced to it as SETTINGS_MAX_CONCURRENT_STREAMS over HTTP/2 and as the limit on bidirectional streams over HTTP/3.

A client that has reached it is refused the next request rather than kept waiting for a stream: Session::async_submit() fails with would_block, the way HTTP/1.1 does with its limit of one, see the page on concurrent requests.

HTTP/1.1 has no use for this: one request at a time is what the protocol gives it.

std::chrono::seconds alt_svc_max_age = std::chrono::hours{24}

How long a client may remember the HTTP/3 endpoint this server advertises in every response it sends over HTTP/1.1 and HTTP/2, as Alt-Svc: h3=":8080"; ma=86400 (RFC 7838).

A client that takes it up makes its next connection over QUIC – there is no in-band upgrade to HTTP/3, so this is the whole of it. HTTP/3 shares the endpoint the TCP acceptor is listening on, so what is advertised is the port and nothing else: the same host, over QUIC. Zero sends no “Alt-Svc” at all.

Advertising over cleartext HTTP is of no use to browsers and curl, which honour “Alt-Svc” for HTTPS origins only – the alternative has to be at least as secure as the origin.

std::chrono::nanoseconds idle_timeout = std::chrono::seconds{30}

HTTP/3 only: how long a QUIC connection may go without a packet from its peer before it is dropped.

This is the only way a peer that vanished without a CONNECTION_CLOSE – a killed client, a machine that went to sleep – is ever noticed, so it also bounds how long its session and streams stay around. 30s is what the ngtcp2 examples use.

double drop_rate_rx = 0.0

HTTP/3 only, testing aid: probability (0.0 … 1.0) with which an individual QUIC datagram is thrown away instead of being processed (rx) or actually sent (tx).

This exercises loss recovery – retransmits, PTO, ACK handling – without needing a lossy network. Dropping happens per QUIC packet, i.e. GRO-coalesced datagrams are dropped individually and TX GSO batching is bypassed while drop_rate_tx is non-zero.

double drop_rate_tx = 0.0

See drop_rate_rx.

bool disable_gro = false

HTTP/3 only, for benchmarking: turn off the kernel’s UDP offloads.

Without GRO, every received datagram costs a recvmsg() of its own; without GSO, every QUIC packet costs a sendto() of its own instead of a whole same-sized run going out in one sendmsg().

bool disable_gso = false

See disable_gro.

using anyhttp::server::RequestHandler = std::function<Task<void>(Request, Response)>

What serves a request, see Server::on_request.

class Request : public anyhttp::Reader

A request as a handler gets it, as a handle: its method, URL and header fields, and a Reader for its body.

It can be moved but not copied, and the handler may let go of it before it is done with the Response.

Public Functions

void reset() noexcept

Releases the request, as the destructor does.

std::string_view method() const noexcept
Returns:

The request method, as it arrived: “GET”, “POST”, …

boost::url_view url() const
Returns:

The requested URL: scheme, authority (host and port) and path.

const Fields &fields() const
Returns:

The request header fields, without HTTP/2 and HTTP/3 pseudo-headers.

template<typename T>
inline std::optional<T> get_param_as(std::string_view name) const

Looks up a query parameter and converts its value to T.

Returns std::nullopt if the parameter is missing, has no value at all, or if its value does not convert to T – the latter is logged as a warning. Use value_or() for a default:

auto delay = request.get_param_as<size_t>("delay").value_or(0);
Template Parameters:

T – What to convert the value to.

Parameters:

name – The name of the parameter.

Returns:

The value, if there is one.

class Response : public anyhttp::Writer

The response to a request as a handler gets it, as a handle: async_submit for its header, and a Writer for its body.

It can be moved but not copied.

Public Functions

void reset() noexcept

Releases the response, as the destructor does.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_submit(unsigned int status_code, const Fields &headers, CompletionToken &&token = CompletionToken())

Sends the response header, which opens the body for writing.

Parameters:
  • status_code – The status code of the response.

  • headers – The header fields of the response.

  • token – The completion token, for a handler of the signature Status.

Returns:

What token makes of the operation.

IoTask submit(unsigned int status_code, Fields headers)

async_submit() as a coroutine: auto [ec] = co_await response.submit(200, {});

Parameters:
  • status_code – The status code of the response.

  • headers – The header fields of the response.

Returns:

The error.

Reading and writing

#include <anyhttp/reader.hpp>
#include <anyhttp/writer.hpp>
class Reader

The reading half of a message, as a handle: what a server::Request and a client::Response have in common, and all that an operation which only consumes a body – drain() – needs to see of either.

This is the PIMPL handle itself, not a view onto one: it owns its share of the implementation, and server::Request and client::Response derive from it rather than holding a pointer of their own. They add no data members, so moving one into a Reader (“slicing”) is a sound and useful thing to do – it hands off the reading half with its ownership intact, to be drained by a coroutine that has no business with the rest of the message. That is also why the destructor is not virtual: these are handles, never owned through a base pointer.

Subclassed by anyhttp::client::Response, anyhttp::quic::Stream, anyhttp::server::Request

Public Types

using executor_type = Executor

The executor type, for what looks for one.

Public Functions

void reset() noexcept

Releases the implementation, as the destructor does. Reading afterwards fails.

inline explicit constexpr operator bool() const noexcept
Returns:

Whether this handle has an implementation: not after reset(), or being moved from.

executor_type get_executor() const noexcept

Without one, what is started on an empty handle with a completion token cannot complete the way ASIO promises: it fails with errors::bad_descriptor from inside the call, and a token that needs an executor of the operation, as cancel_after does for its timer, throws bad_executor instead. Awaited, it just fails.

Returns:

The executor of the session this message belongs to, or an empty one after reset().

std::string log_prefix() const
Returns:

The [proto:address:port.stream] tag the library’s log lines carry for this message, without the brackets, or an empty string after reset().

std::optional<size_t> content_length() const noexcept
Returns:

What the incoming message announced as its body length, if it announced one.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_read_some(boost::asio::mutable_buffer buffer, CompletionToken &&token = CompletionToken())

Reads a part of the incoming body.

The end of the body is reported as errors::eof with zero bytes, as the runtime does everywhere else, and so is every read after it. A body cut short by a reset stream or a lost connection completes with errors::partial_message instead. See the page on the end of a body.

Parameters:
  • buffer – Where to put what has been read, which has to stay valid until the operation completes.

  • token – The completion token, for a handler of the signature ReadSome.

Returns:

What token makes of the operation.

template<typename Buffers, typename CompletionToken = DefaultCompletionToken>
inline auto async_read_some(const Buffers &buffers, CompletionToken &&token = CompletionToken())

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

When given an actual sequence of buffers, this fills only the first non-empty one.

Parameters:
  • buffers – Where to put what has been read, which has to stay valid until the operation completes.

  • token – The completion token, for a handler of the signature ReadSome.

Returns:

What token makes of the operation.

IoTask<size_t> read_some(boost::asio::mutable_buffer buffer)

Reads a part of the incoming body: auto [ec, n] = co_await reader.read_some(buffer);

Parameters:

buffer – Where to put what has been read.

Returns:

The error, and how many bytes have been read.

template<typename Buffers>
inline IoTask<size_t> read_some(const Buffers &buffers)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

As with async_read_some(), this fills only the first non-empty buffer.

Parameters:

buffers – Where to put what has been read.

Returns:

The error, and how many bytes have been read.

class Writer

The writing half of a message, as a handle: what a server::Response and a client::Request have in common, and all that an operation which only produces a body – send() – needs to see of either.

The counterpart of Reader, and owned the same way: server::Response and client::Request derive from it and add no data members of their own, so moving one into a Writer hands off the writing half with its ownership intact. See Reader for why the destructor is not virtual.

Subclassed by anyhttp::client::Request, anyhttp::quic::Stream, anyhttp::server::Response

Public Types

using executor_type = Executor

The executor type, for what looks for one.

Public Functions

void reset() noexcept

Releases the implementation, as the destructor does. Writing afterwards fails.

inline explicit constexpr operator bool() const noexcept
Returns:

Whether this handle has an implementation: not after reset(), or being moved from.

executor_type get_executor() const noexcept
Returns:

The executor of the session this message belongs to, or an empty one after reset(), with what that means for an operation started then, see Reader::get_executor().

std::string log_prefix() const
Returns:

The [proto:address:port.stream] tag the library’s log lines carry for this message, without the brackets, or an empty string after reset().

void content_length(std::optional<size_t> content_length)

Announces the length of the outgoing body, before its header is submitted.

Parameters:

content_length – The length, or nothing to take back what has been announced.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_write(boost::asio::const_buffer buffer, CompletionToken &&token = CompletionToken())

Writes buffer as part of the outgoing body, which stays open for more.

An empty buffer writes nothing and completes immediately – use async_write_eof() to end the body.

Parameters:
  • buffer – What to write, which has to stay valid until the operation completes.

  • token – The completion token, for a handler of the signature Write.

Returns:

What token makes of the operation.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_write_eof(boost::asio::const_buffer buffer, CompletionToken &&token = CompletionToken())

Writes buffer as the last part of the outgoing body and ends it.

Both go out together, so ending a body that has a tail of data left costs no more than writing that tail: no second, empty write and no extra round trip through the protocol stack. Re-ending an already-ended body with an empty buffer completes immediately and changes nothing; with data attached it completes with errc::broken_pipe, just as writing that data would – there is no body left for it to belong to.

Parameters:
  • buffer – What to write, which has to stay valid until the operation completes.

  • token – The completion token, for a handler of the signature Write.

Returns:

What token makes of the operation.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_write_eof(CompletionToken &&token = CompletionToken())

Ends the outgoing body without writing anything more.

Parameters:

token – The completion token, for a handler of the signature Write.

Returns:

What token makes of the operation.

IoTask<size_t> write(boost::asio::const_buffer buffer)

Writes buffer as part of the body: auto [ec, n] = co_await writer.write(buffer);

Parameters:

buffer – What to write.

Returns:

The error, and how many bytes of buffer the writer has taken, see Write.

IoTask<size_t> write_eof(boost::asio::const_buffer buffer = {})

Writes buffer, if any, and ends the body: auto [ec, n] = co_await writer.write_eof();

Parameters:

buffer – What to write.

Returns:

The error, and how many bytes of buffer the writer has taken, see Write.

Headers and protocols

#include <anyhttp/common.hpp>
enum class anyhttp::Protocol

What a connection speaks.

Values:

enumerator h1

HTTP/1.1, over TCP, with or without TLS.

enumerator h2

HTTP/2, over TCP, with or without TLS.

enumerator h3

HTTP/3, over QUIC.

std::string anyhttp::to_string(Protocol protocol)

The name of a protocol, as it appears in the logs.

Parameters:

protocol – The protocol.

Returns:

“HTTP11”, “HTTP2” or “HTTP3”.

using anyhttp::Fields = boost::beast::http::fields

The header fields of a message, in both directions: Beast’s container for them.

inline Fields anyhttp::fields(std::initializer_list<std::pair<std::string_view, FieldValue>> headers)

Beast’s fields have no initializer-list constructor, so building a small, fixed set of headers takes a statement per header.

This lets it be spelled inline:

fields({{"Content-Length", body.size()}, {"Content-Type", "text/plain"}})
Parameters:

headers – The names and values of the header fields.

Returns:

The header fields, which own their copy of all of that.

class FieldValue

A header value as passed to fields(): either something string-like, or anything std::format can turn into a string, so sizes and counts need no conversion at the call site.

String-like values are only referenced, never copied: a FieldValue lives just long enough for fields() to hand the bytes to Beast, which copies them. Only formatted values need storage.

Public Functions

template<typename T>
inline FieldValue(const T &value)

Takes value by reference if it is string-like, and formats it otherwise.

Parameters:

value – The value of the header field.

inline operator std::string_view() const noexcept
Returns:

The value, which lives as long as the FieldValue and what it refers to.

constexpr size_t anyhttp::default_max_header_size = 64 * 1024

Default for server::Config::max_header_size and client::Config::max_header_size.

constexpr size_t anyhttp::header_field_size(std::string_view name, std::string_view value) noexcept

What a received header field counts against the limit on the size of a header section: its name and value plus 32 bytes of overhead, as for SETTINGS_MAX_HEADER_LIST_SIZE in HTTP/2 (RFC 9113, section 6.5.2) and SETTINGS_MAX_FIELD_SECTION_SIZE in HTTP/3 (RFC 9114, section 4.2.2).

Parameters:
  • name – The name of the field.

  • value – The value of the field.

Returns:

The size it counts as, in bytes.

Plain QUIC streams

#include <anyhttp/quic.hpp>
IoTask<Connection> anyhttp::quic::connect(Executor executor, std::string host, std::string port, Config config)

Connects to host : port, negotiating config.alpn, and returns the connection.

The server’s certificate has to be valid for host.

Parameters:
  • executor – What the connection runs on.

  • host – The name or address of the server.

  • port – The port of the server.

  • config – The protocol, and how to speak it.

Returns:

The error and the connection.

template<typename CompletionToken = DefaultCompletionToken>
auto anyhttp::quic::async_connect(Executor executor, std::string host, std::string port, Config config, CompletionToken &&token = CompletionToken())

connect() with a completion token.

Parameters:
  • executor – What the connection runs on.

  • host – The name or address of the server.

  • port – The port of the server.

  • config – The protocol, and how to speak it.

  • token – The completion token, for a handler of the signature Connect.

Returns:

What token makes of the operation.

struct Config

What plain QUIC connections are made with, on either side.

Public Members

std::string alpn

The application protocol, as negotiated by ALPN: what the client offers and what the server registers it under.

Required, at most 255 bytes.

std::string tls_ca_file

Client: the CA certificates to verify the server with, see client::Config::tls_ca_file.

std::chrono::nanoseconds idle_timeout = std::chrono::seconds{30}

Client: how long the connection may be silent.

The server’s is server::Config::idle_timeout.

uint64_t max_stream_data = 256 * 1024

Flow control, as announced to the peer: how much it may send on one stream, and on all of them together, before we read, and how many streams it may have open at once.

A stream’s window opens up as its Reader reads, so a stream nobody reads stops its sender at max_stream_data.

uint64_t max_data = 1024 * 1024

How much the peer may send on all streams together, see max_stream_data.

uint64_t max_streams = 100

How many streams the peer may have open at once, see max_stream_data.

uint64_t reset_error_code = 0

The application error codes for aborts the library makes on the user’s behalf: RESET_STREAM when a Writer goes away before write_eof() (or, with zero_copy, a write with unacknowledged bytes is cancelled), STOP_SENDING when a Reader goes away before the end.

Meaning is up to the protocol.

uint64_t stop_sending_error_code = 0

The application error code for STOP_SENDING, see reset_error_code.

bool zero_copy = false

How writes reach the stack.

By default they are copied, in chunks of 16 KiB, and complete once handed over. With zero_copy, the stack reads straight from the caller’s buffer and a write completes only when the peer has acknowledged all of it – which saves the copy but costs a round trip per write, and cancelling such a write resets the stream.

class Connection

One QUIC connection carrying plain streams, as a handle.

Dropping it (or reset()) closes the connection, failing whatever streams are still open, as close() does.

All of it is to be used on the connection’s executor: on the server, that is where the connection handler runs, on the client, the executor passed to connect().

Public Types

using executor_type = Executor

The executor type, for what looks for one.

Public Functions

Connection() noexcept

A handle without a connection, as the one that comes with an error.

inline explicit operator bool() const noexcept
Returns:

Whether this handle has a connection.

executor_type get_executor() const noexcept
Returns:

The executor of the connection, which all of it is to be used on, or an empty one for a handle without a connection, see Reader::get_executor().

std::string log_prefix() const
Returns:

The [quic:address:port] tag the library’s log lines carry for this connection.

results<Stream> open_stream()

Opens a bidirectional stream.

The peer learns of it with the first thing written.

Opening a stream never waits: at the limit of concurrent streams the peer allows, this fails with errors::would_block – the caller may be the one who has to finish a stream first. On a connection that is over, it fails with errors::connection_aborted.

Returns:

The error and the stream.

template<typename CompletionToken = DefaultCompletionToken>
inline auto async_accept_stream(CompletionToken &&token = CompletionToken())

Waits for the next stream the peer opens.

Fails with errors::connection_aborted once the connection is over.

Parameters:

token – The completion token, for a handler of the signature AcceptStream.

Returns:

What token makes of the operation.

IoTask<Stream> accept_stream()

async_accept_stream() as a coroutine: auto [ec, stream] = co_await connection.accept_stream();

Returns:

The error and the stream.

void close(uint64_t code = 0) noexcept

Closes the connection: CONNECTION_CLOSE with the application error code.

What is still open fails with errc::connection_reset.

Parameters:

code – The application error code.

void reset() noexcept

Releases the connection, closing it, as the destructor does.

class Stream : public anyhttp::Reader, public anyhttp::Writer

A bidirectional QUIC stream: a Reader for what the peer sends and a Writer for what we send, independent of each other.

The two halves can be used concurrently (one read and one write at a time), and handed off on their own by moving them into a Reader or Writer – dropping one half ends just that direction. Dropping a Writer before write_eof() resets the stream’s sending side, dropping a Reader before errors::eof stops the peer’s.

Public Types

using executor_type = Executor

The executor type, for what looks for one.

Public Functions

Stream() noexcept

A handle without a stream, as the one that comes with an error.

executor_type get_executor() const noexcept
Returns:

The executor of the connection, or an empty one after reset(), with what that means for an operation started then, see Reader::get_executor().

std::string log_prefix() const
Returns:

The [quic:address:port.stream] tag the library’s log lines carry for this stream.

inline explicit operator bool() const noexcept
Returns:

Whether either half is still there.

void reset() noexcept

Releases both halves, as the destructor does.

int64_t id() const noexcept
Returns:

The QUIC stream ID, or -1 after reset().

void reset_stream(uint64_t code)

Aborts the sending side: RESET_STREAM with code.

A write in progress completes with errors::canceled, and every write from then on with errc::connection_reset; the peer’s reads report errors::partial_message.

Parameters:

code – The application error code.

void stop_sending(uint64_t code)

Asks the peer to stop sending: STOP_SENDING with code.

What has not been read is dropped, and reads report errors::partial_message from then on.

Parameters:

code – The application error code.

using anyhttp::quic::ConnectionHandler = std::function<Task<void>(Connection connection)>

What the server hands each connection to, see Server::on_quic().

The connection is closed when it returns.

Alternative services

#include <anyhttp/alt_svc.hpp>
AltSvc anyhttp::parse_alt_svc(std::string_view value)

Parses an “Alt-Svc” field value, which is a list of alternatives, or the single token “clear”.

Nothing in here is fatal: an alternative that does not parse is skipped, and so is one whose parameters are malformed – a client is free to ignore what it does not understand (RFC 7838, section 3), and the alternatives it can read are still worth having. The result is empty for a field value that yields nothing at all.

Parameters:

value – The field value.

Returns:

What could be made of it.

struct AltSvc

The parsed value of an “Alt-Svc” header field or an HTTP/2 ALTSVC frame.

Public Functions

const AltService *find(std::string_view protocol) const noexcept
Parameters:

protocol – The ALPN protocol name to look for.

Returns:

The first alternative offering protocol, or nullptr if there is none.

Public Members

bool clear = false

The field value was “clear”: everything known about the origin is to be forgotten.

std::vector<AltService> services

The advertised alternatives, in the order they were given, which is the order of the server’s preference (RFC 7838, section 3).

struct AltService

One advertised alternative: which protocol to speak, where to reach it, and for how long that is worth remembering.

Public Members

std::string protocol

ALPN protocol name, percent-decoding undone: “h3”, “h2”, …

std::string host

empty if the alternative is on the host of the origin itself (an IPv6 literal comes without its brackets)

std::string port

decimal, never empty – an alt-authority without a port is invalid

std::chrono::seconds max_age = {86400}

The “ma” parameter: how long this may be used without being advertised again.

bool persist = false

The “persist=1” parameter: the alternative survives a change of network.

The runtime

#include <anyhttp/runtime.hpp>

What the interface is written in. The names are the same on both I/O runtimes, and what each stands for is the runtime’s own, given here for both, as in the table.

Types

template<typename T = void>
using anyhttp::Task

A coroutine, as the runtime runs it.

ASIO:

boost::asio::awaitable<T>

COROSIO:

boost::capy::task<T>

template<typename ...Ts>
using anyhttp::IoTask

An operation, as the coroutine that yields its results: IoTask<size_t> is Task<results<size_t>>, IoTask<> one that reports only its error.

ASIO:

boost::asio::awaitable<std::tuple<error_code, Ts...>>

COROSIO:

boost::capy::task<boost::capy::results<Ts...>>

template<typename ...Ts>
using anyhttp::results

What an operation yields: its error first, then what it has to report besides, which auto [ec, n] = co_await reader.read_some(buffer); takes apart. Whether that is meaningful when there is an error is up to the operation.

ASIO:

std::tuple<error_code, Ts...>

COROSIO:

boost::capy::results<Ts...>, which is std::tuple<std::error_code, Ts...>

type anyhttp::error_code

What every operation reports its outcome with.

ASIO:

boost::system::error_code

COROSIO:

std::error_code

type anyhttp::system_error

What the runtime throws an error_code as.

ASIO:

boost::system::system_error

COROSIO:

std::system_error

type anyhttp::Executor

What sessions, streams and their readers and writers run on.

ASIO:

boost::asio::any_io_executor

COROSIO:

boost::capy::any_executor

type anyhttp::IpAddress

The runtime’s own IP address, which the API speaks, too.

ASIO:

boost::asio::ip::address

COROSIO:

boost::corosio::ip_address

type anyhttp::TcpEndpoint

The runtime’s own TCP endpoint.

ASIO:

boost::asio::ip::tcp::endpoint

COROSIO:

boost::corosio::endpoint

type anyhttp::UdpEndpoint

The runtime’s own UDP endpoint.

ASIO:

boost::asio::ip::udp::endpoint

COROSIO:

boost::corosio::endpoint, the same type as TcpEndpoint

using anyhttp::DefaultCompletionToken = boost::asio::default_completion_token_t<Executor>

What an operation of a runtime that has completion tokens takes when it is given none.

inline void anyhttp::throw_error(const error_code &ec)

Throws ec, for code that reports errors as exceptions, as the runtime’s own does.

Parameters:

ec – The error.

Errors

The codes anyhttp reports where the two runtimes spell them differently. Anything else compares against errc::, which is boost::system::errc on ASIO and std::errc on COROSIO.

const error_code anyhttp::errors::eof

The end of an incoming body.

ASIO:

boost::asio::error::eof

COROSIO:

capy::error::eof

const error_code anyhttp::errors::partial_message

An incoming body that ended before it was complete: a reset stream, a lost connection.

ASIO:

beast::http::error::partial_message

COROSIO:

capy::error::stream_truncated

const error_code anyhttp::errors::canceled

An operation that was cancelled before it completed.

ASIO:

errc::operation_canceled

COROSIO:

capy::error::canceled

const error_code anyhttp::errors::would_block

HTTP/1.1: a request or response that has to wait for an earlier one, see Session.

ASIO:

boost::asio::error::would_block

COROSIO:

errc::operation_would_block

const error_code anyhttp::errors::connection_aborted

A connection that can not carry any more requests or responses.

ASIO:

boost::asio::error::connection_aborted

COROSIO:

errc::connection_aborted

const error_code anyhttp::errors::bad_descriptor

An operation on a Reader or Writer after its implementation has been released.

ASIO:

boost::asio::error::bad_descriptor

COROSIO:

errc::bad_file_descriptor

const error_code anyhttp::errors::already_started

An operation that may be in progress only once at a time.

ASIO:

boost::asio::error::already_started

COROSIO:

errc::connection_already_in_progress

const error_code anyhttp::errors::header_limit

A header section larger than Config::max_header_size.

ASIO:

beast::http::error::header_limit

COROSIO:

beast::http::error::header_limit

Coroutines

inline void anyhttp::launch(const Executor &executor, Task<void> task)

Starts task on executor, detached: nobody waits for it, and what it throws is dropped.

Parameters:
  • executor – What to run it on.

  • task – What to run.

template<typename F>
inline void anyhttp::launch(const Executor &executor, Task<void> task, F &&on_done)

Starts task on executor and calls on_done with what it threw, if anything, when it is done: void(const std::exception_ptr&).

Parameters:
  • executor – What to run it on.

  • task – What to run.

  • on_done – What to call when it is done.

inline Task<error_code> anyhttp::delay(std::chrono::steady_clock::duration duration)

Waits for duration to pass.

Cancelling the wait completes it early, with the error code the runtime’s timers report for that.

Parameters:

duration – How long to wait.

Returns:

The error, if the wait has been cancelled.

template<typename A, typename B>
auto anyhttp::when_both(Task<A> a, Task<B> b)

Runs a and b concurrently, until both are done, and yields what they return: nothing, the one value, or both as a tuple.

If one of them throws, the other is cancelled, and the exception is rethrown once both are done.

Parameters:
  • a – One of the two.

  • b – The other one.

Returns:

What to await.

template<typename A, typename B>
auto anyhttp::when_either(Task<A> a, Task<B> b)

Runs a and b concurrently, until one of them succeeds, which cancels the other, and yields what it returned as a variant, with std::monostate for nothing.

One that throws does not succeed: only when both have thrown is one of the exceptions rethrown (wrapped in ASIO’s multiple_exceptions). Waits for the other one to finish all the same.

Parameters:
  • a – One of the two.

  • b – The other one.

Returns:

What to await.

inline auto anyhttp::reset_cancellation()

Lets a coroutine that has been cancelled go on awaiting, to clean up after the cancellation: ASIO throws from everything it awaits after that otherwise – a nested coroutine included, which is why this has to be awaited in the cancelled coroutine itself.

In COROSIO, there is nothing to reset, see shielded().

Returns:

What to await.

template<typename T>
Task<T> anyhttp::shielded(Task<T> task)

Awaits task although the caller has been cancelled already, once reset_cancellation() has been awaited.

With ASIO, that is all it takes. A stop request in COROSIO can not be taken back, and there, the task runs with a stop token of its own instead.

Parameters:

task – What to await.

Returns:

What to await instead.