Squashed 'src/beast/' changes from 6d5547a..3bcd986

3bcd986 Set version to 79
4f42f8c Remove spurious fallthrough guidance
fa1ac16 Set version to 78
0cb9b63 Fix warning in root ca declaration
f78c73a Tidy up file_posix unused variable
72ce9ef Tidy up dstream for existing Boost versions
efe8e58 Add Boost.Locale utf8 benchmark comparison
f7c745e Remove string_view_body
7a79efa Tidy up FieldsReader doc
e51aefd Header file tidying
69898f4 Fix warning in zlib
53723c0 Add message keep_alive, chunked, content_length members
d7af73b Fix spurious uninitialized warning
ca42cc0 Tidy up invalid characters in test vector
48d3e60 Use make_unique_noinit
a1ff804 span, string, vector bodies are public
feab6a0 Documentation work
116c0b0 Add span_body
7fb901d Tidy up includes and javadocs
f0f58be Add span
7a8982b Add vector_body
524f73a Tidy up basic_string_body
a8ad67b Set version to 77
d555859 file_posix works without large file support
1bc30cb Set version to 76
9a1e7a8 Disable SSE4.2 optimizations
09af312 Fix parse illegal characters in obs-fold
7dd684c Add file_body_win32:
1bbc71c serializing file_body is not const
9a4b55e BodyReader, BodyWriter use two-phase init (API Change):
dc400ce Serializer members are not const
1a33c37 Rename to serializer::keep_alive (API Change):
55935c5 Add serializer::chunked
63ace37 Add serializer::get
2c4047b BodyReader may construct from a non-const message
0a0a225 Use Boost.Config
6f83d07 Always go through write_some:
0e23066 Set version to 75
28f3ece Doc tidying
3495331 Using SSE4.2 intrinsics in basic_parser if available
bc1f0ac file_body tests
4e03d7e Add serializer::limit
85e3ee8 Shrink serializer buffers using buffers_ref
78bcdb1 Tidy up BEAST_NO_BIG_VARIANTS
3ea6cf2 Construct buffer_prefix_view in-place
69f9f7a Use file_body for valid requests, string_body otherwise.
6f88f01 Set version to 74
93fed8e remove redundant flush() from example:
e0f56da Fix Beast include directories for cmake targets
5ff9e0a Add file_posix
1bb5705 Remove common/file_body.hpp
5c89d87 Add file_body
67a55c8 Add file_win32
647d3b0 Add file_stdio and File concept
89c416c Set version to 73
0efc32f Fixes for gcc-4.8
c8910ab Initialize local variable in basic_parser
8a28193 Adjust benchmarks
81e51d8 Verify certificates in SSL clients
a43f6d4 Jamroot tweak
8c85ee8 Put more... links on overload reference pages
ff1104e Documentation tidy
826ff0e serializer::next replaces serializer::get (API Change):
8d67775 Refactor header and message constructors:
6c79f19 Add basic_parser tests
25127d9 basic_parser optimizations:
9d082fd Set version to 72
c88e2b9 Various improvements to http_server_fast.cpp:
20b0fdb Documentation tidying
afd1fa7 Add websocket-server-async example
954b597 Add http-server-threaded example
df8f253 Refactor file_body for best practices
11c1037 Newly constructed responses have a 200 OK result
a648817 Refine Body::size specification
40aad37 Tidy up set payload in http-server-fast
52cefbc Set version to 71
8c51c77 Tidy up Jamroot /permissive-
5efecea Update README.md
5a47acd Tidy up http_sync_port error check
a2af2b5 Concept check in basic_dynamic_body
8b80a6f Fix buffer overflow handling for string_body and mutable_body
ec3b4fd Return std::size_t from Body::writer::put (API Change)
effbb37 Check trailers in test
f5368cf Call prepare_payload in HTTP example
a3e5e01 Fix spurious on_chunk invocation
96d94eb Add options for building examples and tests. Move zlib test sources to test/zlib
e0efdc7 Allow close, ping, and write to happen concurrently
9c1a419 Refactor websocket composed ops
d5659a4 Fine tune websocket asserts
b8e8943 std::pair "last" -> "first" in http_message.qbk
c691bf4 Fix can/cannot thinko in FAQ.
6dd006b Documentation revision
6d2e315 Fix extra ; warning
78a065b Set version to 70
00c7e9d Fix HEAD response in file_service
67d70d2 Fix BEAST_FALLTHROUGH in config
4f33655 Add parser::on_header to set a callback
9c16b21 Add basic_parser header and body limits:
b64d1f7 Rename to message::base (API Change):
436c66a Serialize in one step when possible
3e1061b Set version to 69
f709273 Add /permissive- to msvc toolchain
0dae464 Use BEAST_FALLTHROUGH to silence warnings
a70d386 basic_parser optimizations
4269f35 Set version to 68
544327f Link statically on cmake MSVC
e213ffe Add const_body, mutable_body to examples
0568763 Optimize field lookups
8fc3001 Use string_ref in older Boost versions
8af77da bad_target replaces bad_path (API Change):
325dd62 Adjust buffer size in fast server
be59785 Doc erratum
d9b44f3 Small speed up in fields comparisons
3e6ce38 Use Boost master on Appveyor
09f3d64 Split common tests to a new project
adfd22a Remove BodyReader::is_deferred (API Change):
582d28d Change BodyReader, BodyWriter requirements (API Change):
8982e14 Set version to 67
daa58a2 Group common example headers
afd8f1a Rename to http-server-fast
07cb9f7 control_callback replaces ping_callback (API Change):
91e83ed Use boost::string_view
067db87 Merge stream_base to stream and tidy
d61241a Add http-server-small example
eb08e92 Fix doc example link
7fb75d0 Set version to 66
df86723 Respect debug flag for marked output
c08565a Squelch spurious warning on gcc
188ef7c Documentation work
1c62d3a Add http-server example
3f54582 basic_fields optimizations
a8b05b8 Add header aliases
b94eac3 Tidy up message piecewise ctors
9c48b52 Handle bad_alloc in parser
1b57c54 Fix costly potential value-init in parser
1edc41e Make consuming_buffers smaller
72ac918 Add serializer request/response aliases
18f7606 string_param optimizations
c675252 Set version to 65
c398cdd Enable msvc warnings in Jamfile
380cceb Fix unused variable warnings
4172e7e Enable unused variable warning on msvc cmake
f04d227 Fix integer warnings
ca975b3 Fix narrowing in deflate_stream
2fab796 Fix narrowing in inflate_stream
cff87f6 Fix narrowing in ostream
1956886 Fix narrowing in static_ostream
69cdc4b Fix integer types in deflate_stream::bi_reverse
3e3dd21 Enable narrowing warning on msvc cmake
e11a294 Set version to 64
a00e070 Remove make_serializer (API Change):
8449697 Add link_directories to cmake
7b2b066 Doc tidying
158d3e8 async_write requires a non-const message:
d13328b Better User-Agent in examples
ebcb2c0 Exemplars are compiled code
b9054d3 Simplify websocket write_op
c2571fe Simplify ssl teardown composed op
d8ad3d1 Simplify buffered_read_stream composed op
f68dc34 Set version to 63
a99f7ef Control running with valgrind explicitly
4eb7af4 Tidy up Jamfiles
49b42a5 Tidy up CMakeLists.txt
dadb54f Only run the tests under ubasan
d1c7696 Move benchmarks to a separate project
a4aada8 Only build and run tests in variant=coverage
f835b9a Don't use cached Boost
a0edd82 Put num_jobs back up on Travis
a8d5823 Use std::to_string instead of lexical_cast
45d8b81 Set version to 62
09e07ce Put slow tests back for coverage builds
295b1d7 Doc tidy
f58425c Squelch harmless not_connected errors
9b537f7 Add http::is_fields trait
d43701b message::prepare_payload replaces message::prepare (API Change):
42ba289 Refine FieldsReader concept (API Change)
bde90a1 Narrow the use of Fields parameters:
5f47526 parser requires basic_fields (API Change):
60f58e4 Avoid explicit operator bool for error
352f8de Clear the error faster
34befd8 Tidy up namespaces in examples
9e0b4b5 Doc fixes and tidy
c003a2a Tidy up test build scripts and projects
b929130 Add server-framework tests
03d4301 Increase detail::static_ostream coverage
80d7cbc Remove libssl-dev from a Travis matrix item
4c15db4 Set version to 61
1dfbd0b Don't run slow tests on certain targets
6bb1109 Use one job less on CI
6f58342 Tidy up resolver calls
530b044 Add multi_port to server-framework
bfef5d1 Tidy up http-crawl example
e2f2f33 Reorganize SSL examples
adc301b Fix shadowing warnings
c60185e Add server-framework SSL HTTP and WebSocket ports
7912fb8 Refactor WebSocket, HTTP examples:
cd4b9e0 Flush the output stream in the example
d046b20 Tidy up names in error categories
9d4a422 status-codes is unsigned (API Change)
e3599b0 header::version is unsigned (API Change)
a26b043 Add message::header_part()
fc8d2e9 Tidy up some integer conversion warnings
c91732e Reorganize SSL examples
9907b31 Documentation work
4b2e78e Use generic_cateogry for errno
38c46cd Remove Spirit dependency
c111d6f Set version to 60
d78dc12 Documentation work
141a524 New server-framework, full featured server example:
3f7ffd9 Fix response message type in async websocket accept
13f3750 String comparisons are public interfaces
4e4bcf8 Set version to 59
5015cdb Remove obsolete doc/README.md
71c3f0a Fix base64 alphabet
aa2b843 Change Body::size signature (API Change):
80a599a Documentation work
9c19449 Integrated Beast interface.
3f8097b Set version to 58
4581777 Better message formal parameter names
5879cd8 Fix parsing chunk size with leading zeroes
56bd228 Remove redundant code
534ca63 Use static string in basic_fields::reader
a7b3810 basic_parser::put doc
1e4413f basic_fields::set optimization:
9b244c1 Fix basic_fields insert ordering
4f854d0 Avoid std::string in websocket
dc8f146 Renamed to basic_fields::set (API Change):
660c465 Specification for http read
981285b Documentation tidy
983d676 Reorganize examples:
a2a5c57 Qualify size_t in message template
d86769c Fix unaligned reads in utf8-checker
8ba182c Set version to 57
42e2791 Update doc/ for docca
1ee0afd Merge commit '101d7dbfb9725674cb9ce5a4196f19aa1d4bb801' as 'doc/docca'
101d7db Squashed 'doc/docca/' content from commit c50b3ba5
900c04e Documentation work
8eee932 Fix warning in basic_parser.cpp
437a616 Fix message.hpp javadocs
18c68ce Set version to 56
b058e90 Convert buffer in range loops
cbc9212 Add Beast INTERFACE CMake target
2914b59 More basic_parser tests
ed5c317 Reset error codes
ba14251 Test error code handling
e45e50b Tidy up README.md build instructions
16efb9b Try harder to find Boost (cmake)
e281d91 HTTP/1 is the default version
916fe4a Call on_chunk when the extension is empty
9855598 Add string_view_body
19d4520 Tidy up
6e59f9b Add provisional IANA header field names
84722f2 Revert "Add a Beast CMake interface target:"
fde6929 Set version to 55
01f6cc4 Documentation work
a7a388c read_size replaces read_size_helper:
ed8f0bb Tidy up type_traits
c2f6268 Avoid a parser allocation using non-flat buffer:
906db45 Add a Beast CMake interface target:
47f2541 Don't allocate memory to handle obs-fold
6a8912a Set version to 54
296ef3b Documentation work
e3e9b61 Fix incorrect use of [[fallthrough]]
3c44398 Retain ownership when reading using a message
a71bb2b basic_fields refactor (API Change):
d8d3562 Add string_param
83b2558 basic_fields members and coverage
c4f5fa5 consuming_buffers members and coverage
e10507c multi_buffer members and coverage
0e6bd3f flat_buffer coverage
7351d6e static_buffer coverage
18a8ef5 Set version to 53
452df59 Remove extraneous doc file
3ef0359 Fix read_size_helper usage:
b64e6d3 Fix basic_parser::maybe_flatten (#462)
76402f7 Set version to 52
b0ceb2a Add drain_buffer class
4c6735a flat_buffer is an AllocatorAwareContainer
9d5d4d5 Documentation work
d4ec693 finish(error_code&) is a BodyReader requirement (API Change)
7b24cad opcode is private (API Change):
068c2ac Documentation work
a1ff89b Disable std::future snippet for libstdc++ bug
b5ef664 read_frame returns `bool` fin (API Change):
7911847 Remove opcode from read, async_read (API Change):
c72d70f ping_callback is a member of stream (API Change):
720a309 write_buffer_size is a member of stream (API Change):
7ff0178 read_message_max is a member of stream (API Change):
cd40964 read_buffer_size is a member of stream (API Change):
a58e5e1 binary, text are members of stream (API Change):
ad35846 auto_fragment is a member of stream (API Change):
ccee139 Documentation work
13c64e3 Set version to 51
cafc8e2 Fix infinite loop in basic_parser
dc4b69d Add construct, destroy to handler_alloc
58c2739 multi_buffer implementation change (API Change):
dd7f5c0 DynamicBuffer benchmarks
1c4811b Use BOOST_STRINGIZE
31051ac Use BOOST_FALLTHROUGH
8f2430b Documentation work
eb35b92 Fix file_body::get() not setting the more flag correctly
566244a Tidy up file_body
53cbeea Tune up static_buffer (API Change):
20c59b7 Fix operator<< for header
a2c1117 Set version to 50
6045b74 http read_some, async_read_some don't return bytes (API Change):
4df6885 Fix chunk header parsing
36bf32b Fix test::pipe read_size
bf69ce1 Fix chunk delimiter parsing
0c6b6b1 Add missing handler_alloc nested types
a06b8f9 Tidy up message javadoc
3bd8260 Remove obsolete serializer allocator (API Change)
001c979 Remove message free functions (API Change)
745876b Remove message connection settings (API Change)
bcf2c33 Body documentation work
1e303b0 Fields concept work
9d0464a Tidy up basic_fields, header, and concepts
3ba81b5 Use field in basic_parser
b5f6cc1 Use field in basic_fields and call sites
cfd6d14 Documentation reference tidy
2adc80a Protect basic_fields special members (API Change)
d55b079 Fix basic_fields allocator awareness
d8febda Documentation work
485a6e5 Refactor prepare (API Change)
9fd3071 Derive header from Fields (API Change)
8ad26b8 Use allocator more in basic_fields
0071039 Add verb to on_request for parsers (API Change)
74f6cbb Add field enumeration
be0d74f Documentation fixes
054ac40 Remove header_parser (API Change)
a007eba parser is constructible from other body types
d89809f Documentation work (buffer_body)
ac5bc4f Set version to 49
af47128 Documentation work
a1848f0 Add HEAD request example
ddfbfbf Use <iosfwd> instead of <ostream>
e67c0ab Refactor header status, reason, and target (API Change):
60f044a Tidy up empty_body writer error
7d267f4 Canonicalize string_view parameter types
ac175cb Refactor method and verb (API Change):
e18efed Documentation work
d77e423 Set version to 48
d3a5a05 Documentation work
acf18fb Tidy up traits
6cb188e Remove detail::sync_ostream
d6092bc Documentation work
4707b21 Rename to parser (API Change):
3cb385d Consolidate parsers to parser.hpp
290bdf1 Documentation work
7cb442c Make buffer_prefix_view public
ef0b121 Rename to buffer_cat_view (API Change)
b9df187 Tidy up chunk decorator (API Change):
458fa7e Set version to 47
fc83a03 Documentation work
1ee3013 Fix leak in basic_flat_buffer
55fbf76 Fix undefined behavior in pausation
fe75a7c Refactor HTTP serialization and parsing (API Change):
50cba32 buffer_size overload for basic_multi_buffer::const_buffers_type
d977bf2 Disable operator<< for buffer_body
5db707a Refactor treatment of status code and obsolete reason (API Change):
9a585a8 Refactor treatment of request-method (API Change):
3ae76d0 Set version to 46
6004712 Documentation work
34ea0b3 Refactor serialization algorithms:
407b046 Rename to make_serializer
c29451a Refactor type_traits (API Change):
8578419 Refactor HTTP serialization (API Change):
f8612aa Remove HTTP header aliases (API Change):
b0054e3 Add test::pipe
dfba72b Set version to 45
6ba3697 Disable reverse_iterator buffer_view test
266ebac buffer_view skips empty buffer sequences
96b9892 Documentation work
c23f1e2 Fix header::reason
9796106 Better test::enable_yield_to
9a8bcb7 Fix message doc image
7a5e87e Workaround for boost::asio::basic_streambuf type check
663c275 Set version to 44
f205976 Make buffers_adapter meet requirements
8e39c60 Tidy up is_dynamic_buffer traits test
0088f7c Add buffers_adapter regression test
8a23de1 Fix README websocket example
949504a Fix async return values in docs
cd9f41b Use BOOST_STATIC_ASSERT
1b616fa Tidy up and make get_lowest_layer public
612e616 Require Boost 1.58 or later
1b1daa7 Tidy up read_size_helper and dynamic buffers
bf0145d Use BOOST_THROW_EXCEPTION
e762818 Add GitHub issue template
dab679c Set version to 43
386b817 Reformat README.md QR code
50e5123 Additional constructors for consuming_buffers
f7289b9 Add write limit to test::string_ostream
3aa87e0 Tidy up buffer_prefix overloads and test
bee583c Fix strict aliasing warnings in buffers_view
6b54d3a Require Boost 1.64.0
76f1084 Set version to 42
0bdb148 Make buffers_view a public interface
338fc81 Add formal review notes
784f965 Fix javadoc typo
823aee2 Set version to v41
88adbdd Remove handler helpers, tidy up hook invocations (API Change)
4974af2 Rename prepare_buffer(s) to buffer_prefix (API Change)
ebd459b Tidy up websocket::close_code enum and constructors
c3fd6f9 Tidy up formal parameter names
210cd70 Remove coveralls integration
d811962 Concept revision and documentation (API Change):
bdae92a Replace asynchronous helper macros with template aliases (API Change)
df95a09 Move prepare_buffers to prepare_buffer.hpp (API Change)
787de21 Remove placeholders (API Change)
c59b544 Trim Appveyor matrix rows
b7184f3 Return http::error::end_of_stream on HTTP read eof (API Change)
f2d8255 Set version to 40
40b9194 Tidy up .travis.yml:
9b240c7 Fix basic_streambuf movable trait
76a2617 Consolidate get_lowest_layer in type_traits.hpp
6d00321 Add to_static_string:
f888136 Set version to 39
47c82b5 Better travis deps
4ed7865 Squelch openssl spurious leak and memory errors
b6bc26f Fixed braced-init error with older gcc
59b2f8f ostream workaround for gcc 4.8.4
8363d86 Increase ostream test coverage
5631936 Tidy up HTTP reason_string (API Change):
2bf5150 Harmonize concepts and identifiers with net-ts (API Change):
728e9d8 Tidy up basic_parser javadocs
1c9067b Use beast::string_view alias
771c5ca Doc fixes and tidying
e2b5c31 Rename to buffered_read_stream (API Change):
a753f1c Rename to static_buffer, static_buffer_n (API Change):
24b6686 Rename to flat_buffer, basic_flat_buffer (API Change):
69259ef Rename to multi_buffer, basic_multi_buffer (API Change):
bef9ae1 New buffers() replaces to_string() (API Change):
a7ef4f5 New ostream() returns dynamic buffer output stream (API Change):
87fd60c Fix eof error on ssl::stream shutdown
606fc9d Add websocket async echo ssl server test:
ff5e659 Refactor http::header contents (API Change):
dd02097 Set version to 1.0.0-b38
5596e97 Prevent basic_fields operator[] assignment
c2b32dc Remove websocket::keep_alive option (API Change):
32dbfb2 Refactor WebSocket error codes (API Change):
dd6b500 WebSocket doc work
0b4d87c More flat_streambuf tests
aacefb4 Add test_allocator to extras/test
931a5fb Simplify get_lowest_layer test
ba4228a Use static_string for WebSocket handshakes:
6df3ff3 Refactor base64:
19b124d Refactor static_string:
30e8d16 Set version to 1.0.0-b37
b141020 Fix narrowing warning in table constants
d554b81 Add -funsigned-char to asan build target
bcc6ad8 Add ub sanitizer blacklist
e1f08e9 Fix flat_streambuf:
7d08f59 Fix typo in documentation example
21ef97d Rename to http::dynamic_body, consolidate header:
45a2d73 Rename project to http-bench
c86fee9 Move everything in basic_fields.hpp to fields.hpp (API Change)
a14a5d6 Rename to detail::is_invocable
540d037 Rename to websocket::detail::pausation
84e1739 Document websocket::stream thread safety
dc274af Add is_upgrade() free function:
2c17d04 Refactor websocket decorators (API Change):
235fe68 Provide websocket::stream accept() overloads (API Change):
a715825 CMake hide command lines in .vcxproj Output windows
32024d8 Set version to 1.0.0-b36
f48b95f Update README.md
d8db5f1 Set version to 1.0.0-b35
dd2a514 Tidy up doc declarations
2c50aba Fix README.md CMake instructions
4ffdce2 Update .gitignore for VS2017
403011f Remove http::empty_body (API Change)
f47b661 New HTTP interfaces (API Change):
f6835b8 Rename to BEAST_DOXYGEN
7e37723 Add flat_streambuf:
5b68faa Doc XSL support for list and table markdown
3de46de Make websocket::close_code a proper enum:
0128743 Tidy up MSVC CMake configuration
ccd188e Add appveyor build script

git-subtree-dir: src/beast
git-subtree-split: 3bcd9865f80f12ba5faad35c564918f85b02e271
This commit is contained in:
Miguel Portilla
2017-07-11 12:17:02 -04:00
parent d8dea963fa
commit f0f96bd1da
487 changed files with 60952 additions and 23793 deletions

16
example/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
# Part of 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)
if (OPENSSL_FOUND)
add_subdirectory (http-client-ssl)
add_subdirectory (websocket-client-ssl)
endif()

20
example/Jamfile Normal file
View File

@@ -0,0 +1,20 @@
#
# 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)
#
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 ;
# 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 ;

View File

@@ -0,0 +1,481 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP
#define BEAST_EXAMPLE_COMMON_DETECT_SSL_HPP
#include <boost/assert.hpp>
#include <boost/config.hpp>
//------------------------------------------------------------------------------
//
// Example: Detect TLS/SSL
//
//------------------------------------------------------------------------------
//[example_core_detect_ssl_1
#include <beast.hpp>
#include <boost/logic/tribool.hpp>
/** Return `true` if a buffer contains a TLS/SSL client handshake.
This function returns `true` if the beginning of the buffer
indicates that a TLS handshake is being negotiated, and that
there are at least four octets in the buffer.
If the content of the buffer cannot possibly be a TLS handshake
request, the function returns `false`. Otherwise, if additional
octets are required, `boost::indeterminate` is returned.
@param buffer The input buffer to inspect. This type must meet
the requirements of @b ConstBufferSequence.
@return `boost::tribool` indicating whether the buffer contains
a TLS client handshake, does not contain a handshake, or needs
additional octets.
@see
http://www.ietf.org/rfc/rfc2246.txt
7.4. Handshake protocol
*/
template<class ConstBufferSequence>
boost::tribool
is_ssl_handshake(ConstBufferSequence const& buffers);
//]
using namespace beast;
//[example_core_detect_ssl_2
template<
class ConstBufferSequence>
boost::tribool
is_ssl_handshake(
ConstBufferSequence const& buffers)
{
// Make sure buffers meets the requirements
static_assert(is_const_buffer_sequence<ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
// We need at least one byte to really do anything
if(boost::asio::buffer_size(buffers) < 1)
return boost::indeterminate;
// Extract the first byte, which holds the
// "message" type for the Handshake protocol.
unsigned char v;
boost::asio::buffer_copy(boost::asio::buffer(&v, 1), buffers);
// Check that the message type is "SSL Handshake" (rfc2246)
if(v != 0x16)
{
// This is definitely not a handshake
return false;
}
// At least four bytes are needed for the handshake
// so make sure that we get them before returning `true`
if(boost::asio::buffer_size(buffers) < 4)
return boost::indeterminate;
// This can only be a TLS/SSL handshake
return true;
}
//]
//[example_core_detect_ssl_3
/** Detect a TLS/SSL handshake on a stream.
This function reads from a stream to determine if a TLS/SSL
handshake is being received. The function call will block
until one of the following conditions is true:
@li The disposition of the handshake is determined
@li An error occurs
Octets read from the stream will be stored in the passed dynamic
buffer, which may be used to perform the TLS handshake if the
detector returns true, or otherwise consumed by the caller based
on the expected protocol.
@param stream The stream to read from. This type must meet the
requirements of @b SyncReadStream.
@param buffer The dynamic buffer to use. This type must meet the
requirements of @b DynamicBuffer.
@param ec Set to the error if any occurred.
@return `boost::tribool` indicating whether the buffer contains
a TLS client handshake, does not contain a handshake, or needs
additional octets. If an error occurs, the return value is
undefined.
*/
template<
class SyncReadStream,
class DynamicBuffer>
boost::tribool
detect_ssl(
SyncReadStream& stream,
DynamicBuffer& buffer,
error_code& ec)
{
// Make sure arguments meet the requirements
static_assert(is_sync_read_stream<SyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(is_dynamic_buffer<DynamicBuffer>::value,
"DynamicBuffer requirements not met");
// Loop until an error occurs or we get a definitive answer
for(;;)
{
// There could already be data in the buffer
// so we do this first, before reading from the stream.
auto const result = is_ssl_handshake(buffer.data());
// If we got an answer, return it
if(! boost::indeterminate(result))
{
ec = {}; // indicate no error
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());
// Try to fill our buffer by reading from the stream
std::size_t const bytes_transferred = stream.read_some(mutable_buffer, ec);
// Check for an error
if(ec)
break;
// Commit what we read into the buffer's input area.
buffer.commit(bytes_transferred);
}
// error
return false;
}
//]
//[example_core_detect_ssl_4
/** Detect a TLS/SSL handshake asynchronously on a stream.
This function is used to asynchronously determine if a TLS/SSL
handshake is being received.
The function call always returns immediately. The asynchronous
operation will continue until one of the following conditions
is true:
@li The disposition of the handshake is determined
@li An error occurs
This operation is implemented in terms of zero or more calls to
the next layer's `async_read_some` function, and is known as a
<em>composed operation</em>. The program must ensure that the
stream performs no other operations until this operation completes.
Octets read from the stream will be stored in the passed dynamic
buffer, which may be used to perform the TLS handshake if the
detector returns true, or otherwise consumed by the caller based
on the expected protocol.
@param stream The stream to read from. This type must meet the
requirements of @b AsyncReadStream.
@param buffer The dynamic buffer to use. This type must meet the
requirements of @b DynamicBuffer.
@param handler The handler to be called when the request
completes. Copies will be made of the handler as required.
The equivalent function signature of the handler must be:
@code
void handler(
error_code const& error, // Set to the error, if any
boost::tribool result // The result of the detector
);
@endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<
class AsyncReadStream,
class DynamicBuffer,
class CompletionToken>
async_return_type< /*< The [link beast.ref.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 >*/
async_detect_ssl(
AsyncReadStream& stream,
DynamicBuffer& buffer,
CompletionToken&& token);
//]
//[example_core_detect_ssl_5
// This is the composed operation.
template<
class AsyncReadStream,
class DynamicBuffer,
class Handler>
class detect_ssl_op;
// Here is the implementation of the asynchronous initation function
template<
class AsyncReadStream,
class DynamicBuffer,
class CompletionToken>
async_return_type<
CompletionToken,
void(error_code, boost::tribool)>
async_detect_ssl(
AsyncReadStream& stream,
DynamicBuffer& buffer,
CompletionToken&& token)
{
// Make sure arguments meet the requirements
static_assert(is_async_read_stream<AsyncReadStream>::value,
"SyncReadStream requirements not met");
static_assert(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.
//
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
// to convert the completion token into the correct handler type,
// 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)>>{
stream, buffer, init.completion_handler}(
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
// CompletionToken is boost::asio::use_future.
//
// If a coroutine is used for the token, the return value from
// this function will be the `boost::tribool` representing the result.
//
return init.result.get();
}
//]
//[example_core_detect_ssl_6
// Read from a stream to invoke is_tls_handshake asynchronously
//
template<
class AsyncReadStream,
class DynamicBuffer,
class Handler>
class detect_ssl_op
{
// This composed operation has trivial state,
// so it is just kept inside the class and can
// be cheaply copied as needed by the implementation.
// Indicates what step in the operation's state
// machine to perform next, starting from zero.
int step_ = 0;
AsyncReadStream& stream_;
DynamicBuffer& buffer_;
Handler handler_;
boost::tribool result_ = false;
public:
// Boost.Asio requires that handlers are CopyConstructible.
// The state for this operation is cheap to copy.
detect_ssl_op(detect_ssl_op const&) = default;
// The constructor just keeps references the callers varaibles.
//
template<class DeducedHandler>
detect_ssl_op(AsyncReadStream& stream,
DynamicBuffer& buffer, DeducedHandler&& handler)
: stream_(stream)
, buffer_(buffer)
, handler_(std::forward<DeducedHandler>(handler))
{
}
// Determines if the next asynchronous operation represents a
// continuation of the asynchronous flow of control associated
// with the final handler. If we are past step two, it means
// we have performed an asynchronous operation therefore any
// subsequent operation would represent a continuation.
// Otherwise, we propagate the handler's associated value of
// is_continuation. Getting this right means the implementation
// may schedule the invokation of the invoked functions more
// efficiently.
//
friend bool asio_handler_is_continuation(detect_ssl_op* op)
{
// This next call is structured to permit argument
// dependent lookup to take effect.
using boost::asio::asio_handler_is_continuation;
// Always use std::addressof to pass the pointer to the handler,
// otherwise an unwanted overload of operator& may be called instead.
return op->step_ > 2 ||
asio_handler_is_continuation(std::addressof(op->handler_));
}
// Handler hook forwarding. These free functions invoke the hooks
// associated with the final completion handler. In effect, they
// make the Asio implementation treat our composed operation the
// same way it would treat the final completion handler for the
// purpose of memory allocation and invocation.
//
// Our implementation just passes through the call to the hook
// associated with the final handler.
friend void* asio_handler_allocate(std::size_t size, detect_ssl_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(size, std::addressof(op->handler_));
}
friend void asio_handler_deallocate(void* p, std::size_t size, detect_ssl_op* op)
{
using boost::asio::asio_handler_deallocate;
return asio_handler_deallocate(p, size, std::addressof(op->handler_));
}
template<class Function>
friend void asio_handler_invoke(Function&& f, detect_ssl_op* op)
{
using boost::asio::asio_handler_invoke;
return asio_handler_invoke(f, std::addressof(op->handler_));
}
// Our main entry point. This will get called as our
// intermediate operations complete. Definition below.
//
void operator()(beast::error_code ec, std::size_t bytes_transferred);
};
//]
//[example_core_detect_ssl_7
// detect_ssl_op is callable with the signature
// void(error_code, bytes_transferred),
// allowing `*this` to be used as a ReadHandler
//
template<
class AsyncStream,
class DynamicBuffer,
class Handler>
void
detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
operator()(beast::error_code ec, std::size_t bytes_transferred)
{
// Execute the state machine
switch(step_)
{
// Initial state
case 0:
// See if we can detect the handshake
result_ = is_ssl_handshake(buffer_.data());
// If there's a result, call the handler
if(! boost::indeterminate(result_))
{
// We need to invoke the handler, but the guarantee
// is that the handler will not be called before the
// call to async_detect_ssl returns, so we must post
// the operation to the io_service. The helper function
// `bind_handler` lets us bind arguments in a safe way
// that preserves the type customization hooks of the
// original handler.
step_ = 1;
return stream_.get_io_service().post(
bind_handler(std::move(*this), ec, 0));
}
// The algorithm should never need more than 4 bytes
BOOST_ASSERT(buffer_.size() < 4);
step_ = 2;
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));
case 1:
// Call the handler
break;
case 2:
// Set this so that asio_handler_is_continuation knows that
// the next asynchronous operation represents a continuation
// of the initial asynchronous operation.
step_ = 3;
BOOST_FALLTHROUGH;
case 3:
if(ec)
{
// Deliver the error to the handler
result_ = false;
// We don't need bind_handler here because we were invoked
// as a result of an intermediate asynchronous operation.
break;
}
// Commit the bytes that we read
buffer_.commit(bytes_transferred);
// See if we can detect the handshake
result_ = is_ssl_handshake(buffer_.data());
// If it is detected, call the handler
if(! boost::indeterminate(result_))
{
// We don't need bind_handler here because we were invoked
// as a result of an intermediate asynchronous operation.
break;
}
// Read some more
goto do_read;
}
// Invoke the final handler.
handler_(ec, result_);
}
//]
#endif

View File

@@ -0,0 +1,56 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_COMMON_HELPERS_HPP
#define BEAST_EXAMPLE_COMMON_HELPERS_HPP
#include <boost/asio/io_service.hpp>
#include <boost/asio/signal_set.hpp>
#include <ostream>
#include <sstream>
/// Block until SIGINT or SIGTERM is received.
inline
void
sig_wait()
{
boost::asio::io_service ios{1};
boost::asio::signal_set signals(ios, SIGINT, SIGTERM);
signals.async_wait([&](boost::system::error_code const&, int){});
ios.run();
}
namespace detail {
inline
void
print_1(std::ostream&)
{
}
template<class T1, class... TN>
void
print_1(std::ostream& os, T1 const& t1, TN const&... tn)
{
os << t1;
print_1(os, tn...);
}
} // detail
// compose a string to std::cout or std::cerr atomically
//
template<class...Args>
void
print(std::ostream& os, Args const&... args)
{
std::stringstream ss;
detail::print_1(ss, args...);
os << ss.str() << std::endl;
}
#endif

View File

@@ -0,0 +1,46 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_COMMON_MIME_TYPES_HPP
#define BEAST_EXAMPLE_COMMON_MIME_TYPES_HPP
#include <beast/core/string.hpp>
#include <boost/filesystem/path.hpp>
// Return a reasonable mime type based on the extension of a file.
//
template<class = void>
beast::string_view
mime_type(boost::filesystem::path const& path)
{
using beast::iequals;
auto const ext = path.extension().string();
if(iequals(ext, ".txt")) return "text/plain";
if(iequals(ext, ".htm")) return "text/html";
if(iequals(ext, ".html")) return "text/html";
if(iequals(ext, ".php")) return "text/html";
if(iequals(ext, ".css")) return "text/css";
if(iequals(ext, ".js")) return "application/javascript";
if(iequals(ext, ".json")) return "application/json";
if(iequals(ext, ".xml")) return "application/xml";
if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
if(iequals(ext, ".flv")) return "video/x-flv";
if(iequals(ext, ".png")) return "image/png";
if(iequals(ext, ".jpe")) return "image/jpeg";
if(iequals(ext, ".jpeg")) return "image/jpeg";
if(iequals(ext, ".jpg")) return "image/jpeg";
if(iequals(ext, ".gif")) return "image/gif";
if(iequals(ext, ".bmp")) return "image/bmp";
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
if(iequals(ext, ".tiff")) return "image/tiff";
if(iequals(ext, ".tif")) return "image/tiff";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
#endif

View File

@@ -0,0 +1,39 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_COMMON_RFC7231_HPP
#define BEAST_EXAMPLE_COMMON_RFC7231_HPP
#include <beast/core/string.hpp>
#include <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(beast::http::request<
Body, beast::http::basic_fields<Allocator>> const& req)
{
return beast::iequals(
req[beast::http::field::expect], "100-continue");
}
} // rfc7231
#endif

View File

@@ -0,0 +1,118 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_COMMON_ROOT_CERTIFICATES_HPP
#define BEAST_EXAMPLE_COMMON_ROOT_CERTIFICATES_HPP
#include <boost/asio/ssl.hpp>
#include <string>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
namespace detail {
// The template argument is gratuituous, to
// allow the implementation to be header-only.
//
template<class = void>
void
load_root_certificates(ssl::context& ctx, boost::system::error_code& ec)
{
std::string const cert =
/* This is the DigiCert root certificate.
CN = DigiCert High Assurance EV Root CA
OU = www.digicert.com
O = DigiCert Inc
C = US
Valid to: Sunday, ?November ?9, ?2031 5:00:00 PM
Thumbprint(sha1):
5f b7 ee 06 33 e2 59 db ad 0c 4c 9a e6 d3 8f 1a 61 c7 dc 25
*/
"-----BEGIN CERTIFICATE-----\n"
"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n"
"ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n"
"MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n"
"LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n"
"RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n"
"+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n"
"PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n"
"xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n"
"Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n"
"hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n"
"EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n"
"MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n"
"FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n"
"nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n"
"eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n"
"hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n"
"Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n"
"vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n"
"+OkuE6N36B9K\n"
"-----END CERTIFICATE-----\n"
/* This is the GeoTrust root certificate.
CN = GeoTrust Global CA
O = GeoTrust Inc.
C = US
Valid to: Friday, May 20, 2022 9:00:00 PM
Thumbprint(sha1):
de 28 f4 a4 ff e5 b9 2f a3 c5 03 d1 a3 49 a7 f9 96 2a 82 12
*/
"-----BEGIN CERTIFICATE-----\n"
"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n"
"ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n"
"MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n"
"LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n"
"RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n"
"+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n"
"PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n"
"xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n"
"Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n"
"hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n"
"EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n"
"MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n"
"FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n"
"nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n"
"eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n"
"hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n"
"Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n"
"vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n"
"+OkuE6N36B9K\n"
"-----END CERTIFICATE-----\n"
;
ctx.add_certificate_authority(
boost::asio::buffer(cert.data(), cert.size()), ec);
if(ec)
return;
}
} // detail
// Load the root certificates into an ssl::context
//
// This function is inline so that its easy to take
// the address and there's nothing weird like a
// gratuituous template argument; thus it appears
// like a "normal" function.
//
inline
void
load_root_certificates(ssl::context& ctx, boost::system::error_code& ec)
{
detail::load_root_certificates(ctx, ec);
}
#endif

View File

@@ -0,0 +1,334 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_COMMON_SSL_STREAM_HPP
#define BEAST_EXAMPLE_COMMON_SSL_STREAM_HPP
// This include is necessary to work with `ssl::stream` and `beast::websocket::stream`
#include <beast/websocket/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
/** C++11 enabled SSL socket wrapper
This wrapper provides an interface identical to `boost::asio::ssl::stream`,
with the following additional properties:
@li Satisfies @b MoveConstructible
@li Satisfies @b MoveAssignable
@li Constructible from a moved socket.
*/
template<class NextLayer>
class ssl_stream
: public boost::asio::ssl::stream_base
{
// only works for boost::asio::ip::tcp::socket
// for now because of the move limitations
static_assert(std::is_same<NextLayer, boost::asio::ip::tcp::socket>::value,
"NextLayer requirements not met");
using stream_type = boost::asio::ssl::stream<NextLayer>;
std::unique_ptr<stream_type> p_;
boost::asio::ssl::context* ctx_;
public:
/// The native handle type of the SSL stream.
using native_handle_type = typename stream_type::native_handle_type;
/// Structure for use with deprecated impl_type.
using impl_struct = typename stream_type::impl_struct;
/// (Deprecated: Use native_handle_type.) The underlying implementation type.
using impl_type = typename stream_type::impl_type;
/// The type of the next layer.
using next_layer_type = typename stream_type::next_layer_type;
/// 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})
, ctx_(&ctx)
{
p_->next_layer() = std::move(sock);
}
ssl_stream(ssl_stream&& other)
: p_(new stream_type(other.get_io_service(), *other.ctx_))
, ctx_(other.ctx_)
{
using std::swap;
swap(p_, other.p_);
}
ssl_stream& operator=(ssl_stream&& other)
{
std::unique_ptr<stream_type> p(
new stream_type{other.get_io_service(), other.ctx_});
using std::swap;
swap(p_, p);
swap(p_, other.p_);
ctx_ = other.ctx_;
return *this;
}
boost::asio::io_service&
get_io_service()
{
return p_->get_io_service();
}
native_handle_type
native_handle()
{
return p_->native_handle();
}
impl_type
impl()
{
return p_->impl();
}
next_layer_type const&
next_layer() const
{
return p_->next_layer();
}
next_layer_type&
next_layer()
{
return p_->next_layer();
}
lowest_layer_type&
lowest_layer()
{
return p_->lowest_layer();
}
lowest_layer_type const&
lowest_layer() const
{
return p_->lowest_layer();
}
void
set_verify_mode(boost::asio::ssl::verify_mode v)
{
p_->set_verify_mode(v);
}
boost::system::error_code
set_verify_mode(boost::asio::ssl::verify_mode v,
boost::system::error_code& ec)
{
return p_->set_verify_mode(v, ec);
}
void
set_verify_depth(int depth)
{
p_->set_verify_depth(depth);
}
boost::system::error_code
set_verify_depth(
int depth, boost::system::error_code& ec)
{
return p_->set_verify_depth(depth, ec);
}
template<class VerifyCallback>
void
set_verify_callback(VerifyCallback callback)
{
p_->set_verify_callback(callback);
}
template<class VerifyCallback>
boost::system::error_code
set_verify_callback(VerifyCallback callback,
boost::system::error_code& ec)
{
return p_->set_verify_callback(callback, ec);
}
void
handshake(handshake_type type)
{
p_->handshake(type);
}
boost::system::error_code
handshake(handshake_type type,
boost::system::error_code& ec)
{
return p_->handshake(type, ec);
}
template<class ConstBufferSequence>
void
handshake(
handshake_type type, ConstBufferSequence const& buffers)
{
p_->handshake(type, buffers);
}
template<class ConstBufferSequence>
boost::system::error_code
handshake(handshake_type type,
ConstBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->handshake(type, buffers, ec);
}
template<class HandshakeHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler,
void(boost::system::error_code))
async_handshake(handshake_type type,
BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler)
{
return p_->async_handshake(type,
BOOST_ASIO_MOVE_CAST(HandshakeHandler)(handler));
}
template<class ConstBufferSequence, class BufferedHandshakeHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler,
void (boost::system::error_code, std::size_t))
async_handshake(handshake_type type, ConstBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler)
{
return p_->async_handshake(type, buffers,
BOOST_ASIO_MOVE_CAST(BufferedHandshakeHandler)(handler));
}
void
shutdown()
{
p_->shutdown();
}
boost::system::error_code
shutdown(boost::system::error_code& ec)
{
return p_->shutdown(ec);
}
template<class ShutdownHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler,
void (boost::system::error_code))
async_shutdown(BOOST_ASIO_MOVE_ARG(ShutdownHandler) handler)
{
return p_->async_shutdown(
BOOST_ASIO_MOVE_CAST(ShutdownHandler)(handler));
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers)
{
return p_->write_some(buffers);
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->write_some(buffers, ec);
}
template<class ConstBufferSequence, class WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
void (boost::system::error_code, std::size_t))
async_write_some(ConstBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(WriteHandler) handler)
{
return p_->async_write_some(buffers,
BOOST_ASIO_MOVE_CAST(WriteHandler)(handler));
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
return p_->read_some(buffers);
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->read_some(buffers, ec);
}
template<class MutableBufferSequence, class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
void(boost::system::error_code, std::size_t))
async_read_some(MutableBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
{
return p_->async_read_some(buffers,
BOOST_ASIO_MOVE_CAST(ReadHandler)(handler));
}
template<class SyncStream>
friend
void
teardown(beast::websocket::teardown_tag,
ssl_stream<SyncStream>& stream,
boost::system::error_code& ec);
template<class AsyncStream, class TeardownHandler>
friend
void
async_teardown(beast::websocket::teardown_tag,
ssl_stream<AsyncStream>& stream, TeardownHandler&& handler);
};
// These hooks are used to inform beast::websocket::stream on
// how to tear down the connection as part of the WebSocket
// protocol specifications
template<class SyncStream>
inline
void
teardown(beast::websocket::teardown_tag,
ssl_stream<SyncStream>& stream,
boost::system::error_code& ec)
{
// Just forward it to the wrapped ssl::stream
using beast::websocket::teardown;
teardown(beast::websocket::teardown_tag{}, *stream.p_, ec);
}
template<class AsyncStream, class TeardownHandler>
inline
void
async_teardown(beast::websocket::teardown_tag,
ssl_stream<AsyncStream>& stream, TeardownHandler&& handler)
{
// Just forward it to the wrapped ssl::stream
using beast::websocket::async_teardown;
async_teardown(beast::websocket::teardown_tag{},
*stream.p_, std::forward<TeardownHandler>(handler));
}
#endif

