]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/beast/example/http/server/async-ssl/http_server_async_ssl.cpp
update sources to v12.2.3
[ceph.git] / ceph / src / boost / libs / beast / example / http / server / async-ssl / http_server_async_ssl.cpp
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 SSL server, 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/version.hpp>
21 #include <boost/asio/bind_executor.hpp>
22 #include <boost/asio/ip/tcp.hpp>
23 #include <boost/asio/ssl/stream.hpp>
24 #include <boost/asio/strand.hpp>
25 #include <boost/config.hpp>
26 #include <algorithm>
27 #include <cstdlib>
28 #include <functional>
29 #include <iostream>
30 #include <memory>
31 #include <string>
32 #include <thread>
33 #include <vector>
34
35 using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
36 namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
37 namespace http = boost::beast::http; // from <boost/beast/http.hpp>
38
39 // Return a reasonable mime type based on the extension of a file.
40 boost::beast::string_view
41 mime_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.
77 std::string
78 path_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.
106 template<
107 class Body, class Allocator,
108 class Send>
109 void
110 handle_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
209 void
210 fail(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 class session : public std::enable_shared_from_this<session>
217 {
218 // This is the C++11 equivalent of a generic lambda.
219 // The function object is used to send an HTTP message.
220 struct send_lambda
221 {
222 session& self_;
223
224 explicit
225 send_lambda(session& self)
226 : self_(self)
227 {
228 }
229
230 template<bool isRequest, class Body, class Fields>
231 void
232 operator()(http::message<isRequest, Body, Fields>&& msg) const
233 {
234 // The lifetime of the message has to extend
235 // for the duration of the async operation so
236 // we use a shared_ptr to manage it.
237 auto sp = std::make_shared<
238 http::message<isRequest, Body, Fields>>(std::move(msg));
239
240 // Store a type-erased version of the shared
241 // pointer in the class to keep it alive.
242 self_.res_ = sp;
243
244 // Write the response
245 http::async_write(
246 self_.stream_,
247 *sp,
248 boost::asio::bind_executor(
249 self_.strand_,
250 std::bind(
251 &session::on_write,
252 self_.shared_from_this(),
253 std::placeholders::_1,
254 std::placeholders::_2,
255 sp->need_eof())));
256 }
257 };
258
259 tcp::socket socket_;
260 ssl::stream<tcp::socket&> stream_;
261 boost::asio::strand<
262 boost::asio::io_context::executor_type> strand_;
263 boost::beast::flat_buffer buffer_;
264 std::string const& doc_root_;
265 http::request<http::string_body> req_;
266 std::shared_ptr<void> res_;
267 send_lambda lambda_;
268
269 public:
270 // Take ownership of the socket
271 explicit
272 session(
273 tcp::socket socket,
274 ssl::context& ctx,
275 std::string const& doc_root)
276 : socket_(std::move(socket))
277 , stream_(socket_, ctx)
278 , strand_(socket_.get_executor())
279 , doc_root_(doc_root)
280 , lambda_(*this)
281 {
282 }
283
284 // Start the asynchronous operation
285 void
286 run()
287 {
288 // Perform the SSL handshake
289 stream_.async_handshake(
290 ssl::stream_base::server,
291 boost::asio::bind_executor(
292 strand_,
293 std::bind(
294 &session::on_handshake,
295 shared_from_this(),
296 std::placeholders::_1)));
297 }
298
299 void
300 on_handshake(boost::system::error_code ec)
301 {
302 if(ec)
303 return fail(ec, "handshake");
304
305 do_read();
306 }
307
308 void
309 do_read()
310 {
311 // Read a request
312 http::async_read(stream_, buffer_, req_,
313 boost::asio::bind_executor(
314 strand_,
315 std::bind(
316 &session::on_read,
317 shared_from_this(),
318 std::placeholders::_1,
319 std::placeholders::_2)));
320 }
321
322 void
323 on_read(
324 boost::system::error_code ec,
325 std::size_t bytes_transferred)
326 {
327 boost::ignore_unused(bytes_transferred);
328
329 // This means they closed the connection
330 if(ec == http::error::end_of_stream)
331 return do_close();
332
333 if(ec)
334 return fail(ec, "read");
335
336 // Send the response
337 handle_request(doc_root_, std::move(req_), lambda_);
338 }
339
340 void
341 on_write(
342 boost::system::error_code ec,
343 std::size_t bytes_transferred,
344 bool close)
345 {
346 boost::ignore_unused(bytes_transferred);
347
348 if(ec)
349 return fail(ec, "write");
350
351 if(close)
352 {
353 // This means we should close the connection, usually because
354 // the response indicated the "Connection: close" semantic.
355 return do_close();
356 }
357
358 // We're done with the response so delete it
359 res_ = nullptr;
360
361 // Read another request
362 do_read();
363 }
364
365 void
366 do_close()
367 {
368 // Perform the SSL shutdown
369 stream_.async_shutdown(
370 boost::asio::bind_executor(
371 strand_,
372 std::bind(
373 &session::on_shutdown,
374 shared_from_this(),
375 std::placeholders::_1)));
376 }
377
378 void
379 on_shutdown(boost::system::error_code ec)
380 {
381 if(ec)
382 return fail(ec, "shutdown");
383
384 // At this point the connection is closed gracefully
385 }
386 };
387
388 //------------------------------------------------------------------------------
389
390 // Accepts incoming connections and launches the sessions
391 class listener : public std::enable_shared_from_this<listener>
392 {
393 ssl::context& ctx_;
394 tcp::acceptor acceptor_;
395 tcp::socket socket_;
396 std::string const& doc_root_;
397
398 public:
399 listener(
400 boost::asio::io_context& ioc,
401 ssl::context& ctx,
402 tcp::endpoint endpoint,
403 std::string const& doc_root)
404 : ctx_(ctx)
405 , acceptor_(ioc)
406 , socket_(ioc)
407 , doc_root_(doc_root)
408 {
409 boost::system::error_code ec;
410
411 // Open the acceptor
412 acceptor_.open(endpoint.protocol(), ec);
413 if(ec)
414 {
415 fail(ec, "open");
416 return;
417 }
418
419 // Bind to the server address
420 acceptor_.bind(endpoint, ec);
421 if(ec)
422 {
423 fail(ec, "bind");
424 return;
425 }
426
427 // Start listening for connections
428 acceptor_.listen(
429 boost::asio::socket_base::max_listen_connections, ec);
430 if(ec)
431 {
432 fail(ec, "listen");
433 return;
434 }
435 }
436
437 // Start accepting incoming connections
438 void
439 run()
440 {
441 if(! acceptor_.is_open())
442 return;
443 do_accept();
444 }
445
446 void
447 do_accept()
448 {
449 acceptor_.async_accept(
450 socket_,
451 std::bind(
452 &listener::on_accept,
453 shared_from_this(),
454 std::placeholders::_1));
455 }
456
457 void
458 on_accept(boost::system::error_code ec)
459 {
460 if(ec)
461 {
462 fail(ec, "accept");
463 }
464 else
465 {
466 // Create the session and run it
467 std::make_shared<session>(
468 std::move(socket_),
469 ctx_,
470 doc_root_)->run();
471 }
472
473 // Accept another connection
474 do_accept();
475 }
476 };
477
478 //------------------------------------------------------------------------------
479
480 int main(int argc, char* argv[])
481 {
482 // Check command line arguments.
483 if (argc != 5)
484 {
485 std::cerr <<
486 "Usage: http-server-async-ssl <address> <port> <doc_root> <threads>\n" <<
487 "Example:\n" <<
488 " http-server-async-ssl 0.0.0.0 8080 . 1\n";
489 return EXIT_FAILURE;
490 }
491 auto const address = boost::asio::ip::make_address(argv[1]);
492 auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
493 std::string const doc_root = argv[3];
494 auto const threads = std::max<int>(1, std::atoi(argv[4]));
495
496 // The io_context is required for all I/O
497 boost::asio::io_context ioc{threads};
498
499 // The SSL context is required, and holds certificates
500 ssl::context ctx{ssl::context::sslv23};
501
502 // This holds the self-signed certificate used by the server
503 load_server_certificate(ctx);
504
505 // Create and launch a listening port
506 std::make_shared<listener>(
507 ioc,
508 ctx,
509 tcp::endpoint{address, port},
510 doc_root)->run();
511
512 // Run the I/O service on the requested number of threads
513 std::vector<std::thread> v;
514 v.reserve(threads - 1);
515 for(auto i = threads - 1; i > 0; --i)
516 v.emplace_back(
517 [&ioc]
518 {
519 ioc.run();
520 });
521 ioc.run();
522
523 return EXIT_SUCCESS;
524 }