1//
2// Copyright (c) 2016-2019 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/boostorg/beast
8//
9
10//------------------------------------------------------------------------------
11//
12// Example: Advanced server, flex (plain + SSL)
13//
14//------------------------------------------------------------------------------
15
16#include "example/common/server_certificate.hpp"
17
18#include <boost/beast/core.hpp>
19#include <boost/beast/http.hpp>
20#include <boost/beast/ssl.hpp>
21#include <boost/beast/websocket.hpp>
22#include <boost/beast/version.hpp>
23#include <boost/asio/bind_executor.hpp>
24#include <boost/asio/dispatch.hpp>
25#include <boost/asio/signal_set.hpp>
26#include <boost/asio/strand.hpp>
27#include <boost/make_unique.hpp>
28#include <boost/optional.hpp>
29#include <algorithm>
30#include <cstdlib>
31#include <functional>
32#include <iostream>
33#include <memory>
34#include <queue>
35#include <string>
36#include <thread>
37#include <vector>
38
39namespace beast = boost::beast; // from <boost/beast.hpp>
40namespace http = beast::http; // from <boost/beast/http.hpp>
41namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
42namespace net = boost::asio; // from <boost/asio.hpp>
43namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
44using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
45
46// Return a reasonable mime type based on the extension of a file.
47beast::string_view
48mime_type(beast::string_view path)
49{
50 using beast::iequals;
51 auto const ext = [&path]
52 {
53 auto const pos = path.rfind(s: ".");
54 if(pos == beast::string_view::npos)
55 return beast::string_view{};
56 return path.substr(pos);
57 }();
58 if(iequals(lhs: ext, rhs: ".htm")) return "text/html";
59 if(iequals(lhs: ext, rhs: ".html")) return "text/html";
60 if(iequals(lhs: ext, rhs: ".php")) return "text/html";
61 if(iequals(lhs: ext, rhs: ".css")) return "text/css";
62 if(iequals(lhs: ext, rhs: ".txt")) return "text/plain";
63 if(iequals(lhs: ext, rhs: ".js")) return "application/javascript";
64 if(iequals(lhs: ext, rhs: ".json")) return "application/json";
65 if(iequals(lhs: ext, rhs: ".xml")) return "application/xml";
66 if(iequals(lhs: ext, rhs: ".swf")) return "application/x-shockwave-flash";
67 if(iequals(lhs: ext, rhs: ".flv")) return "video/x-flv";
68 if(iequals(lhs: ext, rhs: ".png")) return "image/png";
69 if(iequals(lhs: ext, rhs: ".jpe")) return "image/jpeg";
70 if(iequals(lhs: ext, rhs: ".jpeg")) return "image/jpeg";
71 if(iequals(lhs: ext, rhs: ".jpg")) return "image/jpeg";
72 if(iequals(lhs: ext, rhs: ".gif")) return "image/gif";
73 if(iequals(lhs: ext, rhs: ".bmp")) return "image/bmp";
74 if(iequals(lhs: ext, rhs: ".ico")) return "image/vnd.microsoft.icon";
75 if(iequals(lhs: ext, rhs: ".tiff")) return "image/tiff";
76 if(iequals(lhs: ext, rhs: ".tif")) return "image/tiff";
77 if(iequals(lhs: ext, rhs: ".svg")) return "image/svg+xml";
78 if(iequals(lhs: ext, rhs: ".svgz")) return "image/svg+xml";
79 return "application/text";
80}
81
82// Append an HTTP rel-path to a local filesystem path.
83// The returned path is normalized for the platform.
84std::string
85path_cat(
86 beast::string_view base,
87 beast::string_view path)
88{
89 if(base.empty())
90 return std::string(path);
91 std::string result(base);
92#ifdef BOOST_MSVC
93 char constexpr path_separator = '\\';
94 if(result.back() == path_separator)
95 result.resize(result.size() - 1);
96 result.append(path.data(), path.size());
97 for(auto& c : result)
98 if(c == '/')
99 c = path_separator;
100#else
101 char constexpr path_separator = '/';
102 if(result.back() == path_separator)
103 result.resize(n: result.size() - 1);
104 result.append(s: path.data(), n: path.size());
105#endif
106 return result;
107}
108
109// Return a response for the given request.
110//
111// The concrete type of the response message (which depends on the
112// request), is type-erased in message_generator.
113template<class Body, class Allocator>
114http::message_generator
115handle_request(
116 beast::string_view doc_root,
117 http::request<Body, http::basic_fields<Allocator>>&& req)
118{
119 // Returns a bad request response
120 auto const bad_request =
121 [&req](beast::string_view why)
122 {
123 http::response<http::string_body> res{http::status::bad_request, req.version()};
124 res.set(name: http::field::server, BOOST_BEAST_VERSION_STRING);
125 res.set(name: http::field::content_type, value: "text/html");
126 res.keep_alive(req.keep_alive());
127 res.body() = std::string(why);
128 res.prepare_payload();
129 return res;
130 };
131
132 // Returns a not found response
133 auto const not_found =
134 [&req](beast::string_view target)
135 {
136 http::response<http::string_body> res{http::status::not_found, req.version()};
137 res.set(name: http::field::server, BOOST_BEAST_VERSION_STRING);
138 res.set(name: http::field::content_type, value: "text/html");
139 res.keep_alive(req.keep_alive());
140 res.body() = "The resource '" + std::string(target) + "' was not found.";
141 res.prepare_payload();
142 return res;
143 };
144
145 // Returns a server error response
146 auto const server_error =
147 [&req](beast::string_view what)
148 {
149 http::response<http::string_body> res{http::status::internal_server_error, req.version()};
150 res.set(name: http::field::server, BOOST_BEAST_VERSION_STRING);
151 res.set(name: http::field::content_type, value: "text/html");
152 res.keep_alive(req.keep_alive());
153 res.body() = "An error occurred: '" + std::string(what) + "'";
154 res.prepare_payload();
155 return res;
156 };
157
158 // Make sure we can handle the method
159 if( req.method() != http::verb::get &&
160 req.method() != http::verb::head)
161 return bad_request("Unknown HTTP-method");
162
163 // Request path must be absolute and not contain "..".
164 if( req.target().empty() ||
165 req.target()[0] != '/' ||
166 req.target().find("..") != beast::string_view::npos)
167 return bad_request("Illegal request-target");
168
169 // Build the path to the requested file
170 std::string path = path_cat(doc_root, req.target());
171 if(req.target().back() == '/')
172 path.append(s: "index.html");
173
174 // Attempt to open the file
175 beast::error_code ec;
176 http::file_body::value_type body;
177 body.open(path: path.c_str(), mode: beast::file_mode::scan, ec);
178
179 // Handle the case where the file doesn't exist
180 if(ec == beast::errc::no_such_file_or_directory)
181 return not_found(req.target());
182
183 // Handle an unknown error
184 if(ec)
185 return server_error(ec.message());
186
187 // Cache the size since we need it after the move
188 auto const size = body.size();
189
190 // Respond to HEAD request
191 if(req.method() == http::verb::head)
192 {
193 http::response<http::empty_body> res{http::status::ok, req.version()};
194 res.set(name: http::field::server, BOOST_BEAST_VERSION_STRING);
195 res.set(name: http::field::content_type, value: mime_type(path));
196 res.content_length(value: size);
197 res.keep_alive(req.keep_alive());
198 return res;
199 }
200
201 // Respond to GET request
202 http::response<http::file_body> res{
203 std::piecewise_construct,
204 std::make_tuple(args: std::move(body)),
205 std::make_tuple(http::status::ok, req.version())};
206 res.set(name: http::field::server, BOOST_BEAST_VERSION_STRING);
207 res.set(name: http::field::content_type, value: mime_type(path));
208 res.content_length(value: size);
209 res.keep_alive(req.keep_alive());
210 return res;
211}
212
213//------------------------------------------------------------------------------
214
215// Report a failure
216void
217fail(beast::error_code ec, char const* what)
218{
219 // ssl::error::stream_truncated, also known as an SSL "short read",
220 // indicates the peer closed the connection without performing the
221 // required closing handshake (for example, Google does this to
222 // improve performance). Generally this can be a security issue,
223 // but if your communication protocol is self-terminated (as
224 // it is with both HTTP and WebSocket) then you may simply
225 // ignore the lack of close_notify.
226 //
227 // https://github.com/boostorg/beast/issues/38
228 //
229 // https://security.stackexchange.com/questions/91435/how-to-handle-a-malicious-ssl-tls-shutdown
230 //
231 // When a short read would cut off the end of an HTTP message,
232 // Beast returns the error beast::http::error::partial_message.
233 // Therefore, if we see a short read here, it has occurred
234 // after the message has been completed, so it is safe to ignore it.
235
236 if(ec == net::ssl::error::stream_truncated)
237 return;
238
239 std::cerr << what << ": " << ec.message() << "\n";
240}
241
242//------------------------------------------------------------------------------
243
244// Echoes back all received WebSocket messages.
245// This uses the Curiously Recurring Template Pattern so that
246// the same code works with both SSL streams and regular sockets.
247template<class Derived>
248class websocket_session
249{
250 // Access the derived class, this is part of
251 // the Curiously Recurring Template Pattern idiom.
252 Derived&
253 derived()
254 {
255 return static_cast<Derived&>(*this);
256 }
257
258 beast::flat_buffer buffer_;
259
260 // Start the asynchronous operation
261 template<class Body, class Allocator>
262 void
263 do_accept(http::request<Body, http::basic_fields<Allocator>> req)
264 {
265 // Set suggested timeout settings for the websocket
266 derived().ws().set_option(
267 websocket::stream_base::timeout::suggested(
268 role: beast::role_type::server));
269
270 // Set a decorator to change the Server of the handshake
271 derived().ws().set_option(
272 websocket::stream_base::decorator(
273 [](websocket::response_type& res)
274 {
275 res.set(name: http::field::server,
276 value: std::string(BOOST_BEAST_VERSION_STRING) +
277 " advanced-server-flex");
278 }));
279
280 // Accept the websocket handshake
281 derived().ws().async_accept(
282 req,
283 beast::bind_front_handler(
284 &websocket_session::on_accept,
285 derived().shared_from_this()));
286 }
287
288private:
289 void
290 on_accept(beast::error_code ec)
291 {
292 if(ec)
293 return fail(ec, what: "accept");
294
295 // Read a message
296 do_read();
297 }
298
299 void
300 do_read()
301 {
302 // Read a message into our buffer
303 derived().ws().async_read(
304 buffer_,
305 beast::bind_front_handler(
306 &websocket_session::on_read,
307 derived().shared_from_this()));
308 }
309
310 void
311 on_read(
312 beast::error_code ec,
313 std::size_t bytes_transferred)
314 {
315 boost::ignore_unused(bytes_transferred);
316
317 // This indicates that the websocket_session was closed
318 if(ec == websocket::error::closed)
319 return;
320
321 if(ec)
322 return fail(ec, what: "read");
323
324 // Echo the message
325 derived().ws().text(derived().ws().got_text());
326 derived().ws().async_write(
327 buffer_.data(),
328 beast::bind_front_handler(
329 &websocket_session::on_write,
330 derived().shared_from_this()));
331 }
332
333 void
334 on_write(
335 beast::error_code ec,
336 std::size_t bytes_transferred)
337 {
338 boost::ignore_unused(bytes_transferred);
339
340 if(ec)
341 return fail(ec, what: "write");
342
343 // Clear the buffer
344 buffer_.consume(n: buffer_.size());
345
346 // Do another read
347 do_read();
348 }
349
350public:
351 // Start the asynchronous operation
352 template<class Body, class Allocator>
353 void
354 run(http::request<Body, http::basic_fields<Allocator>> req)
355 {
356 // Accept the WebSocket upgrade request
357 do_accept(std::move(req));
358 }
359};
360
361//------------------------------------------------------------------------------
362
363// Handles a plain WebSocket connection
364class plain_websocket_session
365 : public websocket_session<plain_websocket_session>
366 , public std::enable_shared_from_this<plain_websocket_session>
367{
368 websocket::stream<beast::tcp_stream> ws_;
369
370public:
371 // Create the session
372 explicit
373 plain_websocket_session(
374 beast::tcp_stream&& stream)
375 : ws_(std::move(stream))
376 {
377 }
378
379 // Called by the base class
380 websocket::stream<beast::tcp_stream>&
381 ws()
382 {
383 return ws_;
384 }
385};
386
387//------------------------------------------------------------------------------
388
389// Handles an SSL WebSocket connection
390class ssl_websocket_session
391 : public websocket_session<ssl_websocket_session>
392 , public std::enable_shared_from_this<ssl_websocket_session>
393{
394 websocket::stream<
395 beast::ssl_stream<beast::tcp_stream>> ws_;
396
397public:
398 // Create the ssl_websocket_session
399 explicit
400 ssl_websocket_session(
401 beast::ssl_stream<beast::tcp_stream>&& stream)
402 : ws_(std::move(stream))
403 {
404 }
405
406 // Called by the base class
407 websocket::stream<
408 beast::ssl_stream<beast::tcp_stream>>&
409 ws()
410 {
411 return ws_;
412 }
413};
414
415//------------------------------------------------------------------------------
416
417template<class Body, class Allocator>
418void
419make_websocket_session(
420 beast::tcp_stream stream,
421 http::request<Body, http::basic_fields<Allocator>> req)
422{
423 std::make_shared<plain_websocket_session>(
424 args: std::move(stream))->run(std::move(req));
425}
426
427template<class Body, class Allocator>
428void
429make_websocket_session(
430 beast::ssl_stream<beast::tcp_stream> stream,
431 http::request<Body, http::basic_fields<Allocator>> req)
432{
433 std::make_shared<ssl_websocket_session>(
434 args: std::move(stream))->run(std::move(req));
435}
436
437//------------------------------------------------------------------------------
438
439// Handles an HTTP server connection.
440// This uses the Curiously Recurring Template Pattern so that
441// the same code works with both SSL streams and regular sockets.
442template<class Derived>
443class http_session
444{
445 std::shared_ptr<std::string const> doc_root_;
446
447 // Access the derived class, this is part of
448 // the Curiously Recurring Template Pattern idiom.
449 Derived&
450 derived()
451 {
452 return static_cast<Derived&>(*this);
453 }
454
455 static constexpr std::size_t queue_limit = 8; // max responses
456 std::queue<http::message_generator> response_queue_;
457
458 // The parser is stored in an optional container so we can
459 // construct it from scratch it at the beginning of each new message.
460 boost::optional<http::request_parser<http::string_body>> parser_;
461
462protected:
463 beast::flat_buffer buffer_;
464
465public:
466 // Construct the session
467 http_session(
468 beast::flat_buffer buffer,
469 std::shared_ptr<std::string const> const& doc_root)
470 : doc_root_(doc_root)
471 , buffer_(std::move(buffer))
472 {
473 }
474
475 void
476 do_read()
477 {
478 // Construct a new parser for each message
479 parser_.emplace();
480
481 // Apply a reasonable limit to the allowed size
482 // of the body in bytes to prevent abuse.
483 parser_->body_limit(v: 10000);
484
485 // Set the timeout.
486 beast::get_lowest_layer(
487 derived().stream()).expires_after(std::chrono::seconds(30));
488
489 // Read a request using the parser-oriented interface
490 http::async_read(
491 derived().stream(),
492 buffer_,
493 *parser_,
494 beast::bind_front_handler(
495 &http_session::on_read,
496 derived().shared_from_this()));
497 }
498
499 void
500 on_read(beast::error_code ec, std::size_t bytes_transferred)
501 {
502 boost::ignore_unused(bytes_transferred);
503
504 // This means they closed the connection
505 if(ec == http::error::end_of_stream)
506 return derived().do_eof();
507
508 if(ec)
509 return fail(ec, what: "read");
510
511 // See if it is a WebSocket Upgrade
512 if(websocket::is_upgrade(req: parser_->get()))
513 {
514 // Disable the timeout.
515 // The websocket::stream uses its own timeout settings.
516 beast::get_lowest_layer(derived().stream()).expires_never();
517
518 // Create a websocket session, transferring ownership
519 // of both the socket and the HTTP request.
520 return make_websocket_session(
521 derived().release_stream(),
522 parser_->release());
523 }
524
525 // Send the response
526 queue_write(response: handle_request(doc_root: *doc_root_, req: parser_->release()));
527
528 // If we aren't at the queue limit, try to pipeline another request
529 if (response_queue_.size() < queue_limit)
530 do_read();
531 }
532
533 void
534 queue_write(http::message_generator response)
535 {
536 // Allocate and store the work
537 response_queue_.push(x: std::move(response));
538
539 // If there was no previous work, start the write loop
540 if (response_queue_.size() == 1)
541 do_write();
542 }
543
544 // Called to start/continue the write-loop. Should not be called when
545 // write_loop is already active.
546 void
547 do_write()
548 {
549 if(! response_queue_.empty())
550 {
551 bool keep_alive = response_queue_.front().keep_alive();
552
553 beast::async_write(
554 derived().stream(),
555 std::move(response_queue_.front()),
556 beast::bind_front_handler(
557 &http_session::on_write,
558 derived().shared_from_this(),
559 keep_alive));
560 }
561 }
562
563 void
564 on_write(
565 bool keep_alive,
566 beast::error_code ec,
567 std::size_t bytes_transferred)
568 {
569 boost::ignore_unused(bytes_transferred);
570
571 if(ec)
572 return fail(ec, what: "write");
573
574 if(! keep_alive)
575 {
576 // This means we should close the connection, usually because
577 // the response indicated the "Connection: close" semantic.
578 return derived().do_eof();
579 }
580
581 // Resume the read if it has been paused
582 if(response_queue_.size() == queue_limit)
583 do_read();
584
585 response_queue_.pop();
586
587 do_write();
588 }
589};
590
591//------------------------------------------------------------------------------
592
593// Handles a plain HTTP connection
594class plain_http_session
595 : public http_session<plain_http_session>
596 , public std::enable_shared_from_this<plain_http_session>
597{
598 beast::tcp_stream stream_;
599
600public:
601 // Create the session
602 plain_http_session(
603 beast::tcp_stream&& stream,
604 beast::flat_buffer&& buffer,
605 std::shared_ptr<std::string const> const& doc_root)
606 : http_session<plain_http_session>(
607 std::move(buffer),
608 doc_root)
609 , stream_(std::move(stream))
610 {
611 }
612
613 // Start the session
614 void
615 run()
616 {
617 this->do_read();
618 }
619
620 // Called by the base class
621 beast::tcp_stream&
622 stream()
623 {
624 return stream_;
625 }
626
627 // Called by the base class
628 beast::tcp_stream
629 release_stream()
630 {
631 return std::move(stream_);
632 }
633
634 // Called by the base class
635 void
636 do_eof()
637 {
638 // Send a TCP shutdown
639 beast::error_code ec;
640 stream_.socket().shutdown(what: tcp::socket::shutdown_send, ec);
641
642 // At this point the connection is closed gracefully
643 }
644};
645
646//------------------------------------------------------------------------------
647
648// Handles an SSL HTTP connection
649class ssl_http_session
650 : public http_session<ssl_http_session>
651 , public std::enable_shared_from_this<ssl_http_session>
652{
653 beast::ssl_stream<beast::tcp_stream> stream_;
654
655public:
656 // Create the http_session
657 ssl_http_session(
658 beast::tcp_stream&& stream,
659 ssl::context& ctx,
660 beast::flat_buffer&& buffer,
661 std::shared_ptr<std::string const> const& doc_root)
662 : http_session<ssl_http_session>(
663 std::move(buffer),
664 doc_root)
665 , stream_(std::move(stream), ctx)
666 {
667 }
668
669 // Start the session
670 void
671 run()
672 {
673 // Set the timeout.
674 beast::get_lowest_layer(t&: stream_).expires_after(expiry_time: std::chrono::seconds(30));
675
676 // Perform the SSL handshake
677 // Note, this is the buffered version of the handshake.
678 stream_.async_handshake(
679 type: ssl::stream_base::server,
680 buffers: buffer_.data(),
681 handler: beast::bind_front_handler(
682 handler: &ssl_http_session::on_handshake,
683 args: shared_from_this()));
684 }
685
686 // Called by the base class
687 beast::ssl_stream<beast::tcp_stream>&
688 stream()
689 {
690 return stream_;
691 }
692
693 // Called by the base class
694 beast::ssl_stream<beast::tcp_stream>
695 release_stream()
696 {
697 return std::move(stream_);
698 }
699
700 // Called by the base class
701 void
702 do_eof()
703 {
704 // Set the timeout.
705 beast::get_lowest_layer(t&: stream_).expires_after(expiry_time: std::chrono::seconds(30));
706
707 // Perform the SSL shutdown
708 stream_.async_shutdown(
709 handler: beast::bind_front_handler(
710 handler: &ssl_http_session::on_shutdown,
711 args: shared_from_this()));
712 }
713
714private:
715 void
716 on_handshake(
717 beast::error_code ec,
718 std::size_t bytes_used)
719 {
720 if(ec)
721 return fail(ec, what: "handshake");
722
723 // Consume the portion of the buffer used by the handshake
724 buffer_.consume(n: bytes_used);
725
726 do_read();
727 }
728
729 void
730 on_shutdown(beast::error_code ec)
731 {
732 if(ec)
733 return fail(ec, what: "shutdown");
734
735 // At this point the connection is closed gracefully
736 }
737};
738
739//------------------------------------------------------------------------------
740
741// Detects SSL handshakes
742class detect_session : public std::enable_shared_from_this<detect_session>
743{
744 beast::tcp_stream stream_;
745 ssl::context& ctx_;
746 std::shared_ptr<std::string const> doc_root_;
747 beast::flat_buffer buffer_;
748
749public:
750 explicit
751 detect_session(
752 tcp::socket&& socket,
753 ssl::context& ctx,
754 std::shared_ptr<std::string const> const& doc_root)
755 : stream_(std::move(socket))
756 , ctx_(ctx)
757 , doc_root_(doc_root)
758 {
759 }
760
761 // Launch the detector
762 void
763 run()
764 {
765 // We need to be executing within a strand to perform async operations
766 // on the I/O objects in this session. Although not strictly necessary
767 // for single-threaded contexts, this example code is written to be
768 // thread-safe by default.
769 net::dispatch(
770 ex: stream_.get_executor(),
771 token: beast::bind_front_handler(
772 handler: &detect_session::on_run,
773 args: this->shared_from_this()));
774 }
775
776 void
777 on_run()
778 {
779 // Set the timeout.
780 stream_.expires_after(expiry_time: std::chrono::seconds(30));
781
782 beast::async_detect_ssl(
783 stream&: stream_,
784 buffer&: buffer_,
785 token: beast::bind_front_handler(
786 handler: &detect_session::on_detect,
787 args: this->shared_from_this()));
788 }
789
790 void
791 on_detect(beast::error_code ec, bool result)
792 {
793 if(ec)
794 return fail(ec, what: "detect");
795
796 if(result)
797 {
798 // Launch SSL session
799 std::make_shared<ssl_http_session>(
800 args: std::move(stream_),
801 args&: ctx_,
802 args: std::move(buffer_),
803 args&: doc_root_)->run();
804 return;
805 }
806
807 // Launch plain session
808 std::make_shared<plain_http_session>(
809 args: std::move(stream_),
810 args: std::move(buffer_),
811 args&: doc_root_)->run();
812 }
813};
814
815// Accepts incoming connections and launches the sessions
816class listener : public std::enable_shared_from_this<listener>
817{
818 net::io_context& ioc_;
819 ssl::context& ctx_;
820 tcp::acceptor acceptor_;
821 std::shared_ptr<std::string const> doc_root_;
822
823public:
824 listener(
825 net::io_context& ioc,
826 ssl::context& ctx,
827 tcp::endpoint endpoint,
828 std::shared_ptr<std::string const> const& doc_root)
829 : ioc_(ioc)
830 , ctx_(ctx)
831 , acceptor_(net::make_strand(ctx&: ioc))
832 , doc_root_(doc_root)
833 {
834 beast::error_code ec;
835
836 // Open the acceptor
837 acceptor_.open(protocol: endpoint.protocol(), ec);
838 if(ec)
839 {
840 fail(ec, what: "open");
841 return;
842 }
843
844 // Allow address reuse
845 acceptor_.set_option(option: net::socket_base::reuse_address(true), ec);
846 if(ec)
847 {
848 fail(ec, what: "set_option");
849 return;
850 }
851
852 // Bind to the server address
853 acceptor_.bind(endpoint, ec);
854 if(ec)
855 {
856 fail(ec, what: "bind");
857 return;
858 }
859
860 // Start listening for connections
861 acceptor_.listen(
862 backlog: net::socket_base::max_listen_connections, ec);
863 if(ec)
864 {
865 fail(ec, what: "listen");
866 return;
867 }
868 }
869
870 // Start accepting incoming connections
871 void
872 run()
873 {
874 do_accept();
875 }
876
877private:
878 void
879 do_accept()
880 {
881 // The new connection gets its own strand
882 acceptor_.async_accept(
883 ex: net::make_strand(ctx&: ioc_),
884 token: beast::bind_front_handler(
885 handler: &listener::on_accept,
886 args: shared_from_this()));
887 }
888
889 void
890 on_accept(beast::error_code ec, tcp::socket socket)
891 {
892 if(ec)
893 {
894 fail(ec, what: "accept");
895 }
896 else
897 {
898 // Create the detector http_session and run it
899 std::make_shared<detect_session>(
900 args: std::move(socket),
901 args&: ctx_,
902 args&: doc_root_)->run();
903 }
904
905 // Accept another connection
906 do_accept();
907 }
908};
909
910//------------------------------------------------------------------------------
911
912int main(int argc, char* argv[])
913{
914 // Check command line arguments.
915 if (argc != 5)
916 {
917 std::cerr <<
918 "Usage: advanced-server-flex <address> <port> <doc_root> <threads>\n" <<
919 "Example:\n" <<
920 " advanced-server-flex 0.0.0.0 8080 . 1\n";
921 return EXIT_FAILURE;
922 }
923 auto const address = net::ip::make_address(str: argv[1]);
924 auto const port = static_cast<unsigned short>(std::atoi(nptr: argv[2]));
925 auto const doc_root = std::make_shared<std::string>(args&: argv[3]);
926 auto const threads = std::max<int>(a: 1, b: std::atoi(nptr: argv[4]));
927
928 // The io_context is required for all I/O
929 net::io_context ioc{threads};
930
931 // The SSL context is required, and holds certificates
932 ssl::context ctx{ssl::context::tlsv12};
933
934 // This holds the self-signed certificate used by the server
935 load_server_certificate(ctx);
936
937 // Create and launch a listening port
938 std::make_shared<listener>(
939 args&: ioc,
940 args&: ctx,
941 args: tcp::endpoint{address, port},
942 args: doc_root)->run();
943
944 // Capture SIGINT and SIGTERM to perform a clean shutdown
945 net::signal_set signals(ioc, SIGINT, SIGTERM);
946 signals.async_wait(
947 token: [&](beast::error_code const&, int)
948 {
949 // Stop the `io_context`. This will cause `run()`
950 // to return immediately, eventually destroying the
951 // `io_context` and all of the sockets in it.
952 ioc.stop();
953 });
954
955 // Run the I/O service on the requested number of threads
956 std::vector<std::thread> v;
957 v.reserve(n: threads - 1);
958 for(auto i = threads - 1; i > 0; --i)
959 v.emplace_back(
960 args: [&ioc]
961 {
962 ioc.run();
963 });
964 ioc.run();
965
966 // (If we get here, it means we got a SIGINT or SIGTERM)
967
968 // Block until all the threads exit
969 for(auto& t : v)
970 t.join();
971
972 return EXIT_SUCCESS;
973}
974

source code of boost/libs/beast/example/advanced/server-flex/advanced_server_flex.cpp