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