View File

@@ -0,0 +1,228 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_COMMON_WRITE_MSG_HPP
#define BEAST_EXAMPLE_COMMON_WRITE_MSG_HPP
#include <beast/core/async_result.hpp>
#include <beast/core/handler_ptr.hpp>
#include <beast/core/type_traits.hpp>
#include <beast/http/message.hpp>
#include <beast/http/write.hpp>
#include <beast/http/type_traits.hpp>
#include <boost/asio/handler_alloc_hook.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
namespace detail {
/** Composed operation to send an HTTP message
This implements the composed operation needed for the
@ref async_write_msg function.
*/
template<
class AsyncWriteStream,
class Handler,
bool isRequest, class Body, class Fields>
class write_msg_op
{
// This composed operation has a state which is not trivial
// to copy (msg) so we need to store the state in an allocated
// object.
//
struct data
{
// The stream we are writing to
AsyncWriteStream& stream;
// The message we are sending. Note that this composed
// operation takes ownership of the message and destroys
// it when it is done.
//
beast::http::message<isRequest, Body, Fields> msg;
// Serializer for the message
beast::http::serializer<isRequest, Body, Fields> sr;
data(
Handler& handler,
AsyncWriteStream& stream_,
beast::http::message<isRequest, Body, Fields>&& msg_)
: stream(stream_)
, msg(std::move(msg_))
, sr(msg)
{
boost::ignore_unused(handler);
}
};
// `handler_ptr` is a utility which helps to manage a composed
// operation's state. It is similar to a shared pointer, but
// it uses handler allocation hooks to allocate and free memory,
// and it also helps to meet Asio's deallocate-before-invocation
// guarantee.
//
beast::handler_ptr<data, Handler> d_;
public:
// Asio can move and copy the handler, we support both
write_msg_op(write_msg_op&&) = default;
write_msg_op(write_msg_op const&) = default;
// Constructor
//
// We take the handler as a template type to
// support both const and rvalue references.
//
template<
class DeducedHandler,
class... Args>
write_msg_op(
DeducedHandler&& h,
AsyncWriteStream& s,
Args&&... args)
: d_(std::forward<DeducedHandler>(h),
s, std::forward<Args>(args)...)
{
}
// Entry point
//
// The initiation function calls this to start the operation
//
void
operator()()
{
auto& d = *d_;
beast::http::async_write(
d.stream, d.sr, std::move(*this));
}
// Completion handler
//
// This gets called when beast::http::async_write completes
//
void
operator()(beast::error_code ec)
{
d_.invoke(ec);
}
//
// These hooks are necessary for Asio
//
// The meaning is explained in the Beast documentation
//
friend
void* asio_handler_allocate(
std::size_t size, write_msg_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(
size, std::addressof(op->d_.handler()));
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_msg_op* op)
{
using boost::asio::asio_handler_deallocate;
asio_handler_deallocate(
p, size, std::addressof(op->d_.handler()));
}
friend
bool asio_handler_is_continuation(write_msg_op* op)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(std::addressof(op->d_.handler()));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_msg_op* op)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(
f, std::addressof(op->d_.handler()));
}
};
} // detail
/** Write an HTTP message to a stream asynchronously
This function is used to write a complete message to a stream asynchronously
using HTTP/1. The function call always returns immediately. The asynchronous
operation will continue until one of the following conditions is true:
@li The entire message is written.
@li An error occurs.
This operation is implemented in terms of zero or more calls to the stream's
`async_write_some` function, and is known as a <em>composed operation</em>.
The program must ensure that the stream performs no other write operations
until this operation completes. The algorithm will use a temporary
@ref serializer with an empty chunk decorator to produce buffers. If
the semantics of the message indicate that the connection should be
closed after the message is sent, the error delivered by this function
will be @ref error::end_of_stream
@param stream The stream to which the data is to be written.
The type must support the @b AsyncWriteStream concept.
@param msg The message to write. The function will take ownership
of the object as if by move constrction.
@param handler The handler to be called when the operation
completes. Copies will be made of the handler as required.
The equivalent function signature of the handler must be:
@code void handler(
error_code const& error // result of operation
); @endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<
class AsyncWriteStream,
bool isRequest, class Body, class Fields,
class WriteHandler>
beast::async_return_type<WriteHandler, void(beast::error_code)>
async_write_msg(
AsyncWriteStream& stream,
beast::http::message<isRequest, Body, Fields>&& msg,
WriteHandler&& handler)
{
static_assert(
beast::is_async_write_stream<AsyncWriteStream>::value,
"AsyncWriteStream requirements not met");
static_assert(beast::http::is_body<Body>::value,
"Body requirements not met");
static_assert(beast::http::is_body_reader<Body>::value,
"BodyReader requirements not met");
beast::async_completion<WriteHandler, void(beast::error_code)> init{handler};
::detail::write_msg_op<
AsyncWriteStream,
beast::handler_type<WriteHandler, void(beast::error_code)>,
isRequest, Body, Fields>{
init.completion_handler,
stream,
std::move(msg)}();
return init.result.get();
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/echo-op "/")
add_executable (echo-op
${BEAST_INCLUDES}
echo_op.cpp
)
target_link_libraries(echo-op Beast)

13
example/echo-op/Jamfile Normal file
View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe echo-op :
echo_op.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

356
example/echo-op/echo_op.cpp Normal file
View File

@@ -0,0 +1,356 @@
//
// 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)
//
#include <beast/core.hpp>
#include <boost/asio.hpp>
#include <cstddef>
#include <iostream>
#include <memory>
#include <utility>
//[example_core_echo_op_1
template<
class AsyncStream,
class CompletionToken>
auto
async_echo(AsyncStream& stream, CompletionToken&& token)
//]
-> beast::async_return_type<CompletionToken, void(beast::error_code)>;
//[example_core_echo_op_2
/** Asynchronously read a line and echo it back.
This function is used to asynchronously read a line ending
in a carriage-return ("CR") from the stream, and then write
it back. The function call always returns immediately. The
asynchronous operation will continue until one of the
following conditions is true:
@li A line was read in and sent back on the stream
@li An error occurs.
This operation is implemented in terms of one or more calls to
the stream's `async_read_some` and `async_write_some` functions,
and is known as a <em>composed operation</em>. The program must
ensure that the stream performs no other operations until this
operation completes. The implementation may read additional octets
that lie past the end of the line being read. These octets are
silently discarded.
@param The stream to operate on. The type must meet the
requirements of @b AsyncReadStream and @AsyncWriteStream
@param token The completion token to use. If this is a
completion handler, copies will be made as required.
The equivalent signature of the handler must be:
@code
void handler(
error_code ec // result of operation
);
@endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<
class AsyncStream,
class CompletionToken>
beast::async_return_type< /*< The [link beast.ref.beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/
CompletionToken,
void(beast::error_code)> /*< This is the signature for the completion handler >*/
async_echo(
AsyncStream& stream,
CompletionToken&& token);
//]
//[example_core_echo_op_4
// This composed operation reads a line of input and echoes it back.
//
template<class AsyncStream, class Handler>
class echo_op
{
// This holds all of the state information required by the operation.
struct state
{
// The stream to read and write to
AsyncStream& stream;
// Indicates what step in the operation's state machine
// to perform next, starting from zero.
int step = 0;
// The buffer used to hold the input and output data.
//
// We use a custom allocator for performance, this allows
// the implementation of the io_service to make efficient
// re-use of memory allocated by composed operations during
// a continuation.
//
boost::asio::basic_streambuf<beast::handler_alloc<char, Handler>> buffer;
// handler_ptr requires that the first parameter to the
// contained object constructor is a reference to the
// managed final completion handler.
//
explicit state(Handler& handler, AsyncStream& stream_)
: stream(stream_)
, buffer((std::numeric_limits<std::size_t>::max)(),
beast::handler_alloc<char, Handler>{handler})
{
}
};
// The operation's data is kept in a cheap-to-copy smart
// pointer container called `handler_ptr`. This efficiently
// satisfies the CopyConstructible requirements of completion
// handlers.
//
// `handler_ptr` uses these memory allocation hooks associated
// with the final completion handler, in order to allocate the
// storage for `state`:
//
// asio_handler_allocate
// asio_handler_deallocate
//
beast::handler_ptr<state, Handler> p_;
public:
// Boost.Asio requires that handlers are CopyConstructible.
// In some cases, it takes advantage of handlers that are
// MoveConstructible. This operation supports both.
//
echo_op(echo_op&&) = default;
echo_op(echo_op const&) = default;
// The constructor simply creates our state variables in
// the smart pointer container.
//
template<class DeducedHandler, class... Args>
echo_op(AsyncStream& stream, DeducedHandler&& handler)
: p_(std::forward<DeducedHandler>(handler), stream)
{
}
// The entry point for this handler. This will get called
// as our intermediate operations complete. Definition below.
//
void operator()(beast::error_code ec, std::size_t bytes_transferred);
// The next four functions are required for our class
// to meet the requirements for composed operations.
// Definitions and exposition will follow.
template<class AsyncStream_, class Handler_, class Function>
friend void asio_handler_invoke(
Function&& f, echo_op<AsyncStream_, Handler_>* op);
template<class AsyncStream_, class Handler_>
friend void* asio_handler_allocate(
std::size_t size, echo_op<AsyncStream_, Handler_>* op);
template<class AsyncStream_, class Handler_>
friend void asio_handler_deallocate(
void* p, std::size_t size, echo_op<AsyncStream_, Handler_>* op);
template<class AsyncStream_, class Handler_>
friend bool asio_handler_is_continuation(
echo_op<AsyncStream_, Handler_>* op);
};
//]
//[example_core_echo_op_5
// echo_op is callable with the signature void(error_code, bytes_transferred),
// allowing `*this` to be used as both a ReadHandler and a WriteHandler.
//
template<class AsyncStream, class Handler>
void echo_op<AsyncStream, Handler>::
operator()(beast::error_code ec, std::size_t bytes_transferred)
{
// Store a reference to our state. The address of the state won't
// change, and this solves the problem where dereferencing the
// data member is undefined after a move.
auto& p = *p_;
// Now perform the next step in the state machine
switch(ec ? 2 : p.step)
{
// initial entry
case 0:
// read up to the first newline
p.step = 1;
return boost::asio::async_read_until(p.stream, p.buffer, "\r", std::move(*this));
case 1:
// write everything back
p.step = 2;
// async_read_until could have read past the newline,
// use buffer_prefix to make sure we only send one line
return boost::asio::async_write(p.stream,
beast::buffer_prefix(bytes_transferred, p.buffer.data()), std::move(*this));
case 2:
p.buffer.consume(bytes_transferred);
break;
}
// Invoke the final handler. The implementation of `handler_ptr`
// will deallocate the storage for the state before the handler
// is invoked. This is necessary to provide the
// destroy-before-invocation guarantee on handler memory
// customizations.
//
// If we wanted to pass any arguments to the handler which come
// from the `state`, they would have to be moved to the stack
// first or else undefined behavior results.
//
p_.invoke(ec);
return;
}
//]
//[example_core_echo_op_6
// Handler hook forwarding. These free functions invoke the hooks
// associated with the final completion handler. In effect, they
// make the Asio implementation treat our composed operation the
// same way it would treat the final completion handler for the
// purpose of memory allocation and invocation.
//
// Our implementation just passes the call through to the hook
// associated with the final handler. The "using" statements are
// structured to permit argument dependent lookup. Always use
// `std::addressof` or its equivalent to pass the pointer to the
// handler, otherwise an unwanted overload of `operator&` may be
// called instead.
template<class AsyncStream, class Handler, class Function>
void asio_handler_invoke(
Function&& f, echo_op<AsyncStream, Handler>* op)
{
using boost::asio::asio_handler_invoke;
return asio_handler_invoke(f, std::addressof(op->p_.handler()));
}
template<class AsyncStream, class Handler>
void* asio_handler_allocate(
std::size_t size, echo_op<AsyncStream, Handler>* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(size, std::addressof(op->p_.handler()));
}
template<class AsyncStream, class Handler>
void asio_handler_deallocate(
void* p, std::size_t size, echo_op<AsyncStream, Handler>* op)
{
using boost::asio::asio_handler_deallocate;
return asio_handler_deallocate(p, size,
std::addressof(op->p_.handler()));
}
// Determines if the next asynchronous operation represents a
// continuation of the asynchronous flow of control associated
// with the final handler. If we are past step one, it means
// we have performed an asynchronous operation therefore any
// subsequent operation would represent a continuation.
// Otherwise, we propagate the handler's associated value of
// is_continuation. Getting this right means the implementation
// may schedule the invokation of the invoked functions more
// efficiently.
//
template<class AsyncStream, class Handler>
bool asio_handler_is_continuation(echo_op<AsyncStream, Handler>* op)
{
// This next call is structured to permit argument
// dependent lookup to take effect.
using boost::asio::asio_handler_is_continuation;
// Always use std::addressof to pass the pointer to the handler,
// otherwise an unwanted overload of operator& may be called instead.
return op->p_->step > 1 ||
asio_handler_is_continuation(std::addressof(op->p_.handler()));
}
//]
//[example_core_echo_op_3
template<class AsyncStream, class Handler>
class echo_op;
// Read a line and echo it back
//
template<class AsyncStream, class CompletionToken>
beast::async_return_type<CompletionToken, void(beast::error_code)>
async_echo(AsyncStream& stream, CompletionToken&& token)
{
// Make sure stream meets the requirements. We use static_assert
// to cause a friendly message instead of an error novel.
//
static_assert(beast::is_async_stream<AsyncStream>::value,
"AsyncStream 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.
//
beast::async_completion<CompletionToken, void(beast::error_code)> init{token};
// Create the composed operation and launch it. This is a constructor
// call followed by invocation of operator(). We use handler_type
// to convert the completion token into the correct handler type,
// allowing user-defined specializations of the async_result template
// to be used.
//
echo_op<AsyncStream, beast::handler_type<CompletionToken, void(beast::error_code)>>{
stream, init.completion_handler}(beast::error_code{}, 0);
// This hook lets the caller see a return value when appropriate.
// For example this might return std::future<error_code> if
// CompletionToken is boost::asio::use_future, or this might
// return an error code if CompletionToken specifies a coroutine.
//
return init.result.get();
}
//]
int main(int, char** argv)
{
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
using endpoint_type = boost::asio::ip::tcp::endpoint;
// Create a listening socket, accept a connection, perform
// the echo, and then shut everything down and exit.
boost::asio::io_service ios;
socket_type sock{ios};
boost::asio::ip::tcp::acceptor acceptor{ios};
endpoint_type ep{address_type::from_string("0.0.0.0"), 0};
acceptor.open(ep.protocol());
acceptor.bind(ep);
acceptor.listen();
acceptor.accept(sock);
async_echo(sock,
[&](beast::error_code ec)
{
if(ec)
std::cerr << argv[0] << ": " << ec.message() << std::endl;
});
ios.run();
return 0;
}

View File

@@ -0,0 +1,16 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/common common)
GroupSources(example/http-client-ssl "/")
add_executable (http-client-ssl
${BEAST_INCLUDES}
${COMMON_INCLUDES}
http_client_ssl.cpp
)
target_link_libraries(http-client-ssl
Beast
${OPENSSL_LIBRARIES}
)

