Refactor all examples:

fix #575, fix #604, fix #608, fix #634, fix #712

All examples are rewritten:

* Using Best Practices
* Mostly self-contained
* New examples to complete the feature matrix
* The server-framework example is removed
This commit is contained in:
Vinnie Falco
2017-08-07 20:53:11 -07:00
parent 20a8f7d75b
commit dd71b0f94d
187 changed files with 11759 additions and 7098 deletions
+4 -13
View File
@@ -7,17 +7,8 @@
# Official repository: https://github.com/boostorg/beast
#
add_subdirectory (echo-op)
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)
add_subdirectory (websocket-server-async)
add_subdirectory (advanced)
add_subdirectory (http)
add_subdirectory (websocket)
if (OPENSSL_FOUND)
add_subdirectory (http-client-ssl)
add_subdirectory (websocket-client-ssl)
endif()
add_subdirectory (echo-op)
+5 -12
View File
@@ -7,16 +7,9 @@
# Official repository: https://github.com/boostorg/beast
#
build-project echo-op ;
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 ;
build-project websocket-server-async ;
build-project advanced ;
build-project http ;
build-project websocket ;
# VFALCO How do I make this work on Windows and if OpenSSL is not available?
#build-project ssl-http-client ;
#build-project ssl-websocket-client ;
# legacy
build-project echo-op ;
+11
View File
@@ -0,0 +1,11 @@
#
# 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)
#
# Official repository: https://github.com/boostorg/beast
#
add_subdirectory (server)
add_subdirectory (server-flex)
+13
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)
#
# Official repository: https://github.com/boostorg/beast
#
build-project server ;
# VFALCO How do I make this work on Windows and if OpenSSL is not available?
#build-project server-flex ;
@@ -0,0 +1,26 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/advanced/server-flex "/")
add_executable (advanced-server-flex
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/detect_ssl.hpp
${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp
${PROJECT_SOURCE_DIR}/example/common/ssl_stream.hpp
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
Jamfile
advanced_server_flex.cpp
)
target_link_libraries (advanced-server-flex
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe advanced-server-flex :
advanced_server_flex.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
File diff suppressed because it is too large Load Diff
@@ -9,11 +9,11 @@
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http-server-threaded "/")
GroupSources(example/advanced/server "/")
add_executable (http-server-threaded
add_executable (advanced-server
${BOOST_BEAST_INCLUDES}
${COMMON_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
Jamfile
http_server_threaded.cpp
advanced_server.cpp
)
@@ -7,8 +7,8 @@
# Official repository: https://github.com/boostorg/beast
#
exe http-client :
http_client.cpp
exe advanced-server :
advanced_server.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
+732
View File
@@ -0,0 +1,732 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: Advanced server
//
//------------------------------------------------------------------------------
#include "example/common/write_msg.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
namespace websocket = boost::beast::websocket; // from <boost/beast/websocket.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Echoes back all received WebSocket messages
class websocket_session : public std::enable_shared_from_this<websocket_session>
{
websocket::stream<tcp::socket> ws_;
boost::asio::io_service::strand strand_;
boost::asio::steady_timer timer_;
boost::beast::multi_buffer buffer_;
public:
// Take ownership of the socket
explicit
websocket_session(tcp::socket socket)
: ws_(std::move(socket))
, strand_(ws_.get_io_service())
, timer_(ws_.get_io_service(),
(std::chrono::steady_clock::time_point::max)())
{
}
// Start the asynchronous operation
template<class Body, class Allocator>
void
run(http::request<Body, http::basic_fields<Allocator>> req)
{
// Run the timer. The timer is operated
// continuously, this simplifies the code.
on_timer({});
// Set the timer
timer_.expires_from_now(std::chrono::seconds(15));
// Accept the websocket handshake
ws_.async_accept(
req,
strand_.wrap(std::bind(
&websocket_session::on_accept,
shared_from_this(),
std::placeholders::_1)));
}
// Called when the timer expires.
void
on_timer(boost::system::error_code ec)
{
if(ec && ec != boost::asio::error::operation_aborted)
return fail(ec, "timer");
// Verify that the timer really expired since the deadline may have moved.
if(timer_.expires_at() <= std::chrono::steady_clock::now())
{
// Closing the socket cancels all outstanding operations. They
// will complete with boost::asio::error::operation_aborted
ws_.next_layer().shutdown(tcp::socket::shutdown_both, ec);
ws_.next_layer().close(ec);
return;
}
// Wait on the timer
timer_.async_wait(
strand_.wrap(std::bind(
&websocket_session::on_timer,
shared_from_this(),
std::placeholders::_1)));
}
void
on_accept(boost::system::error_code ec)
{
// Happens when the timer closes the socket
if(ec == boost::asio::error::operation_aborted)
return;
if(ec)
return fail(ec, "accept");
// Read a message
do_read();
}
void
do_read()
{
// Set the timer
timer_.expires_from_now(std::chrono::seconds(15));
// Read a message into our buffer
ws_.async_read(
buffer_,
strand_.wrap(std::bind(
&websocket_session::on_read,
shared_from_this(),
std::placeholders::_1)));
}
void
on_read(boost::system::error_code ec)
{
// Happens when the timer closes the socket
if(ec == boost::asio::error::operation_aborted)
return;
// This indicates that the websocket_session was closed
if(ec == websocket::error::closed)
return;
if(ec)
fail(ec, "read");
// Echo the message
ws_.text(ws_.got_text());
ws_.async_write(
buffer_.data(),
strand_.wrap(std::bind(
&websocket_session::on_write,
shared_from_this(),
std::placeholders::_1)));
}
void
on_write(boost::system::error_code ec)
{
// Happens when the timer closes the socket
if(ec == boost::asio::error::operation_aborted)
return;
if(ec)
return fail(ec, "write");
// Clear the buffer
buffer_.consume(buffer_.size());
// Do another read
do_read();
}
};
// Handles an HTTP server connection
class http_session : public std::enable_shared_from_this<http_session>
{
// This queue is used for HTTP pipelining.
class queue
{
enum
{
// Maximum number of responses we will queue
limit = 8
};
// The type-erased, saved work item
struct work
{
virtual ~work() = default;
virtual void operator()() = 0;
};
bool busy_ = false; // true if a write is in progress
http_session& self_;
std::vector<std::unique_ptr<work>> items_;
public:
explicit
queue(http_session& self)
: self_(self)
{
static_assert(limit > 0, "queue limit must be positive");
items_.reserve(limit);
}
// Returns `true` if we have reached the queue limit
bool
is_full() const
{
return items_.size() + (busy_ ? 1 : 0) >= limit;
}
// Called when a message finishes sending
// Returns `true` if the caller should initiate a read
bool
on_write()
{
BOOST_ASSERT(busy_);
auto const do_read = items_.size() + (busy_ ? 1 : 0) >= limit;
if(! items_.empty())
{
(*items_.back())();
items_.erase(items_.begin());
}
else
{
busy_ = false;
}
return do_read;
}
// Called by the HTTP handler to send a response.
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg)
{
// See if a write is in progress
if(! busy_)
{
// No write in progress so start one
busy_ = true;
// This function takes ownership of the message by moving
// it into a temporary buffer, otherwise we would have
// to manage the lifetime of the message and serializer.
return async_write_msg(
self_.socket_,
std::move(msg),
self_.strand_.wrap(std::bind(
&http_session::on_write,
self_.shared_from_this(),
std::placeholders::_1)));
}
// This holds a work item
struct work_impl : work
{
http_session& self_;
http::message<isRequest, Body, Fields> msg_;
work_impl(
http_session& self,
http::message<isRequest, Body, Fields>&& msg)
: self_(self)
, msg_(std::move(msg))
{
}
void
operator()()
{
async_write_msg(
self_.socket_,
std::move(msg_),
self_.strand_.wrap(std::bind(
&http_session::on_write,
self_.shared_from_this(),
std::placeholders::_1)));
}
};
// A write is in progress, so allocate storage to
// save this work item so we can invoke it later.
items_.emplace_back(new work_impl(self_, std::move(msg)));
}
};
tcp::socket socket_;
boost::asio::io_service::strand strand_;
boost::asio::steady_timer timer_;
boost::beast::flat_buffer buffer_;
std::string const& doc_root_;
http::request<http::string_body> req_;
queue queue_;
public:
// Take ownership of the socket
explicit
http_session(
tcp::socket socket,
std::string const& doc_root)
: socket_(std::move(socket))
, strand_(socket_.get_io_service())
, timer_(socket_.get_io_service(),
(std::chrono::steady_clock::time_point::max)())
, doc_root_(doc_root)
, queue_(*this)
{
}
// Start the asynchronous operation
void
run()
{
// Run the timer. The timer is operated
// continuously, this simplifies the code.
on_timer({});
do_read();
}
void
do_read()
{
// Set the timer
timer_.expires_from_now(std::chrono::seconds(15));
// Read a request
http::async_read(socket_, buffer_, req_,
strand_.wrap(std::bind(
&http_session::on_read,
shared_from_this(),
std::placeholders::_1)));
}
// Called when the timer expires.
void
on_timer(boost::system::error_code ec)
{
if(ec && ec != boost::asio::error::operation_aborted)
return fail(ec, "timer");
// Verify that the timer really expired since the deadline may have moved.
if(timer_.expires_at() <= std::chrono::steady_clock::now())
{
// Closing the socket cancels all outstanding operations. They
// will complete with boost::asio::error::operation_aborted
socket_.shutdown(tcp::socket::shutdown_both, ec);
socket_.close(ec);
return;
}
// Wait on the timer
timer_.async_wait(
strand_.wrap(std::bind(
&http_session::on_timer,
shared_from_this(),
std::placeholders::_1)));
}
void
on_read(boost::system::error_code ec)
{
// Happens when the timer closes the socket
if(ec == boost::asio::error::operation_aborted)
return;
// This means they closed the connection
if(ec == http::error::end_of_stream)
return do_close();
if(ec)
return fail(ec, "read");
// See if it is a WebSocket Upgrade
if(websocket::is_upgrade(req_))
{
// Create a WebSocket websocket_session by transferring the socket
std::make_shared<websocket_session>(
std::move(socket_))->run(std::move(req_));
return;
}
// Send the response
handle_request(doc_root_, std::move(req_), queue_);
// If we aren't at the queue limit, try to pipeline another request
if(! queue_.is_full())
do_read();
}
void
on_write(boost::system::error_code ec)
{
// Happens when the timer closes the socket
if(ec == boost::asio::error::operation_aborted)
return;
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
return do_close();
}
if(ec)
return fail(ec, "write");
// Inform the queue that a write completed
if(queue_.on_write())
{
// Read another request
do_read();
}
}
void
do_close()
{
// Send a TCP shutdown
boost::system::error_code ec;
socket_.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
};
//------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
boost::asio::io_service::strand strand_;
tcp::acceptor acceptor_;
tcp::socket socket_;
std::string const& doc_root_;
public:
listener(
boost::asio::io_service& ios,
tcp::endpoint endpoint,
std::string const& doc_root)
: strand_(ios)
, acceptor_(ios)
, socket_(ios)
, doc_root_(doc_root)
{
boost::system::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if(ec)
{
fail(ec, "open");
return;
}
// Bind to the server address
acceptor_.bind(endpoint, ec);
if(ec)
{
fail(ec, "bind");
return;
}
// Start listening for connections
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
{
fail(ec, "listen");
return;
}
}
// Start accepting incoming connections
void
run()
{
if(! acceptor_.is_open())
return;
do_accept();
}
void
do_accept()
{
acceptor_.async_accept(
socket_,
std::bind(
&listener::on_accept,
shared_from_this(),
std::placeholders::_1));
}
void
on_accept(boost::system::error_code ec)
{
if(ec)
{
fail(ec, "accept");
}
else
{
// Create the http_session and run it
std::make_shared<http_session>(
std::move(socket_),
doc_root_)->run();
}
// Accept another connection
do_accept();
}
};
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr <<
"Usage: advanced-server <address> <port> <doc_root> <threads>\n" <<
"Example:\n" <<
" advanced-server 0.0.0.0 8080 . 1\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string const doc_root = argv[3];
auto const threads = std::max<std::size_t>(1, std::atoi(argv[3]));
// The io_service is required for all I/O
boost::asio::io_service ios{threads};
// Create and launch a listening port
std::make_shared<listener>(
ios,
tcp::endpoint{address, port},
doc_root)->run();
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for(auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ios]
{
ios.run();
});
ios.run();
return EXIT_SUCCESS;
}
+33 -25
View File
@@ -21,7 +21,7 @@
//[example_core_detect_ssl_1
#include <boost/beast.hpp>
#include <boost/beast/core.hpp>
#include <boost/logic/tribool.hpp>
/** Return `true` if a buffer contains a TLS/SSL client handshake.
@@ -52,8 +52,6 @@ is_ssl_handshake(ConstBufferSequence const& buffers);
//]
using namespace boost::beast;
//[example_core_detect_ssl_2
template<
@@ -63,7 +61,7 @@ is_ssl_handshake(
ConstBufferSequence const& buffers)
{
// Make sure buffers meets the requirements
static_assert(is_const_buffer_sequence<ConstBufferSequence>::value,
static_assert(boost::beast::is_const_buffer_sequence<ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
// We need at least one byte to really do anything
@@ -130,12 +128,14 @@ boost::tribool
detect_ssl(
SyncReadStream& stream,
DynamicBuffer& buffer,
error_code& ec)
boost::beast::error_code& ec)
{
namespace beast = boost::beast;
// Make sure arguments meet the requirements
static_assert(is_sync_read_stream<SyncReadStream>::value,
static_assert(beast::is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
static_assert(beast::is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// Loop until an error occurs or we get a definitive answer
@@ -148,15 +148,17 @@ detect_ssl(
// If we got an answer, return it
if(! boost::indeterminate(result))
{
ec = {}; // indicate no error
// This is a fast way to indicate success
// without retrieving the default category.
ec.assign(0, ec.category());
return result;
}
// The algorithm should never need more than 4 bytes
BOOST_ASSERT(buffer.size() < 4);
// Create up to 4 bytes of space in the buffer's output area.
auto const mutable_buffer = buffer.prepare(4 - buffer.size());
// Prepare the buffer's output area.
auto const mutable_buffer = buffer.prepare(beast::read_size(buffer, 1536));
// Try to fill our buffer by reading from the stream
std::size_t const bytes_transferred = stream.read_some(mutable_buffer, ec);
@@ -223,9 +225,9 @@ template<
class AsyncReadStream,
class DynamicBuffer,
class CompletionToken>
async_return_type< /*< The [link beast.ref.boost__beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/
boost::beast::async_return_type< /*< The [link beast.ref.boost__beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/
CompletionToken,
void(error_code, boost::tribool)> /*< This is the signature for the completion handler >*/
void(boost::beast::error_code, boost::tribool)> /*< This is the signature for the completion handler >*/
async_detect_ssl(
AsyncReadStream& stream,
DynamicBuffer& buffer,
@@ -247,26 +249,28 @@ template<
class AsyncReadStream,
class DynamicBuffer,
class CompletionToken>
async_return_type<
boost::beast::async_return_type<
CompletionToken,
void(error_code, boost::tribool)>
void(boost::beast::error_code, boost::tribool)>
async_detect_ssl(
AsyncReadStream& stream,
DynamicBuffer& buffer,
CompletionToken&& token)
{
namespace beast = boost::beast;
// Make sure arguments meet the requirements
static_assert(is_async_read_stream<AsyncReadStream>::value,
static_assert(beast::is_async_read_stream<AsyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
static_assert(beast::is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// This helper manages some of the handler's lifetime and
// uses the result and handler specializations associated with
// the completion token to help customize the return value.
//
boost::beast::async_completion<
CompletionToken, void(boost::beast::error_code, boost::tribool)> init{token};
beast::async_completion<
CompletionToken, void(beast::error_code, boost::tribool)> init{token};
// Create the composed operation and launch it. This is a constructor
// call followed by invocation of operator(). We use handler_type
@@ -274,10 +278,10 @@ async_detect_ssl(
// allowing user defined specializations of the async result template
// to take effect.
//
detect_ssl_op<AsyncReadStream, DynamicBuffer, handler_type<
CompletionToken, void(error_code, boost::tribool)>>{
detect_ssl_op<AsyncReadStream, DynamicBuffer, beast::handler_type<
CompletionToken, void(beast::error_code, boost::tribool)>>{
stream, buffer, init.completion_handler}(
boost::beast::error_code{}, 0);
beast::error_code{}, 0);
// This hook lets the caller see a return value when appropriate.
// For example this might return std::future<error_code, boost::tribool> if
@@ -322,8 +326,10 @@ public:
// The constructor just keeps references the callers varaibles.
//
template<class DeducedHandler>
detect_ssl_op(AsyncReadStream& stream,
DynamicBuffer& buffer, DeducedHandler&& handler)
detect_ssl_op(
AsyncReadStream& stream,
DynamicBuffer& buffer,
DeducedHandler&& handler)
: stream_(stream)
, buffer_(buffer)
, handler_(std::forward<DeducedHandler>(handler))
@@ -402,6 +408,8 @@ void
detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
operator()(boost::beast::error_code ec, std::size_t bytes_transferred)
{
namespace beast = boost::beast;
// Execute the state machine
switch(step_)
{
@@ -422,7 +430,7 @@ operator()(boost::beast::error_code ec, std::size_t bytes_transferred)
// original handler.
step_ = 1;
return stream_.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
beast::bind_handler(std::move(*this), ec, 0));
}
// The algorithm should never need more than 4 bytes
@@ -432,7 +440,7 @@ operator()(boost::beast::error_code ec, std::size_t bytes_transferred)
do_read:
// We need more bytes, but no more than four total.
return stream_.async_read_some(buffer_.prepare(4 - buffer_.size()), std::move(*this));
return stream_.async_read_some(buffer_.prepare(beast::read_size(buffer_, 1536)), std::move(*this));
case 1:
// Call the handler
-58
View File
@@ -1,58 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_COMMON_HELPERS_HPP
#define BOOST_BEAST_EXAMPLE_COMMON_HELPERS_HPP
#include <boost/asio/io_service.hpp>
#include <boost/asio/signal_set.hpp>
#include <ostream>
#include <sstream>
/// Block until SIGINT or SIGTERM is received.
inline
void
sig_wait()
{
boost::asio::io_service ios{1};
boost::asio::signal_set signals(ios, SIGINT, SIGTERM);
signals.async_wait([&](boost::system::error_code const&, int){});
ios.run();
}
namespace detail {
inline
void
print_1(std::ostream&)
{
}
template<class T1, class... TN>
void
print_1(std::ostream& os, T1 const& t1, TN const&... tn)
{
os << t1;
print_1(os, tn...);
}
} // detail
// compose a string to std::cout or std::cerr atomically
//
template<class...Args>
void
print(std::ostream& os, Args const&... args)
{
std::stringstream ss;
detail::print_1(ss, args...);
os << ss.str() << std::endl;
}
#endif
-48
View File
@@ -1,48 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_COMMON_MIME_TYPES_HPP
#define BOOST_BEAST_EXAMPLE_COMMON_MIME_TYPES_HPP
#include <boost/beast/core/string.hpp>
#include <boost/filesystem/path.hpp>
// Return a reasonable mime type based on the extension of a file.
//
template<class = void>
boost::beast::string_view
mime_type(boost::filesystem::path const& path)
{
using boost::beast::iequals;
auto const ext = path.extension().string();
if(iequals(ext, ".txt")) return "text/plain";
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, ".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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
#endif
-41
View File
@@ -1,41 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_COMMON_RFC7231_HPP
#define BOOST_BEAST_EXAMPLE_COMMON_RFC7231_HPP
#include <boost/beast/core/string.hpp>
#include <boost/beast/http/message.hpp>
namespace rfc7231 {
// This aggregates a collection of algorithms
// corresponding to specifications in rfc7231:
//
// https://tools.ietf.org/html/rfc7231
//
/** Returns `true` if the message specifies Expect: 100-continue
@param req The request to check
@see https://tools.ietf.org/html/rfc7231#section-5.1.1
*/
template<class Body, class Allocator>
bool
is_expect_100_continue(boost::beast::http::request<
Body, boost::beast::http::basic_fields<Allocator>> const& req)
{
return boost::beast::iequals(
req[boost::beast::http::field::expect], "100-continue");
}
} // rfc7231
#endif
+11
View File
@@ -110,6 +110,7 @@ load_root_certificates(ssl::context& ctx, boost::system::error_code& ec)
// gratuituous template argument; thus it appears
// like a "normal" function.
//
inline
void
load_root_certificates(ssl::context& ctx, boost::system::error_code& ec)
@@ -117,4 +118,14 @@ load_root_certificates(ssl::context& ctx, boost::system::error_code& ec)
detail::load_root_certificates(ctx, ec);
}
inline
void
load_root_certificates(ssl::context& ctx)
{
boost::system::error_code ec;
detail::load_root_certificates(ctx, ec);
if(ec)
throw boost::system::system_error{ec};
}
#endif
@@ -7,48 +7,26 @@
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP
#ifndef BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP
#define BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP
#include <boost/asio/buffer.hpp>
#include <boost/asio/ssl/context.hpp>
#include <cstddef>
#include <memory>
namespace framework {
/* Load a signed certificate into the ssl context, and configure
the context for use with a server.
// This sets up the self-signed certificate that the server
// uses for its encrypted connections
class ssl_certificate
{
// The template argument is gratuitous, to
// make the definition header-only without
// also making it inline.
//
template<class = void>
void
construct();
boost::asio::ssl::context ctx_;
public:
ssl_certificate()
: ctx_(boost::asio::ssl::context::sslv23)
{
construct();
}
boost::asio::ssl::context&
get()
{
return ctx_;
}
};
template<class>
For this to work with the browser or operating system, it is
necessary to import the "Beast Test CA" certificate into
the local certificate store, browser, or operating system
depending on your environment Please see the documentation
accompanying the Beast certificate for more details.
*/
inline
void
ssl_certificate::construct()
load_server_certificate(boost::asio::ssl::context& ctx)
{
/*
The certificate was generated from CMD.EXE on Windows 10 using:
@@ -120,29 +98,27 @@ ssl_certificate::construct()
"QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n"
"-----END DH PARAMETERS-----\n";
ctx_.set_password_callback(
ctx.set_password_callback(
[](std::size_t,
boost::asio::ssl::context_base::password_purpose)
{
return "test";
});
ctx_.set_options(
ctx.set_options(
boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use);
ctx_.use_certificate_chain(
ctx.use_certificate_chain(
boost::asio::buffer(cert.data(), cert.size()));
ctx_.use_private_key(
ctx.use_private_key(
boost::asio::buffer(key.data(), key.size()),
boost::asio::ssl::context::file_format::pem);
ctx_.use_tmp_dh(
ctx.use_tmp_dh(
boost::asio::buffer(dh.data(), dh.size()));
}
} // framework
#endif
+5 -3
View File
@@ -61,11 +61,13 @@ public:
/// The type of the lowest layer.
using lowest_layer_type = typename stream_type::lowest_layer_type;
ssl_stream(boost::asio::ip::tcp::socket&& sock, boost::asio::ssl::context& ctx)
: p_(new stream_type{sock.get_io_service(), ctx})
ssl_stream(
boost::asio::ip::tcp::socket socket,
boost::asio::ssl::context& ctx)
: p_(new stream_type{socket.get_io_service(), ctx})
, ctx_(&ctx)
{
p_->next_layer() = std::move(sock);
p_->next_layer() = std::move(socket);
}
ssl_stream(ssl_stream&& other)
-53
View File
@@ -1,53 +0,0 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
import os ;
if [ os.name ] = SOLARIS
{
lib socket ;
lib nsl ;
}
else if [ os.name ] = NT
{
lib ws2_32 ;
lib mswsock ;
}
else if [ os.name ] = HPUX
{
lib ipv6 ;
}
else if [ os.name ] = HAIKU
{
lib network ;
}
if [ os.name ] = NT
{
lib ssl : : <name>ssleay32 ;
lib crypto : : <name>libeay32 ;
}
else
{
lib ssl ;
lib crypto ;
}
project
: requirements
<library>ssl
<library>crypto
;
exe http-client-ssl :
http_client_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
-106
View File
@@ -1,106 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#include "../common/root_certificates.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
int main()
{
// A helper for reporting errors
auto const fail =
[](std::string what, boost::beast::error_code ec)
{
std::cerr << what << ": " << ec.message() << std::endl;
std::cerr.flush();
return EXIT_FAILURE;
};
boost::system::error_code ec;
// Normal boost::asio setup
boost::asio::io_service ios;
tcp::resolver r{ios};
tcp::socket sock{ios};
// Look up the domain name
std::string const host = "www.example.com";
auto const lookup = r.resolve({host, "https"}, ec);
if(ec)
return fail("resolve", ec);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(sock, lookup, ec);
if(ec)
return fail("connect", ec);
// Create the required ssl context
ssl::context ctx{ssl::context::sslv23_client};
// This holds the root certificate used for verification
load_root_certificates(ctx, ec);
if(ec)
return fail("certificate", ec);
// Wrap the now-connected socket in an SSL stream
ssl::stream<tcp::socket&> stream{sock, ctx};
stream.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert);
// Perform SSL handshaking
stream.handshake(ssl::stream_base::client, ec);
if(ec)
return fail("handshake", ec);
// Set up an HTTP GET request message
http::request<http::string_body> req;
req.method(http::verb::get);
req.target("/");
req.version = 11;
req.set(http::field::host, host + ":" +
std::to_string(sock.remote_endpoint().port()));
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
req.prepare_payload();
// Write the HTTP request to the remote host
http::write(stream, req, ec);
if(ec)
return fail("write", ec);
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer b;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Read the response
http::read(stream, b, res, ec);
if(ec)
return fail("read", ec);
// Write the message to standard out
std::cout << res << std::endl;
// Shut down SSL on the stream
stream.shutdown(ec);
if(ec && ec != boost::asio::error::eof)
fail("ssl_shutdown ", ec);
// If we get here then the connection is closed gracefully
return EXIT_SUCCESS;
}
-89
View File
@@ -1,89 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//[example_http_client
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
int main()
{
// A helper for reporting errors
auto const fail =
[](std::string what, boost::beast::error_code ec)
{
std::cerr << what << ": " << ec.message() << std::endl;
return EXIT_FAILURE;
};
boost::beast::error_code ec;
// Set up an asio socket
boost::asio::io_service ios;
tcp::resolver r{ios};
tcp::socket sock{ios};
// Look up the domain name
std::string const host = "www.example.com";
auto const lookup = r.resolve({host, "http"}, ec);
if(ec)
return fail("resolve", ec);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(sock, lookup, ec);
if(ec)
return fail("connect", ec);
// Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, "/", 11};
req.set(http::field::host, host + ":" +
std::to_string(sock.remote_endpoint().port()));
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
req.prepare_payload();
// Write the HTTP request to the remote host
http::write(sock, req, ec);
if(ec)
return fail("write", ec);
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer b;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Read the response
http::read(sock, b, res, ec);
if(ec)
return fail("read", ec);
// Write the message to standard out
std::cout << res << std::endl;
// Gracefully close the socket
sock.shutdown(tcp::socket::shutdown_both, ec);
// not_connected happens sometimes
// so don't bother reporting it.
//
if(ec && ec != boost::beast::errc::not_connected)
return fail("shutdown", ec);
// If we get here then the connection is closed gracefully
return EXIT_SUCCESS;
}
//]
-139
View File
@@ -1,139 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#include "urls_large_data.hpp"
#include <boost/beast/core/multi_buffer.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <cstdlib>
#include <iostream>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
template<class String>
void
err(boost::beast::error_code const& ec, String const& what)
{
std::cerr << what << ": " << ec.message() << std::endl;
}
/* This simple program just visits a list with a few
thousand domain names and tries to retrieve and print
the home page of each site.
*/
int
main(int, char const*[])
{
// A helper for reporting errors
auto const fail =
[](std::string what, boost::beast::error_code ec)
{
std::cerr << what << ": " << ec.message() << std::endl;
std::cerr.flush();
return EXIT_FAILURE;
};
// Obligatory Asio variable
boost::asio::io_service ios;
// Loop over all the URLs
for(auto const& host : urls_large_data())
{
boost::beast::error_code ec;
// Look up the domain name
tcp::resolver r(ios);
auto lookup = r.resolve({host, "http"}, ec);
if(ec)
{
fail("resolve", ec);
continue;
}
// Now create a socket and connect
tcp::socket sock(ios);
boost::asio::connect(sock, lookup, ec);
if(ec)
{
fail("connect", ec);
continue;
}
// Grab the remote endpoint
auto ep = sock.remote_endpoint(ec);
if(ec)
{
fail("remote_endpoint", ec);
continue;
}
// Set up an HTTP GET request
http::request<http::string_body> req{http::verb::get, "/", 11};
req.set(http::field::host, host + std::string(":") + std::to_string(ep.port()));
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Set the Connection: close field, this way the server will close
// the connection. This consumes less resources (no TIME_WAIT) because
// of the graceful close. It also makes things go a little faster.
//
req.set(http::field::connection, "close");
// Send the GET request
http::write(sock, req, ec);
if(ec == http::error::end_of_stream)
{
// This special error received on a write indicates that the
// semantics of the sent message are such that the connection
// should be closed after the response is done. We do a TCP/IP
// "half-close" here to shut down our end.
//
sock.shutdown(tcp::socket::shutdown_send, ec);
if(ec && ec != boost::beast::errc::not_connected)
return fail("shutdown", ec);
}
if(ec)
{
fail("write", ec);
continue;
}
// This buffer is needed for reading
boost::beast::multi_buffer b;
// The response will go into this object
http::response<http::string_body> res;
// Read the response
http::read(sock, b, res, ec);
if(ec == http::error::end_of_stream)
{
// This special error means that the other end closed the socket,
// which is what we want since we asked for Connection: close.
// However, we are going through a rather large number of servers
// and sometimes they misbehave.
ec = {};
}
else if(ec)
{
fail("read", ec);
continue;
}
// Now we do the other half of the close,
// which is to shut down the receiver.
sock.shutdown(tcp::socket::shutdown_receive, ec);
if(ec && ec != boost::beast::errc::not_connected)
return fail("shutdown", ec);
std::cout << res << std::endl;
}
}
@@ -1,229 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#include "../common/mime_types.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <chrono>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
//------------------------------------------------------------------------------
//
// Example: HTTP server, synchronous, one thread per connection
//
//------------------------------------------------------------------------------
namespace ip = boost::asio::ip; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
class connection
: public std::enable_shared_from_this<connection>
{
tcp::socket sock_;
boost::beast::string_view root_;
public:
explicit
connection(tcp::socket&& sock, boost::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<http::span_body<char const>>
client_error(http::status result, boost::beast::string_view text)
{
http::response<http::span_body<char const>> res{result, 11};
res.set(http::field::server, BOOST_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
//
http::response<http::string_body>
not_found() const
{
http::response<http::string_body> res{http::status::not_found, 11};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(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
//
http::response<http::string_body>
server_error(boost::beast::error_code const& ec) const
{
http::response<http::string_body> res{http::status::internal_server_error, 11};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(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
//
http::response<boost::beast::http::file_body>
get(boost::filesystem::path const& full_path,
boost::beast::error_code& ec) const
{
http::response<http::file_body> res;
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, mime_type(full_path));
res.set(http::field::connection, "close");
res.body.open(full_path.string<std::string>().c_str(), boost::beast::file_mode::scan, ec);
if(ec)
return res;
res.set(http::field::content_length, res.body.size());
return res;
}
// Handle a request
template<class Body>
void
do_request(http::request<Body> const& req, boost::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());
boost::beast::error_code file_ec;
auto res = get(full_path, file_ec);
if(file_ec == boost::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::serializer<false, decltype(res)::body_type> sr{res};
http::write(sock_, sr, ec);
}
}
void
do_run()
{
try
{
boost::beast::error_code ec;
boost::beast::flat_buffer buffer;
for(;;)
{
http::request_parser<http::string_body> 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 boost::beast::system_error{ec};
do_request(parser.get(), ec);
if(ec)
{
if(ec != http::error::end_of_stream)
throw boost::beast::system_error{ec};
break;
}
}
sock_.shutdown(tcp::socket::shutdown_both, ec);
if(ec && ec != boost::asio::error::not_connected)
throw boost::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 <address> <port> <doc_root>\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]));
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<connection>(std::move(sock), doc_root)->run();
}
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
+11
View File
@@ -0,0 +1,11 @@
#
# 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)
#
# Official repository: https://github.com/boostorg/beast
#
add_subdirectory (client)
add_subdirectory (server)
+11
View File
@@ -0,0 +1,11 @@
#
# 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)
#
# Official repository: https://github.com/boostorg/beast
#
build-project client ;
build-project server ;
+19
View File
@@ -0,0 +1,19 @@
#
# 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)
#
# Official repository: https://github.com/boostorg/beast
#
add_subdirectory (async)
add_subdirectory (coro)
add_subdirectory (crawl)
add_subdirectory (sync)
if (OPENSSL_FOUND)
add_subdirectory (async-ssl)
add_subdirectory (coro-ssl)
add_subdirectory (sync-ssl)
endif()
+18
View File
@@ -0,0 +1,18 @@
#
# 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)
#
# Official repository: https://github.com/boostorg/beast
#
build-project async ;
build-project coro ;
build-project crawl ;
build-project sync ;
# VFALCO How do I make this work on Windows and if OpenSSL is not available?
#build-project async-ssl ;
#build-project coro-ssl ;
#build-project sync-ssl ;
@@ -0,0 +1,23 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/client/async-ssl "/")
add_executable (http-client-async-ssl
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp
Jamfile
http_client_async_ssl.cpp
)
target_link_libraries (http-client-async-ssl
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe http-client-async-ssl :
http_client_async_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,212 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP SSL client, asynchronous
//
//------------------------------------------------------------------------------
#include "example/common/root_certificates.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Performs an HTTP GET and prints the response
class session : public std::enable_shared_from_this<session>
{
tcp::resolver resolver_;
ssl::stream<tcp::socket> stream_;
boost::beast::flat_buffer buffer_; // (Must persist between reads)
http::request<http::empty_body> req_;
http::response<http::string_body> res_;
public:
// Resolver and stream require an io_service
explicit
session(boost::asio::io_service& ios, ssl::context& ctx)
: resolver_(ios)
, stream_(ios, ctx)
{
}
// Start the asynchronous operation
void
run(
char const* host,
char const* port,
char const* target)
{
// Set up an HTTP GET request message
req_.version = 11;
req_.method(http::verb::get);
req_.target(target);
req_.set(http::field::host, host);
req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Look up the domain name
resolver_.async_resolve({host, port},
std::bind(
&session::on_resolve,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void
on_resolve(
boost::system::error_code ec,
tcp::resolver::iterator result)
{
if(ec)
return fail(ec, "resolve");
// Make the connection on the IP address we get from a lookup
boost::asio::async_connect(
stream_.next_layer(),
result,
std::bind(
&session::on_connect,
shared_from_this(),
std::placeholders::_1));
}
void
on_connect(boost::system::error_code ec)
{
if(ec)
return fail(ec, "connect");
// Perform the SSL handshake
stream_.async_handshake(
ssl::stream_base::client,
std::bind(
&session::on_handshake,
shared_from_this(),
std::placeholders::_1));
}
void
on_handshake(boost::system::error_code ec)
{
if(ec)
return fail(ec, "handshake");
// Send the HTTP request to the remote host
http::async_write(stream_, req_,
std::bind(
&session::on_write,
shared_from_this(),
std::placeholders::_1));
}
void
on_write(boost::system::error_code ec)
{
if(ec)
return fail(ec, "write");
// Receive the HTTP response
http::async_read(stream_, buffer_, res_,
std::bind(
&session::on_read,
shared_from_this(),
std::placeholders::_1));
}
void
on_read(boost::system::error_code ec)
{
if(ec)
return fail(ec, "read");
// Write the message to standard out
std::cout << res_ << std::endl;
// Gracefully close the stream
stream_.async_shutdown(
std::bind(
&session::on_shutdown,
shared_from_this(),
std::placeholders::_1));
}
void
on_shutdown(boost::system::error_code ec)
{
if(ec == boost::asio::error::eof)
{
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec.assign(0, ec.category());
}
if(ec)
return fail(ec, "shutdown");
// If we get here then the connection is closed gracefully
}
};
//------------------------------------------------------------------------------
int main(int argc, char** argv)
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: http-client-async-ssl <host> <port> <target>\n" <<
"Example:\n" <<
" http-client-async-ssl www.example.com 443 /\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios;
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23_client};
// This holds the root certificate used for verification
load_root_certificates(ctx);
// Launch the asynchronous operation
std::make_shared<session>(ios, ctx)->run(host, port, target);
// Run the I/O service. The call will return when
// the get operation is complete.
ios.run();
return EXIT_SUCCESS;
}
+17
View File
@@ -0,0 +1,17 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/http/client/async "/")
add_executable (http-client-async
${BOOST_BEAST_INCLUDES}
Jamfile
http_client_async.cpp
)
@@ -7,8 +7,8 @@
# Official repository: https://github.com/boostorg/beast
#
exe http-server-threaded :
http_server_threaded.cpp
exe http-client-async :
http_client_async.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
@@ -0,0 +1,172 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP client, asynchronous
//
//------------------------------------------------------------------------------
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Performs an HTTP GET and prints the response
class session : public std::enable_shared_from_this<session>
{
tcp::resolver resolver_;
tcp::socket socket_;
boost::beast::flat_buffer buffer_; // (Must persist between reads)
http::request<http::empty_body> req_;
http::response<http::string_body> res_;
public:
// Resolver and socket require an io_service
explicit
session(boost::asio::io_service& ios)
: resolver_(ios)
, socket_(ios)
{
}
// Start the asynchronous operation
void
run(
char const* host,
char const* port,
char const* target)
{
// Set up an HTTP GET request message
req_.version = 11;
req_.method(http::verb::get);
req_.target(target);
req_.set(http::field::host, host);
req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Look up the domain name
resolver_.async_resolve({host, port},
std::bind(
&session::on_resolve,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void
on_resolve(
boost::system::error_code ec,
tcp::resolver::iterator result)
{
if(ec)
return fail(ec, "resolve");
// Make the connection on the IP address we get from a lookup
boost::asio::async_connect(socket_, result,
std::bind(
&session::on_connect,
shared_from_this(),
std::placeholders::_1));
}
void
on_connect(boost::system::error_code ec)
{
if(ec)
return fail(ec, "connect");
// Send the HTTP request to the remote host
http::async_write(socket_, req_,
std::bind(
&session::on_write,
shared_from_this(),
std::placeholders::_1));
}
void
on_write(boost::system::error_code ec)
{
if(ec)
return fail(ec, "write");
// Receive the HTTP response
http::async_read(socket_, buffer_, res_,
std::bind(
&session::on_read,
shared_from_this(),
std::placeholders::_1));
}
void
on_read(boost::system::error_code ec)
{
if(ec)
return fail(ec, "read");
// Write the message to standard out
std::cout << res_ << std::endl;
// Gracefully close the socket
socket_.shutdown(tcp::socket::shutdown_both, ec);
// not_connected happens sometimes so don't bother reporting it.
if(ec && ec != boost::system::errc::not_connected)
return fail(ec, "shutdown");
// If we get here then the connection is closed gracefully
}
};
//------------------------------------------------------------------------------
int main(int argc, char** argv)
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: http-client-async <host> <port> <target>\n" <<
"Example:\n" <<
" http-client-async www.example.com 80 /\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios;
// Launch the asynchronous operation
std::make_shared<session>(ios)->run(host, port, target);
// Run the I/O service. The call will return when
// the get operation is complete.
ios.run();
return EXIT_SUCCESS;
}
@@ -0,0 +1,23 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/client/coro-ssl "/")
add_executable (http-client-coro-ssl
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp
Jamfile
http_client_coro_ssl.cpp
)
target_link_libraries (http-client-coro-ssl
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe http-client-coro-ssl :
http_client_coro_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,153 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP SSL client, coroutine
//
//------------------------------------------------------------------------------
#include "example/common/root_certificates.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Performs an HTTP GET and prints the response
void
do_session(
std::string const& host,
std::string const& port,
std::string const& target,
boost::asio::io_service& ios,
ssl::context& ctx,
boost::asio::yield_context yield)
{
boost::system::error_code ec;
// These objects perform our I/O
tcp::resolver resolver{ios};
ssl::stream<tcp::socket> stream{ios, ctx};
// Look up the domain name
auto const lookup = resolver.async_resolve({host, port}, yield[ec]);
if(ec)
return fail(ec, "resolve");
// Make the connection on the IP address we get from a lookup
boost::asio::async_connect(stream.next_layer(), lookup, yield[ec]);
if(ec)
return fail(ec, "connect");
// Perform the SSL handshake
stream.async_handshake(ssl::stream_base::client, yield[ec]);
if(ec)
return fail(ec, "handshake");
// Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, target, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::async_write(stream, req, yield[ec]);
if(ec)
return fail(ec, "write");
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer b;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Receive the HTTP response
http::async_read(stream, b, res, yield[ec]);
if(ec)
return fail(ec, "read");
// Write the message to standard out
std::cout << res << std::endl;
// Gracefully close the stream
stream.async_shutdown(yield[ec]);
if(ec == boost::asio::error::eof)
{
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec.assign(0, ec.category());
}
if(ec)
return fail(ec, "shutdown");
// If we get here then the connection is closed gracefully
}
//------------------------------------------------------------------------------
int main(int argc, char** argv)
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: http-client-coro-ssl <host> <port> <target>\n" <<
"Example:\n" <<
" http-client-coro-ssl www.example.com 443 /\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios;
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23_client};
// This holds the root certificate used for verification
load_root_certificates(ctx);
// Launch the asynchronous operation
boost::asio::spawn(ios, std::bind(
&do_session,
std::string(host),
std::string(port),
std::string(target),
std::ref(ios),
std::ref(ctx),
std::placeholders::_1));
// Run the I/O service. The call will return when
// the get operation is complete.
ios.run();
return EXIT_SUCCESS;
}
@@ -8,10 +8,10 @@
#
GroupSources(include/boost/beast beast)
GroupSources(example/http-client "/")
GroupSources(example/http/client/coro "/")
add_executable (http-client
add_executable (http-client-coro
${BOOST_BEAST_INCLUDES}
Jamfile
http_client.cpp
http_client_coro.cpp
)
@@ -7,8 +7,8 @@
# Official repository: https://github.com/boostorg/beast
#
exe websocket-client :
websocket_client.cpp
exe http-client-coro :
http_client_coro.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
@@ -0,0 +1,134 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP client, coroutine
//
//------------------------------------------------------------------------------
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Performs an HTTP GET and prints the response
void
do_session(
std::string const& host,
std::string const& port,
std::string const& target,
boost::asio::io_service& ios,
boost::asio::yield_context yield)
{
boost::system::error_code ec;
// These objects perform our I/O
tcp::resolver resolver{ios};
tcp::socket socket{ios};
// Look up the domain name
auto const lookup = resolver.async_resolve({host, port}, yield[ec]);
if(ec)
return fail(ec, "resolve");
// Make the connection on the IP address we get from a lookup
boost::asio::async_connect(socket, lookup, yield[ec]);
if(ec)
return fail(ec, "connect");
// Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, target, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::async_write(socket, req, yield[ec]);
if(ec)
return fail(ec, "write");
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer b;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Receive the HTTP response
http::async_read(socket, b, res, yield[ec]);
if(ec)
return fail(ec, "read");
// Write the message to standard out
std::cout << res << std::endl;
// Gracefully close the socket
socket.shutdown(tcp::socket::shutdown_both, ec);
// not_connected happens sometimes
// so don't bother reporting it.
//
if(ec && ec != boost::system::errc::not_connected)
return fail(ec, "shutdown");
// If we get here then the connection is closed gracefully
}
//------------------------------------------------------------------------------
int main(int argc, char** argv)
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: http-client-coro <host> <port> <target>\n" <<
"Example:\n" <<
" http-client-coro www.example.com 80 /\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios;
// Launch the asynchronous operation
boost::asio::spawn(ios, std::bind(
&do_session,
std::string(host),
std::string(port),
std::string(target),
std::ref(ios),
std::placeholders::_1));
// Run the I/O service. The call will return when
// the get operation is complete.
ios.run();
return EXIT_SUCCESS;
}
@@ -8,7 +8,7 @@
#
GroupSources(include/boost/beast beast)
GroupSources(example/http-crawl "/")
GroupSources(example/http/client/crawl "/")
add_executable (http-crawl
${BOOST_BEAST_INCLUDES}
+450
View File
@@ -0,0 +1,450 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP crawl (asynchronous)
//
//------------------------------------------------------------------------------
#include "urls_large_data.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/optional.hpp>
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <functional>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include <map>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
namespace chrono = std::chrono; // from <chrono>
//------------------------------------------------------------------------------
// This structure aggregates statistics on all the sites
class crawl_report
{
boost::asio::io_service& ios_;
boost::asio::io_service::strand strand_;
std::atomic<std::size_t> index_;
std::vector<char const*> const& hosts_;
std::size_t count_ = 0;
public:
crawl_report(boost::asio::io_service& ios)
: ios_(ios)
, strand_(ios_)
, index_(0)
, hosts_(urls_large_data())
{
}
// Run an aggregation function on the strand.
// This allows synchronization without a mutex.
template<class F>
void
aggregate(F const& f)
{
ios_.post(strand_.wrap(
[&, f]
{
f(*this);
if(count_ % 100 == 0)
{
std::cerr <<
"Progress: " << count_ << " of " << hosts_.size() << "\n";
//std::cerr << *this;
}
++count_;
}));
}
// Returns the next host to check
char const*
get_host()
{
auto const n = index_++;
if(n >= hosts_.size())
return nullptr;
return hosts_[n];
}
// Counts the number of timer failures
std::size_t timer_failures = 0;
// Counts the number of name resolution failures
std::size_t resolve_failures = 0;
// Counts the number of connection failures
std::size_t connect_failures = 0;
// Counts the number of write failures
std::size_t write_failures = 0;
// Counts the number of read failures
std::size_t read_failures = 0;
// Counts the number of success reads
std::size_t success = 0;
// Counts the number received of each status code
std::map<unsigned, std::size_t> status_codes;
};
std::ostream&
operator<<(std::ostream& os, crawl_report const& report)
{
// Print the report
os <<
"Crawl report\n" <<
" Failure counts\n" <<
" Timer : " << report.timer_failures << "\n" <<
" Resolve : " << report.resolve_failures << "\n" <<
" Connect : " << report.connect_failures << "\n" <<
" Write : " << report.write_failures << "\n" <<
" Read : " << report.read_failures << "\n" <<
" Success : " << report.success << "\n" <<
" Status codes\n"
;
for(auto const& result : report.status_codes)
os <<
" " << std::setw(3) << result.first << ": " << result.second <<
" (" << http::obsolete_reason(static_cast<http::status>(result.first)) << ")\n";
os.flush();
return os;
}
//------------------------------------------------------------------------------
// Performs HTTP GET requests and aggregates the results into a report
class worker : public std::enable_shared_from_this<worker>
{
enum
{
// Use a small timeout to keep things lively
timeout = 5
};
crawl_report& report_;
tcp::resolver resolver_;
tcp::socket socket_;
boost::asio::steady_timer timer_;
boost::asio::io_service::strand strand_;
boost::beast::flat_buffer buffer_; // (Must persist between reads)
http::request<http::empty_body> req_;
http::response<http::string_body> res_;
public:
worker(worker&&) = default;
// Resolver and socket require an io_service
worker(
crawl_report& report,
boost::asio::io_service& ios)
: report_(report)
, resolver_(ios)
, socket_(ios)
, timer_(ios,
(chrono::steady_clock::time_point::max)())
, strand_(ios)
{
// Set up the common fields of the request
req_.version = 11;
req_.method(http::verb::get);
req_.target("/");
req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
}
// Start the asynchronous operation
void
run()
{
// Run the timer. The timer is operated
// continuously, this simplifies the code.
on_timer({});
do_get_host();
}
void
on_timer(boost::system::error_code ec)
{
if(ec && ec != boost::asio::error::operation_aborted)
{
// Should never happen
report_.aggregate(
[](crawl_report& rep)
{
++rep.timer_failures;
});
return;
}
// Verify that the timer really expired since the deadline may have moved.
if(timer_.expires_at() <= chrono::steady_clock::now())
{
socket_.shutdown(tcp::socket::shutdown_both, ec);
socket_.close(ec);
return;
}
// Wait on the timer
timer_.async_wait(
strand_.wrap(std::bind(
&worker::on_timer,
shared_from_this(),
std::placeholders::_1)));
}
void
do_get_host()
{
// Grab another host
auto const host = report_.get_host();
// nullptr means no more work
if(! host)
{
boost::system::error_code ec;
timer_.cancel(ec);
return;
}
// The Host HTTP field is required
req_.set(http::field::host, host);
// Set the timer
timer_.expires_from_now(chrono::seconds(timeout));
// Set up an HTTP GET request message
// Look up the domain name
resolver_.async_resolve(
tcp::resolver::query{host, "http"},
strand_.wrap(std::bind(
&worker::on_resolve,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2)));
}
void
on_resolve(
boost::system::error_code ec,
tcp::resolver::iterator result)
{
if(ec)
{
report_.aggregate(
[](crawl_report& rep)
{
++rep.resolve_failures;
});
return do_get_host();
}
// Set the timer
timer_.expires_from_now(chrono::seconds(timeout));
// Make the connection on the IP address we get from a lookup
boost::asio::async_connect(
socket_,
result,
strand_.wrap(std::bind(
&worker::on_connect,
shared_from_this(),
std::placeholders::_1)));
}
void
on_connect(boost::system::error_code ec)
{
if(ec)
{
report_.aggregate(
[](crawl_report& rep)
{
++rep.connect_failures;
});
return do_get_host();
}
// Set the timer
timer_.expires_from_now(chrono::seconds(timeout));
// Send the HTTP request to the remote host
http::async_write(
socket_,
req_,
strand_.wrap(std::bind(
&worker::on_write,
shared_from_this(),
std::placeholders::_1)));
}
void
on_write(boost::system::error_code ec)
{
if(ec)
{
report_.aggregate(
[](crawl_report& rep)
{
++rep.write_failures;
});
return do_get_host();
}
// Set the timer
timer_.expires_from_now(chrono::seconds(timeout));
// Receive the HTTP response
http::async_read(
socket_,
buffer_,
res_,
strand_.wrap(std::bind(
&worker::on_read,
shared_from_this(),
std::placeholders::_1)));
}
void
on_read(boost::system::error_code ec)
{
if(ec)
{
report_.aggregate(
[](crawl_report& rep)
{
++rep.read_failures;
});
return do_get_host();
}
auto const code = res_.result_int();
report_.aggregate(
[code](crawl_report& rep)
{
++rep.success;
++rep.status_codes[code];
});
// Gracefully close the socket
socket_.shutdown(tcp::socket::shutdown_both, ec);
socket_.close(ec);
// If we get here then the connection is closed gracefully
do_get_host();
}
};
class timer
{
using clock_type = chrono::system_clock;
clock_type::time_point when_;
public:
using duration = clock_type::duration;
timer()
: when_(clock_type::now())
{
}
duration
elapsed() const
{
return clock_type::now() - when_;
}
};
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 2)
{
std::cerr <<
"Usage: http-crawl <threads>\n" <<
"Example:\n" <<
" http-crawl 100 1\n";
return EXIT_FAILURE;
}
auto const threads = std::max<std::size_t>(1, std::atoi(argv[1]));
// The io_service is required for all I/O
boost::asio::io_service ios{1};
// The work keeps io_service::run from returning
boost::optional<boost::asio::io_service::work> work{ios};
// The report holds the aggregated statistics
crawl_report report{ios};
timer t;
// Create and launch the worker threads.
std::vector<std::thread> workers;
workers.reserve(threads + 1);
for(std::size_t i = 0; i < threads; ++i)
workers.emplace_back(
[&report]
{
// We use a separate io_service for each worker because
// the asio resolver simulates asynchronous operation using
// a dedicated worker thread per io_service, and we want to
// do a lot of name resolutions in parallel.
boost::asio::io_service ios{1};
std::make_shared<worker>(report, ios)->run();
ios.run();
});
// Add another thread to run the main io_service which
// is used to aggregate the statistics
workers.emplace_back(
[&ios]
{
ios.run();
});
// Now block until all threads exit
for(std::size_t i = 0; i < workers.size(); ++i)
{
auto& thread = workers[i];
// If this is the last thread, destroy the
// work object so that it can return from run.
//if(&thread == &workers.back())
if(i == workers.size() - 1)
work = boost::none;
// Wait for the thread to exit
thread.join();
}
std::cout <<
"Elapsed time: " << chrono::duration_cast<chrono::seconds>(t.elapsed()).count() << " seconds\n";
std::cout << report;
return EXIT_SUCCESS;
}
@@ -7,8 +7,8 @@
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
#define BOOST_BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
#ifndef BOOST_BEAST_EXAMPLE_HTTP_CLIENT_CRAWL_URLS_LARGE_DATA_HPP
#define BOOST_BEAST_EXAMPLE_HTTP_CLIENT_CRAWL_URLS_LARGE_DATA_HPP
#include <vector>
@@ -0,0 +1,23 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/client/sync-ssl "/")
add_executable (http-client-sync-ssl
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp
Jamfile
http_client_sync_ssl.cpp
)
target_link_libraries (http-client-sync-ssl
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe http-client-sync-ssl :
http_client_sync_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,112 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP SSL client, synchronous
//
//------------------------------------------------------------------------------
#include "example/common/root_certificates.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Performs an HTTP GET and prints the response
int main(int argc, char** argv)
{
try
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: http-client-sync-ssl <host> <port> <target>\n" <<
"Example:\n" <<
" http-client-sync-ssl www.example.com 443 /\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios;
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23_client};
// This holds the root certificate used for verification
load_root_certificates(ctx);
// These objects perform our I/O
tcp::resolver resolver{ios};
ssl::stream<tcp::socket> stream{ios, ctx};
// Look up the domain name
auto const lookup = resolver.resolve({host, port});
// Make the connection on the IP address we get from a lookup
boost::asio::connect(stream.next_layer(), lookup);
// Perform the SSL handshake
stream.handshake(ssl::stream_base::client);
// Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, target, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::write(stream, req);
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Receive the HTTP response
http::read(stream, buffer, res);
// Write the message to standard out
std::cout << res << std::endl;
// Gracefully close the stream
boost::system::error_code ec;
stream.shutdown(ec);
if(ec == boost::asio::error::eof)
{
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec.assign(0, ec.category());
}
if(ec)
throw boost::system::system_error{ec};
// If we get here then the connection is closed gracefully
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
@@ -8,10 +8,10 @@
#
GroupSources(include/boost/beast beast)
GroupSources(example/websocket-client "/")
GroupSources(example/http/client/sync "/")
add_executable (websocket-client
add_executable (http-client-sync
${BOOST_BEAST_INCLUDES}
Jamfile
websocket_client.cpp
http_client_sync.cpp
)
@@ -7,8 +7,8 @@
# Official repository: https://github.com/boostorg/beast
#
exe server-framework :
main.cpp
exe http-client-sync :
http_client_sync.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
@@ -0,0 +1,101 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP client, synchronous
//
//------------------------------------------------------------------------------
//[example_http_client
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Performs an HTTP GET and prints the response
int main(int argc, char** argv)
{
try
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: http-client-sync <host> <port> <target>\n" <<
"Example:\n" <<
" http-client-sync www.example.com 80 /\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios;
// These objects perform our I/O
tcp::resolver resolver{ios};
tcp::socket socket{ios};
// Look up the domain name
auto const lookup = resolver.resolve({host, port});
// Make the connection on the IP address we get from a lookup
boost::asio::connect(socket, lookup);
// Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, target, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::write(socket, req);
// This buffer is used for reading and must be persisted
boost::beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Receive the HTTP response
http::read(socket, buffer, res);
// Write the message to standard out
std::cout << res << std::endl;
// Gracefully close the socket
boost::system::error_code ec;
socket.shutdown(tcp::socket::shutdown_both, ec);
// not_connected happens sometimes
// so don't bother reporting it.
//
if(ec && ec != boost::system::errc::not_connected)
throw boost::system::system_error{ec};
// If we get here then the connection is closed gracefully
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
//]
+23
View File
@@ -0,0 +1,23 @@
#
# 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)
#
# Official repository: https://github.com/boostorg/beast
#
add_subdirectory (async)
add_subdirectory (coro)
add_subdirectory (fast)
add_subdirectory (small)
add_subdirectory (stackless)
add_subdirectory (sync)
if (OPENSSL_FOUND)
add_subdirectory (async-ssl)
add_subdirectory (coro-ssl)
add_subdirectory (flex)
add_subdirectory (stackless-ssl)
add_subdirectory (sync-ssl)
endif()
+22
View File
@@ -0,0 +1,22 @@
#
# 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)
#
# Official repository: https://github.com/boostorg/beast
#
build-project async ;
build-project coro ;
build-project fast ;
build-project small ;
build-project stackless ;
build-project sync ;
# VFALCO How do I make this work on Windows and if OpenSSL is not available?
#build-project async-ssl ;
#build-project coro-ssl ;
#build-project flex ;
#build-project stackless-ssl ;
#build-project sync-ssl ;
@@ -0,0 +1,24 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/server/async-ssl "/")
add_executable (http-server-async-ssl
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
Jamfile
http_server_async_ssl.cpp
)
target_link_libraries (http-server-async-ssl
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe http-server-async-ssl :
http_server_async_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,493 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP SSL server, asynchronous
//
//------------------------------------------------------------------------------
#include "example/common/server_certificate.hpp"
#include "example/common/write_msg.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Handles an HTTP server connection
class session : public std::enable_shared_from_this<session>
{
// This is the C++11 equivalent of a generic lambda.
// The function object is used to send an HTTP message.
struct send_lambda
{
session& self_;
explicit
send_lambda(session& self)
: self_(self)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// This function takes ownership of the message by moving
// it into a temporary buffer, otherwise we would have
// to manage the lifetime of the message and serializer.
async_write_msg(
self_.stream_,
std::move(msg),
self_.strand_.wrap(std::bind(
&session::on_write,
self_.shared_from_this(),
std::placeholders::_1)));
}
};
tcp::socket socket_;
ssl::stream<tcp::socket&> stream_;
boost::asio::io_service::strand strand_;
boost::beast::flat_buffer buffer_;
std::string const& doc_root_;
http::request<http::string_body> req_;
send_lambda lambda_;
public:
// Take ownership of the socket
explicit
session(
tcp::socket socket,
ssl::context& ctx,
std::string const& doc_root)
: socket_(std::move(socket))
, stream_(socket_, ctx)
, strand_(socket_.get_io_service())
, doc_root_(doc_root)
, lambda_(*this)
{
}
// Start the asynchronous operation
void
run()
{
// Perform the SSL handshake
stream_.async_handshake(
ssl::stream_base::server,
strand_.wrap(std::bind(
&session::on_handshake,
shared_from_this(),
std::placeholders::_1)));
}
void
on_handshake(boost::system::error_code ec)
{
if(ec)
return fail(ec, "handshake");
do_read();
}
void
do_read()
{
// Read a request
http::async_read(stream_, buffer_, req_,
strand_.wrap(std::bind(
&session::on_read,
shared_from_this(),
std::placeholders::_1)));
}
void
on_read(boost::system::error_code ec)
{
// This means they closed the connection
if(ec == http::error::end_of_stream)
return do_close();
if(ec)
return fail(ec, "read");
// Send the response
handle_request(doc_root_, std::move(req_), lambda_);
}
void
on_write(boost::system::error_code ec)
{
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
return do_close();
}
if(ec)
return fail(ec, "write");
// Read another request
do_read();
}
void
do_close()
{
// Perform the SSL shutdown
stream_.async_shutdown(
strand_.wrap(std::bind(
&session::on_shutdown,
shared_from_this(),
std::placeholders::_1)));
}
void
on_shutdown(boost::system::error_code ec)
{
if(ec)
return fail(ec, "shutdown");
// At this point the connection is closed gracefully
}
};
//------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
ssl::context& ctx_;
boost::asio::io_service::strand strand_;
tcp::acceptor acceptor_;
tcp::socket socket_;
std::string const& doc_root_;
public:
listener(
boost::asio::io_service& ios,
ssl::context& ctx,
tcp::endpoint endpoint,
std::string const& doc_root)
: ctx_(ctx)
, strand_(ios)
, acceptor_(ios)
, socket_(ios)
, doc_root_(doc_root)
{
boost::system::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if(ec)
{
fail(ec, "open");
return;
}
// Bind to the server address
acceptor_.bind(endpoint, ec);
if(ec)
{
fail(ec, "bind");
return;
}
// Start listening for connections
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
{
fail(ec, "listen");
return;
}
}
// Start accepting incoming connections
void
run()
{
if(! acceptor_.is_open())
return;
do_accept();
}
void
do_accept()
{
acceptor_.async_accept(
socket_,
std::bind(
&listener::on_accept,
shared_from_this(),
std::placeholders::_1));
}
void
on_accept(boost::system::error_code ec)
{
if(ec)
{
fail(ec, "accept");
}
else
{
// Create the session and run it
std::make_shared<session>(
std::move(socket_),
ctx_,
doc_root_)->run();
}
// Accept another connection
do_accept();
}
};
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr <<
"Usage: http-server-async-ssl <address> <port> <doc_root> <threads>\n" <<
"Example:\n" <<
" http-server-async-ssl 0.0.0.0 8080 . 1\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string const doc_root = argv[3];
auto const threads = std::max<std::size_t>(1, std::atoi(argv[3]));
// The io_service is required for all I/O
boost::asio::io_service ios{threads};
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23};
// This holds the self-signed certificate used by the server
load_server_certificate(ctx);
// Create and launch a listening port
std::make_shared<listener>(
ios,
ctx,
tcp::endpoint{address, port},
doc_root)->run();
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for(auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ios]
{
ios.run();
});
ios.run();
return EXIT_SUCCESS;
}
@@ -9,15 +9,11 @@
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http-client-ssl "/")
GroupSources(example/http/server/async "/")
add_executable (http-client-ssl
add_executable (http-server-async
${BOOST_BEAST_INCLUDES}
${COMMON_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
Jamfile
http_client_ssl.cpp
http_server_async.cpp
)
target_link_libraries (http-client-ssl
${OPENSSL_LIBRARIES}
)
+15
View File
@@ -0,0 +1,15 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
exe http-server-async :
http_server_async.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,451 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP server, asynchronous
//
//------------------------------------------------------------------------------
#include "example/common/write_msg.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Handles an HTTP server connection
class session : public std::enable_shared_from_this<session>
{
// This is the C++11 equivalent of a generic lambda.
// The function object is used to send an HTTP message.
struct send_lambda
{
session& self_;
explicit
send_lambda(session& self)
: self_(self)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// This function takes ownership of the message by moving
// it into a temporary buffer, otherwise we would have
// to manage the lifetime of the message and serializer.
async_write_msg(
self_.socket_,
std::move(msg),
self_.strand_.wrap(std::bind(
&session::on_write,
self_.shared_from_this(),
std::placeholders::_1)));
}
};
tcp::socket socket_;
boost::asio::io_service::strand strand_;
boost::beast::flat_buffer buffer_;
std::string const& doc_root_;
http::request<http::string_body> req_;
send_lambda lambda_;
public:
// Take ownership of the socket
explicit
session(
tcp::socket socket,
std::string const& doc_root)
: socket_(std::move(socket))
, strand_(socket_.get_io_service())
, doc_root_(doc_root)
, lambda_(*this)
{
}
// Start the asynchronous operation
void
run()
{
do_read();
}
void
do_read()
{
// Read a request
http::async_read(socket_, buffer_, req_,
strand_.wrap(std::bind(
&session::on_read,
shared_from_this(),
std::placeholders::_1)));
}
void
on_read(boost::system::error_code ec)
{
// This means they closed the connection
if(ec == http::error::end_of_stream)
return do_close();
if(ec)
return fail(ec, "read");
// Send the response
handle_request(doc_root_, std::move(req_), lambda_);
}
void
on_write(boost::system::error_code ec)
{
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
return do_close();
}
if(ec)
return fail(ec, "write");
// Read another request
do_read();
}
void
do_close()
{
// Send a TCP shutdown
boost::system::error_code ec;
socket_.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
};
//------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
boost::asio::io_service::strand strand_;
tcp::acceptor acceptor_;
tcp::socket socket_;
std::string const& doc_root_;
public:
listener(
boost::asio::io_service& ios,
tcp::endpoint endpoint,
std::string const& doc_root)
: strand_(ios)
, acceptor_(ios)
, socket_(ios)
, doc_root_(doc_root)
{
boost::system::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if(ec)
{
fail(ec, "open");
return;
}
// Bind to the server address
acceptor_.bind(endpoint, ec);
if(ec)
{
fail(ec, "bind");
return;
}
// Start listening for connections
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
{
fail(ec, "listen");
return;
}
}
// Start accepting incoming connections
void
run()
{
if(! acceptor_.is_open())
return;
do_accept();
}
void
do_accept()
{
acceptor_.async_accept(
socket_,
std::bind(
&listener::on_accept,
shared_from_this(),
std::placeholders::_1));
}
void
on_accept(boost::system::error_code ec)
{
if(ec)
{
fail(ec, "accept");
}
else
{
// Create the session and run it
std::make_shared<session>(
std::move(socket_),
doc_root_)->run();
}
// Accept another connection
do_accept();
}
};
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr <<
"Usage: http-server-async <address> <port> <doc_root> <threads>\n" <<
"Example:\n" <<
" http-server-async 0.0.0.0 8080 . 1\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string const doc_root = argv[3];
auto const threads = std::max<std::size_t>(1, std::atoi(argv[3]));
// The io_service is required for all I/O
boost::asio::io_service ios{threads};
// Create and launch a listening port
std::make_shared<listener>(
ios,
tcp::endpoint{address, port},
doc_root)->run();
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for(auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ios]
{
ios.run();
});
ios.run();
return EXIT_SUCCESS;
}
@@ -0,0 +1,23 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/server/coro-ssl "/")
add_executable (http-server-coro-ssl
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp
Jamfile
http_server_coro_ssl.cpp
)
target_link_libraries (http-server-coro-ssl
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe http-server-coro-ssl :
http_server_coro_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,393 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP SSL server, coroutine
//
//------------------------------------------------------------------------------
#include "example/common/server_certificate.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::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<class Stream>
struct send_lambda
{
Stream& stream_;
boost::system::error_code& ec_;
boost::asio::yield_context yield_;
explicit
send_lambda(
Stream& stream,
boost::system::error_code& ec,
boost::asio::yield_context yield)
: stream_(stream)
, ec_(ec)
, yield_(yield)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// 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<isRequest, Body, Fields> sr{msg};
http::async_write(stream_, sr, yield_[ec_]);
}
};
// Handles an HTTP server connection
void
do_session(
tcp::socket& socket,
ssl::context& ctx,
std::string const& doc_root,
boost::asio::yield_context yield)
{
boost::system::error_code ec;
// Construct the stream around the socket
ssl::stream<tcp::socket&> stream{socket, ctx};
// Perform the SSL handshake
stream.async_handshake(ssl::stream_base::server, yield[ec]);
if(ec)
return fail(ec, "handshake");
// This buffer is required to persist across reads
boost::beast::flat_buffer buffer;
// This lambda is used to send messages
send_lambda<ssl::stream<tcp::socket&>> lambda{stream, ec, yield};
for(;;)
{
// Read a request
http::request<http::string_body> req;
http::async_read(stream, buffer, req, yield[ec]);
if(ec == http::error::end_of_stream)
break;
if(ec)
return fail(ec, "read");
// Send the response
handle_request(doc_root, std::move(req), lambda);
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
break;
}
if(ec)
return fail(ec, "write");
}
// Perform the SSL shutdown
stream.async_shutdown(yield[ec]);
if(ec)
return fail(ec, "shutdown");
// At this point the connection is closed gracefully
}
//------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions
void
do_listen(
boost::asio::io_service& ios,
ssl::context& ctx,
tcp::endpoint endpoint,
std::string const& doc_root,
boost::asio::yield_context yield)
{
boost::system::error_code ec;
// Open the acceptor
tcp::acceptor acceptor(ios);
acceptor.open(endpoint.protocol(), ec);
if(ec)
return fail(ec, "open");
// Bind to the server address
acceptor.bind(endpoint, ec);
if(ec)
return fail(ec, "bind");
// Start listening for connections
acceptor.listen(boost::asio::socket_base::max_connections, ec);
if(ec)
return fail(ec, "listen");
for(;;)
{
tcp::socket socket(ios);
acceptor.async_accept(socket, yield[ec]);
if(ec)
fail(ec, "accept");
else
boost::asio::spawn(
acceptor.get_io_service(),
std::bind(
&do_session,
std::move(socket),
std::ref(ctx),
doc_root,
std::placeholders::_1));
}
}
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr <<
"Usage: http-server-coro-ssl <address> <port> <doc_root> <threads>\n" <<
"Example:\n" <<
" http-server-coro-ssl 0.0.0.0 8080 . 1\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
auto const threads = std::max<std::size_t>(1, std::atoi(argv[3]));
std::string const doc_root = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios{threads};
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23};
// This holds the self-signed certificate used by the server
load_server_certificate(ctx);
// Spawn a listening port
boost::asio::spawn(ios,
std::bind(
&do_listen,
std::ref(ios),
std::ref(ctx),
tcp::endpoint{address, port},
doc_root,
std::placeholders::_1));
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for(auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ios]
{
ios.run();
});
ios.run();
return EXIT_SUCCESS;
}
+17
View File
@@ -0,0 +1,17 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/http/server/coro "/")
add_executable (http-server-coro
${BOOST_BEAST_INCLUDES}
Jamfile
http_server_coro.cpp
)
+15
View File
@@ -0,0 +1,15 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
exe http-server-coro :
http_server_coro.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,369 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP server, coroutine
//
//------------------------------------------------------------------------------
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::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<class Stream>
struct send_lambda
{
Stream& stream_;
boost::system::error_code& ec_;
boost::asio::yield_context yield_;
explicit
send_lambda(
Stream& stream,
boost::system::error_code& ec,
boost::asio::yield_context yield)
: stream_(stream)
, ec_(ec)
, yield_(yield)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// 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<isRequest, Body, Fields> sr{msg};
http::async_write(stream_, sr, yield_[ec_]);
}
};
// Handles an HTTP server connection
void
do_session(
tcp::socket& socket,
std::string const& doc_root,
boost::asio::yield_context yield)
{
boost::system::error_code ec;
// This buffer is required to persist across reads
boost::beast::flat_buffer buffer;
// This lambda is used to send messages
send_lambda<tcp::socket> lambda{socket, ec, yield};
for(;;)
{
// Read a request
http::request<http::string_body> req;
http::async_read(socket, buffer, req, yield[ec]);
if(ec == http::error::end_of_stream)
break;
if(ec)
return fail(ec, "read");
// Send the response
handle_request(doc_root, std::move(req), lambda);
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
break;
}
if(ec)
return fail(ec, "write");
}
// Send a TCP shutdown
socket.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
//------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions
void
do_listen(
boost::asio::io_service& ios,
tcp::endpoint endpoint,
std::string const& doc_root,
boost::asio::yield_context yield)
{
boost::system::error_code ec;
// Open the acceptor
tcp::acceptor acceptor(ios);
acceptor.open(endpoint.protocol(), ec);
if(ec)
return fail(ec, "open");
// Bind to the server address
acceptor.bind(endpoint, ec);
if(ec)
return fail(ec, "bind");
// Start listening for connections
acceptor.listen(boost::asio::socket_base::max_connections, ec);
if(ec)
return fail(ec, "listen");
for(;;)
{
tcp::socket socket(ios);
acceptor.async_accept(socket, yield[ec]);
if(ec)
fail(ec, "accept");
else
boost::asio::spawn(
acceptor.get_io_service(),
std::bind(
&do_session,
std::move(socket),
doc_root,
std::placeholders::_1));
}
}
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr <<
"Usage: http-server-coro <address> <port> <doc_root> <threads>\n" <<
"Example:\n" <<
" http-server-coro 0.0.0.0 8080 . 1\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
auto const threads = std::max<std::size_t>(1, std::atoi(argv[3]));
std::string const doc_root = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios{threads};
// Spawn a listening port
boost::asio::spawn(ios,
std::bind(
&do_listen,
std::ref(ios),
tcp::endpoint{address, port},
doc_root,
std::placeholders::_1));
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for(auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ios]
{
ios.run();
});
ios.run();
return EXIT_SUCCESS;
}
@@ -9,7 +9,7 @@
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http-server-fast "/")
GroupSources(example/http/server/fast "/")
add_executable (http-server-fast
${BOOST_BEAST_INCLUDES}
@@ -7,9 +7,13 @@
// Official repository: https://github.com/boostorg/beast
//
#include "fields_alloc.hpp"
//------------------------------------------------------------------------------
//
// Example: HTTP server, fast
//
//------------------------------------------------------------------------------
#include "../common/mime_types.hpp"
#include "fields_alloc.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
@@ -19,7 +23,6 @@
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <list>
#include <memory>
@@ -29,6 +32,42 @@ namespace ip = boost::asio::ip; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
class http_worker
{
public:
@@ -306,7 +345,7 @@ int main(int argc, char* argv[])
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
+25
View File
@@ -0,0 +1,25 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/server/flex "/")
add_executable (http-server-flex
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/detect_ssl.hpp
${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
Jamfile
http_server_flex.cpp
)
target_link_libraries (http-server-flex
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe http-server-async-ssl :
http_server_async_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,657 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP flex server (plain and SSL), asynchronous
//
//------------------------------------------------------------------------------
#include "example/common/detect_ssl.hpp"
#include "example/common/server_certificate.hpp"
#include "example/common/write_msg.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Handles an HTTP server connection.
// This uses the Curiously Recurring Template Pattern so that
// the same code works with both SSL streams and regular sockets.
template<class Derived>
class session
{
// Access the derived class, this is part of
// the Curiously Recurring Template Pattern idiom.
Derived&
derived()
{
return static_cast<Derived&>(*this);
}
// This is the C++11 equivalent of a generic lambda.
// The function object is used to send an HTTP message.
struct send_lambda
{
session& self_;
explicit
send_lambda(session& self)
: self_(self)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// This function takes ownership of the message by moving
// it into a temporary buffer, otherwise we would have
// to manage the lifetime of the message and serializer.
async_write_msg(
self_.derived().stream(),
std::move(msg),
self_.strand_.wrap(std::bind(
&session::on_write,
self_.derived().shared_from_this(),
std::placeholders::_1)));
}
};
std::string const& doc_root_;
http::request<http::string_body> req_;
send_lambda lambda_;
protected:
boost::asio::io_service::strand strand_;
boost::beast::flat_buffer buffer_;
public:
// Take ownership of the buffer
explicit
session(
boost::asio::io_service& ios,
boost::beast::flat_buffer buffer,
std::string const& doc_root)
: doc_root_(doc_root)
, lambda_(*this)
, strand_(ios)
, buffer_(std::move(buffer))
{
}
void
do_read()
{
// Read a request
http::async_read(
derived().stream(),
buffer_,
req_,
strand_.wrap(std::bind(
&session::on_read,
derived().shared_from_this(),
std::placeholders::_1)));
}
void
on_read(boost::system::error_code ec)
{
// This means they closed the connection
if(ec == http::error::end_of_stream)
return derived().do_eof();
if(ec)
return fail(ec, "read");
// Send the response
handle_request(doc_root_, std::move(req_), lambda_);
}
void
on_write(boost::system::error_code ec)
{
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
return derived().do_eof();
}
if(ec)
return fail(ec, "write");
// Read another request
do_read();
}
};
// Handles a plain HTTP connection
class plain_session
: public session<plain_session>
, public std::enable_shared_from_this<plain_session>
{
tcp::socket socket_;
boost::asio::io_service::strand strand_;
public:
// Create the session
plain_session(
tcp::socket socket,
boost::beast::flat_buffer buffer,
std::string const& doc_root)
: session<plain_session>(
socket.get_io_service(),
std::move(buffer),
doc_root)
, socket_(std::move(socket))
, strand_(socket_.get_io_service())
{
}
// Called by the base class
tcp::socket&
stream()
{
return socket_;
}
// Start the asynchronous operation
void
run()
{
do_read();
}
void
do_eof()
{
// Send a TCP shutdown
boost::system::error_code ec;
socket_.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
};
// Handles an SSL HTTP connection
class ssl_session
: public session<ssl_session>
, public std::enable_shared_from_this<ssl_session>
{
tcp::socket socket_;
ssl::stream<tcp::socket&> stream_;
boost::asio::io_service::strand strand_;
public:
// Create the session
ssl_session(
tcp::socket socket,
ssl::context& ctx,
boost::beast::flat_buffer buffer,
std::string const& doc_root)
: session<ssl_session>(
socket.get_io_service(),
std::move(buffer),
doc_root)
, socket_(std::move(socket))
, stream_(socket_, ctx)
, strand_(stream_.get_io_service())
{
}
// Called by the base class
ssl::stream<tcp::socket&>&
stream()
{
return stream_;
}
// Start the asynchronous operation
void
run()
{
// Perform the SSL handshake
// Note, this is the buffered version of the handshake.
stream_.async_handshake(
ssl::stream_base::server,
buffer_.data(),
strand_.wrap(std::bind(
&ssl_session::on_handshake,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2)));
}
void
on_handshake(
boost::system::error_code ec,
std::size_t bytes_used)
{
if(ec)
return fail(ec, "handshake");
// Consume the portion of the buffer used by the handshake
buffer_.consume(bytes_used);
do_read();
}
void
do_eof()
{
// Perform the SSL shutdown
stream_.async_shutdown(
strand_.wrap(std::bind(
&ssl_session::on_shutdown,
shared_from_this(),
std::placeholders::_1)));
}
void
on_shutdown(boost::system::error_code ec)
{
if(ec)
return fail(ec, "shutdown");
// At this point the connection is closed gracefully
}
};
//------------------------------------------------------------------------------
// Detects SSL handshakes
class detect_session : public std::enable_shared_from_this<detect_session>
{
tcp::socket socket_;
ssl::context& ctx_;
boost::asio::io_service::strand strand_;
std::string const& doc_root_;
boost::beast::flat_buffer buffer_;
public:
explicit
detect_session(
tcp::socket socket,
ssl::context& ctx,
std::string const& doc_root)
: socket_(std::move(socket))
, ctx_(ctx)
, strand_(socket_.get_io_service())
, doc_root_(doc_root)
{
}
// Launch the detector
void
run()
{
async_detect_ssl(
socket_,
buffer_,
strand_.wrap(std::bind(
&detect_session::on_detect,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2)));
}
void
on_detect(boost::system::error_code ec, boost::tribool result)
{
if(ec)
return fail(ec, "detect");
if(result)
{
// Launch SSL session
std::make_shared<ssl_session>(
std::move(socket_),
ctx_,
std::move(buffer_),
doc_root_)->run();
return;
}
// Launch plain session
std::make_shared<plain_session>(
std::move(socket_),
std::move(buffer_),
doc_root_)->run();
}
};
// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
ssl::context& ctx_;
boost::asio::io_service::strand strand_;
tcp::acceptor acceptor_;
tcp::socket socket_;
std::string const& doc_root_;
public:
listener(
boost::asio::io_service& ios,
ssl::context& ctx,
tcp::endpoint endpoint,
std::string const& doc_root)
: ctx_(ctx)
, strand_(ios)
, acceptor_(ios)
, socket_(ios)
, doc_root_(doc_root)
{
boost::system::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if(ec)
{
fail(ec, "open");
return;
}
// Bind to the server address
acceptor_.bind(endpoint, ec);
if(ec)
{
fail(ec, "bind");
return;
}
// Start listening for connections
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
{
fail(ec, "listen");
return;
}
}
// Start accepting incoming connections
void
run()
{
if(! acceptor_.is_open())
return;
do_accept();
}
void
do_accept()
{
acceptor_.async_accept(
socket_,
std::bind(
&listener::on_accept,
shared_from_this(),
std::placeholders::_1));
}
void
on_accept(boost::system::error_code ec)
{
if(ec)
{
fail(ec, "accept");
}
else
{
// Create the detector session and run it
std::make_shared<detect_session>(
std::move(socket_),
ctx_,
doc_root_)->run();
}
// Accept another connection
do_accept();
}
};
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr <<
"Usage: http-server-sync <address> <port> <doc_root> <threads>\n" <<
"Example:\n" <<
" http-server-sync 0.0.0.0 8080 .\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string const doc_root = argv[3];
auto const threads = std::max<std::size_t>(1, std::atoi(argv[3]));
// The io_service is required for all I/O
boost::asio::io_service ios{threads};
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23};
// This holds the self-signed certificate used by the server
load_server_certificate(ctx);
// Create and launch a listening port
std::make_shared<listener>(
ios,
ctx,
tcp::endpoint{address, port},
doc_root)->run();
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for(auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ios]
{
ios.run();
});
ios.run();
return EXIT_SUCCESS;
}
@@ -8,7 +8,7 @@
#
GroupSources(include/boost/beast beast)
GroupSources(example/http-server-small "/")
GroupSources(example/http/server/small "/")
add_executable (http-server-small
${BOOST_BEAST_INCLUDES}
@@ -7,6 +7,12 @@
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP server, small
//
//------------------------------------------------------------------------------
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
@@ -236,7 +242,7 @@ main(int argc, char* argv[])
}
catch(std::exception const& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
@@ -0,0 +1,24 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/server/stackless-ssl "/")
add_executable (http-server-stackless-ssl
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
Jamfile
http_server_stackless_ssl.cpp
)
target_link_libraries (http-server-stackless-ssl
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe http-server-async-ssl :
http_server_async_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,479 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP SSL server, stackless coroutine
//
//------------------------------------------------------------------------------
#include "example/common/server_certificate.hpp"
#include "example/common/write_msg.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Handles an HTTP server connection
class session
: public boost::asio::coroutine
, public std::enable_shared_from_this<session>
{
// This is the C++11 equivalent of a generic lambda.
// The function object is used to send an HTTP message.
struct send_lambda
{
session& self_;
explicit
send_lambda(session& self)
: self_(self)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// This function takes ownership of the message by moving
// it into a temporary buffer, otherwise we would have
// to manage the lifetime of the message and serializer.
async_write_msg(
self_.stream_,
std::move(msg),
self_.strand_.wrap(std::bind(
&session::loop,
self_.shared_from_this(),
std::placeholders::_1)));
}
};
tcp::socket socket_;
ssl::stream<tcp::socket&> stream_;
boost::asio::io_service::strand strand_;
boost::beast::flat_buffer buffer_;
std::string const& doc_root_;
http::request<http::string_body> req_;
send_lambda lambda_;
public:
// Take ownership of the socket
explicit
session(
tcp::socket socket,
ssl::context& ctx,
std::string const& doc_root)
: socket_(std::move(socket))
, stream_(socket_, ctx)
, strand_(socket_.get_io_service())
, doc_root_(doc_root)
, lambda_(*this)
{
}
// Start the asynchronous operation
void
run()
{
loop();
}
#include <boost/asio/yield.hpp>
void
loop(boost::system::error_code ec = {})
{
reenter(*this)
{
// Perform the SSL handshake
yield stream_.async_handshake(
ssl::stream_base::server,
strand_.wrap(std::bind(
&session::loop,
shared_from_this(),
std::placeholders::_1)));
if(ec)
return fail(ec, "handshake");
for(;;)
{
// Read a request
yield http::async_read(stream_, buffer_, req_,
strand_.wrap(std::bind(
&session::loop,
shared_from_this(),
std::placeholders::_1)));
if(ec == http::error::end_of_stream)
{
// The remote host closed the connection
break;
}
if(ec)
return fail(ec, "read");
// Send the response
yield handle_request(doc_root_, std::move(req_), lambda_);
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
break;
}
if(ec)
return fail(ec, "write");
}
// Perform the SSL shutdown
yield stream_.async_shutdown(
strand_.wrap(std::bind(
&session::loop,
shared_from_this(),
std::placeholders::_1)));
if(ec)
return fail(ec, "shutdown");
// At this point the connection is closed gracefully
}
}
#include <boost/asio/unyield.hpp>
};
//------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions
class listener
: public boost::asio::coroutine
, public std::enable_shared_from_this<listener>
{
ssl::context& ctx_;
boost::asio::io_service::strand strand_;
tcp::acceptor acceptor_;
tcp::socket socket_;
std::string const& doc_root_;
public:
listener(
boost::asio::io_service& ios,
ssl::context& ctx,
tcp::endpoint endpoint,
std::string const& doc_root)
: ctx_(ctx)
, strand_(ios)
, acceptor_(ios)
, socket_(ios)
, doc_root_(doc_root)
{
boost::system::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if(ec)
{
fail(ec, "open");
return;
}
// Bind to the server address
acceptor_.bind(endpoint, ec);
if(ec)
{
fail(ec, "bind");
return;
}
// Start listening for connections
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
{
fail(ec, "listen");
return;
}
}
// Start accepting incoming connections
void
run()
{
if(! acceptor_.is_open())
return;
loop();
}
#include <boost/asio/yield.hpp>
void
loop(boost::system::error_code ec = {})
{
reenter(*this)
{
for(;;)
{
yield acceptor_.async_accept(
socket_,
std::bind(
&listener::loop,
shared_from_this(),
std::placeholders::_1));
if(ec)
{
fail(ec, "accept");
}
else
{
// Create the session and run it
std::make_shared<session>(
std::move(socket_),
ctx_,
doc_root_)->run();
}
}
}
}
#include <boost/asio/unyield.hpp>
};
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr <<
"Usage: http-server-stackless-ssl <address> <port> <doc_root> <threads>\n" <<
"Example:\n" <<
" http-server-stackless-ssl 0.0.0.0 8080 . 1\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string const doc_root = argv[3];
auto const threads = std::max<std::size_t>(1, std::atoi(argv[3]));
// The io_service is required for all I/O
boost::asio::io_service ios{threads};
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23};
// This holds the self-signed certificate used by the server
load_server_certificate(ctx);
// Create and launch a listening port
std::make_shared<listener>(
ios,
ctx,
tcp::endpoint{address, port},
doc_root)->run();
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for(auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ios]
{
ios.run();
});
ios.run();
return EXIT_SUCCESS;
}
@@ -0,0 +1,19 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/server/stackless "/")
add_executable (http-server-stackless
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
Jamfile
http_server_stackless.cpp
)
+15
View File
@@ -0,0 +1,15 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
exe http-server-stackless :
http_server_stackless.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,445 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP server, stackless coroutine
//
//------------------------------------------------------------------------------
#include "example/common/write_msg.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Handles an HTTP server connection
class session
: public boost::asio::coroutine
, public std::enable_shared_from_this<session>
{
// This is the C++11 equivalent of a generic lambda.
// The function object is used to send an HTTP message.
struct send_lambda
{
session& self_;
explicit
send_lambda(session& self)
: self_(self)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// This function takes ownership of the message by moving
// it into a temporary buffer, otherwise we would have
// to manage the lifetime of the message and serializer.
async_write_msg(
self_.socket_,
std::move(msg),
self_.strand_.wrap(std::bind(
&session::loop,
self_.shared_from_this(),
std::placeholders::_1)));
}
};
tcp::socket socket_;
boost::asio::io_service::strand strand_;
boost::beast::flat_buffer buffer_;
std::string const& doc_root_;
http::request<http::string_body> req_;
send_lambda lambda_;
public:
// Take ownership of the socket
explicit
session(
tcp::socket socket,
std::string const& doc_root)
: socket_(std::move(socket))
, strand_(socket_.get_io_service())
, doc_root_(doc_root)
, lambda_(*this)
{
}
// Start the asynchronous operation
void
run()
{
loop();
}
#include <boost/asio/yield.hpp>
void
loop(boost::system::error_code ec = {})
{
reenter(*this)
{
for(;;)
{
// Read a request
yield http::async_read(socket_, buffer_, req_,
strand_.wrap(std::bind(
&session::loop,
shared_from_this(),
std::placeholders::_1)));
if(ec == http::error::end_of_stream)
{
// The remote host closed the connection
break;
}
if(ec)
return fail(ec, "read");
// Send the response
yield handle_request(doc_root_, std::move(req_), lambda_);
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
break;
}
if(ec)
return fail(ec, "write");
}
// Send a TCP shutdown
socket_.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
}
#include <boost/asio/unyield.hpp>
};
//------------------------------------------------------------------------------
// Accepts incoming connections and launches the sessions
class listener
: public boost::asio::coroutine
, public std::enable_shared_from_this<listener>
{
boost::asio::io_service::strand strand_;
tcp::acceptor acceptor_;
tcp::socket socket_;
std::string const& doc_root_;
public:
listener(
boost::asio::io_service& ios,
tcp::endpoint endpoint,
std::string const& doc_root)
: strand_(ios)
, acceptor_(ios)
, socket_(ios)
, doc_root_(doc_root)
{
boost::system::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if(ec)
{
fail(ec, "open");
return;
}
// Bind to the server address
acceptor_.bind(endpoint, ec);
if(ec)
{
fail(ec, "bind");
return;
}
// Start listening for connections
acceptor_.listen(boost::asio::socket_base::max_connections, ec);
if(ec)
{
fail(ec, "listen");
return;
}
}
// Start accepting incoming connections
void
run()
{
if(! acceptor_.is_open())
return;
loop();
}
#include <boost/asio/yield.hpp>
void
loop(boost::system::error_code ec = {})
{
reenter(*this)
{
for(;;)
{
yield acceptor_.async_accept(
socket_,
std::bind(
&listener::loop,
shared_from_this(),
std::placeholders::_1));
if(ec)
{
fail(ec, "accept");
}
else
{
// Create the session and run it
std::make_shared<session>(
std::move(socket_),
doc_root_)->run();
}
}
}
}
#include <boost/asio/unyield.hpp>
};
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr <<
"Usage: http-server-stackless <address> <port> <doc_root> <threads>\n" <<
"Example:\n" <<
" http-server-stackless 0.0.0.0 8080 . 1\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string const doc_root = argv[3];
auto const threads = std::max<std::size_t>(1, std::atoi(argv[3]));
// The io_service is required for all I/O
boost::asio::io_service ios{threads};
// Create and launch a listening port
std::make_shared<listener>(
ios,
tcp::endpoint{address, port},
doc_root)->run();
// Run the I/O service on the requested number of threads
std::vector<std::thread> v;
v.reserve(threads - 1);
for(auto i = threads - 1; i > 0; --i)
v.emplace_back(
[&ios]
{
ios.run();
});
ios.run();
return EXIT_SUCCESS;
}
@@ -0,0 +1,22 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/common common)
GroupSources(example/http/server/sync-ssl "/")
add_executable (http-server-sync-ssl
${BOOST_BEAST_INCLUDES}
${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp
Jamfile
http_server_sync_ssl.cpp
)
target_link_libraries (http-server-sync-ssl
${OPENSSL_LIBRARIES}
)
+21
View File
@@ -0,0 +1,21 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
project
: requirements
<library>ssl
<library>crypto
;
exe http-server-sync-ssl :
http_server_sync_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,343 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP SSL server, synchronous
//
//------------------------------------------------------------------------------
#include "example/common/server_certificate.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/config.hpp>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::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<class Stream>
struct send_lambda
{
Stream& stream_;
boost::system::error_code& ec_;
explicit
send_lambda(
Stream& stream,
boost::system::error_code& ec)
: stream_(stream)
, ec_(ec)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// 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<isRequest, Body, Fields> sr{msg};
http::write(stream_, sr, ec_);
}
};
// Handles an HTTP server connection
void
do_session(
tcp::socket& socket,
ssl::context& ctx,
std::string const& doc_root)
{
boost::system::error_code ec;
// Construct the stream around the socket
ssl::stream<tcp::socket&> stream{socket, ctx};
// Perform the SSL handshake
stream.handshake(ssl::stream_base::server, ec);
if(ec)
return fail(ec, "handshake");
// This buffer is required to persist across reads
boost::beast::flat_buffer buffer;
// This lambda is used to send messages
send_lambda<ssl::stream<tcp::socket&>> lambda{stream, ec};
for(;;)
{
// Read a request
http::request<http::string_body> req;
http::read(stream, buffer, req, ec);
if(ec == http::error::end_of_stream)
break;
if(ec)
return fail(ec, "read");
// Send the response
handle_request(doc_root, std::move(req), lambda);
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
break;
}
if(ec)
return fail(ec, "write");
}
// Perform the SSL shutdown
stream.shutdown(ec);
if(ec)
return fail(ec, "shutdown");
// At this point the connection is closed gracefully
}
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 4)
{
std::cerr <<
"Usage: http-server-sync-ssl <address> <port> <doc_root>\n" <<
"Example:\n" <<
" http-server-sync-ssl 0.0.0.0 8080 .\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string const doc_root = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios{1};
// The SSL context is required, and holds certificates
ssl::context ctx{ssl::context::sslv23};
// This holds the self-signed certificate used by the server
load_server_certificate(ctx);
// The acceptor receives incoming connections
tcp::acceptor acceptor{ios, {address, port}};
for(;;)
{
// This will receive the new connection
tcp::socket socket{ios};
// Block until we get a connection
acceptor.accept(socket);
// Launch the session, transferring ownership of the socket
std::thread{std::bind(
&do_session,
std::move(socket),
std::ref(ctx),
doc_root)}.detach();
}
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
+17
View File
@@ -0,0 +1,17 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/http/server/sync "/")
add_executable (http-server-sync
${BOOST_BEAST_INCLUDES}
Jamfile
http_server_sync.cpp
)
+15
View File
@@ -0,0 +1,15 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
exe http-server-sync :
http_server_sync.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;
@@ -0,0 +1,323 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP server, synchronous
//
//------------------------------------------------------------------------------
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/config.hpp>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
//------------------------------------------------------------------------------
// Return a reasonable mime type based on the extension of a file.
boost::beast::string_view
mime_type(boost::beast::string_view path)
{
using boost::beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == boost::beast::string_view::npos)
return boost::beast::string_view{};
return path.substr(pos);
}();
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";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
path_cat(
boost::beast::string_view base,
boost::beast::string_view path)
{
if(base.empty())
return path.to_string();
std::string result = base.to_string();
#if 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(
boost::beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> 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 = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> 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 '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req](boost::beast::string_view what)
{
http::response<http::string_body> 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: '" + what.to_string() + "'";
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("..") != boost::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
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if(ec == boost::system::errc::no_such_file_or_directory)
return send(not_found(req.target()));
// Handle an unknown error
if(ec)
return send(server_error(ec.message()));
// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
// Respond to HEAD request
http::response<http::file_body> 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(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
//------------------------------------------------------------------------------
// Report a failure
void
fail(boost::system::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<class Stream>
struct send_lambda
{
Stream& stream_;
boost::system::error_code& ec_;
explicit
send_lambda(
Stream& stream,
boost::system::error_code& ec)
: stream_(stream)
, ec_(ec)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& msg) const
{
// 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<isRequest, Body, Fields> sr{msg};
http::write(stream_, sr, ec_);
}
};
// Handles an HTTP server connection
void
do_session(
tcp::socket& socket,
std::string const& doc_root)
{
boost::system::error_code ec;
// This buffer is required to persist across reads
boost::beast::flat_buffer buffer;
// This lambda is used to send messages
send_lambda<tcp::socket> lambda{socket, ec};
for(;;)
{
// Read a request
http::request<http::string_body> req;
http::read(socket, buffer, req, ec);
if(ec == http::error::end_of_stream)
break;
if(ec)
return fail(ec, "read");
// Send the response
handle_request(doc_root, std::move(req), lambda);
if(ec == http::error::end_of_stream)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
break;
}
if(ec)
return fail(ec, "write");
}
// Send a TCP shutdown
socket.shutdown(tcp::socket::shutdown_send, ec);
// At this point the connection is closed gracefully
}
//------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 4)
{
std::cerr <<
"Usage: http-server-sync <address> <port> <doc_root>\n" <<
"Example:\n" <<
" http-server-sync 0.0.0.0 8080 .\n";
return EXIT_FAILURE;
}
auto const address = boost::asio::ip::address::from_string(argv[1]);
auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string const doc_root = argv[3];
// The io_service is required for all I/O
boost::asio::io_service ios{1};
// The acceptor receives incoming connections
tcp::acceptor acceptor{ios, {address, port}};
for(;;)
{
// This will receive the new connection
tcp::socket socket{ios};
// Block until we get a connection
acceptor.accept(socket);
// Launch the session, transferring ownership of the socket
std::thread{std::bind(
&do_session,
std::move(socket),
doc_root)}.detach();
}
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
-30
View File
@@ -1,30 +0,0 @@
#
# Copyright (c) 2016-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)
#
# Official repository: https://github.com/boostorg/beast
#
GroupSources(include/boost/beast beast)
GroupSources(example/server-framework "/")
GroupSources(example/common "common")
file(GLOB_RECURSE SERVER_INCLUDES
${PROJECT_SOURCE_DIR}/example/server-framework/*.hpp
)
add_executable (server-framework
${BOOST_BEAST_INCLUDES}
${COMMON_INCLUDES}
${SERVER_INCLUDES}
Jamfile
main.cpp
)
if (OPENSSL_FOUND)
target_link_libraries(server-framework
${OPENSSL_LIBRARIES}
)
endif()
-159
View File
@@ -1,159 +0,0 @@
<img width="880" height = "80" alt = "Beast"
src="https://raw.githubusercontent.com/boostorg/beast/master/doc/images/readme.png">
# HTTP and WebSocket built on Boost.Asio in C++11
## Server-Framework
This example is a complete, multi-threaded server built with Beast.
It contains the following components
* WebSocket ports (synchronous and asynchronous)
- Echoes back any message received
- Plain or SSL (if OpenSSL available)
* HTTP ports (synchronous and asynchronous)
- Serves files from a configurable directory on GET request
- Responds to HEAD requests with the appropriate result
- Routes WebSocket Upgrade requests to a WebSocket port
- Handles Expect: 100-continue
- Supports pipelined requests
- Plain or SSL (if OpenSSL available)
* Multi-Port: Plain, OpenSSL, HTTP, WebSocket **All on the same port!**
The server is designed to use modular components that users may simply copy
into their own project to get started quickly. Two concepts are introduced:
## PortHandler
The **PortHandler** concept defines an algorithm for handling incoming
connections received on a listening socket. The example comes with a
total of *nine* port handlers!
| Type | Plain | SSL |
| ----- | ----------------- | ------------------ |
| Sync | `http_sync_port` | `https_sync_port` |
| | `ws_sync_port` | `wss_sync_port` |
| Async | `http_async_port` | `https_async_port` |
| | `wss_sync_port` | `wss_async_port` |
| | `multi_port` | `multi_port` |
A port handler takes the stream object resulting form an incoming connection
request and constructs a handler-specific connection object which provides
the desired behavior.
The HTTP ports which come with the example have a system built in which allows
installation of framework and user-defined "HTTP services". These services
inform connections using the port on how to handle specific requests. This is
similar in concept to an "HTTP router" which is an element of most modern
servers.
These HTTP services are represented by the **Service** concept, and managed
in a container holding a type-list, called a `service_list`. Each HTTP port
allows the sevice list to be defined at compile-time and initialized at run
time. The framework provides these services:
* `file_service` Produces HTTP responses delivering files from a system path
* `ws_upgrade_service` Transports a connection requesting a WebSocket Upgrade
to a websocket port handler.
## Relationship
This diagram shows the relationship of the server object, to the nine
ports created in the example program, and the HTTP services contained by
the HTTP ports:
<img width="880" height = "344" alt = "ServerFramework"
src="https://raw.githubusercontent.com/boostorg/beast/master/doc/images/server.png">
## PortHandler Requirements
```C++
/** An synchronous WebSocket @b PortHandler which implements echo.
This is a port handler which accepts WebSocket upgrade HTTP
requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
struct PortHandler
{
/** Accept a TCP/IP socket.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(
socket_type&& sock,
endpoint_type ep);
};
```
## Service Requirements
```C++
struct Service
{
/** Initialize the service
@param ec Set to the error, if any occurred
*/
void
init(error_code& ec);
/** Maybe respond to an HTTP request
Upon handling the response, the service may optionally
take ownership of either the stream, the request, or both.
@param stream The stream representing the connection
@param ep The remote endpoint of the stream
@param req The HTTP request
@param send A function object which operates on a single
argument of type beast::http::message. The function object
has this equivalent signature:
@code
template<class Body, class Fields>
void send(beast::http::response<Body, Fields>&& res);
@endcode
@return `true` if the service handled the response.
*/
template<
class Stream,
class Body, class Fields,
class Send>
bool
respond(
Stream&& stream,
endpoint_type const& ep,
beast::http::request<Body, Fields>&& req,
Send const& send) const
};
```
## Upgrade Service Requirements
To work with the `ws_upgrade_service`, a port or handler needs
this signature:
```C++
struct UpgradePort
{
template<class Stream, class Body, class Fields>
void
on_upgrade(
Stream&& stream,
endpoint_type ep,
beast::http::request<Body, Fields>&& req);
```
-281
View File
@@ -1,281 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP
#include "framework.hpp"
#include "../common/mime_types.hpp"
#include <boost/beast/core/string.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/file_body.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/filesystem/path.hpp>
#include <string>
namespace framework {
/** An HTTP service which delivers files from a root directory.
This service will accept GET and HEAD requests for files,
and deliver them as responses. The service constructs with
the location on the file system to act as the root for the
tree of files to serve.
Meets the requirements of @b Service
*/
class file_service
{
// The path to serve files from
boost::filesystem::path root_;
// The name to use in the Server HTTP field
std::string server_;
public:
/** Constructor
@param root A path with files to serve. A GET request
for "/" will try to deliver the file "/index.html".
@param The string to use in the Server HTTP field.
*/
explicit
file_service(
boost::filesystem::path const& root,
boost::beast::string_view server)
: root_(root)
, server_(server)
{
}
/** Initialize the service.
This provides an opportunity for the service to perform
initialization which may fail, while reporting an error
code instead of throwing an exception from the constructor.
@note This is needed for to meet the requirements for @b Service
*/
void
init(error_code& ec)
{
// This is required by the error_code specification
//
ec = {};
}
/** Try to handle a file request.
@param stream The stream belonging to the connection.
Ownership is not transferred.
@param ep The remote endpoint of the connection
corresponding to the stream.
@param req The request message to attempt handling.
Ownership is not transferred.
@param send The function to invoke with the response.
The function will have this equivalent signature:
@code
template<class Body, class Fields>
void
send(response<Body, Fields>&&);
@endcode
In C++14 this can be expressed using a generic lambda. In
C++11 it will require a template member function of an invocable
object.
@return `true` if the request was handled by the service.
*/
template<
class Stream,
class Body, class Fields,
class Send>
bool
respond(
Stream&&,
endpoint_type const& ep,
boost::beast::http::request<Body, Fields>&& req,
Send const& send) const
{
boost::ignore_unused(ep);
// Determine our action based on the method
switch(req.method())
{
case boost::beast::http::verb::get:
{
// For GET requests we deliver the actual file
boost::filesystem::path rel_path(req.target().to_string());
// Give them the root web page if the target is "/"
if(rel_path == "/")
rel_path = "/index.html";
// Calculate full path from root
boost::filesystem::path full_path = root_ / rel_path;
boost::beast::error_code ec;
auto res = get(req, full_path, ec);
if(ec == boost::beast::errc::no_such_file_or_directory)
{
send(not_found(req, rel_path));
}
else if(ec)
{
send(server_error(req, rel_path, ec));
}
else
{
send(std::move(*res));
}
// Indicate that we handled the request
return true;
}
case boost::beast::http::verb::head:
{
// We are just going to give them the headers they
// would otherwise get, but without the body.
boost::filesystem::path rel_path(req.target().to_string());
if(rel_path == "/")
rel_path = "/index.html";
// Calculate full path from root
boost::filesystem::path full_path = root_ / rel_path;
boost::beast::error_code ec;
auto res = head(req, full_path, ec);
if(ec == boost::beast::errc::no_such_file_or_directory)
{
send(not_found(req, rel_path));
}
else if(ec)
{
send(server_error(req, rel_path, ec));
}
else
{
send(std::move(*res));
}
// Indicate that we handled the request
return true;
}
default:
break;
}
// We didn't handle this request, so return false to
// inform the service list to try the next service.
//
return false;
}
private:
// Return an HTTP Not Found response
//
template<class Body, class Fields>
boost::beast::http::response<boost::beast::http::string_body>
not_found(
boost::beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& rel_path) const
{
boost::ignore_unused(rel_path);
boost::beast::http::response<boost::beast::http::string_body> res;
res.version = req.version;
res.result(boost::beast::http::status::not_found);
res.set(boost::beast::http::field::server, server_);
res.set(boost::beast::http::field::content_type, "text/html");
res.body = "The file was not found"; // VFALCO append rel_path
res.prepare_payload();
return res;
}
// Return an HTTP Server Error
//
template<class Body, class Fields>
boost::beast::http::response<boost::beast::http::string_body>
server_error(
boost::beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& rel_path,
error_code const& ec) const
{
boost::ignore_unused(rel_path);
boost::beast::http::response<boost::beast::http::string_body> res;
res.version = req.version;
res.result(boost::beast::http::status::internal_server_error);
res.set(boost::beast::http::field::server, server_);
res.set(boost::beast::http::field::content_type, "text/html");
res.body = "Error: " + ec.message();
res.prepare_payload();
return res;
}
// Return a file response to an HTTP GET request
//
template<class Body, class Fields>
boost::optional<boost::beast::http::response<boost::beast::http::file_body>>
get(
boost::beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path,
boost::beast::error_code& ec) const
{
boost::beast::http::response<boost::beast::http::file_body> res;
res.version = req.version;
res.set(boost::beast::http::field::server, server_);
res.set(boost::beast::http::field::content_type, mime_type(full_path));
res.body.open(full_path.string<std::string>().c_str(), boost::beast::file_mode::scan, ec);
if(ec)
return boost::none;
res.set(boost::beast::http::field::content_length, res.body.size());
return {std::move(res)};
}
// Return a response to an HTTP HEAD request
//
template<class Body, class Fields>
boost::optional<boost::beast::http::response<boost::beast::http::empty_body>>
head(
boost::beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path,
boost::beast::error_code& ec) const
{
boost::beast::http::response<boost::beast::http::empty_body> res;
res.version = req.version;
res.set(boost::beast::http::field::server, server_);
res.set(boost::beast::http::field::content_type, mime_type(full_path));
// Use a manual file body here
boost::beast::http::file_body::value_type body;
body.open(full_path.string<std::string>().c_str(), boost::beast::file_mode::scan, ec);
if(ec)
return boost::none;
res.set(boost::beast::http::field::content_length, body.size());
return {std::move(res)};
}
};
} // framework
#endif
-55
View File
@@ -1,55 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP
#include <boost/asio/io_service.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <utility>
/** The framework namespace
This namespace contains all of the identifiers in the
server-framework system. Here we import some commonly
used types for brevity.
*/
namespace framework {
// This is our own base from member idiom written for C++11
// which is simple and works around a glitch in boost's version.
//
template<class T>
class base_from_member
{
public:
template<class... Args>
explicit
base_from_member(Args&&... args)
: member(std::forward<Args>(args)...)
{
}
protected:
T member;
};
using error_code = boost::system::error_code;
using socket_type = boost::asio::ip::tcp::socket;
using strand_type = boost::asio::io_service::strand;
using address_type = boost::asio::ip::address_v4;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using acceptor_type = boost::asio::ip::tcp::acceptor;
using io_service_type = boost::asio::io_service;
} // framework
#endif
@@ -1,655 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP
#include "server.hpp"
#include "http_base.hpp"
#include "service_list.hpp"
#include "../common/rfc7231.hpp"
#include "../common/write_msg.hpp"
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/http/dynamic_body.hpp>
#include <boost/beast/http/parser.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/write.hpp>
#include <memory>
#include <utility>
#include <ostream>
namespace framework {
// Base class for a type-erased, queued asynchronous HTTP write operation
//
struct queued_http_write
{
// Destructor must be virtual since we delete a
// derived class through a pointer to the base!
//
virtual ~queued_http_write() = default;
// When invoked, performs the write operation.
virtual void invoke() = 0;
};
/* This implements an object which, when invoked, writes an HTTP
message asynchronously to the stream. These objects are used
to form a queue of outgoing messages for pipelining. The base
class type-erases the message so the queue can hold messsages
of different types.
*/
template<
class Stream,
bool isRequest, class Body, class Fields,
class Handler>
class queued_http_write_impl : public queued_http_write
{
// The stream to write to
Stream& stream_;
// The message to send, which we acquire by move or copy
boost::beast::http::message<isRequest, Body, Fields> msg_;
// The handler to invoke when the send completes.
Handler handler_;
public:
// Constructor.
//
// Ownership of the message is transferred into the object
//
template<class DeducedHandler>
queued_http_write_impl(
Stream& stream,
boost::beast::http::message<isRequest, Body, Fields>&& msg,
DeducedHandler&& handler)
: stream_(stream)
, msg_(std::move(msg))
, handler_(std::forward<DeducedHandler>(handler))
{
}
// Writes the stored message.
//
// The caller must make sure this invocation represents
// a continuation of an asynchronous operation which is
// already in the right context. For example, already
// running on the associated strand.
//
void
invoke() override
{
async_write_msg(
stream_,
std::move(msg_),
std::move(handler_));
}
};
// This helper function creates a queued_http_write
// object and returns it inside a unique_ptr.
//
template<
class Stream,
bool isRequest, class Body, class Fields,
class Handler>
std::unique_ptr<queued_http_write>
make_queued_http_write(
Stream& stream,
boost::beast::http::message<isRequest, Body, Fields>&& msg,
Handler&& handler)
{
return std::unique_ptr<queued_http_write>{
new queued_http_write_impl<
Stream,
isRequest, Body, Fields,
typename std::decay<Handler>::type>{
stream,
std::move(msg),
std::forward<Handler>(handler)}};
}
//------------------------------------------------------------------------------
/** An asynchronous HTTP connection.
This base class implements an HTTP connection object using
asynchronous calls.
It uses the Curiously Recurring Template pattern (CRTP) where
we refer to the derived class in order to access the stream object
to use for reading and writing. This lets the same class be used
for plain and SSL stream objects.
@tparam Services The list of services this connection will support.
*/
template<class Derived, class... Services>
class async_http_con_base : public http_base
{
protected:
// This function lets us access members of the derived class
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The stream to use for logging
std::ostream& log_;
// The services configured for the port
service_list<Services...> const& services_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
// The buffer for performing reads
boost::beast::flat_buffer buffer_;
// The parser for reading the requests
boost::optional<boost::beast::http::request_parser<boost::beast::http::dynamic_body>> parser_;
// This is the queue of outgoing messages
std::vector<std::unique_ptr<queued_http_write>> queue_;
// Indicates if we have a write active.
bool writing_ = false;
// The strand makes sure that our data is
// accessed from only one thread at a time.
//
strand_type strand_;
public:
// Constructor
async_http_con_base(
boost::beast::string_view server_name,
std::ostream& log,
service_list<Services...> const& services,
std::size_t id,
endpoint_type const& ep)
: http_base(server_name)
, log_(log)
, services_(services)
, id_(id)
, ep_(ep)
// The buffer has a limit of 8192, otherwise
// the server is vulnerable to a buffer attack.
//
, buffer_(8192)
, strand_(impl().stream().get_io_service())
{
}
// Called to start the object after the listener accepts
// an incoming connection, when no bytes have been read yet.
//
void
run()
{
// Just call run with an empty buffer
run(boost::asio::null_buffers{});
}
// Called to start the object after the
// listener accepts an incoming connection.
//
template<class ConstBufferSequence>
void
run(ConstBufferSequence const& buffers)
{
// Copy the data into the buffer for performing
// HTTP reads, so that the bytes get used.
//
buffer_.commit(boost::asio::buffer_copy(
buffer_.prepare(boost::asio::buffer_size(buffers)),
buffers));
// Give the derived class a chance to do stuff
//
impl().do_handshake();
}
protected:
void
do_run()
{
do_read_header();
}
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
// Don't log operation aborted since those happen normally.
//
if(ec && ec != boost::asio::error::operation_aborted)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
}
// Perform an asynchronous read for the next request header
//
void
do_read_header()
{
// 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(1024 * 1024));
// Read just the header
boost::beast::http::async_read_header(
impl().stream(),
buffer_,
*parser_,
strand_.wrap(std::bind(
&async_http_con_base::on_read_header,
impl().shared_from_this(),
std::placeholders::_1)));
}
// This lambda is passed to the service list to handle
// the case of sending request objects of varying types.
// In C++14 this is more easily accomplished using a generic
// lambda, but we want C+11 compatibility so we manually
// write the lambda out.
//
struct send_lambda
{
// holds "this"
async_http_con_base& self_;
public:
// capture "this"
explicit
send_lambda(async_http_con_base& self)
: self_(self)
{
}
// sends a message
template<class Body, class Fields>
void
operator()(boost::beast::http::response<Body, Fields>&& res) const
{
self_.do_write(std::move(res));
}
};
// Called when the header has been read in
void
on_read_header(error_code ec)
{
// This happens when the other end closes gracefully
//
if(ec == boost::beast::http::error::end_of_stream)
{
// VFALCO what about the write queue?
return impl().do_shutdown();
}
// On failure we just return, the shared_ptr that is bound
// into the completion will go out of scope and eventually
// this will get destroyed.
//
if(ec)
return fail("on_read", ec);
// The parser holds the request object,
// at this point it only has the header in it.
auto& req = parser_->get();
send_lambda send{*this};
// See if they are specifying Expect: 100-continue
//
if(rfc7231::is_expect_100_continue(req))
{
// They want to know if they should continue,
// so send the appropriate response.
//
send(this->continue_100(req));
}
// Read the rest of the message, if any.
//
boost::beast::http::async_read(
impl().stream(),
buffer_,
*parser_,
strand_.wrap(std::bind(
&async_http_con_base::on_read,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Called when the message is complete
void
on_read(error_code ec)
{
// Shouldn't be getting end_of_stream here;
// that would mean that we got an incomplete
// message, counting as an error.
//
if(ec)
return fail("on_read", ec);
// Grab a reference to the request again
auto& req = parser_->get();
// Create a variable for our send
// lambda since we use it more than once.
//
send_lambda send{*this};
// Give each service a chance to handle the request
//
if(! services_.respond(
std::move(impl().stream()),
ep_,
std::move(req),
send))
{
// No service handled the request,
// send a Bad Request result to the client.
//
send(this->bad_request(req));
}
else
{
// See if the service that handled the
// response took ownership of the stream.
//
if(! impl().stream().lowest_layer().is_open())
{
// They took ownership so just return and
// let this async_http_con_base object get destroyed.
//
return;
}
}
// VFALCO Right now we do unlimited pipelining which
// can lead to unbounded resource consumption.
// A more sophisticated server might only issue
// this read when the queue is below some limit.
//
// Start reading another header
do_read_header();
}
// This function either queues a message or
// starts writing it if no other writes are taking place.
//
template<class Body, class Fields>
void
do_write(boost::beast::http::response<Body, Fields>&& res)
{
// See if a write is in progress
if(! writing_)
{
// An assert or two to keep things sane when
// writing asynchronous code can be very helpful.
BOOST_ASSERT(queue_.empty());
// We're going to be writing so set the flag
writing_ = true;
// And now perform the write
return async_write_msg(
impl().stream(),
std::move(res),
strand_.wrap(std::bind(
&async_http_con_base::on_write,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Queue is not empty, so append this message to the queue.
// It will be sent late when the queue empties.
//
queue_.emplace_back(make_queued_http_write(
impl().stream(),
std::move(res),
strand_.wrap(std::bind(
&async_http_con_base::on_write,
impl().shared_from_this(),
std::placeholders::_1))));
}
// Called when a message finishes writing
void
on_write(error_code ec)
{
// Make sure our state is what we think it is
BOOST_ASSERT(writing_);
// This happens when we send an HTTP message
// whose semantics indicate that the connection
// should be closed afterwards. For example if
// we send a Connection: close.
//
if(ec == boost::beast::http::error::end_of_stream)
return impl().do_shutdown();
// On failure just log and return
if(ec)
return fail("on_write", ec);
// See if the queue is empty
if(queue_.empty())
{
// Queue was empty so clear the flag...
writing_ = false;
// ...and return
return;
}
// Queue was not empty, so invoke the object
// at the head of the queue. This will start
// another wrte.
queue_.front()->invoke();
// Delete the item since we used it
queue_.erase(queue_.begin());
}
};
//------------------------------------------------------------------------------
// This class represents an asynchronous HTTP connection which
// uses a plain TCP/IP socket (no encryption) as the stream.
//
template<class... Services>
class async_http_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<async_http_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<socket_type>
// Constructs last, destroys first
//
, public async_http_con_base<async_http_con<Services...>, Services...>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
async_http_con(
socket_type&& sock,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, async_http_con_base<async_http_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
socket_type&
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend class async_http_con_base<async_http_con<Services...>, Services...>;
// This is called by the base before running the main loop.
//
void
do_handshake()
{
// Run the main loop right away
//
this->do_run();
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown()
{
error_code ec;
stream().shutdown(socket_type::shutdown_both, ec);
// not_connected happens under normal
// circumstances so don't bother reporting it.
//
if(ec && ec != boost::beast::errc::not_connected)
return this->fail("shutdown", ec);
}
};
//------------------------------------------------------------------------------
/* An asynchronous HTTP port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides an asynchronous connection implementation to service
*/
template<class... Services>
class http_async_port
{
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The list of services connections created from this port will support
service_list<Services...> services_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
*/
http_async_port(
server& instance,
std::ostream& log)
: instance_(instance)
, log_(log)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create a plain http connection object
// and transfer ownership of the socket.
//
std::make_shared<async_http_con<Services...>>(
std::move(sock),
"http_async_port",
log_,
services_,
instance_.next_id(),
ep)->run();
}
};
} // framework
#endif
-79
View File
@@ -1,79 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP
#include <boost/beast/core/string.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/string_body.hpp>
#include <memory>
#include <utility>
#include <ostream>
namespace framework {
/* Base class for HTTP PortHandlers
This holds the server name and has some shared
routines for building typical HTTP responses.
*/
class http_base
{
boost::beast::string_view server_name_;
public:
explicit
http_base(boost::beast::string_view server_name)
: server_name_(server_name)
{
}
protected:
// Returns a bad request result response
//
template<class Body, class Fields>
boost::beast::http::response<boost::beast::http::string_body>
bad_request(boost::beast::http::request<Body, Fields> const& req) const
{
boost::beast::http::response<boost::beast::http::string_body> res;
// Match the version to the request
res.version = req.version;
res.result(boost::beast::http::status::bad_request);
res.set(boost::beast::http::field::server, server_name_);
res.set(boost::beast::http::field::content_type, "text/html");
res.body = "Bad request";
res.prepare_payload();
return res;
}
// Returns a 100 Continue result response
//
template<class Body, class Fields>
boost::beast::http::response<boost::beast::http::empty_body>
continue_100(boost::beast::http::request<Body, Fields> const& req) const
{
boost::beast::http::response<boost::beast::http::empty_body> res;
// Match the version to the request
res.version = req.version;
res.result(boost::beast::http::status::continue_);
res.set(boost::beast::http::field::server, server_name_);
return res;
}
};
} // framework
#endif
-479
View File
@@ -1,479 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP
#include "server.hpp"
#include "http_base.hpp"
#include "service_list.hpp"
#include "../common/rfc7231.hpp"
#include "../common/write_msg.hpp"
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/core/handler_ptr.hpp>
#include <boost/beast/http/dynamic_body.hpp>
#include <boost/beast/http/parser.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/write.hpp>
#include <memory>
#include <utility>
#include <ostream>
#include <thread>
namespace framework {
/** A synchronous HTTP connection.
This base class implements an HTTP connection object using
synchronous calls.
It uses the Curiously Recurring Template pattern (CRTP) where
we refer to the derived class in order to access the stream object
to use for reading and writing. This lets the same class be used
for plain and SSL stream objects.
@tparam Services The list of services this connection will support.
*/
template<class Derived, class... Services>
class sync_http_con_base
: public http_base
{
// This function lets us access members of the derived class
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The stream to use for logging
std::ostream& log_;
// The services configured for the port
service_list<Services...> const& services_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
// The buffer for performing reads
boost::beast::flat_buffer buffer_;
public:
/// Constructor
sync_http_con_base(
boost::beast::string_view server_name,
std::ostream& log,
service_list<Services...> const& services,
std::size_t id,
endpoint_type const& ep)
: http_base(server_name)
, log_(log)
, services_(services)
, id_(id)
, ep_(ep)
// The buffer has a limit of 8192, otherwise
// the server is vulnerable to a buffer attack.
//
, buffer_(8192)
{
}
// This is called to start the connection after
// it is accepted.
//
void
run()
{
// Bind a shared pointer into the lambda for the
// thread, so the sync_http_con_base is destroyed after
// the thread function exits.
//
std::thread{
&sync_http_con_base::do_run,
impl().shared_from_this()
}.detach();
}
protected:
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
if(ec)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
}
private:
// This lambda is passed to the service list to handle
// the case of sending request objects of varying types.
// In C++14 this is more easily accomplished using a generic
// lambda, but we want C+11 compatibility so we manually
// write the lambda out.
//
struct send_lambda
{
// holds "this"
sync_http_con_base& self_;
// holds the captured error code
error_code& ec_;
public:
// Constructor
//
// Capture "this" and "ec"
//
send_lambda(sync_http_con_base& self, error_code& ec)
: self_(self)
, ec_(ec)
{
}
// Sends a message
//
// Since this is a synchronous implementation we
// just call the write function and block.
//
template<class Body, class Fields>
void
operator()(
boost::beast::http::response<Body, Fields>&& res) const
{
boost::beast::http::serializer<false, Body, Fields> sr{res};
boost::beast::http::write(self_.impl().stream(), sr, ec_);
}
};
void
do_run()
{
error_code ec;
// Give the derived class a chance to do stuff before we
// enter the main loop. This is for SSL connections really.
//
impl().do_handshake(ec);
if(ec)
return fail("handshake", ec);
// The main connection loop, we alternate between
// reading a request and sending a response. On
// error we log and return, which destroys the thread
// and the stream (thus closing the connection)
//
for(;;)
{
// 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.
//
boost::beast::http::request_parser<boost::beast::http::dynamic_body> parser(
std::piecewise_construct, std::make_tuple(1024* 1024));
// Read the header first
boost::beast::http::read_header(impl().stream(), buffer_, parser, ec);
// This happens when the other end closes gracefully
//
if(ec == boost::beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec && ec != boost::beast::errc::not_connected)
return fail("shutdown", ec);
return;
}
// Any other error and we fail the connection
if(ec)
return fail("read_header", ec);
send_lambda send{*this, ec};
auto& req = parser.get();
// See if they are specifying Expect: 100-continue
//
if(rfc7231::is_expect_100_continue(req))
{
// They want to know if they should continue,
// so send the appropriate response synchronously.
//
send(this->continue_100(req));
// This happens when we send an HTTP message
// whose semantics indicate that the connection
// should be closed afterwards. For example if
// we send a Connection: close.
//
if(ec == boost::beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec && ec != boost::beast::errc::not_connected)
return fail("shutdown", ec);
return;
}
// Have to check the error every time we call the lambda
//
if(ec)
return fail("write", ec);
}
// Read the rest of the message, if any.
//
boost::beast::http::read(impl().stream(), buffer_, parser, ec);
// Shouldn't be getting end_of_stream here;
// that would mean that we got an incomplete
// message, counting as an error.
//
if(ec)
return fail("read", ec);
// Give each service a chance to handle the request
//
if(! services_.respond(
std::move(impl().stream()),
ep_,
std::move(req),
send))
{
// No service handled the request,
// send a Bad Request result to the client.
//
send(this->bad_request(req));
// This happens when we send an HTTP message
// whose semantics indicate that the connection
// should be closed afterwards. For example if
// we send a Connection: close.
//
if(ec == boost::beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec && ec != boost::beast::errc::not_connected)
return fail("shutdown", ec);
return;
}
// Have to check the error every time we call the lambda
//
if(ec)
return fail("write", ec);
}
else
{
// This happens when we send an HTTP message
// whose semantics indicate that the connection
// should be closed afterwards. For example if
// we send a Connection: close.
//
if(ec == boost::beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
if(ec && ec != boost::beast::errc::not_connected)
return fail("shutdown", ec);
return;
}
// Have to check the error every time we call the lambda
//
if(ec)
return fail("write", ec);
// See if the service that handled the
// response took ownership of the stream.
if(! impl().stream().lowest_layer().is_open())
{
// They took ownership so just return and
// let this sync_http_con_base object get destroyed.
return;
}
}
// Theres no pipelining possible in a synchronous server
// because we can't do reads and writes at the same time.
}
}
};
//------------------------------------------------------------------------------
// This class represents a synchronous HTTP connection which
// uses a plain TCP/IP socket (no encryption) as the stream.
//
template<class... Services>
class sync_http_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<sync_http_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<socket_type>
// Constructs last, destroys first
//
, public sync_http_con_base<sync_http_con<Services...>, Services...>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
sync_http_con(
socket_type&& sock,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, sync_http_con_base<sync_http_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
socket_type&
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend class sync_http_con_base<sync_http_con<Services...>, Services...>;
// This is called by the base before running the main loop.
// There's nothing to do for a plain connection.
//
void
do_handshake(error_code& ec)
{
// This is required by the specifications for error_code
//
ec = {};
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown(error_code& ec)
{
stream().shutdown(socket_type::shutdown_both, ec);
}
};
//------------------------------------------------------------------------------
/* A synchronous HTTP port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service
*/
template<class... Services>
class http_sync_port
{
server& instance_;
std::ostream& log_;
service_list<Services...> services_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
*/
http_sync_port(
server& instance,
std::ostream& log)
: instance_(instance)
, log_(log)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param ec Set to the error, if any occurred
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create a plain http connection object
// and transfer ownership of the socket.
//
std::make_shared<sync_http_con<Services...>>(
std::move(sock),
"http_sync_port",
log_,
services_,
instance_.next_id(),
ep)->run();
}
};
} // framework
#endif
-428
View File
@@ -1,428 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP
#include "http_sync_port.hpp"
#include "http_async_port.hpp"
#include "../common/ssl_stream.hpp"
#include <boost/asio/ssl.hpp>
namespace framework {
//------------------------------------------------------------------------------
// This class represents a synchronous HTTP connection which
// uses an OpenSSL socket as the stream.
//
template<class... Services>
class sync_https_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<sync_https_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<ssl_stream<socket_type>>
// Constructs last, destroys first
//
, public sync_http_con_base<sync_https_con<Services...>, Services...>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
sync_https_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<ssl_stream<socket_type>>(std::move(sock), ctx)
, sync_http_con_base<sync_https_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
ssl_stream<socket_type>&
stream()
{
return this->member;
}
private:
friend class sync_http_con_base<sync_https_con<Services...>, Services...>;
// This is called by the base before running the main loop.
//
void
do_handshake(error_code& ec)
{
// Perform the SSL handshake
//
stream().handshake(boost::asio::ssl::stream_base::server, ec);
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown(error_code& ec)
{
// Note that this is an SSL shutdown
//
stream().shutdown(ec);
if(ec)
return this->fail("ssl_shutdown", ec);
}
};
//------------------------------------------------------------------------------
// This class represents an asynchronous HTTP connection which
// uses an OpenSSL socket as the stream.
//
template<class... Services>
class async_https_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<async_https_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<ssl_stream<socket_type>>
// Constructs last, destroys first
//
, public async_http_con_base<async_https_con<Services...>, Services...>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
async_https_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<ssl_stream<socket_type>>(std::move(sock), ctx)
, async_http_con_base<async_https_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
ssl_stream<socket_type>&
stream()
{
return this->member;
}
// Called by the multi-port after reading some
// bytes from the stream and detecting SSL.
//
template<class ConstBufferSequence>
void
handshake(ConstBufferSequence const& buffers)
{
// Copy the caller's bytes into the buffer we
// use for reading HTTP messages, otherwise
// the memory pointed to by buffers will go out
// of scope.
//
this->buffer_.commit(
boost::asio::buffer_copy(
this->buffer_.prepare(boost::asio::buffer_size(buffers)),
buffers));
// Perform SSL handshake. We use the "buffered"
// overload which lets us pass those extra bytes.
//
stream().async_handshake(
boost::asio::ssl::stream_base::server,
buffers,
this->strand_.wrap(
std::bind(
&async_https_con::on_buffered_handshake,
this->shared_from_this(),
std::placeholders::_1,
std::placeholders::_2)));
}
private:
friend class async_http_con_base<async_https_con<Services...>, Services...>;
// Called by the base class before starting the main loop.
//
void
do_handshake()
{
// This is SSL so perform the handshake
//
stream().async_handshake(
boost::asio::ssl::stream_base::server,
this->strand_.wrap(
std::bind(
&async_https_con::on_handshake,
this->shared_from_this(),
std::placeholders::_1)));
}
// Called when the SSL handshake completes
void
on_handshake(error_code ec)
{
if(ec)
return this->fail("on_handshake", ec);
// No error so run the main loop
this->do_run();
}
// Called when the buffered SSL handshake completes
void
on_buffered_handshake(error_code ec, std::size_t bytes_transferred)
{
if(ec)
return this->fail("on_handshake", ec);
// Consume what was read but leave the rest
this->buffer_.consume(bytes_transferred);
// No error so run the main loop
this->do_run();
}
// Called when the end of stream is reached
void
do_shutdown()
{
// This is an SSL shutdown
//
stream().async_shutdown(
this->strand_.wrap(
std::bind(
&async_https_con::on_shutdown,
this->shared_from_this(),
std::placeholders::_1)));
}
// Called when the SSL shutdown completes
void
on_shutdown(error_code ec)
{
if(ec)
return this->fail("on_shutdown", ec);
}
};
//------------------------------------------------------------------------------
/* A synchronous HTTPS port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service
*/
template<class... Services>
class https_sync_port
{
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The list of services connections created from this port will support
service_list<Services...> services_;
// The SSL context containing the server's credentials
boost::asio::ssl::context& ctx_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param ctx The SSL context holding the SSL certificates to use
*/
https_sync_port(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx)
: instance_(instance)
, log_(log)
, ctx_(ctx)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create an HTTPS connection object
// and transfer ownership of the socket.
//
std::make_shared<sync_https_con<Services...>>(
std::move(sock),
ctx_,
"https_sync_port",
log_,
services_,
instance_.next_id(),
ep)->run();
}
};
//------------------------------------------------------------------------------
/* An asynchronous HTTPS port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service
*/
template<class... Services>
class https_async_port
{
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The list of services connections created from this port will support
service_list<Services...> services_;
// The SSL context containing the server's credentials
boost::asio::ssl::context& ctx_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
*/
https_async_port(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx)
: instance_(instance)
, log_(log)
, ctx_(ctx)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create an SSL connection object
// and transfer ownership of the socket.
//
std::make_shared<async_https_con<Services...>>(
std::move(sock),
ctx_,
"https_async_port",
log_,
services_,
instance_.next_id(),
ep)->run();
}
};
} // framework
#endif
-431
View File
@@ -1,431 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#include "server.hpp"
#include "http_async_port.hpp"
#include "http_sync_port.hpp"
#include "ws_async_port.hpp"
#include "ws_sync_port.hpp"
#if BOOST_BEAST_USE_OPENSSL
#include "https_ports.hpp"
#include "multi_port.hpp"
#include "wss_ports.hpp"
#include "ssl_certificate.hpp"
#endif
#include "file_service.hpp"
#include "ws_upgrade_service.hpp"
#include <boost/asio/signal_set.hpp>
#include <iostream>
/// Block until SIGINT or SIGTERM is received.
void
sig_wait()
{
// Create our own io_service for this
boost::asio::io_service ios;
// Get notified on the signals we want
boost::asio::signal_set signals(
ios, SIGINT, SIGTERM);
// Now perform the asynchronous call
signals.async_wait(
[&](boost::system::error_code const&, int)
{
});
// Block the current thread on run(), when the
// signal is received then this call will return.
ios.run();
}
/** Set the options on a WebSocket stream.
This is used by the websocket server port handlers.
It is called every time a new websocket stream is
created, to provide the opportunity to set settings
for the connection.
*/
class set_ws_options
{
boost::beast::websocket::permessage_deflate pmd_;
public:
set_ws_options(boost::beast::websocket::permessage_deflate const& pmd)
: pmd_(pmd)
{
}
template<class NextLayer>
void
operator()(boost::beast::websocket::stream<NextLayer>& ws) const
{
ws.auto_fragment(false);
ws.set_option(pmd_);
ws.read_message_max(64 * 1024 * 1024);
}
};
int
main(
int ac,
char const* av[])
{
using namespace framework;
using namespace boost::beast::http;
// Helper for reporting failures
//
auto const fail =
[&](
std::string const& what,
error_code const& ec)
{
std::cerr <<
av[0] << ": " <<
what << " failed, " <<
ec.message() <<
std::endl;
return EXIT_FAILURE;
};
// Check command line arguments.
if(ac != 5)
{
std::cerr <<
"Usage: " << av[0] <<
" <address> <port> <threads> <root-directory>";
return EXIT_FAILURE;
}
auto const addr = boost::asio::ip::address::from_string(av[1]);
auto const port = static_cast<unsigned short>(std::atoi(av[2]));
auto const threads = static_cast<std::size_t>(std::atoi(av[3]));
auto const root = std::string(av[4]);
// These settings will be applied to all new websocket connections
boost::beast::websocket::permessage_deflate pmd;
pmd.client_enable = true;
pmd.server_enable = true;
pmd.compLevel = 3;
error_code ec;
// Create our server instance with the specified number of threads
server instance{threads};
//--------------------------------------------------------------------------
//
// Synchronous WebSocket HTTP
//
// port + 0 port + 1
//
//--------------------------------------------------------------------------
{
// Create a WebSocket port
//
auto wsp = instance.make_port<ws_sync_port>(
ec,
endpoint_type{addr,static_cast<unsigned short>(port + 0)},
instance,
std::cout,
set_ws_options{pmd});
if(ec)
return fail("ws_sync_port", ec);
// Create an HTTP port
//
auto sp = instance.make_port<http_sync_port<
ws_upgrade_service<ws_sync_port>,
file_service
>>(
ec,
endpoint_type{addr,static_cast<unsigned short>(port + 1)},
instance,
std::cout);
if(ec)
return fail("http_sync_port", ec);
// Init the ws_upgrade_service to
// forward upgrades to the WebSocket port.
//
sp->template init<0>(
ec,
*wsp // The WebSocket port handler
);
if(ec)
return fail("http_sync_port/ws_upgrade_service", ec);
// Init the file_service to point to the root path.
//
sp->template init<1>(
ec,
root, // The root path
"http_sync_port" // The value for the Server field
);
if(ec)
return fail("http_sync_port/file_service", ec);
}
//--------------------------------------------------------------------------
//
// Asynchronous WebSocket HTTP
//
// port + 2 port + 3
//
//--------------------------------------------------------------------------
{
// Create a WebSocket port
//
auto wsp = instance.make_port<ws_async_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 2)},
instance,
std::cout,
set_ws_options{pmd}
);
if(ec)
return fail("ws_async_port", ec);
// Create an HTTP port
//
auto sp = instance.make_port<http_async_port<
ws_upgrade_service<ws_async_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 3)},
instance,
std::cout);
if(ec)
return fail("http_async_port", ec);
// Init the ws_upgrade_service to
// forward upgrades to the WebSocket port.
//
sp->template init<0>(
ec,
*wsp // The websocket port handler
);
if(ec)
return fail("http_async_port/ws_upgrade_service", ec);
// Init the file_service to point to the root path.
//
sp->template init<1>(
ec,
root, // The root path
"http_async_port" // The value for the Server field
);
if(ec)
return fail("http_async_port/file_service", ec);
}
//
// The next section supports encrypted connections and requires
// an installed and configured OpenSSL as part of the build.
//
#if BOOST_BEAST_USE_OPENSSL
ssl_certificate cert;
//--------------------------------------------------------------------------
//
// Synchronous Secure WebSocket HTTPS
//
// port + 4 port + 5
//
//--------------------------------------------------------------------------
{
// Create a WebSocket port
//
auto wsp = instance.make_port<wss_sync_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 4)},
instance,
std::cout,
cert.get(),
set_ws_options{pmd});
if(ec)
return fail("wss_sync_port", ec);
// Create an HTTP port
//
auto sp = instance.make_port<https_sync_port<
ws_upgrade_service<wss_sync_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 5)},
instance,
std::cout,
cert.get());
if(ec)
return fail("https_sync_port", ec);
// Init the ws_upgrade_service to
// forward upgrades to the WebSocket port.
//
sp->template init<0>(
ec,
*wsp // The websocket port handler
);
if(ec)
return fail("http_sync_port/ws_upgrade_service", ec);
// Init the file_service to point to the root path.
//
sp->template init<1>(
ec,
root, // The root path
"http_sync_port" // The value for the Server field
);
if(ec)
return fail("https_sync_port/file_service", ec);
}
//--------------------------------------------------------------------------
//
// Asynchronous Secure WebSocket HTTPS
//
// port + 6 port + 7
//
//--------------------------------------------------------------------------
{
// Create a WebSocket port
//
auto wsp = instance.make_port<wss_async_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 6)},
instance,
std::cout,
cert.get(),
set_ws_options{pmd}
);
if(ec)
return fail("ws_async_port", ec);
// Create an HTTP port
//
auto sp = instance.make_port<https_async_port<
ws_upgrade_service<wss_async_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 7)},
instance,
std::cout,
cert.get());
if(ec)
return fail("https_async_port", ec);
// Init the ws_upgrade_service to
// forward upgrades to the WebSocket port.
//
sp->template init<0>(
ec,
*wsp // The websocket port handler
);
if(ec)
return fail("https_async_port/ws_upgrade_service", ec);
// Init the file_service to point to the root path.
//
sp->template init<1>(
ec,
root, // The root path
"https_async_port" // The value for the Server field
);
if(ec)
return fail("https_async_port/file_service", ec);
}
//--------------------------------------------------------------------------
//
// Multi-Port HTTP, WebSockets,
// HTTPS Secure WebSockets
//
// Asynchronous, all on the same port!
//
// port + 8
//
//--------------------------------------------------------------------------
{
// Create a multi_port
//
auto sp = instance.make_port<multi_port<
ws_upgrade_service<multi_port_base>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 8)},
instance,
std::cout,
cert.get(),
set_ws_options{pmd});
if(ec)
return fail("multi_port", ec);
// Init the ws_upgrade_service to forward requests to the multi_port.
//
sp->template init<0>(
ec,
*sp // The websocket port handler
);
if(ec)
return fail("multi_port/ws_upgrade_service", ec);
// Init the ws_upgrade_service to
// forward upgrades to the Multi port.
//
sp->template init<1>(
ec,
root, // The root path
"multi_port" // The value for the Server field
);
if(ec)
return fail("multi_port/file_service", ec);
}
#endif
sig_wait();
}
-399
View File
@@ -1,399 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP
#include "ws_async_port.hpp"
#include "http_async_port.hpp"
#include "https_ports.hpp"
#include "wss_ports.hpp"
#include "../common/detect_ssl.hpp"
#include <boost/beast/core.hpp>
#include <boost/function.hpp>
namespace framework {
// A connection that detects an opening SSL handshake
//
// If the SSL handshake is detected, then an HTTPS connection object
// is move constructed from this object. Otherwise, this object continues
// as a normal unencrypted HTTP connection. If the underlying port has
// the ws_upgrade_service configured, the connection may be optionally
// be upgraded to WebSocket by the client.
//
template<class... Services>
class multi_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<multi_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<socket_type>
// Constructs last, destroys first
//
, public async_http_con_base<multi_con<Services...>, Services...>
{
// Context to use if we get an SSL handshake
boost::asio::ssl::context& ctx_;
// Holds the data we read during ssl detection
boost::beast::flat_static_buffer<6> buffer_;
public:
// Constructor
//
// Additional arguments are simply forwarded to the base class
//
template<class... Args>
multi_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, async_http_con_base<multi_con<Services...>, Services...>(std::forward<Args>(args)...)
, ctx_(ctx)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
socket_type&
stream()
{
return this->member;
}
// Called by the port to launch the connection in detect mode
void
detect()
{
// The detect function operates asynchronously by reading
// in some data from the stream to figure out if its an SSL
// handshake. When it completes, it informs us of the result
// and also stores the bytes it read in the buffer.
//
async_detect_ssl(
stream(),
buffer_,
this->strand_.wrap(
std::bind(
&multi_con::on_detect,
this->shared_from_this(),
std::placeholders::_1,
std::placeholders::_2)));
}
private:
// Base class needs to be a friend to call our private members
friend class async_http_con_base<multi_con<Services...>, Services...>;
// Called when the handshake detection is complete
//
void
on_detect(
error_code ec,
boost::tribool result)
{
// Report failures if any
if(ec)
return this->fail("on_detect", ec);
// Was an SSL handshake detected?
if(result)
{
// Yes, get the remote endpoint since it is
// needed to construct the new connection.
//
endpoint_type ep = stream().remote_endpoint(ec);
if(ec)
return this->fail("remote_endpoint", ec);
// Now launch our new connection object
//
std::make_shared<async_https_con<Services...>>(
std::move(stream()),
ctx_,
"multi_port",
this->log_,
this->services_,
this->id_,
ep)->handshake(buffer_.data());
// When we return the last shared pointer to this
// object will go away and `*this` will be destroyed.
//
return;
}
// No SSL handshake, so start the HTTP connection normally.
//
// Since we read some bytes from the connection that might
// contain an HTTP request, we pass the buffer holding those
// bytes to the base class so it can use them.
//
this->run(buffer_.data());
}
// This is called by the base before running the main loop.
//
void
do_handshake()
{
// Just run the main loop right away.
//
this->do_run();
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown()
{
// Attempt a clean TCP/IP shutdown
//
error_code ec;
stream().shutdown(
socket_type::shutdown_both,
ec);
// not_connected happens under normal
// circumstances so don't bother reporting it.
//
if(ec && ec != boost::beast::errc::not_connected)
return this->fail("shutdown", ec);
}
};
//------------------------------------------------------------------------------
/* An asynchronous HTTP and WebSocket port handler, plain or SSL
This type meets the requirements of @b PortHandler. It supports a
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service.
The port will automatically detect OpenSSL handshakes and establish
encrypted connections, otherwise will use a plain unencrypted
connection. This all happens through the same port.
In addition this port can process WebSocket upgrade requests by
launching them as a new asynchronous WebSocket connection using
either plain or OpenSSL transport.
This class is split up into two parts, the multi_port_base,
and the multi_port, to avoid a recursive type reference when
we name the type of the ws_upgrade_service.
*/
class multi_port_base
{
protected:
// VFALCO We use boost::function to work around a compiler
// crash with gcc and clang using libstdc++
// The types of the on_stream callback
using on_new_stream_cb1 = boost::function<void(boost::beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 = boost::function<void(boost::beast::websocket::stream<ssl_stream<socket_type>>&)>;
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The context holds the SSL certificates the server uses
boost::asio::ssl::context& ctx_;
// Called for each new websocket stream
on_new_stream_cb1 cb1_;
on_new_stream_cb2 cb2_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param ctx The SSL context holding the SSL certificates to use
@param cb A callback which will be invoked for every new
WebSocket connection. This provides an opportunity to change
the settings on the stream before it is used. The callback
should have this equivalent signature:
@code
template<class NextLayer>
void callback(boost::beast::websocket::stream<NextLayer>&);
@endcode
In C++14 this can be accomplished with a generic lambda. In
C++11 it will be necessary to write out a lambda manually,
with a templated operator().
*/
template<class Callback>
multi_port_base(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx,
Callback const& cb)
: instance_(instance)
, log_(log)
, ctx_(ctx)
, cb1_(cb)
, cb2_(cb)
{
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body>
void
on_upgrade(
socket_type&& sock,
endpoint_type ep,
boost::beast::http::request<Body>&& req)
{
// Create the connection and call the version of
// run that takes the request since we have it already
//
std::make_shared<async_ws_con>(
std::move(sock),
"multi_port",
log_,
instance_.next_id(),
ep,
cb1_
)->run(std::move(req));
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body>
void
on_upgrade(
ssl_stream<socket_type>&& stream,
endpoint_type ep,
boost::beast::http::request<Body>&& req)
{
std::make_shared<async_wss_con>(
std::move(stream),
"multi_port",
log_,
instance_.next_id(),
ep,
cb2_)->run(std::move(req));
}
};
/* An asynchronous HTTP and WebSocket port handler, plain or SSL
This class is the other half of multi_port_base. It gets the
Services... variadic type list and owns the service list.
*/
template<class... Services>
class multi_port : public multi_port_base
{
// The list of services connections created from this port will support
service_list<Services...> services_;
public:
/** Constructor
All arguments are forwarded to the multi_port_base constructor.
*/
template<class... Args>
multi_port(Args&&... args)
: multi_port_base(std::forward<Args>(args)...)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(
socket_type&& sock,
endpoint_type ep)
{
// Create a plain http connection object by transferring
// ownership of the socket, then launch it to perform
// the SSL handshake detection.
//
std::make_shared<multi_con<Services...>>(
std::move(sock),
ctx_,
"multi_port",
log_,
services_,
instance_.next_id(),
ep)->detect();
}
};
} // framework
#endif
-268
View File
@@ -1,268 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP
#define BOOST_BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP
#include "framework.hpp"
#include <boost/optional.hpp>
#include <memory>
#include <string>
#include <stdexcept>
#include <thread>
#include <type_traits>
#include <utility>
namespace framework {
/** A server instance that accepts TCP/IP connections.
This is a general purpose TCP/IP server which contains
zero or more user defined "ports". Each port represents
a listening socket whose behavior is defined by an
instance of the @b PortHandler concept.
To use the server, construct the class and then add the
ports that you want using @ref make_port.
@par Example
@code
// Create a server with 4 threads
//
framework::server si(4);
// Create a port that echoes everything back.
// Bind all available interfaces on port 1000.
//
framework::error_code ec;
si.make_port<echo_port>(
ec,
server::endpoint_type{
server::address_type::from_string("0.0.0.0"), 1000}
);
...
// Close all connections, shut down the server
si.stop();
@endcode
*/
class server
{
io_service_type ios_;
std::vector<std::thread> tv_;
boost::optional<boost::asio::io_service::work> work_;
public:
server(server const&) = delete;
server& operator=(server const&) = delete;
/** Constructor
@param n The number of threads to run on the `io_service`,
which must be greater than zero.
*/
explicit
server(std::size_t n = 1)
: work_(ios_)
{
if(n < 1)
throw std::invalid_argument{"threads < 1"};
tv_.reserve(n);
while(n--)
tv_.emplace_back(
[&]
{
ios_.run();
});
}
/** Destructor
Upon destruction, the `io_service` will be stopped
and all pending completion handlers destroyed.
*/
~server()
{
work_ = boost::none;
ios_.stop();
for(auto& t : tv_)
t.join();
}
/// Return the `io_service` associated with the server
boost::asio::io_service&
get_io_service()
{
return ios_;
}
/** Return a new, small integer unique id
These ids are used to uniquely identify connections
in log output.
*/
std::size_t
next_id()
{
static std::atomic<std::size_t> id_{0};
return ++id_;
}
/** Create a listening port.
@param ec Set to the error, if any occurred.
@param ep The address and port to bind to.
@param args Optional arguments, forwarded to the
port handler's constructor.
@tparam PortHandler The port handler to use for handling
incoming connections on this port. This handler must meet
the requirements of @b PortHandler. A model of PortHandler
is as follows:
@code
struct PortHandler
{
void
on_accept(
endpoint_type ep, // address of the remote endpoint
socket_type&& sock, // the connected socket
);
};
@endcode
*/
template<class PortHandler, class... Args>
std::shared_ptr<PortHandler>
make_port(
error_code& ec,
endpoint_type const& ep,
Args&&... args);
};
//------------------------------------------------------------------------------
/* This implementation class wraps the PortHandler and
manages the listening socket. Upon an incoming connection
it transfers ownership of the socket to the PortHandler.
*/
template<class PortHandler>
class port
: public std::enable_shared_from_this<
port<PortHandler>>
{
server& instance_;
PortHandler handler_;
endpoint_type ep_;
strand_type strand_;
acceptor_type acceptor_;
socket_type sock_;
public:
// Constructor
//
// args are forwarded to the PortHandler
//
template<class... Args>
explicit
port(server& instance, Args&&... args)
: instance_(instance)
, handler_(std::forward<Args>(args)...)
, strand_(instance.get_io_service())
, acceptor_(instance.get_io_service())
, sock_(instance.get_io_service())
{
}
// Return the PortHandler wrapped in a shared_ptr
//
std::shared_ptr<PortHandler>
handler()
{
// This uses a feature of std::shared_ptr invented by
// Peter Dimov where the managed object piggy backs off
// the reference count of another object containing it.
//
return std::shared_ptr<PortHandler>(
this->shared_from_this(), &handler_);
}
// Open the listening socket
//
void
open(endpoint_type const& ep, error_code& ec)
{
acceptor_.open(ep.protocol(), ec);
if(ec)
return;
acceptor_.set_option(
boost::asio::socket_base::reuse_address{true});
acceptor_.bind(ep, ec);
if(ec)
return;
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
return;
acceptor_.async_accept(sock_, ep_,
std::bind(&port::on_accept, this->shared_from_this(),
std::placeholders::_1));
}
private:
// Called when an incoming connection is accepted
//
void
on_accept(error_code ec)
{
if(! acceptor_.is_open())
return;
if(ec == boost::asio::error::operation_aborted)
return;
if(! ec)
{
// Transfer ownership of the socket to the PortHandler
//
handler_.on_accept(std::move(sock_), ep_);
}
acceptor_.async_accept(sock_, ep_,
std::bind(&port::on_accept, this->shared_from_this(),
std::placeholders::_1));
}
};
//------------------------------------------------------------------------------
template<class PortHandler, class... Args>
std::shared_ptr<PortHandler>
server::
make_port(
error_code& ec,
endpoint_type const& ep,
Args&&... args)
{
auto sp = std::make_shared<port<PortHandler>>(
*this, std::forward<Args>(args)...);
sp->open(ep, ec);
if(ec)
return nullptr;
return sp->handler();
}
} // framework
#endif
-194
View File
@@ -1,194 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP
#include "framework.hpp"
#include <boost/beast/http/message.hpp>
#include <boost/optional.hpp>
#include <utility>
namespace framework {
/** A list of HTTP services which may process requests.
When a service is invoked, it is provided with the stream and
endpoint metadata in addtion to an HTTP request. The service
decides whether or not the process the request, returning
`true` if the request is processed or `false` if it does not
process the request.
@see file_service, ws_upgrade_service
*/
template<class... Services>
class service_list
{
// This helper is for tag-dispatching tuple index
template<std::size_t I>
using C = std::integral_constant<std::size_t, I>;
// Each service is wrapped in a boost::optional so we
// can construct them one by one later, instead of
// having to construct them all at once.
//
std::tuple<boost::optional<Services>...> list_;
public:
/// Constructor
service_list() = default;
/// Constructor
service_list(service_list&&) = default;
/// Constructor
service_list(service_list const&) = default;
/** Initialize a service.
Every service in the list must be initialized exactly once
before the service list is invoked.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
// First, construct the service inside the optional
std::get<Index>(list_).emplace(std::forward<Args>(args)...);
// Now allow the service to finish the initialization
std::get<Index>(list_)->init(ec);
}
/** Handle a request.
This function attempts to process the given HTTP request by
invoking each service one at a time starting with the first
service in the list. When a service indicates that it handles
the request, by returning `true`, the function stops and
returns the value `true`. Otherwise, if no service handles
the request then the function returns the value `false`.
@param stream The stream belonging to the connection. A service
which handles the request may optionally take ownership of the
stream.
@param ep The remote endpoint of the connection corresponding
to the stream.
@param req The request message to attempt handling. A service
which handles the request may optionally take ownership of the
message.
@param send The function to invoke with the response. The function
should have this equivalent signature:
@code
template<class Body>
void
send(response<Body>&&);
@endcode
In C++14 this can be expressed using a generic lambda. In
C++11 it will require a template member function of an invocable
object.
@return `true` if the request was handled by a service.
*/
template<
class Stream,
class Body,
class Send>
bool
respond(
Stream&& stream,
endpoint_type const& ep,
boost::beast::http::request<Body>&& req,
Send const& send) const
{
return try_respond(
std::move(stream),
ep,
std::move(req),
send, C<0>{});
}
private:
/* The implementation of `try_respond` is implemented using
tail recursion which can usually be optimized away to
something resembling a switch statement.
*/
template<
class Stream,
class Body,
class Send>
bool
try_respond(
Stream&&,
endpoint_type const&,
boost::beast::http::request<Body>&&,
Send const&,
C<sizeof...(Services)> const&) const
{
// This function breaks the recursion for the case where
// where the Index is one past the last type in the list.
//
return false;
}
// Invoke the I-th type in the type list
//
template<
class Stream,
class Body,
class Send,
std::size_t I>
bool
try_respond(
Stream&& stream,
endpoint_type const& ep,
boost::beast::http::request<Body>&& req,
Send const& send,
C<I> const&) const
{
// If the I-th service handles the request then return
//
if(std::get<I>(list_)->respond(
std::move(stream),
ep,
std::move(req),
send))
return true;
// Try the I+1th service. If I==sizeof...(Services)
// then we call the other overload and return false.
//
return try_respond(
std::move(stream),
ep,
std::move(req),
send,
C<I+1>{});
}
};
} // framework
#endif
-376
View File
@@ -1,376 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP
#include "server.hpp"
#include <boost/beast/core/multi_buffer.hpp>
#include <boost/beast/websocket/stream.hpp>
#include <boost/function.hpp>
#include <memory>
#include <ostream>
namespace framework {
// This object holds the state of the connection
// including, most importantly, the socket or stream.
//
//
template<class Derived>
class async_ws_con_base
{
// This function lets us access members of the derived class
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The string used to set the Server http field
std::string server_name_;
// The stream to use for logging
std::ostream& log_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
// This is used to hold the message data
boost::beast::multi_buffer buffer_;
protected:
// The strand makes sure that our data is
// accessed from only one thread at a time.
//
strand_type strand_;
public:
// Constructor
template<class Callback>
async_ws_con_base(
boost::beast::string_view server_name,
std::ostream& log,
std::size_t id,
endpoint_type const& ep,
Callback const& cb)
: server_name_(server_name)
, log_(log)
, id_(id)
, ep_(ep)
// Limit of 1MB on messages
, buffer_(1024 * 1024)
, strand_(impl().stream().get_io_service())
{
cb(impl().stream());
}
// Run the connection
//
void
run()
{
impl().do_handshake();
}
// Run the connection.
//
// This overload handles the case where we
// already have the WebSocket Upgrade request.
//
template<class Body>
void
run(boost::beast::http::request<Body> const& req)
{
// Call the overload of accept() which takes
// the request by parameter, instead of reading
// it from the network.
//
impl().stream().async_accept_ex(req,
[&](boost::beast::websocket::response_type& res)
{
res.set(boost::beast::http::field::server, server_name_);
},
strand_.wrap(std::bind(
&async_ws_con_base::on_accept,
impl().shared_from_this(),
std::placeholders::_1)));
}
protected:
// Performs the WebSocket handshake
void
do_accept()
{
// Read the WebSocket upgrade request and attempt
// to send back the response.
//
impl().stream().async_accept_ex(
[&](boost::beast::websocket::response_type& res)
{
res.set(boost::beast::http::field::server, server_name_);
},
strand_.wrap(std::bind(
&async_ws_con_base::on_accept,
impl().shared_from_this(),
std::placeholders::_1)));
}
// This helper reports failures
//
void
fail(std::string what, error_code ec)
{
if(ec != boost::beast::websocket::error::closed)
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
private:
// Called when accept_ex completes
//
void
on_accept(error_code ec)
{
if(ec)
return fail("async_accept", ec);
do_read();
}
// Read the next WebSocket message
//
void
do_read()
{
impl().stream().async_read(
buffer_,
strand_.wrap(std::bind(
&async_ws_con_base::on_read,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Called when the message read completes
//
void
on_read(error_code ec)
{
if(ec)
return fail("on_read", ec);
// Set the outgoing message type. We will use
// the same setting as the message we just read.
//
impl().stream().binary(impl().stream().got_binary());
// Now echo back the message
//
impl().stream().async_write(
buffer_.data(),
strand_.wrap(std::bind(
&async_ws_con_base::on_write,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Called when the message write completes
//
void
on_write(error_code ec)
{
if(ec)
return fail("on_write", ec);
// Empty out the contents of the message buffer
// to prepare it for the next call to read.
//
buffer_.consume(buffer_.size());
// Now read another message
//
do_read();
}
};
//------------------------------------------------------------------------------
// This class represents an asynchronous WebSocket connection
// which uses a plain TCP/IP socket (no encryption) as the stream.
//
class async_ws_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<async_ws_con>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<boost::beast::websocket::stream<socket_type>>
// Constructs last, destroys first
//
, public async_ws_con_base<async_ws_con>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
explicit
async_ws_con(
socket_type&& sock,
Args&&... args)
: base_from_member<boost::beast::websocket::stream<socket_type>>(std::move(sock))
, async_ws_con_base<async_ws_con>(std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
boost::beast::websocket::stream<socket_type>&
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend async_ws_con_base<async_ws_con>;
void
do_handshake()
{
do_accept();
}
};
//------------------------------------------------------------------------------
/** An asynchronous WebSocket @b PortHandler which implements echo.
This is a port handler which accepts WebSocket upgrade HTTP
requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
class ws_async_port
{
// The type of the on_new_stream callback
//
using on_new_stream_cb =
boost::function<void(boost::beast::websocket::stream<socket_type>&)>;
server& instance_;
std::ostream& log_;
on_new_stream_cb cb_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param cb A callback which will be invoked for every new
WebSocket connection. This provides an opportunity to change
the settings on the stream before it is used. The callback
should have this equivalent signature:
@code
template<class NextLayer>
void callback(boost::beast::websocket::stream<NextLayer>&);
@endcode
In C++14 this can be accomplished with a generic lambda. In
C++11 it will be necessary to write out a lambda manually,
with a templated operator().
*/
template<class Callback>
ws_async_port(
server& instance,
std::ostream& log,
Callback const& cb)
: instance_(instance)
, log_(log)
, cb_(cb)
{
}
/** Accept a TCP/IP connection.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(
socket_type&& sock,
endpoint_type ep)
{
std::make_shared<async_ws_con>(
std::move(sock),
"ws_async_port",
log_,
instance_.next_id(),
ep,
cb_)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body>
void
on_upgrade(
socket_type&& sock,
endpoint_type ep,
boost::beast::http::request<Body>&& req)
{
std::make_shared<async_ws_con>(
std::move(sock),
"ws_async_port",
log_,
instance_.next_id(),
ep,
cb_)->run(std::move(req));
}
};
} // framework
#endif
-434
View File
@@ -1,434 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP
#include "server.hpp"
#include <boost/beast/core/multi_buffer.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/function.hpp>
#include <memory>
#include <ostream>
#include <thread>
namespace framework {
/** A synchronous WebSocket connection.
This base class implements a WebSocket connection object using
synchronous calls over an unencrypted connection.
It uses the Curiously Recurring Template pattern (CRTP) where
we refer to the derived class in order to access the stream object
to use for reading and writing. This lets the same class be used
for plain and SSL stream objects.
*/
template<class Derived>
class sync_ws_con_base
{
// This function lets us access members of the derived class
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The string used to set the Server http field
std::string server_name_;
// The stream to use for logging
std::ostream& log_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class Callback>
sync_ws_con_base(
boost::beast::string_view server_name,
std::ostream& log,
std::size_t id,
endpoint_type const& ep,
Callback const& cb)
: server_name_(server_name)
, log_(log)
, id_(id)
, ep_(ep)
{
cb(impl().stream());
}
// Run the connection. This is called for the case
// where we have not received the upgrade request yet.
//
void
run()
{
// We run the do_run function in its own thread,
// and bind a shared pointer to the connection object
// into the function. The last reference to the shared
// pointer will go away when the thread exits, thus
// destroying the connection object.
//
std::thread{
&sync_ws_con_base::do_accept,
impl().shared_from_this()
}.detach();
}
// Run the connection from an already-received Upgrade request.
//
template<class Body>
void
run(boost::beast::http::request<Body>&& req)
{
BOOST_ASSERT(boost::beast::websocket::is_upgrade(req));
// We need to transfer ownership of the request object into
// the lambda, but there's no C++14 lambda capture
// so we have to write it out by manually specifying the lambda.
//
std::thread{
lambda<Body>{
impl().shared_from_this(),
std::move(req)
}}.detach();
}
protected:
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
// Don't report the "closed" error since that
// happens under normal circumstances.
//
if(ec && ec != boost::beast::websocket::error::closed)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
log_.flush();
}
}
private:
// This function performs the WebSocket handshake
// and runs the main loop upon success.
void
do_accept()
{
error_code ec;
// Give the derived class a chance to do stuff before we
// enter the main loop. This is for SSL connections really.
//
impl().do_handshake(ec);
if(ec)
return fail("handshake", ec);
// Read the WebSocket upgrade request and attempt
// to send back the response.
//
impl().stream().accept_ex(
[&](boost::beast::websocket::response_type& res)
{
res.insert(boost::beast::http::field::server, server_name_);
},
ec);
if(ec)
return fail("accept", ec);
// Run the connection
//
do_run();
}
// This is the lambda used when launching a connection from
// an already-received request. In C++14 we could simply use
// a lambda capture but this example requires only C++11 so
// we write out the lambda ourselves. This is similar to what
// the compiler would generate anyway.
//
template<class Body>
class lambda
{
std::shared_ptr<sync_ws_con_base> self_;
boost::beast::http::request<Body> req_;
public:
// Constructor
//
// This is the equivalent of the capture section of the lambda.
//
lambda(
std::shared_ptr<sync_ws_con_base> self,
boost::beast::http::request<Body>&& req)
: self_(std::move(self))
, req_(std::move(req))
{
BOOST_ASSERT(boost::beast::websocket::is_upgrade(req_));
}
// Invoke the lambda
//
void
operator()()
{
BOOST_ASSERT(boost::beast::websocket::is_upgrade(req_));
error_code ec;
{
// Move the message to the stack so we can get
// rid of resources, otherwise it will linger
// for the lifetime of the connection.
//
auto req = std::move(req_);
// Call the overload of accept() which takes
// the request by parameter, instead of reading
// it from the network.
//
self_->impl().stream().accept_ex(req,
[&](boost::beast::websocket::response_type& res)
{
res.insert(boost::beast::http::field::server, self_->server_name_);
},
ec);
}
if(ec)
return self_->fail("accept", ec);
self_->do_run();
}
};
void
do_run()
{
error_code ec;
// Loop, reading messages and echoing them back.
//
for(;;)
{
// This buffer holds the message. We place a one
// megabyte limit on the size to prevent abuse.
//
boost::beast::multi_buffer buffer{1024*1024};
// Read the message
//
impl().stream().read(buffer, ec);
if(ec)
return fail("read", ec);
// Set the outgoing message type. We will use
// the same setting as the message we just read.
//
impl().stream().binary(impl().stream().got_binary());
// Now echo back the message
//
impl().stream().write(buffer.data(), ec);
if(ec)
return fail("write", ec);
}
}
};
//------------------------------------------------------------------------------
// This class represents a synchronous WebSocket connection
// which uses a plain TCP/IP socket (no encryption) as the stream.
//
class sync_ws_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<sync_ws_con>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<boost::beast::websocket::stream<socket_type>>
// Constructs last, destroys first
//
, public sync_ws_con_base<sync_ws_con>
{
public:
// Construct the plain connection.
//
template<class... Args>
explicit
sync_ws_con(
socket_type&& sock,
Args&&... args)
: base_from_member<boost::beast::websocket::stream<socket_type>>(std::move(sock))
, sync_ws_con_base<sync_ws_con>(std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
boost::beast::websocket::stream<socket_type>&
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend class sync_ws_con_base<sync_ws_con>;
// This is called by the base before running the main loop.
// There's nothing to do for a plain connection.
//
void
do_handshake(error_code& ec)
{
// This is required by the specifications for error_code
//
ec = {};
}
};
//------------------------------------------------------------------------------
/** A synchronous WebSocket @b PortHandler which implements echo.
This is a port handler which accepts WebSocket upgrade HTTP
requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
class ws_sync_port
{
// The type of the on_new_stream callback
//
using on_new_stream_cb =
boost::function<void(boost::beast::websocket::stream<socket_type>&)>;
server& instance_;
std::ostream& log_;
on_new_stream_cb cb_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param cb A callback which will be invoked for every new
WebSocket connection. This provides an opportunity to change
the settings on the stream before it is used. The callback
should have this equivalent signature:
@code
template<class NextLayer>
void callback(boost::beast::websocket::stream<NextLayer>&);
@endcode
In C++14 this can be accomplished with a generic lambda. In
C++11 it will be necessary to write out a lambda manually,
with a templated operator().
*/
template<class Callback>
ws_sync_port(
server& instance,
std::ostream& log,
Callback const& cb)
: instance_(instance)
, log_(log)
, cb_(cb)
{
}
/** Accept a TCP/IP connection.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create our connection object and run it
//
std::make_shared<sync_ws_con>(
std::move(sock),
"ws_sync_port",
log_,
instance_.next_id(),
ep,
cb_)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body>
void
on_upgrade(
socket_type&& sock,
endpoint_type ep,
boost::beast::http::request<Body>&& req)
{
// Create the connection object and run it,
// transferring ownership of the ugprade request.
//
std::make_shared<sync_ws_con>(
std::move(sock),
"ws_sync_port",
log_,
instance_.next_id(),
ep,
cb_)->run(std::move(req));
}
};
} // framework
#endif
@@ -1,103 +0,0 @@
//
// Copyright (c) 2016-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)
//
// Official repository: https://github.com/boostorg/beast
//
#ifndef BOOST_BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP
#define BOOST_BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP
#include "framework.hpp"
#include <boost/beast/http/message.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <memory>
namespace framework {
/** An HTTP service which transfers WebSocket upgrade request to another port handler.
@tparam PortHandler The type of port handler. The service will
handle WebSocket Upgrade requests by transferring ownership
of the stream and request to a port handler of this type.
*/
template<class PortHandler>
class ws_upgrade_service
{
PortHandler& handler_;
public:
/** Constructor
@param handler A shared pointer to the @b PortHandler to
handle WebSocket upgrade requests.
*/
explicit
ws_upgrade_service(PortHandler& handler)
: handler_(handler)
{
}
/** Initialize the service.
This provides an opportunity for the service to perform
initialization which may fail, while reporting an error
code instead of throwing an exception from the constructor.
*/
void
init(error_code& ec)
{
// This is required by the error_code specification
//
ec = {};
}
/** Handle a WebSocket Upgrade request.
If the request is an upgrade request, ownership of the
stream and request will be transferred to the corresponding
WebSocket port handler.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint associated with the stream.
@req The request to check.
*/
template<
class Stream,
class Body,
class Send>
bool
respond(
Stream&& stream,
endpoint_type const& ep,
boost::beast::http::request<Body>&& req,
Send const&) const
{
// If its not an upgrade request, return `false`
// to indicate that we are not handling it.
//
if(! boost::beast::websocket::is_upgrade(req))
return false;
// Its an ugprade request, so transfer ownership
// of the stream and request to the port handler.
//
handler_.on_upgrade(
std::move(stream),
ep,
std::move(req));
// Tell the service list that we handled the request.
//
return true;
}
};
} // framework
#endif

Some files were not shown because too many files have changed in this diff Show More