]> git.proxmox.com Git - ceph.git/blame - ceph/src/boost/libs/beast/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / boost / libs / beast / example / http / server / stackless-ssl / http_server_stackless_ssl.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 SSL server, stackless coroutine
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/coroutine.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#include <vector>
35
36using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
37namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
38namespace http = boost::beast::http; // from <boost/beast/http.hpp>
39
40// Return a reasonable mime type based on the extension of a file.
41boost::beast::string_view
42mime_type(boost::beast::string_view path)
43{
44 using boost::beast::iequals;
45 auto const ext = [&path]
46 {
47 auto const pos = path.rfind(".");
48 if(pos == boost::beast::string_view::npos)
49 return boost::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.
78std::string
79path_cat(
80 boost::beast::string_view base,
81 boost::beast::string_view path)
82{
83 if(base.empty())
84 return path.to_string();
85 std::string result = base.to_string();
86#if 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.
107template<
108 class Body, class Allocator,
109 class Send>
110void
111handle_request(
112 boost::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](boost::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() = why.to_string();
125 res.prepare_payload();
126 return res;
127 };
128
129 // Returns a not found response
130 auto const not_found =
131 [&req](boost::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 '" + target.to_string() + "' 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](boost::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: '" + what.to_string() + "'";
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("..") != boost::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 boost::beast::error_code ec;
173 http::file_body::value_type body;
174 body.open(path.c_str(), boost::beast::file_mode::scan, ec);
175
176 // Handle the case where the file doesn't exist
177 if(ec == boost::system::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
11fdf7f2
TL
184 // Cache the size since we need it after the move
185 auto const size = body.size();
186
b32b8144
FG
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));
11fdf7f2 193 res.content_length(size);
b32b8144
FG
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));
11fdf7f2 205 res.content_length(size);
b32b8144
FG
206 res.keep_alive(req.keep_alive());
207 return send(std::move(res));
208}
209
210//------------------------------------------------------------------------------
211
212// Report a failure
213void
214fail(boost::system::error_code ec, char const* what)
215{
216 std::cerr << what << ": " << ec.message() << "\n";
217}
218
219// Handles an HTTP server connection
220class session
221 : public boost::asio::coroutine
222 , public std::enable_shared_from_this<session>
223{
224 // This is the C++11 equivalent of a generic lambda.
225 // The function object is used to send an HTTP message.
226 struct send_lambda
227 {
228 session& self_;
229
230 explicit
231 send_lambda(session& self)
232 : self_(self)
233 {
234 }
235
236 template<bool isRequest, class Body, class Fields>
237 void
238 operator()(http::message<isRequest, Body, Fields>&& msg) const
239 {
240 // The lifetime of the message has to extend
241 // for the duration of the async operation so
242 // we use a shared_ptr to manage it.
243 auto sp = std::make_shared<
244 http::message<isRequest, Body, Fields>>(std::move(msg));
245
246 // Store a type-erased version of the shared
247 // pointer in the class to keep it alive.
248 self_.res_ = sp;
249
250 // Write the response
251 http::async_write(
252 self_.socket_,
253 *sp,
254 boost::asio::bind_executor(
255 self_.strand_,
256 std::bind(
257 &session::loop,
258 self_.shared_from_this(),
259 std::placeholders::_1,
260 std::placeholders::_2,
261 sp->need_eof())));
262 }
263 };
264
265 tcp::socket socket_;
266 ssl::stream<tcp::socket&> stream_;
267 boost::asio::strand<
268 boost::asio::io_context::executor_type> strand_;
269 boost::beast::flat_buffer buffer_;
270 std::string const& doc_root_;
271 http::request<http::string_body> req_;
272 std::shared_ptr<void> res_;
273 send_lambda lambda_;
274
275public:
276 // Take ownership of the socket
277 explicit
278 session(
279 tcp::socket socket,
280 ssl::context& ctx,
281 std::string const& doc_root)
282 : socket_(std::move(socket))
283 , stream_(socket_, ctx)
284 , strand_(socket_.get_executor())
285 , doc_root_(doc_root)
286 , lambda_(*this)
287 {
288 }
289
290 // Start the asynchronous operation
291 void
292 run()
293 {
294 loop({}, 0, false);
295 }
296
297#include <boost/asio/yield.hpp>
298 void
299 loop(
300 boost::system::error_code ec,
301 std::size_t bytes_transferred,
302 bool close)
303 {
304 boost::ignore_unused(bytes_transferred);
305 reenter(*this)
306 {
307 // Perform the SSL handshake
308 yield stream_.async_handshake(
309 ssl::stream_base::server,
310 boost::asio::bind_executor(
311 strand_,
312 std::bind(
313 &session::loop,
314 shared_from_this(),
315 std::placeholders::_1,
316 0,
317 false)));
318 if(ec)
319 return fail(ec, "handshake");
320
321 for(;;)
322 {
11fdf7f2
TL
323 // Make the request empty before reading,
324 // otherwise the operation behavior is undefined.
325 req_ = {};
326
b32b8144
FG
327 // Read a request
328 yield http::async_read(stream_, buffer_, req_,
329 boost::asio::bind_executor(
330 strand_,
331 std::bind(
332 &session::loop,
333 shared_from_this(),
334 std::placeholders::_1,
335 std::placeholders::_2,
336 false)));
337 if(ec == http::error::end_of_stream)
338 {
339 // The remote host closed the connection
340 break;
341 }
342 if(ec)
343 return fail(ec, "read");
344
345 // Send the response
346 yield handle_request(doc_root_, std::move(req_), lambda_);
347 if(ec)
348 return fail(ec, "write");
349 if(close)
350 {
351 // This means we should close the connection, usually because
352 // the response indicated the "Connection: close" semantic.
353 break;
354 }
355
356 // We're done with the response so delete it
357 res_ = nullptr;
358 }
359
360 // Perform the SSL shutdown
361 yield stream_.async_shutdown(
362 boost::asio::bind_executor(
363 strand_,
364 std::bind(
365 &session::loop,
366 shared_from_this(),
367 std::placeholders::_1,
368 0,
369 false)));
370 if(ec)
371 return fail(ec, "shutdown");
372
373 // At this point the connection is closed gracefully
374 }
375 }
376#include <boost/asio/unyield.hpp>
377};
378
379//------------------------------------------------------------------------------
380
381// Accepts incoming connections and launches the sessions
382class listener
383 : public boost::asio::coroutine
384 , public std::enable_shared_from_this<listener>
385{
386 ssl::context& ctx_;
387 tcp::acceptor acceptor_;
388 tcp::socket socket_;
389 std::string const& doc_root_;
390
391public:
392 listener(
393 boost::asio::io_context& ioc,
394 ssl::context& ctx,
395 tcp::endpoint endpoint,
396 std::string const& doc_root)
397 : ctx_(ctx)
398 , acceptor_(ioc)
399 , socket_(ioc)
400 , doc_root_(doc_root)
401 {
402 boost::system::error_code ec;
403
404 // Open the acceptor
405 acceptor_.open(endpoint.protocol(), ec);
406 if(ec)
407 {
408 fail(ec, "open");
409 return;
410 }
411
11fdf7f2
TL
412 // Allow address reuse
413 acceptor_.set_option(boost::asio::socket_base::reuse_address(true));
414 if(ec)
415 {
416 fail(ec, "set_option");
417 return;
418 }
419
b32b8144
FG
420 // Bind to the server address
421 acceptor_.bind(endpoint, ec);
422 if(ec)
423 {
424 fail(ec, "bind");
425 return;
426 }
427
428 // Start listening for connections
429 acceptor_.listen(
430 boost::asio::socket_base::max_listen_connections, ec);
431 if(ec)
432 {
433 fail(ec, "listen");
434 return;
435 }
436 }
437
438 // Start accepting incoming connections
439 void
440 run()
441 {
442 if(! acceptor_.is_open())
443 return;
444 loop();
445 }
446
447#include <boost/asio/yield.hpp>
448 void
449 loop(boost::system::error_code ec = {})
450 {
451 reenter(*this)
452 {
453 for(;;)
454 {
455 yield acceptor_.async_accept(
456 socket_,
457 std::bind(
458 &listener::loop,
459 shared_from_this(),
460 std::placeholders::_1));
461 if(ec)
462 {
463 fail(ec, "accept");
464 }
465 else
466 {
467 // Create the session and run it
468 std::make_shared<session>(
469 std::move(socket_),
470 ctx_,
471 doc_root_)->run();
472 }
473 }
474 }
475 }
476#include <boost/asio/unyield.hpp>
477};
478
479//------------------------------------------------------------------------------
480
481int main(int argc, char* argv[])
482{
483 // Check command line arguments.
484 if (argc != 5)
485 {
486 std::cerr <<
487 "Usage: http-server-stackless-ssl <address> <port> <doc_root> <threads>\n" <<
488 "Example:\n" <<
489 " http-server-stackless-ssl 0.0.0.0 8080 . 1\n";
490 return EXIT_FAILURE;
491 }
492 auto const address = boost::asio::ip::make_address(argv[1]);
493 auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
494 std::string const doc_root = argv[3];
495 auto const threads = std::max<int>(1, std::atoi(argv[4]));
496
497 // The io_context is required for all I/O
498 boost::asio::io_context ioc{threads};
499
500 // The SSL context is required, and holds certificates
501 ssl::context ctx{ssl::context::sslv23};
502
503 // This holds the self-signed certificate used by the server
504 load_server_certificate(ctx);
505
506 // Create and launch a listening port
507 std::make_shared<listener>(
508 ioc,
509 ctx,
510 tcp::endpoint{address, port},
511 doc_root)->run();
512
513 // Run the I/O service on the requested number of threads
514 std::vector<std::thread> v;
515 v.reserve(threads - 1);
516 for(auto i = threads - 1; i > 0; --i)
517 v.emplace_back(
518 [&ioc]
519 {
520 ioc.run();
521 });
522 ioc.run();
523
524 return EXIT_SUCCESS;
525}