View File

@@ -0,0 +1,51 @@
#
# 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)
#
import os ;
if [ os.name ] = SOLARIS
{
lib socket ;
lib nsl ;
}
else if [ os.name ] = NT
{
lib ws2_32 ;
lib mswsock ;
}
else if [ os.name ] = HPUX
{
lib ipv6 ;
}
else if [ os.name ] = HAIKU
{
lib network ;
}
if [ os.name ] = NT
{
lib ssl : : <name>ssleay32 ;
lib crypto : : <name>libeay32 ;
}
else
{
lib ssl ;
lib crypto ;
}
project
: requirements
<library>ssl
<library>crypto
;
exe http-client-ssl :
http_client_ssl.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,104 @@
//
// 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)
//
#include "../common/root_certificates.hpp"
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <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 = beast::http; // from <beast/http.hpp>
int main()
{
// A helper for reporting errors
auto const fail =
[](std::string what, 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, 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
beast::flat_buffer b;
// Declare a container to hold the response
http::response<http::dynamic_body> res;
// Read the response
http::read(stream, b, res, ec);
if(ec)
return fail("read", ec);
// Write the message to standard out
std::cout << res << std::endl;
// Shut down SSL on the stream
stream.shutdown(ec);
if(ec && ec != boost::asio::error::eof)
fail("ssl_shutdown ", ec);
// If we get here then the connection is closed gracefully
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,13 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/http-client "/")
add_executable (http-client
${BEAST_INCLUDES}
${EXTRAS_INCLUDES}
http_client.cpp
)
target_link_libraries(http-client Beast)

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe http-client :
http_client.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,88 @@
//
// 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)
//
//[example_http_client
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <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 = beast::http; // from <beast/http.hpp>
int main()
{
// A helper for reporting errors
auto const fail =
[](std::string what, beast::error_code ec)
{
std::cerr << what << ": " << ec.message() << std::endl;
return EXIT_FAILURE;
};
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, 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
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 != beast::errc::not_connected)
return fail("shutdown", ec);
// If we get here then the connection is closed gracefully
return EXIT_SUCCESS;
}
//]

