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
6 changes: 3 additions & 3 deletions .github/instructions/anyhttp.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ Everything is asynchronous and written with C++20 coroutines. Every operation is
(`read_some`, `write`, `write_eof`, `submit`, `get_response`, `connect`, `get`) that yields a
tuple and reports errors instead of throwing.

There are two API styles, chosen at configure time with `-DANYHTTP_API=`:
There are two I/O runtimes, chosen at configure time with `-DANYHTTP_API=`:

* **ASIO** (default), on Boost.Asio: every operation is also an ASIO asynchronous operation
(`async_x(..., token)`) and takes any completion token, adhering to its [async model].
* **COROSIO**, on [capy](https://github.com/cppalliance/capy) and
[corosio](https://github.com/cppalliance/corosio): coroutines only.

Both styles build the same library sources and tests; code that belongs to one of them is guarded
Both runtimes build the same library sources and tests; code that belongs to one of them is guarded
with `ANYHTTP_ASIO` / `ANYHTTP_COROSIO` (exactly one is 1, from the generated `anyhttp/config.hpp`)
and lives in `asio/` or `corosio/`, under both `include/anyhttp/` and `src/`. Generic code reaches
the runtime only through `anyhttp/runtime.hpp`, `anyhttp/net.hpp` and `anyhttp/formatter.hpp`, and
Expand All @@ -33,7 +33,7 @@ functions `snake_case`, members `foo_`. Commit subjects are prefixed `fix:`, `re
`chore:`, `style:`, `test:`, or with the protocol (`h1:`, `h2:`, `h3:`).

Builds are out-of-source, one tree per configuration (`build/` Debug, `build-release/` Release,
`build-corosio/` the COROSIO style, plus sanitizer trees); `scripts/configure.sh` sets them up.
`build-corosio/` the COROSIO runtime, plus sanitizer trees); `scripts/configure.sh` sets them up.
Run the tests from the repository root -- they load TLS material through relative paths -- and
prefer `gtest-parallel build/test/test_all`.

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ env:

jobs:
#
# Both API styles in all three flavors, on OpenSSL: COROSIO needs it (corosio's openssl_stream
# Both I/O runtimes in all three flavors, on OpenSSL: COROSIO needs it (corosio's openssl_stream
# uses a zero-copy BIO API that AWS-LC lacks), so it is the baseline rather than the default
# AWS-LC. The legs after the product each isolate one variable the product holds fixed -- the
# standard library, the TLS library -- so that a red job names its own cause. COROSIO TSAN is
Expand Down Expand Up @@ -50,7 +50,7 @@ jobs:
api: [ASIO, COROSIO]
flavor: [RelWithDebInfo, ASAN, TSAN]
include:
# Flags per flavor, merged into the leg of each API style.
# Flags per flavor, merged into the leg of each I/O runtime.
- flavor: RelWithDebInfo
cmake: -DCMAKE_BUILD_TYPE=RelWithDebInfo
- flavor: ASAN
Expand All @@ -59,7 +59,7 @@ jobs:
- flavor: TSAN
cmake: -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer"
opts: TSAN_OPTIONS=report_signal_unsafe=0
# Legs of their own: GCC and libstdc++ for both styles, and the other TLS library.
# Legs of their own: GCC and libstdc++ for both runtimes, and the other TLS library.
- api: ASIO
flavor: GCC
cc: gcc
Expand Down
25 changes: 12 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ benchmark with the latter, never with `build/`. Both use clang, which builds aga
the libraries in `/opt/libc++` (`USE_LIBCXX`, `LIBCXX_ROOT`); `build-gcc/` is GCC with libstdc++
and `/usr/local`. Keep `build/` on clang: clangd reads its `compile_commands.json`.

`build-corosio/` is the COROSIO API style (`-DANYHTTP_API=COROSIO -DTLS_LIBRARY=OpenSSL`, see
`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 styles build the same library sources and test files; what belongs to one style
only is guarded with `ANYHTTP_ASIO` or `ANYHTTP_COROSIO` (exactly one of them is 1). So is the
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
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
Expand Down Expand Up @@ -137,10 +137,9 @@ buffer (see `WriteMode`), and `build-h2v2-asan/` is the version 2 ASAN tree for
Each protocol lives in a directory of its own, `h1/` (Beast/HTTP/1.1), `h2/` (nghttp2), `quic/`
(ngtcp2) and `h3/` (nghttp3, on top of `quic/`), in both `include/anyhttp/` and `src/`. **Only
files in `h2/` may include `<nghttp2/*>`, only files in `h2v2/` `<nghttp2v2/*>`, only files in
`quic/` and `h3/` `<ngtcp2/*>`, only files in `h3/` `<nghttp3/*>`.** What belongs to one API style
only
is in `asio/` or `corosio/`, again in both; the library compiles the `src/` directory of its style
and not the other. `src/apps/` has the `server` and `client` programs, `src/issues/` standalone
`quic/` and `h3/` `<ngtcp2/*>`, only files in `h3/` `<nghttp3/*>`.** What belongs to one async
runtime only is in `asio/` or `corosio/`, again in both; the library compiles the `src/` directory
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.

What gets installed is the public headers, the ones reachable from `client.hpp`, `server.hpp`,
Expand Down Expand Up @@ -194,7 +193,7 @@ may: the h2c upgrade and formatter tests drive nghttp2 by hand.)
**Generic and protocol code include nothing from Boost.Asio but `<boost/asio/buffer.hpp>`**, which
Beast's parser, serializer and `Fields` bring anyway, and nothing from capy or corosio. They reach
the runtime through `anyhttp/runtime.hpp`, `anyhttp/net.hpp` and `anyhttp/formatter.hpp`, which
include the `asio/` or `corosio/` header of the build's style. Addresses and endpoints are the
include the `asio/` or `corosio/` header of the build's runtime. Addresses and endpoints are the
runtime's own (`IpAddress`, `TcpEndpoint`, `UdpEndpoint`); where the two differ, `net.hpp` declares
a function each runtime defines (`io::make_address()`, `io::to_sockaddr()`, ...). Verify in a
COROSIO tree that no library object includes more of Asio:
Expand All @@ -204,7 +203,7 @@ ninja -C build-corosio -t deps | awk '/^[^ ].*: #deps/{o=$1} /boost\/asio\// &&
```

It must print nothing. (Test objects may: `test_external.cpp` runs its child processes on an ASIO
context in both styles.)
context on both runtimes.)

QUIC and HTTP/3 are two layers, and each shares one implementation between server and client.
`quic/session.*` has all ngtcp2 callbacks, packet writing, timers and connection flow control,
Expand All @@ -222,16 +221,16 @@ shared files.
## Conventions

- clang-format is authoritative: 3-space indent, 100 columns.
- The two API styles are ASIO and COROSIO: `ANYHTTP_ASIO`/`ANYHTTP_COROSIO`,
- The two I/O runtimes are ASIO and COROSIO: `ANYHTTP_ASIO`/`ANYHTTP_COROSIO`,
`asio/`/`corosio/`, `build-corosio/`. "capy" names only the library itself (`capy::task`,
`<boost/capy/...>`), the non-I/O base that corosio adds I/O to.
- Where the two styles differ by more than a few lines, each gets a file of its own, of the same
- Where the two runtimes differ by more than a few lines, each gets a file of its own, of the same
name in `asio/` and `corosio/`, with the same definitions in the same order, so that the pair can
be read side by side: `runtime.hpp`, `net.hpp`, `src/*/net.cpp`. What both share stays in the
generic file of that name (`anyhttp/runtime.hpp`, which selects one of the two, `anyhttp/net.hpp`,
`src/net.cpp`). The test fixtures follow the old pattern, `test/test_fixtures_*.hpp`. Keep the
order when adding to either.
- The style comes from the generated `anyhttp/config.hpp` (`cmake/config.hpp.in`), not from a
- The runtime comes from the generated `anyhttp/config.hpp` (`cmake/config.hpp.in`), not from a
compile definition: a file that tests `ANYHTTP_ASIO`/`ANYHTTP_COROSIO` must include it (or
`anyhttp/runtime.hpp`) first, or the test is silently false.
- Test `.cpp` files put `using namespace testing;` after the includes and use `HasSubstr`,
Expand Down Expand Up @@ -271,7 +270,7 @@ After adding a dependency, check `ldd` of an AWS-LC build shows no `libssl.so.3`
`libcrypto.so.3` -- a shared OpenSSL would interpose the executable's AWS-LC symbols. `curl`,
`osslclient` and `osslserver` are OpenSSL builds and are useful for interop testing.

**A context has to run dry, in both styles.** This is anyhttp's doing, not capy's any more. Since
**A context has to run dry, on both runtimes.** This is anyhttp's doing, not capy's any more. Since
capy `8e1244f` and corosio `232c79f`, destroying an `io_context` unwinds the coroutines suspended on
it, provided the continuation they are parked with carries `env->owner`: whatever in
`corosio/runtime.hpp` parks or queues a coroutine has to set it. What that cannot reach is a cycle
Expand Down
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ endif()
# --------------------------------------------------------------------------------------------------

#
# API style and I/O runtime, one per build tree: ASIO (completion tokens, on Boost.Asio) or COROSIO
# The I/O runtime, one per build tree: ASIO (completion tokens, on Boost.Asio) or COROSIO
# (coroutines only, on capy/corosio). The code tests ANYHTTP_ASIO or ANYHTTP_COROSIO, exactly one of
# which is 1, from the generated anyhttp/config.hpp. See docs/corosio-port-plan.md.
#
set(ANYHTTP_API ASIO CACHE STRING "API style and I/O runtime: ASIO or COROSIO")
set(ANYHTTP_API ASIO CACHE STRING "I/O runtime: ASIO or COROSIO")
set_property(CACHE ANYHTTP_API PROPERTY STRINGS ASIO COROSIO)
if (ANYHTTP_API STREQUAL "ASIO")
set(ANYHTTP_ASIO 1)
Expand Down Expand Up @@ -170,7 +170,7 @@ elseif (ANYHTTP_API STREQUAL "COROSIO")
GIT_TAG 41d0de18b6e4fa1b397c38a866f2daf52c6c36b5
EXCLUDE_FROM_ALL)
FetchContent_MakeAvailable(capy corosio)
message(STATUS "Using API style: COROSIO (capy/corosio)")
message(STATUS "Using I/O runtime: COROSIO (capy/corosio)")
else()
message(FATAL_ERROR "ANYHTTP_API must be ASIO or COROSIO, not '${ANYHTTP_API}'")
endif()
Expand Down
4 changes: 2 additions & 2 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
{
"name": "corosio",
"hidden": true,
"description": "The COROSIO API style, on capy/corosio, which needs OpenSSL",
"description": "The COROSIO I/O runtime, on capy/corosio, which needs OpenSSL",
"inherits": "openssl",
"cacheVariables": {
"ANYHTTP_API": "COROSIO"
Expand Down Expand Up @@ -177,7 +177,7 @@
{
"name": "build-corosio",
"displayName": "COROSIO",
"description": "The COROSIO API style, see docs/corosio-port-plan.md; capy and corosio are FetchContent'd at pinned SHAs",
"description": "The COROSIO I/O runtime, see docs/corosio-port-plan.md; capy and corosio are FetchContent'd at pinned SHAs",
"inherits": [
"clang",
"corosio",
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Documentation](https://img.shields.io/badge/documentation-read-blue)](https://pgit.github.io/anyhttp/)
[![Coverage](https://img.shields.io/badge/coverage-report-blue)](https://pgit.github.io/anyhttp/coverage/)

Low-level C++23 HTTP client and server library, in one of two API styles, chosen at configure time:
Low-level C++23 HTTP client and server library, on one of two I/O runtimes, chosen at configure time:

* **ASIO**, on [Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html): every
operation is an asynchronous operation of Asio and takes any completion token.
Expand All @@ -28,7 +28,7 @@ This project started as a hand-written replacement for [nghttp2-asio](https://gi

**<https://pgit.github.io/anyhttp/>** has the [programmer's guide](docs/programmers-guide.rst), which
is the place to start, the API reference and how it all works:
[API styles](docs/api-styles.rst), [the end of a body](docs/end-of-body.rst),
[I/O runtimes](docs/io-runtimes.rst), [the end of a body](docs/end-of-body.rst),
[concurrent requests](docs/concurrent-requests.rst), [moving to HTTP/3](docs/alt-svc.rst) and
[the HTTP/2 library](docs/http2-library.rst). [docs/README.md](docs/README.md) says how to make it.

Expand Down Expand Up @@ -78,7 +78,7 @@ Task<void> do_session(client::Client& client, boost::urls::url url)
}
```

Both API styles have every operation as such a coroutine, that yields a tuple and reports errors
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](docs/examples/hello_server.cpp) and
[hello_client.cpp](docs/examples/hello_client.cpp).
Expand Down
2 changes: 1 addition & 1 deletion cmake/config.hpp.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

//
// Generated by CMake from cmake/config.hpp.in: the API style this library was built for (see
// Generated by CMake from cmake/config.hpp.in: the I/O runtime this library was built for (see
// anyhttp/runtime.hpp). Code using the library has to see the same one, so it comes with the
// headers instead of a compile definition.
//
Expand Down
2 changes: 1 addition & 1 deletion docs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ target_link_libraries(hello_client PRIVATE anyhttp)
# cmake --build build --target docs
#
# Doxygen does not compile the headers, so unlike the snippets the documentation depends on neither
# the API style of the tree nor on what that style needs. See Doxyfile.in.
# the I/O runtime of the tree nor on what that runtime needs. See Doxyfile.in.
#
find_program(DOXYGEN_EXECUTABLE doxygen DOC "Doxygen, which reads the comments in the headers")
find_program(SPHINX_EXECUTABLE sphinx-build DOC "Sphinx, which makes the documentation")
Expand Down
4 changes: 2 additions & 2 deletions docs/Doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/include

#
# The public interface, and nothing else. The comments on the vocabulary of the two runtimes are in
# asio/runtime.hpp, and corosio/runtime.hpp has the same names in the same order -- api-styles.rst
# asio/runtime.hpp, and corosio/runtime.hpp has the same names in the same order -- io-runtimes.rst
# has the table of what they map to. What these headers have for the library's own use is between
# \cond and \endcond.
#
Expand All @@ -33,7 +33,7 @@ JAVADOC_AUTOBRIEF = YES

# Doxygen does not compile anything, so what a compiler would get from config.hpp and from Asio's
# headers is said here -- which is also why the reference takes neither Boost nor a configured
# runtime, and comes out the same from a tree of either style
# runtime, and comes out the same from a tree of either runtime
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
PREDEFINED = ANYHTTP_ASIO=1 \
Expand Down
6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ apt-get install doxygen python3-sphinx python3-breathe python3-sphinx-rtd-theme
```

The target is not part of a regular build, and not there at all where CMake finds no `doxygen` or no
`sphinx-build`, or was told `ANYHTTP_BUILD_DOCS=OFF`. It can be made from a tree of either API style
`sphinx-build`, or was told `ANYHTTP_BUILD_DOCS=OFF`. It can be made from a tree of either I/O runtime
and gives the same result: the comments on the types of the two runtimes are in `asio/runtime.hpp`.

## Writing it
Expand All @@ -43,12 +43,12 @@ Warnings are errors in both tools, so a public symbol without a comment, a param
What a public header has for the library's own use goes between `\cond internal` and `\endcond`.
* In a comment, Doxygen takes `http::...` and `https://` for a link, and `\c` of either, or of a word
that starts with `[`, comes out empty, which neither tool warns about. So write `errors::...`, as
that is the spelling that holds in both API styles anyway, and `<tt>[...]</tt>`.
that is the spelling that holds on both I/O runtimes anyway, and `<tt>[...]</tt>`.
* Code in the guide comes from `snippets.cpp`, between a `// [name]` and a `// [name-end]` comment,
and is shown with `literalinclude`. Only pseudo code is written in place.
* In a page, names link to the reference with `` :cpp:func:`Session::submit` `` and the like, and
files of the repository with `` :src:`src/server.cpp` ``.
* In prose, the libraries are Asio, Corosio and Capy. `ASIO` and `COROSIO` are the API styles, the
* In prose, the libraries are Asio, Corosio and Capy. `ASIO` and `COROSIO` are the I/O runtimes, the
values of `ANYHTTP_API`: "an ASIO build". Capy is named only where Capy itself is meant.

[`Doxyfile.in`](Doxyfile.in) configures Doxygen and [`conf.py`](conf.py) configures Sphinx.
8 changes: 4 additions & 4 deletions docs/benchmark.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ The tables below use `-c 32` unless they say otherwise.
## HTTP/2: nghttpd vs anyhttp

*2026-10-07, measured at `d445bdf`.* nghttpd, which `bench.sh` runs as the HTTP/2 reference,
serves twice as many requests as either API style: `bench.sh -D 5 -c 32` gave 555k req/s against
serves twice as many requests as either I/O runtime: `bench.sh -D 5 -c 32` gave 555k req/s against
284k (ASIO) and 273k (COROSIO), on one thread each, with the same nghttp2 and OpenSSL. This is
where the time goes; [Done since](#done-since) lists what has been fixed.

Expand Down Expand Up @@ -144,7 +144,7 @@ instructions may sit in a neighbouring row:

COROSIO, by `perf` share of user time: malloc/free 21%, `std::format` 13%, capy/corosio 12%,
nghttp2 15%, Boost.URL 5%, Beast 3.5%. The same shape, so the cost lies in the generic code and
the HTTP/2 backend, not in the API style.
the HTTP/2 backend, not in the I/O runtime.

### Where it goes

Expand Down Expand Up @@ -304,7 +304,7 @@ Scaling across independent contexts, h2 (`h2load -D 6 -c 32 -m 10 -t 4`, req/s):
- **COROSIO's threaded scheduler is far better than ASIO's shared one,** but a context per thread
takes most of its lead away. In the independent model COROSIO stays ahead on h1 only and is
7-22% behind on h2 and h3.
- **h1 and h3 gain from independent contexts in both styles.** COROSIO h1 drops from 26 to 15.5 µs
- **h1 and h3 gain from independent contexts on both runtimes.** COROSIO h1 drops from 26 to 15.5 µs
per request, 4.7 µs of the saving in system time.
- **COROSIO h2 gets slower** with independent contexts: 6.2 instead of 5.0 µs per request, with
system time up by half. Load is spread evenly (all threads equally busy), and
Expand Down Expand Up @@ -353,7 +353,7 @@ causes, both fixed:
completed inside the initiating call, which `Response::submit()` always does. HTTP/3's write
pass, posted by `wake_write()`, then ran first and sent each response head in a packet of its
own, before the handler could add the body: twice the flushes, packets and ACKs of ASIO. Found
by counting events per request in both styles, not by profiling. Fixed by resuming at once,
by counting events per request on both runtimes, not by profiling. Fixed by resuming at once,
after the initiating call has returned (`6c3c50f`): h3 97k → 143k (ASIO 124k), h2 197k → 247k
(ASIO 207k), h2 cleartext 229k → 276k (ASIO 191k), with `bench.sh -D 3`.

Expand Down
Loading