]> git.proxmox.com Git - ceph.git/blame - ceph/src/boost/libs/beast/example/http/server/flex/http_server_flex.cpp
Add patch for failing prerm scripts
[ceph.git] / ceph / src / boost / libs / beast / example / http / server / flex / http_server_flex.cpp
CommitLineData
b32b8144
FG
1//
2// Copyright (c) 2016-2017 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/detect_ssl.hpp"
17#include "example/common/server_certificate.hpp"
18
19#include <boost/beast/core.hpp>
20#include <boost/beast/http.hpp>
21#include <boost/beast/version.hpp>
22#include <boost/asio/bind_executor.hpp>
23#include <boost/asio/ip/tcp.hpp>
24#include <boost/asio/ssl/stream.hpp>
25#include <boost/asio/strand.hpp>
26#include <boost/config.hpp>
27#include <algorithm>
28#include <cstdlib>
29#include <functional>
30#include <iostream>
31#include <memory>
32#include <string>
33#include <thread>
34
35using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
36namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
37namespace http = boost::beast::http; // from <boost/beast/http.hpp>
38
39// Return a reasonable mime type based on the extension of a file.
40boost::beast::string_view
41mime_type(boost::beast::string_view path)
42{
43 using boost::beast::iequals;
44 auto const ext = [&path]
45 {
46 auto const pos = path.rfind(".");
47 if(pos == boost::beast::string_view::npos)
48 return boost::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.
77std::string
78path_cat(
79 boost::beast::string_view base,
80 boost::beast::string_view path)
81{
82 if(base.empty())
83 return path.to_string();
84 std::string result = base.to_string();
85#if 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.
106template<
107 class Body, class Allocator,
108 class Send>
109void
110handle_request(
111 boost::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](boost::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() = why.to_string();
124 res.prepare_payload();
125 return res;
126 };
127
128 // Returns a not found response
129 auto const not_found =
130 [&req](boost::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 '" + target.to_string() + "' 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](boost::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: '" + what.to_string() + "'";
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("..") != boost::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 boost::beast::error_code ec;
172 http::file_body::value_type body;
173 body.open(path.c_str(), boost::beast::file_mode::scan, ec);
174
175 // Handle the case where the file doesn't exist
176 if(ec == boost::system::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
11fdf7f2
TL
183 // Cache the size since we need it after the move
184 auto const size = body.size();
185
b32b8144
FG
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));
11fdf7f2 192 res.content_length(size);
b32b8144
FG
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));
11fdf7f2 204 res.content_length(size);
b32b8144
FG
205 res.keep_alive(req.keep_alive());
206 return send(std::move(res));
207}
208
209//------------------------------------------------------------------------------
210
211// Report a failure
212void
213fail(boost::system::error_code ec, char const* what)
214{
215 std::cerr << what << ": " << ec.message() << "\n";
216}
217
218// Handles an HTTP server connection.
219// This uses the Curiously Recurring Template Pattern so that
220// the same code works with both SSL streams and regular sockets.
221template<class Derived>
222class session
223{
224 // Access the derived class, this is part of
225 // the Curiously Recurring Template Pattern idiom.
226 Derived&
227 derived()
228 {
229 return static_cast<Derived&>(*this);
230 }
231
232 // This is the C++11 equivalent of a generic lambda.
233 // The function object is used to send an HTTP message.
234 struct send_lambda
235 {
236 session& self_;
237
238 explicit
239 send_lambda(session& self)
240 : self_(self)
241 {
242 }
243
244 template<bool isRequest, class Body, class Fields>
245 void
246 operator()(http::message<isRequest, Body, Fields>&& msg) const
247 {
248 // The lifetime of the message has to extend
249 // for the duration of the async operation so
250 // we use a shared_ptr to manage it.
251 auto sp = std::make_shared<
252 http::message<isRequest, Body, Fields>>(std::move(msg));
253
254 // Store a type-erased version of the shared
255 // pointer in the class to keep it alive.
256 self_.res_ = sp;
257
258 // Write the response
259 http::async_write(
260 self_.derived().stream(),
261 *sp,
262 boost::asio::bind_executor(
263 self_.strand_,
264 std::bind(
265 &session::on_write,
266 self_.derived().shared_from_this(),
267 std::placeholders::_1,
268 std::placeholders::_2,
269 sp->need_eof())));
270 }
271 };
272
273 std::string const& doc_root_;
274 http::request<http::string_body> req_;
275 std::shared_ptr<void> res_;
276 send_lambda lambda_;
277
278protected:
279 boost::asio::strand<
280 boost::asio::io_context::executor_type> strand_;
281 boost::beast::flat_buffer buffer_;
282
283public:
284 // Take ownership of the buffer
285 explicit
286 session(
287 boost::asio::io_context& ioc,
288 boost::beast::flat_buffer buffer,
289 std::string const& doc_root)
290 : doc_root_(doc_root)
291 , lambda_(*this)
292 , strand_(ioc.get_executor())
293 , buffer_(std::move(buffer))
294 {
295 }
296
297 void
298 do_read()
299 {
300 // Read a request
301 http::async_read(
302 derived().stream(),
303 buffer_,
304 req_,
305 boost::asio::bind_executor(
306 strand_,
307 std::bind(
308 &session::on_read,
309 derived().shared_from_this(),
310 std::placeholders::_1,
311 std::placeholders::_2)));
312 }
313
314 void
315 on_read(
316 boost::system::error_code ec,
317 std::size_t bytes_transferred)
318 {
319 boost::ignore_unused(bytes_transferred);
320
321 // This means they closed the connection
322 if(ec == http::error::end_of_stream)
323 return derived().do_eof();
324
325 if(ec)
326 return fail(ec, "read");
327
328 // Send the response
329 handle_request(doc_root_, std::move(req_), lambda_);
330 }
331
332 void
333 on_write(
334 boost::system::error_code ec,
335 std::size_t bytes_transferred,
336 bool close)
337 {
338 boost::ignore_unused(bytes_transferred);
339
340 if(ec)
341 return fail(ec, "write");
342
343 if(close)
344 {
345 // This means we should close the connection, usually because
346 // the response indicated the "Connection: close" semantic.
347 return derived().do_eof();
348 }
349
350 // We're done with the response so delete it
351 res_ = nullptr;
352
353 // Read another request
354 do_read();
355 }
356};
357
358// Handles a plain HTTP connection
359class plain_session
360 : public session<plain_session>
361 , public std::enable_shared_from_this<plain_session>
362{
363 tcp::socket socket_;
364 boost::asio::strand<
365 boost::asio::io_context::executor_type> strand_;
366
367public:
368 // Create the session
369 plain_session(
370 tcp::socket socket,
371 boost::beast::flat_buffer buffer,
372 std::string const& doc_root)
373 : session<plain_session>(
374 socket.get_executor().context(),
375 std::move(buffer),
376 doc_root)
377 , socket_(std::move(socket))
378 , strand_(socket_.get_executor())
379 {
380 }
381
382 // Called by the base class
383 tcp::socket&
384 stream()
385 {
386 return socket_;
387 }
388
389 // Start the asynchronous operation
390 void
391 run()
392 {
393 do_read();
394 }
395
396 void
397 do_eof()
398 {
399 // Send a TCP shutdown
400 boost::system::error_code ec;
401 socket_.shutdown(tcp::socket::shutdown_send, ec);
402
403 // At this point the connection is closed gracefully
404 }
405};
406
407// Handles an SSL HTTP connection
408class ssl_session
409 : public session<ssl_session>
410 , public std::enable_shared_from_this<ssl_session>
411{
412 tcp::socket socket_;
413 ssl::stream<tcp::socket&> stream_;
414 boost::asio::strand<
415 boost::asio::io_context::executor_type> strand_;
416
417public:
418 // Create the session
419 ssl_session(
420 tcp::socket socket,
421 ssl::context& ctx,
422 boost::beast::flat_buffer buffer,
423 std::string const& doc_root)
424 : session<ssl_session>(
425 socket.get_executor().context(),
426 std::move(buffer),
427 doc_root)
428 , socket_(std::move(socket))
429 , stream_(socket_, ctx)
430 , strand_(stream_.get_executor())
431 {
432 }
433
434 // Called by the base class
435 ssl::stream<tcp::socket&>&
436 stream()
437 {
438 return stream_;
439 }
440
441 // Start the asynchronous operation
442 void
443 run()
444 {
445 // Perform the SSL handshake
446 // Note, this is the buffered version of the handshake.
447 stream_.async_handshake(
448 ssl::stream_base::server,
449 buffer_.data(),
450 boost::asio::bind_executor(
451 strand_,
452 std::bind(
453 &ssl_session::on_handshake,
454 shared_from_this(),
455 std::placeholders::_1,
456 std::placeholders::_2)));
457 }
458 void
459 on_handshake(
460 boost::system::error_code ec,
461 std::size_t bytes_used)
462 {
463 if(ec)
464 return fail(ec, "handshake");
465
466 // Consume the portion of the buffer used by the handshake
467 buffer_.consume(bytes_used);
468
469 do_read();
470 }
471
472 void
473 do_eof()
474 {
475 // Perform the SSL shutdown
476 stream_.async_shutdown(
477 boost::asio::bind_executor(
478 strand_,
479 std::bind(
480 &ssl_session::on_shutdown,
481 shared_from_this(),
482 std::placeholders::_1)));
483 }
484
485 void
486 on_shutdown(boost::system::error_code ec)
487 {
488 if(ec)
489 return fail(ec, "shutdown");
490
491 // At this point the connection is closed gracefully
492 }
493};
494
495//------------------------------------------------------------------------------
496
497// Detects SSL handshakes
498class detect_session : public std::enable_shared_from_this<detect_session>
499{
500 tcp::socket socket_;
501 ssl::context& ctx_;
502 boost::asio::strand<
503 boost::asio::io_context::executor_type> strand_;
504 std::string const& doc_root_;
505 boost::beast::flat_buffer buffer_;
506
507public:
508 explicit
509 detect_session(
510 tcp::socket socket,
511 ssl::context& ctx,
512 std::string const& doc_root)
513 : socket_(std::move(socket))
514 , ctx_(ctx)
515 , strand_(socket_.get_executor())
516 , doc_root_(doc_root)
517 {
518 }
519
520 // Launch the detector
521 void
522 run()
523 {
524 async_detect_ssl(
525 socket_,
526 buffer_,
527 boost::asio::bind_executor(
528 strand_,
529 std::bind(
530 &detect_session::on_detect,
531 shared_from_this(),
532 std::placeholders::_1,
533 std::placeholders::_2)));
534
535 }
536
537 void
538 on_detect(boost::system::error_code ec, boost::tribool result)
539 {
540 if(ec)
541 return fail(ec, "detect");
542
543 if(result)
544 {
545 // Launch SSL session
546 std::make_shared<ssl_session>(
547 std::move(socket_),
548 ctx_,
549 std::move(buffer_),
550 doc_root_)->run();
551 return;
552 }
553
554 // Launch plain session
555 std::make_shared<plain_session>(
556 std::move(socket_),
557 std::move(buffer_),
558 doc_root_)->run();
559 }
560};
561
562// Accepts incoming connections and launches the sessions
563class listener : public std::enable_shared_from_this<listener>
564{
565 ssl::context& ctx_;
566 boost::asio::strand<
567 boost::asio::io_context::executor_type> strand_;
568 tcp::acceptor acceptor_;
569 tcp::socket socket_;
570 std::string const& doc_root_;
571
572public:
573 listener(
574 boost::asio::io_context& ioc,
575 ssl::context& ctx,
576 tcp::endpoint endpoint,
577 std::string const& doc_root)
578 : ctx_(ctx)
579 , strand_(ioc.get_executor())
580 , acceptor_(ioc)
581 , socket_(ioc)
582 , doc_root_(doc_root)
583 {
584 boost::system::error_code ec;
585
586 // Open the acceptor
587 acceptor_.open(endpoint.protocol(), ec);
588 if(ec)
589 {
590 fail(ec, "open");
591 return;
592 }
593
11fdf7f2
TL
594 // Allow address reuse
595 acceptor_.set_option(boost::asio::socket_base::reuse_address(true));
596 if(ec)
597 {
598 fail(ec, "set_option");
599 return;
600 }
601
b32b8144
FG
602 // Bind to the server address
603 acceptor_.bind(endpoint, ec);
604 if(ec)
605 {
606 fail(ec, "bind");
607 return;
608 }
609
610 // Start listening for connections
611 acceptor_.listen(
612 boost::asio::socket_base::max_listen_connections, ec);
613 if(ec)
614 {
615 fail(ec, "listen");
616 return;
617 }
618 }
619
620 // Start accepting incoming connections
621 void
622 run()
623 {
624 if(! acceptor_.is_open())
625 return;
626 do_accept();
627 }
628
629 void
630 do_accept()
631 {
632 acceptor_.async_accept(
633 socket_,
634 std::bind(
635 &listener::on_accept,
636 shared_from_this(),
637 std::placeholders::_1));
638 }
639
640 void
641 on_accept(boost::system::error_code ec)
642 {
643 if(ec)
644 {
645 fail(ec, "accept");
646 }
647 else
648 {
649 // Create the detector session and run it
650 std::make_shared<detect_session>(
651 std::move(socket_),
652 ctx_,
653 doc_root_)->run();
654 }
655
656 // Accept another connection
657 do_accept();
658 }
659};
660
661//------------------------------------------------------------------------------
662
663int main(int argc, char* argv[])
664{
665 // Check command line arguments.
666 if (argc != 5)
667 {
668 std::cerr <<
669 "Usage: http-server-flex <address> <port> <doc_root> <threads>\n" <<
670 "Example:\n" <<
671 " http-server-flex 0.0.0.0 8080 .\n";
672 return EXIT_FAILURE;
673 }
674 auto const address = boost::asio::ip::make_address(argv[1]);
675 auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
676 std::string const doc_root = argv[3];
677 auto const threads = std::max<int>(1, std::atoi(argv[4]));
678
679 // The io_context is required for all I/O
680 boost::asio::io_context ioc{threads};
681
682 // The SSL context is required, and holds certificates
683 ssl::context ctx{ssl::context::sslv23};
684
685 // This holds the self-signed certificate used by the server
686 load_server_certificate(ctx);
687
688 // Create and launch a listening port
689 std::make_shared<listener>(
690 ioc,
691 ctx,
692 tcp::endpoint{address, port},
693 doc_root)->run();
694
695 // Run the I/O service on the requested number of threads
696 std::vector<std::thread> v;
697 v.reserve(threads - 1);
698 for(auto i = threads - 1; i > 0; --i)
699 v.emplace_back(
700 [&ioc]
701 {
702 ioc.run();
703 });
704 ioc.run();
705
706 return EXIT_SUCCESS;
707}