[taar] Interest in an HTTP application library on Asio/Beast
Hello all, I would like to gauge interest in Boost.Taar, a C++20 header-only library for writing HTTP services on top of Boost.Asio, Boost.Beast, Boost.JSON and Boost.URL. How it started In 2021 a colleague challenged me: "In Python you can serve HTTP in one line (python -m http.server). Can you do that in C++?" With Beast you can build anything, but even a small JSON endpoint means writing your own routing, argument parsing, error mapping and connection loop. Taar is my answer to that challenge. It went through several shapes before I settled on the current design in 2024, and it has grown into a library since. The gap it fills Beast is deliberately low level: it gives you HTTP messages, parsers and streams, and leaves routing, parameter extraction, conversions, sessions, static files and protocol handling to the application. Every non-trivial Beast server rewrites those layers. Outside Boost there are frameworks (Crow, Drogon, Oat++, cpp-httplib, userver), but they bring their own networking, threading and JSON models and do not compose with Asio executors, coroutines, Boost.JSON or Boost.URL. Boost has nothing at the application level. Taar is that layer, built only from Boost components and in their idiom: - the server and every connection are Asio coroutines; handlers may be plain functions or awaitables; it runs on any Asio executor, with a strand per connection on a multi-threaded io_context; - a composable matcher DSL selects routes (method, path templates with placeholders, headers, cookies, version); - handlers are ordinary callables: arguments are extracted from the path, query, headers, cookies or body and converted to the parameter types, and return values are converted to responses (both through tag_invoke customisation points); - HTTP protocols sit on top of the same callables: a JSON-RPC 2.0 endpoint, a Model Context Protocol (MCP) server so C++ applications can expose tools and resources to AI agents, and an OpenAPI 3.1 document generated from the registered routes. The layering is strict: Asio/Beast/JSON/URL at the bottom, the HTTP framework above them, and JSON-RPC, MCP and OpenAPI as optional layers on the HTTP framework that do not affect users who only need REST. What it looks like The answer to the challenge, a static file server for the current directory: taar::session::http session; session.register_request_handler( method == http::verb::get && target == "/{*path}", taar::handler::htdocs {"."}); boost::asio::io_context io; taar::cancellation_signals signals; boost::asio::co_spawn(io, taar::server::tcp("0.0.0.0", "8080", session, signals), boost::asio::detached); io.run(); And one function served three ways: int add(int a, int b) { return a + b; } // REST: GET /add/2/3 -> 5, documented in the generated OpenAPI file session.register_request_handler( method == http::verb::get && target == "/add/{a}/{b}", taar::handler::rest(&add, path_arg("a"), path_arg("b")), openapi.describe("Add two numbers")); // JSON-RPC 2.0: {"method": "add", "params": {"a": 2, "b": 3}} rpc.method("add", &add, rpc_arg("a"), rpc_arg("b")); // MCP: a tool whose input schema is derived from the parameter types mcp.tool("add", "Add two numbers", &add, mcp_arg("a"), mcp_arg("b")); Beyond that - static files with ETag/Last-Modified and conditional requests, byte ranges, precompressed variants, cache rules and caches; - streaming responses (chunked, server-sent events) and streaming request bodies, multipart forms; - per-connection state, cookie or header keyed sessions, interceptors and route guards, CORS, TLS, connection timeouts, graceful shutdown; - one error model: taar::failure with nested causes, taar::result<T> (boost::system::result with a rich error) that throws on access, mapped per surface to HTTP statuses, JSON-RPC errors and MCP results; - MCP over Streamable HTTP and stdio, with sessions, progress, cancellation, server-to-client requests and resource subscriptions. Status Version 1.0 is released. The library has about 480 test cases (unit and end-to-end over TCP and TLS). C++20 is the minimum; CI runs the tests as C++20 on GCC 13, Clang 17 and 18 (libstdc++ and libc++), MSVC 2022 and Apple Clang, as C++23 on newer GCC, Clang, MSVC and Apple Clang, as C++26 on GCC 15 and Clang 22, and under ASan, UBSan and TSan. It builds with b2 in the Boost superproject and with CMake (including the Boost CMake integration tests), and has QuickBook documentation with a reference and quick reference. Questions I would value the list's opinion on - Is MCP appropriate inside a Boost library given how young the specification is? It is an optional layer and versioned by protocol revision, but I would like to hear concerns. - I plan to replace much of the type introspection with C++26 reflection once compilers ship it (parameter names, schemas without BOOST_DESCRIBE, annotations for descriptions and constraints), keeping the C++20 path. Is that a reasonable direction? - The name: "taar" is Persian for a strand or thread, as in a spider's web, and is also the name of a long-necked lute; a library of strands for the web seemed fitting. Is it acceptable as a Boost name? Links - Repository: https://github.com/rjahanbakhshi/boost.taar - Documentation: https://rjahanbakhshi.github.io/boost.taar/ - Examples: https://github.com/rjahanbakhshi/boost.taar/tree/master/example - Known gaps and roadmap: https://github.com/rjahanbakhshi/boost.taar/blob/master/ROADMAP.md If there is interest, I would be grateful for feedback on the design and API, and I am looking for a review manager. Thank you, Reza Jahanbakhshi
Questions I would value the list's opinion on
- Is MCP appropriate inside a Boost library given how young the specification is? It is an optional layer and versioned by protocol revision, but I would like to hear concerns. - I plan to replace much of the type introspection with C++26 reflection once compilers ship it (parameter names, schemas without BOOST_DESCRIBE, annotations for descriptions and constraints), keeping the C++20 path. Is that a reasonable direction? - The name: "taar" is Persian for a strand or thread, as in a spider's web, and is also the name of a long-necked lute; a library of strands for the web seemed fitting. Is it acceptable as a Boost name?
Links
- Repository: https://github.com/rjahanbakhshi/boost.taar - Documentation: https://rjahanbakhshi.github.io/boost.taar/ - Examples: https://github.com/rjahanbakhshi/boost.taar/tree/master/example - Known gaps and roadmap: https://github.com/rjahanbakhshi/boost.taar/blob/master/ROADMAP.md
If there is interest, I would be grateful for feedback on the design and API, and I am looking for a review manager.
Hi Reza, Thanks for writing this up. Taar is close to what I've been working on, and I'd like to explore collaborating. I work on a family of libraries. Capy is the coroutine I/O foundation, Corosio is coroutine networking, Http is sans-I/O HTTP/1.1, and Beast2 builds servers on top of those. Boost conditionally accepted both Capy and Corosio. Http covers much of Taar's lower half, including path-pattern routing with automatic Allow handling, middleware, CORS, and static files with ETag, freshness and range support. It also applies gzip/deflate/brotli encoding on the fly. We don't have the layer you built on top. Taar appears to bind plain callables to request arguments and responses and expose them over JSON-RPC and MCP, as well as generate an OpenAPI document from the routes. I'd like to understand how much your handler binding and protocol layers depend on Beast, and whether they could sit on Http as well. If you're interested, come join us in #boost-corosio on the C++ Slack (cpplang.slack.com). The repositories are below. https://github.com/cppalliance/capy https://github.com/cppalliance/corosio https://github.com/cppalliance/http https://github.com/cppalliance/beast2 Steve
Hi Steve, Thanks for your message, and for the pointers. I spent some time in the Http repository, and the overlap is clear. The router, middleware, CORS and static file support cover much of what taar does below the handler layer, and Http goes further on content encoding and conditional and range requests. To answer your question, the upper layers depend on Beast mostly for vocabulary, not for I/O. The binding layer, JSON-RPC, MCP and OpenAPI use Beast's request header, fields, verb, status and string_body/response types, plus asio::awaitable for coroutine handlers. The parts that really need Beast and Asio are the session and the TCP server, which are the parts Http and Beast2 already cover. From a first read, a taar handler could sit behind a route_task adapter. rest(f, path_arg("id"), json_body_arg()) would read route_params::params and req_body, convert the arguments, call f, and write the result to res. The work on my side would be to abstract the message types taar uses for argument extraction and response generation, and to support capy::task next to asio::awaitable for asynchronous handlers. I also noticed that dynamic_invoke resolves arguments by type from a polystore, while taar binds each argument by source and name (path, query, header, cookie, body). The two seem complementary rather than overlapping. My current plan is to take taar to review on Beast and Asio, since both are established Boost libraries. I would like to keep the protocol layers separable enough that an Http backend can follow without changing user code. Does that fit with how you see Http and Beast2 evolving, and do you have a rough timeline for Http's review? I will join #boost-corosio on Slack. Regards, Reza
It's probably a better priority to at least get HTTP/2 working before going down this path. HTTP/3 will, alas, always be completely out of scope as I expect no solo dev will deliver a working QUIC implementation. - Christian
On Wed, Oct 28, 2026 at 1:33 PM Christian Mazakas via Boost < boost@lists.boost.org> wrote:
It's probably a better priority to at least get HTTP/2 working before going down this path.
Why? Every single LLM inference provider that I have seen supports HTTP/1.1 What part of his requirements needs HTTP/2 and can you explain why? Thanks
On Wed, Oct 28, 2026 at 1:35 PM Vinnie Falco <vinnie.falco@gmail.com> wrote:
On Wed, Oct 28, 2026 at 1:33 PM Christian Mazakas via Boost < boost@lists.boost.org> wrote:
It's probably a better priority to at least get HTTP/2 working before going down this path.
Why? Every single LLM inference provider that I have seen supports HTTP/1.1
What part of his requirements needs HTTP/2 and can you explain why?
Consider asking your friendly neighborhood LLM inference provider. - Christian
HTTP/2 and HTTP/3 are out of scope for taar, by design. Beast is HTTP/1.1 only, and the expected deployment puts a reverse proxy or load balancer in front that terminates TLS and h2/h3. That's how we run it in production too. REST, JSON-RPC, MCP (Streamable HTTP), SSE and WebSocket all work fine over HTTP/1.1. It was on the roadmap but the reasoning wasn't written down, so I just added a design note: https://rjahanbakhshi.github.io/boost.taar/boost_taar/design.html#boost_taar... The handler and protocol layers don't care how the bytes arrive, so in theory, if an HTTP/2 transport shows up in Beast or Http later, the same handlers should run on it.
On Thu, Oct 29, 2026 at 2:12 AM Reza Jahanbakhshi via Boost < boost@lists.boost.org> wrote:
HTTP/2 and HTTP/3 are out of scope for taar, by design. Beast is HTTP/1.1 only, and the expected deployment puts a reverse proxy or load balancer in front that terminates TLS and h2/h3. That's how we run it in production too. REST, JSON-RPC, MCP (Streamable HTTP), SSE and WebSocket all work fine over HTTP/1.1.
It was on the roadmap but the reasoning wasn't written down, so I just added a design note:
https://rjahanbakhshi.github.io/boost.taar/boost_taar/design.html#boost_taar...
The handler and protocol layers don't care how the bytes arrive, so in theory, if an HTTP/2 transport shows up in Beast or Http later, the same handlers should run on it.
Right. I think this is actually even more reason why you shouldn't necessarily pursue this. You have to look at the trade-offs you're making. You have a header-only library using Asio and Beast, so it's slow to compile and the binary bloat can be tremendously large. What's more, you're also using an entirely memory unsafe language in a security-critical context. Then even worse, you're deliberately eschewing performance at a protocol level by only using HTTP/1. When in all reality, users can just use an off-the-shelf memory safe language which _does_ support modern protocols. Following up on this, real servers demand a ton of different functionality like handling cookies, JWTs, OAuth 2.0, etc. There's just so much a user would actually need to write a real server here and unfortunately, I think C++ is woefully deficient here. If one was going to use C++, it'd actually demand best-in-class performance across the board and telling people to terminate multiplexed streams into a sequential protocol I think is a non-starter when you look at all the other pieces that are in play here. In short, what would a user even gain here over just using Node, Go, C#, Kotlin, Rust, Python, Java? - Christian
Le vendredi 30 octobre 2026 à 08:06 -0700, Christian Mazakas via Boost a écrit :
In short, what would a user even gain here over just using Node, Go, C#, Kotlin, Rust, Python, Java?
In short, code reuse, not learning a new language, environment consistency, etc. That does not of course invalidate the points you made, but may weigh off the balance a bit (typically for an embedded device it may make sense). Regards, Julien
Hi Christian, Fair questions, so taking them one at a time: Compile time and bloat: those should be measured, not assumed. Compile time I'm already working on (see my other mail). Binary size I checked: the hello example in release, stripped, is 1.8 MB with GCC. A hand-written Beast server with the same two routes is 1.1 MB. So taar adds about 0.7 MB on top of Beast, and that includes shutdown and error handling the hand-written one doesn't have. That 0.7 MB is a one-off cost. Each route after that adds about 25 KB (measured up to 52 routes. Memory safety: true for any C++ server, including everything already built on Beast today. Taar doesn't make anyone pick C++. It helps people who already have. HTTP/1.1: that comes from Beast, not from a decision to give something up. Taar builds on what Boost has and doesn't take anything away. In most deployments HTTP/2 and HTTP/3 end at the load balancer or proxy anyway (AWS ALB, GCP's load balancer, Azure Front Door, Envoy, nginx), and the hop to the app is often plain HTTP/1.1. If Boost gets HTTP/2 support, taar can sit on top of it. JWT, OAuth, etc.: those usually live in a different layer. A gateway or auth proxy (API gateway authorizers, Envoy ext_authz, nginx auth_request, oauth2-proxy) validates the token and forwards the request with the identity in a header. The service reads that header, and taar already matches and extracts headers and cookies. I don't think a routing library should implement OAuth. Why not Node, Go, C#...: if you're starting from scratch, maybe. But plenty of teams already have a large C++ codebase and just need an HTTP or REST endpoint in front of it, or even choose to use C++ with new projects. Adding a second language means another runtime, build, deployment and on-call story. That cost is real, and that's the user taar is for. Cheers, Reza
On Sat, Oct 31, 2026 at 12:00 AM Reza Jahanbakhshi via Boost < boost@lists.boost.org> wrote:
Compile time and bloat: those should be measured, not assumed. Compile time I'm already working on (see my other mail).
I'll explain it all starts with ASIO. For example in ASIO Unix domain socket and TCP sockets are two separate class that share only "API" but if you want to use them both in same class you need templates to use them Example fastcgi/ scgi connection that work over both but can't actually be implemented over direct ASIO objects without templates. Same for SSL and TCP sockets. If I create a large class that handles communication there is no business whatsoever to have tcp and ssl sockets as template classes. It is the biggest design flaw of ASIO and related libraries. As an early adopter of boost.asio for some of the projects where a tiny networking part took 80% of compilation and in another required me to wrap the networking object into classic abstractions to support two different types of"streams". I'm not going down this road again. Now consider that any serious web service project contains a huge amount of business logic. Do you really want to have it header only? It isn't only a question of compilation time or code bloat - it is a question of design. For example in one of my open source astronomy /astrophotography projects openlivestacker - I use web UI that communicates with complex business logic backend that does image processing, algorithms and communicates with web ui via REST, sends video stream (mjpeg), real time updates via server sent events and more and all is in a classic library/framework that supports way-way more web0-development essential tools that beast even thinks to provide) Do you think I would ever touch a header only library for RESTful API implementation? While template metaprogramming is an amazing technique. it is heavily abused in ASIO and Beast. My $0.02
On Sat, Oct 31, 2026, at 8:07 AM, Artyom Beilis via Boost wrote:
Do you think I would ever touch a header only library for RESTful API implementation? While template metaprogramming is an amazing technique. it is heavily abused in ASIO and Beast.
My $0.02
None of these are necessarily connected. Asio is a *general purpose* library, and precisely as John Kalb outlined in his recent C++Now talk it *should* not box you in or accept non-zero overhead of abstraction. However, when you consume it, you *pick* how you want to use it in your domain. You implement your RESTful API, with all the classical implementation fire-walling of separate TUs, no issue. I imagine you might need to include your JSON library of choice, but none of the ASIO internals could be visible, and none of the Beast HTTP internals *need* to be. In short, unlike `const`, header-only isn't contagious. My -$0.02
None of these are necessarily connected. Asio is a *general purpose* library, and precisely as John Kalb outlined in his recent C++Now talk it *should* not box you in or accept non-zero overhead of abstraction. However, when you consume it, you *pick* how you want to use it in your domain. You implement your RESTful API, with all the classical implementation fire-walling of separate TUs, no issue. I imagine you might need to include your JSON library of
....
In short, unlike `const`, header-only isn't contagious.
I explain my point: the proposed library as beast and and as asio suffer from major serious design flaws. They use static polymorphism instead of dynamic everywhere while for ASIO being a low level library it may be reasonable) for higher level protocols it is totally problematic. And this is a huge issue Here example: https://github.com/rjahanbakhshi/boost.taar/blob/master/example/tls_server.c... If you want to support lets say both SSL and TCP you need to "compile" against two statically polymorph classes. It is a very poor design if all your complex app needs to be "templated per "connection type" This takes static polymorphism to level of abuse especially there is 0 "performance gain" can be made as all is transferred to system calls that ironically handles dynamic polymorphism over file descriptor. So this is the main issue. "header only" is only a symptom. Real problem is abuse of static polymorphism. Artyom
I would like to gauge interest in Boost.Taar, a C++20 header-only library for writing HTTP services on top of Boost.Asio, Boost.Beast, Boost.JSON and Boost.URL.
Why would you want header only http/asio based library. It was my biggest criticism of Beast (and asio) that I think totally missed the point Asio while being good/state of the art networking library is painfully hard to use due to its over reliance on template meta programming For example there is no reason why would SSL/TCP or Unix domain sockets would not be compiled into different code? More than that header only heavy libraries are horrible for intensive development and create enormous compilation times. You want to write real http service (rest/jweb etc)? There are plenty of stuff around. (*) Going header only is going backward not forward Header only should be limited to small utility libraries that compile fast and serve specific purposes. Artyom Beilis [*]for example cppcms - disclosure I'm the author of it
On Fri, Oct 30, 2026 at 2:11 AM Artyom Beilis via Boost < boost@lists.boost.org> wrote:
Why would you want header only http/asio based library. It was my biggest criticism of Beast (and asio) that I think totally missed the point Asio while being good/state of the art networking library is painfully hard to use due to its over reliance on template meta programming
Corosio is designed from the ground up with this principle: minimize the dependence on templates, maximize implementation hiding in the linked object, with little to nothing visible in the header files where possible. https://github.com/cppalliance/corosio and https://corosio.org
Fair point. I measured 13-22 s per TU for the examples (GCC 16, debug) on my machine. In practice, handlers are plain functions and don't include taar, so only the file that registers routes pays the cost. TCP and TLS are separate headers, so you only compile what you use. The binding itself has to be templates, since it is generated from the user's function signatures. But the session, the servers, static files, the OpenAPI writer and the JSON-RPC/MCP dispatch don't need to be. I'm looking at an optional separately-compiled mode for those, the way Asio, Beast, and JSON offer one, with header-only staying the default. I'm profiling the compile to see how much a compiled mode could actually save.
Initial profiling looks promising. Most of the compile time is a fixed cost. The session's Beast/Asio read and write path over tcp_stream, which is the same in every program. A quick prototype that compiles that part once brought hello_server from about 13 s to 6s on my machine. So I'm going to build a proper separately compiled I'm now not sure if header-only must stay the default or not. It will take some time to get right. Good thing that it's weekend and I can get busy with it. I'll report the numbers here when it's done. The handler binding will stay in headers, because it is generated from the user's own function signatures: rest(f, path_arg("id"), json_body_arg()) checks the arguments against the route at compile time, converts each one to the parameter's type, and derives the JSON Schema and OpenAPI description from the same types. That is what lets one plain C++ function serve REST, JSON-RPC and MCP without macros, code generation or hand-written schemas, and it costs almost nothing per route.
participants (8)
-
Artyom Beilis -
Christian Mazakas -
Julien Blanc -
Reza Jahanbakhshi -
reza.jahanbakhshi@gmail.com -
Seth -
Steve Gerbino -
Vinnie Falco