Add http-server-small example

This commit is contained in:
Vinnie Falco
2017-06-23 18:23:55 -07:00
parent 3570895391
commit fc15f5e0b2
7 changed files with 283 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
Version 67:
* Fix doc example link
* Add http-server-small example
--------------------------------------------------------------------------------

View File

@@ -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,

View File

@@ -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)

View File

@@ -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
)

View File

@@ -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
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -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 <beast/core.hpp>
#include <beast/http.hpp>
#include <beast/version.hpp>
#include <boost/asio.hpp>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
namespace ip = boost::asio::ip; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace http = beast::http; // from <beast/http.hpp>
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<http_connection>
{
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<http::dynamic_body> request_;
// The response message.
http::response<http::dynamic_body> response_;
// The timer for putting a deadline on connection processing.
boost::asio::basic_waitable_timer<std::chrono::steady_clock> 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)
<< "<html>\n"
<< "<head><title>Request count</title></head>\n"
<< "<body>\n"
<< "<h1>Request count</h1>\n"
<< "<p>There have been "
<< my_program_state::request_count()
<< " requests so far.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else if(request_.target() == "/time")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body)
<< "<html>\n"
<< "<head><title>Current time</title></head>\n"
<< "<body>\n"
<< "<h1>Current time</h1>\n"
<< "<p>The current time is "
<< my_program_state::now()
<< " seconds since the epoch.</p>\n"
<< "</body>\n"
<< "</html>\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<http_connection>(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] << " <address> <port>\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<unsigned short>(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;
}
}

View File

@@ -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)