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-04 19:55:28 -07:00
parent 20a8f7d75b
commit dd71b0f94d
187 changed files with 11759 additions and 7098 deletions

View File

@ -1,3 +1,9 @@
Version 101:
* Refactor all examples
--------------------------------------------------------------------------------
Version 100:
* Fix doc convenience includes

View File

@ -97,7 +97,9 @@ INC_DIR="$BOOST_ROOT/boost/beast"
function build_bjam ()
{
if [[ $VARIANT == "coverage" ]]; then
if [[ $VARIANT == "coverage" ]] || \
[[ $VARIANT == "valgrind" ]] || \
[[ $VARIANT == "ubasan" ]]; then
bjam \
libs/beast/test/beast/core//fat-tests \
libs/beast/test/beast/http//fat-tests \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -85,8 +85,8 @@
[import ../../example/common/detect_ssl.hpp]
[import ../../example/doc/http_examples.hpp]
[import ../../example/echo-op/echo_op.cpp]
[import ../../example/http-client/http_client.cpp]
[import ../../example/websocket-client/websocket_client.cpp]
[import ../../example/http/client/sync/http_client_sync.cpp]
[import ../../example/websocket/client/sync/websocket_client_sync.cpp]
[import ../../include/boost/beast/http/basic_file_body.hpp]

View File

@ -14,13 +14,11 @@ These complete programs are intended to quickly impress upon readers
the flavor of the library. Source code and build scripts for them are
located in the example/ directory.
[section HTTP Client]
Use HTTP to make a GET request to a website and print the response:
File: [repo_file example/http-client/http_client.cpp]
File: [repo_file example/http/client/sync/http_client_sync.cpp]
[example_http_client]
@ -30,7 +28,7 @@ File: [repo_file example/http-client/http_client.cpp]
Establish a WebSocket connection, send a message and receive the reply:
File: [repo_file example/websocket-client/websocket_client.cpp]
File: [repo_file example/websocket/client/sync/websocket_client_sync.cpp]
[example_websocket_client]
@ -44,85 +42,196 @@ File: [repo_file example/websocket-client/websocket_client.cpp]
[block'''<?dbhtml stop-chunking?>''']
Source code and build scripts for these programs are located
in the example/ directory.
in the [repo_file example] directory.
[section HTTP Crawl]
[template example_src[path name] '''<ulink url="../../'''[path]'''">'''[name]'''</ulink>''']
This example retrieves the page at each of the most popular domains
as measured by Alexa.
[heading Clients]
* [repo_file example/http-crawl/http_crawl.cpp]
These HTTP clients submit a GET request to a server specified on the command
line, and prints the resulting response. The crawl client asynchronously
fetches the document root of the 10,000 top ranked domains, this may be
used to evaluate robustness.
[endsect]
[table
[[Description] [Source File] [Source File (using SSL)]]
[
[HTTP, synchronous]
[[example_src example/http/client/sync/http_client_sync.cpp http_client_sync.cpp]]
[[example_src example/http/client/sync-ssl/http_client_sync_ssl.cpp http_client_sync_ssl.cpp]]
][
[HTTP, asynchronous]
[[example_src example/http/client/async/http_client_async.cpp http_client_async.cpp]]
[[example_src example/http/client/async-ssl/http_client_async_ssl.cpp http_client_async_ssl.cpp]]
][
[HTTP, coroutine]
[[example_src example/http/client/coro/http_client_coro.cpp http_client_coro.cpp]]
[[example_src example/http/client/coro-ssl/http_client_coro_ssl.cpp http_client_coro_ssl.cpp]]
][
[HTTP crawl (asynchronous)]
[[example_src example/http/client/crawl/http_crawl.cpp http_crawl.cpp]]
[]
]]
These WebSocket clients connect to a
server and send a message, then receive a message and print the response
before disconnecting.
[table
[[Description] [Source File] [Source File (using SSL)]]
[
[WebSocket, synchronous]
[[example_src example/websocket/client/sync/websocket_client_sync.cpp websocket_client_sync.cpp]]
[[example_src example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp websocket_client_sync_ssl.cpp]]
][
[WebSocket, asynchronous]
[[example_src example/websocket/client/async/websocket_client_async.cpp websocket_client_async.cpp]]
[[example_src example/websocket/client/async-ssl/websocket_client_async_ssl.cpp websocket_client_async_ssl.cpp]]
][
[WebSocket, coroutine]
[[example_src example/websocket/client/coro/websocket_client_coro.cpp websocket_client_coro.cpp]]
[[example_src example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp websocket_client_coro_ssl.cpp]]
]]
[section HTTP Client (with SSL)]
[heading Servers]
This example demonstrates sending and receiving HTTP messages
over a TLS connection. Requires OpenSSL to build.
These HTTP servers deliver files from a root directory specified on the
command line.
* [repo_file example/http-client-ssl/http_client_ssl.cpp]
[table
[[Description] [Source File] [Source File (using SSL)]]
[
[HTTP, synchronous]
[[example_src example/http/server/sync/http_server_sync.cpp http_server_sync.cpp]]
[[example_src example/http/server/sync-ssl/http_server_sync_ssl.cpp http_server_sync_ssl.cpp]]
][
[HTTP, asynchronous]
[[example_src example/http/server/async/http_server_async.cpp http_server_async.cpp]]
[[example_src example/http/server/async-ssl/http_server_async_ssl.cpp http_server_async_ssl.cpp]]
][
[HTTP, coroutine]
[[example_src example/http/server/coro/http_server_coro.cpp http_server_coro.cpp]]
[[example_src example/http/server/coro-ssl/http_server_coro_ssl.cpp http_server_coro_ssl.cpp]]
][
[HTTP, stackless coroutine]
[[example_src example/http/server/stackless/http_server_stackless.cpp http_server_stackless.cpp]]
[[example_src example/http/server/stackless-ssl/http_server_stackless_ssl.cpp http_server_stackless_ssl.cpp]]
][
[HTTP, fast (optimized for speed)]
[[example_src example/http/server/fast/http_server_fast.cpp http_server_fast.cpp]]
[]
][
[HTTP, small (optimized for space)]
[[example_src example/http/server/small/http_server_small.cpp http_server_small.cpp]]
[]
][
[HTTP, flex (plain + SSL)]
[]
[[example_src example/http/server/flex/http_server_flex.cpp http_server_flex.cpp]]
]]
[endsect]
These WebSocket servers echo back any message received, keeping the
session open until the client disconnects.
[table
[[Description] [Source File] [Source File (using SSL)]]
[
[WebSocket, synchronous]
[[example_src example/websocket/server/sync/websocket_server_sync.cpp websocket_server_sync.cpp]]
[[example_src example/websocket/server/sync-ssl/websocket_server_sync_ssl.cpp websocket_server_sync_ssl.cpp]]
][
[WebSocket, asynchronous]
[[example_src example/websocket/server/async/websocket_server_async.cpp websocket_server_async.cpp]]
[[example_src example/websocket/server/async-ssl/websocket_server_async_ssl.cpp websocket_server_async_ssl.cpp]]
][
[WebSocket, coroutine]
[[example_src example/websocket/server/coro/websocket_server_coro.cpp websocket_server_coro.cpp]]
[[example_src example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp websocket_server_coro_ssl.cpp]]
][
[WebSocket, stackless coroutine]
[[example_src example/websocket/server/stackless/websocket_server_stackless.cpp websocket_server_stackless.cpp]]
[[example_src example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp websocket_server_stackless_ssl.cpp]]
][
[WebSocket, fast (suited for benchmarks)]
[[example_src example/websocket/server/fast/websocket_server_fast.cpp websocket_server_fast.cpp]]
[]
]]
[heading Advanced Servers]
[section HTTP Server (Fast)]
These servers offer both HTTP and WebSocket services on the same port,
and illustrate the implementation of advanced features.
This example implements a very simple HTTP server with
some optimizations suitable for calculating benchmarks.
[table
[[Description] [Features] [Source File]]
[
[Advanced]
[[itemized_list
[HTTP pipelining]
[Asynchronous timeouts]
[Dual protocols: HTTP and WebSocket]]]
[[example_src example/advanced/server/advanced_server.cpp advanced_server.cpp]]
][
[Advanced, flex (plain + SSL)]
[[itemized_list
[HTTP pipelining]
[Asynchronous timeouts]
[Dual protocols: HTTP and WebSocket]
[Flexible ports; plain and SSL on the same port]]]
[[example_src example/advanced/server-flex/advanced_server_flex.cpp advanced_server_flex.cpp]]
]]
* [repo_file example/http-server-fast/fields_alloc.hpp]
* [repo_file example/http-server-fast/http_server_fast.cpp]
[heading Common Files]
[endsect]
Some of the examples use one or more shared header files, they are
listed here along with a description of their use:
[section HTTP Server (Small)]
This example implements a very simple HTTP server
suitable as a starting point on an embedded device.
* [repo_file example/http-server-small/http_server_small.cpp]
[endsect]
[section HTTP Server (Threaded)]
This example implements a very simple HTTP server using
synchronous interfaces and using one thread per connection:
* [repo_file example/http-server-threaded/http_server_threaded.cpp]
[endsect]
[section WebSocket Client (with SSL)]
Establish a WebSocket connection over an encrypted TLS connection,
send a message and receive the reply. Requires OpenSSL to build.
* [repo_file example/websocket-client-ssl/websocket_client_ssl.cpp]
[endsect]
[section WebSocket Server (Asynchronous)]
This program implements a WebSocket echo server using asynchronous
interfaces and a configurable number of threads.
* [repo_file example/websocket-server-async/websocket_server_async.cpp]
[endsect]
[table
[[Source File] [Description]]
[
[[repo_file example/common/detect_ssl.hpp]]
[
This contains the detect SSL algorithm including the
synchronous and asynchronous initiating functions, described
in the documentation. It is used by the "flex" servers which
support both plain and SSL sessions on the same port.
]
][
[[repo_file example/common/root_certificates.hpp]]
[
This contains the root SSL certificates used in the SSL client
examples. These certificates are used to verify the signatures
of SSL certificates presented by remote servers. They represent
a subset of the public keys usually installed as part of the
operating system or browser, so they may not identify every
possible server.
]
][
[[repo_file example/common/server_certificate.hpp]]
[
This file contains a self-signed SSL certificate used by the
SSL server examples. It has not been validated by a Certificate
Authority ("CA") so connecting to an example HTTP server using
a browser may generate security warnings.
]
][
[[repo_file example/common/ssl_stream.hpp]]
[
The `ssl_stream` is a replacement for `boost::asio::ssl::stream`
which supports construction from a moved-froms ocket and is also
itself move constructible.
]
][
[[repo_file example/common/write_msg.hpp]]
[
The function `async_write_msg` takes ownership of an HTTP message
using the move constructor, and sends it asynchronously. It is
used as a convenience for managing the lifetime of temporary
objects, as well as for implementing HTTP pipelining.
]
]]
@ -143,7 +252,8 @@ in your program without modification
This program shows how to use Beast's network foundations to build a
composable asynchronous initiation function with associated composed
operation implementation. This is a complete, runnable version of
the example described in the Core Foundations document section.
the example described in
[link beast.using_io.writing_composed_operations.echo Writing Composed Operations: Echo].
* [repo_file example/echo-op/echo_op.cpp]
@ -151,46 +261,4 @@ the example described in the Core Foundations document section.
[section Common Code]
This code is reused between some of the examples. The header files
stand alone can be directly included in your projects.
* [repo_file example/common/detect_ssl.hpp]
* [repo_file example/common/helpers.hpp]
* [repo_file example/common/mime_types.hpp]
* [repo_file example/common/rfc7231.hpp]
* [repo_file example/common/ssl_stream.hpp]
* [repo_file example/common/write_msg.hpp]
[endsect]
[section Server Framework]
This is a complete program and framework of classes implementing
a general purpose server that users may copy to use as the basis
for writing their own servers. It serves both HTTP and WebSocket.
* [repo_file example/server-framework/file_service.hpp]
* [repo_file example/server-framework/framework.hpp]
* [repo_file example/server-framework/http_async_port.hpp]
* [repo_file example/server-framework/http_base.hpp]
* [repo_file example/server-framework/http_sync_port.hpp]
* [repo_file example/server-framework/https_ports.hpp]
* [repo_file example/server-framework/main.cpp]
* [repo_file example/server-framework/multi_port.hpp]
* [repo_file example/server-framework/server.hpp]
* [repo_file example/server-framework/service_list.hpp]
* [repo_file example/server-framework/ssl_certificate.hpp]
* [repo_file example/server-framework/ws_async_port.hpp]
* [repo_file example/server-framework/ws_sync_port.hpp]
* [repo_file example/server-framework/ws_upgrade_service.hpp]
* [repo_file example/server-framework/wss_ports.hpp]
[endsect]
[endsect]

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)

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 ;

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
example/advanced/Jamfile Normal file
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 ;

