Skip to content

Commit bc956f7

Browse files
vinniefalcoclaude
andcommitted
docs: add server router chapter
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 <noreply@anthropic.com>
1 parent 9244247 commit bc956f7

File tree

4 files changed

+352
-2
lines changed

4 files changed

+352
-2
lines changed

doc/modules/ROOT/nav.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
* Serializing
88
* Parsing
99
* xref:Message.adoc[]
10+
* Server
11+
** xref:server/router.adoc[Router]
12+
// ** xref:server/middleware.adoc[Middleware]
13+
// ** xref:server/errors.adoc[Error Handling]
14+
// ** xref:server/params.adoc[Route Parameters]
15+
// ** xref:server/advanced.adoc[Advanced Topics]
16+
// ** xref:server/cors.adoc[CORS]
1017
* Design Requirements
1118
** xref:design_requirements/serializer.adoc[Serializer]
1219
** xref:design_requirements/parser.adoc[Parser]
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
//
2+
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/http_proto
8+
//
9+
10+
= Server
11+
12+
This library provides an Express.js-style request dispatcher for HTTP servers.
13+
The interface is Sans-I/O: it handles routing and response generation without
14+
performing network operations. A separate I/O framework such as Boost.Beast2
15+
manages connections and drives the protocol.
16+
17+
== Router
18+
19+
cpp:router[] is a class template that implements request routing. It stores a
20+
collection of routes, each with a path pattern, HTTP method, and one or more
21+
handlers. Callers (typically a framework) use the router to dispatch an HTTP
22+
request to a handler.
23+
24+
=== Route Handler
25+
26+
Route handlers have this signature:
27+
[source,cpp]
28+
----
29+
route_result handler( route_params& rp );
30+
----
31+
32+
After this chapter you can: dispatch HTTP requests to handlers based on method
33+
and path, chain handlers together, and control request flow.
34+
35+
== Overview
36+
37+
The router is an Express.js-style request dispatcher. You register handlers
38+
for path patterns and HTTP methods, then dispatch incoming requests. The
39+
router matches the request against registered routes and invokes the
40+
appropriate handlers in order.
41+
42+
[source,cpp]
43+
----
44+
#include <boost/http_proto.hpp>
45+
46+
using namespace boost::http_proto;
47+
48+
basic_router<route_params> router;
49+
50+
router.add(method::get, "/hello",
51+
[](route_params& p)
52+
{
53+
p.status(status::ok);
54+
p.set_body("Hello, world!");
55+
return route::send;
56+
});
57+
----
58+
59+
The library provides `route_params` as the standard parameters type. It
60+
contains the request, response, URL, and other context needed by handlers.
61+
62+
== Handlers
63+
64+
A handler is any callable that accepts a reference to the params object and
65+
returns a `route_result`:
66+
67+
[source,cpp]
68+
----
69+
route_result handler(route_params& p);
70+
----
71+
72+
The return value tells the router what to do next:
73+
74+
[cols="1,3"]
75+
|===
76+
|Value |Meaning
77+
78+
|`route::send`
79+
|Response is ready. Send it to the client.
80+
81+
|`route::next`
82+
|Continue to the next handler in the chain.
83+
84+
|`route::next_route`
85+
|Skip remaining handlers in this route, try the next route.
86+
87+
|`route::close`
88+
|Close the connection after sending any response.
89+
90+
|`route::complete`
91+
|Request fully handled; no response to send.
92+
93+
|`route::detach`
94+
|Handler took ownership of the session (advanced).
95+
|===
96+
97+
Most handlers return `route::send` when they produce a response, or
98+
`route::next` when they perform setup work and defer to later handlers.
99+
100+
== Adding Routes
101+
102+
Use `add()` to register a handler for a specific HTTP method and path:
103+
104+
[source,cpp]
105+
----
106+
router.add(method::get, "/users", get_users);
107+
router.add(method::post, "/users", create_user);
108+
router.add(method::get, "/users/:id", get_user);
109+
router.add(method::put, "/users/:id", update_user);
110+
router.add(method::delete_, "/users/:id", delete_user);
111+
----
112+
113+
Use `all()` to match any HTTP method:
114+
115+
[source,cpp]
116+
----
117+
router.all("/status", check_status);
118+
----
119+
120+
== Fluent Route Interface
121+
122+
The `route()` method returns a fluent interface for registering multiple
123+
handlers on the same path:
124+
125+
[source,cpp]
126+
----
127+
router.route("/users/:id")
128+
.add(method::get, get_user)
129+
.add(method::put, update_user)
130+
.add(method::delete_, delete_user)
131+
.all(log_access);
132+
----
133+
134+
This is equivalent to calling `add()` separately for each method, but more
135+
concise when a path has multiple method handlers.
136+
137+
== Dispatching Requests
138+
139+
Call `dispatch()` to route a request:
140+
141+
[source,cpp]
142+
----
143+
route_params p;
144+
// ... populate p.req, p.url from parsed request ...
145+
146+
route_result rv = router.dispatch(method::get, p.url, p);
147+
148+
if(rv == route::send)
149+
{
150+
// p.res contains the response to send
151+
}
152+
else if(rv == route::next)
153+
{
154+
// No handler matched; send 404
155+
}
156+
----
157+
158+
The router tries each matching route in registration order. If a handler
159+
returns `route::next`, the router continues to the next handler. If all
160+
handlers return `route::next`, dispatch returns `route::next` to indicate
161+
no handler produced a response.
162+
163+
== Handler Chaining
164+
165+
Multiple handlers can be registered for the same route. They execute in
166+
order until one returns something other than `route::next`:
167+
168+
[source,cpp]
169+
----
170+
router.add(method::get, "/admin",
171+
[](route_params& p)
172+
{
173+
// Authentication check
174+
if(!is_authenticated(p))
175+
{
176+
p.status(status::unauthorized);
177+
p.set_body("Unauthorized");
178+
return route::send;
179+
}
180+
return route::next;
181+
},
182+
[](route_params& p)
183+
{
184+
// Authorization check
185+
if(!is_admin(p))
186+
{
187+
p.status(status::forbidden);
188+
p.set_body("Forbidden");
189+
return route::send;
190+
}
191+
return route::next;
192+
},
193+
[](route_params& p)
194+
{
195+
// Actual handler
196+
p.status(status::ok);
197+
p.set_body("Admin panel");
198+
return route::send;
199+
});
200+
----
201+
202+
This pattern separates concerns: authentication, authorization, and business
203+
logic each have their own handler.
204+
205+
== Path Patterns
206+
207+
Route paths support named parameters and wildcards:
208+
209+
[cols="1,2,2"]
210+
|===
211+
|Pattern |Example URL |Matches
212+
213+
|`/users`
214+
|`/users`
215+
|Exact match
216+
217+
|`/users/:id`
218+
|`/users/42`
219+
|Named parameter `id` = `"42"`
220+
221+
|`/files/*`
222+
|`/files/docs/readme.txt`
223+
|Wildcard suffix
224+
|===
225+
226+
Path matching is case-insensitive by default. Use `router_options` to change
227+
this behavior.
228+
229+
== Router Options
230+
231+
Configure matching behavior when constructing the router:
232+
233+
[source,cpp]
234+
----
235+
basic_router<route_params> router(
236+
router_options()
237+
.case_sensitive(true) // Paths are case-sensitive
238+
.strict(true)); // Trailing slash matters
239+
----
240+
241+
[cols="1,1,3"]
242+
|===
243+
|Option |Default |Description
244+
245+
|`case_sensitive`
246+
|`false`
247+
|When true, `/Users` and `/users` are different routes.
248+
249+
|`strict`
250+
|`false`
251+
|When true, `/api` and `/api/` are different routes.
252+
253+
|`merge_params`
254+
|`false`
255+
|When true, inherit parameters from parent routers.
256+
|===
257+
258+
== Complete Example
259+
260+
[source,cpp]
261+
----
262+
#include <boost/http_proto.hpp>
263+
264+
using namespace boost::http_proto;
265+
266+
int main()
267+
{
268+
basic_router<route_params> router;
269+
270+
// Health check endpoint
271+
router.add(method::get, "/health",
272+
[](route_params& p)
273+
{
274+
p.status(status::ok);
275+
p.set_body("OK");
276+
return route::send;
277+
});
278+
279+
// API routes
280+
router.route("/api/echo")
281+
.add(method::post,
282+
[](route_params& p)
283+
{
284+
p.status(status::ok);
285+
// Echo back the request body
286+
return route::send;
287+
})
288+
.add(method::get,
289+
[](route_params& p)
290+
{
291+
p.status(status::method_not_allowed);
292+
return route::send;
293+
});
294+
295+
// Dispatch a request
296+
route_params p;
297+
auto rv = router.dispatch(
298+
method::get,
299+
urls::url_view("/health"),
300+
p);
301+
302+
// rv == route::send, p.res contains "OK"
303+
}
304+
----
305+
306+
== See Also
307+
308+
* xref:server/middleware.adoc[Middleware] - Path-based handler chains
309+
* xref:server/errors.adoc[Error Handling] - Error and exception handlers
310+
* xref:server/params.adoc[Route Parameters] - The `route_params` object

