Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ and `/usr/local`. Keep `build/` on clang: clangd reads its `compile_commands.jso
`build-corosio/` is the COROSIO I/O runtime (`-DANYHTTP_API=COROSIO -DTLS_LIBRARY=OpenSSL`, see
[docs/corosio-port-plan.md](docs/corosio-port-plan.md)); capy and corosio are FetchContent'd at
pinned SHAs. Both runtimes build the same library sources and test files; what belongs to one
runtime only is guarded with `ANYHTTP_ASIO` or `ANYHTTP_COROSIO` (exactly one of them is 1). So is the
`server` program; the `client` program is ASIO-only so far. `build-corosio-asan/` is its ASAN
runtime only is guarded with `ANYHTTP_ASIO` or `ANYHTTP_COROSIO` (exactly one of them is 1). So are the
`server` and `client` programs. `build-corosio-asan/` is its ASAN
tree, `build-corosio-release/` its Release tree (benchmark COROSIO with it) and
`build-corosio-gcc/` its GCC one -- capy and corosio do build against libstdc++, but GCC is
pickier about coroutine expressions than clang, so keep a `co_await` out of any larger expression.
Expand Down Expand Up @@ -142,6 +142,17 @@ runtime only is in `asio/` or `corosio/`, again in both; the library compiles th
of its runtime and not the other. `src/apps/` has the `server` and `client` programs, `src/issues/` standalone
reproducers of upstream bugs. Everything else at the top level is generic.

Each of these directories is a namespace of the same name: `anyhttp::h1`, `h2`, `h2v2`, `quic`, `h3`,
and what a backend header offers generic code is in it too (`h1::make_server_session()`,
`quic::make_quic_server()`, `h3::async_connect_http3()`). The one exception is `h2v2/`, which
defines the factories of `h2/backend.hpp` in `anyhttp::h2`, where they are declared, so that nothing
above that header can tell the two backends apart. The runtimes are *inline* namespaces,
`anyhttp::asio` and `anyhttp::corosio`: code names their vocabulary as anyhttp's (`Task`,
`io::read_some()`), but the symbols carry the runtime, so objects of the two cannot link together.
That takes the names `asio` and `corosio` inside anyhttp, so spell Boost's in full,
`boost::asio::` and `boost::corosio::`. An alias of either name is ambiguous anywhere
`using namespace anyhttp` reaches, and the tests and examples have it.

What gets installed is the public headers, the ones reachable from `client.hpp`, `server.hpp`,
`quic.hpp`, `session.hpp` and the like; `cmake/install.cmake` leaves out the protocol directories,
`net.hpp` and `*_impl.hpp`. A public header that starts including one of those needs the list changed
Expand Down Expand Up @@ -252,6 +263,14 @@ constructed, for TCP and QUIC alike, and keeps serving the old ones. curl then f
`verify result: 20`; the HTTP/3 client (which reads its CA file on every connect) with "unable to
get local issuer certificate".

**Never reopen a runtime's namespace from generic code.** `namespace anyhttp::io {` (or `errors`,
`detail`) outside `asio/` and `corosio/` ought to extend the runtime's `anyhttp::asio::io`, and does
with GCC, but clang makes a namespace of its own: the declarations in it never meet their
definitions, which shows only at link time. `anyhttp/net.hpp` and `src/net.cpp` open
`anyhttp::inline asio::io` or `anyhttp::inline corosio::io` by `#if`. A function that a generic
header would declare and each runtime would define (like `normalize(IpAddress)`) is declared in the
runtime headers instead.

**One nghttp2 per process.** Version 2 installs as its own package (`<nghttp2v2/nghttp2.h>`,
`libnghttp2v2`), but still exports nine of version 1's symbol names -- `nghttp2_strerror`,
`nghttp2_http2_strerror`, `nghttp2_check_header_name`/`_value`, `nghttp2_check_authority` and the
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ Task<void> echo(server::Request request, server::Response response)
std::array<uint8_t, 64 * 1024> buffer;
for (;;)
{
auto [ec, n] = co_await request.read_some(asio::buffer(buffer));
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(asio::buffer(buffer, n)); write_ec)
if (auto [write_ec] = co_await response.write(boost::asio::buffer(buffer, n)); write_ec)
co_return;
}

Expand Down
5 changes: 1 addition & 4 deletions cmake/install.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ include(CMakePackageConfigHelpers)
# --------------------------------------------------------------------------------------------------

if (ANYHTTP_BUILD_APPS)
set(anyhttp_programs server)
if (ANYHTTP_ASIO)
list(APPEND anyhttp_programs client)
endif()
set(anyhttp_programs server client)

