From a0b8f4906d6f4302373c8a7646db68a0e8b99f74 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 15 Dec 2025 19:51:24 -0800 Subject: [PATCH 1/3] chore: spelling --- include/boost/http_proto/server/cors.hpp | 2 +- src/server/cors.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/http_proto/server/cors.hpp b/include/boost/http_proto/server/cors.hpp index fdfc0e1b..b936ed0a 100644 --- a/include/boost/http_proto/server/cors.hpp +++ b/include/boost/http_proto/server/cors.hpp @@ -26,7 +26,7 @@ struct cors_options std::string exposedHeaders; std::chrono::seconds max_age{ 0 }; status result = status::no_content; - bool preFligthContinue = false; + bool preFlightContinue = false; bool credentials = false; }; diff --git a/src/server/cors.cpp b/src/server/cors.cpp index 25f5b5a6..528b51f5 100644 --- a/src/server/cors.cpp +++ b/src/server/cors.cpp @@ -170,7 +170,7 @@ operator()( setMaxAge(v, options_); setExposeHeaders(v, options_); - if(options_.preFligthContinue) + if(options_.preFlightContinue) return route::next; // Safari and others need this for 204 or may hang p.res.set_status(options_.result); From c7100594e8fca96ac2389d8d71caf2be79eee4a3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 15 Dec 2025 19:51:32 -0800 Subject: [PATCH 2/3] chore: describe project in CLAUDE.md --- CLAUDE.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c14dd921..3057a717 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ - Symbols in "detail" namespaces are never public - public headers in "include/" - library cpp files in "src/" +- no ABI compatibility guarantee in different Boost version ## Javadoc Documentation @@ -61,20 +62,83 @@ T default_value(); - Full files, not diffs - Accurate, compiling C++ code -## Symbol Visibility +# Project -- Mark **all public classes with virtual functions or virtual base classes** with - `BOOST_HTTP_PROTO_SYMBOL_VISIBLE`. +## Structure - - This is required for: - - DSO (Dynamic Shared Object) builds compiled with hidden visibility. - - DLL builds with MinGW GCC due to its [non-conformance with MSVC](https://github.com/cppalliance/http_proto/issues/214). +* Boost.Beast2 refers to both the library which uses Boost.Asio and the collection of + libraries which includes Boost.Capy, Boost.Buffers, Boost.Http (http-proto), and Boost.Beast2 -- Mark **all public exception types** and **public classes used as the operand of `dynamic_cast`** with - `BOOST_HTTP_PROTO_DECL`. +* Boost.Capy contains general purpose data structures and algorithms - - This ensures: - - RTTI (typeinfo) is exported from DLLs. - - RTTI is visible from DSOs. - - Once a class is marked with `BOOST_HTTP_PROTO_DECL`, **all of its member functions are exported automatically**. - - Do **not** mark the member functions individually. +* Boost.Buffers provides Asio-style buffers without requiring Asio, and it + introduces blocking source and sink types with buffered variants + +* Boost.Http provides a Sans-IO HTTP which includes the following: + - Containers for request and response headers + - Low level HTTP algorithms such as parsing, serialization, and field processing + - High level HTTP Server components + - High level HTTP Client components + +* Boost.Beast2 builds on Capy, Buffers, and Boost.Http by providing the I/O layer + using Boost.Asio. It can open ports and maintain TCP connections. + +* Boost.Http Server component is based on an Express JS style router, ported to + idiomatic C++ + + - In the C++ router, route handlers are declarative rather than imperative. They + package up the response using a Sans-IO API. Beast2 handles the sesnding + and receiving. + + - Alternatively, a route handler can opt-in to seeing the Asio stream type + and its associated executor, and bypass the declarative framework to take + over the async aspect of sending and receiving the bytes. + + - Sans-IO route handlers can be built depending only on Boost.Http (no Asio) + while opting-in to seeing the Asio stream allows for C++20 style co_await + and all the other async continuation models that Asio supports. + +# Documentation + +## System Context + +You are an expert programming educator creating tutorial documentation for expert C++ programmers. +Goal: Minimize time-to-competence. Maximize learning velocity. +Style: Problem-first, progressive disclosure, concrete examples. + +## Instructions + +INPUT ANALYSIS +- Identify distinct problems this API solves +- Map API concepts to problem domains +- Determine prerequisite knowledge (assume expert C++, standard dependencies) + +CHAPTER DESIGN +- One problem domain per chapter +- Order by: simplest useful → most powerful +- Each chapter states upfront: "After this you can [solve X problem]" +- Build dependency chain: later chapters assume earlier ones + +CHAPTER STRUCTURE +1. Problem hook: concrete scenario reader wants to solve +2. Minimal working solution: fastest path to success +3. Progressive enhancement: add one capability at a time +4. Each step: why this matters, what it unlocks +5. End state: reader can now [do concrete thing] + +OPTIMIZATION PRINCIPLES +- No teaching C++ basics or documented dependencies +- Show power immediately (avoid toy examples) +- Concrete > abstract (real problems > hypotheticals) +- Remove anything not on critical path to competence +- Examples must compile and demonstrate capability +- Emotional tone: "you can do powerful things quickly" + +TRANSPARENCY +- State chapter scope and outcomes first +- Flag prerequisites explicitly +- No surprise complexity or hidden dependencies + +VALIDATION METRIC +- Could reader A/B test learn faster from this than alternatives? +- Does each paragraph reduce time-to-competence? From 648c808f16e77e0270df38b4058f24df16e70fce Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 15 Dec 2025 20:09:40 -0800 Subject: [PATCH 3/3] docs: add server router chapter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add initial documentation for the Express.js-style router API covering: - Handler signatures and return values - Adding routes with add() and all() - Fluent route() interface - Dispatching requests - Handler chaining - Path patterns - Router options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- doc/modules/ROOT/nav.adoc | 7 + doc/modules/ROOT/pages/server/router.adoc | 310 +++++++++++++++++++++ include/boost/http_proto.hpp | 4 +- include/boost/http_proto/server/router.hpp | 33 +++ 4 files changed, 352 insertions(+), 2 deletions(-) create mode 100644 doc/modules/ROOT/pages/server/router.adoc create mode 100644 include/boost/http_proto/server/router.hpp diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index dc863de9..22df34d1 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -7,6 +7,13 @@ * Serializing * Parsing * xref:Message.adoc[] +* Server +** xref:server/router.adoc[Router] +// ** xref:server/middleware.adoc[Middleware] +// ** xref:server/errors.adoc[Error Handling] +// ** xref:server/params.adoc[Route Parameters] +// ** xref:server/advanced.adoc[Advanced Topics] +// ** xref:server/cors.adoc[CORS] * Design Requirements ** xref:design_requirements/serializer.adoc[Serializer] ** xref:design_requirements/parser.adoc[Parser] diff --git a/doc/modules/ROOT/pages/server/router.adoc b/doc/modules/ROOT/pages/server/router.adoc new file mode 100644 index 00000000..06567ec6 --- /dev/null +++ b/doc/modules/ROOT/pages/server/router.adoc @@ -0,0 +1,310 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + += Server + +This library provides an Express.js-style request dispatcher for HTTP servers. +The interface is Sans-I/O: it handles routing and response generation without +performing network operations. A separate I/O framework such as Boost.Beast2 +manages connections and drives the protocol. + +== Router + +cpp:router[] is a class template that implements request routing. It stores a +collection of routes, each with a path pattern, HTTP method, and one or more +handlers. Callers (typically a framework) use the router to dispatch an HTTP +request to a handler. + +=== Route Handler + +Route handlers have this signature: +[source,cpp] +---- +route_result handler( route_params& rp ); +---- + +After this chapter you can: dispatch HTTP requests to handlers based on method +and path, chain handlers together, and control request flow. + +== Overview + +The router is an Express.js-style request dispatcher. You register handlers +for path patterns and HTTP methods, then dispatch incoming requests. The +router matches the request against registered routes and invokes the +appropriate handlers in order. + +[source,cpp] +---- +#include + +using namespace boost::http_proto; + +basic_router router; + +router.add(method::get, "/hello", + [](route_params& p) + { + p.status(status::ok); + p.set_body("Hello, world!"); + return route::send; + }); +---- + +The library provides `route_params` as the standard parameters type. It +contains the request, response, URL, and other context needed by handlers. + +== Handlers + +A handler is any callable that accepts a reference to the params object and +returns a `route_result`: + +[source,cpp] +---- +route_result handler(route_params& p); +---- + +The return value tells the router what to do next: + +[cols="1,3"] +|=== +|Value |Meaning + +|`route::send` +|Response is ready. Send it to the client. + +|`route::next` +|Continue to the next handler in the chain. + +|`route::next_route` +|Skip remaining handlers in this route, try the next route. + +|`route::close` +|Close the connection after sending any response. + +|`route::complete` +|Request fully handled; no response to send. + +|`route::detach` +|Handler took ownership of the session (advanced). +|=== + +Most handlers return `route::send` when they produce a response, or +`route::next` when they perform setup work and defer to later handlers. + +== Adding Routes + +Use `add()` to register a handler for a specific HTTP method and path: + +[source,cpp] +---- +router.add(method::get, "/users", get_users); +router.add(method::post, "/users", create_user); +router.add(method::get, "/users/:id", get_user); +router.add(method::put, "/users/:id", update_user); +router.add(method::delete_, "/users/:id", delete_user); +---- + +Use `all()` to match any HTTP method: + +[source,cpp] +---- +router.all("/status", check_status); +---- + +== Fluent Route Interface + +The `route()` method returns a fluent interface for registering multiple +handlers on the same path: + +[source,cpp] +---- +router.route("/users/:id") + .add(method::get, get_user) + .add(method::put, update_user) + .add(method::delete_, delete_user) + .all(log_access); +---- + +This is equivalent to calling `add()` separately for each method, but more +concise when a path has multiple method handlers. + +== Dispatching Requests + +Call `dispatch()` to route a request: + +[source,cpp] +---- +route_params p; +// ... populate p.req, p.url from parsed request ... + +route_result rv = router.dispatch(method::get, p.url, p); + +if(rv == route::send) +{ + // p.res contains the response to send +} +else if(rv == route::next) +{ + // No handler matched; send 404 +} +---- + +The router tries each matching route in registration order. If a handler +returns `route::next`, the router continues to the next handler. If all +handlers return `route::next`, dispatch returns `route::next` to indicate +no handler produced a response. + +== Handler Chaining + +Multiple handlers can be registered for the same route. They execute in +order until one returns something other than `route::next`: + +[source,cpp] +---- +router.add(method::get, "/admin", + [](route_params& p) + { + // Authentication check + if(!is_authenticated(p)) + { + p.status(status::unauthorized); + p.set_body("Unauthorized"); + return route::send; + } + return route::next; + }, + [](route_params& p) + { + // Authorization check + if(!is_admin(p)) + { + p.status(status::forbidden); + p.set_body("Forbidden"); + return route::send; + } + return route::next; + }, + [](route_params& p) + { + // Actual handler + p.status(status::ok); + p.set_body("Admin panel"); + return route::send; + }); +---- + +This pattern separates concerns: authentication, authorization, and business +logic each have their own handler. + +== Path Patterns + +Route paths support named parameters and wildcards: + +[cols="1,2,2"] +|=== +|Pattern |Example URL |Matches + +|`/users` +|`/users` +|Exact match + +|`/users/:id` +|`/users/42` +|Named parameter `id` = `"42"` + +|`/files/*` +|`/files/docs/readme.txt` +|Wildcard suffix +|=== + +Path matching is case-insensitive by default. Use `router_options` to change +this behavior. + +== Router Options + +Configure matching behavior when constructing the router: + +[source,cpp] +---- +basic_router router( + router_options() + .case_sensitive(true) // Paths are case-sensitive + .strict(true)); // Trailing slash matters +---- + +[cols="1,1,3"] +|=== +|Option |Default |Description + +|`case_sensitive` +|`false` +|When true, `/Users` and `/users` are different routes. + +|`strict` +|`false` +|When true, `/api` and `/api/` are different routes. + +|`merge_params` +|`false` +|When true, inherit parameters from parent routers. +|=== + +== Complete Example + +[source,cpp] +---- +#include + +using namespace boost::http_proto; + +int main() +{ + basic_router router; + + // Health check endpoint + router.add(method::get, "/health", + [](route_params& p) + { + p.status(status::ok); + p.set_body("OK"); + return route::send; + }); + + // API routes + router.route("/api/echo") + .add(method::post, + [](route_params& p) + { + p.status(status::ok); + // Echo back the request body + return route::send; + }) + .add(method::get, + [](route_params& p) + { + p.status(status::method_not_allowed); + return route::send; + }); + + // Dispatch a request + route_params p; + auto rv = router.dispatch( + method::get, + urls::url_view("/health"), + p); + + // rv == route::send, p.res contains "OK" +} +---- + +== See Also + +* xref:server/middleware.adoc[Middleware] - Path-based handler chains +* xref:server/errors.adoc[Error Handling] - Error and exception handlers +* xref:server/params.adoc[Route Parameters] - The `route_params` object diff --git a/include/boost/http_proto.hpp b/include/boost/http_proto.hpp index 8944aba9..f552d00c 100644 --- a/include/boost/http_proto.hpp +++ b/include/boost/http_proto.hpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -37,7 +36,7 @@ #include #include #include - + #include #include //#include @@ -50,6 +49,7 @@ #include #include #include +#include #include #endif diff --git a/include/boost/http_proto/server/router.hpp b/include/boost/http_proto/server/router.hpp new file mode 100644 index 00000000..689d203e --- /dev/null +++ b/include/boost/http_proto/server/router.hpp @@ -0,0 +1,33 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/http_proto +// + +#ifndef BOOST_HTTP_PROTO_SERVER_ROUTER_HPP +#define BOOST_HTTP_PROTO_SERVER_ROUTER_HPP + +#include +#include + +namespace boost { +namespace http_proto { + +/** A router for HTTP servers + + This is a specialization of `basic_router` using + `route_params` as the handler parameter type. + + @see + @ref basic_router, + @ref route_params +*/ +using router = basic_router; + +} // http_proto +} // boost + +#endif \ No newline at end of file