From c12ded9abc89bb7799e286b32f4c102a7fef9719 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 2 Jul 2017 11:24:10 -0700 Subject: [PATCH] Add http-server-threaded example --- CHANGELOG.md | 1 + doc/2_examples.qbk | 11 + example/CMakeLists.txt | 1 + example/Jamfile | 1 + example/http-server-threaded/CMakeLists.txt | 17 ++ example/http-server-threaded/Jamfile | 13 + .../http_server_threaded.cpp | 224 ++++++++++++++++++ 7 files changed, 268 insertions(+) create mode 100644 example/http-server-threaded/CMakeLists.txt create mode 100644 example/http-server-threaded/Jamfile create mode 100644 example/http-server-threaded/http_server_threaded.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9f7133..b6985faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ HTTP: * Refine Body::size specification * Newly constructed responses have a 200 OK result * Refactor file_body for best practices +* Add http-server-threaded example -------------------------------------------------------------------------------- diff --git a/doc/2_examples.qbk b/doc/2_examples.qbk index 703e63d0..a94de120 100644 --- a/doc/2_examples.qbk +++ b/doc/2_examples.qbk @@ -89,6 +89,17 @@ suitable as a starting point on an embedded device. +[section HTTP Server (Threaded)] + +This example implements a very simple HTTP server using +synchronous interfaces and using one thread per connection: + +* [repo_file example/http-server-threaded/http_server_threaded.cpp] + +[endsect] + + + [section WebSocket Client (with SSL)] Establish a WebSocket connection over an encrypted TLS connection, diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index e728f48b..b42db182 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory (http-client) add_subdirectory (http-crawl) add_subdirectory (http-server-fast) add_subdirectory (http-server-small) +add_subdirectory (http-server-threaded) add_subdirectory (server-framework) add_subdirectory (websocket-client) diff --git a/example/Jamfile b/example/Jamfile index be752006..2d913f4f 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -10,6 +10,7 @@ build-project http-client ; build-project http-crawl ; build-project http-server-fast ; build-project http-server-small ; +build-project http-server-threaded ; build-project server-framework ; build-project websocket-client ; diff --git a/example/http-server-threaded/CMakeLists.txt b/example/http-server-threaded/CMakeLists.txt new file mode 100644 index 00000000..a11dbfa0 --- /dev/null +++ b/example/http-server-threaded/CMakeLists.txt @@ -0,0 +1,17 @@ +# Part of Beast + +GroupSources(include/beast beast) +GroupSources(example/common common) +GroupSources(example/http-server-threaded "/") + +add_executable (http-server-threaded + ${BEAST_INCLUDES} + ${COMMON_INCLUDES} + http_server_threaded.cpp +) + +target_link_libraries(http-server-threaded + Beast + ${Boost_FILESYSTEM_LIBRARY} + ) + diff --git a/example/http-server-threaded/Jamfile b/example/http-server-threaded/Jamfile new file mode 100644 index 00000000..f2f5f16e --- /dev/null +++ b/example/http-server-threaded/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# + +exe http-server-threaded : + http_server_threaded.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http-server-threaded/http_server_threaded.cpp b/example/http-server-threaded/http_server_threaded.cpp new file mode 100644 index 00000000..84a00763 --- /dev/null +++ b/example/http-server-threaded/http_server_threaded.cpp @@ -0,0 +1,224 @@ +// +// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "../common/file_body.hpp" +#include "../common/mime_types.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +// +// Example: HTTP server, synchronous, one thread per connection +// +//------------------------------------------------------------------------------ + +namespace ip = boost::asio::ip; // from +using tcp = boost::asio::ip::tcp; // from +namespace http = beast::http; // from + +class connection + : public std::enable_shared_from_this +{ + tcp::socket sock_; + beast::string_view root_; + +public: + explicit + connection(tcp::socket&& sock, beast::string_view root) + : sock_(std::move(sock)) + , root_(root) + { + } + + void + run() + { + // Bind a shared_ptr to *this into the thread. + // When the thread exits, the connection object + // will be destroyed. + // + std::thread{&connection::do_run, shared_from_this()}.detach(); + } + +private: + // Send a client error response + http::response + client_error(http::status result, beast::string_view text) + { + http::response res; + res.result(result); + res.set(http::field::server, BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/plain"); + res.set(http::field::connection, "close"); + res.body = text; + res.prepare_payload(); + return res; + } + + // Return an HTTP Not Found response + // + beast::http::response + not_found() const + { + beast::http::response res; + res.result(beast::http::status::not_found); + res.set(beast::http::field::server, BEAST_VERSION_STRING); + res.set(beast::http::field::content_type, "text/html"); + res.set(http::field::connection, "close"); + res.body = "The file was not found"; + res.prepare_payload(); + return res; + } + + // Return an HTTP Server Error + // + beast::http::response + server_error(beast::error_code const& ec) const + { + beast::http::response res; + res.result(beast::http::status::internal_server_error); + res.set(beast::http::field::server, BEAST_VERSION_STRING); + res.set(beast::http::field::content_type, "text/html"); + res.set(http::field::connection, "close"); + res.body = "Error: " + ec.message(); + res.prepare_payload(); + return res; + } + + // Return a file response to an HTTP GET request + // + boost::optional> + get(boost::filesystem::path const& full_path, + beast::error_code& ec) const + { + beast::http::response res; + res.set(beast::http::field::server, BEAST_VERSION_STRING); + res.set(beast::http::field::content_type, mime_type(full_path)); + res.set(http::field::connection, "close"); + res.body.open(full_path, "rb", ec); + if(ec) + return boost::none; + res.set(beast::http::field::content_length, res.body.size()); + return res; + } + + // Handle a request + template + void + do_request(http::request const& req, beast::error_code& ec) + { + // verb must be get + if(req.method() != http::verb::get) + { + http::write(sock_, client_error(http::status::bad_request, "Unsupported method"), ec); + return; + } + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != std::string::npos) + { + http::write(sock_, client_error(http::status::not_found, "File not found"), ec); + return; + } + + auto full_path = root_.to_string(); + full_path.append(req.target().data(), req.target().size()); + + beast::error_code file_ec; + auto res = get(full_path, file_ec); + + if(file_ec == beast::errc::no_such_file_or_directory) + http::write(sock_, not_found(), ec); + else if(ec) + http::write(sock_, server_error(file_ec), ec); + else + http::write(sock_, std::move(*res), ec); + } + + void + do_run() + { + try + { + beast::error_code ec; + beast::flat_buffer buffer; + for(;;) + { + http::request_parser parser; + parser.header_limit(8192); + parser.body_limit(1024 * 1024); + http::read(sock_, buffer, parser, ec); + if(ec == http::error::end_of_stream) + break; + if(ec) + throw beast::system_error{ec}; + do_request(parser.get(), ec); + if(ec) + { + if(ec != http::error::end_of_stream) + throw beast::system_error{ec}; + break; + } + } + sock_.shutdown(tcp::socket::shutdown_both, ec); + if(ec && ec != boost::asio::error::not_connected) + throw beast::system_error{ec}; + } + catch (const std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + } + } +}; + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 4) + { + std::cerr << "Usage: http_server
\n"; + std::cerr << " For IPv4, try:\n"; + std::cerr << " receiver 0.0.0.0 80 .\n"; + std::cerr << " For IPv6, try:\n"; + std::cerr << " receiver 0::0 80 .\n"; + return EXIT_FAILURE; + } + + auto address = ip::address::from_string(argv[1]); + unsigned short port = static_cast(std::atoi(argv[2])); + std::string doc_root = argv[3]; + + boost::asio::io_service ios{1}; + tcp::acceptor acceptor{ios, {address, port}}; + for(;;) + { + tcp::socket sock{ios}; + acceptor.accept(sock); + std::make_shared(std::move(sock), doc_root)->run(); + } + } + catch (const std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } +}