mirror of
https://github.com/boostorg/beast.git
synced 2025-07-30 04:47:29 +02:00
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:
@ -1,3 +1,9 @@
|
||||
Version 101:
|
||||
|
||||
* Refactor all examples
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Version 100:
|
||||
|
||||
* Fix doc convenience includes
|
||||
|
@ -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 |
@ -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]
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -7,16 +7,9 @@
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
build-project echo-op ;
|
||||
build-project http-client ;
|
||||
build-project http-crawl ;
|
||||
build-project http-server-fast ;
|
||||
build-project http-server-small ;
|
||||
build-project http-server-threaded ;
|
||||
build-project server-framework ;
|
||||
build-project websocket-client ;
|
||||
build-project websocket-server-async ;
|
||||
build-project advanced ;
|
||||
build-project http ;
|
||||
build-project websocket ;
|
||||
|
||||
# VFALCO How do I make this work on Windows and if OpenSSL is not available?
|
||||
#build-project ssl-http-client ;
|
||||
#build-project ssl-websocket-client ;
|
||||
# legacy
|
||||
build-project echo-op ;
|
||||
|
11
example/advanced/CMakeLists.txt
Normal file
11
example/advanced/CMakeLists.txt
Normal 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
13
example/advanced/Jamfile
Normal 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 ;
|
26
example/advanced/server-flex/CMakeLists.txt
Normal file
26
example/advanced/server-flex/CMakeLists.txt
Normal 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}
|
||||
)
|
21
example/advanced/server-flex/Jamfile
Normal file
21
example/advanced/server-flex/Jamfile
Normal 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
|
||||
;
|
1171
example/advanced/server-flex/advanced_server_flex.cpp
Normal file
1171
example/advanced/server-flex/advanced_server_flex.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,11 +9,11 @@
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/common common)
|
||||
GroupSources(example/http-server-threaded "/")
|
||||
GroupSources(example/advanced/server "/")
|
||||
|
||||
add_executable (http-server-threaded
|
||||
add_executable (advanced-server
|
||||
${BOOST_BEAST_INCLUDES}
|
||||
${COMMON_INCLUDES}
|
||||
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
|
||||
Jamfile
|
||||
http_server_threaded.cpp
|
||||
advanced_server.cpp
|
||||
)
|
@ -7,8 +7,8 @@
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
exe server-framework :
|
||||
main.cpp
|
||||
exe advanced-server :
|
||||
advanced_server.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
732
example/advanced/server/advanced_server.cpp
Normal file
732
example/advanced/server/advanced_server.cpp
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -110,6 +110,7 @@ load_root_certificates(ssl::context& ctx, boost::system::error_code& ec)
|
||||
// gratuituous template argument; thus it appears
|
||||
// like a "normal" function.
|
||||
//
|
||||
|
||||
inline
|
||||
void
|
||||
load_root_certificates(ssl::context& ctx, boost::system::error_code& ec)
|
||||
@ -117,4 +118,14 @@ load_root_certificates(ssl::context& ctx, boost::system::error_code& ec)
|
||||
detail::load_root_certificates(ctx, ec);
|
||||
}
|
||||
|
||||
inline
|
||||
void
|
||||
load_root_certificates(ssl::context& ctx)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
detail::load_root_certificates(ctx, ec);
|
||||
if(ec)
|
||||
throw boost::system::system_error{ec};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -7,48 +7,26 @@
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
#ifndef BOOST_BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP
|
||||
#define BOOST_BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP
|
||||
#ifndef BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP
|
||||
#define BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace framework {
|
||||
/* Load a signed certificate into the ssl context, and configure
|
||||
the context for use with a server.
|
||||
|
||||
// This sets up the self-signed certificate that the server
|
||||
// uses for its encrypted connections
|
||||
|
||||
class ssl_certificate
|
||||
{
|
||||
// The template argument is gratuitous, to
|
||||
// make the definition header-only without
|
||||
// also making it inline.
|
||||
//
|
||||
template<class = void>
|
||||
void
|
||||
construct();
|
||||
|
||||
boost::asio::ssl::context ctx_;
|
||||
|
||||
public:
|
||||
ssl_certificate()
|
||||
: ctx_(boost::asio::ssl::context::sslv23)
|
||||
{
|
||||
construct();
|
||||
}
|
||||
|
||||
boost::asio::ssl::context&
|
||||
get()
|
||||
{
|
||||
return ctx_;
|
||||
}
|
||||
};
|
||||
|
||||
template<class>
|
||||
For this to work with the browser or operating system, it is
|
||||
necessary to import the "Beast Test CA" certificate into
|
||||
the local certificate store, browser, or operating system
|
||||
depending on your environment Please see the documentation
|
||||
accompanying the Beast certificate for more details.
|
||||
*/
|
||||
inline
|
||||
void
|
||||
ssl_certificate::construct()
|
||||
load_server_certificate(boost::asio::ssl::context& ctx)
|
||||
{
|
||||
/*
|
||||
The certificate was generated from CMD.EXE on Windows 10 using:
|
||||
@ -120,29 +98,27 @@ ssl_certificate::construct()
|
||||
"QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n"
|
||||
"-----END DH PARAMETERS-----\n";
|
||||
|
||||
ctx_.set_password_callback(
|
||||
ctx.set_password_callback(
|
||||
[](std::size_t,
|
||||
boost::asio::ssl::context_base::password_purpose)
|
||||
{
|
||||
return "test";
|
||||
});
|
||||
|
||||
ctx_.set_options(
|
||||
ctx.set_options(
|
||||
boost::asio::ssl::context::default_workarounds |
|
||||
boost::asio::ssl::context::no_sslv2 |
|
||||
boost::asio::ssl::context::single_dh_use);
|
||||
|
||||
ctx_.use_certificate_chain(
|
||||
ctx.use_certificate_chain(
|
||||
boost::asio::buffer(cert.data(), cert.size()));
|
||||
|
||||
ctx_.use_private_key(
|
||||
ctx.use_private_key(
|
||||
boost::asio::buffer(key.data(), key.size()),
|
||||
boost::asio::ssl::context::file_format::pem);
|
||||
|
||||
ctx_.use_tmp_dh(
|
||||
ctx.use_tmp_dh(
|
||||
boost::asio::buffer(dh.data(), dh.size()));
|
||||
}
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
@ -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)
|
||||
|
@ -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
|
||||
;
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
//]
|
@ -1,139 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
#include "urls_large_data.hpp"
|
||||
|
||||
#include <boost/beast/core/multi_buffer.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
|
||||
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
|
||||
|
||||
template<class String>
|
||||
void
|
||||
err(boost::beast::error_code const& ec, String const& what)
|
||||
{
|
||||
std::cerr << what << ": " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
/* This simple program just visits a list with a few
|
||||
thousand domain names and tries to retrieve and print
|
||||
the home page of each site.
|
||||
*/
|
||||
int
|
||||
main(int, char const*[])
|
||||
{
|
||||
// A helper for reporting errors
|
||||
auto const fail =
|
||||
[](std::string what, boost::beast::error_code ec)
|
||||
{
|
||||
std::cerr << what << ": " << ec.message() << std::endl;
|
||||
std::cerr.flush();
|
||||
return EXIT_FAILURE;
|
||||
};
|
||||
|
||||
// Obligatory Asio variable
|
||||
boost::asio::io_service ios;
|
||||
|
||||
// Loop over all the URLs
|
||||
for(auto const& host : urls_large_data())
|
||||
{
|
||||
boost::beast::error_code ec;
|
||||
|
||||
// Look up the domain name
|
||||
tcp::resolver r(ios);
|
||||
auto lookup = r.resolve({host, "http"}, ec);
|
||||
if(ec)
|
||||
{
|
||||
fail("resolve", ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now create a socket and connect
|
||||
tcp::socket sock(ios);
|
||||
boost::asio::connect(sock, lookup, ec);
|
||||
if(ec)
|
||||
{
|
||||
fail("connect", ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grab the remote endpoint
|
||||
auto ep = sock.remote_endpoint(ec);
|
||||
if(ec)
|
||||
{
|
||||
fail("remote_endpoint", ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set up an HTTP GET request
|
||||
http::request<http::string_body> req{http::verb::get, "/", 11};
|
||||
req.set(http::field::host, host + std::string(":") + std::to_string(ep.port()));
|
||||
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
|
||||
// Set the Connection: close field, this way the server will close
|
||||
// the connection. This consumes less resources (no TIME_WAIT) because
|
||||
// of the graceful close. It also makes things go a little faster.
|
||||
//
|
||||
req.set(http::field::connection, "close");
|
||||
|
||||
// Send the GET request
|
||||
http::write(sock, req, ec);
|
||||
if(ec == http::error::end_of_stream)
|
||||
{
|
||||
// This special error received on a write indicates that the
|
||||
// semantics of the sent message are such that the connection
|
||||
// should be closed after the response is done. We do a TCP/IP
|
||||
// "half-close" here to shut down our end.
|
||||
//
|
||||
sock.shutdown(tcp::socket::shutdown_send, ec);
|
||||
if(ec && ec != boost::beast::errc::not_connected)
|
||||
return fail("shutdown", ec);
|
||||
}
|
||||
if(ec)
|
||||
{
|
||||
fail("write", ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
// This buffer is needed for reading
|
||||
boost::beast::multi_buffer b;
|
||||
|
||||
// The response will go into this object
|
||||
http::response<http::string_body> res;
|
||||
|
||||
// Read the response
|
||||
http::read(sock, b, res, ec);
|
||||
if(ec == http::error::end_of_stream)
|
||||
{
|
||||
// This special error means that the other end closed the socket,
|
||||
// which is what we want since we asked for Connection: close.
|
||||
// However, we are going through a rather large number of servers
|
||||
// and sometimes they misbehave.
|
||||
ec = {};
|
||||
}
|
||||
else if(ec)
|
||||
{
|
||||
fail("read", ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we do the other half of the close,
|
||||
// which is to shut down the receiver.
|
||||
sock.shutdown(tcp::socket::shutdown_receive, ec);
|
||||
if(ec && ec != boost::beast::errc::not_connected)
|
||||
return fail("shutdown", ec);
|
||||
|
||||
std::cout << res << std::endl;
|
||||
}
|
||||
}
|
@ -1,229 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
#include "../common/mime_types.hpp"
|
||||
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Example: HTTP server, synchronous, one thread per connection
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ip = boost::asio::ip; // from <boost/asio.hpp>
|
||||
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
|
||||
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
|
||||
|
||||
class connection
|
||||
: public std::enable_shared_from_this<connection>
|
||||
{
|
||||
tcp::socket sock_;
|
||||
boost::beast::string_view root_;
|
||||
|
||||
public:
|
||||
explicit
|
||||
connection(tcp::socket&& sock, boost::beast::string_view root)
|
||||
: sock_(std::move(sock))
|
||||
, root_(root)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
// Bind a shared_ptr to *this into the thread.
|
||||
// When the thread exits, the connection object
|
||||
// will be destroyed.
|
||||
//
|
||||
std::thread{&connection::do_run, shared_from_this()}.detach();
|
||||
}
|
||||
|
||||
private:
|
||||
// Send a client error response
|
||||
http::response<http::span_body<char const>>
|
||||
client_error(http::status result, boost::beast::string_view text)
|
||||
{
|
||||
http::response<http::span_body<char const>> res{result, 11};
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/plain");
|
||||
res.set(http::field::connection, "close");
|
||||
res.body = text;
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return an HTTP Not Found response
|
||||
//
|
||||
http::response<http::string_body>
|
||||
not_found() const
|
||||
{
|
||||
http::response<http::string_body> res{http::status::not_found, 11};
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.set(http::field::connection, "close");
|
||||
res.body = "The file was not found";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return an HTTP Server Error
|
||||
//
|
||||
http::response<http::string_body>
|
||||
server_error(boost::beast::error_code const& ec) const
|
||||
{
|
||||
http::response<http::string_body> res{http::status::internal_server_error, 11};
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, "text/html");
|
||||
res.set(http::field::connection, "close");
|
||||
res.body = "Error: " + ec.message();
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return a file response to an HTTP GET request
|
||||
//
|
||||
http::response<boost::beast::http::file_body>
|
||||
get(boost::filesystem::path const& full_path,
|
||||
boost::beast::error_code& ec) const
|
||||
{
|
||||
http::response<http::file_body> res;
|
||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
res.set(http::field::content_type, mime_type(full_path));
|
||||
res.set(http::field::connection, "close");
|
||||
res.body.open(full_path.string<std::string>().c_str(), boost::beast::file_mode::scan, ec);
|
||||
if(ec)
|
||||
return res;
|
||||
res.set(http::field::content_length, res.body.size());
|
||||
return res;
|
||||
}
|
||||
|
||||
// Handle a request
|
||||
template<class Body>
|
||||
void
|
||||
do_request(http::request<Body> const& req, boost::beast::error_code& ec)
|
||||
{
|
||||
// verb must be get
|
||||
if(req.method() != http::verb::get)
|
||||
{
|
||||
http::write(sock_, client_error(http::status::bad_request, "Unsupported method"), ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Request path must be absolute and not contain "..".
|
||||
if( req.target().empty() ||
|
||||
req.target()[0] != '/' ||
|
||||
req.target().find("..") != std::string::npos)
|
||||
{
|
||||
http::write(sock_, client_error(http::status::not_found, "File not found"), ec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto full_path = root_.to_string();
|
||||
full_path.append(req.target().data(), req.target().size());
|
||||
|
||||
boost::beast::error_code file_ec;
|
||||
auto res = get(full_path, file_ec);
|
||||
|
||||
if(file_ec == boost::beast::errc::no_such_file_or_directory)
|
||||
{
|
||||
http::write(sock_, not_found(), ec);
|
||||
}
|
||||
else if(ec)
|
||||
{
|
||||
http::write(sock_, server_error(file_ec), ec);
|
||||
}
|
||||
else
|
||||
{
|
||||
http::serializer<false, decltype(res)::body_type> sr{res};
|
||||
http::write(sock_, sr, ec);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
do_run()
|
||||
{
|
||||
try
|
||||
{
|
||||
boost::beast::error_code ec;
|
||||
boost::beast::flat_buffer buffer;
|
||||
for(;;)
|
||||
{
|
||||
http::request_parser<http::string_body> parser;
|
||||
parser.header_limit(8192);
|
||||
parser.body_limit(1024 * 1024);
|
||||
http::read(sock_, buffer, parser, ec);
|
||||
if(ec == http::error::end_of_stream)
|
||||
break;
|
||||
if(ec)
|
||||
throw boost::beast::system_error{ec};
|
||||
do_request(parser.get(), ec);
|
||||
if(ec)
|
||||
{
|
||||
if(ec != http::error::end_of_stream)
|
||||
throw boost::beast::system_error{ec};
|
||||
break;
|
||||
}
|
||||
}
|
||||
sock_.shutdown(tcp::socket::shutdown_both, ec);
|
||||
if(ec && ec != boost::asio::error::not_connected)
|
||||
throw boost::beast::system_error{ec};
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << "Exception: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check command line arguments.
|
||||
if (argc != 4)
|
||||
{
|
||||
std::cerr << "Usage: http_server <address> <port> <doc_root>\n";
|
||||
std::cerr << " For IPv4, try:\n";
|
||||
std::cerr << " receiver 0.0.0.0 80 .\n";
|
||||
std::cerr << " For IPv6, try:\n";
|
||||
std::cerr << " receiver 0::0 80 .\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
auto address = ip::address::from_string(argv[1]);
|
||||
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
|
||||
std::string doc_root = argv[3];
|
||||
|
||||
boost::asio::io_service ios{1};
|
||||
tcp::acceptor acceptor{ios, {address, port}};
|
||||
for(;;)
|
||||
{
|
||||
tcp::socket sock{ios};
|
||||
acceptor.accept(sock);
|
||||
std::make_shared<connection>(std::move(sock), doc_root)->run();
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << "Exception: " << e.what() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
11
example/http/CMakeLists.txt
Normal file
11
example/http/CMakeLists.txt
Normal 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
11
example/http/Jamfile
Normal file
@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
build-project client ;
|
||||
build-project server ;
|
19
example/http/client/CMakeLists.txt
Normal file
19
example/http/client/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
add_subdirectory (async)
|
||||
add_subdirectory (coro)
|
||||
add_subdirectory (crawl)
|
||||
add_subdirectory (sync)
|
||||
|
||||
if (OPENSSL_FOUND)
|
||||
add_subdirectory (async-ssl)
|
||||
add_subdirectory (coro-ssl)
|
||||
add_subdirectory (sync-ssl)
|
||||
endif()
|
18
example/http/client/Jamfile
Normal file
18
example/http/client/Jamfile
Normal 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 ;
|
23
example/http/client/async-ssl/CMakeLists.txt
Normal file
23
example/http/client/async-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
21
example/http/client/async-ssl/Jamfile
Normal file
21
example/http/client/async-ssl/Jamfile
Normal 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
|
||||
;
|
212
example/http/client/async-ssl/http_client_async_ssl.cpp
Normal file
212
example/http/client/async-ssl/http_client_async_ssl.cpp
Normal 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;
|
||||
}
|
17
example/http/client/async/CMakeLists.txt
Normal file
17
example/http/client/async/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/http/client/async "/")
|
||||
|
||||
add_executable (http-client-async
|
||||
${BOOST_BEAST_INCLUDES}
|
||||
Jamfile
|
||||
http_client_async.cpp
|
||||
)
|
@ -7,8 +7,8 @@
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
exe http-server-threaded :
|
||||
http_server_threaded.cpp
|
||||
exe http-client-async :
|
||||
http_client_async.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
172
example/http/client/async/http_client_async.cpp
Normal file
172
example/http/client/async/http_client_async.cpp
Normal 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;
|
||||
}
|
23
example/http/client/coro-ssl/CMakeLists.txt
Normal file
23
example/http/client/coro-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
21
example/http/client/coro-ssl/Jamfile
Normal file
21
example/http/client/coro-ssl/Jamfile
Normal 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
|
||||
;
|
153
example/http/client/coro-ssl/http_client_coro_ssl.cpp
Normal file
153
example/http/client/coro-ssl/http_client_coro_ssl.cpp
Normal 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;
|
||||
}
|
@ -8,10 +8,10 @@
|
||||
#
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/http-client "/")
|
||||
GroupSources(example/http/client/coro "/")
|
||||
|
||||
add_executable (http-client
|
||||
add_executable (http-client-coro
|
||||
${BOOST_BEAST_INCLUDES}
|
||||
Jamfile
|
||||
http_client.cpp
|
||||
http_client_coro.cpp
|
||||
)
|
@ -7,8 +7,8 @@
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
exe websocket-client :
|
||||
websocket_client.cpp
|
||||
exe http-client-coro :
|
||||
http_client_coro.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
134
example/http/client/coro/http_client_coro.cpp
Normal file
134
example/http/client/coro/http_client_coro.cpp
Normal 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;
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
#
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/http-crawl "/")
|
||||
GroupSources(example/http/client/crawl "/")
|
||||
|
||||
add_executable (http-crawl
|
||||
${BOOST_BEAST_INCLUDES}
|
450
example/http/client/crawl/http_crawl.cpp
Normal file
450
example/http/client/crawl/http_crawl.cpp
Normal file
@ -0,0 +1,450 @@
|
||||
//
|
||||
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Example: HTTP crawl (asynchronous)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#include "urls_large_data.hpp"
|
||||
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
|
||||
namespace chrono = std::chrono; // from <chrono>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// This structure aggregates statistics on all the sites
|
||||
class crawl_report
|
||||
{
|
||||
boost::asio::io_service& ios_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
std::atomic<std::size_t> index_;
|
||||
std::vector<char const*> const& hosts_;
|
||||
std::size_t count_ = 0;
|
||||
|
||||
public:
|
||||
crawl_report(boost::asio::io_service& ios)
|
||||
: ios_(ios)
|
||||
, strand_(ios_)
|
||||
, index_(0)
|
||||
, hosts_(urls_large_data())
|
||||
{
|
||||
}
|
||||
|
||||
// Run an aggregation function on the strand.
|
||||
// This allows synchronization without a mutex.
|
||||
template<class F>
|
||||
void
|
||||
aggregate(F const& f)
|
||||
{
|
||||
ios_.post(strand_.wrap(
|
||||
[&, f]
|
||||
{
|
||||
f(*this);
|
||||
if(count_ % 100 == 0)
|
||||
{
|
||||
std::cerr <<
|
||||
"Progress: " << count_ << " of " << hosts_.size() << "\n";
|
||||
//std::cerr << *this;
|
||||
}
|
||||
++count_;
|
||||
}));
|
||||
}
|
||||
|
||||
// Returns the next host to check
|
||||
char const*
|
||||
get_host()
|
||||
{
|
||||
auto const n = index_++;
|
||||
if(n >= hosts_.size())
|
||||
return nullptr;
|
||||
return hosts_[n];
|
||||
}
|
||||
|
||||
// Counts the number of timer failures
|
||||
std::size_t timer_failures = 0;
|
||||
|
||||
// Counts the number of name resolution failures
|
||||
std::size_t resolve_failures = 0;
|
||||
|
||||
// Counts the number of connection failures
|
||||
std::size_t connect_failures = 0;
|
||||
|
||||
// Counts the number of write failures
|
||||
std::size_t write_failures = 0;
|
||||
|
||||
// Counts the number of read failures
|
||||
std::size_t read_failures = 0;
|
||||
|
||||
// Counts the number of success reads
|
||||
std::size_t success = 0;
|
||||
|
||||
// Counts the number received of each status code
|
||||
std::map<unsigned, std::size_t> status_codes;
|
||||
};
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, crawl_report const& report)
|
||||
{
|
||||
// Print the report
|
||||
os <<
|
||||
"Crawl report\n" <<
|
||||
" Failure counts\n" <<
|
||||
" Timer : " << report.timer_failures << "\n" <<
|
||||
" Resolve : " << report.resolve_failures << "\n" <<
|
||||
" Connect : " << report.connect_failures << "\n" <<
|
||||
" Write : " << report.write_failures << "\n" <<
|
||||
" Read : " << report.read_failures << "\n" <<
|
||||
" Success : " << report.success << "\n" <<
|
||||
" Status codes\n"
|
||||
;
|
||||
for(auto const& result : report.status_codes)
|
||||
os <<
|
||||
" " << std::setw(3) << result.first << ": " << result.second <<
|
||||
" (" << http::obsolete_reason(static_cast<http::status>(result.first)) << ")\n";
|
||||
os.flush();
|
||||
return os;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Performs HTTP GET requests and aggregates the results into a report
|
||||
class worker : public std::enable_shared_from_this<worker>
|
||||
{
|
||||
enum
|
||||
{
|
||||
// Use a small timeout to keep things lively
|
||||
timeout = 5
|
||||
};
|
||||
|
||||
crawl_report& report_;
|
||||
tcp::resolver resolver_;
|
||||
tcp::socket socket_;
|
||||
boost::asio::steady_timer timer_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
boost::beast::flat_buffer buffer_; // (Must persist between reads)
|
||||
http::request<http::empty_body> req_;
|
||||
http::response<http::string_body> res_;
|
||||
|
||||
public:
|
||||
worker(worker&&) = default;
|
||||
|
||||
// Resolver and socket require an io_service
|
||||
worker(
|
||||
crawl_report& report,
|
||||
boost::asio::io_service& ios)
|
||||
: report_(report)
|
||||
, resolver_(ios)
|
||||
, socket_(ios)
|
||||
, timer_(ios,
|
||||
(chrono::steady_clock::time_point::max)())
|
||||
, strand_(ios)
|
||||
{
|
||||
// Set up the common fields of the request
|
||||
req_.version = 11;
|
||||
req_.method(http::verb::get);
|
||||
req_.target("/");
|
||||
req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
}
|
||||
|
||||
// Start the asynchronous operation
|
||||
void
|
||||
run()
|
||||
{
|
||||
// Run the timer. The timer is operated
|
||||
// continuously, this simplifies the code.
|
||||
on_timer({});
|
||||
|
||||
do_get_host();
|
||||
}
|
||||
|
||||
void
|
||||
on_timer(boost::system::error_code ec)
|
||||
{
|
||||
if(ec && ec != boost::asio::error::operation_aborted)
|
||||
{
|
||||
// Should never happen
|
||||
report_.aggregate(
|
||||
[](crawl_report& rep)
|
||||
{
|
||||
++rep.timer_failures;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify that the timer really expired since the deadline may have moved.
|
||||
if(timer_.expires_at() <= chrono::steady_clock::now())
|
||||
{
|
||||
socket_.shutdown(tcp::socket::shutdown_both, ec);
|
||||
socket_.close(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait on the timer
|
||||
timer_.async_wait(
|
||||
strand_.wrap(std::bind(
|
||||
&worker::on_timer,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
void
|
||||
do_get_host()
|
||||
{
|
||||
// Grab another host
|
||||
auto const host = report_.get_host();
|
||||
|
||||
// nullptr means no more work
|
||||
if(! host)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
timer_.cancel(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// The Host HTTP field is required
|
||||
req_.set(http::field::host, host);
|
||||
|
||||
// Set the timer
|
||||
timer_.expires_from_now(chrono::seconds(timeout));
|
||||
|
||||
// Set up an HTTP GET request message
|
||||
// Look up the domain name
|
||||
resolver_.async_resolve(
|
||||
tcp::resolver::query{host, "http"},
|
||||
strand_.wrap(std::bind(
|
||||
&worker::on_resolve,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2)));
|
||||
}
|
||||
|
||||
void
|
||||
on_resolve(
|
||||
boost::system::error_code ec,
|
||||
tcp::resolver::iterator result)
|
||||
{
|
||||
if(ec)
|
||||
{
|
||||
report_.aggregate(
|
||||
[](crawl_report& rep)
|
||||
{
|
||||
++rep.resolve_failures;
|
||||
});
|
||||
return do_get_host();
|
||||
}
|
||||
|
||||
// Set the timer
|
||||
timer_.expires_from_now(chrono::seconds(timeout));
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
boost::asio::async_connect(
|
||||
socket_,
|
||||
result,
|
||||
strand_.wrap(std::bind(
|
||||
&worker::on_connect,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
void
|
||||
on_connect(boost::system::error_code ec)
|
||||
{
|
||||
if(ec)
|
||||
{
|
||||
report_.aggregate(
|
||||
[](crawl_report& rep)
|
||||
{
|
||||
++rep.connect_failures;
|
||||
});
|
||||
return do_get_host();
|
||||
}
|
||||
|
||||
// Set the timer
|
||||
timer_.expires_from_now(chrono::seconds(timeout));
|
||||
|
||||
// Send the HTTP request to the remote host
|
||||
http::async_write(
|
||||
socket_,
|
||||
req_,
|
||||
strand_.wrap(std::bind(
|
||||
&worker::on_write,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
void
|
||||
on_write(boost::system::error_code ec)
|
||||
{
|
||||
if(ec)
|
||||
{
|
||||
report_.aggregate(
|
||||
[](crawl_report& rep)
|
||||
{
|
||||
++rep.write_failures;
|
||||
});
|
||||
return do_get_host();
|
||||
}
|
||||
|
||||
// Set the timer
|
||||
timer_.expires_from_now(chrono::seconds(timeout));
|
||||
|
||||
// Receive the HTTP response
|
||||
http::async_read(
|
||||
socket_,
|
||||
buffer_,
|
||||
res_,
|
||||
strand_.wrap(std::bind(
|
||||
&worker::on_read,
|
||||
shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
void
|
||||
on_read(boost::system::error_code ec)
|
||||
{
|
||||
if(ec)
|
||||
{
|
||||
report_.aggregate(
|
||||
[](crawl_report& rep)
|
||||
{
|
||||
++rep.read_failures;
|
||||
});
|
||||
return do_get_host();
|
||||
}
|
||||
|
||||
auto const code = res_.result_int();
|
||||
report_.aggregate(
|
||||
[code](crawl_report& rep)
|
||||
{
|
||||
++rep.success;
|
||||
++rep.status_codes[code];
|
||||
});
|
||||
|
||||
// Gracefully close the socket
|
||||
socket_.shutdown(tcp::socket::shutdown_both, ec);
|
||||
socket_.close(ec);
|
||||
|
||||
// If we get here then the connection is closed gracefully
|
||||
|
||||
do_get_host();
|
||||
}
|
||||
};
|
||||
|
||||
class timer
|
||||
{
|
||||
using clock_type = chrono::system_clock;
|
||||
|
||||
clock_type::time_point when_;
|
||||
|
||||
public:
|
||||
using duration = clock_type::duration;
|
||||
|
||||
timer()
|
||||
: when_(clock_type::now())
|
||||
{
|
||||
}
|
||||
|
||||
duration
|
||||
elapsed() const
|
||||
{
|
||||
return clock_type::now() - when_;
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
// Check command line arguments.
|
||||
if (argc != 2)
|
||||
{
|
||||
std::cerr <<
|
||||
"Usage: http-crawl <threads>\n" <<
|
||||
"Example:\n" <<
|
||||
" http-crawl 100 1\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
auto const threads = std::max<std::size_t>(1, std::atoi(argv[1]));
|
||||
|
||||
// The io_service is required for all I/O
|
||||
boost::asio::io_service ios{1};
|
||||
|
||||
// The work keeps io_service::run from returning
|
||||
boost::optional<boost::asio::io_service::work> work{ios};
|
||||
|
||||
// The report holds the aggregated statistics
|
||||
crawl_report report{ios};
|
||||
|
||||
timer t;
|
||||
|
||||
// Create and launch the worker threads.
|
||||
std::vector<std::thread> workers;
|
||||
workers.reserve(threads + 1);
|
||||
for(std::size_t i = 0; i < threads; ++i)
|
||||
workers.emplace_back(
|
||||
[&report]
|
||||
{
|
||||
// We use a separate io_service for each worker because
|
||||
// the asio resolver simulates asynchronous operation using
|
||||
// a dedicated worker thread per io_service, and we want to
|
||||
// do a lot of name resolutions in parallel.
|
||||
boost::asio::io_service ios{1};
|
||||
std::make_shared<worker>(report, ios)->run();
|
||||
ios.run();
|
||||
});
|
||||
|
||||
// Add another thread to run the main io_service which
|
||||
// is used to aggregate the statistics
|
||||
workers.emplace_back(
|
||||
[&ios]
|
||||
{
|
||||
ios.run();
|
||||
});
|
||||
|
||||
// Now block until all threads exit
|
||||
for(std::size_t i = 0; i < workers.size(); ++i)
|
||||
{
|
||||
auto& thread = workers[i];
|
||||
|
||||
// If this is the last thread, destroy the
|
||||
// work object so that it can return from run.
|
||||
//if(&thread == &workers.back())
|
||||
if(i == workers.size() - 1)
|
||||
work = boost::none;
|
||||
|
||||
// Wait for the thread to exit
|
||||
thread.join();
|
||||
}
|
||||
|
||||
std::cout <<
|
||||
"Elapsed time: " << chrono::duration_cast<chrono::seconds>(t.elapsed()).count() << " seconds\n";
|
||||
std::cout << report;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -7,8 +7,8 @@
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
#ifndef BOOST_BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
|
||||
#define BOOST_BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
|
||||
#ifndef BOOST_BEAST_EXAMPLE_HTTP_CLIENT_CRAWL_URLS_LARGE_DATA_HPP
|
||||
#define BOOST_BEAST_EXAMPLE_HTTP_CLIENT_CRAWL_URLS_LARGE_DATA_HPP
|
||||
|
||||
#include <vector>
|
||||
|
23
example/http/client/sync-ssl/CMakeLists.txt
Normal file
23
example/http/client/sync-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
21
example/http/client/sync-ssl/Jamfile
Normal file
21
example/http/client/sync-ssl/Jamfile
Normal 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
|
||||
;
|
112
example/http/client/sync-ssl/http_client_sync_ssl.cpp
Normal file
112
example/http/client/sync-ssl/http_client_sync_ssl.cpp
Normal 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;
|
||||
}
|
@ -8,10 +8,10 @@
|
||||
#
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/websocket-client "/")
|
||||
GroupSources(example/http/client/sync "/")
|
||||
|
||||
add_executable (websocket-client
|
||||
add_executable (http-client-sync
|
||||
${BOOST_BEAST_INCLUDES}
|
||||
Jamfile
|
||||
websocket_client.cpp
|
||||
http_client_sync.cpp
|
||||
)
|
@ -7,8 +7,8 @@
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
exe http-client :
|
||||
http_client.cpp
|
||||
exe http-client-sync :
|
||||
http_client_sync.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
101
example/http/client/sync/http_client_sync.cpp
Normal file
101
example/http/client/sync/http_client_sync.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//]
|
23
example/http/server/CMakeLists.txt
Normal file
23
example/http/server/CMakeLists.txt
Normal file
@ -0,0 +1,23 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
add_subdirectory (async)
|
||||
add_subdirectory (coro)
|
||||
add_subdirectory (fast)
|
||||
add_subdirectory (small)
|
||||
add_subdirectory (stackless)
|
||||
add_subdirectory (sync)
|
||||
|
||||
if (OPENSSL_FOUND)
|
||||
add_subdirectory (async-ssl)
|
||||
add_subdirectory (coro-ssl)
|
||||
add_subdirectory (flex)
|
||||
add_subdirectory (stackless-ssl)
|
||||
add_subdirectory (sync-ssl)
|
||||
endif()
|
22
example/http/server/Jamfile
Normal file
22
example/http/server/Jamfile
Normal 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 ;
|
24
example/http/server/async-ssl/CMakeLists.txt
Normal file
24
example/http/server/async-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
21
example/http/server/async-ssl/Jamfile
Normal file
21
example/http/server/async-ssl/Jamfile
Normal 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
|
||||
;
|
493
example/http/server/async-ssl/http_server_async_ssl.cpp
Normal file
493
example/http/server/async-ssl/http_server_async_ssl.cpp
Normal 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;
|
||||
}
|
@ -9,15 +9,11 @@
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/common common)
|
||||
GroupSources(example/http-client-ssl "/")
|
||||
GroupSources(example/http/server/async "/")
|
||||
|
||||
add_executable (http-client-ssl
|
||||
add_executable (http-server-async
|
||||
${BOOST_BEAST_INCLUDES}
|
||||
${COMMON_INCLUDES}
|
||||
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
|
||||
Jamfile
|
||||
http_client_ssl.cpp
|
||||
http_server_async.cpp
|
||||
)
|
||||
|
||||
target_link_libraries (http-client-ssl
|
||||
${OPENSSL_LIBRARIES}
|
||||
)
|
15
example/http/server/async/Jamfile
Normal file
15
example/http/server/async/Jamfile
Normal 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
|
||||
;
|
451
example/http/server/async/http_server_async.cpp
Normal file
451
example/http/server/async/http_server_async.cpp
Normal 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;
|
||||
}
|
23
example/http/server/coro-ssl/CMakeLists.txt
Normal file
23
example/http/server/coro-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
21
example/http/server/coro-ssl/Jamfile
Normal file
21
example/http/server/coro-ssl/Jamfile
Normal 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
|
||||
;
|
393
example/http/server/coro-ssl/http_server_coro_ssl.cpp
Normal file
393
example/http/server/coro-ssl/http_server_coro_ssl.cpp
Normal 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;
|
||||
}
|
17
example/http/server/coro/CMakeLists.txt
Normal file
17
example/http/server/coro/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/http/server/coro "/")
|
||||
|
||||
add_executable (http-server-coro
|
||||
${BOOST_BEAST_INCLUDES}
|
||||
Jamfile
|
||||
http_server_coro.cpp
|
||||
)
|
15
example/http/server/coro/Jamfile
Normal file
15
example/http/server/coro/Jamfile
Normal 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
|
||||
;
|
369
example/http/server/coro/http_server_coro.cpp
Normal file
369
example/http/server/coro/http_server_coro.cpp
Normal 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;
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/common common)
|
||||
GroupSources(example/http-server-fast "/")
|
||||
GroupSources(example/http/server/fast "/")
|
||||
|
||||
add_executable (http-server-fast
|
||||
${BOOST_BEAST_INCLUDES}
|
@ -7,9 +7,13 @@
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
#include "fields_alloc.hpp"
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Example: HTTP server, fast
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#include "../common/mime_types.hpp"
|
||||
#include "fields_alloc.hpp"
|
||||
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
@ -19,7 +23,6 @@
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
@ -29,6 +32,42 @@ namespace ip = boost::asio::ip; // from <boost/asio.hpp>
|
||||
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
|
||||
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
|
||||
|
||||
// Return a reasonable mime type based on the extension of a file.
|
||||
boost::beast::string_view
|
||||
mime_type(boost::beast::string_view path)
|
||||
{
|
||||
using boost::beast::iequals;
|
||||
auto const ext = [&path]
|
||||
{
|
||||
auto const pos = path.rfind(".");
|
||||
if(pos == boost::beast::string_view::npos)
|
||||
return boost::beast::string_view{};
|
||||
return path.substr(pos);
|
||||
}();
|
||||
if(iequals(ext, ".htm")) return "text/html";
|
||||
if(iequals(ext, ".html")) return "text/html";
|
||||
if(iequals(ext, ".php")) return "text/html";
|
||||
if(iequals(ext, ".css")) return "text/css";
|
||||
if(iequals(ext, ".txt")) return "text/plain";
|
||||
if(iequals(ext, ".js")) return "application/javascript";
|
||||
if(iequals(ext, ".json")) return "application/json";
|
||||
if(iequals(ext, ".xml")) return "application/xml";
|
||||
if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
|
||||
if(iequals(ext, ".flv")) return "video/x-flv";
|
||||
if(iequals(ext, ".png")) return "image/png";
|
||||
if(iequals(ext, ".jpe")) return "image/jpeg";
|
||||
if(iequals(ext, ".jpeg")) return "image/jpeg";
|
||||
if(iequals(ext, ".jpg")) return "image/jpeg";
|
||||
if(iequals(ext, ".gif")) return "image/gif";
|
||||
if(iequals(ext, ".bmp")) return "image/bmp";
|
||||
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
|
||||
if(iequals(ext, ".tiff")) return "image/tiff";
|
||||
if(iequals(ext, ".tif")) return "image/tiff";
|
||||
if(iequals(ext, ".svg")) return "image/svg+xml";
|
||||
if(iequals(ext, ".svgz")) return "image/svg+xml";
|
||||
return "application/text";
|
||||
}
|
||||
|
||||
class http_worker
|
||||
{
|
||||
public:
|
||||
@ -306,7 +345,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << "Exception: " << e.what() << std::endl;
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
25
example/http/server/flex/CMakeLists.txt
Normal file
25
example/http/server/flex/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
||||
#
|
||||
# Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/common common)
|
||||
GroupSources(example/http/server/flex "/")
|
||||
|
||||
add_executable (http-server-flex
|
||||
${BOOST_BEAST_INCLUDES}
|
||||
${PROJECT_SOURCE_DIR}/example/common/detect_ssl.hpp
|
||||
${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp
|
||||
${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp
|
||||
Jamfile
|
||||
http_server_flex.cpp
|
||||
)
|
||||
|
||||
target_link_libraries (http-server-flex
|
||||
${OPENSSL_LIBRARIES}
|
||||
)
|
21
example/http/server/flex/Jamfile
Normal file
21
example/http/server/flex/Jamfile
Normal 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
|
||||
;
|
657
example/http/server/flex/http_server_flex.cpp
Normal file
657
example/http/server/flex/http_server_flex.cpp
Normal 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;
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
#
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/http-server-small "/")
|
||||
GroupSources(example/http/server/small "/")
|
||||
|
||||
add_executable (http-server-small
|
||||
${BOOST_BEAST_INCLUDES}
|
@ -7,6 +7,12 @@
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Example: HTTP server, small
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
@ -236,7 +242,7 @@ main(int argc, char* argv[])
|
||||
}
|
||||
catch(std::exception const& e)
|
||||
{
|
||||
std::cerr << "Exception: " << e.what() << std::endl;
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
24
example/http/server/stackless-ssl/CMakeLists.txt
Normal file
24
example/http/server/stackless-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
21
example/http/server/stackless-ssl/Jamfile
Normal file
21
example/http/server/stackless-ssl/Jamfile
Normal 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
|
||||
;
|
479
example/http/server/stackless-ssl/http_server_stackless_ssl.cpp
Normal file
479
example/http/server/stackless-ssl/http_server_stackless_ssl.cpp
Normal 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;
|
||||
}
|
19
example/http/server/stackless/CMakeLists.txt
Normal file
19
example/http/server/stackless/CMakeLists.txt
Normal 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
|
||||
)
|
15
example/http/server/stackless/Jamfile
Normal file
15
example/http/server/stackless/Jamfile
Normal 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
|
||||
;
|
445
example/http/server/stackless/http_server_stackless.cpp
Normal file
445
example/http/server/stackless/http_server_stackless.cpp
Normal 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;
|
||||
}
|
22
example/http/server/sync-ssl/CMakeLists.txt
Normal file
22
example/http/server/sync-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
21
example/http/server/sync-ssl/Jamfile
Normal file
21
example/http/server/sync-ssl/Jamfile
Normal 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
|
||||
;
|
343
example/http/server/sync-ssl/http_server_sync_ssl.cpp
Normal file
343
example/http/server/sync-ssl/http_server_sync_ssl.cpp
Normal 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;
|
||||
}
|
||||
}
|
17
example/http/server/sync/CMakeLists.txt
Normal file
17
example/http/server/sync/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Official repository: https://github.com/boostorg/beast
|
||||
#
|
||||
|
||||
GroupSources(include/boost/beast beast)
|
||||
GroupSources(example/http/server/sync "/")
|
||||
|
||||
add_executable (http-server-sync
|
||||
${BOOST_BEAST_INCLUDES}
|
||||
Jamfile
|
||||
http_server_sync.cpp
|
||||
)
|
15
example/http/server/sync/Jamfile
Normal file
15
example/http/server/sync/Jamfile
Normal 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
|
||||
;
|
323
example/http/server/sync/http_server_sync.cpp
Normal file
323
example/http/server/sync/http_server_sync.cpp
Normal 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;
|
||||
}
|
||||
}
|
@ -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()
|
@ -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);
|
||||
|
||||
```
|
@ -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
|
@ -1,55 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
#ifndef BOOST_BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP
|
||||
#define BOOST_BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP
|
||||
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <utility>
|
||||
|
||||
/** The framework namespace
|
||||
|
||||
This namespace contains all of the identifiers in the
|
||||
server-framework system. Here we import some commonly
|
||||
used types for brevity.
|
||||
*/
|
||||
namespace framework {
|
||||
|
||||
// This is our own base from member idiom written for C++11
|
||||
// which is simple and works around a glitch in boost's version.
|
||||
//
|
||||
template<class T>
|
||||
class base_from_member
|
||||
{
|
||||
public:
|
||||
template<class... Args>
|
||||
explicit
|
||||
base_from_member(Args&&... args)
|
||||
: member(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
T member;
|
||||
};
|
||||
|
||||
using error_code = boost::system::error_code;
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
using strand_type = boost::asio::io_service::strand;
|
||||
using address_type = boost::asio::ip::address_v4;
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using acceptor_type = boost::asio::ip::tcp::acceptor;
|
||||
using io_service_type = boost::asio::io_service;
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
@ -1,655 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP
|
||||
#define BOOST_BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP
|
||||
|
||||
#include "server.hpp"
|
||||
|
||||
#include "http_base.hpp"
|
||||
#include "service_list.hpp"
|
||||
|
||||
#include "../common/rfc7231.hpp"
|
||||
#include "../common/write_msg.hpp"
|
||||
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/beast/http/dynamic_body.hpp>
|
||||
#include <boost/beast/http/parser.hpp>
|
||||
#include <boost/beast/http/read.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/write.hpp>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <ostream>
|
||||
|
||||
namespace framework {
|
||||
|
||||
// Base class for a type-erased, queued asynchronous HTTP write operation
|
||||
//
|
||||
struct queued_http_write
|
||||
{
|
||||
// Destructor must be virtual since we delete a
|
||||
// derived class through a pointer to the base!
|
||||
//
|
||||
virtual ~queued_http_write() = default;
|
||||
|
||||
// When invoked, performs the write operation.
|
||||
virtual void invoke() = 0;
|
||||
};
|
||||
|
||||
/* This implements an object which, when invoked, writes an HTTP
|
||||
message asynchronously to the stream. These objects are used
|
||||
to form a queue of outgoing messages for pipelining. The base
|
||||
class type-erases the message so the queue can hold messsages
|
||||
of different types.
|
||||
*/
|
||||
template<
|
||||
class Stream,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class Handler>
|
||||
class queued_http_write_impl : public queued_http_write
|
||||
{
|
||||
// The stream to write to
|
||||
Stream& stream_;
|
||||
|
||||
// The message to send, which we acquire by move or copy
|
||||
boost::beast::http::message<isRequest, Body, Fields> msg_;
|
||||
|
||||
// The handler to invoke when the send completes.
|
||||
Handler handler_;
|
||||
|
||||
public:
|
||||
// Constructor.
|
||||
//
|
||||
// Ownership of the message is transferred into the object
|
||||
//
|
||||
template<class DeducedHandler>
|
||||
queued_http_write_impl(
|
||||
Stream& stream,
|
||||
boost::beast::http::message<isRequest, Body, Fields>&& msg,
|
||||
DeducedHandler&& handler)
|
||||
: stream_(stream)
|
||||
, msg_(std::move(msg))
|
||||
, handler_(std::forward<DeducedHandler>(handler))
|
||||
{
|
||||
}
|
||||
|
||||
// Writes the stored message.
|
||||
//
|
||||
// The caller must make sure this invocation represents
|
||||
// a continuation of an asynchronous operation which is
|
||||
// already in the right context. For example, already
|
||||
// running on the associated strand.
|
||||
//
|
||||
void
|
||||
invoke() override
|
||||
{
|
||||
async_write_msg(
|
||||
stream_,
|
||||
std::move(msg_),
|
||||
std::move(handler_));
|
||||
}
|
||||
};
|
||||
|
||||
// This helper function creates a queued_http_write
|
||||
// object and returns it inside a unique_ptr.
|
||||
//
|
||||
template<
|
||||
class Stream,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class Handler>
|
||||
std::unique_ptr<queued_http_write>
|
||||
make_queued_http_write(
|
||||
Stream& stream,
|
||||
boost::beast::http::message<isRequest, Body, Fields>&& msg,
|
||||
Handler&& handler)
|
||||
{
|
||||
return std::unique_ptr<queued_http_write>{
|
||||
new queued_http_write_impl<
|
||||
Stream,
|
||||
isRequest, Body, Fields,
|
||||
typename std::decay<Handler>::type>{
|
||||
stream,
|
||||
std::move(msg),
|
||||
std::forward<Handler>(handler)}};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** An asynchronous HTTP connection.
|
||||
|
||||
This base class implements an HTTP connection object using
|
||||
asynchronous calls.
|
||||
|
||||
It uses the Curiously Recurring Template pattern (CRTP) where
|
||||
we refer to the derived class in order to access the stream object
|
||||
to use for reading and writing. This lets the same class be used
|
||||
for plain and SSL stream objects.
|
||||
|
||||
@tparam Services The list of services this connection will support.
|
||||
*/
|
||||
template<class Derived, class... Services>
|
||||
class async_http_con_base : public http_base
|
||||
{
|
||||
protected:
|
||||
// This function lets us access members of the derived class
|
||||
Derived&
|
||||
impl()
|
||||
{
|
||||
return static_cast<Derived&>(*this);
|
||||
}
|
||||
|
||||
// The stream to use for logging
|
||||
std::ostream& log_;
|
||||
|
||||
// The services configured for the port
|
||||
service_list<Services...> const& services_;
|
||||
|
||||
// A small unique integer for logging
|
||||
std::size_t id_;
|
||||
|
||||
// The remote endpoint. We cache it here because
|
||||
// calls to remote_endpoint() can fail / throw.
|
||||
//
|
||||
endpoint_type ep_;
|
||||
|
||||
// The buffer for performing reads
|
||||
boost::beast::flat_buffer buffer_;
|
||||
|
||||
// The parser for reading the requests
|
||||
boost::optional<boost::beast::http::request_parser<boost::beast::http::dynamic_body>> parser_;
|
||||
|
||||
// This is the queue of outgoing messages
|
||||
std::vector<std::unique_ptr<queued_http_write>> queue_;
|
||||
|
||||
// Indicates if we have a write active.
|
||||
bool writing_ = false;
|
||||
|
||||
// The strand makes sure that our data is
|
||||
// accessed from only one thread at a time.
|
||||
//
|
||||
strand_type strand_;
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
async_http_con_base(
|
||||
boost::beast::string_view server_name,
|
||||
std::ostream& log,
|
||||
service_list<Services...> const& services,
|
||||
std::size_t id,
|
||||
endpoint_type const& ep)
|
||||
: http_base(server_name)
|
||||
, log_(log)
|
||||
, services_(services)
|
||||
, id_(id)
|
||||
, ep_(ep)
|
||||
|
||||
// The buffer has a limit of 8192, otherwise
|
||||
// the server is vulnerable to a buffer attack.
|
||||
//
|
||||
, buffer_(8192)
|
||||
|
||||
, strand_(impl().stream().get_io_service())
|
||||
{
|
||||
}
|
||||
|
||||
// Called to start the object after the listener accepts
|
||||
// an incoming connection, when no bytes have been read yet.
|
||||
//
|
||||
void
|
||||
run()
|
||||
{
|
||||
// Just call run with an empty buffer
|
||||
run(boost::asio::null_buffers{});
|
||||
}
|
||||
|
||||
// Called to start the object after the
|
||||
// listener accepts an incoming connection.
|
||||
//
|
||||
template<class ConstBufferSequence>
|
||||
void
|
||||
run(ConstBufferSequence const& buffers)
|
||||
{
|
||||
// Copy the data into the buffer for performing
|
||||
// HTTP reads, so that the bytes get used.
|
||||
//
|
||||
buffer_.commit(boost::asio::buffer_copy(
|
||||
buffer_.prepare(boost::asio::buffer_size(buffers)),
|
||||
buffers));
|
||||
|
||||
// Give the derived class a chance to do stuff
|
||||
//
|
||||
impl().do_handshake();
|
||||
}
|
||||
|
||||
protected:
|
||||
void
|
||||
do_run()
|
||||
{
|
||||
do_read_header();
|
||||
}
|
||||
|
||||
// Called when a failure occurs
|
||||
//
|
||||
void
|
||||
fail(std::string what, error_code ec)
|
||||
{
|
||||
// Don't log operation aborted since those happen normally.
|
||||
//
|
||||
if(ec && ec != boost::asio::error::operation_aborted)
|
||||
{
|
||||
log_ <<
|
||||
"[#" << id_ << " " << ep_ << "] " <<
|
||||
what << ": " << ec.message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform an asynchronous read for the next request header
|
||||
//
|
||||
void
|
||||
do_read_header()
|
||||
{
|
||||
// On each read the parser needs to be destroyed and
|
||||
// recreated. We store it in a boost::optional to
|
||||
// achieve that.
|
||||
//
|
||||
// Arguments passed to the parser constructor are
|
||||
// forwarded to the message object. A single argument
|
||||
// is forwarded to the body constructor.
|
||||
//
|
||||
// We construct the dynamic body with a 1MB limit
|
||||
// to prevent vulnerability to buffer attacks.
|
||||
//
|
||||
parser_.emplace(std::piecewise_construct, std::make_tuple(1024 * 1024));
|
||||
|
||||
// Read just the header
|
||||
boost::beast::http::async_read_header(
|
||||
impl().stream(),
|
||||
buffer_,
|
||||
*parser_,
|
||||
strand_.wrap(std::bind(
|
||||
&async_http_con_base::on_read_header,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// This lambda is passed to the service list to handle
|
||||
// the case of sending request objects of varying types.
|
||||
// In C++14 this is more easily accomplished using a generic
|
||||
// lambda, but we want C+11 compatibility so we manually
|
||||
// write the lambda out.
|
||||
//
|
||||
struct send_lambda
|
||||
{
|
||||
// holds "this"
|
||||
async_http_con_base& self_;
|
||||
|
||||
public:
|
||||
// capture "this"
|
||||
explicit
|
||||
send_lambda(async_http_con_base& self)
|
||||
: self_(self)
|
||||
{
|
||||
}
|
||||
|
||||
// sends a message
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
operator()(boost::beast::http::response<Body, Fields>&& res) const
|
||||
{
|
||||
self_.do_write(std::move(res));
|
||||
}
|
||||
};
|
||||
|
||||
// Called when the header has been read in
|
||||
void
|
||||
on_read_header(error_code ec)
|
||||
{
|
||||
// This happens when the other end closes gracefully
|
||||
//
|
||||
if(ec == boost::beast::http::error::end_of_stream)
|
||||
{
|
||||
// VFALCO what about the write queue?
|
||||
return impl().do_shutdown();
|
||||
}
|
||||
|
||||
// On failure we just return, the shared_ptr that is bound
|
||||
// into the completion will go out of scope and eventually
|
||||
// this will get destroyed.
|
||||
//
|
||||
if(ec)
|
||||
return fail("on_read", ec);
|
||||
|
||||
// The parser holds the request object,
|
||||
// at this point it only has the header in it.
|
||||
auto& req = parser_->get();
|
||||
|
||||
send_lambda send{*this};
|
||||
|
||||
// See if they are specifying Expect: 100-continue
|
||||
//
|
||||
if(rfc7231::is_expect_100_continue(req))
|
||||
{
|
||||
// They want to know if they should continue,
|
||||
// so send the appropriate response.
|
||||
//
|
||||
send(this->continue_100(req));
|
||||
}
|
||||
|
||||
// Read the rest of the message, if any.
|
||||
//
|
||||
boost::beast::http::async_read(
|
||||
impl().stream(),
|
||||
buffer_,
|
||||
*parser_,
|
||||
strand_.wrap(std::bind(
|
||||
&async_http_con_base::on_read,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// Called when the message is complete
|
||||
void
|
||||
on_read(error_code ec)
|
||||
{
|
||||
// Shouldn't be getting end_of_stream here;
|
||||
// that would mean that we got an incomplete
|
||||
// message, counting as an error.
|
||||
//
|
||||
if(ec)
|
||||
return fail("on_read", ec);
|
||||
|
||||
// Grab a reference to the request again
|
||||
auto& req = parser_->get();
|
||||
|
||||
// Create a variable for our send
|
||||
// lambda since we use it more than once.
|
||||
//
|
||||
send_lambda send{*this};
|
||||
|
||||
// Give each service a chance to handle the request
|
||||
//
|
||||
if(! services_.respond(
|
||||
std::move(impl().stream()),
|
||||
ep_,
|
||||
std::move(req),
|
||||
send))
|
||||
{
|
||||
// No service handled the request,
|
||||
// send a Bad Request result to the client.
|
||||
//
|
||||
send(this->bad_request(req));
|
||||
}
|
||||
else
|
||||
{
|
||||
// See if the service that handled the
|
||||
// response took ownership of the stream.
|
||||
//
|
||||
if(! impl().stream().lowest_layer().is_open())
|
||||
{
|
||||
// They took ownership so just return and
|
||||
// let this async_http_con_base object get destroyed.
|
||||
//
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// VFALCO Right now we do unlimited pipelining which
|
||||
// can lead to unbounded resource consumption.
|
||||
// A more sophisticated server might only issue
|
||||
// this read when the queue is below some limit.
|
||||
//
|
||||
|
||||
// Start reading another header
|
||||
do_read_header();
|
||||
}
|
||||
|
||||
// This function either queues a message or
|
||||
// starts writing it if no other writes are taking place.
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
do_write(boost::beast::http::response<Body, Fields>&& res)
|
||||
{
|
||||
// See if a write is in progress
|
||||
if(! writing_)
|
||||
{
|
||||
// An assert or two to keep things sane when
|
||||
// writing asynchronous code can be very helpful.
|
||||
BOOST_ASSERT(queue_.empty());
|
||||
|
||||
// We're going to be writing so set the flag
|
||||
writing_ = true;
|
||||
|
||||
// And now perform the write
|
||||
return async_write_msg(
|
||||
impl().stream(),
|
||||
std::move(res),
|
||||
strand_.wrap(std::bind(
|
||||
&async_http_con_base::on_write,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// Queue is not empty, so append this message to the queue.
|
||||
// It will be sent late when the queue empties.
|
||||
//
|
||||
queue_.emplace_back(make_queued_http_write(
|
||||
impl().stream(),
|
||||
std::move(res),
|
||||
strand_.wrap(std::bind(
|
||||
&async_http_con_base::on_write,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1))));
|
||||
}
|
||||
|
||||
// Called when a message finishes writing
|
||||
void
|
||||
on_write(error_code ec)
|
||||
{
|
||||
// Make sure our state is what we think it is
|
||||
BOOST_ASSERT(writing_);
|
||||
|
||||
// This happens when we send an HTTP message
|
||||
// whose semantics indicate that the connection
|
||||
// should be closed afterwards. For example if
|
||||
// we send a Connection: close.
|
||||
//
|
||||
if(ec == boost::beast::http::error::end_of_stream)
|
||||
return impl().do_shutdown();
|
||||
|
||||
// On failure just log and return
|
||||
if(ec)
|
||||
return fail("on_write", ec);
|
||||
|
||||
// See if the queue is empty
|
||||
if(queue_.empty())
|
||||
{
|
||||
// Queue was empty so clear the flag...
|
||||
writing_ = false;
|
||||
|
||||
// ...and return
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue was not empty, so invoke the object
|
||||
// at the head of the queue. This will start
|
||||
// another wrte.
|
||||
queue_.front()->invoke();
|
||||
|
||||
// Delete the item since we used it
|
||||
queue_.erase(queue_.begin());
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// This class represents an asynchronous HTTP connection which
|
||||
// uses a plain TCP/IP socket (no encryption) as the stream.
|
||||
//
|
||||
template<class... Services>
|
||||
class async_http_con
|
||||
|
||||
// Give this object the enable_shared_from_this, and have
|
||||
// the base class call impl().shared_from_this(). The reason
|
||||
// is so that the shared_ptr has the correct type. This lets
|
||||
// the derived class (this class) use its members in calls to
|
||||
// `std::bind`, without an ugly call to `dynamic_downcast` or
|
||||
// other nonsense.
|
||||
//
|
||||
: public std::enable_shared_from_this<async_http_con<Services...>>
|
||||
|
||||
// The stream should be created before the base class so
|
||||
// use the "base from member" idiom.
|
||||
//
|
||||
, public base_from_member<socket_type>
|
||||
|
||||
// Constructs last, destroys first
|
||||
//
|
||||
, public async_http_con_base<async_http_con<Services...>, Services...>
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
//
|
||||
// Additional arguments are forwarded to the base class
|
||||
//
|
||||
template<class... Args>
|
||||
async_http_con(
|
||||
socket_type&& sock,
|
||||
Args&&... args)
|
||||
: base_from_member<socket_type>(std::move(sock))
|
||||
, async_http_con_base<async_http_con<Services...>, Services...>(
|
||||
std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
// Returns the stream.
|
||||
//
|
||||
// The base class calls this to obtain the object to use for
|
||||
// reading and writing HTTP messages. This allows the same base
|
||||
// class to work with different return types for `stream()` such
|
||||
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
|
||||
//
|
||||
socket_type&
|
||||
stream()
|
||||
{
|
||||
return this->member;
|
||||
}
|
||||
|
||||
private:
|
||||
// Base class needs to be a friend to call our private members
|
||||
friend class async_http_con_base<async_http_con<Services...>, Services...>;
|
||||
|
||||
// This is called by the base before running the main loop.
|
||||
//
|
||||
void
|
||||
do_handshake()
|
||||
{
|
||||
// Run the main loop right away
|
||||
//
|
||||
this->do_run();
|
||||
}
|
||||
|
||||
// This is called when the other end closes the connection gracefully.
|
||||
//
|
||||
void
|
||||
do_shutdown()
|
||||
{
|
||||
error_code ec;
|
||||
stream().shutdown(socket_type::shutdown_both, ec);
|
||||
|
||||
// not_connected happens under normal
|
||||
// circumstances so don't bother reporting it.
|
||||
//
|
||||
if(ec && ec != boost::beast::errc::not_connected)
|
||||
return this->fail("shutdown", ec);
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* An asynchronous HTTP port handler
|
||||
|
||||
This type meets the requirements of @b PortHandler. It supports
|
||||
variable list of HTTP services in its template parameter list,
|
||||
and provides an asynchronous connection implementation to service
|
||||
*/
|
||||
template<class... Services>
|
||||
class http_async_port
|
||||
{
|
||||
// Reference to the server instance that made us
|
||||
server& instance_;
|
||||
|
||||
// The stream to log to
|
||||
std::ostream& log_;
|
||||
|
||||
// The list of services connections created from this port will support
|
||||
service_list<Services...> services_;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
|
||||
@param instance The server instance which owns this port
|
||||
|
||||
@param log The stream to use for logging
|
||||
*/
|
||||
http_async_port(
|
||||
server& instance,
|
||||
std::ostream& log)
|
||||
: instance_(instance)
|
||||
, log_(log)
|
||||
{
|
||||
}
|
||||
|
||||
/** Initialize a service
|
||||
|
||||
Every service in the list must be initialized exactly once.
|
||||
|
||||
@param args Optional arguments forwarded to the service
|
||||
constructor.
|
||||
|
||||
@tparam Index The 0-based index of the service to initialize.
|
||||
|
||||
@return A reference to the service list. This permits
|
||||
calls to be chained in a single expression.
|
||||
*/
|
||||
template<std::size_t Index, class... Args>
|
||||
void
|
||||
init(error_code& ec, Args&&... args)
|
||||
{
|
||||
services_.template init<Index>(
|
||||
ec,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/** Called by the server to provide ownership of the socket for a new connection
|
||||
|
||||
@param sock The socket whose ownership is to be transferred
|
||||
|
||||
@ep The remote endpoint
|
||||
*/
|
||||
void
|
||||
on_accept(socket_type&& sock, endpoint_type ep)
|
||||
{
|
||||
// Create a plain http connection object
|
||||
// and transfer ownership of the socket.
|
||||
//
|
||||
std::make_shared<async_http_con<Services...>>(
|
||||
std::move(sock),
|
||||
"http_async_port",
|
||||
log_,
|
||||
services_,
|
||||
instance_.next_id(),
|
||||
ep)->run();
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
@ -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
|
@ -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
|
@ -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
|
@ -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();
|
||||
}
|
@ -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
Reference in New Issue
Block a user