mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
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:
16
example/CMakeLists.txt
Normal file
16
example/CMakeLists.txt
Normal 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
20
example/Jamfile
Normal 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 ;
|
||||
481
example/common/detect_ssl.hpp
Normal file
481
example/common/detect_ssl.hpp
Normal 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
|
||||
56
example/common/helpers.hpp
Normal file
56
example/common/helpers.hpp
Normal 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
|
||||
46
example/common/mime_types.hpp
Normal file
46
example/common/mime_types.hpp
Normal 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
|
||||
39
example/common/rfc7231.hpp
Normal file
39
example/common/rfc7231.hpp
Normal 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
|
||||
118
example/common/root_certificates.hpp
Normal file
118
example/common/root_certificates.hpp
Normal 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
|
||||
334
example/common/ssl_stream.hpp
Normal file
334
example/common/ssl_stream.hpp
Normal 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
|
||||
228
example/common/write_msg.hpp
Normal file
228
example/common/write_msg.hpp
Normal 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
|
||||
1060
example/doc/http_examples.hpp
Normal file
1060
example/doc/http_examples.hpp
Normal file
File diff suppressed because it is too large
Load Diff
12
example/echo-op/CMakeLists.txt
Normal file
12
example/echo-op/CMakeLists.txt
Normal 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
13
example/echo-op/Jamfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe echo-op :
|
||||
echo_op.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
||||
356
example/echo-op/echo_op.cpp
Normal file
356
example/echo-op/echo_op.cpp
Normal 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;
|
||||
}
|
||||
16
example/http-client-ssl/CMakeLists.txt
Normal file
16
example/http-client-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
||||
51
example/http-client-ssl/Jamfile
Normal file
51
example/http-client-ssl/Jamfile
Normal 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
|
||||
;
|
||||
104
example/http-client-ssl/http_client_ssl.cpp
Normal file
104
example/http-client-ssl/http_client_ssl.cpp
Normal 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;
|
||||
}
|
||||
13
example/http-client/CMakeLists.txt
Normal file
13
example/http-client/CMakeLists.txt
Normal 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)
|
||||
13
example/http-client/Jamfile
Normal file
13
example/http-client/Jamfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe http-client :
|
||||
http_client.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
||||
88
example/http-client/http_client.cpp
Normal file
88
example/http-client/http_client.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//]
|
||||
15
example/http-crawl/CMakeLists.txt
Normal file
15
example/http-crawl/CMakeLists.txt
Normal 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)
|
||||
14
example/http-crawl/Jamfile
Normal file
14
example/http-crawl/Jamfile
Normal 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
|
||||
;
|
||||
137
example/http-crawl/http_crawl.cpp
Normal file
137
example/http-crawl/http_crawl.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
10019
example/http-crawl/urls_large_data.cpp
Normal file
10019
example/http-crawl/urls_large_data.cpp
Normal file
File diff suppressed because it is too large
Load Diff
16
example/http-crawl/urls_large_data.hpp
Normal file
16
example/http-crawl/urls_large_data.hpp
Normal 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
|
||||
18
example/http-server-fast/CMakeLists.txt
Normal file
18
example/http-server-fast/CMakeLists.txt
Normal 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}
|
||||
)
|
||||
|
||||
13
example/http-server-fast/Jamfile
Normal file
13
example/http-server-fast/Jamfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe http-server-fast :
|
||||
http_server_fast.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
||||
195
example/http-server-fast/fields_alloc.hpp
Normal file
195
example/http-server-fast/fields_alloc.hpp
Normal 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
|
||||
310
example/http-server-fast/http_server_fast.cpp
Normal file
310
example/http-server-fast/http_server_fast.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
15
example/http-server-small/CMakeLists.txt
Normal file
15
example/http-server-small/CMakeLists.txt
Normal 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
|
||||
)
|
||||
|
||||
13
example/http-server-small/Jamfile
Normal file
13
example/http-server-small/Jamfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe http-server-small :
|
||||
http_server_small.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
||||
240
example/http-server-small/http_server_small.cpp
Normal file
240
example/http-server-small/http_server_small.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
17
example/http-server-threaded/CMakeLists.txt
Normal file
17
example/http-server-threaded/CMakeLists.txt
Normal 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}
|
||||
)
|
||||
|
||||
13
example/http-server-threaded/Jamfile
Normal file
13
example/http-server-threaded/Jamfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe http-server-threaded :
|
||||
http_server_threaded.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
||||
227
example/http-server-threaded/http_server_threaded.cpp
Normal file
227
example/http-server-threaded/http_server_threaded.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
27
example/server-framework/CMakeLists.txt
Normal file
27
example/server-framework/CMakeLists.txt
Normal 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()
|
||||
13
example/server-framework/Jamfile
Normal file
13
example/server-framework/Jamfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe server-framework :
|
||||
main.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
||||
159
example/server-framework/README.md
Normal file
159
example/server-framework/README.md
Normal 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);
|
||||
|
||||
```
|
||||
279
example/server-framework/file_service.hpp
Normal file
279
example/server-framework/file_service.hpp
Normal 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
|
||||
53
example/server-framework/framework.hpp
Normal file
53
example/server-framework/framework.hpp
Normal 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
|
||||
653
example/server-framework/http_async_port.hpp
Normal file
653
example/server-framework/http_async_port.hpp
Normal 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
|
||||
77
example/server-framework/http_base.hpp
Normal file
77
example/server-framework/http_base.hpp
Normal 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
|
||||
477
example/server-framework/http_sync_port.hpp
Normal file
477
example/server-framework/http_sync_port.hpp
Normal 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
|
||||
426
example/server-framework/https_ports.hpp
Normal file
426
example/server-framework/https_ports.hpp
Normal 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
|
||||
446
example/server-framework/main.cpp
Normal file
446
example/server-framework/main.cpp
Normal 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();
|
||||
}
|
||||
397
example/server-framework/multi_port.hpp
Normal file
397
example/server-framework/multi_port.hpp
Normal 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
|
||||
266
example/server-framework/server.hpp
Normal file
266
example/server-framework/server.hpp
Normal 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
|
||||
192
example/server-framework/service_list.hpp
Normal file
192
example/server-framework/service_list.hpp
Normal 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
|
||||
146
example/server-framework/ssl_certificate.hpp
Normal file
146
example/server-framework/ssl_certificate.hpp
Normal 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
|
||||
374
example/server-framework/ws_async_port.hpp
Normal file
374
example/server-framework/ws_async_port.hpp
Normal 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
|
||||
432
example/server-framework/ws_sync_port.hpp
Normal file
432
example/server-framework/ws_sync_port.hpp
Normal 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
|
||||
101
example/server-framework/ws_upgrade_service.hpp
Normal file
101
example/server-framework/ws_upgrade_service.hpp
Normal 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
|
||||
438
example/server-framework/wss_ports.hpp
Normal file
438
example/server-framework/wss_ports.hpp
Normal 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
|
||||
15
example/websocket-client-ssl/CMakeLists.txt
Normal file
15
example/websocket-client-ssl/CMakeLists.txt
Normal 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}
|
||||
)
|
||||
51
example/websocket-client-ssl/Jamfile
Normal file
51
example/websocket-client-ssl/Jamfile
Normal 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
|
||||
;
|
||||
119
example/websocket-client-ssl/websocket_client_ssl.cpp
Normal file
119
example/websocket-client-ssl/websocket_client_ssl.cpp
Normal 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;
|
||||
}
|
||||
13
example/websocket-client/CMakeLists.txt
Normal file
13
example/websocket-client/CMakeLists.txt
Normal 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)
|
||||
13
example/websocket-client/Jamfile
Normal file
13
example/websocket-client/Jamfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe websocket-client :
|
||||
websocket_client.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
||||
101
example/websocket-client/websocket_client.cpp
Normal file
101
example/websocket-client/websocket_client.cpp
Normal 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;
|
||||
}
|
||||
|
||||
//]
|
||||
13
example/websocket-server-async/CMakeLists.txt
Normal file
13
example/websocket-server-async/CMakeLists.txt
Normal 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
|
||||
)
|
||||
13
example/websocket-server-async/Jamfile
Normal file
13
example/websocket-server-async/Jamfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#
|
||||
# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe websocket-server-async :
|
||||
websocket_server_async.cpp
|
||||
:
|
||||
<variant>coverage:<build>no
|
||||
<variant>ubasan:<build>no
|
||||
;
|
||||
463
example/websocket-server-async/websocket_server_async.cpp
Normal file
463
example/websocket-server-async/websocket_server_async.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user