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/beast2
8 : //
9 :
10 : #ifndef BOOST_BEAST2_SERVER_HTTP_SESSION_HPP
11 : #define BOOST_BEAST2_SERVER_HTTP_SESSION_HPP
12 :
13 : #include <boost/beast2/detail/config.hpp>
14 : #include <boost/beast2/application.hpp>
15 : #include <boost/beast2/log_service.hpp>
16 : #include <boost/beast2/format.hpp>
17 : #include <boost/beast2/read.hpp>
18 : #include <boost/beast2/write.hpp>
19 : #include <boost/beast2/server/any_lambda.hpp>
20 : #include <boost/beast2/server/basic_router.hpp>
21 : #include <boost/beast2/server/route_handler_asio.hpp>
22 : #include <boost/beast2/server/router_asio.hpp>
23 : #include <boost/beast2/error.hpp>
24 : #include <boost/beast2/detail/except.hpp>
25 : #include <boost/http_proto/request_parser.hpp>
26 : #include <boost/http_proto/response.hpp>
27 : #include <boost/http_proto/serializer.hpp>
28 : #include <boost/http_proto/string_body.hpp>
29 : #include <boost/url/parse.hpp>
30 : #include <boost/asio/prepend.hpp>
31 :
32 : namespace boost {
33 : namespace beast2 {
34 :
35 : //------------------------------------------------
36 : /*
37 :
38 : */
39 : /** Mixin for delivering responses to HTTP requests
40 : */
41 : template<class Stream>
42 : class http_session
43 : : private detacher::owner
44 : {
45 : public:
46 : http_session(
47 : application& app,
48 : Stream& stream,
49 : router_asio<Stream> rr,
50 : any_lambda<void(system::error_code)> close_fn);
51 :
52 : /** Called to start a new HTTP session
53 :
54 : The stream must be in a connected,
55 : correct state for a new session.
56 : */
57 : void do_session(acceptor_config const& config);
58 :
59 : private:
60 : void do_read();
61 :
62 : void on_read(
63 : system::error_code ec,
64 : std::size_t bytes_transferred);
65 :
66 : void on_write(
67 : system::error_code const& ec,
68 : std::size_t bytes_transferred);
69 :
70 : void do_fail(core::string_view s,
71 : system::error_code const& ec);
72 :
73 : resumer do_detach() override;
74 :
75 : void do_resume(system::error_code const& ec) override;
76 :
77 : void do_resume2(system::error_code ec);
78 :
79 : protected:
80 0 : std::string id() const
81 : {
82 0 : return std::string("[") + std::to_string(id_) + "] ";
83 : }
84 :
85 : protected:
86 : section sect_;
87 : std::size_t id_ = 0;
88 : Stream& stream_;
89 : router_asio<Stream> rr_;
90 : any_lambda<void(system::error_code)> close_;
91 : route_state route_state_;
92 : http_proto::request_parser pr_;
93 : http_proto::serializer sr_;
94 : http_proto::response res_;
95 : acceptor_config const* pconfig_ = nullptr;
96 :
97 : using work_guard = asio::executor_work_guard<decltype(
98 : std::declval<Stream&>().get_executor())>;
99 : std::unique_ptr<work_guard> pwg_;
100 : std::unique_ptr<Request> preq_;
101 : std::unique_ptr<ResponseAsio<Stream>> pres_;
102 : };
103 :
104 : //------------------------------------------------
105 :
106 : template<class Stream>
107 0 : http_session<Stream>::
108 : http_session(
109 : application& app,
110 : Stream& stream,
111 : router_asio<Stream> rr,
112 : any_lambda<void(system::error_code)> close)
113 0 : : sect_(use_log_service(app).get_section("http_session"))
114 0 : , id_(
115 0 : []() noexcept
116 : {
117 : static std::size_t n = 0;
118 0 : return ++n;
119 0 : }())
120 0 : , stream_(stream)
121 0 : , rr_(std::move(rr))
122 0 : , close_(close)
123 0 : , pr_(app.services())
124 0 : , sr_(app.services())
125 : {
126 0 : }
127 :
128 : /** Called to start a new HTTP session
129 :
130 : The stream must be in a connected,
131 : correct state for a new session.
132 : */
133 : template<class Stream>
134 : void
135 0 : http_session<Stream>::
136 : do_session(
137 : acceptor_config const& config)
138 : {
139 0 : pconfig_ = &config;
140 0 : pr_.reset();
141 0 : do_read();
142 0 : }
143 :
144 : template<class Stream>
145 : void
146 0 : http_session<Stream>::
147 : do_read()
148 : {
149 0 : pr_.start();
150 0 : sr_.reset();
151 0 : beast2::async_read(stream_, pr_,
152 0 : call_mf(&http_session::on_read, this));
153 0 : }
154 :
155 : template<class Stream>
156 : void
157 0 : http_session<Stream>::
158 : on_read(
159 : system::error_code ec,
160 : std::size_t bytes_transferred)
161 : {
162 : (void)bytes_transferred;
163 :
164 0 : if(ec.failed())
165 0 : return do_fail("http_session::on_read", ec);
166 :
167 0 : LOG_TRC(this->sect_)(
168 : "{} http_session::on_read bytes={}",
169 : this->id(), bytes_transferred);
170 :
171 0 : BOOST_ASSERT(pr_.is_complete());
172 :
173 : //----------------------------------------
174 : //
175 : // set up Request and Response objects
176 : //
177 :
178 0 : preq_.reset(new Request(
179 0 : *this->pconfig_,
180 0 : pr_.get(),
181 0 : pr_));
182 :
183 0 : pres_.reset(new ResponseAsio<Stream>(
184 : stream_,
185 0 : res_,
186 0 : sr_));
187 0 : pres_->detach = detacher(*this);
188 :
189 : // copy version
190 0 : pres_->m.set_version(preq_->m.version());
191 :
192 : // copy keep-alive setting
193 0 : pres_->m.set_start_line(
194 0 : http_proto::status::ok, pr_.get().version());
195 0 : pres_->m.set_keep_alive(pr_.get().keep_alive());
196 :
197 : // parse the URL
198 : {
199 0 : auto rv = urls::parse_uri_reference(pr_.get().target());
200 0 : if(rv.has_value())
201 : {
202 0 : preq_->url = rv.value();
203 0 : preq_->base_path = "";
204 0 : preq_->path = std::string(rv->encoded_path());
205 : }
206 : else
207 : {
208 : // error parsing URL
209 0 : pres_->status(
210 : http_proto::status::bad_request);
211 0 : pres_->set_body(
212 0 : "Bad Request: " + rv.error().message());
213 0 : goto do_write;
214 : }
215 : }
216 :
217 : // invoke handlers for the route
218 0 : BOOST_ASSERT(! pwg_);
219 0 : ec = rr_.dispatch(
220 0 : preq_->m.method(),
221 0 : preq_->url,
222 0 : *preq_, *pres_, route_state_);
223 0 : if(ec == route::send)
224 0 : goto do_write;
225 :
226 0 : if(ec == route::next)
227 : {
228 : // unhandled
229 0 : pres_->status(http_proto::status::not_found);
230 0 : std::string s;
231 0 : format_to(s, "The requested URL {} was not found on this server.", preq_->url);
232 : //pres_->m.set_keep_alive(false); // VFALCO?
233 0 : pres_->set_body(s);
234 0 : goto do_write;
235 0 : }
236 :
237 0 : if(ec == route::detach)
238 : {
239 : // make sure they called detach()
240 0 : BOOST_ASSERT(pwg_);
241 0 : return;
242 : }
243 :
244 : // error message of last resort
245 : {
246 0 : BOOST_ASSERT(ec.failed());
247 0 : pres_->status(http_proto::status::internal_server_error);
248 0 : std::string s;
249 0 : format_to(s, "An internal server error occurred: {}", ec.message());
250 : //pres_->m.set_keep_alive(false); // VFALCO?
251 0 : pres_->set_body(s);
252 0 : }
253 :
254 0 : do_write:
255 0 : if(sr_.is_done())
256 : {
257 : // happens when the handler sends the response
258 0 : return on_write(system::error_code(), 0);
259 : }
260 :
261 0 : beast2::async_write(stream_, sr_,
262 0 : call_mf(&http_session::on_write, this));
263 : }
264 :
265 : template<class Stream>
266 : void
267 0 : http_session<Stream>::
268 : on_write(
269 : system::error_code const& ec,
270 : std::size_t bytes_transferred)
271 : {
272 : (void)bytes_transferred;
273 :
274 0 : if(ec.failed())
275 0 : return do_fail("http_session::on_write", ec);
276 :
277 0 : BOOST_ASSERT(sr_.is_done());
278 :
279 0 : LOG_TRC(this->sect_)(
280 : "{} http_session::on_write bytes={}",
281 : this->id(), bytes_transferred);
282 :
283 0 : if(res_.keep_alive())
284 0 : return do_read();
285 :
286 : // tidy up lingering objects
287 0 : pr_.reset();
288 0 : sr_.reset();
289 0 : res_.clear();
290 0 : preq_.reset();
291 0 : pres_.reset();
292 :
293 0 : close_({});
294 : }
295 :
296 : template<class Stream>
297 : void
298 0 : http_session<Stream>::
299 : do_fail(
300 : core::string_view s, system::error_code const& ec)
301 : {
302 0 : LOG_TRC(this->sect_)("{}: {}", s, ec.message());
303 :
304 : // tidy up lingering objects
305 0 : pr_.reset();
306 0 : sr_.reset();
307 0 : res_.clear();
308 0 : preq_.reset();
309 0 : pres_.reset();
310 :
311 0 : close_(ec);
312 0 : }
313 :
314 : template<class Stream>
315 : auto
316 0 : http_session<Stream>::
317 : do_detach() ->
318 : resumer
319 : {
320 0 : BOOST_ASSERT(stream_.get_executor().running_in_this_thread());
321 :
322 : // can't call twice
323 0 : BOOST_ASSERT(! pwg_);
324 0 : pwg_.reset(new work_guard(stream_.get_executor()));
325 :
326 : // VFALCO cancel timer
327 :
328 0 : return resumer(*this);
329 : }
330 :
331 : template<class Stream>
332 : void
333 0 : http_session<Stream>::
334 : do_resume(system::error_code const& ec)
335 : {
336 0 : asio::dispatch(
337 0 : stream_.get_executor(),
338 0 : asio::prepend(call_mf(
339 : &http_session::do_resume2, this), ec));
340 0 : }
341 :
342 : template<class Stream>
343 : void
344 0 : http_session<Stream>::
345 : do_resume2(system::error_code ec)
346 : {
347 0 : BOOST_ASSERT(stream_.get_executor().running_in_this_thread());
348 :
349 0 : BOOST_ASSERT(pwg_.get() != nullptr);
350 0 : pwg_.reset();
351 :
352 : // invoke handlers for the route
353 0 : BOOST_ASSERT(! pwg_);
354 0 : ec = rr_.resume(*preq_, *pres_, ec, route_state_);
355 :
356 0 : if(ec == route::detach)
357 : {
358 : // make sure they called detach()
359 0 : BOOST_ASSERT(pwg_);
360 0 : return;
361 : }
362 :
363 0 : if(ec.failed())
364 : {
365 : // give a default error response?
366 : }
367 0 : beast2::async_write(stream_, sr_,
368 0 : call_mf(&http_session::on_write, this));
369 : }
370 :
371 : } // beast2
372 : } // boost
373 :
374 : #endif
|