set_target_properties(${anyhttp_programs} PROPERTIES
INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}")
Expand Down
4 changes: 4 additions & 0 deletions docs/Doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ PREDEFINED = ANYHTTP_ASIO=1 \
"BOOST_ASIO_COMPLETION_TOKEN_FOR(x)=typename"
SEARCH_INCLUDES = NO

# The runtime's vocabulary is in the inline namespace anyhttp::asio, or anyhttp::corosio, so that
# the two runtimes do not link together. The API names it as anyhttp's, and so does the reference.
INPUT_FILTER = "sed -e 's/^namespace anyhttp::inline asio$/namespace anyhttp/'"

# what is not documented is not finished
QUIET = YES
WARN_IF_UNDOCUMENTED = YES
Expand Down
3 changes: 2 additions & 1 deletion docs/corosio-port-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ the h3 write path also run under ASAN. From step 5 on, COROSIO's own tests must
7. **README**: the two I/O runtimes, the per-mode error table, the constraints (OpenSSL). The error table includes the resolver: corosio reports a host that does not resolve
as `no_such_device_or_address` (`EAI_NONAME` mapped onto a generic code), ASIO as
`netdb_errors::host_not_found`.
8. Later: threads in COROSIO; `client_main` for COROSIO; upstream issues for corosio
8. Later: threads in COROSIO; `client_main` for COROSIO (*done 2026-11-06*, on `Task`,
`launch()` and `when_both()` like the server); upstream issues for corosio
(`openssl_stream` on AWS-LC, the read on an error event, the reactor's use-after-free with
threads, and a TLS read that flushes what an earlier write left behind and fails with what that
fails with: over TLS, a response that has already arrived can not be read once a write has
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/hello_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ static Task<void> hello(server::Request request, server::Response response)
if (auto [ec] = co_await response.submit(200, fields({{"Content-Length", body.size()}})); ec)
co_return;

std::ignore = co_await response.write_eof(asio::buffer(body));
std::ignore = co_await response.write_eof(boost::asio::buffer(body));
}

int main(int argc, char* argv[])
Expand Down
46 changes: 26 additions & 20 deletions docs/io-runtimes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ Both build the same library and protocol sources. Only a thin runtime layer diff
``anyhttp/runtime.hpp`` and ``anyhttp/net.hpp`` with what they include from ``asio/`` or ``corosio/``.
:src:`docs/corosio-port-plan.md` has how that came about.

That layer is the inline namespace ``anyhttp::asio`` or ``anyhttp::corosio``. Its names are
anyhttp's, :cpp:type:`Task` or :cpp:var:`errors::eof`, and a library of one runtime does not link
with code compiled for the other. Because ``asio`` and ``corosio`` are names in ``anyhttp``, code
that says ``using namespace anyhttp`` writes Boost's namespaces in full, as in
``boost::asio::buffer()``: a namespace alias ``asio`` of its own would be ambiguous.

.. _io-runtimes-vocabulary:

What the names stand for
Expand All @@ -34,23 +40,23 @@ What the names stand for
- ``-DANYHTTP_API=ASIO`` (default)
- ``-DANYHTTP_API=COROSIO -DTLS_LIBRARY=OpenSSL``
* - :cpp:type:`Task\<T\> <Task>`
- ``asio::awaitable<T>``
- ``capy::task<T>``
- ``boost::asio::awaitable<T>``
- ``boost::capy::task<T>``
* - :cpp:type:`Executor`
- ``asio::any_io_executor``
- ``capy::any_executor``
- ``boost::asio::any_io_executor``
- ``boost::capy::any_executor``
* - :cpp:type:`error_code`
- ``boost::system::error_code``
- ``std::error_code``
* - :cpp:type:`results\<Ts...\> <results>`
- ``std::tuple<error_code, Ts...>``
- ``capy::results<Ts...>``
- ``boost::capy::results<Ts...>``
* - :cpp:type:`IoTask\<Ts...\> <IoTask>`
- ``asio::awaitable<results<Ts...>>``
- ``capy::task<results<Ts...>>``
- ``boost::asio::awaitable<results<Ts...>>``
- ``boost::capy::task<results<Ts...>>``
* - :cpp:type:`TcpEndpoint`, :cpp:type:`UdpEndpoint`
- ``asio::ip::tcp::endpoint``, ``asio::ip::udp::endpoint``
- ``corosio::endpoint``
- ``boost::asio::ip::tcp::endpoint``, ``boost::asio::ip::udp::endpoint``
- ``boost::corosio::endpoint``
* - Operations
- ``async_x(..., token)`` and ``x()``
- ``x()``
Expand Down Expand Up @@ -82,13 +88,13 @@ example, with the types of the library:

// ASIO: the operation, which completes a handler of the signature void(error_code, size_t) ...
template <typename CompletionToken = DefaultCompletionToken>
auto async_read_some(asio::mutable_buffer buffer, CompletionToken&& token = CompletionToken());
auto async_read_some(boost::asio::mutable_buffer buffer, CompletionToken&& token = CompletionToken());