View File

@@ -0,0 +1,15 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/http-crawl "/")
add_executable (http-crawl
${BEAST_INCLUDES}
${EXTRAS_INCLUDES}
urls_large_data.hpp
urls_large_data.cpp
http_crawl.cpp
)
target_link_libraries(http-crawl Beast)

View File

@@ -0,0 +1,14 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe http-crawl :
http_crawl.cpp
urls_large_data.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,137 @@
//
// 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)
//
#include "urls_large_data.hpp"
#include <beast/core/multi_buffer.hpp>
#include <beast/http.hpp>
#include <beast/version.hpp>
#include <boost/asio.hpp>
#include <cstdlib>
#include <iostream>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace http = beast::http; // from <beast/http.hpp>
template<class String>
void
err(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, 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())
{
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, 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 != beast::errc::not_connected)
return fail("shutdown", ec);
}
if(ec)
{
fail("write", ec);
continue;
}
// This buffer is needed for reading
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 != beast::errc::not_connected)
return fail("shutdown", ec);
std::cout << res << std::endl;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
#define BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
#include <vector>
std::vector<char const*> const&
urls_large_data();
#endif

View File

@@ -0,0 +1,18 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/common common)
GroupSources(example/http-server-fast "/")
add_executable (http-server-fast
${BEAST_INCLUDES}
${COMMON_INCLUDES}
fields_alloc.hpp
http_server_fast.cpp
)
target_link_libraries(http-server-fast
Beast
${Boost_FILESYSTEM_LIBRARY}
)

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe http-server-fast :
http_server_fast.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,195 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_FIELDS_ALLOC_HPP
#define BEAST_EXAMPLE_FIELDS_ALLOC_HPP
#include <boost/throw_exception.hpp>
#include <cstdlib>
#include <memory>
#include <stdexcept>
namespace detail {
struct static_pool
{
std::size_t size_;
std::size_t refs_ = 1;
std::size_t count_ = 0;
char* p_;
char*
end()
{
return reinterpret_cast<char*>(this+1) + size_;
}
explicit
static_pool(std::size_t size)
: size_(size)
, p_(reinterpret_cast<char*>(this+1))
{
}
public:
static
static_pool&
construct(std::size_t size)
{
auto p = new char[sizeof(static_pool) + size];
return *(new(p) static_pool{size});
}
static_pool&
share()
{
++refs_;
return *this;
}
void
destroy()
{
if(refs_--)
return;
this->~static_pool();
delete[] reinterpret_cast<char*>(this);
}
void*
alloc(std::size_t n)
{
auto last = p_ + n;
if(last >= end())
BOOST_THROW_EXCEPTION(std::bad_alloc{});
++count_;
auto p = p_;
p_ = last;
return p;
}
void
dealloc()
{
if(--count_)
return;
p_ = reinterpret_cast<char*>(this+1);
}
};
} // detail
/** A non-thread-safe allocator optimized for @ref basic_fields.
This allocator obtains memory from a pre-allocated memory block
of a given size. It does nothing in deallocate until all
previously allocated blocks are deallocated, upon which it
resets the internal memory block for re-use.
To use this allocator declare an instance persistent to the
connection or session, and construct with the block size.
A good rule of thumb is 20% more than the maximum allowed
header size. For example if the application only allows up
to an 8,000 byte header, the block size could be 9,600.
Then, for every instance of `message` construct the header
with a copy of the previously declared allocator instance.
*/
template<class T>
struct fields_alloc
{
detail::static_pool& pool_;
public:
using value_type = T;
using is_always_equal = std::false_type;
using pointer = T*;
using reference = T&;
using const_pointer = T const*;
using const_reference = T const&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
template<class U>
struct rebind
{
using other = fields_alloc<U>;
};
explicit
fields_alloc(std::size_t size)
: pool_(detail::static_pool::construct(size))
{
}
fields_alloc(fields_alloc const& other)
: pool_(other.pool_.share())
{
}
template<class U>
fields_alloc(fields_alloc<U> const& other)
: pool_(other.pool_.share())
{
}
~fields_alloc()
{
pool_.destroy();
}
value_type*
allocate(size_type n)
{
return static_cast<value_type*>(
pool_.alloc(n * sizeof(T)));
}
void
deallocate(value_type*, size_type)
{
pool_.dealloc();
}
#if defined(BOOST_LIBSTDCXX_VERSION) && BOOST_LIBSTDCXX_VERSION < 60000
template<class U, class... Args>
void
construct(U* ptr, Args&&... args)
{
::new((void*)ptr) U(std::forward<Args>(args)...);
}
template<class U>
void
destroy(U* ptr)
{
ptr->~U();
}
#endif
template<class U>
friend
bool
operator==(
fields_alloc const& lhs,
fields_alloc<U> const& rhs)
{
return &lhs.pool_ == &rhs.pool_;
}
template<class U>
friend
bool
operator!=(
fields_alloc const& lhs,
fields_alloc<U> const& rhs)
{
return ! (lhs == rhs);
}
};
#endif

View File

@@ -0,0 +1,310 @@
//
// Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "fields_alloc.hpp"
#include "../common/mime_types.hpp"
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <beast/version.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <list>
#include <memory>
#include <string>
namespace ip = boost::asio::ip; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace http = beast::http; // from <beast/http.hpp>
class http_worker
{
public:
http_worker(http_worker const&) = delete;
http_worker& operator=(http_worker const&) = delete;
http_worker(tcp::acceptor& acceptor, const std::string& doc_root) :
acceptor_(acceptor),
doc_root_(doc_root)
{
}
void start()
{
accept();
check_deadline();
}
private:
using alloc_t = fields_alloc<char>;
using request_body_t = http::basic_dynamic_body<beast::static_buffer_n<1024 * 1024>>;
// The acceptor used to listen for incoming connections.
tcp::acceptor& acceptor_;
// The path to the root of the document directory.
std::string doc_root_;
// The socket for the currently connected client.
tcp::socket socket_{acceptor_.get_io_service()};
// The buffer for performing reads
beast::static_buffer_n<8192> buffer_;
// The allocator used for the fields in the request and reply.
alloc_t alloc_{8192};
// The parser for reading the requests
boost::optional<http::request_parser<request_body_t, alloc_t>> parser_;
// The timer putting a time limit on requests.
boost::asio::basic_waitable_timer<std::chrono::steady_clock> request_deadline_{
acceptor_.get_io_service(), (std::chrono::steady_clock::time_point::max)()};
// The string-based response message.
boost::optional<http::response<http::string_body, http::basic_fields<alloc_t>>> string_response_;
// The string-based response serializer.
boost::optional<http::response_serializer<http::string_body, http::basic_fields<alloc_t>>> string_serializer_;
// The file-based response message.
boost::optional<http::response<http::file_body, http::basic_fields<alloc_t>>> file_response_;
// The file-based response serializer.
boost::optional<http::response_serializer<http::file_body, http::basic_fields<alloc_t>>> file_serializer_;
void accept()
{
// Clean up any previous connection.
beast::error_code ec;
socket_.close(ec);
buffer_.consume(buffer_.size());
acceptor_.async_accept(
socket_,
[this](beast::error_code ec)
{
if (ec)
{
accept();
}
else
{
// Request must be fully processed within 60 seconds.
request_deadline_.expires_from_now(
std::chrono::seconds(60));
read_request();
}
});
}
void read_request()
{
// 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(),
std::make_tuple(alloc_));
http::async_read(
socket_,
buffer_,
*parser_,
[this](beast::error_code ec)
{
if (ec)
accept();
else
process_request(parser_->get());
});
}
void process_request(http::request<request_body_t, http::basic_fields<alloc_t>> const& req)
{
switch (req.method())
{
case http::verb::get:
send_file(req.target());
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
send_bad_response(
http::status::bad_request,
"Invalid request-method '" + req.method_string().to_string() + "'\r\n");
break;
}
}
void send_bad_response(
http::status status,
std::string const& error)
{
string_response_.emplace(
std::piecewise_construct,
std::make_tuple(),
std::make_tuple(alloc_));
string_response_->result(status);
string_response_->set(http::field::server, "Beast");
string_response_->set(http::field::connection, "close");
string_response_->set(http::field::content_type, "text/plain");
string_response_->body = error;
string_response_->prepare_payload();
string_serializer_.emplace(*string_response_);
http::async_write(
socket_,
*string_serializer_,
[this](beast::error_code ec)
{
socket_.shutdown(tcp::socket::shutdown_send, ec);
string_serializer_.reset();
string_response_.reset();
accept();
});
}
void send_file(beast::string_view target)
{
// Request path must be absolute and not contain "..".
if (target.empty() || target[0] != '/' || target.find("..") != std::string::npos)
{
send_bad_response(
http::status::not_found,
"File not found\r\n");
return;
}
std::string full_path = doc_root_;
full_path.append(
target.data(),
target.size());
http::file_body::value_type file;
beast::error_code ec;
file.open(
full_path.c_str(),
beast::file_mode::read,
ec);
if(ec)
{
send_bad_response(
http::status::not_found,
"File not found\r\n");
return;
}
file_response_.emplace(
std::piecewise_construct,
std::make_tuple(),
std::make_tuple(alloc_));
file_response_->result(http::status::ok);
file_response_->set(http::field::server, "Beast");
file_response_->set(http::field::connection, "close");
file_response_->set(http::field::content_type, mime_type(target.to_string()));
file_response_->body = std::move(file);
file_response_->prepare_payload();
file_serializer_.emplace(*file_response_);
http::async_write(
socket_,
*file_serializer_,
[this](beast::error_code ec)
{
socket_.shutdown(tcp::socket::shutdown_send, ec);
file_serializer_.reset();
file_response_.reset();
accept();
});
}
void check_deadline()
{
// The deadline may have moved, so check it has really passed.
if (request_deadline_.expires_at() <= std::chrono::steady_clock::now())
{
// Close socket to cancel any outstanding operation.
beast::error_code ec;
socket_.close();
// Sleep indefinitely until we're given a new deadline.
request_deadline_.expires_at(
std::chrono::steady_clock::time_point::max());
}
request_deadline_.async_wait(
[this](beast::error_code)
{
check_deadline();
});
}
};
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 6)
{
std::cerr << "Usage: http_server_fast <address> <port> <doc_root> <num_workers> {spin|block}\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " http_server_fast 0.0.0.0 80 . 100 block\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " http_server_fast 0::0 80 . 100 block\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];
int num_workers = std::atoi(argv[4]);
bool spin = (std::strcmp(argv[5], "spin") == 0);
boost::asio::io_service ios{1};
tcp::acceptor acceptor{ios, {address, port}};
std::list<http_worker> workers;
for (int i = 0; i < num_workers; ++i)
{
workers.emplace_back(acceptor, doc_root);
workers.back().start();
}
if (spin)
for (;;) ios.poll();
else
ios.run();
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}

View File