include/boost/http_proto.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
#include <boost/http_proto/request_parser.hpp>
2929
#include <boost/http_proto/response.hpp>
3030
#include <boost/http_proto/response_base.hpp>
31-
#include <boost/http_proto/response_parser.hpp>
3231
#include <boost/http_proto/serializer.hpp>
3332
#include <boost/http_proto/sink.hpp>
3433
#include <boost/http_proto/source.hpp>
@@ -37,7 +36,7 @@
3736
#include <boost/http_proto/status.hpp>
3837
#include <boost/http_proto/string_body.hpp>
3938
#include <boost/http_proto/version.hpp>
40-
39+
4140
#include <boost/http_proto/rfc/combine_field_values.hpp>
4241
#include <boost/http_proto/rfc/list_rule.hpp>
4342
//#include <boost/http_proto/rfc/media_type.hpp>
@@ -50,6 +49,7 @@
5049
#include <boost/http_proto/server/basic_router.hpp>
5150
#include <boost/http_proto/server/cors.hpp>
5251
#include <boost/http_proto/server/route_handler.hpp>
52+
#include <boost/http_proto/server/router.hpp>
5353
#include <boost/http_proto/server/router_types.hpp>
5454

5555
#endif
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/http_proto
8+
//
9+
10+
#ifndef BOOST_HTTP_PROTO_SERVER_ROUTER_HPP
11+
#define BOOST_HTTP_PROTO_SERVER_ROUTER_HPP
12+
13+
#include <boost/http_proto/detail/config.hpp>
14+
#include <boost/http_proto/server/basic_router.hpp>
15+
16+
namespace boost {
17+
namespace http_proto {
18+
19+
/** A router for HTTP servers
20+
21+
This is a specialization of `basic_router` using
22+
`route_params` as the handler parameter type.
23+
24+
@see
25+
@ref basic_router,
26+
@ref route_params
27+
*/
28+
using router = basic_router<route_params>;
29+
30+
} // http_proto
31+
} // boost
32+
33+
#endif

0 commit comments

Comments
 (0)