// ... and the coroutine around it.
IoTask<size_t> read_some(asio::mutable_buffer buffer);
IoTask<size_t> read_some(boost::asio::mutable_buffer buffer);

// COROSIO: the coroutine.
IoTask<size_t> read_some(asio::mutable_buffer buffer);
IoTask<size_t> read_some(boost::asio::mutable_buffer buffer);

The coroutine has the same declaration on both, so code that uses only coroutines builds with
either, which is what the guide does. It yields :cpp:type:`results`, a tuple with the error first,
Expand Down Expand Up @@ -116,8 +122,8 @@ On ASIO, the implementation of each operation is hidden behind `any_completion_h
<https://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/any_completion_handler.html>`__,
so that it can be compiled separately. On COROSIO, they are coroutines of `Capy
<https://github.com/cppalliance/capy>`__, Corosio's base, cancelled through the awaiting chain's
``std::stop_token``. :cpp:class:`Reader` and :cpp:class:`Writer` model ``capy::ReadStream`` and
``capy::WriteStream``.
``std::stop_token``. :cpp:class:`Reader` and :cpp:class:`Writer` model ``boost::capy::ReadStream`` and
``boost::capy::WriteStream``.

.. _io-runtimes-errors:

Expand All @@ -134,19 +140,19 @@ Where the two runtimes spell an error differently, the namespace ``anyhttp::erro
- ASIO
- COROSIO
* - :cpp:var:`errors::eof`
- ``asio::error::eof``
- ``capy::error::eof``
- ``boost::asio::error::eof``
- ``boost::capy::error::eof``
* - :cpp:var:`errors::partial_message`
- ``beast::http::error::partial_message``
- ``capy::error::stream_truncated``
- ``boost::capy::error::stream_truncated``
* - :cpp:var:`errors::canceled`
- ``errc::operation_canceled``
- ``capy::error::canceled``
- ``boost::capy::error::canceled``
* - :cpp:var:`errors::would_block`
- ``asio::error::would_block``
- ``boost::asio::error::would_block``
- ``errc::operation_would_block``
* - :cpp:var:`errors::connection_aborted`
- ``asio::error::connection_aborted``
- ``boost::asio::error::connection_aborted``
- ``errc::connection_aborted``
* - :cpp:var:`errors::header_limit`
- ``beast::http::error::header_limit``
Expand Down
26 changes: 13 additions & 13 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,16 @@ Types

A coroutine, as the runtime runs it.

:ASIO: ``asio::awaitable<T>``
:COROSIO: ``capy::task<T>``
:ASIO: ``boost::asio::awaitable<T>``
:COROSIO: ``boost::capy::task<T>``

.. cpp:type:: template <typename... Ts> anyhttp::IoTask

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

:ASIO: ``asio::awaitable<std::tuple<error_code, Ts...>>``
:COROSIO: ``capy::task<capy::results<Ts...>>``
:ASIO: ``boost::asio::awaitable<std::tuple<error_code, Ts...>>``
:COROSIO: ``boost::capy::task<boost::capy::results<Ts...>>``

.. cpp:type:: template <typename... Ts> anyhttp::results

Expand All @@ -169,7 +169,7 @@ Types
when there is an error is up to the operation.

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

.. cpp:type:: anyhttp::error_code

Expand All @@ -189,29 +189,29 @@ Types

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

:ASIO: ``asio::any_io_executor``
:COROSIO: ``capy::any_executor``
:ASIO: ``boost::asio::any_io_executor``
:COROSIO: ``boost::capy::any_executor``

.. cpp:type:: anyhttp::IpAddress

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

:ASIO: ``asio::ip::address``
:COROSIO: ``corosio::ip_address``
:ASIO: ``boost::asio::ip::address``
:COROSIO: ``boost::corosio::ip_address``

.. cpp:type:: anyhttp::TcpEndpoint

The runtime's own TCP endpoint.

:ASIO: ``asio::ip::tcp::endpoint``
:COROSIO: ``corosio::endpoint``
:ASIO: ``boost::asio::ip::tcp::endpoint``
:COROSIO: ``boost::corosio::endpoint``

.. cpp:type:: anyhttp::UdpEndpoint

The runtime's own UDP endpoint.

:ASIO: ``asio::ip::udp::endpoint``
:COROSIO: ``corosio::endpoint``, the same type as :cpp:type:`TcpEndpoint`
:ASIO: ``boost::asio::ip::udp::endpoint``
:COROSIO: ``boost::corosio::endpoint``, the same type as :cpp:type:`TcpEndpoint`

.. doxygentypedef:: anyhttp::DefaultCompletionToken