@@ -0,0 +1,15 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/http-server-small "/")
add_executable (http-server-small
${BEAST_INCLUDES}
http_server_small.cpp
)
target_link_libraries(http-server-small
Beast
)

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe http-server-small :
http_server_small.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,240 @@
//
// Copyright (c) 2017 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <beast/version.hpp>
#include <boost/asio.hpp>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
namespace ip = boost::asio::ip; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace http = beast::http; // from <beast/http.hpp>
namespace my_program_state
{
std::size_t
request_count()
{
static std::size_t count = 0;
return ++count;
}
std::time_t
now()
{
return std::time(0);
}
}
class http_connection : public std::enable_shared_from_this<http_connection>
{
public:
http_connection(tcp::socket socket)
: socket_(std::move(socket))
{
}
// Initiate the asynchronous operations associated with the connection.
void
start()
{
read_request();
check_deadline();
}
private:
// The socket for the currently connected client.
tcp::socket socket_;
// The buffer for performing reads.
beast::flat_buffer buffer_{8192};
// The request message.
http::request<http::dynamic_body> request_;
// The response message.
http::response<http::dynamic_body> response_;
// The timer for putting a deadline on connection processing.
boost::asio::basic_waitable_timer<std::chrono::steady_clock> deadline_{
socket_.get_io_service(), std::chrono::seconds(60)};
// Asynchronously receive a complete request message.
void
read_request()
{
auto self = shared_from_this();
http::async_read(
socket_,
buffer_,
request_,
[self](beast::error_code ec)
{
if(!ec)
self->process_request();
});
}
// Determine what needs to be done with the request message.
void
process_request()
{
response_.version = 11;
response_.set(http::field::connection, "close");
switch(request_.method())
{
case http::verb::get:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
create_response();
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
response_.result(http::status::bad_request);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body)
<< "Invalid request-method '"
<< request_.method_string().to_string()
<< "'";
break;
}
write_response();
}
// Construct a response message based on the program state.
void
create_response()
{
if(request_.target() == "/count")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body)
<< "<html>\n"
<< "<head><title>Request count</title></head>\n"
<< "<body>\n"
<< "<h1>Request count</h1>\n"
<< "<p>There have been "
<< my_program_state::request_count()
<< " requests so far.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else if(request_.target() == "/time")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body)
<< "<html>\n"
<< "<head><title>Current time</title></head>\n"
<< "<body>\n"
<< "<h1>Current time</h1>\n"
<< "<p>The current time is "
<< my_program_state::now()
<< " seconds since the epoch.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body) << "File not found\r\n";
}
}
// Asynchronously transmit the response message.
void
write_response()
{
auto self = shared_from_this();
response_.set(http::field::content_length, response_.body.size());
http::async_write(
socket_,
response_,
[self](beast::error_code ec)
{
self->socket_.shutdown(tcp::socket::shutdown_send, ec);
self->deadline_.cancel();
});
}
// Check whether we have spent enough time on this connection.
void
check_deadline()
{
auto self = shared_from_this();
deadline_.async_wait(
[self](beast::error_code ec)
{
if(!ec)
{
// Close socket to cancel any outstanding operation.
self->socket_.close(ec);
}
});
}
};
// "Loop" forever accepting new connections.
void
http_server(tcp::acceptor& acceptor, tcp::socket& socket)
{
acceptor.async_accept(socket,
[&](beast::error_code ec)
{
if(!ec)
std::make_shared<http_connection>(std::move(socket))->start();
http_server(acceptor, socket);
});
}
int
main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <address> <port>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80\n";
return EXIT_FAILURE;
}
auto address = ip::address::from_string(argv[1]);
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
boost::asio::io_service ios{1};
tcp::acceptor acceptor{ios, {address, port}};
tcp::socket socket{ios};
http_server(acceptor, socket);
ios.run();
}
catch(std::exception const& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}

View File

@@ -0,0 +1,17 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/common common)
GroupSources(example/http-server-threaded "/")
add_executable (http-server-threaded
${BEAST_INCLUDES}
${COMMON_INCLUDES}
http_server_threaded.cpp
)
target_link_libraries(http-server-threaded
Beast
${Boost_FILESYSTEM_LIBRARY}
)

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe http-server-threaded :
http_server_threaded.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,227 @@
//
// 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)
//
#include "../common/mime_types.hpp"
#include <beast/core.hpp>
#include <beast/http.hpp>
#include <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 = beast::http; // from <beast/http.hpp>
class connection
: public std::enable_shared_from_this<connection>
{
tcp::socket sock_;
beast::string_view root_;
public:
explicit
connection(tcp::socket&& sock, 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, beast::string_view text)
{
http::response<http::span_body<char const>> res{result, 11};
res.set(http::field::server, 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, 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(beast::error_code const& ec) const
{
http::response<http::string_body> res{http::status::internal_server_error, 11};
res.set(http::field::server, 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<beast::http::file_body>
get(boost::filesystem::path const& full_path,
beast::error_code& ec) const
{
http::response<http::file_body> res;
res.set(http::field::server, 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(), 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, 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());
beast::error_code file_ec;
auto res = get(full_path, file_ec);
if(file_ec == 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
{
beast::error_code ec;
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 beast::system_error{ec};
do_request(parser.get(), ec);
if(ec)
{
if(ec != http::error::end_of_stream)
throw beast::system_error{ec};
break;
}
}
sock_.shutdown(tcp::socket::shutdown_both, ec);
if(ec && ec != boost::asio::error::not_connected)
throw beast::system_error{ec};
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
}
};
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 4)
{
std::cerr << "Usage: http_server <address> <port> <doc_root>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80 .\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80 .\n";
return EXIT_FAILURE;
}
auto address = ip::address::from_string(argv[1]);
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string doc_root = argv[3];
boost::asio::io_service ios{1};
tcp::acceptor acceptor{ios, {address, port}};
for(;;)
{
tcp::socket sock{ios};
acceptor.accept(sock);
std::make_shared<connection>(std::move(sock), doc_root)->run();
}
}
catch (const std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}

View File

@@ -0,0 +1,27 @@
# Part of Beast
GroupSources(include/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
${BEAST_INCLUDES}
${COMMON_INCLUDES}
${SERVER_INCLUDES}
main.cpp
)
target_link_libraries(
server-framework
Beast
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY})
if (OPENSSL_FOUND)
target_link_libraries(server-framework ${OPENSSL_LIBRARIES})
endif()

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe server-framework :
main.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,159 @@
<img width="880" height = "80" alt = "Beast"
src="https://raw.githubusercontent.com/vinniefalco/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/vinniefalco/Beast/master/doc/images/server.png">
## PortHandler Requirements
```C++
/** An synchronous WebSocket @b PortHandler which implements echo.
This is a port handler which accepts WebSocket upgrade HTTP
requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
struct PortHandler
{
/** Accept a TCP/IP socket.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(
socket_type&& sock,
endpoint_type ep);
};
```
## Service Requirements
```C++
struct Service
{
/** Initialize the service
@param ec Set to the error, if any occurred
*/
void
init(error_code& ec);
/** Maybe respond to an HTTP request
Upon handling the response, the service may optionally
take ownership of either the stream, the request, or both.
@param stream The stream representing the connection
@param ep The remote endpoint of the stream
@param req The HTTP request
@param send A function object which operates on a single
argument of type beast::http::message. The function object
has this equivalent signature:
@code
template<class Body, class Fields>
void send(beast::http::response<Body, Fields>&& res);
@endcode
@return `true` if the service handled the response.
*/
template<
class Stream,
class Body, class Fields,
class Send>
bool
respond(
Stream&& stream,
endpoint_type const& ep,
beast::http::request<Body, Fields>&& req,
Send const& send) const
};
```
## Upgrade Service Requirements
To work with the `ws_upgrade_service`, a port or handler needs
this signature:
```C++
struct UpgradePort
{
template<class Stream, class Body, class Fields>
void
on_upgrade(
Stream&& stream,
endpoint_type ep,
beast::http::request<Body, Fields>&& req);
```

View File

@@ -0,0 +1,279 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP
#define BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP
#include "framework.hpp"
#include "../common/mime_types.hpp"
#include <beast/core/string.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/file_body.hpp>
#include <beast/http/message.hpp>
#include <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,
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,
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 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;
beast::error_code ec;
auto res = get(req, full_path, ec);
if(ec == 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 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;
beast::error_code ec;
auto res = head(req, full_path, ec);
if(ec == 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>
beast::http::response<beast::http::string_body>
not_found(
beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& rel_path) const
{
boost::ignore_unused(rel_path);
beast::http::response<beast::http::string_body> res;
res.version = req.version;
res.result(beast::http::status::not_found);
res.set(beast::http::field::server, server_);
res.set(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>
beast::http::response<beast::http::string_body>
server_error(
beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& rel_path,
error_code const& ec) const
{
boost::ignore_unused(rel_path);
beast::http::response<beast::http::string_body> res;
res.version = req.version;
res.result(beast::http::status::internal_server_error);
res.set(beast::http::field::server, server_);
res.set(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<beast::http::response<beast::http::file_body>>
get(
beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path,
beast::error_code& ec) const
{
beast::http::response<beast::http::file_body> res;
res.version = req.version;
res.set(beast::http::field::server, server_);
res.set(beast::http::field::content_type, mime_type(full_path));
res.body.open(full_path.string<std::string>().c_str(), beast::file_mode::scan, ec);
if(ec)
return boost::none;
res.set(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<beast::http::response<beast::http::empty_body>>
head(
beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path,
beast::error_code& ec) const
{
beast::http::response<beast::http::empty_body> res;
res.version = req.version;
res.set(beast::http::field::server, server_);
res.set(beast::http::field::content_type, mime_type(full_path));
// Use a manual file body here
beast::http::file_body::value_type body;
body.open(full_path.string<std::string>().c_str(), beast::file_mode::scan, ec);
if(ec)
return boost::none;
res.set(beast::http::field::content_length, body.size());
return {std::move(res)};
}
};
} // framework
#endif

View File

@@ -0,0 +1,53 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP
#define BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP
#include <boost/asio/io_service.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <utility>
/** The framework namespace
This namespace contains all of the identifiers in the
server-framework system. Here we import some commonly
used types for brevity.
*/
namespace framework {
// This is our own base from member idiom written for C++11
// which is simple and works around a glitch in boost's version.
//
template<class T>
class base_from_member
{
public:
template<class... Args>
explicit
base_from_member(Args&&... args)
: member(std::forward<Args>(args)...)
{
}
protected:
T member;
};
using error_code = boost::system::error_code;
using socket_type = boost::asio::ip::tcp::socket;
using strand_type = boost::asio::io_service::strand;
using address_type = boost::asio::ip::address_v4;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using acceptor_type = boost::asio::ip::tcp::acceptor;
using io_service_type = boost::asio::io_service;
} // framework
#endif

View File

@@ -0,0 +1,653 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP
#define 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 <beast/core/flat_buffer.hpp>
#include <beast/http/dynamic_body.hpp>
#include <beast/http/parser.hpp>
#include <beast/http/read.hpp>
#include <beast/http/string_body.hpp>
#include <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
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,
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,
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
beast::flat_buffer buffer_;
// The parser for reading the requests
boost::optional<beast::http::request_parser<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(
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
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()(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 == 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.
//
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(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 == 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 != 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 a synchronous connection implementation to service
*/
template<class... Services>
class http_async_port
{
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The list of services connections created from this port will support
service_list<Services...> services_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
*/
http_async_port(
server& instance,
std::ostream& log)
: instance_(instance)
, log_(log)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create a plain http connection object
// and transfer ownership of the socket.
//
std::make_shared<async_http_con<Services...>>(
std::move(sock),
"http_async_port",
log_,
services_,
instance_.next_id(),
ep)->run();
}
};
} // framework
#endif

View File

@@ -0,0 +1,77 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP
#define BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP
#include <beast/core/string.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/message.hpp>
#include <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
{
beast::string_view server_name_;
public:
explicit
http_base(beast::string_view server_name)
: server_name_(server_name)
{
}
protected:
// Returns a bad request result response
//
template<class Body, class Fields>
beast::http::response<beast::http::string_body>
bad_request(beast::http::request<Body, Fields> const& req) const
{
beast::http::response<beast::http::string_body> res;
// Match the version to the request
res.version = req.version;
res.result(beast::http::status::bad_request);
res.set(beast::http::field::server, server_name_);
res.set(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>
beast::http::response<beast::http::empty_body>
continue_100(beast::http::request<Body, Fields> const& req) const
{
beast::http::response<beast::http::empty_body> res;
// Match the version to the request
res.version = req.version;
res.result(beast::http::status::continue_);
res.set(beast::http::field::server, server_name_);
return res;
}
};
} // framework
#endif

View File

@@ -0,0 +1,477 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP
#define 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 <beast/core/flat_buffer.hpp>
#include <beast/core/handler_ptr.hpp>
#include <beast/http/dynamic_body.hpp>
#include <beast/http/parser.hpp>
#include <beast/http/read.hpp>
#include <beast/http/string_body.hpp>
#include <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
beast::flat_buffer buffer_;
public:
/// Constructor
sync_http_con_base(
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()(
beast::http::response<Body, Fields>&& res) const
{
beast::http::serializer<false, Body, Fields> sr{res};
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.
//
beast::http::request_parser<beast::http::dynamic_body> parser(
std::piecewise_construct, std::make_tuple(1024* 1024));
// Read the header first
beast::http::read_header(impl().stream(), buffer_, parser, ec);
// This happens when the other end closes gracefully
//
if(ec == beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec && ec != 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 == beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec && ec != 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.
//
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 == beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec && ec != 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 == beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
if(ec && ec != beast::errc::not_connected)
return fail("shutdown", ec);
return;
}
// Have to check the error every time we call the lambda
//
if(ec)
return fail("write", ec);
// See if the service that handled the
// response took ownership of the stream.
if(! impl().stream().lowest_layer().is_open())
{
// They took ownership so just return and
// let this sync_http_con_base object get destroyed.
return;
}
}
// Theres no pipelining possible in a synchronous server
// because we can't do reads and writes at the same time.
}
}
};
//------------------------------------------------------------------------------
// This class represents a synchronous HTTP connection which
// uses a plain TCP/IP socket (no encryption) as the stream.
//
template<class... Services>
class sync_http_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<sync_http_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<socket_type>
// Constructs last, destroys first
//
, public sync_http_con_base<sync_http_con<Services...>, Services...>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
sync_http_con(
socket_type&& sock,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, sync_http_con_base<sync_http_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
socket_type&
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend class sync_http_con_base<sync_http_con<Services...>, Services...>;
// This is called by the base before running the main loop.
// There's nothing to do for a plain connection.
//
void
do_handshake(error_code& ec)
{
// This is required by the specifications for error_code
//
ec = {};
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown(error_code& ec)
{
stream().shutdown(socket_type::shutdown_both, ec);
}
};
//------------------------------------------------------------------------------
/* A synchronous HTTP port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service
*/
template<class... Services>
class http_sync_port
{
server& instance_;
std::ostream& log_;
service_list<Services...> services_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
*/
http_sync_port(
server& instance,
std::ostream& log)
: instance_(instance)
, log_(log)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param ec Set to the error, if any occurred
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create a plain http connection object
// and transfer ownership of the socket.
//
std::make_shared<sync_http_con<Services...>>(
std::move(sock),
"http_sync_port",
log_,
services_,
instance_.next_id(),
ep)->run();
}
};
} // framework
#endif

View File

@@ -0,0 +1,426 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP
#define BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP
#include "http_sync_port.hpp"
#include "http_async_port.hpp"
#include "../common/ssl_stream.hpp"
#include <boost/asio/ssl.hpp>
namespace framework {
//------------------------------------------------------------------------------
// This class represents a synchronous HTTP connection which
// uses an OpenSSL socket as the stream.
//
template<class... Services>
class sync_https_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<sync_https_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<ssl_stream<socket_type>>
// Constructs last, destroys first
//
, public sync_http_con_base<sync_https_con<Services...>, Services...>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
sync_https_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<ssl_stream<socket_type>>(std::move(sock), ctx)
, sync_http_con_base<sync_https_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
ssl_stream<socket_type>&
stream()
{
return this->member;
}
private:
friend class sync_http_con_base<sync_https_con<Services...>, Services...>;
// This is called by the base before running the main loop.
//
void
do_handshake(error_code& ec)
{
// Perform the SSL handshake
//
stream().handshake(boost::asio::ssl::stream_base::server, ec);
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown(error_code& ec)
{
// Note that this is an SSL shutdown
//
stream().shutdown(ec);
if(ec)
return this->fail("ssl_shutdown", ec);
}
};
//------------------------------------------------------------------------------
// This class represents an asynchronous HTTP connection which
// uses an OpenSSL socket as the stream.
//
template<class... Services>
class async_https_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<async_https_con<Services...>>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<ssl_stream<socket_type>>
// Constructs last, destroys first
//
, public async_http_con_base<async_https_con<Services...>, Services...>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
async_https_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<ssl_stream<socket_type>>(std::move(sock), ctx)
, async_http_con_base<async_https_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
ssl_stream<socket_type>&
stream()
{
return this->member;
}
// Called by the multi-port after reading some
// bytes from the stream and detecting SSL.
//
template<class ConstBufferSequence>
void
handshake(ConstBufferSequence const& buffers)
{
// Copy the caller's bytes into the buffer we
// use for reading HTTP messages, otherwise
// the memory pointed to by buffers will go out
// of scope.
//
this->buffer_.commit(
boost::asio::buffer_copy(
this->buffer_.prepare(boost::asio::buffer_size(buffers)),
buffers));
// Perform SSL handshake. We use the "buffered"
// overload which lets us pass those extra bytes.
//
stream().async_handshake(
boost::asio::ssl::stream_base::server,
buffers,
this->strand_.wrap(
std::bind(
&async_https_con::on_buffered_handshake,
this->shared_from_this(),
std::placeholders::_1,
std::placeholders::_2)));
}
private:
friend class async_http_con_base<async_https_con<Services...>, Services...>;
// Called by the base class before starting the main loop.
//
void
do_handshake()
{
// This is SSL so perform the handshake
//
stream().async_handshake(
boost::asio::ssl::stream_base::server,
this->strand_.wrap(
std::bind(
&async_https_con::on_handshake,
this->shared_from_this(),
std::placeholders::_1)));
}
// Called when the SSL handshake completes
void
on_handshake(error_code ec)
{
if(ec)
return this->fail("on_handshake", ec);
// No error so run the main loop
this->do_run();
}
// Called when the buffered SSL handshake completes
void
on_buffered_handshake(error_code ec, std::size_t bytes_transferred)
{
if(ec)
return this->fail("on_handshake", ec);
// Consume what was read but leave the rest
this->buffer_.consume(bytes_transferred);
// No error so run the main loop
this->do_run();
}
// Called when the end of stream is reached
void
do_shutdown()
{
// This is an SSL shutdown
//
stream().async_shutdown(
this->strand_.wrap(
std::bind(
&async_https_con::on_shutdown,
this->shared_from_this(),
std::placeholders::_1)));
}
// Called when the SSL shutdown completes
void
on_shutdown(error_code ec)
{
if(ec)
return this->fail("on_shutdown", ec);
}
};
//------------------------------------------------------------------------------
/* A synchronous HTTPS port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service
*/
template<class... Services>
class https_sync_port
{
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The list of services connections created from this port will support
service_list<Services...> services_;
// The SSL context containing the server's credentials
boost::asio::ssl::context& ctx_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param ctx The SSL context holding the SSL certificates to use
*/
https_sync_port(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx)
: instance_(instance)
, log_(log)
, ctx_(ctx)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create an HTTPS connection object
// and transfer ownership of the socket.
//
std::make_shared<sync_https_con<Services...>>(
std::move(sock),
ctx_,
"https_sync_port",
log_,
services_,
instance_.next_id(),
ep)->run();
}
};
//------------------------------------------------------------------------------
/* An asynchronous HTTPS port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service
*/
template<class... Services>
class https_async_port
{
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The list of services connections created from this port will support
service_list<Services...> services_;
// The SSL context containing the server's credentials
boost::asio::ssl::context& ctx_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
*/
https_async_port(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx)
: instance_(instance)
, log_(log)
, ctx_(ctx)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create an SSL connection object
// and transfer ownership of the socket.
//
std::make_shared<async_https_con<Services...>>(
std::move(sock),
ctx_,
"https_async_port",
log_,
services_,
instance_.next_id(),
ep)->run();
}
};
} // framework
#endif

View File

@@ -0,0 +1,446 @@
//
// 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)
//
#include "server.hpp"
#include "http_async_port.hpp"
#include "http_sync_port.hpp"
#include "ws_async_port.hpp"
#include "ws_sync_port.hpp"
#if 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/program_options.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
{
beast::websocket::permessage_deflate pmd_;
public:
set_ws_options(beast::websocket::permessage_deflate const& pmd)
: pmd_(pmd)
{
}
template<class NextLayer>
void
operator()(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 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;
};
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()
("root,r", po::value<std::string>()->default_value("."),
"Set the root directory for serving files")
("port,p", po::value<std::uint16_t>()->default_value(1000),
"Set the base port number for the server")
("ip", po::value<std::string>()->default_value("0.0.0.0"),
"Set the IP address to bind to, \"0.0.0.0\" for all")
("threads,n", po::value<std::size_t>()->default_value(4),
"Set the number of threads to use")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
// Get the IP address from the options
std::string const ip = vm["ip"].as<std::string>();
// Get the port number from the options
std::uint16_t const port = vm["port"].as<std::uint16_t>();
// Build an endpoint from the address and port
address_type const addr{address_type::from_string(ip)};
// Get the number of threads from the command line
std::size_t const threads = vm["threads"].as<std::size_t>();
// Get the root path from the command line
boost::filesystem::path const root = vm["root"].as<std::string>();
// These settings will be applied to all new websocket connections
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 BEAST_USE_OPENSSL
ssl_certificate cert;
//--------------------------------------------------------------------------
//
// Synchronous Secure WebSocket HTTPS
//
// port + 4 port + 5
//
//--------------------------------------------------------------------------
{
// Create a WebSocket port
//
auto wsp = instance.make_port<wss_sync_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 4)},
instance,
std::cout,
cert.get(),
set_ws_options{pmd});
if(ec)
return fail("wss_sync_port", ec);
// Create an HTTP port
//
auto sp = instance.make_port<https_sync_port<
ws_upgrade_service<wss_sync_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 5)},
instance,
std::cout,
cert.get());
if(ec)
return fail("https_sync_port", ec);
// Init the ws_upgrade_service to
// forward upgrades to the WebSocket port.
//
sp->template init<0>(
ec,
*wsp // The websocket port handler
);
if(ec)
return fail("http_sync_port/ws_upgrade_service", ec);
// Init the file_service to point to the root path.
//
sp->template init<1>(
ec,
root, // The root path
"http_sync_port" // The value for the Server field
);
if(ec)
return fail("https_sync_port/file_service", ec);
}
//--------------------------------------------------------------------------
//
// Asynchronous Secure WebSocket HTTPS
//
// port + 6 port + 7
//
//--------------------------------------------------------------------------
{
// Create a WebSocket port
//
auto wsp = instance.make_port<wss_async_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 6)},
instance,
std::cout,
cert.get(),
set_ws_options{pmd}
);
if(ec)
return fail("ws_async_port", ec);
// Create an HTTP port
//
auto sp = instance.make_port<https_async_port<
ws_upgrade_service<wss_async_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 7)},
instance,
std::cout,
cert.get());
if(ec)
return fail("https_async_port", ec);
// Init the ws_upgrade_service to
// forward upgrades to the WebSocket port.
//
sp->template init<0>(
ec,
*wsp // The websocket port handler
);
if(ec)
return fail("https_async_port/ws_upgrade_service", ec);
// Init the file_service to point to the root path.
//
sp->template init<1>(
ec,
root, // The root path
"https_async_port" // The value for the Server field
);
if(ec)
return fail("https_async_port/file_service", ec);
}
//--------------------------------------------------------------------------
//
// Multi-Port HTTP, WebSockets,
// HTTPS Secure WebSockets
//
// Asynchronous, all on the same port!
//
// port + 8
//
//--------------------------------------------------------------------------
{
// Create a multi_port
//
auto sp = instance.make_port<multi_port<
ws_upgrade_service<multi_port_base>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 8)},
instance,
std::cout,
cert.get(),
set_ws_options{pmd});
if(ec)
return fail("multi_port", ec);
// Init the ws_upgrade_service to forward requests to the multi_port.
//
sp->template init<0>(
ec,
*sp // The websocket port handler
);
if(ec)
return fail("multi_port/ws_upgrade_service", ec);
// Init the ws_upgrade_service to
// forward upgrades to the Multi port.
//
sp->template init<1>(
ec,
root, // The root path
"multi_port" // The value for the Server field
);
if(ec)
return fail("multi_port/file_service", ec);
}
#endif
sig_wait();
}

