Imported Upstream version 1.72.0
[platform/upstream/boost.git] / libs / beast / example / http / server / flex / http_server_flex.cpp
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: HTTP flex server (plain and SSL), asynchronous
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/version.hpp>
22 #include <boost/asio/dispatch.hpp>
23 #include <boost/asio/strand.hpp>
24 #include <boost/config.hpp>
25 #include <algorithm>
26 #include <cstdlib>
27 #include <functional>
28 #include <iostream>
29 #include <memory>
30 #include <string>
31 #include <thread>
32
33 namespace beast = boost::beast;         // from <boost/beast.hpp>
34 namespace http = beast::http;           // from <boost/beast/http.hpp>
35 namespace net = boost::asio;            // from <boost/asio.hpp>
36 namespace ssl = boost::asio::ssl;       // from <boost/asio/ssl.hpp>
37 using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
38
39 // Return a reasonable mime type based on the extension of a file.
40 beast::string_view
41 mime_type(beast::string_view path)
42 {
43     using beast::iequals;
44     auto const ext = [&path]
45     {
46         auto const pos = path.rfind(".");
47         if(pos == beast::string_view::npos)
48             return beast::string_view{};
49         return path.substr(pos);
50     }();
51     if(iequals(ext, ".htm"))  return "text/html";
52     if(iequals(ext, ".html")) return "text/html";
53     if(iequals(ext, ".php"))  return "text/html";
54     if(iequals(ext, ".css"))  return "text/css";
55     if(iequals(ext, ".txt"))  return "text/plain";
56     if(iequals(ext, ".js"))   return "application/javascript";
57     if(iequals(ext, ".json")) return "application/json";
58     if(iequals(ext, ".xml"))  return "application/xml";
59     if(iequals(ext, ".swf"))  return "application/x-shockwave-flash";
60     if(iequals(ext, ".flv"))  return "video/x-flv";
61     if(iequals(ext, ".png"))  return "image/png";
62     if(iequals(ext, ".jpe"))  return "image/jpeg";
63     if(iequals(ext, ".jpeg")) return "image/jpeg";
64     if(iequals(ext, ".jpg"))  return "image/jpeg";
65     if(iequals(ext, ".gif"))  return "image/gif";
66     if(iequals(ext, ".bmp"))  return "image/bmp";
67     if(iequals(ext, ".ico"))  return "image/vnd.microsoft.icon";
68     if(iequals(ext, ".tiff")) return "image/tiff";
69     if(iequals(ext, ".tif"))  return "image/tiff";
70     if(iequals(ext, ".svg"))  return "image/svg+xml";
71     if(iequals(ext, ".svgz")) return "image/svg+xml";
72     return "application/text";
73 }
74
75 // Append an HTTP rel-path to a local filesystem path.
76 // The returned path is normalized for the platform.
77 std::string
78 path_cat(
79     beast::string_view base,
80     beast::string_view path)
81 {
82     if(base.empty())
83         return std::string(path);
84     std::string result(base);
85 #ifdef BOOST_MSVC
86     char constexpr path_separator = '\\';
87     if(result.back() == path_separator)
88         result.resize(result.size() - 1);
89     result.append(path.data(), path.size());
90     for(auto& c : result)
91         if(c == '/')
92             c = path_separator;
93 #else
94     char constexpr path_separator = '/';
95     if(result.back() == path_separator)
96         result.resize(result.size() - 1);
97     result.append(path.data(), path.size());
98 #endif
99     return result;
100 }
101
102 // This function produces an HTTP response for the given
103 // request. The type of the response object depends on the
104 // contents of the request, so the interface requires the
105 // caller to pass a generic lambda for receiving the response.
106 template<
107     class Body, class Allocator,
108     class Send>
109 void
110 handle_request(
111     beast::string_view doc_root,
112     http::request<Body, http::basic_fields<Allocator>>&& req,
113     Send&& send)
114 {
115     // Returns a bad request response
116     auto const bad_request =
117     [&req](beast::string_view why)
118     {
119         http::response<http::string_body> res{http::status::bad_request, req.version()};
120         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
121         res.set(http::field::content_type, "text/html");
122         res.keep_alive(req.keep_alive());
123         res.body() = std::string(why);
124         res.prepare_payload();
125         return res;
126     };
127
128     // Returns a not found response
129     auto const not_found =
130     [&req](beast::string_view target)
131     {
132         http::response<http::string_body> res{http::status::not_found, req.version()};
133         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
134         res.set(http::field::content_type, "text/html");
135         res.keep_alive(req.keep_alive());
136         res.body() = "The resource '" + std::string(target) + "' was not found.";
137         res.prepare_payload();
138         return res;
139     };
140
141     // Returns a server error response
142     auto const server_error =
143     [&req](beast::string_view what)
144     {
145         http::response<http::string_body> res{http::status::internal_server_error, req.version()};
146         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
147         res.set(http::field::content_type, "text/html");
148         res.keep_alive(req.keep_alive());
149         res.body() = "An error occurred: '" + std::string(what) + "'";
150         res.prepare_payload();
151         return res;
152     };
153
154     // Make sure we can handle the method
155     if( req.method() != http::verb::get &&
156         req.method() != http::verb::head)
157         return send(bad_request("Unknown HTTP-method"));
158
159     // Request path must be absolute and not contain "..".
160     if( req.target().empty() ||
161         req.target()[0] != '/' ||
162         req.target().find("..") != beast::string_view::npos)
163         return send(bad_request("Illegal request-target"));
164
165     // Build the path to the requested file
166     std::string path = path_cat(doc_root, req.target());
167     if(req.target().back() == '/')
168         path.append("index.html");
169
170     // Attempt to open the file
171     beast::error_code ec;
172     http::file_body::value_type body;
173     body.open(path.c_str(), beast::file_mode::scan, ec);
174
175     // Handle the case where the file doesn't exist
176     if(ec == beast::errc::no_such_file_or_directory)
177         return send(not_found(req.target()));
178
179     // Handle an unknown error
180     if(ec)
181         return send(server_error(ec.message()));
182
183     // Cache the size since we need it after the move
184     auto const size = body.size();
185
186     // Respond to HEAD request
187     if(req.method() == http::verb::head)
188     {
189         http::response<http::empty_body> res{http::status::ok, req.version()};
190         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
191         res.set(http::field::content_type, mime_type(path));
192         res.content_length(size);
193         res.keep_alive(req.keep_alive());
194         return send(std::move(res));
195     }
196
197     // Respond to GET request
198     http::response<http::file_body> res{
199         std::piecewise_construct,
200         std::make_tuple(std::move(body)),
201         std::make_tuple(http::status::ok, req.version())};
202     res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
203     res.set(http::field::content_type, mime_type(path));
204     res.content_length(size);
205     res.keep_alive(req.keep_alive());
206     return send(std::move(res));
207 }
208
209 //------------------------------------------------------------------------------
210
211 // Report a failure
212 void
213 fail(beast::error_code ec, char const* what)
214 {
215     // ssl::error::stream_truncated, also known as an SSL "short read",
216     // indicates the peer closed the connection without performing the
217     // required closing handshake (for example, Google does this to
218     // improve performance). Generally this can be a security issue,
219     // but if your communication protocol is self-terminated (as
220     // it is with both HTTP and WebSocket) then you may simply
221     // ignore the lack of close_notify.
222     //
223     // https://github.com/boostorg/beast/issues/38
224     //
225     // https://security.stackexchange.com/questions/91435/how-to-handle-a-malicious-ssl-tls-shutdown
226     //
227     // When a short read would cut off the end of an HTTP message,
228     // Beast returns the error beast::http::error::partial_message.
229     // Therefore, if we see a short read here, it has occurred
230     // after the message has been completed, so it is safe to ignore it.
231
232     if(ec == net::ssl::error::stream_truncated)
233         return;
234
235     std::cerr << what << ": " << ec.message() << "\n";
236 }
237
238 // Handles an HTTP server connection.
239 // This uses the Curiously Recurring Template Pattern so that
240 // the same code works with both SSL streams and regular sockets.
241 template<class Derived>
242 class session
243 {
244     // Access the derived class, this is part of
245     // the Curiously Recurring Template Pattern idiom.
246     Derived&
247     derived()
248     {
249         return static_cast<Derived&>(*this);
250     }
251
252     // This is the C++11 equivalent of a generic lambda.
253     // The function object is used to send an HTTP message.
254     struct send_lambda
255     {
256         session& self_;
257
258         explicit
259         send_lambda(session& self)
260             : self_(self)
261         {
262         }
263
264         template<bool isRequest, class Body, class Fields>
265         void
266         operator()(http::message<isRequest, Body, Fields>&& msg) const
267         {
268             // The lifetime of the message has to extend
269             // for the duration of the async operation so
270             // we use a shared_ptr to manage it.
271             auto sp = std::make_shared<
272                 http::message<isRequest, Body, Fields>>(std::move(msg));
273
274             // Store a type-erased version of the shared
275             // pointer in the class to keep it alive.
276             self_.res_ = sp;
277
278             // Write the response
279             http::async_write(
280                 self_.derived().stream(),
281                 *sp,
282                 beast::bind_front_handler(
283                     &session::on_write,
284                     self_.derived().shared_from_this(),
285                     sp->need_eof()));
286         }
287     };
288
289     std::shared_ptr<std::string const> doc_root_;
290     http::request<http::string_body> req_;
291     std::shared_ptr<void> res_;
292     send_lambda lambda_;
293
294 protected:
295     beast::flat_buffer buffer_;
296
297 public:
298     // Take ownership of the buffer
299     session(
300         beast::flat_buffer buffer,
301         std::shared_ptr<std::string const> const& doc_root)
302         : doc_root_(doc_root)
303         , lambda_(*this)
304         , buffer_(std::move(buffer))
305     {
306     }
307
308     void
309     do_read()
310     {
311         // Set the timeout.
312         beast::get_lowest_layer(
313             derived().stream()).expires_after(std::chrono::seconds(30));
314
315         // Read a request
316         http::async_read(
317             derived().stream(),
318             buffer_,
319             req_,
320             beast::bind_front_handler(
321                 &session::on_read,
322                 derived().shared_from_this()));
323     }
324
325     void
326     on_read(
327         beast::error_code ec,
328         std::size_t bytes_transferred)
329     {
330         boost::ignore_unused(bytes_transferred);
331
332         // This means they closed the connection
333         if(ec == http::error::end_of_stream)
334             return derived().do_eof();
335
336         if(ec)
337             return fail(ec, "read");
338
339         // Send the response
340         handle_request(*doc_root_, std::move(req_), lambda_);
341     }
342
343     void
344     on_write(
345         bool close,
346         beast::error_code ec,
347         std::size_t bytes_transferred)
348     {
349         boost::ignore_unused(bytes_transferred);
350
351         if(ec)
352             return fail(ec, "write");
353
354         if(close)
355         {
356             // This means we should close the connection, usually because
357             // the response indicated the "Connection: close" semantic.
358             return derived().do_eof();
359         }
360
361         // We're done with the response so delete it
362         res_ = nullptr;
363
364         // Read another request
365         do_read();
366     }
367 };
368
369 // Handles a plain HTTP connection
370 class plain_session
371     : public session<plain_session>
372     , public std::enable_shared_from_this<plain_session>
373 {
374     beast::tcp_stream stream_;
375
376 public:
377     // Create the session
378     plain_session(
379         tcp::socket&& socket,
380         beast::flat_buffer buffer,
381         std::shared_ptr<std::string const> const& doc_root)
382         : session<plain_session>(
383             std::move(buffer),
384             doc_root)
385         , stream_(std::move(socket))
386     {
387     }
388
389     // Called by the base class
390     beast::tcp_stream&
391     stream()
392     {
393         return stream_;
394     }
395
396     // Start the asynchronous operation
397     void
398     run()
399     {
400         // We need to be executing within a strand to perform async operations
401         // on the I/O objects in this session. Although not strictly necessary
402         // for single-threaded contexts, this example code is written to be
403         // thread-safe by default.
404         net::dispatch(stream_.get_executor(),
405                       beast::bind_front_handler(
406                           &session::do_read,
407                           shared_from_this()));
408     }
409
410     void
411     do_eof()
412     {
413         // Send a TCP shutdown
414         beast::error_code ec;
415         stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
416
417         // At this point the connection is closed gracefully
418     }
419 };
420
421 // Handles an SSL HTTP connection
422 class ssl_session
423     : public session<ssl_session>
424     , public std::enable_shared_from_this<ssl_session>
425 {
426     beast::ssl_stream<beast::tcp_stream> stream_;
427
428 public:
429     // Create the session
430     ssl_session(
431         tcp::socket&& socket,
432         ssl::context& ctx,
433         beast::flat_buffer buffer,
434         std::shared_ptr<std::string const> const& doc_root)
435         : session<ssl_session>(
436             std::move(buffer),
437             doc_root)
438         , stream_(std::move(socket), ctx)
439     {
440     }
441
442     // Called by the base class
443     beast::ssl_stream<beast::tcp_stream>&
444     stream()
445     {
446         return stream_;
447     }
448
449     // Start the asynchronous operation
450     void
451     run()
452     {
453         auto self = shared_from_this();
454         // We need to be executing within a strand to perform async operations
455         // on the I/O objects in this session.
456         net::dispatch(stream_.get_executor(), [self]() {
457             // Set the timeout.
458             beast::get_lowest_layer(self->stream_).expires_after(
459                 std::chrono::seconds(30));
460
461             // Perform the SSL handshake
462             // Note, this is the buffered version of the handshake.
463             self->stream_.async_handshake(
464                 ssl::stream_base::server,
465                 self->buffer_.data(),
466                 beast::bind_front_handler(
467                     &ssl_session::on_handshake,
468                     self));
469         });
470     }
471
472     void
473     on_handshake(
474         beast::error_code ec,
475         std::size_t bytes_used)
476     {
477         if(ec)
478             return fail(ec, "handshake");
479
480         // Consume the portion of the buffer used by the handshake
481         buffer_.consume(bytes_used);
482
483         do_read();
484     }
485
486     void
487     do_eof()
488     {
489         // Set the timeout.
490         beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
491
492         // Perform the SSL shutdown
493         stream_.async_shutdown(
494             beast::bind_front_handler(
495                 &ssl_session::on_shutdown,
496                 shared_from_this()));
497     }
498
499     void
500     on_shutdown(beast::error_code ec)
501     {
502         if(ec)
503             return fail(ec, "shutdown");
504
505         // At this point the connection is closed gracefully
506     }
507 };
508
509 //------------------------------------------------------------------------------
510
511 // Detects SSL handshakes
512 class detect_session : public std::enable_shared_from_this<detect_session>
513 {
514     beast::tcp_stream stream_;
515     ssl::context& ctx_;
516     std::shared_ptr<std::string const> doc_root_;
517     beast::flat_buffer buffer_;
518
519 public:
520     detect_session(
521         tcp::socket&& socket,
522         ssl::context& ctx,
523         std::shared_ptr<std::string const> const& doc_root)
524         : stream_(std::move(socket))
525         , ctx_(ctx)
526         , doc_root_(doc_root)
527     {
528     }
529
530     // Launch the detector
531     void
532     run()
533     {
534         // Set the timeout.
535         beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
536
537         // Detect a TLS handshake
538         async_detect_ssl(
539             stream_,
540             buffer_,
541             beast::bind_front_handler(
542                 &detect_session::on_detect,
543                 shared_from_this()));
544     }
545
546     void
547     on_detect(beast::error_code ec, bool result)
548     {
549         if(ec)
550             return fail(ec, "detect");
551
552         if(result)
553         {
554             // Launch SSL session
555             std::make_shared<ssl_session>(
556                 stream_.release_socket(),
557                 ctx_,
558                 std::move(buffer_),
559                 doc_root_)->run();
560             return;
561         }
562
563         // Launch plain session
564         std::make_shared<plain_session>(
565             stream_.release_socket(),
566             std::move(buffer_),
567             doc_root_)->run();
568     }
569 };
570
571 // Accepts incoming connections and launches the sessions
572 class listener : public std::enable_shared_from_this<listener>
573 {
574     net::io_context& ioc_;
575     ssl::context& ctx_;
576     tcp::acceptor acceptor_;
577     std::shared_ptr<std::string const> doc_root_;
578
579 public:
580     listener(
581         net::io_context& ioc,
582         ssl::context& ctx,
583         tcp::endpoint endpoint,
584         std::shared_ptr<std::string const> const& doc_root)
585         : ioc_(ioc)
586         , ctx_(ctx)
587         , acceptor_(net::make_strand(ioc))
588         , doc_root_(doc_root)
589     {
590         beast::error_code ec;
591
592         // Open the acceptor
593         acceptor_.open(endpoint.protocol(), ec);
594         if(ec)
595         {
596             fail(ec, "open");
597             return;
598         }
599
600         // Allow address reuse
601         acceptor_.set_option(net::socket_base::reuse_address(true), ec);
602         if(ec)
603         {
604             fail(ec, "set_option");
605             return;
606         }
607
608         // Bind to the server address
609         acceptor_.bind(endpoint, ec);
610         if(ec)
611         {
612             fail(ec, "bind");
613             return;
614         }
615
616         // Start listening for connections
617         acceptor_.listen(
618             net::socket_base::max_listen_connections, ec);
619         if(ec)
620         {
621             fail(ec, "listen");
622             return;
623         }
624     }
625
626     // Start accepting incoming connections
627     void
628     run()
629     {
630         do_accept();
631     }
632
633 private:
634     void
635     do_accept()
636     {
637         // The new connection gets its own strand
638         acceptor_.async_accept(
639             net::make_strand(ioc_),
640             beast::bind_front_handler(
641                 &listener::on_accept,
642                 shared_from_this()));
643     }
644
645     void
646     on_accept(beast::error_code ec, tcp::socket socket)
647     {
648         if(ec)
649         {
650             fail(ec, "accept");
651         }
652         else
653         {
654             // Create the detector session and run it
655             std::make_shared<detect_session>(
656                 std::move(socket),
657                 ctx_,
658                 doc_root_)->run();
659         }
660
661         // Accept another connection
662         do_accept();
663     }
664 };
665
666 //------------------------------------------------------------------------------
667
668 int main(int argc, char* argv[])
669 {
670     // Check command line arguments.
671     if (argc != 5)
672     {
673         std::cerr <<
674             "Usage: http-server-flex <address> <port> <doc_root> <threads>\n" <<
675             "Example:\n" <<
676             "    http-server-flex 0.0.0.0 8080 .\n";
677         return EXIT_FAILURE;
678     }
679     auto const address = net::ip::make_address(argv[1]);
680     auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
681     auto const doc_root = std::make_shared<std::string>(argv[3]);
682     auto const threads = std::max<int>(1, std::atoi(argv[4]));
683
684     // The io_context is required for all I/O
685     net::io_context ioc{threads};
686
687     // The SSL context is required, and holds certificates
688     ssl::context ctx{ssl::context::tlsv12};
689
690     // This holds the self-signed certificate used by the server
691     load_server_certificate(ctx);
692
693     // Create and launch a listening port
694     std::make_shared<listener>(
695         ioc,
696         ctx,
697         tcp::endpoint{address, port},
698         doc_root)->run();
699
700     // Run the I/O service on the requested number of threads
701     std::vector<std::thread> v;
702     v.reserve(threads - 1);
703     for(auto i = threads - 1; i > 0; --i)
704         v.emplace_back(
705         [&ioc]
706         {
707             ioc.run();
708         });
709     ioc.run();
710
711     return EXIT_SUCCESS;
712 }