Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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
8 : //
9 :
10 : #ifndef BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP
11 : #define BOOST_HTTP_SERVER_ROUTE_HANDLER_HPP
12 :
13 : #include <boost/http/detail/config.hpp>
14 : #include <boost/http/server/router_types.hpp>
15 : #include <boost/capy/any_bufref.hpp>
16 : #include <boost/capy/buffers.hpp>
17 : #include <boost/capy/datastore.hpp>
18 : #include <boost/capy/task.hpp>
19 : #include <boost/http/request.hpp> // VFALCO forward declare?
20 : #include <boost/http/request_parser.hpp> // VFALCO forward declare?
21 : #include <boost/http/response.hpp> // VFALCO forward declare?
22 : #include <boost/http/serializer.hpp> // VFALCO forward declare?
23 : #include <boost/url/url_view.hpp>
24 : #include <boost/system/error_code.hpp>
25 : #include <memory>
26 :
27 : namespace boost {
28 : namespace http {
29 :
30 : struct acceptor_config
31 : {
32 : bool is_ssl;
33 : bool is_admin;
34 : };
35 :
36 : //-----------------------------------------------
37 :
38 : /** The coroutine task type returned by route handlers.
39 :
40 : Route handlers are coroutines that process HTTP requests and
41 : must return this type. The underlying @ref route_result
42 : (a `system::error_code`) communicates the routing disposition
43 : back to the router:
44 :
45 : @li Return @ref route::next to decline handling and allow
46 : subsequent handlers in the same route to process the request.
47 :
48 : @li Return @ref route::next_route to skip remaining handlers
49 : in the current route and proceed to the next matching route.
50 :
51 : @li Return a non-failing code (one for which
52 : `error_code::failed()` returns `false`) to indicate the
53 : response is complete. The connection will either close
54 : or proceed to the next request.
55 :
56 : @li Return a failing error code to signal an error that
57 : prevents normal processing.
58 :
59 : @par Example
60 : @code
61 : // A handler that serves static files
62 : route_task serve_file(route_params& p)
63 : {
64 : auto path = find_file(p.path);
65 : if(path.empty())
66 : co_return route::next; // Not found, try next handler
67 :
68 : p.res.set_body_file(path);
69 : co_return {}; // Success
70 : }
71 :
72 : // A handler that requires authentication
73 : route_task require_auth(route_params& p)
74 : {
75 : if(! p.session_data.contains<user_session>())
76 : {
77 : p.status(http::status::unauthorized);
78 : co_return {};
79 : }
80 : co_return route::next; // Authenticated, continue chain
81 : }
82 : @endcode
83 :
84 : @see @ref route_result, @ref route, @ref route_params
85 : */
86 : using route_task = capy::task<route_result>;
87 :
88 : //-----------------------------------------------
89 :
90 : /** Parameters object for HTTP route handlers
91 : */
92 : struct BOOST_HTTP_SYMBOL_VISIBLE
93 : route_params : route_params_base
94 : {
95 : /** The complete request target
96 :
97 : This is the parsed directly from the start
98 : line contained in the HTTP request and is
99 : never modified.
100 : */
101 : urls::url_view url;
102 :
103 : /** The HTTP request message
104 : */
105 : http::request req;
106 :
107 : /** The HTTP response message
108 : */
109 : http::response res;
110 :
111 : /** The HTTP request parser
112 : This can be used to take over reading the body.
113 : */
114 : http::request_parser parser;
115 :
116 : /** The HTTP response serializer
117 : */
118 : http::serializer serializer;
119 :
120 : /** A container for storing arbitrary data associated with the request.
121 : This starts out empty for each new request.
122 : */
123 : capy::datastore route_data;
124 :
125 : /** A container for storing arbitrary data associated with the session.
126 :
127 : This starts out empty for each new session.
128 : */
129 : capy::datastore session_data;
130 :
131 : /** Destructor
132 : */
133 : BOOST_HTTP_DECL
134 : ~route_params();
135 :
136 : /** Reset the object for a new request.
137 : This clears any state associated with
138 : the previous request, preparing the object
139 : for use with a new request.
140 : */
141 : BOOST_HTTP_DECL
142 : void reset();
143 :
144 : /** Set the status code of the response.
145 : @par Example
146 : @code
147 : res.status( http::status::not_found );
148 : @endcode
149 : @param code The status code to set.
150 : @return A reference to this response.
151 : */
152 : BOOST_HTTP_DECL
153 : route_params&
154 : status(http::status code);
155 :
156 : BOOST_HTTP_DECL
157 : route_params&
158 : set_body(std::string s);
159 :
160 : /** Send the HTTP response with the given body.
161 :
162 : This convenience coroutine handles the entire response
163 : lifecycle in a single call, similar to Express.js
164 : `res.send()`. It performs the following steps:
165 :
166 : @li Sets the response body to the provided string.
167 : @li Sets the `Content-Length` header automatically.
168 : @li If `Content-Type` is not already set, detects the
169 : type: bodies starting with `<` are sent as
170 : `text/html; charset=utf-8`, otherwise as
171 : `text/plain; charset=utf-8`.
172 : @li Serializes and transmits the complete response.
173 :
174 : After calling this function the response is complete.
175 : Do not attempt to modify or send additional data.
176 :
177 : @par Example
178 : @code
179 : // Plain text (no leading '<')
180 : route_task hello( route_params& rp )
181 : {
182 : co_return co_await rp.send( "Hello, World!" );
183 : }
184 :
185 : // HTML (starts with '<')
186 : route_task greeting( route_params& rp )
187 : {
188 : co_return co_await rp.send( "<h1>Welcome</h1>" );
189 : }
190 :
191 : // Explicit Content-Type for JSON
192 : route_task api( route_params& rp )
193 : {
194 : rp.res.set( http::field::content_type, "application/json" );
195 : co_return co_await rp.send( R"({"status":"ok"})" );
196 : }
197 : @endcode
198 :
199 : @param body The content to send as the response body.
200 :
201 : @return A @ref route_task that completes when the response
202 : has been fully transmitted, yielding a @ref route_result
203 : indicating success or failure.
204 : */
205 : virtual route_task send(std::string_view body) = 0;
206 :
207 : /** Write buffer data to the response body.
208 :
209 : This coroutine writes the provided buffer sequence to
210 : the response output stream. It is used for streaming
211 : responses where the body is sent in chunks.
212 :
213 : The response headers must be set appropriately before
214 : calling this function (e.g., set Transfer-Encoding to
215 : chunked, or set Content-Length if known).
216 :
217 : @par Example
218 : @code
219 : route_task stream_response( route_params& rp )
220 : {
221 : rp.res.set( field::transfer_encoding, "chunked" );
222 :
223 : // Write in chunks
224 : std::string chunk1 = "Hello, ";
225 : co_await rp.write( capy::const_buffer(
226 : chunk1.data(), chunk1.size() ) );
227 :
228 : std::string chunk2 = "World!";
229 : co_await rp.write( capy::const_buffer(
230 : chunk2.data(), chunk2.size() ) );
231 :
232 : co_return co_await rp.end();
233 : }
234 : @endcode
235 :
236 : @param buffers A buffer sequence containing the data to write.
237 :
238 : @return A @ref route_task that completes when the write
239 : operation is finished.
240 : */
241 : template<capy::ConstBufferSequence Buffers>
242 : route_task
243 0 : write(Buffers const& buffers)
244 : {
245 0 : return write_impl(capy::any_bufref(buffers));
246 : }
247 :
248 : /** Complete a streaming response.
249 :
250 : This coroutine finalizes a streaming response that was
251 : started with @ref write calls. For chunked transfers,
252 : it sends the final chunk terminator.
253 :
254 : @par Example
255 : @code
256 : route_task send_file( route_params& rp )
257 : {
258 : rp.res.set( field::transfer_encoding, "chunked" );
259 :
260 : // Stream file contents...
261 : while( ! file.eof() )
262 : {
263 : auto data = file.read();
264 : co_await rp.write( capy::const_buffer(
265 : data.data(), data.size() ) );
266 : }
267 :
268 : co_return co_await rp.end();
269 : }
270 : @endcode
271 :
272 : @return A @ref route_task that completes when the response
273 : has been fully finalized.
274 : */
275 : virtual route_task end() = 0;
276 :
277 : protected:
278 : /** Implementation of write with type-erased buffers.
279 :
280 : Derived classes must implement this to perform the
281 : actual write operation.
282 :
283 : @param buffers Type-erased buffer sequence.
284 :
285 : @return A task that completes when the write is done.
286 : */
287 : virtual route_task write_impl(capy::any_bufref buffers) = 0;
288 : };
289 :
290 : } // http
291 : } // boost
292 :
293 : #endif
|