View File

@@ -0,0 +1,397 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP
#define 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 <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
beast::static_buffer_n<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 != 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(beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 = boost::function<void(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(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,
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,
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

View File

@@ -0,0 +1,266 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP
#define BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP
#include "framework.hpp"
#include <boost/optional.hpp>
#include <memory>
#include <string>
#include <stdexcept>
#include <thread>
#include <type_traits>
#include <utility>
namespace framework {
/** A server instance that accepts TCP/IP connections.
This is a general purpose TCP/IP server which contains
zero or more user defined "ports". Each port represents
a listening socket whose behavior is defined by an
instance of the @b PortHandler concept.
To use the server, construct the class and then add the
ports that you want using @ref make_port.
@par Example
@code
// Create a server with 4 threads
//
framework::server si(4);
// Create a port that echoes everything back.
// Bind all available interfaces on port 1000.
//
framework::error_code ec;
si.make_port<echo_port>(
ec,
server::endpoint_type{
server::address_type::from_string("0.0.0.0"), 1000}
);
...
// Close all connections, shut down the server
si.stop();
@endcode
*/
class server
{
io_service_type ios_;
std::vector<std::thread> tv_;
boost::optional<boost::asio::io_service::work> work_;
public:
server(server const&) = delete;
server& operator=(server const&) = delete;
/** Constructor
@param n The number of threads to run on the `io_service`,
which must be greater than zero.
*/
explicit
server(std::size_t n = 1)
: work_(ios_)
{
if(n < 1)
throw std::invalid_argument{"threads < 1"};
tv_.reserve(n);
while(n--)
tv_.emplace_back(
[&]
{
ios_.run();
});
}
/** Destructor
Upon destruction, the `io_service` will be stopped
and all pending completion handlers destroyed.
*/
~server()
{
work_ = boost::none;
ios_.stop();
for(auto& t : tv_)
t.join();
}
/// Return the `io_service` associated with the server
boost::asio::io_service&
get_io_service()
{
return ios_;
}
/** Return a new, small integer unique id
These ids are used to uniquely identify connections
in log output.
*/
std::size_t
next_id()
{
static std::atomic<std::size_t> id_{0};
return ++id_;
}
/** Create a listening port.
@param ec Set to the error, if any occurred.
@param ep The address and port to bind to.
@param args Optional arguments, forwarded to the
port handler's constructor.
@tparam PortHandler The port handler to use for handling
incoming connections on this port. This handler must meet
the requirements of @b PortHandler. A model of PortHandler
is as follows:
@code
struct PortHandler
{
void
on_accept(
endpoint_type ep, // address of the remote endpoint
socket_type&& sock, // the connected socket
);
};
@endcode
*/
template<class PortHandler, class... Args>
std::shared_ptr<PortHandler>
make_port(
error_code& ec,
endpoint_type const& ep,
Args&&... args);
};
//------------------------------------------------------------------------------
/* This implementation class wraps the PortHandler and
manages the listening socket. Upon an incoming connection
it transfers ownership of the socket to the PortHandler.
*/
template<class PortHandler>
class port
: public std::enable_shared_from_this<
port<PortHandler>>
{
server& instance_;
PortHandler handler_;
endpoint_type ep_;
strand_type strand_;
acceptor_type acceptor_;
socket_type sock_;
public:
// Constructor
//
// args are forwarded to the PortHandler
//
template<class... Args>
explicit
port(server& instance, Args&&... args)
: instance_(instance)
, handler_(std::forward<Args>(args)...)
, strand_(instance.get_io_service())
, acceptor_(instance.get_io_service())
, sock_(instance.get_io_service())
{
}
// Return the PortHandler wrapped in a shared_ptr
//
std::shared_ptr<PortHandler>
handler()
{
// This uses a feature of std::shared_ptr invented by
// Peter Dimov where the managed object piggy backs off
// the reference count of another object containing it.
//
return std::shared_ptr<PortHandler>(
this->shared_from_this(), &handler_);
}
// Open the listening socket
//
void
open(endpoint_type const& ep, error_code& ec)
{
acceptor_.open(ep.protocol(), ec);
if(ec)
return;
acceptor_.set_option(
boost::asio::socket_base::reuse_address{true});
acceptor_.bind(ep, ec);
if(ec)
return;
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
return;
acceptor_.async_accept(sock_, ep_,
std::bind(&port::on_accept, this->shared_from_this(),
std::placeholders::_1));
}
private:
// Called when an incoming connection is accepted
//
void
on_accept(error_code ec)
{
if(! acceptor_.is_open())
return;
if(ec == boost::asio::error::operation_aborted)
return;
if(! ec)
{
// Transfer ownership of the socket to the PortHandler
//
handler_.on_accept(std::move(sock_), ep_);
}
acceptor_.async_accept(sock_, ep_,
std::bind(&port::on_accept, this->shared_from_this(),
std::placeholders::_1));
}
};
//------------------------------------------------------------------------------
template<class PortHandler, class... Args>
std::shared_ptr<PortHandler>
server::
make_port(
error_code& ec,
endpoint_type const& ep,
Args&&... args)
{
auto sp = std::make_shared<port<PortHandler>>(
*this, std::forward<Args>(args)...);
sp->open(ep, ec);
if(ec)
return nullptr;
return sp->handler();
}
} // framework
#endif

View File

@@ -0,0 +1,192 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP
#define BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP
#include "framework.hpp"
#include <beast/http/message.hpp>
#include <boost/optional.hpp>
#include <utility>
namespace framework {
/** A list of HTTP services which may process requests.
When a service is invoked, it is provided with the stream and
endpoint metadata in addtion to an HTTP request. The service
decides whether or not the process the request, returning
`true` if the request is processed or `false` if it does not
process the request.
@see file_service, ws_upgrade_service
*/
template<class... Services>
class service_list
{
// This helper is for tag-dispatching tuple index
template<std::size_t I>
using C = std::integral_constant<std::size_t, I>;
// Each service is wrapped in a boost::optional so we
// can construct them one by one later, instead of
// having to construct them all at once.
//
std::tuple<boost::optional<Services>...> list_;
public:
/// Constructor
service_list() = default;
/// Constructor
service_list(service_list&&) = default;
/// Constructor
service_list(service_list const&) = default;
/** Initialize a service.
Every service in the list must be initialized exactly once
before the service list is invoked.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
// First, construct the service inside the optional
std::get<Index>(list_).emplace(std::forward<Args>(args)...);
// Now allow the service to finish the initialization
std::get<Index>(list_)->init(ec);
}
/** Handle a request.
This function attempts to process the given HTTP request by
invoking each service one at a time starting with the first
service in the list. When a service indicates that it handles
the request, by returning `true`, the function stops and
returns the value `true`. Otherwise, if no service handles
the request then the function returns the value `false`.
@param stream The stream belonging to the connection. A service
which handles the request may optionally take ownership of the
stream.
@param ep The remote endpoint of the connection corresponding
to the stream.
@param req The request message to attempt handling. A service
which handles the request may optionally take ownership of the
message.
@param send The function to invoke with the response. The function
should have this equivalent signature:
@code
template<class Body>
void
send(response<Body>&&);
@endcode
In C++14 this can be expressed using a generic lambda. In
C++11 it will require a template member function of an invocable
object.
@return `true` if the request was handled by a service.
*/
template<
class Stream,
class Body,
class Send>
bool
respond(
Stream&& stream,
endpoint_type const& ep,
beast::http::request<Body>&& req,
Send const& send) const
{
return try_respond(
std::move(stream),
ep,
std::move(req),
send, C<0>{});
}
private:
/* The implementation of `try_respond` is implemented using
tail recursion which can usually be optimized away to
something resembling a switch statement.
*/
template<
class Stream,
class Body,
class Send>
bool
try_respond(
Stream&&,
endpoint_type const&,
beast::http::request<Body>&&,
Send const&,
C<sizeof...(Services)> const&) const
{
// This function breaks the recursion for the case where
// where the Index is one past the last type in the list.
//
return false;
}
// Invoke the I-th type in the type list
//
template<
class Stream,
class Body,
class Send,
std::size_t I>
bool
try_respond(
Stream&& stream,
endpoint_type const& ep,
beast::http::request<Body>&& req,
Send const& send,
C<I> const&) const
{
// If the I-th service handles the request then return
//
if(std::get<I>(list_)->respond(
std::move(stream),
ep,
std::move(req),
send))
return true;
// Try the I+1th service. If I==sizeof...(Services)
// then we call the other overload and return false.
//
return try_respond(
std::move(stream),
ep,
std::move(req),
send,
C<I+1>{});
}
};
} // framework
#endif

View File

@@ -0,0 +1,146 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP
#define BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP
#include <boost/asio/buffer.hpp>
#include <boost/asio/ssl/context.hpp>
#include <cstddef>
#include <memory>
namespace framework {
// 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>
void
ssl_certificate::construct()
{
/*
The certificate was generated from CMD.EXE on Windows 10 using:
winpty openssl dhparam -out dh.pem 2048
winpty openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com"
*/
std::string const cert =
"-----BEGIN CERTIFICATE-----\n"
"MIIDaDCCAlCgAwIBAgIJAO8vBu8i8exWMA0GCSqGSIb3DQEBCwUAMEkxCzAJBgNV\n"
"BAYTAlVTMQswCQYDVQQIDAJDQTEtMCsGA1UEBwwkTG9zIEFuZ2VsZXNPPUJlYXN0\n"
"Q049d3d3LmV4YW1wbGUuY29tMB4XDTE3MDUwMzE4MzkxMloXDTQ0MDkxODE4Mzkx\n"
"MlowSTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMS0wKwYDVQQHDCRMb3MgQW5n\n"
"ZWxlc089QmVhc3RDTj13d3cuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA\n"
"A4IBDwAwggEKAoIBAQDJ7BRKFO8fqmsEXw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcF\n"
"xqGitbnLIrOgiJpRAPLy5MNcAXE1strVGfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7b\n"
"Fu8TsCzO6XrxpnVtWk506YZ7ToTa5UjHfBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO\n"
"9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wWKIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBp\n"
"yY8anC8u4LPbmgW0/U31PH0rRVfGcBbZsAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrv\n"
"enu2tOK9Qx6GEzXh3sekZkxcgh+NlIxCNxu//Dk9AgMBAAGjUzBRMB0GA1UdDgQW\n"
"BBTZh0N9Ne1OD7GBGJYz4PNESHuXezAfBgNVHSMEGDAWgBTZh0N9Ne1OD7GBGJYz\n"
"4PNESHuXezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCmTJVT\n"
"LH5Cru1vXtzb3N9dyolcVH82xFVwPewArchgq+CEkajOU9bnzCqvhM4CryBb4cUs\n"
"gqXWp85hAh55uBOqXb2yyESEleMCJEiVTwm/m26FdONvEGptsiCmF5Gxi0YRtn8N\n"
"V+KhrQaAyLrLdPYI7TrwAOisq2I1cD0mt+xgwuv/654Rl3IhOMx+fKWKJ9qLAiaE\n"
"fQyshjlPP9mYVxWOxqctUdQ8UnsUKKGEUcVrA08i1OAnVKlPFjKBvk+r7jpsTPcr\n"
"9pWXTO9JrYMML7d+XRSZA1n3856OqZDX4403+9FnXCvfcLZLLKTBvwwFgEFGpzjK\n"
"UEVbkhd5qstF6qWK\n"
"-----END CERTIFICATE-----\n";
std::string const key =
"-----BEGIN PRIVATE KEY-----\n"
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJ7BRKFO8fqmsE\n"
"Xw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcFxqGitbnLIrOgiJpRAPLy5MNcAXE1strV\n"
"GfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7bFu8TsCzO6XrxpnVtWk506YZ7ToTa5UjH\n"
"fBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wW\n"
"KIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBpyY8anC8u4LPbmgW0/U31PH0rRVfGcBbZ\n"
"sAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrvenu2tOK9Qx6GEzXh3sekZkxcgh+NlIxC\n"
"Nxu//Dk9AgMBAAECggEBAK1gV8uETg4SdfE67f9v/5uyK0DYQH1ro4C7hNiUycTB\n"
"oiYDd6YOA4m4MiQVJuuGtRR5+IR3eI1zFRMFSJs4UqYChNwqQGys7CVsKpplQOW+\n"
"1BCqkH2HN/Ix5662Dv3mHJemLCKUON77IJKoq0/xuZ04mc9csykox6grFWB3pjXY\n"
"OEn9U8pt5KNldWfpfAZ7xu9WfyvthGXlhfwKEetOuHfAQv7FF6s25UIEU6Hmnwp9\n"
"VmYp2twfMGdztz/gfFjKOGxf92RG+FMSkyAPq/vhyB7oQWxa+vdBn6BSdsfn27Qs\n"
"bTvXrGe4FYcbuw4WkAKTljZX7TUegkXiwFoSps0jegECgYEA7o5AcRTZVUmmSs8W\n"
"PUHn89UEuDAMFVk7grG1bg8exLQSpugCykcqXt1WNrqB7x6nB+dbVANWNhSmhgCg\n"
"VrV941vbx8ketqZ9YInSbGPWIU/tss3r8Yx2Ct3mQpvpGC6iGHzEc/NHJP8Efvh/\n"
"CcUWmLjLGJYYeP5oNu5cncC3fXUCgYEA2LANATm0A6sFVGe3sSLO9un1brA4zlZE\n"
"Hjd3KOZnMPt73B426qUOcw5B2wIS8GJsUES0P94pKg83oyzmoUV9vJpJLjHA4qmL\n"
"CDAd6CjAmE5ea4dFdZwDDS8F9FntJMdPQJA9vq+JaeS+k7ds3+7oiNe+RUIHR1Sz\n"
"VEAKh3Xw66kCgYB7KO/2Mchesu5qku2tZJhHF4QfP5cNcos511uO3bmJ3ln+16uR\n"
"GRqz7Vu0V6f7dvzPJM/O2QYqV5D9f9dHzN2YgvU9+QSlUeFK9PyxPv3vJt/WP1//\n"
"zf+nbpaRbwLxnCnNsKSQJFpnrE166/pSZfFbmZQpNlyeIuJU8czZGQTifQKBgHXe\n"
"/pQGEZhVNab+bHwdFTxXdDzr+1qyrodJYLaM7uFES9InVXQ6qSuJO+WosSi2QXlA\n"
"hlSfwwCwGnHXAPYFWSp5Owm34tbpp0mi8wHQ+UNgjhgsE2qwnTBUvgZ3zHpPORtD\n"
"23KZBkTmO40bIEyIJ1IZGdWO32q79nkEBTY+v/lRAoGBAI1rbouFYPBrTYQ9kcjt\n"
"1yfu4JF5MvO9JrHQ9tOwkqDmNCWx9xWXbgydsn/eFtuUMULWsG3lNjfst/Esb8ch\n"
"k5cZd6pdJZa4/vhEwrYYSuEjMCnRb0lUsm7TsHxQrUd6Fi/mUuFU/haC0o0chLq7\n"
"pVOUFq5mW8p0zbtfHbjkgxyF\n"
"-----END PRIVATE KEY-----\n";
std::string const dh =
"-----BEGIN DH PARAMETERS-----\n"
"MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n"
"/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n"
"4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n"
"tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n"
"oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n"
"QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n"
"-----END DH PARAMETERS-----\n";
ctx_.set_password_callback(
[](std::size_t,
boost::asio::ssl::context_base::password_purpose)
{
return "test";
});
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(
boost::asio::buffer(cert.data(), cert.size()));
ctx_.use_private_key(
boost::asio::buffer(key.data(), key.size()),
boost::asio::ssl::context::file_format::pem);
ctx_.use_tmp_dh(
boost::asio::buffer(dh.data(), dh.size()));
}
} // framework
#endif

View File

@@ -0,0 +1,374 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP
#define BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP
#include "server.hpp"
#include <beast/core/multi_buffer.hpp>
#include <beast/websocket/stream.hpp>
#include <boost/function.hpp>
#include <memory>
#include <ostream>
namespace framework {
// This object holds the state of the connection
// including, most importantly, the socket or stream.
//
//
template<class Derived>
class async_ws_con_base
{
// This function lets us access members of the derived class
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The string used to set the Server http field
std::string server_name_;
// The stream to use for logging
std::ostream& log_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
// This is used to hold the message data
beast::multi_buffer buffer_;
protected:
// The strand makes sure that our data is
// accessed from only one thread at a time.
//
strand_type strand_;
public:
// Constructor
template<class Callback>
async_ws_con_base(
beast::string_view server_name,
std::ostream& log,
std::size_t id,
endpoint_type const& ep,
Callback const& cb)
: server_name_(server_name)
, log_(log)
, id_(id)
, ep_(ep)
// Limit of 1MB on messages
, buffer_(1024 * 1024)
, strand_(impl().stream().get_io_service())
{
cb(impl().stream());
}
// Run the connection
//
void
run()
{
impl().do_handshake();
}
// Run the connection.
//
// This overload handles the case where we
// already have the WebSocket Upgrade request.
//
template<class Body>
void
run(beast::http::request<Body> const& req)
{
// Call the overload of accept() which takes
// the request by parameter, instead of reading
// it from the network.
//
impl().stream().async_accept_ex(req,
[&](beast::websocket::response_type& res)
{
res.set(beast::http::field::server, server_name_);
},
strand_.wrap(std::bind(
&async_ws_con_base::on_accept,
impl().shared_from_this(),
std::placeholders::_1)));
}
protected:
// Performs the WebSocket handshake
void
do_accept()
{
// Read the WebSocket upgrade request and attempt
// to send back the response.
//
impl().stream().async_accept_ex(
[&](beast::websocket::response_type& res)
{
res.set(beast::http::field::server, server_name_);
},
strand_.wrap(std::bind(
&async_ws_con_base::on_accept,
impl().shared_from_this(),
std::placeholders::_1)));
}
// This helper reports failures
//
void
fail(std::string what, error_code ec)
{
if(ec != beast::websocket::error::closed)
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
private:
// Called when accept_ex completes
//
void
on_accept(error_code ec)
{
if(ec)
return fail("async_accept", ec);
do_read();
}
// Read the next WebSocket message
//
void
do_read()
{
impl().stream().async_read(
buffer_,
strand_.wrap(std::bind(
&async_ws_con_base::on_read,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Called when the message read completes
//
void
on_read(error_code ec)
{
if(ec)
return fail("on_read", ec);
// Set the outgoing message type. We will use
// the same setting as the message we just read.
//
impl().stream().binary(impl().stream().got_binary());
// Now echo back the message
//
impl().stream().async_write(
buffer_.data(),
strand_.wrap(std::bind(
&async_ws_con_base::on_write,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Called when the message write completes
//
void
on_write(error_code ec)
{
if(ec)
return fail("on_write", ec);
// Empty out the contents of the message buffer
// to prepare it for the next call to read.
//
buffer_.consume(buffer_.size());
// Now read another message
//
do_read();
}
};
//------------------------------------------------------------------------------
// This class represents an asynchronous WebSocket connection
// which uses a plain TCP/IP socket (no encryption) as the stream.
//
class async_ws_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<async_ws_con>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<beast::websocket::stream<socket_type>>
// Constructs last, destroys first
//
, public async_ws_con_base<async_ws_con>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
explicit
async_ws_con(
socket_type&& sock,
Args&&... args)
: base_from_member<beast::websocket::stream<socket_type>>(std::move(sock))
, async_ws_con_base<async_ws_con>(std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
beast::websocket::stream<socket_type>&
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend async_ws_con_base<async_ws_con>;
void
do_handshake()
{
do_accept();
}
};
//------------------------------------------------------------------------------
/** An asynchronous WebSocket @b PortHandler which implements echo.
This is a port handler which accepts WebSocket upgrade HTTP
requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
class ws_async_port
{
// The type of the on_new_stream callback
//
using on_new_stream_cb =
boost::function<void(beast::websocket::stream<socket_type>&)>;
server& instance_;
std::ostream& log_;
on_new_stream_cb cb_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param cb A callback which will be invoked for every new
WebSocket connection. This provides an opportunity to change
the settings on the stream before it is used. The callback
should have this equivalent signature:
@code
template<class NextLayer>
void callback(beast::websocket::stream<NextLayer>&);
@endcode
In C++14 this can be accomplished with a generic lambda. In
C++11 it will be necessary to write out a lambda manually,
with a templated operator().
*/
template<class Callback>
ws_async_port(
server& instance,
std::ostream& log,
Callback const& cb)
: instance_(instance)
, log_(log)
, cb_(cb)
{
}
/** Accept a TCP/IP connection.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(
socket_type&& sock,
endpoint_type ep)
{
std::make_shared<async_ws_con>(
std::move(sock),
"ws_async_port",
log_,
instance_.next_id(),
ep,
cb_)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body>
void
on_upgrade(
socket_type&& sock,
endpoint_type ep,
beast::http::request<Body>&& req)
{
std::make_shared<async_ws_con>(
std::move(sock),
"ws_async_port",
log_,
instance_.next_id(),
ep,
cb_)->run(std::move(req));
}
};
} // framework
#endif

View File

@@ -0,0 +1,432 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP
#define BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP
#include "server.hpp"
#include <beast/core/multi_buffer.hpp>
#include <beast/websocket.hpp>
#include <boost/function.hpp>
#include <memory>
#include <ostream>
#include <thread>
namespace framework {
/** A synchronous WebSocket connection.
This base class implements a WebSocket connection object using
synchronous calls over an unencrypted connection.
It uses the Curiously Recurring Template pattern (CRTP) where
we refer to the derived class in order to access the stream object
to use for reading and writing. This lets the same class be used
for plain and SSL stream objects.
*/
template<class Derived>
class sync_ws_con_base
{
// This function lets us access members of the derived class
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The string used to set the Server http field
std::string server_name_;
// The stream to use for logging
std::ostream& log_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class Callback>
sync_ws_con_base(
beast::string_view server_name,
std::ostream& log,
std::size_t id,
endpoint_type const& ep,
Callback const& cb)
: server_name_(server_name)
, log_(log)
, id_(id)
, ep_(ep)
{
cb(impl().stream());
}
// Run the connection. This is called for the case
// where we have not received the upgrade request yet.
//
void
run()
{
// We run the do_run function in its own thread,
// and bind a shared pointer to the connection object
// into the function. The last reference to the shared
// pointer will go away when the thread exits, thus
// destroying the connection object.
//
std::thread{
&sync_ws_con_base::do_accept,
impl().shared_from_this()
}.detach();
}
// Run the connection from an already-received Upgrade request.
//
template<class Body>
void
run(beast::http::request<Body>&& req)
{
BOOST_ASSERT(beast::websocket::is_upgrade(req));
// We need to transfer ownership of the request object into
// the lambda, but there's no C++14 lambda capture
// so we have to write it out by manually specifying the lambda.
//
std::thread{
lambda<Body>{
impl().shared_from_this(),
std::move(req)
}}.detach();
}
protected:
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
// Don't report the "closed" error since that
// happens under normal circumstances.
//
if(ec && ec != beast::websocket::error::closed)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
log_.flush();
}
}
private:
// This function performs the WebSocket handshake
// and runs the main loop upon success.
void
do_accept()
{
error_code ec;
// Give the derived class a chance to do stuff before we
// enter the main loop. This is for SSL connections really.
//
impl().do_handshake(ec);
if(ec)
return fail("handshake", ec);
// Read the WebSocket upgrade request and attempt
// to send back the response.
//
impl().stream().accept_ex(
[&](beast::websocket::response_type& res)
{
res.insert(beast::http::field::server, server_name_);
},
ec);
if(ec)
return fail("accept", ec);
// Run the connection
//
do_run();
}
// This is the lambda used when launching a connection from
// an already-received request. In C++14 we could simply use
// a lambda capture but this example requires only C++11 so
// we write out the lambda ourselves. This is similar to what
// the compiler would generate anyway.
//
template<class Body>
class lambda
{
std::shared_ptr<sync_ws_con_base> self_;
beast::http::request<Body> req_;
public:
// Constructor
//
// This is the equivalent of the capture section of the lambda.
//
lambda(
std::shared_ptr<sync_ws_con_base> self,
beast::http::request<Body>&& req)
: self_(std::move(self))
, req_(std::move(req))
{
BOOST_ASSERT(beast::websocket::is_upgrade(req_));
}
// Invoke the lambda
//
void
operator()()
{
BOOST_ASSERT(beast::websocket::is_upgrade(req_));
error_code ec;
{
// Move the message to the stack so we can get
// rid of resources, otherwise it will linger
// for the lifetime of the connection.
//
auto req = std::move(req_);
// Call the overload of accept() which takes
// the request by parameter, instead of reading
// it from the network.
//
self_->impl().stream().accept_ex(req,
[&](beast::websocket::response_type& res)
{
res.insert(beast::http::field::server, self_->server_name_);
},
ec);
}
if(ec)
return self_->fail("accept", ec);
self_->do_run();
}
};
void
do_run()
{
error_code ec;
// Loop, reading messages and echoing them back.
//
for(;;)
{
// This buffer holds the message. We place a one
// megabyte limit on the size to prevent abuse.
//
beast::multi_buffer buffer{1024*1024};
// Read the message
//
impl().stream().read(buffer, ec);
if(ec)
return fail("read", ec);
// Set the outgoing message type. We will use
// the same setting as the message we just read.
//
impl().stream().binary(impl().stream().got_binary());
// Now echo back the message
//
impl().stream().write(buffer.data(), ec);
if(ec)
return fail("write", ec);
}
}
};
//------------------------------------------------------------------------------
// This class represents a synchronous WebSocket connection
// which uses a plain TCP/IP socket (no encryption) as the stream.
//
class sync_ws_con
// Give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason
// is so that the shared_ptr has the correct type. This lets
// the derived class (this class) use its members in calls to
// `std::bind`, without an ugly call to `dynamic_downcast` or
// other nonsense.
//
: public std::enable_shared_from_this<sync_ws_con>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<beast::websocket::stream<socket_type>>
// Constructs last, destroys first
//
, public sync_ws_con_base<sync_ws_con>
{
public:
// Construct the plain connection.
//
template<class... Args>
explicit
sync_ws_con(
socket_type&& sock,
Args&&... args)
: base_from_member<beast::websocket::stream<socket_type>>(std::move(sock))
, sync_ws_con_base<sync_ws_con>(std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
beast::websocket::stream<socket_type>&
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend class sync_ws_con_base<sync_ws_con>;
// This is called by the base before running the main loop.
// There's nothing to do for a plain connection.
//
void
do_handshake(error_code& ec)
{
// This is required by the specifications for error_code
//
ec = {};
}
};
//------------------------------------------------------------------------------
/** A synchronous WebSocket @b PortHandler which implements echo.
This is a port handler which accepts WebSocket upgrade HTTP
requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
class ws_sync_port
{
// The type of the on_new_stream callback
//
using on_new_stream_cb =
boost::function<void(beast::websocket::stream<socket_type>&)>;
server& instance_;
std::ostream& log_;
on_new_stream_cb cb_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param cb A callback which will be invoked for every new
WebSocket connection. This provides an opportunity to change
the settings on the stream before it is used. The callback
should have this equivalent signature:
@code
template<class NextLayer>
void callback(beast::websocket::stream<NextLayer>&);
@endcode
In C++14 this can be accomplished with a generic lambda. In
C++11 it will be necessary to write out a lambda manually,
with a templated operator().
*/
template<class Callback>
ws_sync_port(
server& instance,
std::ostream& log,
Callback const& cb)
: instance_(instance)
, log_(log)
, cb_(cb)
{
}
/** Accept a TCP/IP connection.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create our connection object and run it
//
std::make_shared<sync_ws_con>(
std::move(sock),
"ws_sync_port",
log_,
instance_.next_id(),
ep,
cb_)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body>
void
on_upgrade(
socket_type&& sock,
endpoint_type ep,
beast::http::request<Body>&& req)
{
// Create the connection object and run it,
// transferring ownership of the ugprade request.
//
std::make_shared<sync_ws_con>(
std::move(sock),
"ws_sync_port",
log_,
instance_.next_id(),
ep,
cb_)->run(std::move(req));
}
};
} // framework
#endif

View File

@@ -0,0 +1,101 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP
#define BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP
#include "framework.hpp"
#include <beast/http/message.hpp>
#include <beast/websocket/rfc6455.hpp>
#include <memory>
namespace framework {
/** An HTTP service which transfers WebSocket upgrade request to another port handler.
@tparam PortHandler The type of port handler. The service will
handle WebSocket Upgrade requests by transferring ownership
of the stream and request to a port handler of this type.
*/
template<class PortHandler>
class ws_upgrade_service
{
PortHandler& handler_;
public:
/** Constructor
@param handler A shared pointer to the @b PortHandler to
handle WebSocket upgrade requests.
*/
explicit
ws_upgrade_service(PortHandler& handler)
: handler_(handler)
{
}
/** Initialize the service.
This provides an opportunity for the service to perform
initialization which may fail, while reporting an error
code instead of throwing an exception from the constructor.
*/
void
init(error_code& ec)
{
// This is required by the error_code specification
//
ec = {};
}
/** Handle a WebSocket Upgrade request.
If the request is an upgrade request, ownership of the
stream and request will be transferred to the corresponding
WebSocket port handler.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint associated with the stream.
@req The request to check.
*/
template<
class Stream,
class Body,
class Send>
bool
respond(
Stream&& stream,
endpoint_type const& ep,
beast::http::request<Body>&& req,
Send const&) const
{
// If its not an upgrade request, return `false`
// to indicate that we are not handling it.
//
if(! beast::websocket::is_upgrade(req))
return false;
// Its an ugprade request, so transfer ownership
// of the stream and request to the port handler.
//
handler_.on_upgrade(
std::move(stream),
ep,
std::move(req));
// Tell the service list that we handled the request.
//
return true;
}
};
} // framework
#endif

View File

@@ -0,0 +1,438 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_WSS_PORTS_HPP
#define BEAST_EXAMPLE_SERVER_WSS_PORTS_HPP
#include "ws_sync_port.hpp"
#include "ws_async_port.hpp"
#include "../common/ssl_stream.hpp"
#include <boost/asio/ssl.hpp>
#include <boost/function.hpp>
namespace framework {
//------------------------------------------------------------------------------
// A synchronous WebSocket connection over an SSL connection
//
class sync_wss_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_wss_con>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>
// Constructs last, destroys first
//
, public sync_ws_con_base<sync_wss_con>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
explicit
sync_wss_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>(std::move(sock), ctx)
, sync_ws_con_base<sync_wss_con>(std::forward<Args>(args)...)
{
}
// Construct from an existing, handshaked SSL stream
//
template<class... Args>
sync_wss_con(
ssl_stream<socket_type>&& stream,
Args&&... args)
: base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>(std::move(stream))
, sync_ws_con_base<sync_wss_con>(std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
beast::websocket::stream<ssl_stream<socket_type>>&
stream()
{
return this->member;
}
private:
friend class sync_ws_con_base<sync_wss_con>;
// This is called by the base before running the main loop.
//
void
do_handshake(error_code& ec)
{
// Perform the SSL handshake
//
// We use next_layer() to get at the underlying ssl_stream
//
stream().next_layer().handshake(boost::asio::ssl::stream_base::server, ec);
}
};
//------------------------------------------------------------------------------
// An asynchronous WebSocket connection over an SSL connection
//
class async_wss_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_wss_con>
// The stream should be created before the base class so
// use the "base from member" idiom.
//
, public base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>
// Constructs last, destroys first
//
, public async_ws_con_base<async_wss_con>
{
public:
// Constructor
//
// Additional arguments are forwarded to the base class
//
template<class... Args>
async_wss_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>(std::move(sock), ctx)
, async_ws_con_base<async_wss_con>(std::forward<Args>(args)...)
{
}
// Construct from an existing, handshaked SSL stream
//
template<class... Args>
async_wss_con(
ssl_stream<socket_type>&& stream,
Args&&... args)
: base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>(std::move(stream))
, async_ws_con_base<async_wss_con>(std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the object to use for
// reading and writing HTTP messages. This allows the same base
// class to work with different return types for `stream()` such
// as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&`
//
beast::websocket::stream<ssl_stream<socket_type>>&
stream()
{
return this->member;
}
private:
friend class async_ws_con_base<async_wss_con>;
// Called by the port to start the connection
// after creating the object
//
void
do_handshake()
{
// This is SSL so perform the handshake first
//
stream().next_layer().async_handshake(
boost::asio::ssl::stream_base::server,
this->strand_.wrap(
std::bind(
&async_wss_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);
// Move on to accepting the WebSocket handshake
//
this->do_accept();
}
};
//------------------------------------------------------------------------------
/** A synchronous Secure WebSocket @b PortHandler which implements echo.
This is a port handler which accepts Secure WebSocket upgrade
HTTP requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
class wss_sync_port
{
// VFALCO We use boost::function to work around a compiler
// crash with gcc and clang using libstdc++
// The types of the on_new_stream callbacks
//
using on_new_stream_cb1 =
boost::function<void(beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 =
boost::function<void(beast::websocket::stream<ssl_stream<socket_type>>&)>;
server& instance_;
std::ostream& log_;
boost::asio::ssl::context& ctx_;
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(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>
wss_sync_port(
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 TCP/IP connection.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create our connection object and run it
//
std::make_shared<sync_wss_con>(
std::move(sock),
ctx_,
"wss_sync_port",
log_,
instance_.next_id(),
ep,
cb2_)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body>
void
on_upgrade(
ssl_stream<socket_type>&& stream,
endpoint_type ep,
beast::http::request<Body>&& req)
{
// Create the connection object and run it,
// transferring ownership of the ugprade request.
//
std::make_shared<sync_wss_con>(
std::move(stream),
"wss_sync_port",
log_,
instance_.next_id(),
ep,
cb2_)->run(std::move(req));
}
};
//------------------------------------------------------------------------------
/** An asynchronous WebSocket @b PortHandler which implements echo.
This is a port handler which accepts WebSocket upgrade HTTP
requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
class wss_async_port
{
// VFALCO We use boost::function to work around a compiler
// crash with gcc and clang using libstdc++
// The types of the on_new_stream callbacks
//
using on_new_stream_cb1 =
boost::function<void(beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 =
boost::function<void(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(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>
wss_async_port(
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 TCP/IP connection.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(
socket_type&& sock,
endpoint_type ep)
{
std::make_shared<async_wss_con>(
std::move(sock),
ctx_,
"wss_async_port",
log_,
instance_.next_id(),
ep,
cb2_)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body>
void
on_upgrade(
ssl_stream<socket_type>&& stream,
endpoint_type ep,
beast::http::request<Body>&& req)
{
std::make_shared<async_wss_con>(
std::move(stream),
"wss_async_port",
log_,
instance_.next_id(),
ep,
cb2_)->run(std::move(req));
}
};
} // framework
#endif

View File

@@ -0,0 +1,15 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/websocket-client-ssl "/")
add_executable (websocket-client-ssl
${BEAST_INCLUDES}
websocket_client_ssl.cpp
)
target_link_libraries(websocket-client-ssl
Beast
${OPENSSL_LIBRARIES}
)

View File

@@ -0,0 +1,51 @@
#
# 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)
#
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 ssl-websocket-client :
ssl_websocket_client.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,119 @@
//
// 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)
//
#include "../common/root_certificates.hpp"
#include <beast/core.hpp>
#include <beast/websocket.hpp>
#include <beast/websocket/ssl.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <cstdlib>
#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 websocket = beast::websocket; // from <beast/websocket.hpp>
int main()
{
// A helper for reporting errors
auto const fail =
[](std::string what, beast::error_code ec)
{
std::cerr << what << ": " << ec.message() << std::endl;
std::cerr.flush();
return EXIT_FAILURE;
};
boost::system::error_code ec;
// Set up an asio socket to connect to a remote host
boost::asio::io_service ios;
tcp::resolver r{ios};
tcp::socket sock{ios};
// Look up the domain name
std::string const host = "echo.websocket.org";
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
using stream_type = ssl::stream<tcp::socket&>;
stream_type stream{sock, ctx};
stream.set_verify_mode(ssl::verify_none);
// Perform SSL handshaking
stream.handshake(ssl::stream_base::client, ec);
if(ec)
return fail("ssl handshake", ec);
// Now wrap the handshaked SSL stream in a websocket stream
websocket::stream<stream_type&> ws{stream};
// Perform the websocket handshake
ws.handshake(host, "/", ec);
if(ec)
return fail("handshake", ec);
// Send a message
ws.write(boost::asio::buffer("Hello, world!"), ec);
if(ec)
return fail("write", ec);
// This buffer will hold the incoming message
beast::multi_buffer b;
// Read the message into our buffer
ws.read(b, ec);
if(ec)
return fail("read", ec);
// Send a "close" frame to the other end, this is a websocket thing
ws.close(websocket::close_code::normal, ec);
if(ec)
return fail("close", ec);
// The buffers() function helps print a ConstBufferSequence
std::cout << beast::buffers(b.data()) << std::endl;
// WebSocket says that to close a connection you have
// to keep reading messages until you receive a close frame.
// Beast delivers the close frame as an error from read.
//
beast::drain_buffer drain; // Throws everything away efficiently
for(;;)
{
// Keep reading messages...
ws.read(drain, ec);
// ...until we get the special error code
if(ec == websocket::error::closed)
break;
// Some other error occurred, report it and exit.
if(ec)
return fail("close", ec);
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,13 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/websocket-client "/")
add_executable (websocket-client
${BEAST_INCLUDES}
${EXTRAS_INCLUDES}
websocket_client.cpp
)
target_link_libraries(websocket-client Beast)

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe websocket-client :
websocket_client.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,101 @@
//
// 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)
//
//[example_websocket_client
#include <beast/core.hpp>
#include <beast/websocket.hpp>
#include <boost/asio.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
namespace websocket = beast::websocket; // from <beast/websocket.hpp>
int main()
{
// A helper for reporting errors
auto const fail =
[](std::string what, beast::error_code ec)
{
std::cerr << what << ": " << ec.message() << std::endl;
std::cerr.flush();
return EXIT_FAILURE;
};
boost::system::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 = "echo.websocket.org";
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);
// Wrap the now-connected socket in a websocket stream
websocket::stream<tcp::socket&> ws{sock};
// Perform the websocket handshake
ws.handshake(host, "/", ec);
if(ec)
return fail("handshake", ec);
// Send a message
ws.write(boost::asio::buffer(std::string("Hello, world!")), ec);
if(ec)
return fail("write", ec);
// This buffer will hold the incoming message
beast::multi_buffer b;
// Read the message into our buffer
ws.read(b, ec);
if(ec)
return fail("read", ec);
// Send a "close" frame to the other end, this is a websocket thing
ws.close(websocket::close_code::normal, ec);
if(ec)
return fail("close", ec);
// The buffers() function helps print a ConstBufferSequence
std::cout << beast::buffers(b.data()) << std::endl;
// WebSocket says that to close a connection you have
// to keep reading messages until you receive a close frame.
// Beast delivers the close frame as an error from read.
//
beast::drain_buffer drain; // Throws everything away efficiently
for(;;)
{
// Keep reading messages...
ws.read(drain, ec);
// ...until we get the special error code
if(ec == websocket::error::closed)
break;
// Some other error occurred, report it and exit.
if(ec)
return fail("close", ec);
}
// If we get here the connection was cleanly closed
return EXIT_SUCCESS;
}
//]

View File

@@ -0,0 +1,13 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/websocket-server-async "/")
add_executable (websocket-server-async
${BEAST_INCLUDES}
websocket_server_async.cpp
)
target_link_libraries(websocket-server-async
Beast
)

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
exe websocket-server-async :
websocket_server_async.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@@ -0,0 +1,463 @@
//
// Copyright (c) 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)
//
#include "../common/helpers.hpp"
#include <beast/core.hpp>
#include <beast/websocket.hpp>
#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
namespace http = beast::http; // from <beast/http.hpp>
namespace websocket = beast::websocket; // from <beast/websocket.hpp>
namespace ip = boost::asio::ip; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio.hpp>
//------------------------------------------------------------------------------
//
// Example: WebSocket echo server, asynchronous
//
//------------------------------------------------------------------------------
/** WebSocket asynchronous echo server
The server holds the listening socket, the io_service, and
the threads calling io_service::run
*/
class server
{
using error_code = beast::error_code; // Saves typing
using clock_type =
std::chrono::steady_clock; // For the timer
using stream_type =
websocket::stream<tcp::socket>; // The type of our websocket stream
std::ostream* log_; // Used for diagnostic output, may be null
boost::asio::io_service ios_; // The io_service, required
tcp::socket sock_; // Holds accepted connections
tcp::endpoint ep_; // The remote endpoint during accept
std::vector<std::thread> thread_; // Threads for the io_service
boost::asio::ip::tcp::acceptor acceptor_; // The listening socket
std::function<void(stream_type&)> mod_; // Called on new stream
boost::optional<
boost::asio::io_service::work> work_; // Keeps io_service::run from returning
//--------------------------------------------------------------------------
class connection : public std::enable_shared_from_this<connection>
{
std::ostream* log_; // Where to log, may be null
tcp::endpoint ep_; // The remote endpoing
stream_type ws_; // The websocket stream
boost::asio::basic_waitable_timer<
clock_type> timer_; // Needed for timeouts
boost::asio::io_service::strand strand_;// Needed when threads > 1
beast::multi_buffer buffer_; // Stores the current message
beast::drain_buffer drain_; // Helps discard data on close
std::size_t id_; // A small unique id
public:
/// Constructor
connection(
server& parent,
tcp::endpoint const& ep,
tcp::socket&& sock)
: log_(parent.log_)
, ep_(ep)
, ws_(std::move(sock))
, timer_(ws_.get_io_service(), (clock_type::time_point::max)())
, strand_(ws_.get_io_service())
, id_([]
{
static std::atomic<std::size_t> n{0};
return ++n;
}())
{
// Invoke the callback for new connections if set.
// This allows the settings on the websocket stream
// to be adjusted. For example to turn compression
// on or off or adjust the read and write buffer sizes.
//
if(parent.mod_)
parent.mod_(ws_);
}
// Called immediately after the connection is created.
// We keep this separate from the constructor because
// shared_from_this may not be called from constructors.
void run()
{
// Run the timer
on_timer({});
// Put the handshake on the timer
timer_.expires_from_now(std::chrono::seconds(15));
// Read the websocket handshake and send the response
ws_.async_accept_ex(
[](websocket::response_type& res)
{
res.insert(http::field::server, "websocket-server-async");
},
strand_.wrap(std::bind(
&connection::on_accept,
shared_from_this(),
std::placeholders::_1)));
}
private:
// Called when the timer expires.
// We operate the timer continuously this simplifies the code.
//
void on_timer(error_code ec)
{
if(ec && ec != boost::asio::error::operation_aborted)
return fail("timer", ec);
// Verify that the timer really expired
// since the deadline may have moved.
//
if(timer_.expires_at() <= clock_type::now())
{
// Closing the socket cancels all outstanding
// operations. They will complete with
// boost::asio::error::operation_aborted
//
ws_.next_layer().close(ec);
return;
}
// Wait on the timer
timer_.async_wait(
strand_.wrap(std::bind(
&connection::on_timer,
shared_from_this(),
std::placeholders::_1)));
}
// Called after the handshake is performed
void on_accept(error_code ec)
{
if(ec)
return fail("accept", ec);
do_read();
}
// Read a message from the websocket stream
void do_read()
{
// Put the read on the timer
timer_.expires_from_now(std::chrono::seconds(15));
// Read a message
ws_.async_read(buffer_,
strand_.wrap(std::bind(
&connection::on_read,
shared_from_this(),
std::placeholders::_1)));
}
// Called after the message read completes
void on_read(error_code ec)
{
// This error means the other side
// closed the websocket stream.
if(ec == websocket::error::closed)
return;
if(ec)
return fail("read", ec);
// Put the write on the timer
timer_.expires_from_now(std::chrono::seconds(15));
// Write the received message back
ws_.binary(ws_.got_binary());
ws_.async_write(buffer_.data(),
strand_.wrap(std::bind(
&connection::on_write,
shared_from_this(),
std::placeholders::_1)));
}
// Called after the message write completes
void on_write(error_code ec)
{
if(ec)
return fail("write", ec);
// Empty out the buffer. This is
// needed if we want to do another read.
//
buffer_.consume(buffer_.size());
// This shows how the server can close the
// connection. Alternatively we could call
// do_read again and the connection would
// stay open until the other side closes it.
//
do_close();
}
// Sends a websocket close frame
void do_close()
{
// Put the close frame on the timer
timer_.expires_from_now(std::chrono::seconds(15));
// Send the close frame
ws_.async_close({},
strand_.wrap(std::bind(
&connection::on_close,
shared_from_this(),
std::placeholders::_1)));
}
// Called when writing the close frame completes
void on_close(error_code ec)
{
if(ec)
return fail("close", ec);
on_drain({});
}
// Read and discard any leftover message data
void on_drain(error_code ec)
{
if(ec == websocket::error::closed)
{
// the connection has been closed gracefully
return;
}
if(ec)
return fail("drain", ec);
// WebSocket says that to close a connection you have
// to keep reading messages until you receive a close frame.
// Beast delivers the close frame as an error from read.
//
ws_.async_read(drain_,
strand_.wrap(std::bind(
&connection::on_drain,
shared_from_this(),
std::placeholders::_1)));
}
// Pretty-print an error to the log
void fail(std::string what, error_code ec)
{
if(log_)
if(ec != boost::asio::error::operation_aborted)
print(*log_, "[#", id_, " ", ep_, "] ", what, ": ", ec.message());
}
};
//--------------------------------------------------------------------------
// Pretty-print an error to the log
void fail(std::string what, error_code ec)
{
if(log_)
print(*log_, what, ": ", ec.message());
}
// Initiates an accept
void do_accept()
{
acceptor_.async_accept(sock_, ep_,
std::bind(&server::on_accept, this,
std::placeholders::_1));
}
// Called when receiving an incoming connection
void on_accept(error_code ec)
{
// This can happen during exit
if(! acceptor_.is_open())
return;
// This can happen during exit
if(ec == boost::asio::error::operation_aborted)
return;
if(ec)
fail("accept", ec);
// Create the connection and run it
std::make_shared<connection>(*this, ep_, std::move(sock_))->run();
// Initiate another accept
do_accept();
}
public:
/** Constructor.
@param log A pointer to a stream to log to, or `nullptr`
to disable logging.
@param threads The number of threads in the io_service.
*/
server(std::ostream* log, std::size_t threads)
: log_(log)
, sock_(ios_)
, acceptor_(ios_)
, work_(ios_)
{
thread_.reserve(threads);
for(std::size_t i = 0; i < threads; ++i)
thread_.emplace_back(
[&]{ ios_.run(); });
}
/// Destructor.
~server()
{
work_ = boost::none;
ios_.dispatch([&]
{
error_code ec;
acceptor_.close(ec);
});
for(auto& t : thread_)
t.join();
}
/// Return the listening endpoint.
tcp::endpoint
local_endpoint() const
{
return acceptor_.local_endpoint();
}
/** Set a handler called for new streams.
This function is called for each new stream.
It is used to set options for every connection.
*/
template<class F>
void
on_new_stream(F const& f)
{
mod_ = f;
}
/** Open a listening port.
@param ep The address and port to bind to.
@param ec Set to the error, if any occurred.
*/
void
open(tcp::endpoint const& ep, error_code& ec)
{
acceptor_.open(ep.protocol(), ec);
if(ec)
return fail("open", ec);
acceptor_.set_option(
boost::asio::socket_base::reuse_address{true});
acceptor_.bind(ep, ec);
if(ec)
return fail("bind", ec);
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
return fail("listen", ec);
do_accept();
}
};
//------------------------------------------------------------------------------
// This helper will apply some settings to a WebSocket
// stream. The server applies it to all new connections.
//
class set_stream_options
{
websocket::permessage_deflate pmd_;
public:
set_stream_options(set_stream_options const&) = default;
explicit
set_stream_options(
websocket::permessage_deflate const& pmd)
: pmd_(pmd)
{
}
template<class NextLayer>
void
operator()(websocket::stream<NextLayer>& ws) const
{
ws.set_option(pmd_);
// Turn off the auto-fragment option.
// This improves Autobahn performance.
//
ws.auto_fragment(false);
// 64MB message size limit.
// The high limit is needed for Autobahn.
ws.read_message_max(64 * 1024 * 1024);
}
};
int main(int argc, char* argv[])
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: " << argv[0] << " <address> <port> <threads>\n"
" For IPv4, try: " << argv[0] << " 0.0.0.0 8080 1\n"
" For IPv6, try: " << argv[0] << " 0::0 8080 1\n"
;
return EXIT_FAILURE;
}
// Decode command line options
auto address = ip::address::from_string(argv[1]);
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
unsigned short threads = static_cast<unsigned short>(std::atoi(argv[3]));
// Allow permessage-deflate
// compression on all connections
websocket::permessage_deflate pmd;
pmd.client_enable = true;
pmd.server_enable = true;
pmd.compLevel = 3;
// Create our server
server s{&std::cout, threads};
s.on_new_stream(set_stream_options{pmd});
// Open the listening port
beast::error_code ec;
s.open(tcp::endpoint{address, port}, ec);
if(ec)
{
std::cerr << "Error: " << ec.message();
return EXIT_FAILURE;
}
// Wait for CTRL+C. After receiving CTRL+C,
// the server should shut down cleanly.
//
sig_wait();
return EXIT_SUCCESS;
}