owl – a header-only C++23 HTTP framework where route patterns and handler extractors are checked at compile time (early, looking for contributors)
I've been building owl for about five weeks: a header-only C++23 HTTP framework on libh2o. Posting it now because other people's compilers and opinions are worth more than another week of mine.
Repo: [https://github.com/owl-cxx/owl](https://github.com/owl-cxx/owl)
**The idea**
A handler is a free function. Its parameters are extractors, and the route pattern is a structural NTTP, so the pattern is checked against the extractors when the route is registered:
owl::Response hello(owl::PathView<"name"> name) {
return owl::Response::ok(std::format("hello {}", name.value));
}
coro::task<owl::Response> hits(owl::State<AppState> app) {
co_return owl::Response::json(std::format(R"({{"hits":{}}})", ++app->hits));
}
auto router = owl::Router<AppState>::make()
.route<"/hello/{name}">(owl::get(hello))
.route<"/hits">(owl::get(hits));
A `PathView<"nmae">` typo, or a handler taking `State<Other>` on a `Router<AppState>`, is a compile error rather than a runtime 500. There are 30 `compile_fail/` tests that pin exactly which misuses must not compile; if one of them starts compiling, that is the bug.
Handlers can be coroutines and they stay on the worker's event loop: one `SO_REUSEPORT` listener per worker, Node-cluster style, and a coroutine always resumes on the thread it suspended on. Errors cross library boundaries as `std::expected`, never as exceptions.
Around the core: `coro` (lazy tasks, generators, schedulers, an I/O reactor), `fstr` (string literals as structural NTTPs), async SQLite/Postgres and Redis for handlers, Prometheus with HTTP RED recorded automatically, middleware layers (CORS, basic auth), WebSocket with a controller pattern, HTTP/1.1 + h2c, TLS termination. About 27k lines across 94 headers and 94 test files. MIT.
**What it is not**
Early. APIs move between commits, there are no tagged releases, and nothing has run in production. The README says "do not ship this" and means it.
No C++20 fallback: it leans on `std::expected`, `if consteval`, and structural NTTPs. GCC 14 on Linux, Apple clang on macOS. Ubuntu's clang 18 does not work: libstdc++'s `<expected>` wants a `__cpp_concepts` it does not report, and libc++ 18 has no floating-point `from_chars`.
**Where I'd like help**
* Build it on your platform and tell me where it breaks. libh2o-evloop must be built from h2o source (distros ship 2.2, owl targets 2.3). There is a dev image, `ghcr.io/owl-cxx/owl-dev`, so you do not have to.
* Three self-contained good-first-issues, each with a clear definition of done: `HEAD` falling back to a `GET` handler; a CORS preflight to an unregistered path currently gets a bare 404 with no CORS headers; shared ranges fallbacks for Apple libc++ 21, which has no `views::chunk`, `views::stride`, or `views::enumerate`.
* Opinions on the extractor design. A custom extractor is one `FromContext` specialization that answers either the value or a `KickToken` with a status. I'd like to know where that model breaks before more of the API hardens around it.
* macOS CI. There is no prebuilt image, so h2o would need a source build on every run.
What is next (HTTP client awaited on the worker loop, static files, graceful shutdown, WebSocket over HTTP/2, eventually C++26 reflection for `sql` row mapping) and what is deliberately not planned is in ROADMAP.md. Telegram and Discord links are in the README, or just open an issue.
Happy to answer anything about the design, especially the compile-time route checking and keeping coroutines on the loop.
https://redd.it/1x0q0d9
@r_cpp
Post #25827
9