// // Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff 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) // // Official repository: https://github.com/boostorg/beast // #include "fields_alloc.hpp" #include "../common/mime_types.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ip = boost::asio::ip; // from using tcp = boost::asio::ip::tcp; // from namespace http = boost::beast::http; // from class http_worker { public: http_worker(http_worker const&) = delete; http_worker& operator=(http_worker const&) = delete; http_worker(tcp::acceptor& acceptor, const std::string& doc_root) : acceptor_(acceptor), doc_root_(doc_root) { } void start() { accept(); check_deadline(); } private: using alloc_t = fields_alloc; using request_body_t = http::basic_dynamic_body>; // The acceptor used to listen for incoming connections. tcp::acceptor& acceptor_; // The path to the root of the document directory. std::string doc_root_; // The socket for the currently connected client. tcp::socket socket_{acceptor_.get_io_service()}; // The buffer for performing reads boost::beast::flat_static_buffer<8192> buffer_; // The allocator used for the fields in the request and reply. alloc_t alloc_{8192}; // The parser for reading the requests boost::optional> parser_; // The timer putting a time limit on requests. boost::asio::basic_waitable_timer request_deadline_{ acceptor_.get_io_service(), (std::chrono::steady_clock::time_point::max)()}; // The string-based response message. boost::optional>> string_response_; // The string-based response serializer. boost::optional>> string_serializer_; // The file-based response message. boost::optional>> file_response_; // The file-based response serializer. boost::optional>> file_serializer_; void accept() { // Clean up any previous connection. boost::beast::error_code ec; socket_.close(ec); buffer_.consume(buffer_.size()); acceptor_.async_accept( socket_, [this](boost::beast::error_code ec) { if (ec) { accept(); } else { // Request must be fully processed within 60 seconds. request_deadline_.expires_from_now( std::chrono::seconds(60)); read_request(); } }); } void read_request() { // On each read the parser needs to be destroyed and // recreated. We store it in a boost::optional to // achieve that. // // Arguments passed to the parser constructor are // forwarded to the message object. A single argument // is forwarded to the body constructor. // // We construct the dynamic body with a 1MB limit // to prevent vulnerability to buffer attacks. // parser_.emplace( std::piecewise_construct, std::make_tuple(), std::make_tuple(alloc_)); http::async_read( socket_, buffer_, *parser_, [this](boost::beast::error_code ec) { if (ec) accept(); else process_request(parser_->get()); }); } void process_request(http::request> const& req) { switch (req.method()) { case http::verb::get: send_file(req.target()); break; default: // We return responses indicating an error if // we do not recognize the request method. send_bad_response( http::status::bad_request, "Invalid request-method '" + req.method_string().to_string() + "'\r\n"); break; } } void send_bad_response( http::status status, std::string const& error) { string_response_.emplace( std::piecewise_construct, std::make_tuple(), std::make_tuple(alloc_)); string_response_->result(status); string_response_->set(http::field::server, "Beast"); string_response_->set(http::field::connection, "close"); string_response_->set(http::field::content_type, "text/plain"); string_response_->body = error; string_response_->prepare_payload(); string_serializer_.emplace(*string_response_); http::async_write( socket_, *string_serializer_, [this](boost::beast::error_code ec) { socket_.shutdown(tcp::socket::shutdown_send, ec); string_serializer_.reset(); string_response_.reset(); accept(); }); } void send_file(boost::beast::string_view target) { // Request path must be absolute and not contain "..". if (target.empty() || target[0] != '/' || target.find("..") != std::string::npos) { send_bad_response( http::status::not_found, "File not found\r\n"); return; } std::string full_path = doc_root_; full_path.append( target.data(), target.size()); http::file_body::value_type file; boost::beast::error_code ec; file.open( full_path.c_str(), boost::beast::file_mode::read, ec); if(ec) { send_bad_response( http::status::not_found, "File not found\r\n"); return; } file_response_.emplace( std::piecewise_construct, std::make_tuple(), std::make_tuple(alloc_)); file_response_->result(http::status::ok); file_response_->set(http::field::server, "Beast"); file_response_->set(http::field::connection, "close"); file_response_->set(http::field::content_type, mime_type(target.to_string())); file_response_->body = std::move(file); file_response_->prepare_payload(); file_serializer_.emplace(*file_response_); http::async_write( socket_, *file_serializer_, [this](boost::beast::error_code ec) { socket_.shutdown(tcp::socket::shutdown_send, ec); file_serializer_.reset(); file_response_.reset(); accept(); }); } void check_deadline() { // The deadline may have moved, so check it has really passed. if (request_deadline_.expires_at() <= std::chrono::steady_clock::now()) { // Close socket to cancel any outstanding operation. boost::beast::error_code ec; socket_.close(); // Sleep indefinitely until we're given a new deadline. request_deadline_.expires_at( std::chrono::steady_clock::time_point::max()); } request_deadline_.async_wait( [this](boost::beast::error_code) { check_deadline(); }); } }; int main(int argc, char* argv[]) { try { // Check command line arguments. if (argc != 6) { std::cerr << "Usage: http_server_fast
{spin|block}\n"; std::cerr << " For IPv4, try:\n"; std::cerr << " http_server_fast 0.0.0.0 80 . 100 block\n"; std::cerr << " For IPv6, try:\n"; std::cerr << " http_server_fast 0::0 80 . 100 block\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]; int num_workers = std::atoi(argv[4]); bool spin = (std::strcmp(argv[5], "spin") == 0); boost::asio::io_service ios{1}; tcp::acceptor acceptor{ios, {address, port}}; std::list workers; for (int i = 0; i < num_workers; ++i) { workers.emplace_back(acceptor, doc_root); workers.back().start(); } if (spin) for (;;) ios.poll(); else ios.run(); } catch (const std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; return EXIT_FAILURE; } }