diff --git a/CHANGELOG.md b/CHANGELOG.md index 30aabfd8..48aa6b0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ Version 67: * Fix doc example link +* Add http-server-small example -------------------------------------------------------------------------------- diff --git a/doc/2_examples.qbk b/doc/2_examples.qbk index 310583a4..764da173 100644 --- a/doc/2_examples.qbk +++ b/doc/2_examples.qbk @@ -66,7 +66,7 @@ over a TLS connection. Requires OpenSSL to build. -[section HTTP Server] +[section HTTP Server (Fast)] This example implements a very simple HTTP server with some optimizations suitable for calculating benchmarks. @@ -78,6 +78,17 @@ some optimizations suitable for calculating benchmarks. +[section HTTP Server (Small)] + +This example implements a very simple HTTP server +suitable as a starting point on an embedded device. + +* [repo_file example/http-server-small/http_server_small.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 3a823ab7..c665a92c 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory (echo-op) add_subdirectory (http-client) add_subdirectory (http-crawl) add_subdirectory (http-server) +add_subdirectory (http-server-small) add_subdirectory (server-framework) add_subdirectory (websocket-client) diff --git a/example/http-server-small/CMakeLists.txt b/example/http-server-small/CMakeLists.txt new file mode 100644 index 00000000..dfb1ade7 --- /dev/null +++ b/example/http-server-small/CMakeLists.txt @@ -0,0 +1,15 @@ +# Part of Beast + +GroupSources(include/beast beast) + +GroupSources(example/http-server-small "/") + +add_executable (http-server-small + ${BEAST_INCLUDES} + http_server_small.cpp +) + +target_link_libraries(http-server-small + Beast + ) + diff --git a/example/http-server-small/Jamfile b/example/http-server-small/Jamfile new file mode 100644 index 00000000..2acfe2cb --- /dev/null +++ b/example/http-server-small/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-small : + http_server_small.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http-server-small/http_server_small.cpp b/example/http-server-small/http_server_small.cpp new file mode 100644 index 00000000..d0cd0ba9 --- /dev/null +++ b/example/http-server-small/http_server_small.cpp @@ -0,0 +1,240 @@ +// +// 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) +// + +#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 = beast::http; // from + +namespace my_program_state +{ + std::size_t + request_count() + { + static std::size_t count = 0; + return ++count; + } + + std::time_t + now() + { + return std::time(0); + } +} + +class http_connection : public std::enable_shared_from_this +{ +public: + http_connection(tcp::socket socket) + : socket_(std::move(socket)) + { + } + + // Initiate the asynchronous operations associated with the connection. + void + start() + { + read_request(); + check_deadline(); + } + +private: + // The socket for the currently connected client. + tcp::socket socket_; + + // The buffer for performing reads. + beast::flat_buffer buffer_{8192}; + + // The request message. + http::request request_; + + // The response message. + http::response response_; + + // The timer for putting a deadline on connection processing. + boost::asio::basic_waitable_timer deadline_{ + socket_.get_io_service(), std::chrono::seconds(60)}; + + // Asynchronously receive a complete request message. + void + read_request() + { + auto self{shared_from_this()}; + + http::async_read( + socket_, + buffer_, + request_, + [self](beast::error_code ec) + { + if(!ec) + self->process_request(); + }); + } + + // Determine what needs to be done with the request message. + void + process_request() + { + response_.version = 11; + response_.set(http::field::connection, "close"); + + switch(request_.method()) + { + case http::verb::get: + response_.result(http::status::ok); + response_.set(http::field::server, "Beast"); + create_response(); + break; + + default: + // We return responses indicating an error if + // we do not recognize the request method. + response_.result(http::status::bad_request); + response_.set(http::field::content_type, "text/plain"); + beast::ostream(response_.body) + << "Invalid request-method '" + << request_.method_string().to_string() + << "'"; + break; + } + + write_response(); + } + + // Construct a response message based on the program state. + void + create_response() + { + if(request_.target() == "/count") + { + response_.set(http::field::content_type, "text/html"); + beast::ostream(response_.body) + << "\n" + << "Request count\n" + << "\n" + << "

Request count

\n" + << "

There have been " + << my_program_state::request_count() + << " requests so far.

\n" + << "\n" + << "\n"; + } + else if(request_.target() == "/time") + { + response_.set(http::field::content_type, "text/html"); + beast::ostream(response_.body) + << "\n" + << "Current time\n" + << "\n" + << "

Current time

\n" + << "

The current time is " + << my_program_state::now() + << " seconds since the epoch.

\n" + << "\n" + << "\n"; + } + else + { + response_.result(http::status::not_found); + response_.set(http::field::content_type, "text/plain"); + beast::ostream(response_.body) << "File not found\r\n"; + } + } + + // Asynchronously transmit the response message. + void + write_response() + { + auto self{shared_from_this()}; + + response_.set(http::field::content_length, response_.body.size()); + + http::async_write( + socket_, + response_, + [self](beast::error_code ec) + { + self->socket_.shutdown(tcp::socket::shutdown_send, ec); + self->deadline_.cancel(); + }); + } + + // Check whether we have spent enough time on this connection. + void + check_deadline() + { + auto self{shared_from_this()}; + + deadline_.async_wait( + [self](beast::error_code ec) + { + if(!ec) + { + // Close socket to cancel any outstanding operation. + self->socket_.close(ec); + } + }); + } +}; + +// "Loop" forever accepting new connections. +void +http_server(tcp::acceptor& acceptor, tcp::socket& socket) +{ + acceptor.async_accept(socket, + [&](beast::error_code ec) + { + if(!ec) + std::make_shared(std::move(socket))->start(); + http_server(acceptor, socket); + }); +} + +int +main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if(argc != 3) + { + std::cerr << "Usage: " << argv[0] << "
\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])); + + boost::asio::io_service ios{1}; + + tcp::acceptor acceptor{ios, {address, port}}; + tcp::socket socket{ios}; + http_server(acceptor, socket); + + ios.run(); + } + catch(std::exception const& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/example/http-server/fields_alloc.hpp b/example/http-server/fields_alloc.hpp index a5d62acf..7550ccb0 100644 --- a/example/http-server/fields_alloc.hpp +++ b/example/http-server/fields_alloc.hpp @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// 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)