Expand Down
24 changes: 12 additions & 12 deletions docs/snippets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ Task<void> serve_echo(server::Request request, server::Response response)
std::array<uint8_t, 64_k> buffer;
for (;;)
{
auto [ec, n] = co_await request.read_some(asio::buffer(buffer));
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(asio::buffer(buffer, n)); write_ec)
if (auto [write_ec] = co_await response.write(boost::asio::buffer(buffer, n)); write_ec)
co_return;
}

Expand All @@ -80,7 +80,7 @@ Task<void> respond_with(server::Response response, std::string body)
ec)
co_return;

std::ignore = co_await response.write_eof(asio::buffer(body));
std::ignore = co_await response.write_eof(boost::asio::buffer(body));
}
// [write-at-once-end]

Expand Down Expand Up @@ -115,7 +115,7 @@ IoTask<std::string> read_all(Reader& reader)
std::array<char, 16_k> buffer;
for (;;)
{
auto [ec, n] = co_await reader.read_some(asio::buffer(buffer));
auto [ec, n] = co_await reader.read_some(boost::asio::buffer(buffer));
body.append(buffer.data(), n);
if (ec == errors::eof)
co_return std::tuple{error_code{}, std::move(body)};
Expand All @@ -133,7 +133,7 @@ IoTask<std::string> post(Session& session, boost::urls::url url, std::string bod
if (submit_ec)
co_return std::tuple{submit_ec, std::string{}};

if (auto [ec] = co_await request.write_eof(asio::buffer(body)); ec)
if (auto [ec] = co_await request.write_eof(boost::asio::buffer(body)); ec)
co_return std::tuple{ec, std::string{}};

auto [response_ec, response] = co_await request.get_response();
Expand Down Expand Up @@ -174,7 +174,7 @@ Task<size_t> read_at_most(Session session, boost::urls::url url, size_t limit)
std::array<char, 16_k> buffer;
while (total < limit)
{
auto [ec, n] = co_await response.read_some(asio::buffer(buffer));
auto [ec, n] = co_await response.read_some(boost::asio::buffer(buffer));
total += n;
if (ec)
break;
Expand All @@ -189,7 +189,7 @@ Task<size_t> read_at_most(Session session, boost::urls::url url, size_t limit)
Task<void> upload(Writer& writer, std::string_view body)
{
for (; !body.empty(); body.remove_prefix(std::min(body.size(), size_t{16_k})))
if (auto [ec] = co_await writer.write(asio::buffer(body.substr(0, 16_k))); ec)
if (auto [ec] = co_await writer.write(boost::asio::buffer(body.substr(0, 16_k))); ec)
throw_error(ec);

if (auto [ec] = co_await writer.write_eof(); ec)
Expand Down Expand Up @@ -352,13 +352,13 @@ Task<void> quic_echo(quic::Stream stream)
std::array<uint8_t, 16_k> buffer;
for (;;)
{
auto [ec, n] = co_await stream.read_some(asio::buffer(buffer));
auto [ec, n] = co_await stream.read_some(boost::asio::buffer(buffer));
if (ec == errors::eof)
break;
if (ec)
co_return;

if (auto [write_ec] = co_await stream.write(asio::buffer(buffer, n)); write_ec)
if (auto [write_ec] = co_await stream.write(boost::asio::buffer(buffer, n)); write_ec)
co_return;
}
std::ignore = co_await stream.write_eof();
Expand Down Expand Up @@ -393,7 +393,7 @@ IoTask<std::string> quic_hello(Executor executor, std::string host, std::string
if (open_ec)
co_return std::tuple{open_ec, std::string{}};

if (auto [ec] = co_await stream.write_eof(asio::buffer("hello", 5)); ec)
if (auto [ec] = co_await stream.write_eof(boost::asio::buffer("hello", 5)); ec)
co_return std::tuple{ec, std::string{}};

co_return co_await read_all(stream);
Expand Down Expand Up @@ -455,7 +455,7 @@ Task<std::string> token_exception(Session& session, boost::urls::url url)
// [token-tuple]
Task<std::string> token_tuple(Session& session, boost::urls::url url)
{
auto [ec, message] = co_await session.async_get(url, {}, asio::as_tuple);
auto [ec, message] = co_await session.async_get(url, {}, boost::asio::as_tuple);
if (ec)
co_return ec.message();
co_return std::move(message.body());
Expand All @@ -476,7 +476,7 @@ void token_callback(Session& session, boost::urls::url url)
Task<bool> response_within(client::Request& request, std::chrono::milliseconds limit)
{
auto [ec, response] =
co_await request.async_get_response(asio::cancel_after(limit, asio::as_tuple));
co_await request.async_get_response(boost::asio::cancel_after(limit, boost::asio::as_tuple));
co_return ec != errc::operation_canceled;
}
// [cancel-after-end]
Expand Down
Loading