View File

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

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

View File

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

View File

@ -7,8 +7,8 @@
# Official repository: https://github.com/boostorg/beast
#
exe server-framework :
main.cpp
exe advanced-server :
advanced_server.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no

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;
}

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

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

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

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

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

View File

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

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)

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
;

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;
}

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;
}
//]

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;
}
}

View File

@ -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;
}
}

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
example/http/Jamfile Normal file
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 ;

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

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 ;

View File

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

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
;

View File

@ -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;
}

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
)

View File

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

View File

@ -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;
}

View File

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

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
;

View File

@ -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;
}

View File

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

View File

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

View File

@ -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;
}

View File

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

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;
}

View File

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

View File

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

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
;

View File

@ -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;
}

View File

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

View File

@ -7,8 +7,8 @@
# Official repository: https://github.com/boostorg/beast
#
exe http-client :
http_client.cpp
exe http-client-sync :
http_client_sync.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no

View File

@ -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;
}
//]

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

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 ;

View File

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

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
;

View File

@ -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;
}

View File

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

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
;

View File

@ -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;
}

View File

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

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
;

View File

@ -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;
}

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
)

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
;

View File

@ -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;
}

View File

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

View File

@ -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;
}
}

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

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
;

View File

@ -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;
}

View File

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

View File

@ -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;
}
}

View File

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

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
;

View File

@ -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;
}

View File

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

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
;

View File

@ -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;
}

View File

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

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
;

View File

@ -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;
}
}

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
)

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
;

View File

@ -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;
}
}

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

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);
```

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

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

View File

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

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

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

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

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();
}

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

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