From 99562e3cfc882da8a2e8c81b7eef30e876f2db51 Mon Sep 17 00:00:00 2001 From: Volker Birk Date: Mon, 8 Jun 2020 14:57:26 +0200 Subject: [PATCH] ... --- webserver.cc | 184 ++------------------------------------------------- webserver.hh | 9 +++ 2 files changed, 15 insertions(+), 178 deletions(-) diff --git a/webserver.cc b/webserver.cc index 7e830b6..dd81f23 100644 --- a/webserver.cc +++ b/webserver.cc @@ -20,7 +20,7 @@ using tcp = boost::asio::ip::tcp; // from namespace pEp { Webserver::Webserver(net::ip::address addr, unsigned short port, std::string doc_root) - : _ioc(1), _acceptor(_ioc, {addr, port}), _doc_root(doc_root) { } + : _ioc(1), _acceptor(_ioc, {addr, port}), _doc_root(doc_root), _running(false) { } // Return a reasonable mime type based on the extension of a file. static beast::string_view mime_type(beast::string_view path) @@ -35,20 +35,15 @@ static beast::string_view mime_type(beast::string_view path) }(); if(iequals(ext, ".htm")) return "text/html"; if(iequals(ext, ".html")) return "text/html"; - if(iequals(ext, ".php")) return "text/html"; if(iequals(ext, ".css")) return "text/css"; if(iequals(ext, ".txt")) return "text/plain"; if(iequals(ext, ".js")) return "application/javascript"; if(iequals(ext, ".json")) return "application/json"; if(iequals(ext, ".xml")) return "application/xml"; - if(iequals(ext, ".swf")) return "application/x-shockwave-flash"; - if(iequals(ext, ".flv")) return "video/x-flv"; if(iequals(ext, ".png")) return "image/png"; - if(iequals(ext, ".jpe")) return "image/jpeg"; if(iequals(ext, ".jpeg")) return "image/jpeg"; if(iequals(ext, ".jpg")) return "image/jpeg"; if(iequals(ext, ".gif")) return "image/gif"; - if(iequals(ext, ".bmp")) return "image/bmp"; if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon"; if(iequals(ext, ".tiff")) return "image/tiff"; if(iequals(ext, ".tif")) return "image/tiff"; @@ -57,180 +52,12 @@ static beast::string_view mime_type(beast::string_view path) return "application/text"; } -// Append an HTTP rel-path to a local filesystem path. -// The returned path is normalized for the platform. -static std::string path_cat( - beast::string_view base, - beast::string_view path) -{ - if (base.empty()) - return std::string(path); - std::string result(base); -#ifdef BOOST_MSVC - char constexpr path_separator = '\\'; - if(result.back() == path_separator) - result.resize(result.size() - 1); - result.append(path.data(), path.size()); - for(auto& c : result) - if(c == '/') - c = path_separator; -#else - char constexpr path_separator = '/'; - if(result.back() == path_separator) - result.resize(result.size() - 1); - result.append(path.data(), path.size()); -#endif - return result; -} - -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void handle_request( - beast::string_view doc_root, - http::request>&& req, - Send&& send) -{ - // Returns a bad request response - auto const bad_request = - [&req](beast::string_view why) - { - http::response res{http::status::bad_request, req.version()}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, "text/html"); - res.keep_alive(req.keep_alive()); - res.body() = std::string(why); - res.prepare_payload(); - return res; - }; - - // Returns a not found response - auto const not_found = - [&req](beast::string_view target) - { - http::response res{http::status::not_found, req.version()}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, "text/html"); - res.keep_alive(req.keep_alive()); - res.body() = "The resource '" + std::string(target) + "' was not found."; - res.prepare_payload(); - return res; - }; - - // Returns a server error response - auto const server_error = - [&req](beast::string_view what) - { - http::response res{http::status::internal_server_error, req.version()}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, "text/html"); - res.keep_alive(req.keep_alive()); - res.body() = "An error occurred: '" + std::string(what) + "'"; - res.prepare_payload(); - return res; - }; - - // Make sure we can handle the method - if( req.method() != http::verb::get && - req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); - - // Request path must be absolute and not contain "..". - if( req.target().empty() || - req.target()[0] != '/' || - req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); - - // Build the path to the requested file - std::string path = path_cat(doc_root, req.target()); - if(req.target().back() == '/') - path.append("index.html"); - - // Attempt to open the file - beast::error_code ec; - http::file_body::value_type body; - body.open(path.c_str(), beast::file_mode::scan, ec); - - // Handle the case where the file doesn't exist - if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); - - // Handle an unknown error - if(ec) - return send(server_error(ec.message())); - - // Cache the size since we need it after the move - auto const size = body.size(); - - // Respond to HEAD request - if(req.method() == http::verb::head) - { - http::response res{http::status::ok, req.version()}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, mime_type(path)); - res.content_length(size); - res.keep_alive(req.keep_alive()); - return send(std::move(res)); - } - - // Respond to GET request - http::response res{ - std::piecewise_construct, - std::make_tuple(std::move(body)), - std::make_tuple(http::status::ok, req.version())}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, mime_type(path)); - res.content_length(size); - res.keep_alive(req.keep_alive()); - return send(std::move(res)); -} - // Report a failure -void -fail(beast::error_code ec, char const* what) +void fail(beast::error_code ec, char const* what) { std::cerr << what << ": " << ec.message() << "\n"; } -// This is the C++11 equivalent of a generic lambda. -// The function object is used to send an HTTP message. -template -struct send_lambda -{ - Stream& stream_; - bool& close_; - beast::error_code& ec_; - - explicit - send_lambda( - Stream& stream, - bool& close, - beast::error_code& ec) - : stream_(stream) - , close_(close) - , ec_(ec) - { - } - - template - void - operator()(http::message&& msg) const - { - // Determine if we should close the connection after - close_ = msg.need_eof(); - - // We need the serializer here because the serializer requires - // a non-const file_body, and the message oriented version of - // http::write only works with const messages. - http::serializer sr{msg}; - http::write(stream_, sr, ec_); - } -}; - // Handles an HTTP server connection void Webserver::do_session(tcp::socket& socket) { @@ -241,7 +68,7 @@ void Webserver::do_session(tcp::socket& socket) beast::flat_buffer buffer; // This lambda is used to send messages - send_lambda lambda{socket, close, ec}; +// send_lambda lambda{socket, close, ec}; for(;;) { @@ -254,7 +81,7 @@ void Webserver::do_session(tcp::socket& socket) return fail(ec, "read"); // Send the response - handle_request(_doc_root, std::move(req), lambda); +// handle_request(_doc_root, std::move(req), lambda); if(ec) return fail(ec, "write"); if(close) @@ -273,8 +100,9 @@ void Webserver::do_session(tcp::socket& socket) void Webserver::run() { + _running = true; // The acceptor receives incoming connections - for(;;) + while (_running) { // This will receive the new connection tcp::socket socket{_ioc}; diff --git a/webserver.hh b/webserver.hh index 2661d41..42c5b13 100644 --- a/webserver.hh +++ b/webserver.hh @@ -9,6 +9,13 @@ namespace net = boost::asio; using tcp = boost::asio::ip::tcp; namespace pEp { + // class Webserver + // + // when an URL handler is present it is called for each matching URL + // otherwise this server is searching for static files in doc_root + // only registered file types and no subdirectories are served for + // static files + class Webserver { public: typedef boost::regex url_t; @@ -19,6 +26,7 @@ namespace pEp { tcp::acceptor _acceptor; std::string _doc_root; std::vector< std::pair< url_t, handler_t > > _urls; + bool _running; public: Webserver(net::ip::address addr, unsigned short port, std::string doc_root); @@ -28,6 +36,7 @@ namespace pEp { void add_url_handler(url_t url, handler_t handler); void run(); + void shutdown(); protected: void do_session(tcp::socket& socket);