]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/beast/example/advanced/server/advanced_server.cpp
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / boost / libs / beast / example / advanced / server / advanced_server.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: Advanced server
13 //
14 //------------------------------------------------------------------------------
15
16 #include <boost/beast/core.hpp>
17 #include <boost/beast/http.hpp>
18 #include <boost/beast/websocket.hpp>
19 #include <boost/beast/version.hpp>
20 #include <boost/asio/bind_executor.hpp>
21 #include <boost/asio/signal_set.hpp>
22 #include <boost/asio/strand.hpp>
23 #include <boost/make_unique.hpp>
24 #include <boost/optional.hpp>
25 #include <algorithm>
26 #include <cstdlib>
27 #include <functional>
28 #include <iostream>
29 #include <memory>
30 #include <string>
31 #include <thread>
32 #include <vector>
33
34 namespace beast = boost::beast; // from <boost/beast.hpp>
35 namespace http = beast::http; // from <boost/beast/http.hpp>
36 namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
37 namespace net = boost::asio; // from <boost/asio.hpp>
38 using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
39
40 // Return a reasonable mime type based on the extension of a file.
41 beast::string_view
42 mime_type(beast::string_view path)
43 {
44 using beast::iequals;
45 auto const ext = [&path]
46 {
47 auto const pos = path.rfind(".");
48 if(pos == beast::string_view::npos)
49 return beast::string_view{};
50 return path.substr(pos);
51 }();
52 if(iequals(ext, ".htm")) return "text/html";
53 if(iequals(ext, ".html")) return "text/html";
54 if(iequals(ext, ".php")) return "text/html";
55 if(iequals(ext, ".css")) return "text/css";
56 if(iequals(ext, ".txt")) return "text/plain";
57 if(iequals(ext, ".js")) return "application/javascript";
58 if(iequals(ext, ".json")) return "application/json";
59 if(iequals(ext, ".xml")) return "application/xml";
60 if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
61 if(iequals(ext, ".flv")) return "video/x-flv";
62 if(iequals(ext, ".png")) return "image/png";
63 if(iequals(ext, ".jpe")) return "image/jpeg";
64 if(iequals(ext, ".jpeg")) return "image/jpeg";
65 if(iequals(ext, ".jpg")) return "image/jpeg";
66 if(iequals(ext, ".gif")) return "image/gif";
67 if(iequals(ext, ".bmp")) return "image/bmp";
68 if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
69 if(iequals(ext, ".tiff")) return "image/tiff";
70 if(iequals(ext, ".tif")) return "image/tiff";
71 if(iequals(ext, ".svg")) return "image/svg+xml";
72 if(iequals(ext, ".svgz")) return "image/svg+xml";
73 return "application/text";
74 }
75
76 // Append an HTTP rel-path to a local filesystem path.
77 // The returned path is normalized for the platform.
78 std::string
79 path_cat(
80 beast::string_view base,
81 beast::string_view path)
82 {
83 if(base.empty())
84 return std::string(path);
85 std::string result(base);
86 #ifdef BOOST_MSVC
87 char constexpr path_separator = '\\';
88 if(result.back() == path_separator)
89 result.resize(result.size() - 1);
90 result.append(path.data(), path.size());
91 for(auto& c : result)
92 if(c == '/')
93 c = path_separator;
94 #else
95 char constexpr path_separator = '/';
96 if(result.back() == path_separator)
97 result.resize(result.size() - 1);
98 result.append(path.data(), path.size());
99 #endif
100 return result;
101 }
102
103 // This function produces an HTTP response for the given
104 // request. The type of the response object depends on the
105 // contents of the request, so the interface requires the
106 // caller to pass a generic lambda for receiving the response.
107 template<
108 class Body, class Allocator,
109 class Send>
110 void
111 handle_request(
112 beast::string_view doc_root,
113 http::request<Body, http::basic_fields<Allocator>>&& req,
114 Send&& send)
115 {
116 // Returns a bad request response
117 auto const bad_request =
118 [&req](beast::string_view why)
119 {
120 http::response<http::string_body> res{http::status::bad_request, req.version()};
121 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
122 res.set(http::field::content_type, "text/html");
123 res.keep_alive(req.keep_alive());
124 res.body() = std::string(why);
125 res.prepare_payload();
126 return res;
127 };
128
129 // Returns a not found response
130 auto const not_found =
131 [&req](beast::string_view target)
132 {
133 http::response<http::string_body> res{http::status::not_found, req.version()};
134 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
135 res.set(http::field::content_type, "text/html");
136 res.keep_alive(req.keep_alive());
137 res.body() = "The resource '" + std::string(target) + "' was not found.";
138 res.prepare_payload();
139 return res;
140 };
141
142 // Returns a server error response
143 auto const server_error =
144 [&req](beast::string_view what)
145 {
146 http::response<http::string_body> res{http::status::internal_server_error, req.version()};
147 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
148 res.set(http::field::content_type, "text/html");
149 res.keep_alive(req.keep_alive());
150 res.body() = "An error occurred: '" + std::string(what) + "'";
151 res.prepare_payload();
152 return res;
153 };
154
155 // Make sure we can handle the method
156 if( req.method() != http::verb::get &&
157 req.method() != http::verb::head)
158 return send(bad_request("Unknown HTTP-method"));
159
160 // Request path must be absolute and not contain "..".
161 if( req.target().empty() ||
162 req.target()[0] != '/' ||
163 req.target().find("..") != beast::string_view::npos)
164 return send(bad_request("Illegal request-target"));
165
166 // Build the path to the requested file
167 std::string path = path_cat(doc_root, req.target());
168 if(req.target().back() == '/')
169 path.append("index.html");
170
171 // Attempt to open the file
172 beast::error_code ec;
173 http::file_body::value_type body;
174 body.open(path.c_str(), beast::file_mode::scan, ec);
175
176 // Handle the case where the file doesn't exist
177 if(ec == beast::errc::no_such_file_or_directory)
178 return send(not_found(req.target()));
179
180 // Handle an unknown error
181 if(ec)
182 return send(server_error(ec.message()));
183
184 // Cache the size since we need it after the move
185 auto const size = body.size();
186
187 // Respond to HEAD request
188 if(req.method() == http::verb::head)
189 {
190 http::response<http::empty_body> res{http::status::ok, req.version()};
191 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
192 res.set(http::field::content_type, mime_type(path));
193 res.content_length(size);
194 res.keep_alive(req.keep_alive());
195 return send(std::move(res));
196 }
197
198 // Respond to GET request
199 http::response<http::file_body> res{
200 std::piecewise_construct,
201 std::make_tuple(std::move(body)),
202 std::make_tuple(http::status::ok, req.version())};
203 res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
204 res.set(http::field::content_type, mime_type(path));
205 res.content_length(size);
206 res.keep_alive(req.keep_alive());
207 return send(std::move(res));
208 }
209
210 //------------------------------------------------------------------------------
211
212 // Report a failure
213 void
214 fail(beast::error_code ec, char const* what)
215 {
216 std::cerr << what << ": " << ec.message() << "\n";
217 }
218
219 // Echoes back all received WebSocket messages
220 class websocket_session : public std::enable_shared_from_this<websocket_session>
221 {
222 websocket::stream<beast::tcp_stream> ws_;
223 beast::flat_buffer buffer_;
224
225 public:
226 // Take ownership of the socket
227 explicit
228 websocket_session(tcp::socket&& socket)
229 : ws_(std::move(socket))
230 {
231 }
232
233 // Start the asynchronous accept operation
234 template<class Body, class Allocator>
235 void
236 do_accept(http::request<Body, http::basic_fields<Allocator>> req)
237 {
238 // Set suggested timeout settings for the websocket
239 ws_.set_option(
240 websocket::stream_base::timeout::suggested(
241 beast::role_type::server));
242
243 // Set a decorator to change the Server of the handshake
244 ws_.set_option(websocket::stream_base::decorator(
245 [](websocket::response_type& res)
246 {
247 res.set(http::field::server,
248 std::string(BOOST_BEAST_VERSION_STRING) +
249 " advanced-server");
250 }));
251
252 // Accept the websocket handshake
253 ws_.async_accept(
254 req,
255 beast::bind_front_handler(
256 &websocket_session::on_accept,
257 shared_from_this()));
258 }
259
260 private:
261 void
262 on_accept(beast::error_code ec)
263 {
264 if(ec)
265 return fail(ec, "accept");
266
267 // Read a message
268 do_read();
269 }
270
271 void
272 do_read()
273 {
274 // Read a message into our buffer
275 ws_.async_read(
276 buffer_,
277 beast::bind_front_handler(
278 &websocket_session::on_read,
279 shared_from_this()));
280 }
281
282 void
283 on_read(
284 beast::error_code ec,
285 std::size_t bytes_transferred)
286 {
287 boost::ignore_unused(bytes_transferred);
288
289 // This indicates that the websocket_session was closed
290 if(ec == websocket::error::closed)
291 return;
292
293 if(ec)
294 fail(ec, "read");
295
296 // Echo the message
297 ws_.text(ws_.got_text());
298 ws_.async_write(
299 buffer_.data(),
300 beast::bind_front_handler(
301 &websocket_session::on_write,
302 shared_from_this()));
303 }
304
305 void
306 on_write(
307 beast::error_code ec,
308 std::size_t bytes_transferred)
309 {
310 boost::ignore_unused(bytes_transferred);
311
312 if(ec)
313 return fail(ec, "write");
314
315 // Clear the buffer
316 buffer_.consume(buffer_.size());
317
318 // Do another read
319 do_read();
320 }
321 };
322
323 //------------------------------------------------------------------------------
324
325 // Handles an HTTP server connection
326 class http_session : public std::enable_shared_from_this<http_session>
327 {
328 // This queue is used for HTTP pipelining.
329 class queue
330 {
331 enum
332 {
333 // Maximum number of responses we will queue
334 limit = 8
335 };
336
337 // The type-erased, saved work item
338 struct work
339 {
340 virtual ~work() = default;
341 virtual void operator()() = 0;
342 };
343
344 http_session& self_;
345 std::vector<std::unique_ptr<work>> items_;
346
347 public:
348 explicit
349 queue(http_session& self)
350 : self_(self)
351 {
352 static_assert(limit > 0, "queue limit must be positive");
353 items_.reserve(limit);
354 }
355
356 // Returns `true` if we have reached the queue limit
357 bool
358 is_full() const
359 {
360 return items_.size() >= limit;
361 }
362
363 // Called when a message finishes sending
364 // Returns `true` if the caller should initiate a read
365 bool
366 on_write()
367 {
368 BOOST_ASSERT(! items_.empty());
369 auto const was_full = is_full();
370 items_.erase(items_.begin());
371 if(! items_.empty())
372 (*items_.front())();
373 return was_full;
374 }
375
376 // Called by the HTTP handler to send a response.
377 template<bool isRequest, class Body, class Fields>
378 void
379 operator()(http::message<isRequest, Body, Fields>&& msg)
380 {
381 // This holds a work item
382 struct work_impl : work
383 {
384 http_session& self_;
385 http::message<isRequest, Body, Fields> msg_;
386
387 work_impl(
388 http_session& self,
389 http::message<isRequest, Body, Fields>&& msg)
390 : self_(self)
391 , msg_(std::move(msg))
392 {
393 }
394
395 void
396 operator()()
397 {
398 http::async_write(
399 self_.stream_,
400 msg_,
401 beast::bind_front_handler(
402 &http_session::on_write,
403 self_.shared_from_this(),
404 msg_.need_eof()));
405 }
406 };
407
408 // Allocate and store the work
409 items_.push_back(
410 boost::make_unique<work_impl>(self_, std::move(msg)));
411
412 // If there was no previous work, start this one
413 if(items_.size() == 1)
414 (*items_.front())();
415 }
416 };
417
418 beast::tcp_stream stream_;
419 beast::flat_buffer buffer_;
420 std::shared_ptr<std::string const> doc_root_;
421 queue queue_;
422
423 // The parser is stored in an optional container so we can
424 // construct it from scratch it at the beginning of each new message.
425 boost::optional<http::request_parser<http::string_body>> parser_;
426
427 public:
428 // Take ownership of the socket
429 http_session(
430 tcp::socket&& socket,
431 std::shared_ptr<std::string const> const& doc_root)
432 : stream_(std::move(socket))
433 , doc_root_(doc_root)
434 , queue_(*this)
435 {
436 }
437
438 // Start the session
439 void
440 run()
441 {
442 do_read();
443 }
444
445 private:
446 void
447 do_read()
448 {
449 // Construct a new parser for each message
450 parser_.emplace();
451
452 // Apply a reasonable limit to the allowed size
453 // of the body in bytes to prevent abuse.
454 parser_->body_limit(10000);
455
456 // Set the timeout.
457 stream_.expires_after(std::chrono::seconds(30));
458
459 // Read a request using the parser-oriented interface
460 http::async_read(
461 stream_,
462 buffer_,
463 *parser_,
464 beast::bind_front_handler(
465 &http_session::on_read,
466 shared_from_this()));
467 }
468
469 void
470 on_read(beast::error_code ec, std::size_t bytes_transferred)
471 {
472 boost::ignore_unused(bytes_transferred);
473
474 // This means they closed the connection
475 if(ec == http::error::end_of_stream)
476 return do_close();
477
478 if(ec)
479 return fail(ec, "read");
480
481 // See if it is a WebSocket Upgrade
482 if(websocket::is_upgrade(parser_->get()))
483 {
484 // Create a websocket session, transferring ownership
485 // of both the socket and the HTTP request.
486 std::make_shared<websocket_session>(
487 stream_.release_socket())->do_accept(parser_->release());
488 return;
489 }
490
491 // Send the response
492 handle_request(*doc_root_, parser_->release(), queue_);
493
494 // If we aren't at the queue limit, try to pipeline another request
495 if(! queue_.is_full())
496 do_read();
497 }
498
499 void
500 on_write(bool close, beast::error_code ec, std::size_t bytes_transferred)
501 {
502 boost::ignore_unused(bytes_transferred);
503
504 if(ec)
505 return fail(ec, "write");
506
507 if(close)
508 {
509 // This means we should close the connection, usually because
510 // the response indicated the "Connection: close" semantic.
511 return do_close();
512 }
513
514 // Inform the queue that a write completed
515 if(queue_.on_write())
516 {
517 // Read another request
518 do_read();
519 }
520 }
521
522 void
523 do_close()
524 {
525 // Send a TCP shutdown
526 beast::error_code ec;
527 stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
528
529 // At this point the connection is closed gracefully
530 }
531 };
532
533 //------------------------------------------------------------------------------
534
535 // Accepts incoming connections and launches the sessions
536 class listener : public std::enable_shared_from_this<listener>
537 {
538 net::io_context& ioc_;
539 tcp::acceptor acceptor_;
540 std::shared_ptr<std::string const> doc_root_;
541
542 public:
543 listener(
544 net::io_context& ioc,
545 tcp::endpoint endpoint,
546 std::shared_ptr<std::string const> const& doc_root)
547 : ioc_(ioc)
548 , acceptor_(net::make_strand(ioc))
549 , doc_root_(doc_root)
550 {
551 beast::error_code ec;
552
553 // Open the acceptor
554 acceptor_.open(endpoint.protocol(), ec);
555 if(ec)
556 {
557 fail(ec, "open");
558 return;
559 }
560
561 // Allow address reuse
562 acceptor_.set_option(net::socket_base::reuse_address(true), ec);
563 if(ec)
564 {
565 fail(ec, "set_option");
566 return;
567 }
568
569 // Bind to the server address
570 acceptor_.bind(endpoint, ec);
571 if(ec)
572 {
573 fail(ec, "bind");
574 return;
575 }
576
577 // Start listening for connections
578 acceptor_.listen(
579 net::socket_base::max_listen_connections, ec);
580 if(ec)
581 {
582 fail(ec, "listen");
583 return;
584 }
585 }
586
587 // Start accepting incoming connections
588 void
589 run()
590 {
591 do_accept();
592 }
593
594 private:
595 void
596 do_accept()
597 {
598 // The new connection gets its own strand
599 acceptor_.async_accept(
600 net::make_strand(ioc_),
601 beast::bind_front_handler(
602 &listener::on_accept,
603 shared_from_this()));
604 }
605
606 void
607 on_accept(beast::error_code ec, tcp::socket socket)
608 {
609 if(ec)
610 {
611 fail(ec, "accept");
612 }
613 else
614 {
615 // Create the http session and run it
616 std::make_shared<http_session>(
617 std::move(socket),
618 doc_root_)->run();
619 }
620
621 // Accept another connection
622 do_accept();
623 }
624 };
625
626 //------------------------------------------------------------------------------
627
628 int main(int argc, char* argv[])
629 {
630 // Check command line arguments.
631 if (argc != 5)
632 {
633 std::cerr <<
634 "Usage: advanced-server <address> <port> <doc_root> <threads>\n" <<
635 "Example:\n" <<
636 " advanced-server 0.0.0.0 8080 . 1\n";
637 return EXIT_FAILURE;
638 }
639 auto const address = net::ip::make_address(argv[1]);
640 auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
641 auto const doc_root = std::make_shared<std::string>(argv[3]);
642 auto const threads = std::max<int>(1, std::atoi(argv[4]));
643
644 // The io_context is required for all I/O
645 net::io_context ioc{threads};
646
647 // Create and launch a listening port
648 std::make_shared<listener>(
649 ioc,
650 tcp::endpoint{address, port},
651 doc_root)->run();
652
653 // Capture SIGINT and SIGTERM to perform a clean shutdown
654 net::signal_set signals(ioc, SIGINT, SIGTERM);
655 signals.async_wait(
656 [&](beast::error_code const&, int)
657 {
658 // Stop the `io_context`. This will cause `run()`
659 // to return immediately, eventually destroying the
660 // `io_context` and all of the sockets in it.
661 ioc.stop();
662 });
663
664 // Run the I/O service on the requested number of threads
665 std::vector<std::thread> v;
666 v.reserve(threads - 1);
667 for(auto i = threads - 1; i > 0; --i)
668 v.emplace_back(
669 [&ioc]
670 {
671 ioc.run();
672 });
673 ioc.run();
674
675 // (If we get here, it means we got a SIGINT or SIGTERM)
676
677 // Block until all the threads exit
678 for(auto& t : v)
679 t.join();
680
681 return EXIT_SUCCESS;
682 }